From 084ec6122cf791882103c7dd721a8c7b8aee7866 Mon Sep 17 00:00:00 2001 From: Mateusz Baran Date: Mon, 23 Sep 2024 16:31:48 +0200 Subject: [PATCH] Cubic Hermite interpolation (#5) * Cubic Hermite interpolation * add CondaPkg * we don't need the package right now? * add IJulia * fix? * try this? * Update docs/make.jl Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * add formatting options * formatting * adapt make.jl to make sure IJulia is compiled/updated upfront * updates * trying stuff * maybe move that file? * fixes --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ronny Bergmann --- .JuliaFormatter.toml | 11 +++ .github/workflows/documenter.yml | 2 +- .gitignore | 3 + CondaPkg.toml | 5 ++ Project.toml | 7 ++ docs/Project.toml | 7 ++ docs/build/references.bib | 15 ++++ docs/make.jl | 39 ++++++++++- docs/{ => src}/index.md | 0 docs/src/references.bib | 15 ++++ docs/src/references.md | 4 ++ examples/.gitignore | 1 + examples/Project.toml | 11 +++ examples/_quarto.yml | 4 +- examples/hermite.qmd | 115 +++++++++++++++++++++++++++++++ test/runtests.jl | 4 +- 16 files changed, 234 insertions(+), 9 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 CondaPkg.toml create mode 100644 docs/build/references.bib rename docs/{ => src}/index.md (100%) create mode 100644 docs/src/references.bib create mode 100644 docs/src/references.md create mode 100644 examples/.gitignore create mode 100644 examples/hermite.qmd diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..80d88b8 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,11 @@ +short_to_long_function_def = true +always_for_in = true +whitespace_ops_in_indices = true +pipe_to_function_call = true +import_to_using = true +always_use_return = true +whitespace_in_kwargs = false +remove_extra_newlines = true +annotate_untyped_fields_with_any = false +conditional_to_if = false +ignore = ["tutorials"] \ No newline at end of file diff --git a/.github/workflows/documenter.yml b/.github/workflows/documenter.yml index 05f9e6f..83a7f53 100644 --- a/.github/workflows/documenter.yml +++ b/.github/workflows/documenter.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: quarto-dev/quarto-actions/setup@v2 with: - version: "1.3.353" + version: "1.5.56" - uses: julia-actions/setup-julia@latest with: version: "1.10" diff --git a/.gitignore b/.gitignore index db41f4a..6716258 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ docs/Manifest.toml examples/Manifest.toml +examples/.CondaPkg/ +examples/_freeze/ +docs/.CondaPkg diff --git a/CondaPkg.toml b/CondaPkg.toml new file mode 100644 index 0000000..dd1a7ed --- /dev/null +++ b/CondaPkg.toml @@ -0,0 +1,5 @@ +[deps] +jupyter = "" +python = "3.11" +matplotlib = ">=3.4" +matplotlib-inline = "" diff --git a/Project.toml b/Project.toml index 379d600..7396dff 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,13 @@ version = "0.1.0" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +ManifoldDiff = "af67fdf4-a580-4b9f-bbec-742ef357defd" +Manifolds = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e" + +[compat] +ManifoldDiff = "0.3" +Manifolds = "0.10" +julia = "1.6" [targets] test = ["Test"] diff --git a/docs/Project.toml b/docs/Project.toml index 5694569..d9cc6b4 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,9 +1,16 @@ [deps] +CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterCitations = "daee34ce-89f3-4625-b898-19384cb65244" DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +ManifoldDiff = "af67fdf4-a580-4b9f-bbec-742ef357defd" +ManifoldExamples = "21be47e3-92bf-4199-8515-27870869dcc6" +Manifolds = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" [compat] +CondaPkg = "0.2" Documenter = "1" DocumenterCitations = "1" DocumenterInterLinks = "0.3" +Plots = "1" diff --git a/docs/build/references.bib b/docs/build/references.bib new file mode 100644 index 0000000..ddf4587 --- /dev/null +++ b/docs/build/references.bib @@ -0,0 +1,15 @@ + +@article{Zimmermann:2020, + title = {Hermite {Interpolation} and {Data} {Processing} {Errors} on {Riemannian} {Matrix} {Manifolds}}, + volume = {42}, + issn = {1064-8275}, + url = {https://epubs.siam.org/doi/10.1137/19M1282878}, + doi = {10.1137/19M1282878}, + number = {5}, + journal = {SIAM Journal on Scientific Computing}, + author = {Zimmermann, Ralf}, + month = jan, + year = {2020}, + note = {Publisher: Society for Industrial and Applied Mathematics}, + pages = {A2593--A2619}, +} diff --git a/docs/make.jl b/docs/make.jl index 21c0012..f78a600 100755 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,6 +2,29 @@ # # +if "--help" ∈ ARGS + println( + """ +docs/make.jl + +Render the `Manopt.jl` documentation with optional arguments + +Arguments +* `--help` - print this help and exit without rendering the documentation +* `--prettyurls` – toggle the prettyurls part to true (which is otherwise only true on CI) +* `--quarto` – run the Quarto notebooks from the `tutorials/` folder before generating the documentation + this has to be run locally at least once for the `tutorials/*.md` files to exist that are included in + the documentation (see `--exclude-examples`) for the alternative. + If they are generated once they are cached accordingly. + Then you can spare time in the rendering by not passing this argument. + If quarto is not run, some tutorials are generated as empty files, since they + are referenced from within the documentation. These are currently + `Optimize.md` and `ImplementOwnManifold.md`. +""", + ) + exit(0) +end + # # (a) if docs is not the current active environment, switch to it # (from https://github.com/JuliaIO/HDF5.jl/pull/1020/)  @@ -13,7 +36,7 @@ if Base.active_project() != joinpath(@__DIR__, "Project.toml") Pkg.instantiate() end -# (b) Did someone say render? Then we render! +# (b) Did someone say render? if "--quarto" ∈ ARGS using CondaPkg CondaPkg.withenv() do @@ -21,16 +44,20 @@ if "--quarto" ∈ ARGS examples_folder = (@__DIR__) * "/../examples" # instantiate the tutorials environment if necessary Pkg.activate(examples_folder) + # For a breaking release -> also set the tutorials folder to the most recent version + Pkg.develop(PackageSpec(; path=(@__DIR__) * "/../")) Pkg.resolve() Pkg.instantiate() - Pkg.build("IJulia") # build IJulia to the right version. + Pkg.build("IJulia") # build `IJulia` to the right version. Pkg.activate(@__DIR__) # but return to the docs one before - run(`quarto render $(examples_folder)`) + return run(`quarto render $(examples_folder)`) end end # (c) load necessary packages for the docs using Documenter: DocMeta, HTML, MathJax3, deploydocs, makedocs +using DocumenterCitations + using ManifoldExamples generated_path = joinpath(@__DIR__, "src") @@ -52,13 +79,19 @@ open(joinpath(generated_path, "contributing.md"), "w") do io end end +examples_menu = "Examples" => ["Cubic Hermite interpolation" => "examples/hermite.md"] + +bib = CitationBibliography(joinpath(@__DIR__, "src", "references.bib"); style=:alpha) makedocs(; format=HTML(; mathengine=MathJax3(), prettyurls=get(ENV, "CI", nothing) == "true"), sitename="ManifoldExamples.jl", modules=[ManifoldExamples], pages=[ "Home" => "index.md", + examples_menu, "Contributing to ManifoldExamples.jl" => "contributing.md", + "References" => "references.md", ], + plugins=[bib], ) deploydocs(; repo="github.com/JuliaManifolds/ManifoldExamples.jl", push_preview=true) diff --git a/docs/index.md b/docs/src/index.md similarity index 100% rename from docs/index.md rename to docs/src/index.md diff --git a/docs/src/references.bib b/docs/src/references.bib new file mode 100644 index 0000000..ddf4587 --- /dev/null +++ b/docs/src/references.bib @@ -0,0 +1,15 @@ + +@article{Zimmermann:2020, + title = {Hermite {Interpolation} and {Data} {Processing} {Errors} on {Riemannian} {Matrix} {Manifolds}}, + volume = {42}, + issn = {1064-8275}, + url = {https://epubs.siam.org/doi/10.1137/19M1282878}, + doi = {10.1137/19M1282878}, + number = {5}, + journal = {SIAM Journal on Scientific Computing}, + author = {Zimmermann, Ralf}, + month = jan, + year = {2020}, + note = {Publisher: Society for Industrial and Applied Mathematics}, + pages = {A2593--A2619}, +} diff --git a/docs/src/references.md b/docs/src/references.md new file mode 100644 index 0000000..735ccce --- /dev/null +++ b/docs/src/references.md @@ -0,0 +1,4 @@ +# Literature + +```@bibliography +``` diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..075b254 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +/.quarto/ diff --git a/examples/Project.toml b/examples/Project.toml index 5aa1523..fd291be 100644 --- a/examples/Project.toml +++ b/examples/Project.toml @@ -1,2 +1,13 @@ [deps] +IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" +ManifoldDiff = "af67fdf4-a580-4b9f-bbec-742ef357defd" ManifoldExamples = "21be47e3-92bf-4199-8515-27870869dcc6" +Manifolds = "1cead3c2-87b3-11e9-0ccd-23c62b72b94e" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +PythonPlot = "274fc56d-3b97-40fa-a1cd-1b4a50311bf9" +RecursiveArrayTools = "731186ca-8d62-57ce-b412-fbd966d074cd" + +[compat] +IJulia = "1" +Plots = "1" +PythonPlot = "1" diff --git a/examples/_quarto.yml b/examples/_quarto.yml index 44bcc68..bbe1b67 100644 --- a/examples/_quarto.yml +++ b/examples/_quarto.yml @@ -22,7 +22,7 @@ execute: format: commonmark: - variant: -raw_html+tex_math_dollars + variant: -raw_html+tex_math_dollars+pipe_tables+footnotes wrap: preserve -jupyter: "julia-1.10" +jupyter: julia-1.10 diff --git a/examples/hermite.qmd b/examples/hermite.qmd new file mode 100644 index 0000000..c4efbb3 --- /dev/null +++ b/examples/hermite.qmd @@ -0,0 +1,115 @@ +--- +title: "Cubic Hermite interpolation on manifolds" +author: "Mateusz Baran" +date: 05/09/2024 +--- + +## Introduction + +This example shows how to perform cubic Hermite interpolation on Riemannian manifolds. The idea is described in [Zimmermann:2020](@cite). + +First, let's import necessary libraries. + +```{julia} +using Manifolds, ManifoldDiff, Plots +using ManifoldDiff: differential_log_argument +pythonplot() + +``` + +The main interpolation function directly follows Eq (2.5) from [Zimmermann:2020](@cite). + +```{julia} + +function manifold_hermite_interpolation( + M::AbstractManifold, + p, + q, + Xp, + Xq, + t::Real, +) + Y_qp = log(M, q, p) + Xp_to_q = differential_log_argument(M, q, p, Xp) + a0t = 2*t^3 - 3*t^2 + 1 # simplified (A.1) + b0t = t^3 - 2*t^2 + t # simplified (A.3) + b1t = t^3 - t^2 # simplified (A.4) + return exp(M, q, a0t .* Y_qp .+ b0t .* Xp_to_q .+ b1t .* Xq) +end +``` + +We can now plot the interpolating line between two points on a sphere ($p$ and $q$) with tangent vectors $X_p$ and $X_q$. The curve $c\colon [0, 1] \to \mathbb{S}^2$ defined by `manifold_hermite_interpolation` now has the following interpolation properties: + +1. $c(0) = p$ +2. $c(1) = q$ +3. $\dot{c}(0) = X_p$ +4. $\dot{c}(1) = X_q$ +5. In the Euclidean case, the `manifold_hermite_interpolation` conicides with cubic Hermite interpolation. + +```{julia} + +M = Sphere(2) +p = [0.8266841314682074, 0.3288540904434144, 0.45656142410117206] +Xp = [0.15493539779687937, 0.5824002702016382, -0.7000314284584177] + +q = [0.0, 1.0, 0.0] +Xq = [-2, 0.0, 5] + +scene = plot(M, [p, q]; wireframe_color=colorant"#CCCCCC", markersize=10, camera=(140.0, 10.0)) +plot!(scene, M, [p, q], [Xp, Xq]; wireframe = false, linewidth=1.5) + +interp_line = [manifold_hermite_interpolation(M, p, q, Xp, Xq, t) for t in 0.0:0.01:1.0] +plot!(scene, M, interp_line; wireframe = false, linewidth=1.5) + +``` + +Now, let's add interpolating curve with reversed start and end, and reflected tangent vectors. +Note that for, contrary to the Eulidean case, the order of points does matter for cubic interpolation on a sphere. + +```{julia} + +scene = plot(M, [p, q]; wireframe_color=colorant"#CCCCCC", markersize=10, camera=(140.0, 10.0)) +plot!(scene, M, [p, q], [Xp, Xq]; wireframe = false, linewidth=1.5) + +plot!(scene, M, interp_line; wireframe = false, linewidth=1.5) +interp_line2 = [manifold_hermite_interpolation(M, q, p, -Xq, -Xp, t) for t in 0.0:0.01:1.0] +plot!(scene, M, interp_line2; wireframe = false, linewidth=1.5) + +``` + +One possible way of making an interpolation method that is independent of the order of points in computing the average between these two interplations (see `manifold_hermite_interpolation_symmetric` below). + + +```{julia} + +function manifold_hermite_interpolation_symmetric( + M::AbstractManifold, + p, + q, + Xp, + Xq, + t::Real, +) + r_pq = manifold_hermite_interpolation(M, p, q, Xp, Xq, t) + r_qp = manifold_hermite_interpolation(M, q, p, -Xq, -Xp, 1-t) + return mid_point(M, r_pq, r_qp) +end +scene = plot(M, [p, q]; wireframe_color=colorant"#CCCCCC", markersize=10, camera=(140.0, 10.0)) +plot!(scene, M, [p, q], [Xp, Xq]; wireframe = false, linewidth=1.5) + +plot!(scene, M, interp_line; wireframe = false, linewidth=1.5) +plot!(scene, M, interp_line2; wireframe = false, linewidth=1.5) + +interp_line3 = [manifold_hermite_interpolation_symmetric(M, q, p, -Xq, -Xp, t) for t in 0.0:0.01:1.0] +plot!(scene, M, interp_line3; wireframe = false, linewidth=1.5) + +``` + +## Literature + +````{=commonmark} +```@bibliography +Pages = ["hermite.md"] +Canonical=false +``` +```` diff --git a/test/runtests.jl b/test/runtests.jl index 5011428..3816eb6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,3 @@ using ManifoldExamples, Test -@testset "Manifold Examples" begin - -end +@testset "Manifold Examples" begin end