changeset 866:1784b1c0af3e feature/laplace_opset

Merge with default
author Vidar Stiernström <vidar.stiernstrom@it.uu.se>
date Wed, 19 Jan 2022 14:44:24 +0100
parents 545a6c1a0a0e (diff) 9a2776352c2a (current diff)
children 4bd35ba8f34a
files Notes.md src/SbpOperators/SbpOperators.jl src/SbpOperators/volumeops/inner_products/inner_product.jl src/SbpOperators/volumeops/laplace/laplace.jl test/SbpOperators/volumeops/laplace/laplace_test.jl
diffstat 6 files changed, 290 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/Notes.md	Wed Jan 19 11:08:43 2022 +0100
+++ b/Notes.md	Wed Jan 19 14:44:24 2022 +0100
@@ -147,6 +147,7 @@
  - [ ] How do we handle mixes of periodic and non-periodic grids? Seems it should be supported on the grid level and on the 1d operator level. Between there it should be transparent.
  - [ ] Can we have a trait to tell if a TensorMapping is transposable?
  - [ ] Is it ok to have "Constructors" for abstract types which create subtypes? For example a Grids() functions that gives different kind of grids based on input?
+ - [ ] Figure out how to treat the borrowing parameters of operators. Include in into the struct? Expose via function dispatched on the operator type and grid?
 
 ## Regions and tensormappings
 - [ ] Use a trait to indicate if a TensorMapping uses indices with regions.
--- a/src/Grids/AbstractGrid.jl	Wed Jan 19 11:08:43 2022 +0100
+++ b/src/Grids/AbstractGrid.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -7,7 +7,7 @@
 
 """
 abstract type AbstractGrid end
-
+export AbstractGrid
 function dimension end
 function points end
 export dimension, points
--- a/src/SbpOperators/SbpOperators.jl	Wed Jan 19 11:08:43 2022 +0100
+++ b/src/SbpOperators/SbpOperators.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -3,6 +3,7 @@
 using Sbplib.RegionIndices
 using Sbplib.LazyTensors
 using Sbplib.Grids
+using Sbplib.StaticDicts
 
 @enum Parity begin
     odd = -1
--- a/src/SbpOperators/volumeops/inner_products/inner_product.jl	Wed Jan 19 11:08:43 2022 +0100
+++ b/src/SbpOperators/volumeops/inner_products/inner_product.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -10,8 +10,8 @@
 
 On a 1-dimensional grid, `H` is a `ConstantInteriorScalingOperator`. On a
 N-dimensional grid, `H` is the outer product of the 1-dimensional inner
-product operators for each coordinate direction. Also see the documentation of
-On a 0-dimensional grid, `H` is a 0-dimensional `IdentityMapping`.
+product operators for each coordinate direction. On a 0-dimensional grid,
+`H` is a 0-dimensional `IdentityMapping`.
 """
 function inner_product(grid::EquidistantGrid, interior_weight, closure_weights)
     Hs = ()
