Mercurial > repos > public > sbplib_julia
changeset 2001:f45d32022e4f feature/grids/manifolds
Merge feature/grids/parameter_spaces/in
author | Jonatan Werpers <jonatan@werpers.com> |
---|---|
date | Tue, 29 Apr 2025 08:59:29 +0200 |
parents | a1b2453c02c9 (diff) 889c18ad56bf (current diff) |
children | 4300c59bbeff e48577b5e707 |
files | |
diffstat | 10 files changed, 523 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/Manifest.toml Tue Apr 29 08:48:58 2025 +0200 +++ b/Manifest.toml Tue Apr 29 08:59:29 2025 +0200 @@ -2,7 +2,7 @@ julia_version = "1.11.3" manifest_format = "2.0" -project_hash = "383b10bc09a08d519764abf167e40dae29b16b2e" +project_hash = "51374910f6b7831392791bcca0079860f1023f47" [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
--- a/Project.toml Tue Apr 29 08:48:58 2025 +0200 +++ b/Project.toml Tue Apr 29 08:59:29 2025 +0200 @@ -9,6 +9,7 @@ TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" [weakdeps] +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" SparseArrayKit = "a9a3c162-d163-4c15-8926-b8794fbefed2" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" @@ -16,6 +17,7 @@ [extensions] DiffinitiveMakieExt = "Makie" +DiffinitivePlotsExt = "Plots" DiffinitiveSparseArrayKitExt = ["SparseArrayKit", "Tokens"] DiffinitiveSparseArraysExt = ["SparseArrays", "Tokens"] @@ -24,6 +26,7 @@ StaticArrays = "1.0" TOML = "1.0" Makie = "0.21" +Plots = "1.40" SparseArrayKit = "0.4" Tokens = "0.1.1" SparseArrays = "1.10"
--- a/docs/make.jl Tue Apr 29 08:48:58 2025 +0200 +++ b/docs/make.jl Tue Apr 29 08:59:29 2025 +0200 @@ -30,6 +30,7 @@ "operator_file_format.md", "grids_and_grid_functions.md", "matrix_and_tensor_representations.md", + "manifolds_charts_atlases.md", "Submodules" => [ "submodules/grids.md", "submodules/lazy_tensors.md",
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/src/manifolds_charts_atlases.md Tue Apr 29 08:59:29 2025 +0200 @@ -0,0 +1,41 @@ +# Manifolds, Charts, and Atlases + +To construct grids on more complicated geometries we use manifolds described +by one or more charts. The charts describe a mapping from a logical parameter +space to the geometry that we are interested in. If there are more than one +chart for a given geometry this collection of charts and how they are +connected is described by an atlas. + +We consider a mapping from the logical coordidinates ``\xi \in \Xi`` to the +physical coordinates ``x \in \Omega``. A `Chart` describes the mapping by a +`ParameterSpace` respresenting ``\Xi`` and some mapping object that takes +arguments ``\xi \in \Xi`` and returns coordinates ``x\in\Omega``. The mapping +object can either be a function or some other callable object. + +For the construction of differential and difference operators on a manifold +with a chart the library needs to know the Jacobian, +``\frac{\partial x}{\partial \xi}``, of the mapping as a function of +coordinates in the logical parameter space. Internally, Diffinitive.jl uses a +local Jacobian function, `Grids.jacobian(f, ξ)`. For geometry objects provided +by the library this function should have fast and efficient implementations. +If you are creating your own mapping functions you must implement +`Grids.jacobian` for your function or type, for example + +```julia +f(x) = 2x +Grids.jacobian(::typeof(f), x) = fill(2, length(x)) +``` + +```julia +struct F end +(::F)(x) = 2x +Grids.jacobian(::F, x) = fill(2,length(x)) +``` + +You can also provide a fallback function using one of the many automatic +differentiation packages, for example + +```julia +using ForwardDiff +Grids.jacobian(f,x) = ForwardDiff.jacobian(f,x) +```
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ext/DiffinitivePlotsExt.jl Tue Apr 29 08:59:29 2025 +0200 @@ -0,0 +1,60 @@ +module DiffinitivePlotsExt + +using Diffinitive.Grids +using Plots + +@recipe f(::Type{<:Grid}, g::Grid) = map(Tuple,g)[:] + +@recipe function f(c::Chart{2,<:Rectangle}, n=5, m=n; draw_border=true, bordercolor=1) + Ξ = parameterspace(c) + ξs = range(limits(Ξ,1)..., n) + ηs = range(limits(Ξ,2)..., m) + + label := false + seriescolor --> 2 + for ξ ∈ ξs + @series adapted_curve_grid(η->c((ξ,η)),limits(Ξ,1)) + end + + for η ∈ ηs + @series adapted_curve_grid(ξ->c((ξ,η)),limits(Ξ,2)) + end + + if ~draw_border + return + end + + for ξ ∈ limits(Ξ,1) + @series begin + linewidth --> 3 + seriescolor := bordercolor + adapted_curve_grid(η->c((ξ,η)),limits(Ξ,1)) + end + end + + for η ∈ limits(Ξ,2) + @series begin + linewidth --> 3 + seriescolor := bordercolor + adapted_curve_grid(ξ->c((ξ,η)),limits(Ξ,2)) + end + end +end + +function adapted_curve_grid(g, minmax) + t1, _ = PlotUtils.adapted_grid(t->g(t)[1], minmax) + t2, _ = PlotUtils.adapted_grid(t->g(t)[2], minmax) + + ts = sort(vcat(t1,t2)) + + x = map(ts) do t + g(t)[1] + end + y = map(ts) do t + g(t)[2] + end + + return x, y +end + +end
--- a/src/Grids/Grids.jl Tue Apr 29 08:48:58 2025 +0200 +++ b/src/Grids/Grids.jl Tue Apr 29 08:59:29 2025 +0200 @@ -24,6 +24,16 @@ export unittetrahedron export unitsimplex +export Chart + +export Atlas +export charts +export connections +export CartesianAtlas +export UnstructuredAtlas + +export parameterspace + # Grid export Grid export coordinate_size @@ -66,10 +76,24 @@ include("parameter_space.jl") include("grid.jl") +include("multiblockgrids.jl") +include("manifolds.jl") include("tensor_grid.jl") include("equidistant_grid.jl") include("zero_dim_grid.jl") include("mapped_grid.jl") -include("multiblockgrids.jl") + +function __init__() + if !isdefined(Base.Experimental, :register_error_hint) + return + end + + Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs + if exc.f == Grids.jacobian + print(io, "\nThis possibly means that a function used to define a coordinate mapping is missing a method for `Grids.jacobian`.\n") + print(io, "Provide one by for exmple implementing `Grids.jacobian(::$(typeof(exc.args[1])), x) = ...` or `Grids.jacobian(f, x) = ForwardDiff.jacobian(f,x)`") + end + end +end end # module
--- a/src/Grids/equidistant_grid.jl Tue Apr 29 08:48:58 2025 +0200 +++ b/src/Grids/equidistant_grid.jl Tue Apr 29 08:59:29 2025 +0200 @@ -154,6 +154,10 @@ equidistant_grid(d::Interval, size::Int) = equidistant_grid(limits(d)..., size) equidistant_grid(hb::HyperBox, dims::Vararg{Int}) = equidistant_grid(limits(hb)..., dims...) +function equidistant_grid(c::Chart, dims::Vararg{Int}) + mapped_grid(c, ξ->jacobian(c,ξ), parameterspace(c), dims...) +end + CartesianBoundary{D,BID} = TensorGridBoundary{D,BID} # TBD: What should we do about the naming of this boundary?
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/Grids/manifolds.jl Tue Apr 29 08:59:29 2025 +0200 @@ -0,0 +1,162 @@ +""" + Chart{D} + +A parametrized description of a manifold or part of a manifold. +""" +struct Chart{D, PST<:ParameterSpace{D}, MT} + mapping::MT + parameterspace::PST +end + +Base.ndims(::Chart{D}) where D = D +parameterspace(c::Chart) = c.parameterspace + +function (c::Chart)(ξ) + if ξ ∉ parameterspace(c) + throw(DomainError(ξ, "chart was called logical coordinates outside the parameterspace. If this was inteded, use the `mapping` field from the Chart struct instead.")) + end + return c.mapping(ξ) +end + +""" + jacobian(c::Chart, ξ) + +The jacobian of the mapping evaluated at `ξ`. This defers to the +implementation of `jacobian` for the mapping itself. If no implementation is +available one can easily be specified for either the mapping function or the +chart itself. +```julia +c = Chart(f, ps) +jacobian(f::typeof(f), ξ) = f′(ξ) +``` +or +```julia +c = Chart(f, ps) +jacobian(c::typeof(c),ξ) = f′(ξ) +``` +which will both allow calling `jacobian(c,ξ)`. +""" +function jacobian(c::Chart, ξ) + if ξ ∉ parameterspace(c) + throw(DomainError(ξ, "jacobian was called with logical coordinates outside the parameterspace of the chart. If this was inteded, use the `mapping` field from the Chart struct instead.")) + end + return jacobian(c.mapping, ξ) +end + +boundary_identifiers(c::Chart) = boundary_identifiers(parameterspace(c)) + + +""" + Atlas + +A collection of charts and their connections. +Should implement methods for `charts` and `connections`. +""" +abstract type Atlas end + +""" + charts(::Atlas) + +The colloction of charts in the atlas. +""" +function charts end + +""" + connections(::Atlas) + +Collection of 2-tuples of multiblock boundary identifiers. +""" +function connections end + + +""" + CartesianAtlas{D,C<:Chart,AT<:AbstractArray{C,D}} <: Atlas + +An atlas where the charts are arranged and connected like an array. +""" +struct CartesianAtlas{D,C<:Chart,AT<:AbstractArray{C,D}} <: Atlas + charts::AT +end + +charts(a::CartesianAtlas) = a.charts + +function connections(a::CartesianAtlas) + c = Tuple{MultiBlockBoundary, MultiBlockBoundary}[] + + for d ∈ 1:ndims(charts(a)) + Is = eachslice(CartesianIndices(charts(a)); dims=d) + for i ∈ 1:length(Is)-1 # For each interface between slices + for jk ∈ eachindex(Is[i]) # For each block in slice + Iᵢⱼₖ = Tuple(Is[i][jk]) + Iᵢ₊₁ⱼₖ = Tuple(Is[i+1][jk]) + push!(c, + ( + MultiBlockBoundary{Iᵢⱼₖ, CartesianBoundary{d,UpperBoundary}}(), + MultiBlockBoundary{Iᵢ₊₁ⱼₖ, CartesianBoundary{d,LowerBoundary}}(), + ) + ) + end + end + end + + return c +end + +""" + boundary_identifiers(a::CartesianAtlas) + +All non-connected boundaries of the charts of `a`. +""" +function boundary_identifiers(a::CartesianAtlas) + bs = MultiBlockBoundary[] + + for d ∈ 1:ndims(charts(a)) + Is = eachslice(CartesianIndices(charts(a)); dims=d) + + for (i,b) ∈ ((1,LowerBoundary),(length(Is),UpperBoundary)) # For first and last slice + for jk ∈ eachindex(Is[i]) # For each block in slice + Iᵢⱼₖ = Tuple(Is[i][jk]) + push!(bs, + MultiBlockBoundary{Iᵢⱼₖ, CartesianBoundary{d,b}}(), + ) + end + end + end + + return bs +end + + +""" + UnstructuredAtlas{C<:Chart, CN<:Tuple{MultiBlockBoundary,MultiBlockBoundary}, ...} <: Atlas + +An atlas with connections determined by a vector `MultiBlockBoundary` pairs. +""" +struct UnstructuredAtlas{C<:Chart, CN<:Tuple{MultiBlockBoundary,MultiBlockBoundary}, CV<:AbstractVector{C}, CNV<:AbstractVector{CN}} <: Atlas + charts::CV + connections::CNV +end + +charts(a::UnstructuredAtlas) = a.charts +connections(a::UnstructuredAtlas) = a.connections + +""" + boundary_identifiers(a::UnstructuredAtlas) + +All non-connected boundaries of the charts of `a`. +""" +function boundary_identifiers(a::UnstructuredAtlas) + bs = MultiBlockBoundary[] + + for (i,c) ∈ enumerate(charts(a)) + for b ∈ boundary_identifiers(c) + mbb = MultiBlockBoundary{i,typeof(b)}() + + if !any(cn->mbb∈cn, connections(a)) + push!(bs, mbb) + end + end + end + + return bs +end
--- a/test/Grids/equidistant_grid_test.jl Tue Apr 29 08:48:58 2025 +0200 +++ b/test/Grids/equidistant_grid_test.jl Tue Apr 29 08:59:29 2025 +0200 @@ -163,6 +163,15 @@ @test equidistant_grid(unitinterval(),3) == equidistant_grid(0,1,3) @test equidistant_grid(HyperBox((0,),(2,)),4) == equidistant_grid(@SVector[0], @SVector[2], 4) end + + @testset "equidistant_grid(::Chart)" begin + c = Chart(unitsquare()) do (ξ,η) + @SVector[2ξ, 3η] + end + Grids.jacobian(c::typeof(c), ξ̄) = @SMatrix[2 0; 0 3] + + @test equidistant_grid(c, 5, 4) isa Grid + end end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/Grids/manifolds_test.jl Tue Apr 29 08:59:29 2025 +0200 @@ -0,0 +1,217 @@ +using Test + +using Diffinitive.Grids +using Diffinitive.RegionIndices +using Diffinitive.LazyTensors + +using StaticArrays + +west = CartesianBoundary{1,LowerBoundary} +east = CartesianBoundary{1,UpperBoundary} +south = CartesianBoundary{2,LowerBoundary} +north = CartesianBoundary{2,UpperBoundary} +bottom = CartesianBoundary{3, LowerBoundary} +top = CartesianBoundary{3, UpperBoundary} + +@testset "Chart" begin + X(ξ) = 2ξ + Grids.jacobian(::typeof(X), ξ) = @SMatrix[2 0; 0 2] + c = Chart(X, unitsquare()) + @test c isa Chart{2} + @test parameterspace(c) == unitsquare() + @test ndims(c) == 2 + + @testset "Calling" begin + c = Chart(X, unitsquare()) + @test c([.3,.2]) == [.6,.4] + @test_throws DomainError c([3,2]) + end + + @testset "jacobian(::Chart, ⋅)" begin + c = Chart(X, unitsquare()) + @test_throws DomainError jacobian(c, [3,2]) + @test jacobian(c, [.3,.2]) == [2 0; 0 2] + end + + @test Set(boundary_identifiers(Chart(X,unitsquare()))) == Set([east(),west(),south(),north()]) +end + +@testset "CartesianAtlas" begin + @testset "Constructors" begin + c = Chart(identity, unitsquare()) + @test CartesianAtlas([c c; c c]) isa Atlas + + c2 = Chart(x->2x, unitsquare()) + @test CartesianAtlas([c c2; c2 c]) isa CartesianAtlas + @test CartesianAtlas(@SMatrix[c c; c c]) isa CartesianAtlas + @test CartesianAtlas(@SMatrix[c c2; c2 c]) isa CartesianAtlas + end + + @testset "Getters" begin + c = Chart(identity, unitsquare()) + a = CartesianAtlas([c c; c c]) + @test charts(a) == [c c; c c] + end + + @testset "connections" begin + # 2D + a = CartesianAtlas(fill(Chart(identity, unitsquare()), 2,3)) + + @test Set(connections(a)) == Set([ + (MultiBlockBoundary{(1,1), east}(), MultiBlockBoundary{(2,1), west}()), + (MultiBlockBoundary{(1,1), north}(), MultiBlockBoundary{(1,2), south}()), + (MultiBlockBoundary{(2,1), north}(), MultiBlockBoundary{(2,2), south}()), + (MultiBlockBoundary{(1,2), east}(), MultiBlockBoundary{(2,2), west}()), + (MultiBlockBoundary{(1,2), north}(), MultiBlockBoundary{(1,3), south}()), + (MultiBlockBoundary{(2,2), north}(), MultiBlockBoundary{(2,3), south}()), + (MultiBlockBoundary{(1,3), east}(), MultiBlockBoundary{(2,3), west}()), + ]) + + # 3D + a = CartesianAtlas(fill(Chart(identity, unitcube()), 2,2,3)) + @test Set(connections(a)) == Set([ + (MultiBlockBoundary{(1,1,1), east}(), MultiBlockBoundary{(2,1,1), west}()), + (MultiBlockBoundary{(1,1,1), north}(), MultiBlockBoundary{(1,2,1), south}()), + (MultiBlockBoundary{(2,1,1), north}(), MultiBlockBoundary{(2,2,1), south}()), + (MultiBlockBoundary{(1,2,1), east}(), MultiBlockBoundary{(2,2,1), west}()), + + (MultiBlockBoundary{(1,1,2), east}(), MultiBlockBoundary{(2,1,2), west}()), + (MultiBlockBoundary{(1,1,2), north}(), MultiBlockBoundary{(1,2,2), south}()), + (MultiBlockBoundary{(2,1,2), north}(), MultiBlockBoundary{(2,2,2), south}()), + (MultiBlockBoundary{(1,2,2), east}(), MultiBlockBoundary{(2,2,2), west}()), + + (MultiBlockBoundary{(1,1,3), east}(), MultiBlockBoundary{(2,1,3), west}()), + (MultiBlockBoundary{(1,1,3), north}(), MultiBlockBoundary{(1,2,3), south}()), + (MultiBlockBoundary{(2,1,3), north}(), MultiBlockBoundary{(2,2,3), south}()), + (MultiBlockBoundary{(1,2,3), east}(), MultiBlockBoundary{(2,2,3), west}()), + + (MultiBlockBoundary{(1,1,1), top}(), MultiBlockBoundary{(1,1,2), bottom}()), + (MultiBlockBoundary{(2,1,1), top}(), MultiBlockBoundary{(2,1,2), bottom}()), + (MultiBlockBoundary{(1,2,1), top}(), MultiBlockBoundary{(1,2,2), bottom}()), + (MultiBlockBoundary{(2,2,1), top}(), MultiBlockBoundary{(2,2,2), bottom}()), + + (MultiBlockBoundary{(1,1,2), top}(), MultiBlockBoundary{(1,1,3), bottom}()), + (MultiBlockBoundary{(2,1,2), top}(), MultiBlockBoundary{(2,1,3), bottom}()), + (MultiBlockBoundary{(1,2,2), top}(), MultiBlockBoundary{(1,2,3), bottom}()), + (MultiBlockBoundary{(2,2,2), top}(), MultiBlockBoundary{(2,2,3), bottom}()), + ]) + end + + @testset "boundary_identifiers" begin + # 2D + a = CartesianAtlas(fill(Chart(identity, unitcube()), 2,3)) + @test Set(boundary_identifiers(a)) == Set([ + MultiBlockBoundary{(1,1), south}(), + MultiBlockBoundary{(2,1), south}(), + MultiBlockBoundary{(2,1), east}(), + MultiBlockBoundary{(2,2), east}(), + MultiBlockBoundary{(2,3), east}(), + MultiBlockBoundary{(1,3), north}(), + MultiBlockBoundary{(2,3), north}(), + MultiBlockBoundary{(1,1), west}(), + MultiBlockBoundary{(1,2), west}(), + MultiBlockBoundary{(1,3), west}(), + ]) + + # 3D + a = CartesianAtlas(fill(Chart(identity, unitsquare()), 2,2,3)) + @test Set(boundary_identifiers(a)) == Set([ + MultiBlockBoundary{(1,1,1), bottom}(), + MultiBlockBoundary{(2,1,1), bottom}(), + MultiBlockBoundary{(1,2,1), bottom}(), + MultiBlockBoundary{(2,2,1), bottom}(), + + MultiBlockBoundary{(1,1,3), top}(), + MultiBlockBoundary{(2,1,3), top}(), + MultiBlockBoundary{(1,2,3), top}(), + MultiBlockBoundary{(2,2,3), top}(), + + MultiBlockBoundary{(1,1,1), west}(), + MultiBlockBoundary{(1,2,1), west}(), + MultiBlockBoundary{(1,1,2), west}(), + MultiBlockBoundary{(1,2,2), west}(), + MultiBlockBoundary{(1,1,3), west}(), + MultiBlockBoundary{(1,2,3), west}(), + + MultiBlockBoundary{(2,1,1), east}(), + MultiBlockBoundary{(2,2,1), east}(), + MultiBlockBoundary{(2,1,2), east}(), + MultiBlockBoundary{(2,2,2), east}(), + MultiBlockBoundary{(2,1,3), east}(), + MultiBlockBoundary{(2,2,3), east}(), + + MultiBlockBoundary{(1,1,1), south}(), + MultiBlockBoundary{(2,1,1), south}(), + MultiBlockBoundary{(1,1,2), south}(), + MultiBlockBoundary{(2,1,2), south}(), + MultiBlockBoundary{(1,1,3), south}(), + MultiBlockBoundary{(2,1,3), south}(), + + MultiBlockBoundary{(1,2,1), north}(), + MultiBlockBoundary{(2,2,1), north}(), + MultiBlockBoundary{(1,2,2), north}(), + MultiBlockBoundary{(2,2,2), north}(), + MultiBlockBoundary{(1,2,3), north}(), + MultiBlockBoundary{(2,2,3), north}(), + ]) + end +end + +@testset "UnstructuredAtlas" begin + @testset "Constructors" begin + c1 = Chart(identity, unitsquare()) + c2 = Chart(x->2x, unitsquare()) + cn = [ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + + @test UnstructuredAtlas([c1, c1, c1], cn) isa UnstructuredAtlas + @test UnstructuredAtlas([c1, c2, c1, c2], cn) isa UnstructuredAtlas + + + cn = @SVector[ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + @test UnstructuredAtlas(@SVector[c1, c1, c1], cn) isa UnstructuredAtlas + @test UnstructuredAtlas(@SVector[c1, c2, c1, c2], cn) isa UnstructuredAtlas + end + + @testset "Getters" begin + c = Chart(identity, unitsquare()) + cn = [ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + + a = UnstructuredAtlas([c, c, c], cn) + + @test charts(a) == [c,c,c] + @test connections(a) == cn + end + + @testset "boundary_identifiers" begin + c = Chart(identity, unitsquare()) + cn = [ + (MultiBlockBoundary{1, east}(), MultiBlockBoundary{2, west}()), + (MultiBlockBoundary{1, north}(), MultiBlockBoundary{3, west}()), + (MultiBlockBoundary{2, north}(), MultiBlockBoundary{3, south}()), + ] + + a = UnstructuredAtlas([c, c, c], cn) + + @test Set(boundary_identifiers(a)) == Set([ + MultiBlockBoundary{1, west}(), + MultiBlockBoundary{1, south}(), + MultiBlockBoundary{2, south}(), + MultiBlockBoundary{2, east}(), + MultiBlockBoundary{3, north}(), + MultiBlockBoundary{3, east}(), + ]) + + end +end