--- a/src/SbpOperators/volumeops/laplace/laplace.jl	Wed Jan 19 11:08:43 2022 +0100
+++ b/src/SbpOperators/volumeops/laplace/laplace.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -1,3 +1,131 @@
+"""
+    Laplace{T, Dim, TMdiffop} <: TensorMapping{T,Dim,Dim}
+    Laplace(grid, filename; order)
+
+Implements the Laplace operator, approximating ∑d²/xᵢ² , i = 1,...,`Dim` as a
+`TensorMapping`. Additionally, `Laplace` stores the inner product and boundary
+operators relevant for constructing a SBP finite difference scheme as a `TensorMapping`.
+
+`Laplace(grid, filename; order)` creates the Laplace operator defined on `grid`,
+where the operators are read from TOML. The differential operator is created
+using `laplace(grid,...)`.
+
+Note that all properties of Laplace, excluding the differential operator `Laplace.D`, are
+abstract types. For performance reasons, they should therefore be
+accessed via the provided getter functions (e.g `inner_product(::Laplace)`).
+
+"""
+struct Laplace{T, Dim, TMdiffop<:TensorMapping{T,Dim,Dim}} <: TensorMapping{T,Dim,Dim}
+    D::TMdiffop # Differential operator
+    H::TensorMapping # Inner product operator
+    H_inv::TensorMapping # Inverse inner product operator
+    e::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Boundary restriction operators.
+    d::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Normal derivative operators
+    H_boundary::StaticDict{<:BoundaryIdentifier,<:TensorMapping} # Boundary quadrature operators
+end
+export Laplace
+
+function Laplace(grid, filename; order)
+    
+    # Read stencils
+    stencil_set = read_stencil_set(filename; order)
+    # TODO: Removed once we can construct the volume and
+    # boundary operators by op(grid, read_stencil_set(fn; order,...)).
+    D_inner_stecil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+    D_closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+    H_inner_stencils = parse_scalar(stencil_set["H"]["inner"])
+    H_closure_stencils = parse_tuple(stencil_set["H"]["closure"])
+    e_closure_stencil = parse_stencil(stencil_set["e"]["closure"])
+    d_closure_stencil = parse_stencil(stencil_set["d1"]["closure"])
+
+    # Volume operators
+    Δ =  laplace(grid, D_inner_stecil, D_closure_stencils)
+    H =  inner_product(grid, H_inner_stencils, H_closure_stencils)
+    H⁻¹ = inverse_inner_product(grid, H_inner_stencils, H_closure_stencils)
+
+    # Boundary operator - id pairs
+    ids = boundary_identifiers(grid)
+    n_ids = length(ids)
+    e_pairs = ntuple(i -> ids[i] => boundary_restriction(grid, e_closure_stencil, ids[i]), n_ids)
+    d_pairs = ntuple(i -> ids[i] => normal_derivative(grid, d_closure_stencil, ids[i]), n_ids)
+    Hᵧ_pairs = ntuple(i -> ids[i] => inner_product(boundary_grid(grid, ids[i]), H_inner_stencils, H_closure_stencils), n_ids)
+
+    return Laplace(Δ, H, H⁻¹, StaticDict(e_pairs), StaticDict(d_pairs), StaticDict(Hᵧ_pairs))
+end
+
+# TODO: Consider pretty printing of the following form
+# Base.show(io::IO, L::Laplace{T, Dim}) where {T,Dim,TM} =  print(io, "Laplace{$T, $Dim, $TM}(", L.D, L.H, L.H_inv, L.e, L.d, L.H_boundary, ")")
+
+LazyTensors.range_size(L::Laplace) = LazyTensors.range_size(L.D)
+LazyTensors.domain_size(L::Laplace) = LazyTensors.domain_size(L.D)
+LazyTensors.apply(L::Laplace, v::AbstractArray, I...) = LazyTensors.apply(L.D,v,I...)
+
+
+"""
+    inner_product(L::Laplace)
+
+Returns the inner product operator associated with `L`
+
+"""
+inner_product(L::Laplace) = L.H
+export inner_product
+
+
+"""
+    inverse_inner_product(L::Laplace)
+
+Returns the inverse of the inner product operator associated with `L`
+
+"""
+inverse_inner_product(L::Laplace) = L.H_inv
+export inverse_inner_product
+
+
+"""
+    boundary_restriction(L::Laplace, id::BoundaryIdentifier)
+    boundary_restriction(L::Laplace, ids::NTuple{N,BoundaryIdentifier})
+    boundary_restriction(L::Laplace, ids...)
+
+Returns boundary restriction operator(s) associated with `L` for the boundary(s)
+identified by id(s).
+
+"""
+boundary_restriction(L::Laplace, id::BoundaryIdentifier) = L.e[id]
+boundary_restriction(L::Laplace, ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.e[ids[i]],N)
+boundary_restriction(L::Laplace, ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.e[ids[i]],N)
+export boundary_restriction
+
+
+"""
+    normal_derivative(L::Laplace, id::BoundaryIdentifier)
+    normal_derivative(L::Laplace, ids::NTuple{N,BoundaryIdentifier})
+    normal_derivative(L::Laplace, ids...)
+
+Returns normal derivative operator(s) associated with `L` for the boundary(s)
+identified by id(s).
+
+"""
+normal_derivative(L::Laplace, id::BoundaryIdentifier) = L.d[id]
+normal_derivative(L::Laplace, ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.d[ids[i]],N)
+normal_derivative(L::Laplace, ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.d[ids[i]],N)
+export normal_derivative
+
+
+"""
+    boundary_quadrature(L::Laplace, id::BoundaryIdentifier)
+    boundary_quadrature(L::Laplace, ids::NTuple{N,BoundaryIdentifier})
+    boundary_quadrature(L::Laplace, ids...)
+
+Returns boundary quadrature operator(s) associated with `L` for the boundary(s)
+identified by id(s).
+
+"""
+boundary_quadrature(L::Laplace, id::BoundaryIdentifier) = L.H_boundary[id]
+boundary_quadrature(L::Laplace, ids::NTuple{N,BoundaryIdentifier}) where N = ntuple(i->L.H_boundary[ids[i]],N)
+boundary_quadrature(L::Laplace, ids::Vararg{BoundaryIdentifier,N}) where N = ntuple(i->L.H_boundary[ids[i]],N)
+export boundary_quadrature
+
+
 """
     laplace(grid::EquidistantGrid{Dim}, inner_stencil, closure_stencils)
 
--- a/test/SbpOperators/volumeops/laplace/laplace_test.jl	Wed Jan 19 11:08:43 2022 +0100
+++ b/test/SbpOperators/volumeops/laplace/laplace_test.jl	Wed Jan 19 14:44:24 2022 +0100
@@ -3,14 +3,92 @@
 using Sbplib.SbpOperators
 using Sbplib.Grids
 using Sbplib.LazyTensors
+using Sbplib.RegionIndices
+using Sbplib.StaticDicts
+
+operator_path = sbp_operators_path()*"standard_diagonal.toml"
+# Default stencils (4th order)
+stencil_set = read_stencil_set(operator_path; order=4)
+inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
+closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+e_closure = parse_stencil(stencil_set["e"]["closure"])
+d_closure = parse_stencil(stencil_set["d1"]["closure"])
+quadrature_interior = parse_scalar(stencil_set["H"]["inner"])
+quadrature_closure = parse_tuple(stencil_set["H"]["closure"])
 
 @testset "Laplace" begin
     g_1D = EquidistantGrid(101, 0.0, 1.)
     g_3D = EquidistantGrid((51,101,52), (0.0, -1.0, 0.0), (1., 1., 1.))
     @testset "Constructors" begin
-        stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
-        inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
-        closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
+
+        @testset "1D" begin
+            Δ = laplace(g_1D, inner_stencil, closure_stencils)
+            H = inner_product(g_1D, quadrature_interior, quadrature_closure)
+            Hi = inverse_inner_product(g_1D, quadrature_interior, quadrature_closure)
+
+            (id_l, id_r) = boundary_identifiers(g_1D)
+
+            e_l = boundary_restriction(g_1D, e_closure,id_l)
+            e_r = boundary_restriction(g_1D, e_closure,id_r)
+            e_dict = StaticDict(id_l => e_l, id_r => e_r)
+
+            d_l = normal_derivative(g_1D, d_closure,id_l)
+            d_r = normal_derivative(g_1D, d_closure,id_r)
+            d_dict = StaticDict(id_l => d_l, id_r => d_r)
+
+            H_l = inner_product(boundary_grid(g_1D,id_l), quadrature_interior, quadrature_closure)
+            H_r = inner_product(boundary_grid(g_1D,id_r), quadrature_interior, quadrature_closure)
+            Hb_dict = StaticDict(id_l => H_l, id_r => H_r)
+
+            L = Laplace(g_1D, operator_path; order=4)
+            @test L == Laplace(Δ, H, Hi, e_dict, d_dict, Hb_dict)
+            @test L isa TensorMapping{T,1,1}  where T
+            @inferred Laplace(Δ, H, Hi, e_dict, d_dict, Hb_dict)
+        end
+        @testset "3D" begin
+            Δ = laplace(g_3D, inner_stencil, closure_stencils)
+            H = inner_product(g_3D, quadrature_interior, quadrature_closure)
+            Hi = inverse_inner_product(g_3D, quadrature_interior, quadrature_closure)
+
+            (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
+            e_l = boundary_restriction(g_3D, e_closure,id_l)
+            e_r = boundary_restriction(g_3D, e_closure,id_r)
+            e_s = boundary_restriction(g_3D, e_closure,id_s)
+            e_n = boundary_restriction(g_3D, e_closure,id_n)
+            e_b = boundary_restriction(g_3D, e_closure,id_b)
+            e_t = boundary_restriction(g_3D, e_closure,id_t)
+            e_dict = StaticDict(id_l => e_l, id_r => e_r,
+                                id_s => e_s, id_n => e_n,
+                                id_b => e_b, id_t => e_t)
+
+            d_l = normal_derivative(g_3D, d_closure,id_l)
+            d_r = normal_derivative(g_3D, d_closure,id_r)
+            d_s = normal_derivative(g_3D, d_closure,id_s)
+            d_n = normal_derivative(g_3D, d_closure,id_n)
+            d_b = normal_derivative(g_3D, d_closure,id_b)
+            d_t = normal_derivative(g_3D, d_closure,id_t)
+            d_dict = StaticDict(id_l => d_l, id_r => d_r,
+                                id_s => d_s, id_n => d_n,
+                                id_b => d_b, id_t => d_t)
+
+            H_l = inner_product(boundary_grid(g_3D,id_l), quadrature_interior, quadrature_closure)
+            H_r = inner_product(boundary_grid(g_3D,id_r), quadrature_interior, quadrature_closure)
+            H_s = inner_product(boundary_grid(g_3D,id_s), quadrature_interior, quadrature_closure)
+            H_n = inner_product(boundary_grid(g_3D,id_n), quadrature_interior, quadrature_closure)
+            H_b = inner_product(boundary_grid(g_3D,id_b), quadrature_interior, quadrature_closure)
+            H_t = inner_product(boundary_grid(g_3D,id_t), quadrature_interior, quadrature_closure)
+            Hb_dict = StaticDict(id_l => H_l, id_r => H_r,
+                                 id_s => H_s, id_n => H_n,
+                                 id_b => H_b, id_t => H_t)
+
+            L = Laplace(g_3D, operator_path; order=4)
+            @test L == Laplace(Δ,H,Hi,e_dict,d_dict,Hb_dict)
+            @test L isa TensorMapping{T,3,3} where T
+            @inferred Laplace(Δ,H,Hi,e_dict,d_dict,Hb_dict)
+        end
+    end
+
+    @testset "laplace" begin
         @testset "1D" begin
             L = laplace(g_1D, inner_stencil, closure_stencils)
             @test L == second_derivative(g_1D, inner_stencil, closure_stencils)
@@ -23,9 +101,83 @@
             Dyy = second_derivative(g_3D, inner_stencil, closure_stencils, 2)
             Dzz = second_derivative(g_3D, inner_stencil, closure_stencils, 3)
             @test L == Dxx + Dyy + Dzz
+            @test L isa TensorMapping{T,3,3} where T
         end
     end
 
+    @testset "inner_product" begin
+        L = Laplace(g_3D, operator_path; order=4)
+        @test inner_product(L) == inner_product(g_3D, quadrature_interior, quadrature_closure)
+    end
+
+    @testset "inverse_inner_product" begin
+        L = Laplace(g_3D, operator_path; order=4)
+        @test inverse_inner_product(L) == inverse_inner_product(g_3D, quadrature_interior, quadrature_closure)
+    end
+
+    @testset "boundary_restriction" begin
+        L = Laplace(g_3D, operator_path; order=4)
+        (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
+        @test boundary_restriction(L, id_l) == boundary_restriction(g_3D, e_closure,id_l)
+        @test boundary_restriction(L, id_r) == boundary_restriction(g_3D, e_closure,id_r)
+        @test boundary_restriction(L, id_s) == boundary_restriction(g_3D, e_closure,id_s)
+        @test boundary_restriction(L, id_n) == boundary_restriction(g_3D, e_closure,id_n)
+        @test boundary_restriction(L, id_b) == boundary_restriction(g_3D, e_closure,id_b)
+        @test boundary_restriction(L, id_t) == boundary_restriction(g_3D, e_closure,id_t)
+
+        ids = boundary_identifiers(g_3D)
+        es = boundary_restriction(L, ids)
+        @test es ==  (boundary_restriction(L, id_l),
+                      boundary_restriction(L, id_r),
+                      boundary_restriction(L, id_s),
+                      boundary_restriction(L, id_n),
+                      boundary_restriction(L, id_b),
+                      boundary_restriction(L, id_t));
+        @test es == boundary_restriction(L, ids...)
+    end
+
+    @testset "normal_derivative" begin
+        L = Laplace(g_3D, operator_path; order=4)
+        (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
+        @test normal_derivative(L, id_l) == normal_derivative(g_3D, d_closure,id_l)
+        @test normal_derivative(L, id_r) == normal_derivative(g_3D, d_closure,id_r)
+        @test normal_derivative(L, id_s) == normal_derivative(g_3D, d_closure,id_s)
+        @test normal_derivative(L, id_n) == normal_derivative(g_3D, d_closure,id_n)
+        @test normal_derivative(L, id_b) == normal_derivative(g_3D, d_closure,id_b)
+        @test normal_derivative(L, id_t) == normal_derivative(g_3D, d_closure,id_t)
+
+        ids = boundary_identifiers(g_3D)
+        ds = normal_derivative(L, ids)
+        @test ds ==  (normal_derivative(L, id_l),
+                      normal_derivative(L, id_r),
+                      normal_derivative(L, id_s),
+                      normal_derivative(L, id_n),
+                      normal_derivative(L, id_b),
+                      normal_derivative(L, id_t));
+        @test ds == normal_derivative(L, ids...)
+    end
+
+    @testset "boundary_quadrature" begin
+        L = Laplace(g_3D, operator_path; order=4)
+        (id_l, id_r, id_s, id_n, id_b, id_t) = boundary_identifiers(g_3D)
+        @test boundary_quadrature(L, id_l) == inner_product(boundary_grid(g_3D, id_l), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_r) == inner_product(boundary_grid(g_3D, id_r), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_s) == inner_product(boundary_grid(g_3D, id_s), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_n) == inner_product(boundary_grid(g_3D, id_n), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_b) == inner_product(boundary_grid(g_3D, id_b), quadrature_interior, quadrature_closure)
+        @test boundary_quadrature(L, id_t) == inner_product(boundary_grid(g_3D, id_t), quadrature_interior, quadrature_closure)
+
+        ids = boundary_identifiers(g_3D)
+        H_gammas = boundary_quadrature(L, ids)
+        @test H_gammas ==  (boundary_quadrature(L, id_l),
+                            boundary_quadrature(L, id_r),
+                            boundary_quadrature(L, id_s),
+                            boundary_quadrature(L, id_n),
+                            boundary_quadrature(L, id_b),
+                            boundary_quadrature(L, id_t));
+        @test H_gammas == boundary_quadrature(L, ids...)
+    end
+
     # Exact differentiation is measured point-wise. In other cases
     # the error is measured in the l2-norm.
     @testset "Accuracy" begin
@@ -42,7 +194,7 @@
         # 2nd order interior stencil, 1st order boundary stencil,
         # implies that L*v should be exact for binomials up to order 2.
         @testset "2nd order" begin
-            stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=2)
+            stencil_set = read_stencil_set(operator_path; order=2)
             inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
             closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
             L = laplace(g_3D, inner_stencil, closure_stencils)
@@ -55,7 +207,7 @@
         # 4th order interior stencil, 2nd order boundary stencil,
         # implies that L*v should be exact for binomials up to order 3.
         @testset "4th order" begin
-            stencil_set = read_stencil_set(sbp_operators_path()*"standard_diagonal.toml"; order=4)
+            stencil_set = read_stencil_set(operator_path; order=4)
             inner_stencil = parse_stencil(stencil_set["D2"]["inner_stencil"])
             closure_stencils = parse_stencil.(stencil_set["D2"]["closure_stencils"])
             L = laplace(g_3D, inner_stencil, closure_stencils)