diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index 228279af..f33fa7ae 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-10-19T16:22:01","documenter_version":"1.7.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.11.1","generation_timestamp":"2024-11-08T07:13:05","documenter_version":"1.7.0"}} \ No newline at end of file diff --git a/dev/docstrings/index.html b/dev/docstrings/index.html index 7df9b464..654b2ae3 100644 --- a/dev/docstrings/index.html +++ b/dev/docstrings/index.html @@ -1,5 +1,5 @@ -Docstrings · Inti.jl

Docstrings

Inti.IntiModule
module Inti

Library for solving integral equations using Nyström methods.

source
Inti.CORRECTION_METHODSConstant
const CORRECTION_METHODS = [:none, :dim, :hcubature]

Available correction methods for the singular and nearly-singular integrals in Inti.

source
Inti.AbstractMeshType
abstract type AbstractMesh{N,T}

An abstract mesh structure in dimension N with primite data of type T (e.g. Float64 for double precision representation).

Concrete subtypes of AbstractMesh should implement ElementIterator for accessing the mesh elements.

See also: Mesh

source
Inti.AdjointDoubleLayerKernelType
struct AdjointDoubleLayerKernel{T,Op} <: AbstractKernel{T}

Given an operator Op, construct its free-space adjoint double-layer kernel. This corresponds to the transpose(γ₁,ₓ[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the target variable.

source
Inti.BlockArrayType
struct BlockArray{T<:StaticArray,N,S} <: AbstractMatrix{T,N}

A struct which behaves like an Array{T,N}, but with the underlying data stored as a Matrix{S}, where S::Number = eltype(T) is the scalar type associated with T. This allows for the use of blas routines under-the-hood, while providing a convenient interface for handling matrices over StaticArrays.

using StaticArrays
+Docstrings · Inti.jl

Docstrings

Inti.IntiModule
module Inti

Library for solving integral equations using Nyström methods.

source
Inti.CORRECTION_METHODSConstant
const CORRECTION_METHODS = [:none, :dim, :hcubature]

Available correction methods for the singular and nearly-singular integrals in Inti.

source
Inti.AbstractMeshType
abstract type AbstractMesh{N,T}

An abstract mesh structure in dimension N with primite data of type T (e.g. Float64 for double precision representation).

Concrete subtypes of AbstractMesh should implement ElementIterator for accessing the mesh elements.

See also: Mesh

source
Inti.AdjointDoubleLayerKernelType
struct AdjointDoubleLayerKernel{T,Op} <: AbstractKernel{T}

Given an operator Op, construct its free-space adjoint double-layer kernel. This corresponds to the transpose(γ₁,ₓ[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the target variable.

source
Inti.BlockArrayType
struct BlockArray{T<:StaticArray,N,S} <: AbstractMatrix{T,N}

A struct which behaves like an Array{T,N}, but with the underlying data stored as a Matrix{S}, where S::Number = eltype(T) is the scalar type associated with T. This allows for the use of blas routines under-the-hood, while providing a convenient interface for handling matrices over StaticArrays.

using StaticArrays
 T = SMatrix{2,2,Int,4}
 B = Inti.BlockArray{T}([i*j for i in 1:4, j in 1:4])
 
@@ -8,7 +8,7 @@
 2×2 Inti.BlockArray{SMatrix{2, 2, Int64, 4}, 2, Int64}:
  [1 2; 2 4]  [3 4; 6 8]
  [3 6; 4 8]  [9 12; 12 16]
-
source
Inti.DomainType
struct Domain

Representation of a geometrical domain formed by a set of entities with the same geometric dimension. For basic set operations on domains are supported (union, intersection, difference, etc), and they all return a new Domain object.

Calling keys(Ω) returns the set of EntityKeys that make up the domain; given a key, the underlying entities can be accessed with global_get_entity(key).

source
Inti.DomainMethod
Domain([f::Function,] keys)

Create a domain from a set of EntityKeys. Optionally, a filter function f can be passed to filter the entities.

Note that all entities in a domain must have the same geometric dimension.

source
Inti.DomainMethod
Domain(f::Function, msh::AbstractMesh)

Call Domain(f, ents) on ents = entities(msh).

source
Inti.DoubleLayerKernelType
struct DoubleLayerKernel{T,Op} <: AbstractKernel{T}

Given an operator Op, construct its free-space double-layer kernel. This corresponds to the γ₁ trace of the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the source variable.

source
Inti.ElastostaticType
struct Elastostatic{N,T} <: AbstractDifferentialOperator{N}

Elastostatic operator in N dimensions: -μΔu - (μ+λ)∇(∇⋅u)

Note that the displacement $u$ is a vector of length N since this is a vectorial problem.

source
Inti.ElementIteratorType
struct ElementIterator{E,M} <: AbstractVector{E}

Structure to lazily access elements of type E in a mesh of type M. This is particularly useful for LagrangeElements, where the information to reconstruct the element is stored in the mesh connectivity matrix.

source
Inti.EmbeddedQuadratureType
struct EmbeddedQuadrature{L,H,D} <: ReferenceQuadrature{D}

A quadrature rule for the reference shape D based on a high-order quadrature of type H and a low-order quadrature of type L. The low-order quadrature rule is embedded in the sense that its n nodes are exactly the first n nodes of the high-order quadrature rule.

source
Inti.EntityKeyType
EntityKey

Used to represent the key of a GeometricEntity, comprised of a dim and a tag field, where dim is the geometrical dimension of the entity, and tag is a unique integer identifying the entity.

The sign of the tag field is used to distinguish the orientation of the entity, and is ignored when comparing two EntityKeys for equality.

source
Inti.FejerType
struct Fejer{N}

N-point Fejer's first quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ N-1.

using Inti
+
source
Inti.DomainType
struct Domain

Representation of a geometrical domain formed by a set of entities with the same geometric dimension. For basic set operations on domains are supported (union, intersection, difference, etc), and they all return a new Domain object.

Calling keys(Ω) returns the set of EntityKeys that make up the domain; given a key, the underlying entities can be accessed with global_get_entity(key).

source
Inti.DomainMethod
Domain([f::Function,] keys)

Create a domain from a set of EntityKeys. Optionally, a filter function f can be passed to filter the entities.

Note that all entities in a domain must have the same geometric dimension.

source
Inti.DomainMethod
Domain(f::Function, msh::AbstractMesh)

Call Domain(f, ents) on ents = entities(msh).

source
Inti.DoubleLayerKernelType
struct DoubleLayerKernel{T,Op} <: AbstractKernel{T}

Given an operator Op, construct its free-space double-layer kernel. This corresponds to the γ₁ trace of the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the source variable.

source
Inti.ElastostaticType
struct Elastostatic{N,T} <: AbstractDifferentialOperator{N}

Elastostatic operator in N dimensions: -μΔu - (μ+λ)∇(∇⋅u)

Note that the displacement $u$ is a vector of length N since this is a vectorial problem.

source
Inti.ElementIteratorType
struct ElementIterator{E,M} <: AbstractVector{E}

Structure to lazily access elements of type E in a mesh of type M. This is particularly useful for LagrangeElements, where the information to reconstruct the element is stored in the mesh connectivity matrix.

source
Inti.EmbeddedQuadratureType
struct EmbeddedQuadrature{L,H,D} <: ReferenceQuadrature{D}

A quadrature rule for the reference shape D based on a high-order quadrature of type H and a low-order quadrature of type L. The low-order quadrature rule is embedded in the sense that its n nodes are exactly the first n nodes of the high-order quadrature rule.

source
Inti.EntityKeyType
EntityKey

Used to represent the key of a GeometricEntity, comprised of a dim and a tag field, where dim is the geometrical dimension of the entity, and tag is a unique integer identifying the entity.

The sign of the tag field is used to distinguish the orientation of the entity, and is ignored when comparing two EntityKeys for equality.

source
Inti.FejerType
struct Fejer{N}

N-point Fejer's first quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ N-1.

using Inti
 
 q = Inti.Fejer(;order=10)
 
@@ -16,7 +16,7 @@
 
 # output
 
-true
source
Inti.GaussType
struct Gauss{D,N} <: ReferenceQuadrature{D}

Tabulated N-point symmetric Gauss quadrature rule for integration over D.

source
Inti.GaussLegendreType
struct GaussLegendre{N,T}

N-point Gauss-Legendre quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ 2N-1.

using Inti
+true
source
Inti.GaussType
struct Gauss{D,N} <: ReferenceQuadrature{D}

Tabulated N-point symmetric Gauss quadrature rule for integration over D.

source
Inti.GaussLegendreType
struct GaussLegendre{N,T}

N-point Gauss-Legendre quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ 2N-1.

using Inti
 
 q = Inti.GaussLegendre(;order=10)
 
@@ -24,14 +24,14 @@
 
 # output
 
-true
source
Inti.GeometricEntityType
struct GeometricEntity

Geometrical objects such as lines, surfaces, and volumes.

Geometrical entities are stored in a global ENTITIES dictionary mapping EntityKey to the corresponding GeometricEntity, and usually entities are manipulated through their keys.

A GeometricEntity can also contain a pushforward field used to parametrically represent the entry as the image of a reference domain (pushforward.domain) under some function (pushforward.parametrization).

Note that entities are manipulated through their keys, and the GeometricEntity constructor returns the key of the created entity; to retrieve the entity, use the global_get_entity function.

source
Inti.GeometricEntityMethod
GeometricEntity(shape::String [; translation, rotation, scaling, kwargs...])

Constructs a geometric entity with the specified shape and optional parameters, and returns its key.

Arguments

  • shape::String: The shape of the geometric entity.
  • translation: The translation vector of the geometric entity. Default is SVector(0, 0, 0).
  • rotation: The rotation vector of the geometric entity. Default is SVector(0, 0, 0).
  • scaling: The scaling vector of the geometric entity. Default is SVector(1, 1, 1).
  • kwargs...: Additional keyword arguments to be passed to the shape constructor.

Supported shapes

source
Inti.HelmholtzMethod
Helmholtz(; k, dim)

Helmholtz operator in dim dimensions: -Δu - k²u.

The parameter k can be a real or complex number. For purely imaginary wavenumbers, consider using the Yukawa kernel.

source
Inti.HyperRectangleType
struct HyperRectangle{N,T} <: ReferenceInterpolant{ReferenceHyperCube{N},T}

Axis-aligned hyperrectangle in N dimensions given by low_corner::SVector{N,T} and high_corner::SVector{N,T}.

source
Inti.HyperSingularKernelType
struct HyperSingularKernel{T,Op} <: AbstractKernel{T}

Given an operator Op, construct its free-space hypersingular kernel. This corresponds to the transpose(γ₁,ₓγ₁[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative respect to the target variable of the DoubleLayerKernel.

source
Inti.IntegralOperatorType
struct IntegralOperator{T} <: AbstractMatrix{T}

A discrete linear integral operator given by

\[I[u](x) = \int_{\Gamma\_s} K(x,y)u(y) ds_y, x \in \Gamma_{t}\]

where $\Gamma_s$ and $\Gamma_t$ are the source and target domains, respectively.

source
Inti.IntegralPotentialType
struct IntegralPotential

Represent a potential given by a kernel and a quadrature over which integration is performed.

IntegralPotentials are created using IntegralPotential(kernel, quadrature).

Evaluating an integral potential requires a density σ (defined over the quadrature nodes of the source mesh) and a point x at which to evaluate the integral

\[\int_{\Gamma} K(oldsymbol{x},oldsymbol{y})\sigma(y) ds_y, x \not \in \Gamma\]

Assuming 𝒮 is an integral potential and σ is a vector of values defined on quadrature, calling 𝒮[σ] creates an anonymous function that can be evaluated at any point x.

source
Inti.KronrodType
struct Kronrod{D,N} <: ReferenceQuadrature{D}

N-point Kronrod rule obtained by adding n+1 points to a Gauss quadrature containing n points. The order is either 3n + 1 for n even or 3n + 2 for n odd.

source
Inti.LagrangeElementType
struct LagrangeElement{D,Np,T} <: ReferenceInterpolant{D,T}

A polynomial p : D → T uniquely defined by its Np values on the Np reference nodes of D.

The return type T should be a vector space (i.e. support addition and multiplication by scalars). For istance, T could be a number or a vector, but not a Tuple.

source
Inti.LaplaceMethod
Laplace(; dim)

Laplace's differential operator in dim dimension: $-Δu$. ```

Note the negative sign in the definition.

source
Inti.MeshType
struct Mesh{N,T} <: AbstractMesh{N,T}

Unstructured mesh defined by a set of nodes(of typeSVector{N,T}`), and a dictionary mapping element types to connectivity matrices. Each columns of a given connectivity matrix stores the integer tags of the nodes in the mesh comprising the element.

Additionally, the mesh contains a mapping from EntityKeys to the tags of the elements composing the entity. This can be used to extract submeshes from a given mesh using e.g. view(msh,Γ) or msh[Γ], where Γ is a Domain.

See elements for a way to iterate over the elements of a mesh.

source
Inti.MultiIndexType
MultiIndex{N}

Wrapper around NTuple{N,Int} mimicking a multi-index in ℤ₀ᴺ.

source
Inti.ParametricElementType
ParametricElement{D,T,F} <: ReferenceInterpolant{D,T}

An element represented through a explicit function f mapping D into the element. For performance reasons, f should take as input a StaticVector and return a StaticVector or StaticArray.

See also: ReferenceInterpolant, LagrangeElement

source
Inti.PolynomialSpaceType
struct PolynomialSpace{D,K}

The space of all polynomials of degree ≤K, commonly referred to as ℙₖ.

The type parameter D, of singleton type, is used to determine the reference domain of the polynomial basis. In particular, when D is a hypercube in d dimensions, the precise definition is ℙₖ = span{𝐱ᶿ : 0≤max(θ)≤ K}; when D is a d-dimensional simplex, the space is ℙₖ = span{𝐱ᶿ : 0≤sum(θ)≤ K}, where θ ∈ 𝐍ᵈ is a multi-index.

See also: monomial_basis, lagrange_basis

source
Inti.QuadratureMethod
Quadrature(Ω::Domain; meshsize, qorder)

Construct a Quadrature over the domain Ω with a mesh of size meshsize and quadrature order qorder.

source
Inti.GeometricEntityType
struct GeometricEntity

Geometrical objects such as lines, surfaces, and volumes.

Geometrical entities are stored in a global ENTITIES dictionary mapping EntityKey to the corresponding GeometricEntity, and usually entities are manipulated through their keys.

A GeometricEntity can also contain a pushforward field used to parametrically represent the entry as the image of a reference domain (pushforward.domain) under some function (pushforward.parametrization).

Note that entities are manipulated through their keys, and the GeometricEntity constructor returns the key of the created entity; to retrieve the entity, use the global_get_entity function.

source
Inti.GeometricEntityMethod
GeometricEntity(shape::String [; translation, rotation, scaling, kwargs...])

Constructs a geometric entity with the specified shape and optional parameters, and returns its key.

Arguments

  • shape::String: The shape of the geometric entity.
  • translation: The translation vector of the geometric entity. Default is SVector(0, 0, 0).
  • rotation: The rotation vector of the geometric entity. Default is SVector(0, 0, 0).
  • scaling: The scaling vector of the geometric entity. Default is SVector(1, 1, 1).
  • kwargs...: Additional keyword arguments to be passed to the shape constructor.

Supported shapes

source
Inti.HelmholtzMethod
Helmholtz(; k, dim)

Helmholtz operator in dim dimensions: -Δu - k²u.

The parameter k can be a real or complex number. For purely imaginary wavenumbers, consider using the Yukawa kernel.

source
Inti.HyperRectangleType
struct HyperRectangle{N,T} <: ReferenceInterpolant{ReferenceHyperCube{N},T}

Axis-aligned hyperrectangle in N dimensions given by low_corner::SVector{N,T} and high_corner::SVector{N,T}.

source
Inti.HyperSingularKernelType
struct HyperSingularKernel{T,Op} <: AbstractKernel{T}

Given an operator Op, construct its free-space hypersingular kernel. This corresponds to the transpose(γ₁,ₓγ₁[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative respect to the target variable of the DoubleLayerKernel.

source
Inti.IntegralOperatorType
struct IntegralOperator{T} <: AbstractMatrix{T}

A discrete linear integral operator given by

\[I[u](x) = \int_{\Gamma\_s} K(x,y)u(y) ds_y, x \in \Gamma_{t}\]

where $\Gamma_s$ and $\Gamma_t$ are the source and target domains, respectively.

source
Inti.IntegralPotentialType
struct IntegralPotential

Represent a potential given by a kernel and a quadrature over which integration is performed.

IntegralPotentials are created using IntegralPotential(kernel, quadrature).

Evaluating an integral potential requires a density σ (defined over the quadrature nodes of the source mesh) and a point x at which to evaluate the integral

\[\int_{\Gamma} K(oldsymbol{x},oldsymbol{y})\sigma(y) ds_y, x \not \in \Gamma\]

Assuming 𝒮 is an integral potential and σ is a vector of values defined on quadrature, calling 𝒮[σ] creates an anonymous function that can be evaluated at any point x.

source
Inti.KronrodType
struct Kronrod{D,N} <: ReferenceQuadrature{D}

N-point Kronrod rule obtained by adding n+1 points to a Gauss quadrature containing n points. The order is either 3n + 1 for n even or 3n + 2 for n odd.

source
Inti.LagrangeElementType
struct LagrangeElement{D,Np,T} <: ReferenceInterpolant{D,T}

A polynomial p : D → T uniquely defined by its Np values on the Np reference nodes of D.

The return type T should be a vector space (i.e. support addition and multiplication by scalars). For istance, T could be a number or a vector, but not a Tuple.

source
Inti.LaplaceMethod
Laplace(; dim)

Laplace's differential operator in dim dimension: $-Δu$. ```

Note the negative sign in the definition.

source
Inti.MeshType
struct Mesh{N,T} <: AbstractMesh{N,T}

Unstructured mesh defined by a set of nodes(of typeSVector{N,T}`), and a dictionary mapping element types to connectivity matrices. Each columns of a given connectivity matrix stores the integer tags of the nodes in the mesh comprising the element.

Additionally, the mesh contains a mapping from EntityKeys to the tags of the elements composing the entity. This can be used to extract submeshes from a given mesh using e.g. view(msh,Γ) or msh[Γ], where Γ is a Domain.

See elements for a way to iterate over the elements of a mesh.

source
Inti.MultiIndexType
MultiIndex{N}

Wrapper around NTuple{N,Int} mimicking a multi-index in ℤ₀ᴺ.

source
Inti.ParametricElementType
ParametricElement{D,T,F} <: ReferenceInterpolant{D,T}

An element represented through a explicit function f mapping D into the element. For performance reasons, f should take as input a StaticVector and return a StaticVector or StaticArray.

See also: ReferenceInterpolant, LagrangeElement

source
Inti.PolynomialSpaceType
struct PolynomialSpace{D,K}

The space of all polynomials of degree ≤K, commonly referred to as ℙₖ.

The type parameter D, of singleton type, is used to determine the reference domain of the polynomial basis. In particular, when D is a hypercube in d dimensions, the precise definition is ℙₖ = span{𝐱ᶿ : 0≤max(θ)≤ K}; when D is a d-dimensional simplex, the space is ℙₖ = span{𝐱ᶿ : 0≤sum(θ)≤ K}, where θ ∈ 𝐍ᵈ is a multi-index.

See also: monomial_basis, lagrange_basis

source
Inti.QuadratureMethod
Quadrature(Ω::Domain; meshsize, qorder)

Construct a Quadrature over the domain Ω with a mesh of size meshsize and quadrature order qorder.

source
Inti.QuadratureMethod
Quadrature(msh::AbstractMesh, etype2qrule::Dict)
 Quadrature(msh::AbstractMesh, qrule::ReferenceQuadrature)
-Quadrature(msh::AbstractMesh; qorder)

Construct a Quadrature for msh, where for each element type E in msh the reference quadrature q = etype2qrule[E] is used. When a single qrule is passed, it is used for all element types in msh.

If an order keyword is passed, a default quadrature of the desired order is used for each element type usig _qrule_for_reference_shape.

For co-dimension one elements, the normal vector is also computed and stored in the QuadratureNodes.

source
Inti.QuadratureNodeType
QuadratureNode{N,T<:Real}

A point in ℝᴺ with a weight for performing numerical integration. A QuadratureNode can optionally store a normal vector.

source
Inti.ReferenceCubeType
const ReferenceCube = ReferenceHyperCube{3}

Singleton type representing the unit cube [0,1]³.

source
Inti.ReferenceHyperCubeType
struct ReferenceHyperCube{N} <: ReferenceShape{N}

Singleton type representing the axis-aligned hypercube in N dimensions with the lower corner at the origin and the upper corner at (1,1,…,1).

source
Inti.ReferenceInterpolantType
abstract type ReferenceInterpolant{D,T}

Interpolanting function mapping points on the domain D<:ReferenceShape (of singleton type) to a value of type T.

Instances el of ReferenceInterpolant are expected to implement:

  • el(x̂): evaluate the interpolation scheme at the (reference) coordinate x̂ ∈ D.
  • jacobian(el,x̂) : evaluate the jacobian matrix of the interpolation at the (reference) coordinate x ∈ D.
Note

For performance reasons, both el(x̂) and jacobian(el,x̂) should take as input a StaticVector and output a static vector or static array.

source
Inti.ReferenceLineType
const ReferenceLine = ReferenceHyperCube{1}

Singleton type representing the [0,1] segment.

source
Inti.ReferenceQuadratureType
abstract type ReferenceQuadrature{D}

A quadrature rule for integrating a function over the domain D <: ReferenceShape.

Calling x,w = q() returns the nodes x, given as SVectors, and weights w, for performing integration over domain(q).

source
Inti.ReferenceShapeType
abstract type ReferenceShape

A fixed reference domain/shape. Used mostly for defining more complex shapes as transformations mapping an ReferenceShape to some region of ℜᴹ.

See e.g. ReferenceLine or ReferenceTriangle for some examples of concrete subtypes.

source
Inti.ReferenceSimplexType
struct ReferenceSimplex{N}

Singleton type representing the N-simplex with N+1 vertices (0,...,0),(0,...,0,1),(0,...,0,1,0),(1,0,...,0)

source
Inti.ReferenceSquareType
const ReferenceSquare = ReferenceHyperCube{2}

Singleton type representing the unit square [0,1]².

source
Inti.ReferenceTetrahedronType
struct ReferenceTetrahedron

Singleton type representing the tetrahedron with vertices (0,0,0),(0,0,1),(0,1,0),(1,0,0)

source
Inti.SingleLayerKernelType
struct SingleLayerKernel{T,Op} <: AbstractKernel{T}

The free-space single-layer kernel (i.e. the fundamental solution) of an Op <: AbstractDifferentialOperator.

source
Inti.StokesMethod
Stokes(; μ, dim)

Stokes operator in dim dimensions: $[-μΔu + ∇p, ∇⋅u]$.

source
Inti.SubMeshType
struct SubMesh{N,T} <: AbstractMesh{N,T}

View into a parent mesh over a given domain.

A submesh implements the interface for AbstractMesh; therefore you can iterate over elements of the submesh just like you would with a mesh.

Construct SubMeshs using view(parent,Ω::Domain).

source
Inti.TensorProductQuadratureType
TensorProductQuadrature{N,Q}

A tensor-product of one-dimension quadrature rules. Integrates over [0,1]^N.

Examples

qx = Inti.Fejer(10)
+Quadrature(msh::AbstractMesh; qorder)

Construct a Quadrature for msh, where for each element type E in msh the reference quadrature q = etype2qrule[E] is used. When a single qrule is passed, it is used for all element types in msh.

If an order keyword is passed, a default quadrature of the desired order is used for each element type usig _qrule_for_reference_shape.

For co-dimension one elements, the normal vector is also computed and stored in the QuadratureNodes.

source
Inti.QuadratureNodeType
QuadratureNode{N,T<:Real}

A point in ℝᴺ with a weight for performing numerical integration. A QuadratureNode can optionally store a normal vector.

source
Inti.ReferenceCubeType
const ReferenceCube = ReferenceHyperCube{3}

Singleton type representing the unit cube [0,1]³.

source
Inti.ReferenceHyperCubeType
struct ReferenceHyperCube{N} <: ReferenceShape{N}

Singleton type representing the axis-aligned hypercube in N dimensions with the lower corner at the origin and the upper corner at (1,1,…,1).

source
Inti.ReferenceInterpolantType
abstract type ReferenceInterpolant{D,T}

Interpolanting function mapping points on the domain D<:ReferenceShape (of singleton type) to a value of type T.

Instances el of ReferenceInterpolant are expected to implement:

  • el(x̂): evaluate the interpolation scheme at the (reference) coordinate x̂ ∈ D.
  • jacobian(el,x̂) : evaluate the jacobian matrix of the interpolation at the (reference) coordinate x ∈ D.
Note

For performance reasons, both el(x̂) and jacobian(el,x̂) should take as input a StaticVector and output a static vector or static array.

source
Inti.ReferenceLineType
const ReferenceLine = ReferenceHyperCube{1}

Singleton type representing the [0,1] segment.

source
Inti.ReferenceQuadratureType
abstract type ReferenceQuadrature{D}

A quadrature rule for integrating a function over the domain D <: ReferenceShape.

Calling x,w = q() returns the nodes x, given as SVectors, and weights w, for performing integration over domain(q).

source
Inti.ReferenceShapeType
abstract type ReferenceShape

A fixed reference domain/shape. Used mostly for defining more complex shapes as transformations mapping an ReferenceShape to some region of ℜᴹ.

See e.g. ReferenceLine or ReferenceTriangle for some examples of concrete subtypes.

source
Inti.ReferenceSimplexType
struct ReferenceSimplex{N}

Singleton type representing the N-simplex with N+1 vertices (0,...,0),(0,...,0,1),(0,...,0,1,0),(1,0,...,0)

source
Inti.ReferenceSquareType
const ReferenceSquare = ReferenceHyperCube{2}

Singleton type representing the unit square [0,1]².

source
Inti.ReferenceTetrahedronType
struct ReferenceTetrahedron

Singleton type representing the tetrahedron with vertices (0,0,0),(0,0,1),(0,1,0),(1,0,0)

source
Inti.SingleLayerKernelType
struct SingleLayerKernel{T,Op} <: AbstractKernel{T}

The free-space single-layer kernel (i.e. the fundamental solution) of an Op <: AbstractDifferentialOperator.

source
Inti.StokesMethod
Stokes(; μ, dim)

Stokes operator in dim dimensions: $[-μΔu + ∇p, ∇⋅u]$.

source
Inti.SubMeshType
struct SubMesh{N,T} <: AbstractMesh{N,T}

View into a parent mesh over a given domain.

A submesh implements the interface for AbstractMesh; therefore you can iterate over elements of the submesh just like you would with a mesh.

Construct SubMeshs using view(parent,Ω::Domain).

source
Inti.TensorProductQuadratureType
TensorProductQuadrature{N,Q}

A tensor-product of one-dimension quadrature rules. Integrates over [0,1]^N.

Examples

qx = Inti.Fejer(10)
 qy = Inti.Fejer(15)
-q  = Inti.TensorProductQuadrature(qx,qy)
source
Inti.VioreanuRokhlinType
struct VioreanuRokhlin{D,N} <: ReferenceQuadrature{D}

Tabulated N-point Vioreanu-Rokhlin quadrature rule for integration over D.

source
Inti.YukawaMethod
Yukawa(; λ, dim)

Yukawa operator, also known as modified Helmholtz, in dim dimensions: $-Δu + λ²u$.

The parameter λ is a positive number. Note the negative sign in front of the Laplacian.

source
Base.iterateFunction
iterate(Ω::Domain)

Iterating over a domain means iterating over its entities.

source
Inti._copyto!Method
_copyto!(target,source)

Defaults to Base.copyto!, but includes some specialized methods to copy from a Matrix of SMatrix to a Matrix of Numbers and viceversa.

source
Inti._green_multiplierMethod
_green_multiplier(s::Symbol)

Return -1.0 if s == :inside, 0.0 if s == :outside, and -0.5 if s == :on; otherwise, throw an error. The orientation is relative to the normal of the bounding curve/surface.

source
Inti._green_multiplierMethod
_green_multiplier(x, quad)

Helper function to help determine the constant σ in the Green identity S[γ₁u](x)

  • D[γ₀u](x) + σ*u(x) = 0. This can be used as a predicate to determine whether a

point is inside a domain or not.

source
Inti._meshgenMethod
_meshgen(f,d::HyperRectangle,sz)

Create prod(sz) elements of ParametricElement type representing the push forward of f on each of the subdomains defined by a uniform cartesian mesh of d of size sz.

source
Inti._normalMethod
_normal(jac::SMatrix{M,N})

Given a an M by N matrix representing the jacobian of a codimension one object, compute the normal vector.

source
Inti.acornMethod
acorn(; translation, rotation, scaling, labels)

Create an acorn entity in 3D, and apply optional transformations. Returns the key.

source
Inti.adaptive_correctionMethod
adaptive_correction(iop::IntegralOperator; tol, maxdist = farfield_distance(iop; atol), maxsplit = 1000])

Given an integral operator iop, this function provides a sparse correction to iop for the entries i,j such that the distance between the i-th target and the j-th source is less than maxdist.

Choosing maxdist is a trade-off between accuracy and efficiency. The smaller the value, the fewer corrections are needed, but this may compromise the accuracy. For a fixed quadrature, the size of maxdist has to grow as the tolerance tol decreases. The default [farfield_distance(iop; tol)](@ref) provides a heuristic to determine a suitablemaxdist`.

The correction is computed by using the adaptive_integration routine, with a tolerance atol and a maximum number of subdivisions maxsplit; see adaptive_integration for more details.

source
Inti.adaptive_integrationMethod
adaptive_integration(f, τ̂::RefernceShape; kwargs...)
-adaptive_integration(f, qrule::EmbeddedQuadrature; kwargs...)

Use an adaptive procedure to estimate the integral of f over τ̂ = domain(qrule). The following optional keyword arguments are available:

  • atol::Real=0.0: absolute tolerance for the integral estimate
  • rtol::Real=0.0: relative tolerance for the integral estimate
  • maxsplit::Int=1000: maximum number of times to split the domain
  • norm::Function=LinearAlgebra.norm: norm to use for error estimates
  • buffer::BinaryHeap: a pre-allocated buffer to use for the adaptive procedure (see allocate_buffer)
source
Inti.adaptive_integration_singularMethod
adaptive_integration_singular(f, τ̂, x̂ₛ; kwargs...)

Similar to adaptive_integration, but indicates that f has an isolated (integrable) singularity at x̂ₛ ∈ x̂ₛ.

The integration is performed by splitting τ̂ so that x̂ₛ is a fixed vertex, guaranteeing that f is never evaluated at x̂ₛ. Aditionally, a suitable change of variables may be applied to alleviate the singularity and improve the rate of convergence.

source
Inti.adj_double_layer_hypersingularMethod
adj_double_layer_hypersingular(; op, target, source, compression,
-correction)

Similar to single_double_layer, but for the adjoint double-layer and hypersingular operators. See the documentation of [single_double_layer] for a description of the arguments.

source
Inti.ambient_dimensionFunction
ambient_dimension(x)

Dimension of the ambient space where x lives. For geometrical objects this can differ from its geometric_dimension; for example a triangle in ℝ³ has ambient dimension 3 but geometric dimension 2, while a curve in ℝ³ has ambient dimension 3 but geometric dimension 1.

source
Inti.assemble_fmmMethod
assemble_fmm(iop; atol)

Set up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.

FMMLIB2D

FMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.

source
Inti.assemble_hmatrixMethod
assemble_hmatrix(iop[; atol, rank, rtol, eta])

Assemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.

See the documentation of HMatrices for more details on usage and other keyword arguments.

source
Inti.assemble_matrixMethod
assemble_matrix(iop::IntegralOperator; threads = true)

Assemble a dense matrix representation of an IntegralOperator.

source
Inti.bdim_correctionMethod
bdim_correction(op,X,Y,S,D; green_multiplier, kwargs...)

Given a op and a (possibly innacurate) discretizations of its single and double-layer operators S and D (taking a vector of values on Y and returning a vector on of values on X), compute corrections δS and δD such that S + δS and D + δD are more accurate approximations of the underlying single- and double-layer integral operators.

See [7] for more details on the method.

Arguments

Required:

  • op must be an AbstractDifferentialOperator
  • Y must be a Quadrature object of a closed surface
  • X is either inside, outside, or on Y
  • S and D are approximations to the single- and double-layer operators for op taking densities in Y and returning densities in X.
  • green_multiplier (keyword argument) is a vector with the same length as X storing the value of μ(x) for x ∈ X in the Green identity S\[γ₁u\](x) - D\[γ₀u\](x) + μ*u(x) = 0. See _green_multiplier.

Optional kwargs:

  • parameters::DimParameters: parameters associated with the density interpolation method
  • derivative: if true, compute the correction to the adjoint double-layer and hypersingular operators instead. In this case, S and D should be replaced by a (possibly innacurate) discretization of adjoint double-layer and hypersingular operators, respectively.
  • maxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections when X and Y are different surfaces. When X === Y, this is not needed.
source
Inti.beanMethod
bean(; translation, rotation, scaling, labels)

Create a bean entity in 3D, and apply optional transformations. Returns the key.

source
Inti.boundary_idxsMethod
boundary_idxs(el::LagrangeElement)

The indices of the nodes in el that define the boundary of the element.

source
Inti.cart2sphMethod
cart2sph(x,y,z)

Map cartesian coordinates x,y,z to spherical ones r, θ, φ representing the radius, elevation, and azimuthal angle respectively. The convention followed is that 0 ≤ θ ≤ π and -π < φ ≤ π. Same as the cart2sph function in MATLAB.

source
Inti.connectivityMethod
connectivity(msh::AbstractMesh,E::DataType)

Return the connectivity matrix for elements of type E in msh. The integer tags in the matrix refer to the points in nodes(msh)

source
Inti.cushionMethod
cushion(; translation, rotation, scaling, labels)

Create a cushion entity in 3D, and apply optional transformations. Returns the key.

source
Inti.decomposeFunction
decompose(s::ReferenceShape,x)

Decompose an ReferenceShape into LagrangeElements so that x is a fixed vertex of the children elements.

The decomposed elements may be oriented differently than the parent, and thus care has to be taken regarding e.g. normal vectors.

source
Inti.degreeMethod
degree(el::LagrangeElement)
-degree(el::Type{<:LagrangeElement})

The polynomial degree el.

source
Inti.dimensionMethod
dimension(space)

The length of a basis for space; i.e. the number of linearly independent elements required to span space.

source
Inti.dom2eltMethod
dom2elt(m::Mesh,Ω,E)::Vector{Int}

Compute the element indices idxs of the elements of type E composing Ω.

source
Inti.dom2qtagsMethod
dom2qtags(Q::Quadrature, dom::Domain)

Given a domain, return the indices of the quadratures nodes in Q associated to its quadrature.

source
Inti.domainFunction
domain(f)

Given a function-like object f: Ω → R, return Ω.

source
Inti.domainMethod
domain(msh::AbstractMesh)

Return a [Domain] containing of all entities covered by the mesh.

source
Inti.domainMethod
domain(q::ReferenceQuadrature)

The domain of integratino for quadrature rule q.

source
Inti.elementsFunction
elements(msh::AbstractMesh [, E::DataType])

Return the elements of a msh. Passing and element type E will restricts to elements of that type.

A common pattern to avoid type-instabilies in performance critical parts of the code is to use a function barrier, as illustrated below:

for E in element_types(msh)
+q  = Inti.TensorProductQuadrature(qx,qy)
source
Inti.VioreanuRokhlinType
struct VioreanuRokhlin{D,N} <: ReferenceQuadrature{D}

Tabulated N-point Vioreanu-Rokhlin quadrature rule for integration over D.

source
Inti.YukawaMethod
Yukawa(; λ, dim)

Yukawa operator, also known as modified Helmholtz, in dim dimensions: $-Δu + λ²u$.

The parameter λ is a positive number. Note the negative sign in front of the Laplacian.

source
Base.iterateFunction
iterate(Ω::Domain)

Iterating over a domain means iterating over its entities.

source
Inti._copyto!Method
_copyto!(target,source)

Defaults to Base.copyto!, but includes some specialized methods to copy from a Matrix of SMatrix to a Matrix of Numbers and viceversa.

source
Inti._green_multiplierMethod
_green_multiplier(s::Symbol)

Return -1.0 if s == :inside, 0.0 if s == :outside, and -0.5 if s == :on; otherwise, throw an error. The orientation is relative to the normal of the bounding curve/surface.

source
Inti._green_multiplierMethod
_green_multiplier(x, quad)

Helper function to help determine the constant σ in the Green identity S[γ₁u](x)

  • D[γ₀u](x) + σ*u(x) = 0. This can be used as a predicate to determine whether a

point is inside a domain or not.

source
Inti._meshgenMethod
_meshgen(f,d::HyperRectangle,sz)

Create prod(sz) elements of ParametricElement type representing the push forward of f on each of the subdomains defined by a uniform cartesian mesh of d of size sz.

source
Inti._normalMethod
_normal(jac::SMatrix{M,N})

Given a an M by N matrix representing the jacobian of a codimension one object, compute the normal vector.

source
Inti.acornMethod
acorn(; translation, rotation, scaling, labels)

Create an acorn entity in 3D, and apply optional transformations. Returns the key.

source
Inti.adaptive_correctionMethod
adaptive_correction(iop::IntegralOperator; tol, maxdist = farfield_distance(iop; atol), maxsplit = 1000])

Given an integral operator iop, this function provides a sparse correction to iop for the entries i,j such that the distance between the i-th target and the j-th source is less than maxdist.

Choosing maxdist is a trade-off between accuracy and efficiency. The smaller the value, the fewer corrections are needed, but this may compromise the accuracy. For a fixed quadrature, the size of maxdist has to grow as the tolerance tol decreases. The default [farfield_distance(iop; tol)](@ref) provides a heuristic to determine a suitablemaxdist`.

The correction is computed by using the adaptive_integration routine, with a tolerance atol and a maximum number of subdivisions maxsplit; see adaptive_integration for more details.

source
Inti.adaptive_integrationMethod
adaptive_integration(f, τ̂::RefernceShape; kwargs...)
+adaptive_integration(f, qrule::EmbeddedQuadrature; kwargs...)

Use an adaptive procedure to estimate the integral of f over τ̂ = domain(qrule). The following optional keyword arguments are available:

  • atol::Real=0.0: absolute tolerance for the integral estimate
  • rtol::Real=0.0: relative tolerance for the integral estimate
  • maxsplit::Int=1000: maximum number of times to split the domain
  • norm::Function=LinearAlgebra.norm: norm to use for error estimates
  • buffer::BinaryHeap: a pre-allocated buffer to use for the adaptive procedure (see allocate_buffer)
source
Inti.adaptive_integration_singularMethod
adaptive_integration_singular(f, τ̂, x̂ₛ; kwargs...)

Similar to adaptive_integration, but indicates that f has an isolated (integrable) singularity at x̂ₛ ∈ x̂ₛ.

The integration is performed by splitting τ̂ so that x̂ₛ is a fixed vertex, guaranteeing that f is never evaluated at x̂ₛ. Aditionally, a suitable change of variables may be applied to alleviate the singularity and improve the rate of convergence.

source
Inti.adj_double_layer_hypersingularMethod
adj_double_layer_hypersingular(; op, target, source, compression,
+correction)

Similar to single_double_layer, but for the adjoint double-layer and hypersingular operators. See the documentation of [single_double_layer] for a description of the arguments.

source
Inti.ambient_dimensionFunction
ambient_dimension(x)

Dimension of the ambient space where x lives. For geometrical objects this can differ from its geometric_dimension; for example a triangle in ℝ³ has ambient dimension 3 but geometric dimension 2, while a curve in ℝ³ has ambient dimension 3 but geometric dimension 1.

source
Inti.assemble_fmmMethod
assemble_fmm(iop; atol)

Set up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.

FMMLIB2D

FMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.

source
Inti.assemble_hmatrixMethod
assemble_hmatrix(iop[; atol, rank, rtol, eta])

Assemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.

See the documentation of HMatrices for more details on usage and other keyword arguments.

source
Inti.assemble_matrixMethod
assemble_matrix(iop::IntegralOperator; threads = true)

Assemble a dense matrix representation of an IntegralOperator.

source
Inti.bdim_correctionMethod
bdim_correction(op,X,Y,S,D; green_multiplier, kwargs...)

Given a op and a (possibly innacurate) discretizations of its single and double-layer operators S and D (taking a vector of values on Y and returning a vector on of values on X), compute corrections δS and δD such that S + δS and D + δD are more accurate approximations of the underlying single- and double-layer integral operators.

See [7] for more details on the method.

Arguments

Required:

  • op must be an AbstractDifferentialOperator
  • Y must be a Quadrature object of a closed surface
  • X is either inside, outside, or on Y
  • S and D are approximations to the single- and double-layer operators for op taking densities in Y and returning densities in X.
  • green_multiplier (keyword argument) is a vector with the same length as X storing the value of μ(x) for x ∈ X in the Green identity S\[γ₁u\](x) - D\[γ₀u\](x) + μ*u(x) = 0. See _green_multiplier.

Optional kwargs:

  • parameters::DimParameters: parameters associated with the density interpolation method
  • derivative: if true, compute the correction to the adjoint double-layer and hypersingular operators instead. In this case, S and D should be replaced by a (possibly innacurate) discretization of adjoint double-layer and hypersingular operators, respectively.
  • maxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections when X and Y are different surfaces. When X === Y, this is not needed.
source
Inti.beanMethod
bean(; translation, rotation, scaling, labels)

Create a bean entity in 3D, and apply optional transformations. Returns the key.

source
Inti.boundary_idxsMethod
boundary_idxs(el::LagrangeElement)

The indices of the nodes in el that define the boundary of the element.

source
Inti.cart2sphMethod
cart2sph(x,y,z)

Map cartesian coordinates x,y,z to spherical ones r, θ, φ representing the radius, elevation, and azimuthal angle respectively. The convention followed is that 0 ≤ θ ≤ π and -π < φ ≤ π. Same as the cart2sph function in MATLAB.

source
Inti.connectivityMethod
connectivity(msh::AbstractMesh,E::DataType)

Return the connectivity matrix for elements of type E in msh. The integer tags in the matrix refer to the points in nodes(msh)

source
Inti.cushionMethod
cushion(; translation, rotation, scaling, labels)

Create a cushion entity in 3D, and apply optional transformations. Returns the key.

source
Inti.decomposeFunction
decompose(s::ReferenceShape,x)

Decompose an ReferenceShape into LagrangeElements so that x is a fixed vertex of the children elements.

The decomposed elements may be oriented differently than the parent, and thus care has to be taken regarding e.g. normal vectors.

source
Inti.degreeMethod
degree(el::LagrangeElement)
+degree(el::Type{<:LagrangeElement})

The polynomial degree el.

source
Inti.dimensionMethod
dimension(space)

The length of a basis for space; i.e. the number of linearly independent elements required to span space.

source
Inti.dom2eltMethod
dom2elt(m::Mesh,Ω,E)::Vector{Int}

Compute the element indices idxs of the elements of type E composing Ω.

source
Inti.dom2qtagsMethod
dom2qtags(Q::Quadrature, dom::Domain)

Given a domain, return the indices of the quadratures nodes in Q associated to its quadrature.

source
Inti.domainFunction
domain(f)

Given a function-like object f: Ω → R, return Ω.

source
Inti.domainMethod
domain(msh::AbstractMesh)

Return a [Domain] containing of all entities covered by the mesh.

source
Inti.domainMethod
domain(q::ReferenceQuadrature)

The domain of integratino for quadrature rule q.

source
Inti.elementsFunction
elements(msh::AbstractMesh [, E::DataType])

Return the elements of a msh. Passing and element type E will restricts to elements of that type.

A common pattern to avoid type-instabilies in performance critical parts of the code is to use a function barrier, as illustrated below:

for E in element_types(msh)
     _long_computation(elements(msh, E), args...)
 end
 
@@ -39,11 +39,11 @@
     for el in iter # the type of el is known at compile time
         # do something with el
     end
-end

where a dynamic dispatch is performed only on the element types (typically small for a given mesh).

source
Inti.ellipsoidMethod
ellipsoid(; translation, rotation, scaling, labels)

Create an ellipsoid entity in 3D, and apply optional transformations. Returns the key of the created entity.

source
Inti.ent2etagsMethod
ent2etags(msh::AbstractMesh)

Return a dictionary mapping entities to a dictionary of element types to element tags.

source
Inti.etype_to_nearest_pointsMethod
etype_to_nearest_points(X,Y::Quadrature; maxdist)

For each element el in Y.mesh, return a list with the indices of all points in X for which el is the nearest element. Ignore indices for which the distance exceeds maxdist.

source
Inti.farfield_distanceMethod
farfield_distance(iop::IntegralOperator; tol, maxiter = 10)
-farfield_distance(K, Q::Quadrature; tol, maxiter = 10)

Return an estimate of the distance d such that the (absolute) quadrature error of the integrand y -> K(x,y) is below tol for x at a distance d from the center of the largest element in Q; when an integral operator is passed, we have Q::Quadrature = source(iop) and K = kernel(iop).

The estimate is computed by finding the first integer n such that the quadrature error on the largest element τ lies below tol for points x satisfying dist(x,center(τ)) = n*radius(τ).

Note that the desired tolerance may not be achievable if the quadrature rule is not accurate enough, or if τ is not sufficiently small, and therefore a maximum number of iterations maxiter is provided to avoid an infinite loops. In such cases, it is recommended that you either increase the quadrature order, or decrease the mesh size.

Note: this is obviously a heuristic, and may not be accurate in all cases.

source
Inti.fibonnaci_points_sphereMethod
fibonnaci_points_sphere(N,r,c)

Return N points distributed (roughly) in a uniform manner on the sphere of radius r centered at c.

source
Inti.flip_normalMethod
flip_normal(q::QuadratureNode)

Return a new QuadratureNode with the normal vector flipped.

source
Inti.geometric_dimensionFunction
geometric_dimension(x)

NNumber of degrees of freedom necessary to locally represent the geometrical object. For example, lines have geometric dimension of 1 (whether in ℝ² or in ℝ³), while surfaces have geometric dimension of 2.

source
Inti.gmsh_curveMethod
gmsh_curve(f::Function, a, b; npts=100, meshsize = 0, tag=-1)

Create a curve in the current gmsh model given by {f(t) : t ∈ (a,b) } where f is a function from to ℝ^3. The curve is approximated by C² b-splines passing through npts equispaced in parameter space. If a meshsize is given, gmsh will use it when meshing the curve.

source
Inti.hessianMethod
hesssian(el,x)

Given a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m × m matrix Aᵢⱼⱼ = ∂²fᵢ/∂xⱼ∂xⱼ. By default ForwardDiff is used to compute the hessian, but you should overload this method for specific f if better performance and/or precision is required.

Note: both x and f(x) are expected to be of SVector type.

source
Inti.imageFunction
image(f)

Given a function-like object f: Ω → R, return f(Ω).

source
Inti.import_meshMethod
import_mesh(filename = nothing; dim=3)

Open filename and create a Mesh from the gmsh model in it.

If filename is nothing, the current gmsh model is used. Note that this assumes that the Gmsh API has been initialized through gmsh.initialize.

Passing dim=2 will create a two-dimensional mesh by projecting the original mesh onto the x,y plane.

source
Inti.integrateMethod
integrate(f,quad::Quadrature)

Compute ∑ᵢ f(qᵢ)wᵢ, where the qᵢ are the quadrature nodes of quad, and wᵢ are the quadrature weights.

Note that you must define f(::QuadratureNode): use q.coords and q.normal if you need to access the coordinate or normal vector at que quadrature node.

source
Inti.integrateMethod
integrate(f,q::ReferenceQuadrature)
-integrate(f,x,w)

Integrate the function f using the quadrature rule q. This is simply sum(f.(x) .* w), where x and w are the quadrature nodes and weights, respectively.

The function f should take an SVector as input.

source
Inti.integrate_with_error_estimateFunction
integrate_with_error_estimate(f, quad::EmbeddedQuadrature, norm = LinearAlgebra.norm)

Return I, E where I is the estimated integral of f over domain(quad) using the high-order quadrature and E is the error estimate obtained by taking the norm of the difference between the high and low-order quadratures in quad.

source
Inti.integration_measureMethod
integration_measure(f, x̂)

Given the Jacobian matrix J of a transformation f : ℝᴹ → ℝᴺ compute the integration measure √det(JᵀJ) at the parametric coordinate

source
Inti.interface_methodMethod
interface_method(x)

A method of an abstract type for which concrete subtypes are expected to provide an implementation.

source
Inti.internal_boundaryMethod
internal_boundary(Ω::Domain)

Return the internal boundaries of a Domain. These are entities in skeleton(Ω) which appear at least twice as a boundary of entities in Ω.

source
Inti.interpolation_orderMethod
interpolation_order(qrule::ReferenceQuadrature)

The interpolation order of a quadrature rule is defined as the the smallest k such that there exists a unique polynomial in PolynomialSpace{D,k} that minimizes the error in approximating the function f at the quadrature nodes.

For an N-point Gauss quadrature rule on the segment, the interpolation order is N-1 since N points uniquely determine a polynomial of degree N-1.

For a triangular reference domain, the interpolation order is more difficult to define. An unisolvent three-node quadrature on the triangular, for example, has an interpolation order k=1 since the three nodes uniquely determine a linear polynomial, but a four-node quadrature may also have an interpolation order k=1 since for k=2 there are multiple polynomials that pass through the four nodes.

source
Inti.jacobianMethod
jacobian(f,x)

Given a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m matrix Aᵢⱼ = ∂fᵢ/∂xⱼ. By default ForwardDiff is used to compute the jacobian, but you should overload this method for specific f if better performance and/or precision is required.

Note: both x and f(x) are expected to be of SVector type.

source
Inti.kress_change_of_variablesMethod
kress_change_of_variables(P)

Return a change of variables mapping [0,1] to [0,1] with the property that the first P-1 derivatives of the transformation vanish at x=0.

source
Inti.kress_change_of_variables_periodicMethod
kress_change_of_variables_periodic(P)

Like kress_change_of_variables, this change of variables maps the interval [0,1] onto itself, but the first P derivatives of the transformation vanish at both endpoints (thus making it a periodic function).

This change of variables can be used to periodize integrals over the interval [0,1] by mapping the integrand into a new integrand that vanishes (to order P) at both endpoints.

source
Inti.lagrange_basisMethod
lagrange_basis(nodes,[sp::AbstractPolynomialSpace])

Return the set of n polynomials in sp taking the value of 1 on node i and 0 on nodes j ≂̸ i for 1 ≤ i ≤ n.

source
Inti.lagrange_basisMethod
lagrange_basis(qrule::ReferenceQuadrature)

Return a function L : ℝᴺ → ℝᵖ where N is the dimension of the domain of qrule, and p is the number of nodes in qrule. The function L is a polynomial in polynomial_space(qrule), and L(xⱼ)[i] = δᵢⱼ (i.e. the ith component of L is the ith Lagrange basis).

source
Inti.lagrange_basisMethod
lagrange_basis(E::Type{<:LagrangeElement})

Return the Lagrange basis B for the element E. Evaluating B(x) yields the value of each basis function at x.

source
Inti.lineMethod
line(a,b)

Create a [GeometricEntity] representing a straight line connecting points a and b. The points a and b can be either SVectors or a Tuple.

The parametrization of the line is given by f(u) = a + u(b - a), where 0 ≤ u ≤ 1.

source
Inti.measureFunction
measure(k::EntityKey, rtol)

Compute the length/area/volume of the entity k using an adaptive quadrature with a relative tolerance rtol. Assumes that the entity has an explicit parametrization.

source
Inti.meshgenMethod
meshgen(Ω, n)
+end

where a dynamic dispatch is performed only on the element types (typically small for a given mesh).

source
Inti.ellipsoidMethod
ellipsoid(; translation, rotation, scaling, labels)

Create an ellipsoid entity in 3D, and apply optional transformations. Returns the key of the created entity.

source
Inti.ent2etagsMethod
ent2etags(msh::AbstractMesh)

Return a dictionary mapping entities to a dictionary of element types to element tags.

source
Inti.etype_to_nearest_pointsMethod
etype_to_nearest_points(X,Y::Quadrature; maxdist)

For each element el in Y.mesh, return a list with the indices of all points in X for which el is the nearest element. Ignore indices for which the distance exceeds maxdist.

source
Inti.farfield_distanceMethod
farfield_distance(iop::IntegralOperator; tol, maxiter = 10)
+farfield_distance(K, Q::Quadrature; tol, maxiter = 10)

Return an estimate of the distance d such that the (absolute) quadrature error of the integrand y -> K(x,y) is below tol for x at a distance d from the center of the largest element in Q; when an integral operator is passed, we have Q::Quadrature = source(iop) and K = kernel(iop).

The estimate is computed by finding the first integer n such that the quadrature error on the largest element τ lies below tol for points x satisfying dist(x,center(τ)) = n*radius(τ).

Note that the desired tolerance may not be achievable if the quadrature rule is not accurate enough, or if τ is not sufficiently small, and therefore a maximum number of iterations maxiter is provided to avoid an infinite loops. In such cases, it is recommended that you either increase the quadrature order, or decrease the mesh size.

Note: this is obviously a heuristic, and may not be accurate in all cases.

source
Inti.fibonnaci_points_sphereMethod
fibonnaci_points_sphere(N,r,c)

Return N points distributed (roughly) in a uniform manner on the sphere of radius r centered at c.

source
Inti.flip_normalMethod
flip_normal(q::QuadratureNode)

Return a new QuadratureNode with the normal vector flipped.

source
Inti.geometric_dimensionFunction
geometric_dimension(x)

NNumber of degrees of freedom necessary to locally represent the geometrical object. For example, lines have geometric dimension of 1 (whether in ℝ² or in ℝ³), while surfaces have geometric dimension of 2.

source
Inti.gmsh_curveMethod
gmsh_curve(f::Function, a, b; npts=100, meshsize = 0, tag=-1)

Create a curve in the current gmsh model given by {f(t) : t ∈ (a,b) } where f is a function from to ℝ^3. The curve is approximated by C² b-splines passing through npts equispaced in parameter space. If a meshsize is given, gmsh will use it when meshing the curve.

source
Inti.hessianMethod
hesssian(el,x)

Given a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m × m matrix Aᵢⱼⱼ = ∂²fᵢ/∂xⱼ∂xⱼ. By default ForwardDiff is used to compute the hessian, but you should overload this method for specific f if better performance and/or precision is required.

Note: both x and f(x) are expected to be of SVector type.

source
Inti.imageFunction
image(f)

Given a function-like object f: Ω → R, return f(Ω).

source
Inti.import_meshMethod
import_mesh(filename = nothing; dim=3)

Open filename and create a Mesh from the gmsh model in it.

If filename is nothing, the current gmsh model is used. Note that this assumes that the Gmsh API has been initialized through gmsh.initialize.

Passing dim=2 will create a two-dimensional mesh by projecting the original mesh onto the x,y plane.

source
Inti.integrateMethod
integrate(f,quad::Quadrature)

Compute ∑ᵢ f(qᵢ)wᵢ, where the qᵢ are the quadrature nodes of quad, and wᵢ are the quadrature weights.

Note that you must define f(::QuadratureNode): use q.coords and q.normal if you need to access the coordinate or normal vector at que quadrature node.

source
Inti.integrateMethod
integrate(f,q::ReferenceQuadrature)
+integrate(f,x,w)

Integrate the function f using the quadrature rule q. This is simply sum(f.(x) .* w), where x and w are the quadrature nodes and weights, respectively.

The function f should take an SVector as input.

source
Inti.integrate_with_error_estimateFunction
integrate_with_error_estimate(f, quad::EmbeddedQuadrature, norm = LinearAlgebra.norm)

Return I, E where I is the estimated integral of f over domain(quad) using the high-order quadrature and E is the error estimate obtained by taking the norm of the difference between the high and low-order quadratures in quad.

source
Inti.integration_measureMethod
integration_measure(f, x̂)

Given the Jacobian matrix J of a transformation f : ℝᴹ → ℝᴺ compute the integration measure √det(JᵀJ) at the parametric coordinate

source
Inti.interface_methodMethod
interface_method(x)

A method of an abstract type for which concrete subtypes are expected to provide an implementation.

source
Inti.internal_boundaryMethod
internal_boundary(Ω::Domain)

Return the internal boundaries of a Domain. These are entities in skeleton(Ω) which appear at least twice as a boundary of entities in Ω.

source
Inti.interpolation_orderMethod
interpolation_order(qrule::ReferenceQuadrature)

The interpolation order of a quadrature rule is defined as the the smallest k such that there exists a unique polynomial in PolynomialSpace{D,k} that minimizes the error in approximating the function f at the quadrature nodes.

For an N-point Gauss quadrature rule on the segment, the interpolation order is N-1 since N points uniquely determine a polynomial of degree N-1.

For a triangular reference domain, the interpolation order is more difficult to define. An unisolvent three-node quadrature on the triangular, for example, has an interpolation order k=1 since the three nodes uniquely determine a linear polynomial, but a four-node quadrature may also have an interpolation order k=1 since for k=2 there are multiple polynomials that pass through the four nodes.

source
Inti.jacobianMethod
jacobian(f,x)

Given a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m matrix Aᵢⱼ = ∂fᵢ/∂xⱼ. By default ForwardDiff is used to compute the jacobian, but you should overload this method for specific f if better performance and/or precision is required.

Note: both x and f(x) are expected to be of SVector type.

source
Inti.kress_change_of_variablesMethod
kress_change_of_variables(P)

Return a change of variables mapping [0,1] to [0,1] with the property that the first P-1 derivatives of the transformation vanish at x=0.

source
Inti.kress_change_of_variables_periodicMethod
kress_change_of_variables_periodic(P)

Like kress_change_of_variables, this change of variables maps the interval [0,1] onto itself, but the first P derivatives of the transformation vanish at both endpoints (thus making it a periodic function).

This change of variables can be used to periodize integrals over the interval [0,1] by mapping the integrand into a new integrand that vanishes (to order P) at both endpoints.

source
Inti.lagrange_basisMethod
lagrange_basis(nodes,[sp::AbstractPolynomialSpace])

Return the set of n polynomials in sp taking the value of 1 on node i and 0 on nodes j ≂̸ i for 1 ≤ i ≤ n.

source
Inti.lagrange_basisMethod
lagrange_basis(qrule::ReferenceQuadrature)

Return a function L : ℝᴺ → ℝᵖ where N is the dimension of the domain of qrule, and p is the number of nodes in qrule. The function L is a polynomial in polynomial_space(qrule), and L(xⱼ)[i] = δᵢⱼ (i.e. the ith component of L is the ith Lagrange basis).

source
Inti.lagrange_basisMethod
lagrange_basis(E::Type{<:LagrangeElement})

Return the Lagrange basis B for the element E. Evaluating B(x) yields the value of each basis function at x.

source
Inti.lineMethod
line(a,b)

Create a [GeometricEntity] representing a straight line connecting points a and b. The points a and b can be either SVectors or a Tuple.

The parametrization of the line is given by f(u) = a + u(b - a), where 0 ≤ u ≤ 1.

source
Inti.measureFunction
measure(k::EntityKey, rtol)

Compute the length/area/volume of the entity k using an adaptive quadrature with a relative tolerance rtol. Assumes that the entity has an explicit parametrization.

source
Inti.meshgenFunction
meshgen(Ω, n)
 meshgen(Ω, n_dict)
-meshgen(Ω; meshsize)

Generate a Mesh for the domain Ω where each curve is meshed using n elements. Passing a dictionary allows for a finer control; in such cases, n_dict[ent] should return an integer for each entity ent in Ω of geometric_dimension one.

Alternatively, a meshsize can be passed, in which case, the number of elements is computed as so as to obtain an average mesh size of meshsize. Note that the actual mesh size may vary significantly for each element if the parametrization is far from uniform.

This function requires the entities forming Ω to have an explicit parametrization.

Mesh quality

The quality of the generated mesh created using meshgen depends on the quality of the underlying parametrization. For complex surfaces, you are better off using a proper mesher such as gmsh.

source
Inti.monomial_basisFunction
monomial_basis(sp::PolynomialSpace)

Return a function f : ℝᴺ → ℝᵈ, where N is the dimension of the domain of sp containing a basis of monomials 𝐱ᶿ spanning the polynomial space PolynomialSpace.

source
Inti.near_interaction_listMethod
near_interaction_list(X,Y::AbstractMesh; tol)

For each element el of type E in Y, return the indices of the points in X which are closer than tol to the center of el.

This function returns a dictionary where e.g. dict[E][5] --> Vector{Int} gives the indices of points in X which are closer than tol to the center of the fifth element of type E.

If tol is a Dict, then tol[E] is the tolerance for elements of type E.

source
Inti.new_tagMethod
new_tag(dim)

Return a new tag for an entity of dimension dim so that EntityKey(dim, tag) is not already in ENTITIES.

source
Inti.nodesMethod
nodes(msh::SubMesh)

A view of the nodes of the parent mesh belonging to the submesh. The ordering is given by the nodetags function.

source
Inti.nodetagsMethod
nodetags(msh::SubMesh)

Return the tags of the nodes in the parent mesh belonging to the submesh.

source
Inti.normalMethod
normal(el, x̂)

Return the normal vector of el at the parametric coordinate .

source
Inti.orderMethod
order(q::ReferenceQuadrature)

A quadrature of order p (sometimes called degree of precision) integrates all polynomials of degree ≤ p but not ≤ p + 1.

source
Inti.orderMethod
order(el::LagrangeElement)

The order of the element's interpolating polynomial (e.g. a LagrangeLine with 2 nodes defines a linear polynomial, and thus has order 1).

source
Inti.parametric_curveMethod
parametric_curve(f, a::Real, b::Real)

Create a [GeometricEntity] representing a parametric curve defined by the {f(t) | a ≤ t ≤ b}. The function f should map a scalar to an SVector.

Flipping the orientation is supported by passing a > b.

source
Inti.parametric_surfaceFunction
    parametric_surface(f, lc, hc, boundary = nothing; kwargs...)

Create a parametric surface defined by the function f over the rectangular domain defined by the lower corner lc and the upper corner hc. The optional boundary argument can be used to specify the boundary curves of the surface.

Arguments

  • f: A function that takes two arguments x and y and returns a tuple (u, v) representing the parametric coordinates of the surface at (x, y).
  • lc: A 2-element array representing the lower corner of the rectangular domain.
  • hc: A 2-element array representing the upper corner of the rectangular domain.
  • boundary: An optional array of boundary curves that define the surface.

Keyword Arguments

  • kwargs: Additional keyword arguments that can be passed to the GeometricEntity constructor.

Returns

  • The key of the created GeometricEntity.
source
Inti.polynomial_solutions_vdimFunction
polynomial_solutions_vdim(op, order[, center])

For every monomial term pₙ of degree order, compute a polynomial Pₙ such that ℒ[Pₙ] = pₙ, where is the differential operator associated with op. This function returns {pₙ,Pₙ,γ₁Pₙ}, where γ₁Pₙ is the generalized Neumann trace of Pₙ.

Passing a point center will shift the monomials and solutions accordingly.

source
Inti.qcoordsMethod
qcoords(q)

Return the coordinate of the quadrature nodes associated with q.

source
Inti.quadrature_to_node_valsMethod
quadrature_to_node_vals(Q::Quadrature, qvals::AbstractVector)

Given a vector qvals of scalar values at the quadrature nodes of Q, return a vector ivals of scalar values at the interpolation nodes of Q.mesh.

source
Inti.reference_nodesMethod
reference_nodes(el::LagrangeElement)
-reference_nodes(::Type{<:LagrangeElement})

Return the reference nodes on domain(el) used for the polynomial interpolation. The function values on these nodes completely determines the interpolating polynomial.

We use the same convention as gmsh for defining the reference nodes and their order (see node ordering on gmsh documentation).

source
Inti.return_typeMethod
return_type(f[,args...])

The type returned by f(args...), where args is a tuple of types. Falls back to Base.promote_op by default.

A functors of type T with a knonw return type should extend return_type(::T,args...) to avoid relying on promote_op.

source
Inti.rotation_matrixMethod
rotation_matrix(rot)

Constructs a rotation matrix given the rotation angles around the x, y, and z axes.

Arguments

  • rot: A tuple or vector containing the rotation angles in radians for each axis.

Returns

  • R::SMatrix: The resulting rotation matrix.
source
Inti.single_double_layerMethod
single_double_layer(; op, target, source::Quadrature, compression,
-correction, derivative = false)

Construct a discrete approximation to the single- and double-layer integral operators for op, mapping values defined on the quadrature nodes of source to values defined on the nodes of target. If derivative = true, return instead the adjoint double-layer and hypersingular operators (which are the derivative of the single- and double-layer, respectively).

You must choose a compression method and a correction method, as described below.

Compression

The compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:

  • (method = :none, ): no compression is performed, the resulting matrices are dense.
  • (method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).
  • (method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).

Correction

The correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:

  • (method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.
  • (method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesourcecurve/surface (which is assumed to be closed). Whentarget === source,target_location` is not needed.
source
Inti.skeletonMethod
skeleton(Ω::Domain)

Return all the boundaries of the domain, i.e. the domain's skeleton.

source
Inti.stack_weakdeps_env!Method
stack_weakdeps_env!(; verbose = false, update = false)

Push to the load stack an environment providing the weak dependencies of Inti.jl. This allows benefiting from additional functionalities of Inti.jl which are powered by weak dependencies without having to manually install them in your environment.

Set update=true if you want to update the weakdeps environment.

Warning

Calling this function can take quite some time, especially the first time around, if packages have to be installed or precompiled. Run in verbose mode to see what is happening.

Examples:

Inti.stack_weakdeps_env!()
-using HMatrices
source
Inti.svectorMethod
svector(f,n)

Create an SVector of length n, computing each element as f(i), where i is the index of the element.

source
Inti.torusMethod
torus(; r, R, translation, rotation, scaling, labels)

Create a torus entity in 3D, and apply optional transformations. Returns the key. The parameters r and R are the inner and outer radii of the torus.

source
Inti.vdim_correctionMethod
vdim_correction(op,X,Y,Y_boundary,S,D,V; green_multiplier, kwargs...)

Compute a correction to the volume potential V : Y → X such that V + δV is a more accurate approximation of the underlying volume potential operator. The correction is computed using the (volume) density interpolation method.

This function requires a op::AbstractDifferentialOperator, a target set X, a source quadrature Y, a boundary quadrature Y_boundary, approximations S : Y_boundary -> X and D : Y_boundary -> X to the single- and double-layer potentials (correctly handling nearly-singular integrals), and a naive approximation of the volume potential V. The green_multiplier is a vector of the same length as X storing the value of μ(x) for x ∈ X in the Green identity (see _green_multiplier).

See [8] for more details on the method.

Optional kwargs:

  • interpolation_order: the order of the polynomial interpolation. By default, the maximum order of the quadrature rules is used.
  • maxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections.
  • center: the center of the basis functions. By default, the basis functions are centered at the origin.
  • shift: a boolean indicating whether the basis functions should be shifted and rescaled to each element.
source
Inti.vdim_mesh_centerMethod
vdim_mesh_center(msh)

Point x which minimizes ∑ (x-xⱼ)²/r²ⱼ, where xⱼ and rⱼ are the circumcenter and circumradius of the elements of msh, respectively.

source
Inti.vertices_idxsMethod
vertices_idxs(el::LagrangeElement)

The indices of the nodes in el that define the vertices of the element.

source
Inti.volume_potentialMethod
volume_potential(; op, target, source::Quadrature, compression, correction)

Compute the volume potential operator for a given PDE.

Arguments

  • op: The PDE (Partial Differential Equation) to solve.
  • target: The target domain where the potential is computed.
  • source: The source domain where the potential is generated.
  • compression: The compression method to use for the potential operator.
  • correction: The correction method to use for the potential operator.

Returns

The volume potential operator V that represents the interaction between the target and source domains.

Compression

The compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:

  • (method = :none, ): no compression is performed, the resulting matrices are dense.
  • (method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).
  • (method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).

Correction

The correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:

  • (method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.
  • (method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesource's boundary. Whentarget === source,target_location` is not needed.

Details

The volume potential operator is computed by assembling the integral operator V using the single-layer kernel G. The operator V is then compressed using the specified compression method. If no compression is specified, the operator is returned as is. If a correction method is specified, the correction is computed and added to the compressed operator.

source
+meshgen(Ω; meshsize)

Generate a Mesh for the domain Ω where each curve is meshed using n elements. Passing a dictionary allows for a finer control; in such cases, n_dict[ent] should return an integer for each entity ent in Ω of geometric_dimension one.

Alternatively, a meshsize can be passed, in which case, the number of elements is computed as so as to obtain an average mesh size of meshsize. Note that the actual mesh size may vary significantly for each element if the parametrization is far from uniform.

This function requires the entities forming Ω to have an explicit parametrization.

Mesh quality

The quality of the generated mesh created using meshgen depends on the quality of the underlying parametrization. For complex surfaces, you are better off using a proper mesher such as gmsh.

source
Inti.monomial_basisFunction
monomial_basis(sp::PolynomialSpace)

Return a function f : ℝᴺ → ℝᵈ, where N is the dimension of the domain of sp containing a basis of monomials 𝐱ᶿ spanning the polynomial space PolynomialSpace.

source
Inti.near_interaction_listMethod
near_interaction_list(X,Y::AbstractMesh; tol)

For each element el of type E in Y, return the indices of the points in X which are closer than tol to the center of el.

This function returns a dictionary where e.g. dict[E][5] --> Vector{Int} gives the indices of points in X which are closer than tol to the center of the fifth element of type E.

If tol is a Dict, then tol[E] is the tolerance for elements of type E.

source
Inti.new_tagMethod
new_tag(dim)

Return a new tag for an entity of dimension dim so that EntityKey(dim, tag) is not already in ENTITIES.

source
Inti.nodesMethod
nodes(msh::SubMesh)

A view of the nodes of the parent mesh belonging to the submesh. The ordering is given by the nodetags function.

source
Inti.nodetagsMethod
nodetags(msh::SubMesh)

Return the tags of the nodes in the parent mesh belonging to the submesh.

source
Inti.normalMethod
normal(el, x̂)

Return the normal vector of el at the parametric coordinate .

source
Inti.orderMethod
order(q::ReferenceQuadrature)

A quadrature of order p (sometimes called degree of precision) integrates all polynomials of degree ≤ p but not ≤ p + 1.

source
Inti.orderMethod
order(el::LagrangeElement)

The order of the element's interpolating polynomial (e.g. a LagrangeLine with 2 nodes defines a linear polynomial, and thus has order 1).

source
Inti.parametric_curveMethod
parametric_curve(f, a::Real, b::Real)

Create a [GeometricEntity] representing a parametric curve defined by the {f(t) | a ≤ t ≤ b}. The function f should map a scalar to an SVector.

Flipping the orientation is supported by passing a > b.

source
Inti.parametric_surfaceFunction
    parametric_surface(f, lc, hc, boundary = nothing; kwargs...)

Create a parametric surface defined by the function f over the rectangular domain defined by the lower corner lc and the upper corner hc. The optional boundary argument can be used to specify the boundary curves of the surface.

Arguments

  • f: A function that takes two arguments x and y and returns a tuple (u, v) representing the parametric coordinates of the surface at (x, y).
  • lc: A 2-element array representing the lower corner of the rectangular domain.
  • hc: A 2-element array representing the upper corner of the rectangular domain.
  • boundary: An optional array of boundary curves that define the surface.

Keyword Arguments

  • kwargs: Additional keyword arguments that can be passed to the GeometricEntity constructor.

Returns

  • The key of the created GeometricEntity.
source
Inti.polynomial_solutions_vdimFunction
polynomial_solutions_vdim(op, order[, center])

For every monomial term pₙ of degree order, compute a polynomial Pₙ such that ℒ[Pₙ] = pₙ, where is the differential operator associated with op. This function returns {pₙ,Pₙ,γ₁Pₙ}, where γ₁Pₙ is the generalized Neumann trace of Pₙ.

Passing a point center will shift the monomials and solutions accordingly.

source
Inti.qcoordsMethod
qcoords(q)

Return the coordinate of the quadrature nodes associated with q.

source
Inti.quadrature_to_node_valsMethod
quadrature_to_node_vals(Q::Quadrature, qvals::AbstractVector)

Given a vector qvals of scalar values at the quadrature nodes of Q, return a vector ivals of scalar values at the interpolation nodes of Q.mesh.

source
Inti.reference_nodesMethod
reference_nodes(el::LagrangeElement)
+reference_nodes(::Type{<:LagrangeElement})

Return the reference nodes on domain(el) used for the polynomial interpolation. The function values on these nodes completely determines the interpolating polynomial.

We use the same convention as gmsh for defining the reference nodes and their order (see node ordering on gmsh documentation).

source
Inti.return_typeMethod
return_type(f[,args...])

The type returned by f(args...), where args is a tuple of types. Falls back to Base.promote_op by default.

A functors of type T with a knonw return type should extend return_type(::T,args...) to avoid relying on promote_op.

source
Inti.rotation_matrixMethod
rotation_matrix(rot)

Constructs a rotation matrix given the rotation angles around the x, y, and z axes.

Arguments

  • rot: A tuple or vector containing the rotation angles in radians for each axis.

Returns

  • R::SMatrix: The resulting rotation matrix.
source
Inti.single_double_layerMethod
single_double_layer(; op, target, source::Quadrature, compression,
+correction, derivative = false)

Construct a discrete approximation to the single- and double-layer integral operators for op, mapping values defined on the quadrature nodes of source to values defined on the nodes of target. If derivative = true, return instead the adjoint double-layer and hypersingular operators (which are the derivative of the single- and double-layer, respectively).

You must choose a compression method and a correction method, as described below.

Compression

The compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:

  • (method = :none, ): no compression is performed, the resulting matrices are dense.
  • (method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).
  • (method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).

Correction

The correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:

  • (method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.
  • (method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesourcecurve/surface (which is assumed to be closed). Whentarget === source,target_location` is not needed.
source
Inti.skeletonMethod
skeleton(Ω::Domain)

Return all the boundaries of the domain, i.e. the domain's skeleton.

source
Inti.stack_weakdeps_env!Method
stack_weakdeps_env!(; verbose = false, update = false)

Push to the load stack an environment providing the weak dependencies of Inti.jl. This allows benefiting from additional functionalities of Inti.jl which are powered by weak dependencies without having to manually install them in your environment.

Set update=true if you want to update the weakdeps environment.

Warning

Calling this function can take quite some time, especially the first time around, if packages have to be installed or precompiled. Run in verbose mode to see what is happening.

Examples:

Inti.stack_weakdeps_env!()
+using HMatrices
source
Inti.svectorMethod
svector(f,n)

Create an SVector of length n, computing each element as f(i), where i is the index of the element.

source
Inti.torusMethod
torus(; r, R, translation, rotation, scaling, labels)

Create a torus entity in 3D, and apply optional transformations. Returns the key. The parameters r and R are the inner and outer radii of the torus.

source
Inti.vdim_correctionMethod
vdim_correction(op,X,Y,Y_boundary,S,D,V; green_multiplier, kwargs...)

Compute a correction to the volume potential V : Y → X such that V + δV is a more accurate approximation of the underlying volume potential operator. The correction is computed using the (volume) density interpolation method.

This function requires a op::AbstractDifferentialOperator, a target set X, a source quadrature Y, a boundary quadrature Y_boundary, approximations S : Y_boundary -> X and D : Y_boundary -> X to the single- and double-layer potentials (correctly handling nearly-singular integrals), and a naive approximation of the volume potential V. The green_multiplier is a vector of the same length as X storing the value of μ(x) for x ∈ X in the Green identity (see _green_multiplier).

See [8] for more details on the method.

Optional kwargs:

  • interpolation_order: the order of the polynomial interpolation. By default, the maximum order of the quadrature rules is used.
  • maxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections.
  • center: the center of the basis functions. By default, the basis functions are centered at the origin.
  • shift: a boolean indicating whether the basis functions should be shifted and rescaled to each element.
source
Inti.vdim_mesh_centerMethod
vdim_mesh_center(msh)

Point x which minimizes ∑ (x-xⱼ)²/r²ⱼ, where xⱼ and rⱼ are the circumcenter and circumradius of the elements of msh, respectively.

source
Inti.vertices_idxsMethod
vertices_idxs(el::LagrangeElement)

The indices of the nodes in el that define the vertices of the element.

source
Inti.volume_potentialMethod
volume_potential(; op, target, source::Quadrature, compression, correction)

Compute the volume potential operator for a given PDE.

Arguments

  • op: The PDE (Partial Differential Equation) to solve.
  • target: The target domain where the potential is computed.
  • source: The source domain where the potential is generated.
  • compression: The compression method to use for the potential operator.
  • correction: The correction method to use for the potential operator.

Returns

The volume potential operator V that represents the interaction between the target and source domains.

Compression

The compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:

  • (method = :none, ): no compression is performed, the resulting matrices are dense.
  • (method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).
  • (method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).

Correction

The correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:

  • (method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.
  • (method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesource's boundary. Whentarget === source,target_location` is not needed.

Details

The volume potential operator is computed by assembling the integral operator V using the single-layer kernel G. The operator V is then compressed using the specified compression method. If no compression is specified, the operator is returned as is. If a correction method is specified, the correction is computed and added to the compressed operator.

source
diff --git a/dev/index.html b/dev/index.html index a2b73a4d..0c6601d8 100644 --- a/dev/index.html +++ b/dev/index.html @@ -6,7 +6,7 @@ [1a804d9e] FMMLIB2D v0.3.2 [705231aa] Gmsh v0.3.1 [8646bddf] HMatrices v0.2.10 - [ee78f7c6] Makie v0.21.14 + [ee78f7c6] Makie v0.21.16 ⌅ [eacbb407] Meshes v0.51.22 Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated`

Note that the first time you run this command, it may take a while to download and compile the dependencies. Subsequent runs will be faster. If preferred, extensions can be manually controlled by Pkg.adding the desired packages from the list above.

Basic usage

Inti.jl can be used to solve a variety of linear partial differential equations by recasting them as integral equations. The general workflow for solving a problem consists of the following steps:

\[ \underbrace{\fbox{Geometry} \rightarrow \fbox{Mesh}}_{\textbf{pre-processing}} \rightarrow \fbox{\color{red}{Solver}} \rightarrow \underbrace{\fbox{Visualization}}_{\textbf{post-processing}}\]

As a simple example illustrating the steps above, consider an interior Laplace problem, in two dimensions, with Dirichlet boundary conditions:

\[\begin{aligned} \Delta u &= 0 \quad \text{in } \Omega,\\ @@ -50,4 +50,4 @@ cb = Colorbar(fig[1, 3], h1, size = 20, height = 200) ax2 = Axis(fig[1, 2]; title = "Approx. solution", opts...) h2 = heatmap!(ax2, xx,yy, (x, y) -> inside((x,y)) ? uₕ((x,y)) : NaN, colorrange = cb.limits[]) -viz!(msh; segmentsize = 3)Example block output

Formulation of the problem as an integral equation

Given a PDE and boundary conditions, there are often many ways to recast the problem as an integral equation, and the choice of formulation plays an important role in the unique solvability, efficiency, and accuracy of the numerical solution. Inti.jl provides a flexible framework for experimenting with different formulations, but it is up to the user to choose the most appropriate one for their problem.

While the example above is a simple one, Inti.jl can handle significantly more complex problems involving multiple domains, heterogeneous coefficients, vector-valued PDEs, and three-dimensional geometries. The best way to dive deeper into Inti.jl's capabilities is the tutorials section. More advanced usage can be found in the examples section.

Contributing

There are several ways to contribute to Inti.jl:

Acknowledgements

+viz!(msh; segmentsize = 3)Example block output
Formulation of the problem as an integral equation

Given a PDE and boundary conditions, there are often many ways to recast the problem as an integral equation, and the choice of formulation plays an important role in the unique solvability, efficiency, and accuracy of the numerical solution. Inti.jl provides a flexible framework for experimenting with different formulations, but it is up to the user to choose the most appropriate one for their problem.

While the example above is a simple one, Inti.jl can handle significantly more complex problems involving multiple domains, heterogeneous coefficients, vector-valued PDEs, and three-dimensional geometries. The best way to dive deeper into Inti.jl's capabilities is the tutorials section. More advanced usage can be found in the examples section.

Contributing

There are several ways to contribute to Inti.jl:

Acknowledgements

diff --git a/dev/objects.inv b/dev/objects.inv index c3674ec8..831a052e 100644 Binary files a/dev/objects.inv and b/dev/objects.inv differ diff --git a/dev/pluto-examples/helmholtz_scattering.jl b/dev/pluto-examples/helmholtz_scattering.jl index 3fc9d7b9..e6a7c02b 100644 --- a/dev/pluto-examples/helmholtz_scattering.jl +++ b/dev/pluto-examples/helmholtz_scattering.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.20.0 +# v0.20.3 using Markdown using InteractiveUtils diff --git a/dev/pluto-examples/helmholtz_scattering/index.html b/dev/pluto-examples/helmholtz_scattering/index.html index 3596b0bb..af0f4e58 100644 --- a/dev/pluto-examples/helmholtz_scattering/index.html +++ b/dev/pluto-examples/helmholtz_scattering/index.html @@ -29,12 +29,12 @@ meshsize = λ / 5 gmsh_circle(; meshsize, order = gorder, name)
Info    : Meshing 1D...
 Info    : Meshing curve 1 (Ellipse)
-Info    : Done meshing 1D (Wall 0.000153396s, CPU 0.000155s)
+Info    : Done meshing 1D (Wall 0.000146754s, CPU 0.000148s)
 Info    : 63 nodes 64 elements
 Info    : Meshing order 2 (curvilinear on)...
 Info    : [  0%] Meshing curve 1 order 2
 Info    : [ 60%] Meshing surface 1 order 2
-Info    : Done meshing order 2 (Wall 0.000377956s, CPU 0.000374s)
+Info    : Done meshing order 2 (Wall 0.000377535s, CPU 0.000374s)
 Info    : Writing '/home/runner/work/Inti.jl/Inti.jl/docs/build/pluto-examples/circle.msh'...
 Info    : Done writing '/home/runner/work/Inti.jl/Inti.jl/docs/build/pluto-examples/circle.msh'

We can now import the file and parse the mesh and domain information into Inti.jl using the import_mesh function:

Inti.clear_entities!() # empty the entity cache
 msh = Inti.import_mesh(name; dim = 2)
Inti.Mesh{2, Float64} containing:
@@ -155,9 +155,9 @@
 end

As before, lets write a file with our mesh, and import it into Inti.jl:

name_sphere = joinpath(@__DIR__, "sphere.msh")
 gmsh_sphere(; meshsize = (λ / 5), order = gorder, name = name_sphere, visualize = false)
 msh_3d = Inti.import_mesh(name_sphere; dim = 3)
Inti.Mesh{3, Float64} containing:
+	 7 elements of type StaticArraysCore.SVector{3, Float64}
 	 255 elements of type Inti.LagrangeElement{Inti.ReferenceHyperCube{1}, 3, StaticArraysCore.SVector{3, Float64}}
-	 6239 elements of type Inti.LagrangeElement{Inti.ReferenceSimplex{2}, 6, StaticArraysCore.SVector{3, Float64}}
-	 7 elements of type StaticArraysCore.SVector{3, Float64}
Tip

If you pass visualize=true to gmsh_sphere, it will open a window with the current model. This is done by calling gmsh.fltk.run(). Note that the main julia thread will be blocked until the window is closed.

Since we created physical groups in Gmsh, we can use them to extract the relevant domains Ω and Σ:

Ω_3d = Inti.Domain(e -> "omega" ∈ Inti.labels(e), Inti.entities(msh_3d))
+	 6239 elements of type Inti.LagrangeElement{Inti.ReferenceSimplex{2}, 6, StaticArraysCore.SVector{3, Float64}}
Tip

If you pass visualize=true to gmsh_sphere, it will open a window with the current model. This is done by calling gmsh.fltk.run(). Note that the main julia thread will be blocked until the window is closed.

Since we created physical groups in Gmsh, we can use them to extract the relevant domains Ω and Σ:

Ω_3d = Inti.Domain(e -> "omega" ∈ Inti.labels(e), Inti.entities(msh_3d))
 Σ_3d = Inti.Domain(e -> "sigma" ∈ Inti.labels(e), Inti.entities(msh_3d))
 Γ_3d = Inti.boundary(Ω_3d)

We can now create a quadrature as before

Γ_msh_3d = view(msh_3d, Γ_3d)
 Q_3d = Inti.Quadrature(Γ_msh_3d; qorder)
Writing/reading a mesh from disk

Writing and reading a mesh to/from disk can be time consuming. You can avoid doing so by using import_mesh without a file name to import the mesh from the current gmsh session without the need to write it to disk.

Next we assemble the integral operators, indicating that we wish to compress them using hierarchical matrices:

using HMatrices
@@ -213,7 +213,7 @@
     x = 2 * x̂
     return abs(uₛ_3d(x) - uₑ_3d(x))
 end
-@info "error with correction = $er_3d"
[ Info: error with correction = 3.328221427459476e-5

We see that, once again, the approximation is quite accurate. Let us now visualize the solution on the punctured plane (which we labeled as "sigma"). Since evaluating the integral representation of the solution at many points is expensive, we will use again use a method to accelerate the evaluation:

Σ_msh = view(msh_3d, Σ_3d)
+@info "error with correction = $er_3d"
[ Info: error with correction = 3.2643662518176203e-5

We see that, once again, the approximation is quite accurate. Let us now visualize the solution on the punctured plane (which we labeled as "sigma"). Since evaluating the integral representation of the solution at many points is expensive, we will use again use a method to accelerate the evaluation:

Σ_msh = view(msh_3d, Σ_3d)
 target = Inti.nodes(Σ_msh)
 
 S_viz, D_viz = Inti.single_double_layer(;
@@ -234,4 +234,4 @@
 ax_3d = Axis3(fig_3d[1, 1]; aspect = :data)
 viz!(Γ_msh_3d; colorrange, colormap, color = zeros(nv), interpolate = true)
 viz!(Σ_msh; colorrange, colormap, color = real(u_eval_msh))
-cb = Colorbar(fig_3d[1, 2]; label = "real(u)", colormap, colorrange)
Example block output +cb = Colorbar(fig_3d[1, 2]; label = "real(u)", colormap, colorrange)Example block output diff --git a/dev/pluto-examples/poisson.jl b/dev/pluto-examples/poisson.jl index b0e018dc..3be8241a 100644 --- a/dev/pluto-examples/poisson.jl +++ b/dev/pluto-examples/poisson.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.20.0 +# v0.20.3 using Markdown using InteractiveUtils diff --git a/dev/pluto-examples/poisson/index.html b/dev/pluto-examples/poisson/index.html index 0505c289..acfd15f7 100644 --- a/dev/pluto-examples/poisson/index.html +++ b/dev/pluto-examples/poisson/index.html @@ -21,16 +21,16 @@ msh = Inti.import_mesh(; dim = 2) gmsh.finalize()
Info    : Meshing 1D...
 Info    : Meshing curve 1 (BSpline)
-Info    : Done meshing 1D (Wall 0.0262433s, CPU 0.026224s)
+Info    : Done meshing 1D (Wall 0.0272208s, CPU 0.027223s)
 Info    : Meshing 2D...
 Info    : Meshing surface 1 (Plane, Frontal-Delaunay)
-Info    : Done meshing 2D (Wall 0.0795264s, CPU 0.079524s)
+Info    : Done meshing 2D (Wall 0.0765097s, CPU 0.076507s)
 Info    : 566 nodes 1033 elements
 Info    : Meshing order 2 (curvilinear on)...
 Info    : [  0%] Meshing curve 1 order 2
 Info    : [ 60%] Meshing surface 1 order 2
 Info    : Surface mesh: worst distortion = 0.451559 (0 elements in ]0, 0.2]); worst gamma = 0.758919
-Info    : Done meshing order 2 (Wall 0.00807488s, CPU 0.008076s)

We can now extract components of the mesh corresponding to the $\Omega$ and $\Gamma$ domains:

Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 2, msh)
+Info    : Done meshing order 2 (Wall 0.00776852s, CPU 0.007768s)

We can now extract components of the mesh corresponding to the $\Omega$ and $\Gamma$ domains:

Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 2, msh)
 Γ = Inti.boundary(Ω)
 Ω_msh = view(msh, Ω)
 Γ_msh = view(msh, Γ)

and visualize them:

using Meshes, GLMakie
@@ -110,4 +110,4 @@
 colormap = :inferno
 ax = Axis(fig[1, 3]; aspect = DataAspect())
 viz!(Ω_msh; colorrange, colormap, color = log_er, interpolate = true)
-cb = Colorbar(fig[1, 4]; label = "log₁₀|u - uₑ|", colormap, colorrange)
Example block output +cb = Colorbar(fig[1, 4]; label = "log₁₀|u - uₑ|", colormap, colorrange)Example block output diff --git a/dev/pluto-examples/toy_example.jl b/dev/pluto-examples/toy_example.jl index efca316a..bf9311f1 100644 --- a/dev/pluto-examples/toy_example.jl +++ b/dev/pluto-examples/toy_example.jl @@ -1,5 +1,5 @@ ### A Pluto.jl notebook ### -# v0.20.0 +# v0.20.3 using Markdown using InteractiveUtils diff --git a/dev/pluto-examples/toy_example/index.html b/dev/pluto-examples/toy_example/index.html index ac4310d8..4f365ea7 100644 --- a/dev/pluto-examples/toy_example/index.html +++ b/dev/pluto-examples/toy_example/index.html @@ -1,2 +1,2 @@ -Toy example · Inti.jl

Pluto notebook

Toy example

All examples in Inti.jl are autogenerated by executing the make.jl script in the docs folder. The workflow uses Pluto.jl to generate markdown files passed to Documenter.jl. The original pluto notebook files are downloadable from the example's page.

using Inti
+Toy example · Inti.jl

Pluto notebook

Toy example

All examples in Inti.jl are autogenerated by executing the make.jl script in the docs folder. The workflow uses Pluto.jl to generate markdown files passed to Documenter.jl. The original pluto notebook files are downloadable from the example's page.

using Inti
diff --git a/dev/references/index.html b/dev/references/index.html index a7eaf85b..33defc54 100644 --- a/dev/references/index.html +++ b/dev/references/index.html @@ -1,2 +1,2 @@ -References · Inti.jl

References

[1]
J.-C. Nédélec. Acoustic and electromagnetic equations: integral representations for harmonic problems. Vol. 144 (Springer, 2001).
[2]
D. Colton and R. Kress. Integral equation methods in scattering theory (SIAM, 2013).
[3]
M. Bebendorf. Hierarchical matrices (Springer, 2008).
[4]
W. Hackbusch and others. Hierarchical matrices: algorithms and analysis. Vol. 49 (Springer, 2015).
[5]
V. Rokhlin. Rapid solution of integral equations of classical potential theory. Journal of computational physics 60, 187–207 (1985).
[6]
L. Greengard and V. Rokhlin. A fast algorithm for particle simulations. Journal of computational physics 73, 325–348 (1987).
[7]
L. M. Faria, C. Pérez-Arancibia and M. Bonnet. General-purpose kernel regularization of boundary integral equations via density interpolation. Computer Methods in Applied Mechanics and Engineering 378, 113703 (2021).
[8]
T. G. Anderson, M. Bonnet, L. M. Faria and C. Pérez-Arancibia. Fast, high-order numerical evaluation of volume potentials via polynomial density interpolation. Journal of Computational Physics, 113091 (2024).
+References · Inti.jl

References

[1]
J.-C. Nédélec. Acoustic and electromagnetic equations: integral representations for harmonic problems. Vol. 144 (Springer, 2001).
[2]
D. Colton and R. Kress. Integral equation methods in scattering theory (SIAM, 2013).
[3]
M. Bebendorf. Hierarchical matrices (Springer, 2008).
[4]
W. Hackbusch and others. Hierarchical matrices: algorithms and analysis. Vol. 49 (Springer, 2015).
[5]
V. Rokhlin. Rapid solution of integral equations of classical potential theory. Journal of computational physics 60, 187–207 (1985).
[6]
L. Greengard and V. Rokhlin. A fast algorithm for particle simulations. Journal of computational physics 73, 325–348 (1987).
[7]
L. M. Faria, C. Pérez-Arancibia and M. Bonnet. General-purpose kernel regularization of boundary integral equations via density interpolation. Computer Methods in Applied Mechanics and Engineering 378, 113703 (2021).
[8]
T. G. Anderson, M. Bonnet, L. M. Faria and C. Pérez-Arancibia. Fast, high-order numerical evaluation of volume potentials via polynomial density interpolation. Journal of Computational Physics, 113091 (2024).
diff --git a/dev/search_index.js b/dev/search_index.js index 140000f0..21a4e851 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"tutorials/solvers/#Linear-solvers","page":"Linear solvers","title":"Linear solvers","text":"","category":"section"},{"location":"tutorials/solvers/","page":"Linear solvers","title":"Linear solvers","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/solvers/","page":"Linear solvers","title":"Linear solvers","text":"warning: Work in progress\nThis tutorial is still a work in progress. We will update it with more details and examples in the future.","category":"page"},{"location":"tutorials/solvers/","page":"Linear solvers","title":"Linear solvers","text":"Inti.jl does not provide its own linear solvers, but relies on external libraries such as IterativeSolvers.jl or the LinearAlgebra standard library for the solving the linear systems that arise in the discretization of integral equations.","category":"page"},{"location":"references/","page":"References","title":"References","text":"CurrentModule = Inti","category":"page"},{"location":"references/#References","page":"References","title":"References","text":"","category":"section"},{"location":"references/","page":"References","title":"References","text":"J.-C. Nédélec. Acoustic and electromagnetic equations: integral representations for harmonic problems. Vol. 144 (Springer, 2001).\n\n\n\nD. Colton and R. Kress. Integral equation methods in scattering theory (SIAM, 2013).\n\n\n\nM. Bebendorf. Hierarchical matrices (Springer, 2008).\n\n\n\nW. Hackbusch and others. Hierarchical matrices: algorithms and analysis. Vol. 49 (Springer, 2015).\n\n\n\nV. Rokhlin. Rapid solution of integral equations of classical potential theory. Journal of computational physics 60, 187–207 (1985).\n\n\n\nL. Greengard and V. Rokhlin. A fast algorithm for particle simulations. Journal of computational physics 73, 325–348 (1987).\n\n\n\nL. M. Faria, C. Pérez-Arancibia and M. Bonnet. General-purpose kernel regularization of boundary integral equations via density interpolation. Computer Methods in Applied Mechanics and Engineering 378, 113703 (2021).\n\n\n\nT. G. Anderson, M. Bonnet, L. M. Faria and C. Pérez-Arancibia. Fast, high-order numerical evaluation of volume potentials via polynomial density interpolation. Journal of Computational Physics, 113091 (2024).\n\n\n\n","category":"page"},{"location":"tutorials/layer_potentials/#Layer-potentials","page":"Layer potentials","title":"Layer potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"note: Important points covered in this tutorial\nNearly singular evaluation of layer potentials\nCreating a smooth domain with splines using Gmsh's API\nPlotting values on a mesh","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"In this tutorial we focus on evaluating the layer potentials given a source density. This is a common post-processing task in boundary integral equation methods, and while most of it is straightforward, some subtleties arise when the target points are close to the boundary (nearly-singular integrals).","category":"page"},{"location":"tutorials/layer_potentials/#Integral-potentials","page":"Layer potentials","title":"Integral potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"IntegralPotential represent the following mathematical objects:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"mathcalPsigma(boldsymbolr) = int_Gamma K(boldsymbolr boldsymbolr) sigma(boldsymbolr) dboldsymbolr","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"where K is the kernel of the operator, Gamma is the source's boundary, boldsymbolr not in Gamma is a target point, and sigma is the source density.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Here is a simple example of how to create a kernel representing a Laplace double-layer potential:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using Inti, StaticArrays, LinearAlgebra\n# define a kernel function\nfunction K(target,source)\n r = Inti.coords(target) - Inti.coords(source)\n ny = Inti.normal(source)\n return 1 / (2π * norm(r)^2) * dot(r, ny)\nend\n# define a domain\nΓ = Inti.parametric_curve(s -> SVector(cos(2π * s), sin(2π * s)), 0, 1) |> Inti.Domain\n# and a quadrature of Γ\nQ = Inti.Quadrature(Γ; meshsize = 0.1, qorder = 5)\n𝒮 = Inti.IntegralPotential(K, Q)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"If we have a source density sigma, defined on the quadrature nodes of Gamma, we can create a function that evaluates the layer potential at an arbitrary point:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"σ = map(q -> 1.0, Q)\nu = 𝒮[σ]","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"u is now an anonymous function that evaluates the layer potential at any point:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"r = SVector(0.1, 0.2)\n@assert u(r) ≈ -1 # hide\nu(r)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Although we created the single-layer potential for the Laplace kernel manually, it is often more convenient to use the single_layer_potential when working with a supported PDE, e.g.:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"op = Inti.Laplace(; dim = 2)\n𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"creates the single and double layer potentials for the Laplace equation in 2D.","category":"page"},{"location":"tutorials/layer_potentials/#Direct-evaluation-of-layer-potentials","page":"Layer potentials","title":"Direct evaluation of layer potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We now show how to evaluate the layer potentials of an exact solution on a mesh created through the Gmsh API. Do to so, let us first define the PDE:g","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using Inti, StaticArrays, LinearAlgebra, Meshes, GLMakie, Gmsh\n# define the PDE\nk = 4π\nop = Inti.Helmholtz(; dim = 2, k)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We will now use the gmsh_curve function to create a smooth domain of a kite using splines:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"gmsh.initialize()\nmeshsize = 2π / k / 4\nkite = Inti.gmsh_curve(0, 1; meshsize) do s\n SVector(0.25, 0.0) + SVector(cos(2π * s) + 0.65 * cos(4π * s[1]) - 0.65, 1.5 * sin(2π * s))\nend\ncl = gmsh.model.occ.addCurveLoop([kite])\nsurf = gmsh.model.occ.addPlaneSurface([cl])\ngmsh.model.occ.synchronize()\ngmsh.model.mesh.generate(2)\nmsh = Inti.import_mesh(; dim = 2)\ngmsh.finalize()","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"tip: Tip\nThe GMSH API is a powerful tool to create complex geometries and meshes directly from Julia (the gmsh_curve function above is just a simple wrapper around some spline functionality). For more information, see the official documentation.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We can visualize the triangular mesh using:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using Meshes, GLMakie\n# extract the domain Ω from the mesh entities\nents = Inti.entities(msh)\nΩ = Inti.Domain(e->Inti.geometric_dimension(e) == 2, ents)\nviz(msh[Ω]; showsegments = true, axis = (aspect = DataAspect(), ))","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"For the purpose of testing the accuracy of the layer potential evaluation, we will construct an exact solution of the Helmholtz equation on the interior domain and plot it:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"# construct an exact interior solution as a sum of random plane waves\ndirs = [SVector(cos(θ), sin(θ)) for θ in 2π*rand(10)]\ncoefs = rand(ComplexF64, 10)\nu = (x) -> sum(c*exp(im*k*dot(x, d)) for (c,d) in zip(coefs, dirs))\ndu = (x,ν) -> sum(c*im*k*dot(d, ν)*exp(im*k*dot(x, d)) for (c,d) in zip(coefs, dirs))\n# plot the exact solution\nΩ_msh = view(msh, Ω)\ntarget = Inti.nodes(Ω_msh)\nviz(Ω_msh; showsegments = false, axis = (aspect = DataAspect(), ), color = real(u.(target)))","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Since u satisfies the Helmholtz equation, we know that the following representation holds:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"u(boldsymbolr) = mathcalSgamma_1 u(boldsymbolr) - mathcalDgamma_0 u(boldsymbolr) quad boldsymbolr in Omega","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"where gamma_0 u and gamma_1 u are the respective Dirichlet and Neumann traces of u, and mathcalS and mathcalD are the respective single and double layer potentials over Gamma = partial Omega.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Let's compare next the exact solution with the layer potential evaluation, based on a quadrature of Gamma:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Γ = Inti.boundary(Ω)\nQ = Inti.Quadrature(view(msh,Γ); qorder = 5)\n# evaluate the layer potentials\n𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nγ₀u = map(q -> u(q.coords), Q)\nγ₁u = map(q -> du(q.coords, q.normal), Q)\nuₕ = x -> 𝒮[γ₁u](x) - 𝒟[γ₀u](x)\n# plot the error on the target nodes\ner_log10 = log10.(abs.(u.(target) - uₕ.(target)))\ncolorrange = extrema(er_log10)\nfig, ax, pl = viz(Ω_msh;\n color = er_log10,\n colormap = :viridis,\n colorrange,\n axis = (aspect = DataAspect(),),\n interpolate=true\n)\nColorbar(fig[1, 2]; label = \"log₁₀(error)\", colorrange)\nfig","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We see a common pattern of potential evaluation: the error is small away from the boundary, but grows near it. This is due to the nearly-singular nature of the layer potential integrals, which can be mitigated by using a correction method that accounts for the singularity of the kernel as boldsymbolr to Gamma.","category":"page"},{"location":"tutorials/layer_potentials/#Near-field-correction-of-layer-potentials","page":"Layer potentials","title":"Near-field correction of layer potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"There are two cases where the direct evaluation of layer potentials is not recommended:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"When the target point is close to the boundary (nearly-singular integrals).\nWhen evaluation at many target points is desired (computationally burdensome)and take advantage of an acceleration routine.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"In such contexts, it is recommended to use the single_double_layer function (alternately, one can directly assemble an IntegralOperator) with a correction, for the first case, and/or a compression (acceleration) method, for the latter case, as appropriate. Here is an example of how to use the FMM acceleration with a near-field correction to evaluate the layer potentials::","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using FMM2D\nS, D = Inti.single_double_layer(; op, target, source = Q,\n compression = (method = :fmm, tol = 1e-12),\n correction = (method = :dim, target_location = :inside, maxdist = 0.2)\n)\ner_log10_cor = log10.(abs.(S*γ₁u - D*γ₀u - u.(target)))\ncolorrange = extrema(er_log10) # use scale without correction\nfig = Figure(resolution = (800, 400))\nax1 = Axis(fig[1, 1], aspect = DataAspect(), title = \"Naive evaluation\")\nviz!(Ω_msh; color = er_log10, colormap = :viridis, colorrange,interpolate=true)\nax2 = Axis(fig[1, 2], aspect = DataAspect(), title = \"Nearfield correction\")\nviz!(Ω_msh; color = er_log10_cor, colormap = :viridis, colorrange, interpolate=true)\nColorbar(fig[1, 3]; label = \"log₁₀(error)\", colorrange)\nfig","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"As can be seen, the near-field correction significantly reduces the error near the boundary, making if feasible to evaluate the layer potential near Gamma if necessary.","category":"page"},{"location":"tutorials/integral_operators/#Boundary-integral-operators","page":"Boundary integral operators","title":"Boundary integral operators","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"note: Important points covered in this tutorial\nDefine layer potentials and the four integral operators of Calderón calculus\nConstruct block operators\nSet up a custom kernel","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"A central piece of integral equation methods is the efficient and accurate computation of integral operators. In the first part of this tutorial we will cover how to assemble and manipulate the four integral operators of Calderón calculus, namely the single-layer, double-layer, hypersingular, and adjoint operators [1, 2], for some predefined kernels in Inti.jl. In the second part we will show how to extend the package to handle custom kernels.","category":"page"},{"location":"tutorials/integral_operators/#Predefined-kernels-and-integral-operators","page":"Boundary integral operators","title":"Predefined kernels and integral operators","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"To simplify the construction of integral operators for some commonly used PDEs, Inti.jl defines a few AbstractDifferentialOperators types. For each of these PDEs, the package provides a SingleLayerKernel, DoubleLayerKernel, HyperSingularKernel, and AdjointDoubleLayerKernel that can be used to construct the corresponding kernel functions, e.g.:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using Inti, StaticArrays, LinearAlgebra\nop = Inti.Helmholtz(; dim = 2, k = 2π)\nG = Inti.SingleLayerKernel(op)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Typically, we are not interested in the kernels themselves, but in the integral operators they define. Two functions, single_double_layer and adj_double_layer_hypersingular, are provided as a high-level syntax to construct the four integral operators of Calderón calculus:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Γ = Inti.parametric_curve(s -> SVector(cos(s), sin(s)), 0, 2π) |> Inti.Domain\nQ = Inti.Quadrature(Γ; meshsize = 0.1, qorder = 5)\nS, D = Inti.single_double_layer(; \n op, \n target = Q, \n source = Q, \n compression = (method = :none,), \n correction = (method = :dim,)\n)\nK, N = Inti.adj_double_layer_hypersingular(; \n op, \n target = Q, \n source = Q, \n compression = (method = :none,), \n correction = (method = :dim,)\n)\nnothing # hide","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Much goes on under the hood in the function above, and the sections on correction and compression methods will provide more details on the options available. The important thing to keep in mind is that S, D, K, and N are discrete approximations of the following (linear) operators:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"beginaligned\n Ssigma(boldsymbolx) = int_Gamma G(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly quad \n Dsigma(boldsymbolx) = mathrmpv int_Gamma fracpartial Gpartial nu_boldsymboly(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly \n Ksigma(boldsymbolx) = mathrmpv int_Gamma fracpartial Gpartial nu_boldsymbolx(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly quad\n Nsigma(boldsymbolx) = mathrmfp int_Gamma fracpartial^2 Gpartial nu_boldsymbolx partial nu_boldsymboly(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly\nendaligned","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The actual type of S, D, K, and N depends on the compression and correction methods. In the simple case above, these are simply matrices:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"@assert all(T -> T == Matrix{ComplexF64}, map(typeof, (S, D, K, N))) # hide\nmap(typeof, (S, D, K, N))","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"If we turn on a compression method, such as :fmm, the types may change into something different:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using FMM2D # will load the extension\nSfmm, Dfmm = Inti.single_double_layer(; \n op, \n target = Q, \n source = Q, \n compression = (method = :fmm, tol = 1e-10), \n correction = (method = :dim, )\n)\nKfmm, Nfmm = Inti.adj_double_layer_hypersingular(; \n op, \n target = Q, \n source = Q, \n compression = (method = :fmm, tol = 1e-10), \n correction = (method = :dim,)\n)\ntypeof(Sfmm)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"This is because the FMM method is used to approximate the matrix-vector in a matrix-free way: the only thing guaranteed is that S and D can be applied to a vector:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"x = map(q -> cos(q.coords[1] + q.coords[2]), Q)\n@assert norm(Sfmm*x - S*x, Inf) / norm(S*x, Inf) < 1e-8 # hide\nnorm(Sfmm*x - S*x, Inf)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The Sfmm object above in fact combines two linear maps:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Sfmm","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The FunctionMap computes a matrix-vector by performing a function call to the FMM2D library. The WrappedMap accounts for a sparse matrix used to correct for singular and nearly singular interactions. These two objects are added lazily using LinearMaps.","category":"page"},{"location":"tutorials/integral_operators/#Operator-composition","page":"Boundary integral operators","title":"Operator composition","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Effortlessly and efficiently composing operators is a powerful abstraction for integral equations, as it allows for the construction of complex systems from simple building blocks. To show this, let us show how to construct the Calderón projectors:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"beginaligned\nH = beginbmatrix\n -D S \n -N K\nendbmatrix \nendaligned","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"As is well-known [1, Theorem 3.1.3], the operators C_pm = I2 pm H are the projectors (i.e. C_pm^2 = C_pm):","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using LinearMaps\n# create the block operator\nH = [-Dfmm Sfmm; -Nfmm Kfmm]\nC₊ = I / 2 + H\nC₋ = I / 2 - H\n# define two density functions on Γ\nu = map(q -> cos(q.coords[1] + q.coords[2]), Q)\nv = map(q-> q.coords[1], Q)\nx = [u; v]\n# compute the error in the projector identity\ne₊ = norm(C₊*(C₊*x) - C₊*x, Inf)\ne₋ = norm(C₋*(C₋*x) - C₋*x, Inf)\n@assert e₊ < 1e-5 && e₋ < 1e-5 # hide\nprintln(\"projection error for C₊: $e₊\")\nprintln(\"projection error for C₋: $e₋\")","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"We see that the error in the projector identity is small, as expected. Note that such compositions are not limited to the Calderón projectors, and can be used e.g. to construct the combined field integral equation (CFIE), or to compose a formulation with an operator preconditioner.","category":"page"},{"location":"tutorials/integral_operators/#Custom-kernels","page":"Boundary integral operators","title":"Custom kernels","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"So far we have focused on problems for which Inti.jl provides predefined kernels, and used the high-level syntax of e.g. single_double_layer to construct the integral operators. We will now dig into the details of how to set up a custom kernel function, and how to build an integral operator from it.","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"note: Integral operators coming from PDEs\nIf the integral operator of interest arises from a PDE, it is recommended to define a new AbstractDifferentialOperator type, and implement the required methods for SingleLayerKernel, DoubleLayerKernel, AdjointDoubleLayerKernel, and HyperSingularKernel. This will enable the use of the high-level syntax for constructing boundary integral operators, as well as the use of the compression and correction methods specific to integral operators arising from PDEs.","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"For the sake of simplicity, let us consider the following kernel representing the half-space Dirichlet Green function for Helmholtz's equation in 2D:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":" G_D(boldsymbolx boldsymboly) = fraci4 H^(1)_0(k boldsymbolx - boldsymboly) - fraci4 H^(1)_0(k boldsymbolx - boldsymboly^*)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"where boldsymboly^* = (y_1 -y_2). We can define this kernel as a","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using SpecialFunctions # for hankelh1\nfunction helmholtz_kernel(target, source, k)\n x, y = Inti.coords(target), Inti.coords(source)\n yc = SVector(y[1], -y[2])\n d, dc = norm(x-y), norm(x-yc)\n # the singularity at x = y needs to be handled separately, so just put a zero\n d == 0 ? zero(ComplexF64) : im / 4 * ( hankelh1(0, k * d) - hankelh1(0, k * dc))\nend","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Let us now consider the integral operator S defined by:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":" Ssigma(boldsymbolx) = int_Gamma G_D(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly quad boldsymbolx in Gamma","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"We can represent S by an IntegralOperator type:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"k = 50π\nλ = 2π/k\nmeshsize = λ / 10\ngeo = Inti.parametric_curve(s -> SVector(cos(s), 2 + sin(s)), 0, 2π)\nΓ = Inti.Domain(geo)\nmsh = Inti.meshgen(Γ; meshsize)\nQ = Inti.Quadrature(msh; qorder = 5)\n# create a local scope to capture `k`\nK = let k = k\n (t,q) -> helmholtz_kernel(t,q,k)\nend\nSop = Inti.IntegralOperator(K, Q, Q)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"note: Signature of custom kernels\nKernel functions passed to IntegralOperator should always take two arguments, target and source, which are both of QuadratureNode. This allows for extracting not only the coords of the nodes, but also the normal vector if needed (e.g. for double-layer or hypersingular kernels).","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The approximation of Sop now involves two steps:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"build a dense operator S₀ that efficiently computes the matrix-vector product Sop * x for any vector x\ncorrect for the inaccuracies of S₀ due to singular/nearly-singular interactions by adding to it a correction matrix δS","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"For the first step, we will use a hierarchical matrix:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using HMatrices\nS₀ = Inti.assemble_hmatrix(Sop; rtol = 1e-4)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The correction matrix δS will be constructed using adaptive_correction:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"δS = Inti.adaptive_correction(Sop; tol = 1e-4, maxdist = 5*meshsize)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"How exactly one adds S₀ and δS to get the final operator depends on the intended usage. For instance, one can use the LinearMap type to simply add them lazily:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using LinearMaps\nS = LinearMap(S₀) + LinearMap(δS)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Or, one can add δS to S₀ to create a new object:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"S = S₀ + δS","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"or if performance/memory is a concern, one may want to directly add δS to S₀ in-place:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"axpy!(1.0, δS, S₀)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"All of these should give an identical matrix-vector product, but the latter two allow e.g. for the use of direct solvers though an LU factorization.","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"warning: Limitations\nIntegral operators defined from custom kernel functions do not support all the features of the predefined ones. In particular, some singular integration methods (e.g. the Density Interpolation Method) and acceleration routines (e.g. Fast Multipole Method) used to correct for singular and nearly singular integral operators, and to accelerate the matrix vector products, are only available for specific kernels. Check the corrections and compression for more details concerning which methods are compatible with custom kernels.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"(Image: Pluto notebook)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"begin\n import Pkg as _Pkg\n haskey(ENV, \"PLUTO_PROJECT\") && _Pkg.activate(ENV[\"PLUTO_PROJECT\"])\n using PlutoUI: TableOfContents\nend;","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Helmholtz-scattering","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Important points covered in this example\nCreating a geometry using the Gmsh API\nAssembling integral operators and integral potentials\nSetting up a sound-soft problem in both 2 and 3 spatial dimensions\nUsing GMRES to solve the linear system\nExporting the solution to Gmsh for visualization","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"In this tutorial we will show how to solve an acoustic scattering problem in the context of Helmholtz equation. We will focus on a smooth sound-soft obstacle for simplicity, and introduce along the way the necessary techniques used to handle some difficulties encountered. We will use various packages throughout this example (including of course Inti.jl); if they are not on your environment, you can install them using ] add in the REPL.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"In the following section, we will provide a brief mathematical description of the problem (valid in both 2 and 3 dimensions). We will tackle the two-dimensional problem first, for which we do not need to worry much about performance issues (e.g. compressing the integral operators). Finally, we present a three-dimensional example, where we will use HMatrices.jl to compress the underlying integral operators.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Sound-soft-problem","page":"Helmholtz scattering","title":"Sound-soft problem","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"This example concerns the sound-soft acoustic scattering problem. Mathematically, this means solving an exterior problem governed by Helmholtz equation (time-harmonic acoustics) with a Dirichlet boundary condition. More precisely, letting Omega subset mathbbR^d be a bounded domain, and denoting by Gamma = partial Omega its boundary, we wish to solve","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Delta u + k^2 u = 0 quad texton quad mathbbR^d setminus barOmega","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"subject to Dirichlet boundary conditions on Gamma","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tu(boldsymbolx) = g(boldsymbolx) quad textfor quad boldsymbolx in Gamma","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"and the Sommerfeld radiation condition at infinity","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tlim_boldsymbolx to infty boldsymbolx^(d-1)2 left( fracpartial upartial boldsymbolx - i k u right) = 0","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Here g is a (given) boundary datum, and k is the constant wavenumber.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"For simplicity, we will take Gamma circle/sphere, and focus on the plane-wave scattering problem. This means we will seek a solution u of the form u = u_s + u_i, where u_i is a known incident field, and u_s is the scattered field we wish to compute.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Complex geometries\nThe main reason for focusing on such a simple example is twofold. First, it alleviates the complexities associated with the mesh generation. Second, since exact solutions are known for this problem (in the form of a series), it is easy to assess the accuracy of the solution obtained. In practice, you can use the same techniques to solve the problem on more complex geometries by providing a .msh file containing the mesh.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Using the theory of boundary integral equations, we can express u_s as","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tu_s(boldsymbolr) = mathcalDsigma(boldsymbolr) - i k mathcalSsigma(boldsymbolr)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"where mathcalS is the so-called single layer potential, mathcalD is the double-layer potential, and sigma Gamma to mathbbC is a surface density. This is an indirect formulation (because sigma is an auxiliary density, not necessarily physical) commonly referred to as a combined field formulation. Taking the limit mathbbR^d setminus bar Omega ni x to Gamma, it can be shown that the following equation holds on Gamma:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tleft( fracmathrmI2 + mathrmD - i k mathrmS right)sigma = g","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"where mathrmI is the identity operator, and mathrmS and mathrmD are the single- and double-layer operators. This is the combined field integral equation that we will solve. The boundary data g is obtained by applying the sound-soft condition u=0 on Gamma, from which it readily follows that u_s = -u_i on Gamma.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We are now have the necessary background to solve this problem in both 2 and 3 spatial dimensions. Let's load Inti.jl as well as the required dependencies","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"using Inti\nusing LinearAlgebra\nusing StaticArrays\nusing Gmsh\nusing Meshes\nusing GLMakie\nusing SpecialFunctions\nusing GSL\nusing IterativeSolvers\nusing LinearMaps","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"and setup some of the (global) problem parameters:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"k = 4π\nλ = 2π / k\nqorder = 4 # quadrature order\ngorder = 2 # order of geometrical approximation\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Two-dimensional-scattering","page":"Helmholtz scattering","title":"Two-dimensional scattering","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We will use Gmsh API for creating .msh file containing the desired geometry and mesh. Here is a function to mesh the circle:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"function gmsh_circle(; name, meshsize, order = 1, radius = 1, center = (0, 0))\n try\n gmsh.initialize()\n gmsh.model.add(\"circle-mesh\")\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", meshsize)\n gmsh.option.setNumber(\"Mesh.MeshSizeMin\", meshsize)\n gmsh.model.occ.addDisk(center[1], center[2], 0, radius, radius)\n gmsh.model.occ.synchronize()\n gmsh.model.mesh.generate(1)\n gmsh.model.mesh.setOrder(order)\n gmsh.write(name)\n finally\n gmsh.finalize()\n end\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Let us now use gmsh_circle to create a circle.msh file. As customary in wave-scattering problems, we will choose a mesh size that is proportional to wavelength:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"name = joinpath(@__DIR__, \"circle.msh\")\nmeshsize = λ / 5\ngmsh_circle(; meshsize, order = gorder, name)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now import the file and parse the mesh and domain information into Inti.jl using the import_mesh function:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Inti.clear_entities!() # empty the entity cache\nmsh = Inti.import_mesh(name; dim = 2)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The code above will import the mesh with all of its geometrical entities. The dim=2 projects all points to two dimensions by ignoring the third component. To extract the domain Omega we need to filter the entities in the mesh; here we will simply filter them based on the geometric_dimension:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 2, Inti.entities(msh))","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"To solve our boundary integral equation usign a Nyström method, we actually need a quadrature of our curve/surface (and possibly the normal vectors at the quadrature nodes). Once a mesh is available, creating a quadrature object can be done via the Quadrature constructor, which requires passing a mesh of the domain that one wishes to generate a quadrature for:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Γ = Inti.boundary(Ω)\nΓ_msh = view(msh, Γ)\nQ = Inti.Quadrature(Γ_msh; qorder)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"tip: Views of a mesh\nIn Inti.jl, you can use domain to create a view of a mesh containing only the elements in the domain. For example view(msh,Γ) will return an SubMesh type that you can use to iterate over the elements in the boundary of the disk without actually creating a new mesh. You can use msh[Γ], or collect(view(msh,Γ)) to create a new mesh containing only the elements and nodes in Γ.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The object Q now contains a quadrature (of order 4) that can be used to solve a boundary integral equation on Γ. As a sanity check, let's make sure integrating the function x->1 over Q gives an approximation to the perimeter:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"@assert abs(Inti.integrate(x -> 1, Q) - 2π) < 1e-5","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"abs(Inti.integrate(x -> 1, Q) - 2π)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"With the Quadrature constructed, we now can define discrete approximation to the integral operators mathrmS and mathrmD as follows:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"op = Inti.Helmholtz(; k, dim = 2)\nS, D = Inti.single_double_layer(;\n op,\n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,),\n)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"There are two well-known difficulties related to the discretization of the boundary integral operators S and D:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The kernel of the integral operator is not smooth, and thus specialized quadrature rules are required to accurately approximate the matrix entries for which the target and source point lie close (relative to some scale) to each other.\nThe underlying matrix is dense, and thus the storage and computational cost of the operator is prohibitive for large problems unless acceleration techniques such as Fast Multipole Methods or Hierarchical Matrices are employed.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Inti.jl tries to provide a modular and transparent interface for dealing with both of these difficulties, where the general approach for solving a BIE will be to first construct a (possible compressed) naive representation of the integral operator where singular and nearly-singular integrals are ignored, followed by a the creation of a (sparse) correction intended to account for such singular interactions. See single_double_layer for more details on the various options available.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now combine S and D to form the combined-field operator:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"L = I / 2 + D - im * k * S\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"where I is the identity matrix. Assuming an incident field along the x_1 direction of the form u_i =e^ikx_1, the right-hand side of the equation can be construted using:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"uᵢ = x -> exp(im * k * x[1]) # plane-wave incident field\nrhs = map(Q) do q\n x = q.coords\n return -uᵢ(x)\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Iterating over a quadrature\nIn computing rhs above, we used map to evaluate the incident field at all quadrature nodes. When iterating over Q, the iterator returns a QuadratureNode, and not simply the coordinate of the quadrature node. This is so that you can access additional information, such as the normal vector, at the quadrature node.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now solve the integral equation using e.g. the backslash operator:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"σ = L \\ rhs\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The variable σ contains the value of the approximate density at the quadrature nodes. To reconstruct a continuous approximation to the solution, we can use single_double_layer_potential to obtain the single- and double-layer potentials, and then combine them as follows:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nuₛ = x -> 𝒟[σ](x) - im * k * 𝒮[σ](x)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The variable uₛ is an anonymous/lambda function representing the approximate scattered field.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"To assess the accuracy of the solution, we can compare it to the exact solution (obtained by separation of variables in polar coordinates):","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"function circle_helmholtz_soundsoft(pt; radius = 1, k, θin)\n x = pt[1]\n y = pt[2]\n r = sqrt(x^2 + y^2)\n θ = atan(y, x)\n u = 0.0\n r < radius && return u\n c(n) = -exp(im * n * (π / 2 - θin)) * besselj(n, k * radius) / besselh(n, k * radius)\n u = c(0) * besselh(0, k * r)\n n = 1\n while (abs(c(n)) > 1e-12)\n u +=\n c(n) * besselh(n, k * r) * exp(im * n * θ) +\n c(-n) * besselh(-n, k * r) * exp(-im * n * θ)\n n += 1\n end\n return u\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Here is the maximum error on some points located on a circle of radius 2:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"uₑ = x -> circle_helmholtz_soundsoft(x; k, radius = 1, θin = 0) # exact solution\ner = maximum(0:0.01:2π) do θ\n R = 2\n x = (R * cos(θ), R * sin(θ))\n return abs(uₛ(x) - uₑ(x))\nend\n@info \"maximum error = $er\"","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"@assert er < 1e-3","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"As we can see, the error is quite small! Let's use Makie to visualize the solution in this simple (2d) example:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"xx = yy = range(-4; stop = 4, length = 200)\nvals =\n map(pt -> Inti.isinside(pt, Q) ? NaN : real(uₛ(pt) + uᵢ(pt)), Iterators.product(xx, yy))\nfig, ax, hm = heatmap(\n xx,\n yy,\n vals;\n colormap = :inferno,\n interpolate = true,\n axis = (aspect = DataAspect(), xgridvisible = false, ygridvisible = false),\n)\nviz!(Γ_msh; color = :white, segmentsize = 5)\nColorbar(fig[1, 2], hm)\nfig","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"While here we simply used a heatmap to visualize the solution, more complex problems may require a mesh-based visualization, where we would first create a mesh for the places where we want to visualize the solution.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Before moving on to the 3D example let us simply mention that, besides the fact that an analytic solution was available for comparisson, there was nothing special about the unit disk (or the use of GMSH). We could have, for instance, replaced the disk by shapes created parametrically:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"let\n # vertices of an equilateral triangle centered at the origin with a vertex at (0,1)\n a, b, c = SVector(0, 1), SVector(sqrt(3) / 2, -1 / 2), SVector(-sqrt(3) / 2, -1 / 2)\n function circle_f(center, radius)\n return s -> center + radius * SVector(cospi(2 * s[1]), sinpi(2 * s[1]))\n end\n disk1 = Inti.parametric_curve(circle_f(a, 1 / 2), 0, 1)\n disk2 = Inti.parametric_curve(circle_f(b, 1 / 2), 0, 1)\n disk3 = Inti.parametric_curve(circle_f(c, 1 / 2), 0, 1)\n Γ = disk1 ∪ disk2 ∪ disk3\n msh = Inti.meshgen(Γ; meshsize)\n Γ_msh = view(msh, Γ)\n Q = Inti.Quadrature(Γ_msh; qorder)\n S, D = Inti.single_double_layer(;\n op,\n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,),\n )\n L = I / 2 + D - im * k * S\n rhs = map(q -> -uᵢ(q.coords), Q)\n σ = L \\ rhs\n 𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\n uₛ = x -> 𝒟[σ](x) - im * k * 𝒮[σ](x)\n vals = map(\n pt -> Inti.isinside(pt, Q) ? NaN : real(uₛ(pt) + uᵢ(pt)),\n Iterators.product(xx, yy),\n )\n colorrange = (-2, 2)\n fig, ax, hm = heatmap(\n xx,\n yy,\n vals;\n colormap = :inferno,\n colorrange,\n interpolate = true,\n axis = (aspect = DataAspect(), xgridvisible = false, ygridvisible = false),\n )\n viz!(Γ_msh; color = :black, segmentsize = 4)\n Colorbar(fig[1, 2], hm)\n fig\nend","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Near-field evaluation\nIn the example above we employed a naive evaluation of the integral potentials, and therefore the computed solution is expected to become innacurate near the obstacles. See the layer potential tutorial for more information on how to correct for this.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Three-dimensional-scattering","page":"Helmholtz scattering","title":"Three-dimensional scattering","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We now consider the same problem in 3D. Unlike the 2D case, assembling dense matrix representations of the integral operators quickly becomes unfeasiable as the problem size increases. Inti adds support for compressing the underlying linear operators by wrapping external libraries. In this example, we will rely on HMatrices.jl to handle the compression.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The visualization is also more involved, and we will use the Gmsh API to create a not only a mesh of the scatterer, but also of a punctured plane where we will visualize the solution. Here is the function that setups up the mesh:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"function gmsh_sphere(; meshsize, order = gorder, radius = 1, visualize = false, name)\n gmsh.initialize()\n gmsh.model.add(\"sphere-scattering\")\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", meshsize)\n gmsh.option.setNumber(\"Mesh.MeshSizeMin\", meshsize)\n sphere_tag = gmsh.model.occ.addSphere(0, 0, 0, radius)\n xl, yl, zl = -2 * radius, -2 * radius, 0\n Δx, Δy = 4 * radius, 4 * radius\n rectangle_tag = gmsh.model.occ.addRectangle(xl, yl, zl, Δx, Δy)\n outDimTags, _ =\n gmsh.model.occ.cut([(2, rectangle_tag)], [(3, sphere_tag)], -1, true, false)\n gmsh.model.occ.synchronize()\n gmsh.model.addPhysicalGroup(3, [sphere_tag], -1, \"omega\")\n gmsh.model.addPhysicalGroup(2, [dt[2] for dt in outDimTags], -1, \"sigma\")\n gmsh.model.mesh.generate(2)\n gmsh.model.mesh.setOrder(order)\n visualize && gmsh.fltk.run()\n gmsh.option.setNumber(\"Mesh.SaveAll\", 1) # otherwise only the physical groups are saved\n gmsh.write(name)\n return gmsh.finalize()\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"As before, lets write a file with our mesh, and import it into Inti.jl:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"name_sphere = joinpath(@__DIR__, \"sphere.msh\")\ngmsh_sphere(; meshsize = (λ / 5), order = gorder, name = name_sphere, visualize = false)\nmsh_3d = Inti.import_mesh(name_sphere; dim = 3)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"tip: Tip\nIf you pass visualize=true to gmsh_sphere, it will open a window with the current model. This is done by calling gmsh.fltk.run(). Note that the main julia thread will be blocked until the window is closed.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Since we created physical groups in Gmsh, we can use them to extract the relevant domains Ω and Σ:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Ω_3d = Inti.Domain(e -> \"omega\" ∈ Inti.labels(e), Inti.entities(msh_3d))\nΣ_3d = Inti.Domain(e -> \"sigma\" ∈ Inti.labels(e), Inti.entities(msh_3d))\nΓ_3d = Inti.boundary(Ω_3d)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now create a quadrature as before","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Γ_msh_3d = view(msh_3d, Γ_3d)\nQ_3d = Inti.Quadrature(Γ_msh_3d; qorder)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"tip: Writing/reading a mesh from disk\nWriting and reading a mesh to/from disk can be time consuming. You can avoid doing so by using import_mesh without a file name to import the mesh from the current gmsh session without the need to write it to disk.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Next we assemble the integral operators, indicating that we wish to compress them using hierarchical matrices:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"using HMatrices\nop_3d = Inti.Helmholtz(; k, dim = 3)\nS_3d, D_3d = Inti.single_double_layer(;\n op = op_3d,\n target = Q_3d,\n source = Q_3d,\n compression = (method = :hmatrix, tol = 1e-4),\n correction = (method = :dim,),\n)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Here is how much memory it would take to store the dense representation of these matrices:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"mem = 2 * length(S_3d) * 16 / 1e9 # 16 bytes per complex number, 1e9 bytes per GB, two matrices\nprintln(\"memory required to store S and D: $(mem) GB\")","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Even for this simple example, the dense representation of the integral operators as matrix is already quite expensive!","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Compression methods\nIt is worth mentioning that hierchical matrices are not the only way to compress such integral operators, and may in fact not even be the best for the problem at hand. For example, one could use a fast multipole method (FMM), which has a much lighter memory footprint. See the the tutorial on compression methods for more information.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We will use the generalized minimal residual (GMRES) iterative solver, for the linear system. This requires us to define a linear operator L, approximating the combined-field operator, that supports the matrix-vector product. While it is possible to add two HMatrix objects to obtain a new HMatrix, this is somewhat more involved due to the addition of low-rank blocks (which requires a recompression). To keep things simple, we will use LinearMaps to lazily compose the operators:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"L_3d = I / 2 + LinearMap(D_3d) - im * k * LinearMap(S_3d)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now solve the linear system using GMRES solver:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"rhs_3d = map(Q_3d) do q\n x = q.coords\n return -uᵢ(x)\nend\nσ_3d, hist = gmres(\n L_3d,\n rhs_3d;\n log = true,\n abstol = 1e-6,\n verbose = false,\n restart = 100,\n maxiter = 100,\n)\n@show hist","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"As before, let us represent the solution using IntegralPotentials:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"𝒮_3d, 𝒟_3d = Inti.single_double_layer_potential(; op = op_3d, source = Q_3d)\nuₛ_3d = x -> 𝒟_3d[σ_3d](x) - im * k * 𝒮_3d[σ_3d](x)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"To check the result, we compare against the exact solution obtained through a series:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"sphbesselj(l, r) = sqrt(π / (2r)) * besselj(l + 1 / 2, r)\nsphbesselh(l, r) = sqrt(π / (2r)) * besselh(l + 1 / 2, r)\nsphharmonic(l, m, θ, ϕ) = GSL.sf_legendre_sphPlm(l, abs(m), cos(θ)) * exp(im * m * ϕ)\nfunction sphere_helmholtz_soundsoft(xobs; radius = 1, k = 1, θin = 0, ϕin = 0)\n x = xobs[1]\n y = xobs[2]\n z = xobs[3]\n r = sqrt(x^2 + y^2 + z^2)\n θ = acos(z / r)\n ϕ = atan(y, x)\n u = 0.0\n r < radius && return u\n function c(l, m)\n return -4π * im^l * sphharmonic(l, -m, θin, ϕin) * sphbesselj(l, k * radius) /\n sphbesselh(l, k * radius)\n end\n l = 0\n for l = 0:60\n for m = -l:l\n u += c(l, m) * sphbesselh(l, k * r) * sphharmonic(l, m, θ, ϕ)\n end\n l += 1\n end\n return u\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We will compute the error on some point on the sphere of radius 2:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"uₑ_3d = (x) -> sphere_helmholtz_soundsoft(x; radius = 1, k = k, θin = π / 2, ϕin = 0)\ner_3d = maximum(1:100) do _\n x̂ = rand(Inti.Point3D) |> normalize # an SVector of unit norm\n x = 2 * x̂\n return abs(uₛ_3d(x) - uₑ_3d(x))\nend\n@info \"error with correction = $er_3d\"","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"@assert er_3d < 1e-3","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We see that, once again, the approximation is quite accurate. Let us now visualize the solution on the punctured plane (which we labeled as \"sigma\"). Since evaluating the integral representation of the solution at many points is expensive, we will use again use a method to accelerate the evaluation:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Σ_msh = view(msh_3d, Σ_3d)\ntarget = Inti.nodes(Σ_msh)\n\nS_viz, D_viz = Inti.single_double_layer(;\n op = op_3d,\n target,\n source = Q_3d,\n compression = (method = :hmatrix, tol = 1e-4),\n # correction for the nearfield (for visual purposes, set to `:none` to disable)\n correction = (method = :dim, maxdist = meshsize, target_location = :outside),\n)\n\nui_eval_msh = uᵢ.(target)\nus_eval_msh = D_viz * σ_3d - im * k * S_viz * σ_3d\nu_eval_msh = ui_eval_msh + us_eval_msh\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Finalize, we use Meshes.viz to visualize the scattered field:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"nv = length(Inti.nodes(Γ_msh_3d))\ncolorrange = extrema(real(u_eval_msh))\ncolormap = :inferno\nfig_3d = Figure(; size = (800, 500))\nax_3d = Axis3(fig_3d[1, 1]; aspect = :data)\nviz!(Γ_msh_3d; colorrange, colormap, color = zeros(nv), interpolate = true)\nviz!(Σ_msh; colorrange, colormap, color = real(u_eval_msh))\ncb = Colorbar(fig_3d[1, 2]; label = \"real(u)\", colormap, colorrange)\nfig_3d #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"TableOfContents()","category":"page"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"(Image: Pluto notebook)","category":"page"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"begin\n import Pkg as _Pkg\n haskey(ENV, \"PLUTO_PROJECT\") && _Pkg.activate(ENV[\"PLUTO_PROJECT\"])\nend;","category":"page"},{"location":"pluto-examples/toy_example/#Toy-example","page":"Toy example","title":"Toy example","text":"","category":"section"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"All examples in Inti.jl are autogenerated by executing the make.jl script in the docs folder. The workflow uses Pluto.jl to generate markdown files passed to Documenter.jl. The original pluto notebook files are downloadable from the example's page.","category":"page"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"using Inti","category":"page"},{"location":"docstrings/#Docstrings","page":"Docstrings","title":"Docstrings","text":"","category":"section"},{"location":"docstrings/","page":"Docstrings","title":"Docstrings","text":"CurrentModule = Inti","category":"page"},{"location":"docstrings/","page":"Docstrings","title":"Docstrings","text":"Modules = [Inti]","category":"page"},{"location":"docstrings/#Inti.Inti","page":"Docstrings","title":"Inti.Inti","text":"module Inti\n\nLibrary for solving integral equations using Nyström methods.\n\n\n\n\n\n","category":"module"},{"location":"docstrings/#Inti.COMPRESSION_METHODS","page":"Docstrings","title":"Inti.COMPRESSION_METHODS","text":"const COMPRESSION_METHODS = [:none, :hmatrix, :fmm]\n\nAvailable compression methods for the dense linear operators in Inti.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.CORRECTION_METHODS","page":"Docstrings","title":"Inti.CORRECTION_METHODS","text":"const CORRECTION_METHODS = [:none, :dim, :hcubature]\n\nAvailable correction methods for the singular and nearly-singular integrals in Inti.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.ENTITIES","page":"Docstrings","title":"Inti.ENTITIES","text":"const ENTITIES\n\nDictionary mapping EntityKey to GeometricEntity. Contains all entities created in a given session.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.SAME_POINT_TOLERANCE","page":"Docstrings","title":"Inti.SAME_POINT_TOLERANCE","text":"SAME_POINTS_TOLERANCE\n\nTwo points x and y are considerd the same if norm(x-y) ≤ SAME_POINT_TOLERANCE.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.AbstractDifferentialOperator","page":"Docstrings","title":"Inti.AbstractDifferentialOperator","text":"abstract type AbstractDifferentialOperator{N}\n\nA partial differential operator in dimension N.\n\nAbstractDifferentialOperator types are used to define AbstractKernels related to fundamental solutions of differential operators.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.AbstractKernel","page":"Docstrings","title":"Inti.AbstractKernel","text":"abstract type AbstractKernel{T}\n\nA kernel functions K with the signature K(target,source)::T.\n\nSee also: SingleLayerKernel, DoubleLayerKernel, AdjointDoubleLayerKernel, HyperSingularKernel\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.AbstractMesh","page":"Docstrings","title":"Inti.AbstractMesh","text":"abstract type AbstractMesh{N,T}\n\nAn abstract mesh structure in dimension N with primite data of type T (e.g. Float64 for double precision representation).\n\nConcrete subtypes of AbstractMesh should implement ElementIterator for accessing the mesh elements.\n\nSee also: Mesh\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.AdjointDoubleLayerKernel","page":"Docstrings","title":"Inti.AdjointDoubleLayerKernel","text":"struct AdjointDoubleLayerKernel{T,Op} <: AbstractKernel{T}\n\nGiven an operator Op, construct its free-space adjoint double-layer kernel. This corresponds to the transpose(γ₁,ₓ[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the target variable.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.BlockArray","page":"Docstrings","title":"Inti.BlockArray","text":"struct BlockArray{T<:StaticArray,N,S} <: AbstractMatrix{T,N}\n\nA struct which behaves like an Array{T,N}, but with the underlying data stored as a Matrix{S}, where S::Number = eltype(T) is the scalar type associated with T. This allows for the use of blas routines under-the-hood, while providing a convenient interface for handling matrices over StaticArrays.\n\nusing StaticArrays\nT = SMatrix{2,2,Int,4}\nB = Inti.BlockArray{T}([i*j for i in 1:4, j in 1:4])\n\n# output\n\n2×2 Inti.BlockArray{SMatrix{2, 2, Int64, 4}, 2, Int64}:\n [1 2; 2 4] [3 4; 6 8]\n [3 6; 4 8] [9 12; 12 16]\n\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.DimParameters","page":"Docstrings","title":"Inti.DimParameters","text":"struct DimParameters\n\nParameters associated with the density interpolation method used in bdim_correction.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Domain","page":"Docstrings","title":"Inti.Domain","text":"struct Domain\n\nRepresentation of a geometrical domain formed by a set of entities with the same geometric dimension. For basic set operations on domains are supported (union, intersection, difference, etc), and they all return a new Domain object.\n\nCalling keys(Ω) returns the set of EntityKeys that make up the domain; given a key, the underlying entities can be accessed with global_get_entity(key).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Domain-Tuple{Function, Any}","page":"Docstrings","title":"Inti.Domain","text":"Domain([f::Function,] keys)\n\nCreate a domain from a set of EntityKeys. Optionally, a filter function f can be passed to filter the entities.\n\nNote that all entities in a domain must have the same geometric dimension.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Domain-Tuple{Function, Inti.AbstractMesh}","page":"Docstrings","title":"Inti.Domain","text":"Domain(f::Function, msh::AbstractMesh)\n\nCall Domain(f, ents) on ents = entities(msh).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.DoubleLayerKernel","page":"Docstrings","title":"Inti.DoubleLayerKernel","text":"struct DoubleLayerKernel{T,Op} <: AbstractKernel{T}\n\nGiven an operator Op, construct its free-space double-layer kernel. This corresponds to the γ₁ trace of the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the source variable.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Elastostatic","page":"Docstrings","title":"Inti.Elastostatic","text":"struct Elastostatic{N,T} <: AbstractDifferentialOperator{N}\n\nElastostatic operator in N dimensions: -μΔu - (μ+λ)∇(∇⋅u)\n\nNote that the displacement u is a vector of length N since this is a vectorial problem.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ElementIterator","page":"Docstrings","title":"Inti.ElementIterator","text":"struct ElementIterator{E,M} <: AbstractVector{E}\n\nStructure to lazily access elements of type E in a mesh of type M. This is particularly useful for LagrangeElements, where the information to reconstruct the element is stored in the mesh connectivity matrix.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.EmbeddedQuadrature","page":"Docstrings","title":"Inti.EmbeddedQuadrature","text":"struct EmbeddedQuadrature{L,H,D} <: ReferenceQuadrature{D}\n\nA quadrature rule for the reference shape D based on a high-order quadrature of type H and a low-order quadrature of type L. The low-order quadrature rule is embedded in the sense that its n nodes are exactly the first n nodes of the high-order quadrature rule.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.EntityKey","page":"Docstrings","title":"Inti.EntityKey","text":"EntityKey\n\nUsed to represent the key of a GeometricEntity, comprised of a dim and a tag field, where dim is the geometrical dimension of the entity, and tag is a unique integer identifying the entity.\n\nThe sign of the tag field is used to distinguish the orientation of the entity, and is ignored when comparing two EntityKeys for equality.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Fejer","page":"Docstrings","title":"Inti.Fejer","text":"struct Fejer{N}\n\nN-point Fejer's first quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ N-1.\n\nusing Inti\n\nq = Inti.Fejer(;order=10)\n\nInti.integrate(cos,q) ≈ sin(1) - sin(0)\n\n# output\n\ntrue\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Gauss","page":"Docstrings","title":"Inti.Gauss","text":"struct Gauss{D,N} <: ReferenceQuadrature{D}\n\nTabulated N-point symmetric Gauss quadrature rule for integration over D.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.GaussLegendre","page":"Docstrings","title":"Inti.GaussLegendre","text":"struct GaussLegendre{N,T}\n\nN-point Gauss-Legendre quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ 2N-1.\n\nusing Inti\n\nq = Inti.GaussLegendre(;order=10)\n\nInti.integrate(cos,q) ≈ sin(1) - sin(0)\n\n# output\n\ntrue\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.GeometricEntity","page":"Docstrings","title":"Inti.GeometricEntity","text":"struct GeometricEntity\n\nGeometrical objects such as lines, surfaces, and volumes.\n\nGeometrical entities are stored in a global ENTITIES dictionary mapping EntityKey to the corresponding GeometricEntity, and usually entities are manipulated through their keys.\n\nA GeometricEntity can also contain a pushforward field used to parametrically represent the entry as the image of a reference domain (pushforward.domain) under some function (pushforward.parametrization).\n\nNote that entities are manipulated through their keys, and the GeometricEntity constructor returns the key of the created entity; to retrieve the entity, use the global_get_entity function.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.GeometricEntity-Tuple{String}","page":"Docstrings","title":"Inti.GeometricEntity","text":"GeometricEntity(shape::String [; translation, rotation, scaling, kwargs...])\n\nConstructs a geometric entity with the specified shape and optional parameters, and returns its key.\n\nArguments\n\nshape::String: The shape of the geometric entity.\ntranslation: The translation vector of the geometric entity. Default is SVector(0, 0, 0).\nrotation: The rotation vector of the geometric entity. Default is SVector(0, 0, 0).\nscaling: The scaling vector of the geometric entity. Default is SVector(1, 1, 1).\nkwargs...: Additional keyword arguments to be passed to the shape constructor.\n\nSupported shapes\n\nellipsoid\ntorus\nbean\nacorn\ncushion\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Helmholtz-Tuple{}","page":"Docstrings","title":"Inti.Helmholtz","text":"Helmholtz(; k, dim)\n\nHelmholtz operator in dim dimensions: -Δu - k²u.\n\nThe parameter k can be a real or complex number. For purely imaginary wavenumbers, consider using the Yukawa kernel.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.HyperRectangle","page":"Docstrings","title":"Inti.HyperRectangle","text":"struct HyperRectangle{N,T} <: ReferenceInterpolant{ReferenceHyperCube{N},T}\n\nAxis-aligned hyperrectangle in N dimensions given by low_corner::SVector{N,T} and high_corner::SVector{N,T}.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.HyperSingularKernel","page":"Docstrings","title":"Inti.HyperSingularKernel","text":"struct HyperSingularKernel{T,Op} <: AbstractKernel{T}\n\nGiven an operator Op, construct its free-space hypersingular kernel. This corresponds to the transpose(γ₁,ₓγ₁[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative respect to the target variable of the DoubleLayerKernel.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.IntegralOperator","page":"Docstrings","title":"Inti.IntegralOperator","text":"struct IntegralOperator{T} <: AbstractMatrix{T}\n\nA discrete linear integral operator given by\n\nIu(x) = int_Gamma_s K(xy)u(y) ds_y x in Gamma_t\n\nwhere Gamma_s and Gamma_t are the source and target domains, respectively.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.IntegralPotential","page":"Docstrings","title":"Inti.IntegralPotential","text":"struct IntegralPotential\n\nRepresent a potential given by a kernel and a quadrature over which integration is performed.\n\nIntegralPotentials are created using IntegralPotential(kernel, quadrature).\n\nEvaluating an integral potential requires a density σ (defined over the quadrature nodes of the source mesh) and a point x at which to evaluate the integral\n\nint_Gamma K(oldsymbolxoldsymboly)sigma(y) ds_y x not in Gamma\n\nAssuming 𝒮 is an integral potential and σ is a vector of values defined on quadrature, calling 𝒮[σ] creates an anonymous function that can be evaluated at any point x.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Kronrod","page":"Docstrings","title":"Inti.Kronrod","text":"struct Kronrod{D,N} <: ReferenceQuadrature{D}\n\nN-point Kronrod rule obtained by adding n+1 points to a Gauss quadrature containing n points. The order is either 3n + 1 for n even or 3n + 2 for n odd.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeCube","page":"Docstrings","title":"Inti.LagrangeCube","text":"const LagrangeSquare = LagrangeElement{ReferenceSquare}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeElement","page":"Docstrings","title":"Inti.LagrangeElement","text":"struct LagrangeElement{D,Np,T} <: ReferenceInterpolant{D,T}\n\nA polynomial p : D → T uniquely defined by its Np values on the Np reference nodes of D.\n\nThe return type T should be a vector space (i.e. support addition and multiplication by scalars). For istance, T could be a number or a vector, but not a Tuple.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeLine","page":"Docstrings","title":"Inti.LagrangeLine","text":"const LagrangeLine = LagrangeElement{ReferenceLine}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeSquare","page":"Docstrings","title":"Inti.LagrangeSquare","text":"const LagrangeSquare = LagrangeElement{ReferenceSquare}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeTetrahedron","page":"Docstrings","title":"Inti.LagrangeTetrahedron","text":"const LagrangeTetrahedron = LagrangeElement{ReferenceTetrahedron}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeTriangle","page":"Docstrings","title":"Inti.LagrangeTriangle","text":"const LagrangeTriangle = LagrangeElement{ReferenceTriangle}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Laplace-Tuple{}","page":"Docstrings","title":"Inti.Laplace","text":"Laplace(; dim)\n\nLaplace's differential operator in dim dimension: -Δu. ```\n\nNote the negative sign in the definition.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Mesh","page":"Docstrings","title":"Inti.Mesh","text":"struct Mesh{N,T} <: AbstractMesh{N,T}\n\nUnstructured mesh defined by a set of nodes(of typeSVector{N,T}`), and a dictionary mapping element types to connectivity matrices. Each columns of a given connectivity matrix stores the integer tags of the nodes in the mesh comprising the element.\n\nAdditionally, the mesh contains a mapping from EntityKeys to the tags of the elements composing the entity. This can be used to extract submeshes from a given mesh using e.g. view(msh,Γ) or msh[Γ], where Γ is a Domain.\n\nSee elements for a way to iterate over the elements of a mesh.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ModifiedHelmholtz","page":"Docstrings","title":"Inti.ModifiedHelmholtz","text":"const ModifiedHelmholtz\n\nType alias for the Yukawa operator.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.MultiIndex","page":"Docstrings","title":"Inti.MultiIndex","text":"MultiIndex{N}\n\nWrapper around NTuple{N,Int} mimicking a multi-index in ℤ₀ᴺ.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ParametricElement","page":"Docstrings","title":"Inti.ParametricElement","text":"ParametricElement{D,T,F} <: ReferenceInterpolant{D,T}\n\nAn element represented through a explicit function f mapping D into the element. For performance reasons, f should take as input a StaticVector and return a StaticVector or StaticArray.\n\nSee also: ReferenceInterpolant, LagrangeElement\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ParametricElement-Union{Tuple{T}, Tuple{N}, Tuple{Any, Inti.HyperRectangle{N, T}}} where {N, T}","page":"Docstrings","title":"Inti.ParametricElement","text":"ParametricElement(f, d::HyperRectangle)\n\nConstruct the element defined as the image of f over d.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.PolynomialSpace","page":"Docstrings","title":"Inti.PolynomialSpace","text":"struct PolynomialSpace{D,K}\n\nThe space of all polynomials of degree ≤K, commonly referred to as ℙₖ.\n\nThe type parameter D, of singleton type, is used to determine the reference domain of the polynomial basis. In particular, when D is a hypercube in d dimensions, the precise definition is ℙₖ = span{𝐱ᶿ : 0≤max(θ)≤ K}; when D is a d-dimensional simplex, the space is ℙₖ = span{𝐱ᶿ : 0≤sum(θ)≤ K}, where θ ∈ 𝐍ᵈ is a multi-index.\n\nSee also: monomial_basis, lagrange_basis\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Quadrature","page":"Docstrings","title":"Inti.Quadrature","text":"struct Quadrature{N,T} <: AbstractVector{QuadratureNode{N,T}}\n\nA collection of QuadratureNodes used to integrate over an AbstractMesh.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Quadrature-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.Quadrature","text":"Quadrature(Ω::Domain; meshsize, qorder)\n\nConstruct a Quadrature over the domain Ω with a mesh of size meshsize and quadrature order qorder.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Quadrature-Union{Tuple{T}, Tuple{N}, Tuple{Inti.AbstractMesh{N, T}, Dict}} where {N, T}","page":"Docstrings","title":"Inti.Quadrature","text":"Quadrature(msh::AbstractMesh, etype2qrule::Dict)\nQuadrature(msh::AbstractMesh, qrule::ReferenceQuadrature)\nQuadrature(msh::AbstractMesh; qorder)\n\nConstruct a Quadrature for msh, where for each element type E in msh the reference quadrature q = etype2qrule[E] is used. When a single qrule is passed, it is used for all element types in msh.\n\nIf an order keyword is passed, a default quadrature of the desired order is used for each element type usig _qrule_for_reference_shape.\n\nFor co-dimension one elements, the normal vector is also computed and stored in the QuadratureNodes.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.QuadratureNode","page":"Docstrings","title":"Inti.QuadratureNode","text":"QuadratureNode{N,T<:Real}\n\nA point in ℝᴺ with a weight for performing numerical integration. A QuadratureNode can optionally store a normal vector.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceCube","page":"Docstrings","title":"Inti.ReferenceCube","text":"const ReferenceCube = ReferenceHyperCube{3}\n\nSingleton type representing the unit cube [0,1]³.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceHyperCube","page":"Docstrings","title":"Inti.ReferenceHyperCube","text":"struct ReferenceHyperCube{N} <: ReferenceShape{N}\n\nSingleton type representing the axis-aligned hypercube in N dimensions with the lower corner at the origin and the upper corner at (1,1,…,1).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceInterpolant","page":"Docstrings","title":"Inti.ReferenceInterpolant","text":"abstract type ReferenceInterpolant{D,T}\n\nInterpolanting function mapping points on the domain D<:ReferenceShape (of singleton type) to a value of type T.\n\nInstances el of ReferenceInterpolant are expected to implement:\n\nel(x̂): evaluate the interpolation scheme at the (reference) coordinate x̂ ∈ D.\njacobian(el,x̂) : evaluate the jacobian matrix of the interpolation at the (reference) coordinate x ∈ D.\n\nnote: Note\nFor performance reasons, both el(x̂) and jacobian(el,x̂) should take as input a StaticVector and output a static vector or static array.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceLine","page":"Docstrings","title":"Inti.ReferenceLine","text":"const ReferenceLine = ReferenceHyperCube{1}\n\nSingleton type representing the [0,1] segment.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceQuadrature","page":"Docstrings","title":"Inti.ReferenceQuadrature","text":"abstract type ReferenceQuadrature{D}\n\nA quadrature rule for integrating a function over the domain D <: ReferenceShape.\n\nCalling x,w = q() returns the nodes x, given as SVectors, and weights w, for performing integration over domain(q).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceShape","page":"Docstrings","title":"Inti.ReferenceShape","text":"abstract type ReferenceShape\n\nA fixed reference domain/shape. Used mostly for defining more complex shapes as transformations mapping an ReferenceShape to some region of ℜᴹ.\n\nSee e.g. ReferenceLine or ReferenceTriangle for some examples of concrete subtypes.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceSimplex","page":"Docstrings","title":"Inti.ReferenceSimplex","text":"struct ReferenceSimplex{N}\n\nSingleton type representing the N-simplex with N+1 vertices (0,...,0),(0,...,0,1),(0,...,0,1,0),(1,0,...,0)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceSquare","page":"Docstrings","title":"Inti.ReferenceSquare","text":"const ReferenceSquare = ReferenceHyperCube{2}\n\nSingleton type representing the unit square [0,1]².\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceTetrahedron","page":"Docstrings","title":"Inti.ReferenceTetrahedron","text":"struct ReferenceTetrahedron\n\nSingleton type representing the tetrahedron with vertices (0,0,0),(0,0,1),(0,1,0),(1,0,0)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceTriangle","page":"Docstrings","title":"Inti.ReferenceTriangle","text":"struct ReferenceTriangle\n\nSingleton type representing the triangle with vertices (0,0),(1,0),(0,1)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.SingleLayerKernel","page":"Docstrings","title":"Inti.SingleLayerKernel","text":"struct SingleLayerKernel{T,Op} <: AbstractKernel{T}\n\nThe free-space single-layer kernel (i.e. the fundamental solution) of an Op <: AbstractDifferentialOperator.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Stokes-Tuple{}","page":"Docstrings","title":"Inti.Stokes","text":"Stokes(; μ, dim)\n\nStokes operator in dim dimensions: -μΔu + p u.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.SubMesh","page":"Docstrings","title":"Inti.SubMesh","text":"struct SubMesh{N,T} <: AbstractMesh{N,T}\n\nView into a parent mesh over a given domain.\n\nA submesh implements the interface for AbstractMesh; therefore you can iterate over elements of the submesh just like you would with a mesh.\n\nConstruct SubMeshs using view(parent,Ω::Domain).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.TensorProductQuadrature","page":"Docstrings","title":"Inti.TensorProductQuadrature","text":"TensorProductQuadrature{N,Q}\n\nA tensor-product of one-dimension quadrature rules. Integrates over [0,1]^N.\n\nExamples\n\nqx = Inti.Fejer(10)\nqy = Inti.Fejer(15)\nq = Inti.TensorProductQuadrature(qx,qy)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.VioreanuRokhlin","page":"Docstrings","title":"Inti.VioreanuRokhlin","text":"struct VioreanuRokhlin{D,N} <: ReferenceQuadrature{D}\n\nTabulated N-point Vioreanu-Rokhlin quadrature rule for integration over D.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Yukawa-Tuple{}","page":"Docstrings","title":"Inti.Yukawa","text":"Yukawa(; λ, dim)\n\nYukawa operator, also known as modified Helmholtz, in dim dimensions: -Δu + λ²u.\n\nThe parameter λ is a positive number. Note the negative sign in front of the Laplacian.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Base.iterate","page":"Docstrings","title":"Base.iterate","text":"iterate(Ω::Domain)\n\nIterating over a domain means iterating over its entities.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti._copyto!-Tuple{AbstractMatrix{<:Number}, AbstractMatrix{<:StaticArraysCore.SMatrix}}","page":"Docstrings","title":"Inti._copyto!","text":"_copyto!(target,source)\n\nDefaults to Base.copyto!, but includes some specialized methods to copy from a Matrix of SMatrix to a Matrix of Numbers and viceversa.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._get_gauss_qcoords_and_qweights-Tuple{Type{<:Inti.ReferenceShape}, Any}","page":"Docstrings","title":"Inti._get_gauss_qcoords_and_qweights","text":"_get_gauss_and_qweights(R::Type{<:ReferenceShape{D}}, N) where D\n\nReturns the N-point symmetric gaussian qnodes and qweights (x, w) for integration over R.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._get_vioreanurokhlin_qcoords_and_qweights-Tuple{Type{<:Inti.ReferenceShape}, Any}","page":"Docstrings","title":"Inti._get_vioreanurokhlin_qcoords_and_qweights","text":"_get_vioreanurokhlin_qcoords_and_qweights(R::Type{<:ReferenceShape{D}}, N) where D\n\nReturns the N-point Vioreanu-Rokhlin qnodes and qweights (x, w) for integration over R.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._green_multiplier-Tuple{Symbol}","page":"Docstrings","title":"Inti._green_multiplier","text":"_green_multiplier(s::Symbol)\n\nReturn -1.0 if s == :inside, 0.0 if s == :outside, and -0.5 if s == :on; otherwise, throw an error. The orientation is relative to the normal of the bounding curve/surface.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._green_multiplier-Union{Tuple{N}, Tuple{StaticArraysCore.SVector, Inti.Quadrature{N}}} where N","page":"Docstrings","title":"Inti._green_multiplier","text":"_green_multiplier(x, quad)\n\nHelper function to help determine the constant σ in the Green identity S[γ₁u](x)\n\nD[γ₀u](x) + σ*u(x) = 0. This can be used as a predicate to determine whether a\n\npoint is inside a domain or not.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._meshgen-Tuple{Any, Inti.HyperRectangle, NTuple{N, T} where {N, T}}","page":"Docstrings","title":"Inti._meshgen","text":"_meshgen(f,d::HyperRectangle,sz)\n\nCreate prod(sz) elements of ParametricElement type representing the push forward of f on each of the subdomains defined by a uniform cartesian mesh of d of size sz.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._normal-Union{Tuple{StaticArraysCore.SMatrix{N, M}}, Tuple{M}, Tuple{N}} where {N, M}","page":"Docstrings","title":"Inti._normal","text":"_normal(jac::SMatrix{M,N})\n\nGiven a an M by N matrix representing the jacobian of a codimension one object, compute the normal vector.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._qrule_for_reference_shape-Tuple{Any, Any}","page":"Docstrings","title":"Inti._qrule_for_reference_shape","text":"_qrule_for_reference_shape(ref,order)\n\nGiven a reference shape and a desired quadrature order, return an appropiate quadrature rule.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.acorn-Tuple{}","page":"Docstrings","title":"Inti.acorn","text":"acorn(; translation, rotation, scaling, labels)\n\nCreate an acorn entity in 3D, and apply optional transformations. Returns the key.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adaptive_correction-Tuple{Inti.IntegralOperator}","page":"Docstrings","title":"Inti.adaptive_correction","text":"adaptive_correction(iop::IntegralOperator; tol, maxdist = farfield_distance(iop; atol), maxsplit = 1000])\n\nGiven an integral operator iop, this function provides a sparse correction to iop for the entries i,j such that the distance between the i-th target and the j-th source is less than maxdist.\n\nChoosing maxdist is a trade-off between accuracy and efficiency. The smaller the value, the fewer corrections are needed, but this may compromise the accuracy. For a fixed quadrature, the size of maxdist has to grow as the tolerance tol decreases. The default [farfield_distance(iop; tol)](@ref) provides a heuristic to determine a suitablemaxdist`.\n\nThe correction is computed by using the adaptive_integration routine, with a tolerance atol and a maximum number of subdivisions maxsplit; see adaptive_integration for more details.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adaptive_integration-Tuple{Any, Inti.EmbeddedQuadrature}","page":"Docstrings","title":"Inti.adaptive_integration","text":"adaptive_integration(f, τ̂::RefernceShape; kwargs...)\nadaptive_integration(f, qrule::EmbeddedQuadrature; kwargs...)\n\nUse an adaptive procedure to estimate the integral of f over τ̂ = domain(qrule). The following optional keyword arguments are available:\n\natol::Real=0.0: absolute tolerance for the integral estimate\nrtol::Real=0.0: relative tolerance for the integral estimate\nmaxsplit::Int=1000: maximum number of times to split the domain\nnorm::Function=LinearAlgebra.norm: norm to use for error estimates\nbuffer::BinaryHeap: a pre-allocated buffer to use for the adaptive procedure (see allocate_buffer)\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adaptive_integration_singular-Tuple{Any, Inti.ReferenceHyperCube{1}, Any}","page":"Docstrings","title":"Inti.adaptive_integration_singular","text":"adaptive_integration_singular(f, τ̂, x̂ₛ; kwargs...)\n\nSimilar to adaptive_integration, but indicates that f has an isolated (integrable) singularity at x̂ₛ ∈ x̂ₛ.\n\nThe integration is performed by splitting τ̂ so that x̂ₛ is a fixed vertex, guaranteeing that f is never evaluated at x̂ₛ. Aditionally, a suitable change of variables may be applied to alleviate the singularity and improve the rate of convergence.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adj_double_layer_hypersingular-Tuple{}","page":"Docstrings","title":"Inti.adj_double_layer_hypersingular","text":"adj_double_layer_hypersingular(; op, target, source, compression,\ncorrection)\n\nSimilar to single_double_layer, but for the adjoint double-layer and hypersingular operators. See the documentation of [single_double_layer] for a description of the arguments.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.allocate_buffer-Tuple{Any, Inti.EmbeddedQuadrature}","page":"Docstrings","title":"Inti.allocate_buffer","text":"allocate_buffer(f, quad::EmbeddedQuadrature)\n\nCreate the buffer needed for the call adaptive_integration(f, τ̂; buffer, ...).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.ambient_dimension","page":"Docstrings","title":"Inti.ambient_dimension","text":"ambient_dimension(x)\n\nDimension of the ambient space where x lives. For geometrical objects this can differ from its geometric_dimension; for example a triangle in ℝ³ has ambient dimension 3 but geometric dimension 2, while a curve in ℝ³ has ambient dimension 3 but geometric dimension 1.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.assemble_fmm-Tuple{Inti.IntegralOperator, Vararg{Any}}","page":"Docstrings","title":"Inti.assemble_fmm","text":"assemble_fmm(iop; atol)\n\nSet up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.\n\nwarning: FMMLIB2D\nFMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.assemble_hmatrix-Tuple","page":"Docstrings","title":"Inti.assemble_hmatrix","text":"assemble_hmatrix(iop[; atol, rank, rtol, eta])\n\nAssemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.\n\nSee the documentation of HMatrices for more details on usage and other keyword arguments.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.assemble_matrix-Tuple{Inti.IntegralOperator}","page":"Docstrings","title":"Inti.assemble_matrix","text":"assemble_matrix(iop::IntegralOperator; threads = true)\n\nAssemble a dense matrix representation of an IntegralOperator.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.bdim_correction-Tuple{Any, Any, Inti.Quadrature, Any, Any}","page":"Docstrings","title":"Inti.bdim_correction","text":"bdim_correction(op,X,Y,S,D; green_multiplier, kwargs...)\n\nGiven a op and a (possibly innacurate) discretizations of its single and double-layer operators S and D (taking a vector of values on Y and returning a vector on of values on X), compute corrections δS and δD such that S + δS and D + δD are more accurate approximations of the underlying single- and double-layer integral operators.\n\nSee [7] for more details on the method.\n\nArguments\n\nRequired:\n\nop must be an AbstractDifferentialOperator\nY must be a Quadrature object of a closed surface\nX is either inside, outside, or on Y\nS and D are approximations to the single- and double-layer operators for op taking densities in Y and returning densities in X.\ngreen_multiplier (keyword argument) is a vector with the same length as X storing the value of μ(x) for x ∈ X in the Green identity S\\[γ₁u\\](x) - D\\[γ₀u\\](x) + μ*u(x) = 0. See _green_multiplier.\n\nOptional kwargs:\n\nparameters::DimParameters: parameters associated with the density interpolation method\nderivative: if true, compute the correction to the adjoint double-layer and hypersingular operators instead. In this case, S and D should be replaced by a (possibly innacurate) discretization of adjoint double-layer and hypersingular operators, respectively.\nmaxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections when X and Y are different surfaces. When X === Y, this is not needed.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.bean-Tuple{}","page":"Docstrings","title":"Inti.bean","text":"bean(; translation, rotation, scaling, labels)\n\nCreate a bean entity in 3D, and apply optional transformations. Returns the key.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.boundary-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.boundary","text":"boundary(Ω::Domain)\n\nReturn the external boundaries of a domain.\n\nSee also: external_boundary, internal_boundary, skeleton.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.boundary_idxs-Tuple{Inti.LagrangeElement{Inti.ReferenceHyperCube{1}}}","page":"Docstrings","title":"Inti.boundary_idxs","text":"boundary_idxs(el::LagrangeElement)\n\nThe indices of the nodes in el that define the boundary of the element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.cart2sph-Tuple{Any, Any, Any}","page":"Docstrings","title":"Inti.cart2sph","text":"cart2sph(x,y,z)\n\nMap cartesian coordinates x,y,z to spherical ones r, θ, φ representing the radius, elevation, and azimuthal angle respectively. The convention followed is that 0 ≤ θ ≤ π and -π < φ ≤ π. Same as the cart2sph function in MATLAB.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.connectivity-Tuple{Inti.Mesh, DataType}","page":"Docstrings","title":"Inti.connectivity","text":"connectivity(msh::AbstractMesh,E::DataType)\n\nReturn the connectivity matrix for elements of type E in msh. The integer tags in the matrix refer to the points in nodes(msh)\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.coords-Tuple{T} where T","page":"Docstrings","title":"Inti.coords","text":"coords(q)\n\nReturn the spatial coordinates of q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.cushion-Tuple{}","page":"Docstrings","title":"Inti.cushion","text":"cushion(; translation, rotation, scaling, labels)\n\nCreate a cushion entity in 3D, and apply optional transformations. Returns the key.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.decompose","page":"Docstrings","title":"Inti.decompose","text":"decompose(s::ReferenceShape,x)\n\nDecompose an ReferenceShape into LagrangeElements so that x is a fixed vertex of the children elements.\n\nThe decomposed elements may be oriented differently than the parent, and thus care has to be taken regarding e.g. normal vectors.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.degree-Union{Tuple{Type{<:Inti.LagrangeElement{D, Np}}}, Tuple{Np}, Tuple{D}} where {D, Np}","page":"Docstrings","title":"Inti.degree","text":"degree(el::LagrangeElement)\ndegree(el::Type{<:LagrangeElement})\n\nThe polynomial degree el.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.dimension-Union{Tuple{Type{Inti.PolynomialSpace{D, K}}}, Tuple{K}, Tuple{D}} where {D, K}","page":"Docstrings","title":"Inti.dimension","text":"dimension(space)\n\nThe length of a basis for space; i.e. the number of linearly independent elements required to span space.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.dom2elt-Tuple{Inti.AbstractMesh, Inti.Domain, DataType}","page":"Docstrings","title":"Inti.dom2elt","text":"dom2elt(m::Mesh,Ω,E)::Vector{Int}\n\nCompute the element indices idxs of the elements of type E composing Ω.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.dom2qtags-Tuple{Inti.Quadrature, Inti.Domain}","page":"Docstrings","title":"Inti.dom2qtags","text":"dom2qtags(Q::Quadrature, dom::Domain)\n\nGiven a domain, return the indices of the quadratures nodes in Q associated to its quadrature.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.domain","page":"Docstrings","title":"Inti.domain","text":"domain(f)\n\nGiven a function-like object f: Ω → R, return Ω.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.domain-Tuple{Inti.AbstractMesh}","page":"Docstrings","title":"Inti.domain","text":"domain(msh::AbstractMesh)\n\nReturn a [Domain] containing of all entities covered by the mesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.domain-Tuple{Inti.Quadrature}","page":"Docstrings","title":"Inti.domain","text":"domain(Q::Quadrature)\n\nThe Domain over which Q performs integration.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.domain-Union{Tuple{Inti.ReferenceQuadrature{D}}, Tuple{D}} where D","page":"Docstrings","title":"Inti.domain","text":"domain(q::ReferenceQuadrature)\n\nThe domain of integratino for quadrature rule q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.element_types","page":"Docstrings","title":"Inti.element_types","text":"element_types(msh::AbstractMesh)\n\nReturn the element types present in the msh.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.elements","page":"Docstrings","title":"Inti.elements","text":"elements(msh::AbstractMesh [, E::DataType])\n\nReturn the elements of a msh. Passing and element type E will restricts to elements of that type.\n\nA common pattern to avoid type-instabilies in performance critical parts of the code is to use a function barrier, as illustrated below:\n\nfor E in element_types(msh)\n _long_computation(elements(msh, E), args...)\nend\n\n@noinline function _long_computation(iter, args...)\n for el in iter # the type of el is known at compile time\n # do something with el\n end\nend\n\nwhere a dynamic dispatch is performed only on the element types (typically small for a given mesh).\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.ellipsoid-Tuple{}","page":"Docstrings","title":"Inti.ellipsoid","text":"ellipsoid(; translation, rotation, scaling, labels)\n\nCreate an ellipsoid entity in 3D, and apply optional transformations. Returns the key of the created entity.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.ent2etags-Tuple{Inti.Mesh}","page":"Docstrings","title":"Inti.ent2etags","text":"ent2etags(msh::AbstractMesh)\n\nReturn a dictionary mapping entities to a dictionary of element types to element tags.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.entities-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.entities","text":"entities(Ω::Domain)\n\nReturn all entities making up a domain (as a set of EntityKeys).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.etype_to_nearest_points-Tuple{Any, Inti.Quadrature}","page":"Docstrings","title":"Inti.etype_to_nearest_points","text":"etype_to_nearest_points(X,Y::Quadrature; maxdist)\n\nFor each element el in Y.mesh, return a list with the indices of all points in X for which el is the nearest element. Ignore indices for which the distance exceeds maxdist.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.external_boundary-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.external_boundary","text":"external_boundary(Ω::Domain)\n\nReturn the external boundaries inside a domain. These are entities in the skeleton of Ω which are not in the internal boundaries of Ω.\n\nSee also: internal_boundary, skeleton.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.farfield_distance-Tuple{Inti.IntegralOperator}","page":"Docstrings","title":"Inti.farfield_distance","text":"farfield_distance(iop::IntegralOperator; tol, maxiter = 10)\nfarfield_distance(K, Q::Quadrature; tol, maxiter = 10)\n\nReturn an estimate of the distance d such that the (absolute) quadrature error of the integrand y -> K(x,y) is below tol for x at a distance d from the center of the largest element in Q; when an integral operator is passed, we have Q::Quadrature = source(iop) and K = kernel(iop).\n\nThe estimate is computed by finding the first integer n such that the quadrature error on the largest element τ lies below tol for points x satisfying dist(x,center(τ)) = n*radius(τ).\n\nNote that the desired tolerance may not be achievable if the quadrature rule is not accurate enough, or if τ is not sufficiently small, and therefore a maximum number of iterations maxiter is provided to avoid an infinite loops. In such cases, it is recommended that you either increase the quadrature order, or decrease the mesh size.\n\nNote: this is obviously a heuristic, and may not be accurate in all cases.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.fibonnaci_points_sphere-Tuple{Any, Any, Any}","page":"Docstrings","title":"Inti.fibonnaci_points_sphere","text":"fibonnaci_points_sphere(N,r,c)\n\nReturn N points distributed (roughly) in a uniform manner on the sphere of radius r centered at c.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.flip_normal-Tuple{Inti.QuadratureNode}","page":"Docstrings","title":"Inti.flip_normal","text":"flip_normal(q::QuadratureNode)\n\nReturn a new QuadratureNode with the normal vector flipped.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.gauss_curvature-Tuple{Inti.Quadrature}","page":"Docstrings","title":"Inti.gauss_curvature","text":"gauss_curvature(Q::Quadrature)\n\nCompute the gauss_curvature at each quadrature node in Q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.gauss_curvature-Tuple{Inti.ReferenceInterpolant, Any}","page":"Docstrings","title":"Inti.gauss_curvature","text":"gauss_curvature(τ, x̂)\n\nCalculate the Gaussian curvature of the element τ at the parametric coordinate x̂.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.geometric_dimension","page":"Docstrings","title":"Inti.geometric_dimension","text":"geometric_dimension(x)\n\nNNumber of degrees of freedom necessary to locally represent the geometrical object. For example, lines have geometric dimension of 1 (whether in ℝ² or in ℝ³), while surfaces have geometric dimension of 2.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.global_get_entity-Tuple{Inti.EntityKey}","page":"Docstrings","title":"Inti.global_get_entity","text":"global_get_entity(k::EntityKey)\n\nRetrieve the GeometricEntity corresponding to the EntityKey k from the global ENTITIES dictionary.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.gmsh_curve-Tuple","page":"Docstrings","title":"Inti.gmsh_curve","text":"gmsh_curve(f::Function, a, b; npts=100, meshsize = 0, tag=-1)\n\nCreate a curve in the current gmsh model given by {f(t) : t ∈ (a,b) } where f is a function from ℝ to ℝ^3. The curve is approximated by C² b-splines passing through npts equispaced in parameter space. If a meshsize is given, gmsh will use it when meshing the curve.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.hessian-Tuple{Inti.ReferenceInterpolant, Any}","page":"Docstrings","title":"Inti.hessian","text":"hesssian(el,x)\n\nGiven a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m × m matrix Aᵢⱼⱼ = ∂²fᵢ/∂xⱼ∂xⱼ. By default ForwardDiff is used to compute the hessian, but you should overload this method for specific f if better performance and/or precision is required.\n\nNote: both x and f(x) are expected to be of SVector type.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.image","page":"Docstrings","title":"Inti.image","text":"image(f)\n\nGiven a function-like object f: Ω → R, return f(Ω).\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.import_mesh-Tuple","page":"Docstrings","title":"Inti.import_mesh","text":"import_mesh(filename = nothing; dim=3)\n\nOpen filename and create a Mesh from the gmsh model in it.\n\nIf filename is nothing, the current gmsh model is used. Note that this assumes that the Gmsh API has been initialized through gmsh.initialize.\n\nPassing dim=2 will create a two-dimensional mesh by projecting the original mesh onto the x,y plane.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.integrate-Tuple{Any, Inti.Quadrature}","page":"Docstrings","title":"Inti.integrate","text":"integrate(f,quad::Quadrature)\n\nCompute ∑ᵢ f(qᵢ)wᵢ, where the qᵢ are the quadrature nodes of quad, and wᵢ are the quadrature weights.\n\nNote that you must define f(::QuadratureNode): use q.coords and q.normal if you need to access the coordinate or normal vector at que quadrature node.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.integrate-Tuple{Any, Inti.ReferenceQuadrature}","page":"Docstrings","title":"Inti.integrate","text":"integrate(f,q::ReferenceQuadrature)\nintegrate(f,x,w)\n\nIntegrate the function f using the quadrature rule q. This is simply sum(f.(x) .* w), where x and w are the quadrature nodes and weights, respectively.\n\nThe function f should take an SVector as input.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.integrate_with_error_estimate","page":"Docstrings","title":"Inti.integrate_with_error_estimate","text":"integrate_with_error_estimate(f, quad::EmbeddedQuadrature, norm = LinearAlgebra.norm)\n\nReturn I, E where I is the estimated integral of f over domain(quad) using the high-order quadrature and E is the error estimate obtained by taking the norm of the difference between the high and low-order quadratures in quad.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.integration_measure-Tuple{Any, Any}","page":"Docstrings","title":"Inti.integration_measure","text":"integration_measure(f, x̂)\n\nGiven the Jacobian matrix J of a transformation f : ℝᴹ → ℝᴺ compute the integration measure √det(JᵀJ) at the parametric coordinate x̂\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.interface_method-Tuple{DataType}","page":"Docstrings","title":"Inti.interface_method","text":"interface_method(x)\n\nA method of an abstract type for which concrete subtypes are expected to provide an implementation.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.internal_boundary-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.internal_boundary","text":"internal_boundary(Ω::Domain)\n\nReturn the internal boundaries of a Domain. These are entities in skeleton(Ω) which appear at least twice as a boundary of entities in Ω.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.interpolation_order-Tuple{Inti.ReferenceQuadrature{Inti.ReferenceHyperCube{1}}}","page":"Docstrings","title":"Inti.interpolation_order","text":"interpolation_order(qrule::ReferenceQuadrature)\n\nThe interpolation order of a quadrature rule is defined as the the smallest k such that there exists a unique polynomial in PolynomialSpace{D,k} that minimizes the error in approximating the function f at the quadrature nodes.\n\nFor an N-point Gauss quadrature rule on the segment, the interpolation order is N-1 since N points uniquely determine a polynomial of degree N-1.\n\nFor a triangular reference domain, the interpolation order is more difficult to define. An unisolvent three-node quadrature on the triangular, for example, has an interpolation order k=1 since the three nodes uniquely determine a linear polynomial, but a four-node quadrature may also have an interpolation order k=1 since for k=2 there are multiple polynomials that pass through the four nodes.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.jacobian-Tuple{Any, Any}","page":"Docstrings","title":"Inti.jacobian","text":"jacobian(f,x)\n\nGiven a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m matrix Aᵢⱼ = ∂fᵢ/∂xⱼ. By default ForwardDiff is used to compute the jacobian, but you should overload this method for specific f if better performance and/or precision is required.\n\nNote: both x and f(x) are expected to be of SVector type.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.kress_change_of_variables-Tuple{Any}","page":"Docstrings","title":"Inti.kress_change_of_variables","text":"kress_change_of_variables(P)\n\nReturn a change of variables mapping [0,1] to [0,1] with the property that the first P-1 derivatives of the transformation vanish at x=0.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.kress_change_of_variables_periodic-Tuple{Any}","page":"Docstrings","title":"Inti.kress_change_of_variables_periodic","text":"kress_change_of_variables_periodic(P)\n\nLike kress_change_of_variables, this change of variables maps the interval [0,1] onto itself, but the first P derivatives of the transformation vanish at both endpoints (thus making it a periodic function).\n\nThis change of variables can be used to periodize integrals over the interval [0,1] by mapping the integrand into a new integrand that vanishes (to order P) at both endpoints.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.lagrange_basis-Tuple{Any, Inti.PolynomialSpace}","page":"Docstrings","title":"Inti.lagrange_basis","text":"lagrange_basis(nodes,[sp::AbstractPolynomialSpace])\n\nReturn the set of n polynomials in sp taking the value of 1 on node i and 0 on nodes j ≂̸ i for 1 ≤ i ≤ n.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.lagrange_basis-Union{Tuple{Inti.ReferenceQuadrature{D}}, Tuple{D}} where D","page":"Docstrings","title":"Inti.lagrange_basis","text":"lagrange_basis(qrule::ReferenceQuadrature)\n\nReturn a function L : ℝᴺ → ℝᵖ where N is the dimension of the domain of qrule, and p is the number of nodes in qrule. The function L is a polynomial in polynomial_space(qrule), and L(xⱼ)[i] = δᵢⱼ (i.e. the ith component of L is the ith Lagrange basis).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.lagrange_basis-Union{Tuple{Type{Inti.LagrangeElement{D, N, T}}}, Tuple{T}, Tuple{N}, Tuple{D}} where {D, N, T}","page":"Docstrings","title":"Inti.lagrange_basis","text":"lagrange_basis(E::Type{<:LagrangeElement})\n\nReturn the Lagrange basis B for the element E. Evaluating B(x) yields the value of each basis function at x.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.line-Tuple{Any, Any}","page":"Docstrings","title":"Inti.line","text":"line(a,b)\n\nCreate a [GeometricEntity] representing a straight line connecting points a and b. The points a and b can be either SVectors or a Tuple.\n\nThe parametrization of the line is given by f(u) = a + u(b - a), where 0 ≤ u ≤ 1.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.mean_curvature-Tuple{Inti.Quadrature}","page":"Docstrings","title":"Inti.mean_curvature","text":"mean_curvature(Q::Quadrature)\n\nCompute the mean_curvature at each quadrature node in Q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.mean_curvature-Tuple{Inti.ReferenceInterpolant, Any}","page":"Docstrings","title":"Inti.mean_curvature","text":"mean_curvature(τ, x̂)\n\nCalculate the mean curvature of the element τ at the parametric coordinate x̂.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.measure","page":"Docstrings","title":"Inti.measure","text":"measure(k::EntityKey, rtol)\n\nCompute the length/area/volume of the entity k using an adaptive quadrature with a relative tolerance rtol. Assumes that the entity has an explicit parametrization.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.meshgen!-Tuple{Inti.Mesh, Inti.Domain, Int64}","page":"Docstrings","title":"Inti.meshgen!","text":"meshgen!(mesh,Ω,sz)\n\nSimilar to meshgen, but append entries to mesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.meshgen-Tuple{Inti.Domain, Vararg{Any}}","page":"Docstrings","title":"Inti.meshgen","text":"meshgen(Ω, n)\nmeshgen(Ω, n_dict)\nmeshgen(Ω; meshsize)\n\nGenerate a Mesh for the domain Ω where each curve is meshed using n elements. Passing a dictionary allows for a finer control; in such cases, n_dict[ent] should return an integer for each entity ent in Ω of geometric_dimension one.\n\nAlternatively, a meshsize can be passed, in which case, the number of elements is computed as so as to obtain an average mesh size of meshsize. Note that the actual mesh size may vary significantly for each element if the parametrization is far from uniform.\n\nThis function requires the entities forming Ω to have an explicit parametrization.\n\nwarning: Mesh quality\nThe quality of the generated mesh created using meshgen depends on the quality of the underlying parametrization. For complex surfaces, you are better off using a proper mesher such as gmsh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.monomial_basis","page":"Docstrings","title":"Inti.monomial_basis","text":"monomial_basis(sp::PolynomialSpace)\n\nReturn a function f : ℝᴺ → ℝᵈ, where N is the dimension of the domain of sp containing a basis of monomials 𝐱ᶿ spanning the polynomial space PolynomialSpace.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.near_interaction_list-Union{Tuple{N}, Tuple{AbstractVector{<:StaticArraysCore.SVector{N}}, Inti.AbstractMesh{N}}} where N","page":"Docstrings","title":"Inti.near_interaction_list","text":"near_interaction_list(X,Y::AbstractMesh; tol)\n\nFor each element el of type E in Y, return the indices of the points in X which are closer than tol to the center of el.\n\nThis function returns a dictionary where e.g. dict[E][5] --> Vector{Int} gives the indices of points in X which are closer than tol to the center of the fifth element of type E.\n\nIf tol is a Dict, then tol[E] is the tolerance for elements of type E.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.new_tag-Tuple{Integer}","page":"Docstrings","title":"Inti.new_tag","text":"new_tag(dim)\n\nReturn a new tag for an entity of dimension dim so that EntityKey(dim, tag) is not already in ENTITIES.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.nodes-Tuple{Inti.SubMesh}","page":"Docstrings","title":"Inti.nodes","text":"nodes(msh::SubMesh)\n\nA view of the nodes of the parent mesh belonging to the submesh. The ordering is given by the nodetags function.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.nodetags-Tuple{Inti.SubMesh}","page":"Docstrings","title":"Inti.nodetags","text":"nodetags(msh::SubMesh)\n\nReturn the tags of the nodes in the parent mesh belonging to the submesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.normal-Tuple{Any, Any}","page":"Docstrings","title":"Inti.normal","text":"normal(el, x̂)\n\nReturn the normal vector of el at the parametric coordinate x̂.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.normal-Tuple{T} where T","page":"Docstrings","title":"Inti.normal","text":"normal(q)\n\nReturn the normal vector of q, if it exists.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.notimplemented-Tuple{}","page":"Docstrings","title":"Inti.notimplemented","text":"notimplemented()\n\nThings which should probably be implemented at some point.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.order-Union{Tuple{Inti.Fejer{N}}, Tuple{N}} where N","page":"Docstrings","title":"Inti.order","text":"order(q::ReferenceQuadrature)\n\nA quadrature of order p (sometimes called degree of precision) integrates all polynomials of degree ≤ p but not ≤ p + 1.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.order-Union{Tuple{Type{<:Inti.LagrangeElement{D, Np}}}, Tuple{Np}, Tuple{D}} where {D, Np}","page":"Docstrings","title":"Inti.order","text":"order(el::LagrangeElement)\n\nThe order of the element's interpolating polynomial (e.g. a LagrangeLine with 2 nodes defines a linear polynomial, and thus has order 1).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.parametric_curve-Union{Tuple{F}, Tuple{F, Real, Real}} where F","page":"Docstrings","title":"Inti.parametric_curve","text":"parametric_curve(f, a::Real, b::Real)\n\nCreate a [GeometricEntity] representing a parametric curve defined by the {f(t) | a ≤ t ≤ b}. The function f should map a scalar to an SVector.\n\nFlipping the orientation is supported by passing a > b.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.parametric_surface","page":"Docstrings","title":"Inti.parametric_surface","text":" parametric_surface(f, lc, hc, boundary = nothing; kwargs...)\n\nCreate a parametric surface defined by the function f over the rectangular domain defined by the lower corner lc and the upper corner hc. The optional boundary argument can be used to specify the boundary curves of the surface.\n\nArguments\n\nf: A function that takes two arguments x and y and returns a tuple (u, v) representing the parametric coordinates of the surface at (x, y).\nlc: A 2-element array representing the lower corner of the rectangular domain.\nhc: A 2-element array representing the upper corner of the rectangular domain.\nboundary: An optional array of boundary curves that define the surface.\n\nKeyword Arguments\n\nkwargs: Additional keyword arguments that can be passed to the GeometricEntity constructor.\n\nReturns\n\nThe key of the created GeometricEntity.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.polynomial_solutions_vdim","page":"Docstrings","title":"Inti.polynomial_solutions_vdim","text":"polynomial_solutions_vdim(op, order[, center])\n\nFor every monomial term pₙ of degree order, compute a polynomial Pₙ such that ℒ[Pₙ] = pₙ, where ℒ is the differential operator associated with op. This function returns {pₙ,Pₙ,γ₁Pₙ}, where γ₁Pₙ is the generalized Neumann trace of Pₙ.\n\nPassing a point center will shift the monomials and solutions accordingly.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.polynomial_space-Union{Tuple{Inti.ReferenceQuadrature{D}}, Tuple{D}} where D","page":"Docstrings","title":"Inti.polynomial_space","text":"polynomial_space(qrule::ReferenceQuadrature)\n\nReturn a PolynomialSpace associated with the interpolation_order of the quadrature nodes of qrule.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.qcoords-Tuple{Inti.ReferenceQuadrature}","page":"Docstrings","title":"Inti.qcoords","text":"qcoords(q)\n\nReturn the coordinate of the quadrature nodes associated with q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.quadrature_to_node_vals-Tuple{Inti.Quadrature, AbstractVector}","page":"Docstrings","title":"Inti.quadrature_to_node_vals","text":"quadrature_to_node_vals(Q::Quadrature, qvals::AbstractVector)\n\nGiven a vector qvals of scalar values at the quadrature nodes of Q, return a vector ivals of scalar values at the interpolation nodes of Q.mesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.qweights-Tuple{Inti.ReferenceQuadrature}","page":"Docstrings","title":"Inti.qweights","text":"qweights(q)\n\nReturn the quadrature weights associated with q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.reference_nodes-Tuple{Inti.LagrangeElement}","page":"Docstrings","title":"Inti.reference_nodes","text":"reference_nodes(el::LagrangeElement)\nreference_nodes(::Type{<:LagrangeElement})\n\nReturn the reference nodes on domain(el) used for the polynomial interpolation. The function values on these nodes completely determines the interpolating polynomial.\n\nWe use the same convention as gmsh for defining the reference nodes and their order (see node ordering on gmsh documentation).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.return_type-Tuple{Any, Vararg{Any}}","page":"Docstrings","title":"Inti.return_type","text":"return_type(f[,args...])\n\nThe type returned by f(args...), where args is a tuple of types. Falls back to Base.promote_op by default.\n\nA functors of type T with a knonw return type should extend return_type(::T,args...) to avoid relying on promote_op.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.rotation_matrix-Tuple{Any}","page":"Docstrings","title":"Inti.rotation_matrix","text":"rotation_matrix(rot)\n\nConstructs a rotation matrix given the rotation angles around the x, y, and z axes.\n\nArguments\n\nrot: A tuple or vector containing the rotation angles in radians for each axis.\n\nReturns\n\nR::SMatrix: The resulting rotation matrix.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.single_double_layer-Tuple{}","page":"Docstrings","title":"Inti.single_double_layer","text":"single_double_layer(; op, target, source::Quadrature, compression,\ncorrection, derivative = false)\n\nConstruct a discrete approximation to the single- and double-layer integral operators for op, mapping values defined on the quadrature nodes of source to values defined on the nodes of target. If derivative = true, return instead the adjoint double-layer and hypersingular operators (which are the derivative of the single- and double-layer, respectively).\n\nYou must choose a compression method and a correction method, as described below.\n\nCompression\n\nThe compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:\n\n(method = :none, ): no compression is performed, the resulting matrices are dense.\n(method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).\n(method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).\n\nCorrection\n\nThe correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:\n\n(method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.\n(method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesourcecurve/surface (which is assumed to be closed). Whentarget === source,target_location` is not needed.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.single_double_layer_potential-Tuple{}","page":"Docstrings","title":"Inti.single_double_layer_potential","text":"single_double_layer_potential(; op, source)\n\nReturn the single- and double-layer potentials for op as IntegralPotentials.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.skeleton-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.skeleton","text":"skeleton(Ω::Domain)\n\nReturn all the boundaries of the domain, i.e. the domain's skeleton.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.stack_weakdeps_env!-Tuple{}","page":"Docstrings","title":"Inti.stack_weakdeps_env!","text":"stack_weakdeps_env!(; verbose = false, update = false)\n\nPush to the load stack an environment providing the weak dependencies of Inti.jl. This allows benefiting from additional functionalities of Inti.jl which are powered by weak dependencies without having to manually install them in your environment.\n\nSet update=true if you want to update the weakdeps environment.\n\nwarning: Warning\nCalling this function can take quite some time, especially the first time around, if packages have to be installed or precompiled. Run in verbose mode to see what is happening.\n\nExamples:\n\nInti.stack_weakdeps_env!()\nusing HMatrices\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.standard_basis_vector-Union{Tuple{N}, Tuple{Any, Val{N}}} where N","page":"Docstrings","title":"Inti.standard_basis_vector","text":"standard_basis_vector(k, ::Val{N})\n\nCreate an SVector of length N with a 1 in the kth position and zeros elsewhere.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.svector-Union{Tuple{F}, Tuple{F, Any}} where F","page":"Docstrings","title":"Inti.svector","text":"svector(f,n)\n\nCreate an SVector of length n, computing each element as f(i), where i is the index of the element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.torus-Tuple{}","page":"Docstrings","title":"Inti.torus","text":"torus(; r, R, translation, rotation, scaling, labels)\n\nCreate a torus entity in 3D, and apply optional transformations. Returns the key. The parameters r and R are the inner and outer radii of the torus.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.uniform_points_circle-Tuple{Any, Any, Any}","page":"Docstrings","title":"Inti.uniform_points_circle","text":"uniform_points_circle(N,r,c)\n\nReturn N points uniformly distributed on a circle of radius r centered at c.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vdim_correction-Union{Tuple{SHIFT}, Tuple{Any, Any, Inti.Quadrature, Inti.Quadrature, Any, Any, Any}} where SHIFT","page":"Docstrings","title":"Inti.vdim_correction","text":"vdim_correction(op,X,Y,Y_boundary,S,D,V; green_multiplier, kwargs...)\n\nCompute a correction to the volume potential V : Y → X such that V + δV is a more accurate approximation of the underlying volume potential operator. The correction is computed using the (volume) density interpolation method.\n\nThis function requires a op::AbstractDifferentialOperator, a target set X, a source quadrature Y, a boundary quadrature Y_boundary, approximations S : Y_boundary -> X and D : Y_boundary -> X to the single- and double-layer potentials (correctly handling nearly-singular integrals), and a naive approximation of the volume potential V. The green_multiplier is a vector of the same length as X storing the value of μ(x) for x ∈ X in the Green identity (see _green_multiplier).\n\nSee [8] for more details on the method.\n\nOptional kwargs:\n\ninterpolation_order: the order of the polynomial interpolation. By default, the maximum order of the quadrature rules is used.\nmaxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections.\ncenter: the center of the basis functions. By default, the basis functions are centered at the origin.\nshift: a boolean indicating whether the basis functions should be shifted and rescaled to each element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vdim_mesh_center-Tuple{Inti.AbstractMesh}","page":"Docstrings","title":"Inti.vdim_mesh_center","text":"vdim_mesh_center(msh)\n\nPoint x which minimizes ∑ (x-xⱼ)²/r²ⱼ, where xⱼ and rⱼ are the circumcenter and circumradius of the elements of msh, respectively.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vertices-Tuple{Inti.LagrangeElement}","page":"Docstrings","title":"Inti.vertices","text":"vertices(el::LagrangeElement)\n\nCoordinates of the vertices of el.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vertices_idxs-Tuple{Type{<:Inti.LagrangeElement{Inti.ReferenceHyperCube{1}}}}","page":"Docstrings","title":"Inti.vertices_idxs","text":"vertices_idxs(el::LagrangeElement)\n\nThe indices of the nodes in el that define the vertices of the element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.volume_potential-Tuple{}","page":"Docstrings","title":"Inti.volume_potential","text":"volume_potential(; op, target, source::Quadrature, compression, correction)\n\nCompute the volume potential operator for a given PDE.\n\nArguments\n\nop: The PDE (Partial Differential Equation) to solve.\ntarget: The target domain where the potential is computed.\nsource: The source domain where the potential is generated.\ncompression: The compression method to use for the potential operator.\ncorrection: The correction method to use for the potential operator.\n\nReturns\n\nThe volume potential operator V that represents the interaction between the target and source domains.\n\nCompression\n\nThe compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:\n\n(method = :none, ): no compression is performed, the resulting matrices are dense.\n(method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).\n(method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).\n\nCorrection\n\nThe correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:\n\n(method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.\n(method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesource's boundary. Whentarget === source,target_location` is not needed.\n\nDetails\n\nThe volume potential operator is computed by assembling the integral operator V using the single-layer kernel G. The operator V is then compressed using the specified compression method. If no compression is specified, the operator is returned as is. If a correction method is specified, the correction is computed and added to the compressed operator.\n\n\n\n\n\n","category":"method"},{"location":"tutorials/compression_methods/#Compression-methods","page":"Compression methods","title":"Compression methods","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"note: Important points covered in this tutorial\nOverview of the compression methods available in Inti.jl\nDetails and limitations of the various compression methods\nGuideline on how to choose a compression method","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Inti.jl wraps several external libraries providing acceleration routines for integral operators. In general, acceleration routines have the signature assemble_*(iop, args...; kwargs...), and take an IntegralOperator as a first argument. They return a new object that represents a compressed version of the operator. The following methods are available:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_matrix: create a dense Matrix representation of the integral operator. Not really a compression method, but useful for debugging and small problems.\nassemble_hmatrix: assemble a hierarchical matrix representation of the operator using the HMatrices library.\nassemble_fmm: return a LinearMap object that represents the operator using the fast multipole method. This method is powered by the FMM2D, FMMLIB2D and FMM3D libraries, and is only available for certain kernels.","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"warning: Singular kernels\nAcceleration methods do not correct for singular or nearly-singular interactions. When the underlying kernel is singular, a correction is usually necessary in order to obtain accurate results (see the section on correction methods for more details).","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"To illustrate the use of compression methods, we will use the following problem as an example. Note that for such a small problem, compression methods are not likely not necessary, but they are useful for larger problems.","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"using Inti\nusing LinearAlgebra\n# define the quadrature\ngeo = Inti.GeometricEntity(\"ellipsoid\")\nΩ = Inti.Domain(geo)\nΓ = Inti.boundary(Ω)\nQ = Inti.Quadrature(Γ; meshsize = 0.4, qorder = 5)\n# create the operator\nop = Inti.Helmholtz(; dim = 3, k = 2π)\nK = Inti.SingleLayerKernel(op)\nSop = Inti.IntegralOperator(K, Q, Q)\nx = rand(eltype(Sop), length(Q))\nrtol = 1e-8\nnothing # hide","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"In what follows we compress Sop using the different methods available.","category":"page"},{"location":"tutorials/compression_methods/#Dense-matrix","page":"Compression methods","title":"Dense matrix","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_matrix","category":"page"},{"location":"tutorials/compression_methods/#Inti.assemble_matrix-tutorials-compression_methods","page":"Compression methods","title":"Inti.assemble_matrix","text":"assemble_matrix(iop::IntegralOperator; threads = true)\n\nAssemble a dense matrix representation of an IntegralOperator.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Typically used for small problems, the dense matrix representation converts the IntegralOperator into a Matrix object. The underlying type of the Matrix is determined by the eltype of the IntegralOperator, and depends on the inferred type of the kernel. Here is how assemble_matrix can be used:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Smat = Inti.assemble_matrix(Sop; threads=true)\n@assert Sop * x ≈ Smat * x # hide\ner = norm(Sop * x - Smat * x, Inf) / norm(Sop * x, Inf)\nprintln(\"Forward map error: $er\")","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Since the returned object is plain Julia Matrix, it can be used with any of the linear algebra routines available in Julia (e.g. \\, lu, qr, *, etc.)","category":"page"},{"location":"tutorials/compression_methods/#Hierarchical-matrix","page":"Compression methods","title":"Hierarchical matrix","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_hmatrix","category":"page"},{"location":"tutorials/compression_methods/#Inti.assemble_hmatrix-tutorials-compression_methods","page":"Compression methods","title":"Inti.assemble_hmatrix","text":"assemble_hmatrix(iop[; atol, rank, rtol, eta])\n\nAssemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.\n\nSee the documentation of HMatrices for more details on usage and other keyword arguments.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The hierarchical matrix representation is a compressed representation of the underlying operator; as such, it takes a tolerance parameter that determines the relative error of the compression. Here is an example of how to use the assemble_hmatrix method to compress the previous problem:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"using HMatrices\nShmat = Inti.assemble_hmatrix(Sop; rtol = 1e-8)\ner = norm(Smat * x - Shmat * x, Inf) / norm(Smat * x, Inf)\n@assert er < 10*rtol # hide\nprintln(\"Forward map error: $er\")","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Note that HMatrices are said to be kernel-independent, meaning that they efficiently compress a wide range of integral operators provided they satisfy a certain asymptotic smoothness criterion (see e.g. [3, 4]).","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The HMatrix object can be used to solve linear systems, both iteratively through e.g. GMRES, or directly using an LU factorization.","category":"page"},{"location":"tutorials/compression_methods/#Fast-multipole-method","page":"Compression methods","title":"Fast multipole method","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_fmm","category":"page"},{"location":"tutorials/compression_methods/#Inti.assemble_fmm-tutorials-compression_methods","page":"Compression methods","title":"Inti.assemble_fmm","text":"assemble_fmm(iop; atol)\n\nSet up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.\n\nwarning: FMMLIB2D\nFMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The fast multipole method (FMM) is an acceleration technique based on an analytic multipole expansion of the kernel in the integral operator [5, 6]. It provides a very memory-efficient and fast way to evaluate certain types of integral operators. Here is how assemble_fmm can be used:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"using FMM3D\nSfmm = Inti.assemble_fmm(Sop; rtol = 1e-8)\ner = norm(Sop * x - Sfmm * x, Inf) / norm(Sop * x, Inf)\n@assert er < 10*rtol # hide\nprintln(\"Forward map error: $er\")","category":"page"},{"location":"tutorials/compression_methods/#Tips-on-choosing-a-compression-method","page":"Compression methods","title":"Tips on choosing a compression method","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The choice of compression method depends on the problem at hand, as well as on the available hardware. Here is a rough guide on how to choose a compression:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"For small problems (say less than 5k degrees of freedom), use the dense matrix representation. It is the simplest and most straightforward method, and does not require any additional packages. It is also the most accurate since it does not introduce any approximation errors.\nIf the integral operator is supported by the assemble_fmm, and if an iterative solver is acceptable, use it. The FMM is a very efficient method for certain types of kernels, and can handle problems with up to a few million degrees of freedom on a laptop.\nIf the kernel is not supported by assemble_fmm, if iterative solvers are not an option, or if the system needs solution for many right-hand sides, use the assemble_hmatrix method. It is a very general method that can handle a wide range of kernels, and although assembling the HMatrix can be time and memory consuming (the complexity is still log-linear in the DOFs for many kernels of interest, but the constants can be large), the resulting HMatrix object is very efficient to use. For example, the forward map is usually significantly faster than the one obtained through assemble_fmm.","category":"page"},{"location":"tutorials/correction_methods/#Correction-methods","page":"Correction methods","title":"Correction methods","text":"","category":"section"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"warning: Work in progress\nThis tutorial is still a work in progress. We will update it with more details and examples in the future.","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"note: Important points covered in this tutorial\nOverview of the correction methods available in Inti.jl\nDetails and limitations of the various correction methods\nGuideline on how to choose a correction method","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"When the underlying kernel is singular, a correction is usually necessary in order to obtain accurate results in the approximation of the underlying integral operator by a quadrature. At present, Inti.jl provides the following functions to correct for singularities:","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"adaptive_correction\nbdim_correction\nvdim_correction","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"They have different strengths and weaknesses, and we will discuss them in the following sections.","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"note: High-level API\nNote that the single_double_layer, adj_double_layer_hypersingular, and volume_potential functions have high-level API with a correction keyword argument that allows one to specify the correction method to use when constructing the integral operators; see the documentation of these functions for more details.","category":"page"},{"location":"tutorials/correction_methods/#Adaptive-correction","page":"Correction methods","title":"Adaptive correction","text":"","category":"section"},{"location":"tutorials/correction_methods/#Boundary-density-interpolation-method","page":"Correction methods","title":"Boundary density interpolation method","text":"","category":"section"},{"location":"tutorials/correction_methods/#Volume-density-interpolation-method","page":"Correction methods","title":"Volume density interpolation method","text":"","category":"section"},{"location":"tutorials/correction_methods/#Martensen-Kussmaul-method","page":"Correction methods","title":"Martensen-Kussmaul method","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/#Geometry-and-meshes","page":"Geometry and meshes","title":"Geometry and meshes","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"note: Important points covered in this tutorial\nCombine simple shapes to create domains\nImport a mesh from a file\nIterative over mesh elements","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"In the getting started tutorial, we saw how to solve a simple Helmholtz scattering problem in 2D. We will now dig deeper into how to create and manipulate more complex geometrical shapes, as well the associated meshes.","category":"page"},{"location":"tutorials/geo_and_meshes/#Overview","page":"Geometry and meshes","title":"Overview","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.jl provides a flexible way to define geometrical entities and their associated meshes. Simply put, the GeometricEntity type is the atomic building block of geometries: they can represent points, curves, surfaces, or volumes. Geometrical entities of the same dimension can be combined to form Domain, and domains can be manipulated using basic set operations such union and intersection. Meshes on the other hand are collections of (simple) elements that approximate the geometrical entities. A mesh element is a just a function that maps points from a ReferenceShape to the physical space.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"In most applications involving complex three-dimensional surfaces, an external meshing software is used to generate a mesh, and the mesh is imported using the import_mesh function (which relies on Gmsh). The entities can then be extracted from the mesh based on e.g. their dimension or label. Here is an example of how to import a mesh from a file:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"using Inti\nusing Gmsh \nfilename = joinpath(Inti.PROJECT_ROOT,\"docs\", \"assets\", \"piece.msh\")\nmsh = Inti.import_mesh(filename)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"The imported mesh contains elements of several types, used to represent the segments, triangles, and tetras used to approximate the geometry:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.element_types(msh)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Note that the msh object contains all entities used to construct the mesh, usually defined in a .geo file, which can be extracted using the entities:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"ents = Inti.entities(msh)\nnothing # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Filtering of entities satisfying a certain condition, e.g., entities of a given dimension or containing a certain label, can also be performed in order to construct a domain:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"filter = e -> Inti.geometric_dimension(e) == 3\nΩ = Inti.Domain(filter, ents)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Domains can be used to index the mesh, creating either a new object containing only the necessary elements:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Γ = Inti.boundary(Ω)\nmsh[Γ]","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"or a SubMesh containing a view of the mesh:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Γ_msh = view(msh, Γ)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Finally, we can visualize the mesh using:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"using Meshes, GLMakie\nfig = Figure(; size = (800,400))\nax = Axis3(fig[1, 1]; aspect = :data)\nviz!(Γ_msh; showsegments = true, alpha = 0.5)\nfig","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"warning: Mesh visualization\nNote that although the mesh may be of high order and/or conforming, the visualization of a mesh is always performed on the underlying first order mesh, and therefore elements may look flat even if the problem is solved on a curved mesh.","category":"page"},{"location":"tutorials/geo_and_meshes/#Parametric-entities-and-meshgen","page":"Geometry and meshes","title":"Parametric entities and meshgen","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"In the previous section we saw an example of how to import a mesh from a file, and how to extract the entities from the mesh. For simple geometries for which an explicit parametrization is available, Inti.jl provides a way to create and manipulate geometrical entities and their associated meshes.","category":"page"},{"location":"tutorials/geo_and_meshes/#Parametric-curves","page":"Geometry and meshes","title":"Parametric curves","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"The simplest parametric shapes are parametric_curves, which are defined by a function that maps a scalar parameter t to a point in 2D or 3D space. Parametric curves are expected to return an SVector, and can be created as follows:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"using StaticArrays\nl1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₁\"])","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"The object l1 represents a GeometricEntity with a known push-forward map:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.pushforward(l1)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"For the sake of this example, let's create three more curves, and group them together to form a Domain:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"l2 = Inti.parametric_curve(x->SVector(1 + 0.1 * sin(2π * x), x), 0.0, 1.0, labels = [\"l₂\"])\nl3 = Inti.parametric_curve(x->SVector(1 - x, 1 - 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₃\"])\nl4 = Inti.parametric_curve(x->SVector(0.1 * sin(2π * x), 1 - x), 0.0, 1.0, labels = [\"l₄\"])\nΓ = l1 ∪ l2 ∪ l3 ∪ l4","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Domains for which a parametric representation is available can be passed to the meshgen function:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"msh = Inti.meshgen(Γ; meshsize = 0.05)\nnothing # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"We can use the Meshes.viz function to visualize the mesh, and use domains to index the mesh:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Γ₁ = l1 ∪ l3\nΓ₂ = l2 ∪ l4\nfig, ax, pl = viz(view(msh, Γ₁); segmentsize = 4, label = \"Γ₁\")\nviz!(view(msh, Γ₂); segmentsize = 4, color = :red, label = \"Γ₂\")\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Note that the orientation of the curve determines the direction of the normal vector. The normal points to the right of the curve when moving in the direction of increasing parameter t:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"pts, tangents, normals = Makie.Point2f[], Makie.Vec2f[], Makie.Vec2f[]\nfor l in [l1, l2, l3, l4]\n push!(pts, l(0.5)) # mid-point of the curve \n push!(tangents, vec(Inti.jacobian(l, 0.5)))\n push!(normals,Inti.normal(l, 0.5))\nend\narrows!(pts, tangents, color = :blue, linewidth = 2, linestyle = :dash, lengthscale = 1/4, label = \"tangent\")\narrows!(pts, normals, color = :black, linewidth = 2, linestyle = :dash, lengthscale = 1/4, label = \"normal\")\naxislegend()\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/#Parametric-surfaces","page":"Geometry and meshes","title":"Parametric surfaces","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Like parametric curves, parametric surfaces are defined by a function that maps a reference domain D subset mathbbR^2 to a surface in 3D space. They can be constructed using the parametric_surface function:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"# a patch of the unit sphere\nlc = SVector(-1.0, -1.0)\nhc = SVector(1.0, 1.0)\nf = (u,v) -> begin\n x = SVector(1.0, u, v) # a face of the cube\n x ./ sqrt(u^2 + v^2 + 1) # project to the sphere\nend\npatch = Inti.parametric_surface(f, lc, hc, labels = [\"patch1\"])\nΓ = Inti.Domain(patch)\nmsh = Inti.meshgen(Γ; meshsize = 0.1)\nviz(msh[Γ]; showsegments = true, figure = (; size = (400,400),))","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Since creating parametric surfaces that form a closed volume can be a bit more involved, Inti.jl provide a few helper functions to create simple shapes:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"fig = Figure(; size = (600,400))\nnshapes = Inti.length(Inti.PREDEFINED_SHAPES)\nncols = 3; nrows = ceil(Int, nshapes/ncols)\nfor (n,shape) in enumerate(Inti.PREDEFINED_SHAPES)\n Ω = Inti.GeometricEntity(shape) |> Inti.Domain\n Γ = Inti.boundary(Ω)\n msh = Inti.meshgen(Γ; meshsize = 0.1)\n i,j = (n-1) ÷ ncols + 1, (n-1) % ncols + 1\n ax = Axis3(fig[i,j]; aspect = :data, title = shape)\n hidedecorations!(ax)\n viz!(msh; showsegments = true)\nend\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"See GeometricEntity(shape::String) for a list of predefined geometries.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"warning: Mesh quality\nThe quality of the generated mesh created through meshgen depends heavily on the quality of the underlying parametrization. For surfaces containing a degenerate parametrization, or for complex shapes, one is better off using a suitable CAD (Computer-Aided Design) software in conjunction with a mesh generator.","category":"page"},{"location":"tutorials/geo_and_meshes/#Transfinite-domains","page":"Geometry and meshes","title":"Transfinite domains","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"It is possible to combine parametric curves/surfaces to form a transfinite domain where the parametrization is inherited from the curves/surfaces that form its boundary. At present, Inti.jl only supports transfinite squares, which are defined by four parametric curves:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"l1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₁\"])\nl2 = Inti.parametric_curve(x->SVector(1 + 0.1 * sin(2π * x), x), 0.0, 1.0, labels = [\"l₂\"])\nl3 = Inti.parametric_curve(x->SVector(1 - x, 1 - 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₃\"])\nl4 = Inti.parametric_curve(x->SVector(0.1 * sin(2π * x), 1 - x), 0.0, 1.0, labels = [\"l₄\"])\nsurf = Inti.transfinite_square(l1, l2, l3, l4; labels = [\"Ω\"])\nΩ = Inti.Domain(surf)\nmsh = Inti.meshgen(Ω; meshsize = 0.05)\nviz(msh; showsegments = true)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Note that the msh object contains all entities used to construct Ω, including the boundary segments:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.entities(msh)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"This allows us to probe the msh object to extract e.g. the boundary mesh:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"viz(msh[Inti.boundary(Ω)]; color = :red)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"warning: Limitations\nAt present only the transfinite interpolation for the logically quadrilateral domains is supported. In the future we hope to add support for three-dimensional transfinite interpolation, as well as transfinite formulas for simplices.","category":"page"},{"location":"tutorials/geo_and_meshes/#Elements-of-a-mesh","page":"Geometry and meshes","title":"Elements of a mesh","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"To iterate over the elements of a mesh, use the elements function:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"filename = joinpath(Inti.PROJECT_ROOT,\"docs\", \"assets\", \"piece.msh\")\nmsh = Inti.import_mesh(filename)\nents = Inti.entities(msh)\nΩ = Inti.Domain(e -> Inti.geometric_dimension(e) == 3, ents) \nels = Inti.elements(view(msh, Ω))\ncenters = map(el -> Inti.center(el), els)\nfig = Figure(; size = (800,400))\nax = Axis3(fig[1, 1]; aspect = :data)\nscatter!([c[1] for c in centers], [c[2] for c in centers], [c[3] for c in centers], markersize = 5)\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"This example shows how to extract the centers of the tetrahedral elements in the mesh; and of course we can perform any desired computation on the elements.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"tip: Type-stable iteration over elements\nSince a mesh in Inti.jl can contain elements of various types, the elements function above is not type-stable. For a type-stable iterator approach, one should first iterate over the element types using element_types, and then use elements(msh, E) to iterate over a specific element type E.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Under the hood, each element is simply a functor which maps points x̂ from a ReferenceShape into the physical space:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"el = first(els)\nx̂ = SVector(1/3,1/3, 1/3)\nel(x̂)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Likewise, we can compute the jacobian of the element, or its normal at a given parametric coordinate.","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"(Image: Pluto notebook)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"begin\n import Pkg as _Pkg\n haskey(ENV, \"PLUTO_PROJECT\") && _Pkg.activate(ENV[\"PLUTO_PROJECT\"])\n using PlutoUI: TableOfContents\nend;","category":"page"},{"location":"pluto-examples/poisson/#Poisson-Problem","page":"Poisson problem","title":"Poisson Problem","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"note: Important points covered in this example\nReformulating Poisson-like problems using integral equations\nUsing volume potentials\nCreating interior meshes using Gmsh","category":"page"},{"location":"pluto-examples/poisson/#Problem-definition","page":"Poisson problem","title":"Problem definition","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"In this example we will solve the Poisson equation in a domain Omega with Dirichlet boundary conditions on Gamma = partial Omega:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" beginalign*\n -Delta u = f quad textin quad Omega\n u = g quad texton quad Gamma\n endalign*","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"where f Omega to mathbbR and g Gamma to mathbbR are given functions. To solve this problem using integral equations, we split the solution u into a particular solution u_p and a homogeneous solution u_h:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" u = u_p + u_h","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"The function u_p is given by","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"u_p(boldsymbolr) = int_Omega G(boldsymbolr boldsymbolr) f(boldsymbolr) dboldsymbolr","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"with G the fundamental solution of -Delta.","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"The function u_h satisfies the homogeneous problem","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" beginalign*\n Delta u_h = 0 quad textin quad Omega \n u_h = g - u_p quad texton quad Gamma\n endalign*","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"which can be solved using the integral equation method. In particular, for this example, we employ a double-layer formulation:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"u_h(boldsymbolr) = int_Gamma G(boldsymbolr boldsymbolr) sigma(boldsymbolr) dboldsymbolr","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"where the density function sigma solves the integral equation","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" -fracsigma(boldsymbolx)2 + int_Gamma partial_nu_boldsymbolyG(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly = g(boldsymbolx) - u_p(boldsymbolx)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"In what follows we illustrate how to solve the problem in this manner.","category":"page"},{"location":"pluto-examples/poisson/#Geometry-and-mesh","page":"Poisson problem","title":"Geometry and mesh","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We use the Gmsh API to create a jellyfish-shaped domain and to generate a second order mesh of its interior and boundary:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using Inti, Gmsh\nmeshsize = 0.1\ngmsh.initialize()\njellyfish = Inti.gmsh_curve(0, 2π; meshsize) do s\n r = 1 + 0.3 * cos(4 * s + 2 * sin(s))\n return r * Inti.Point2D(cos(s), sin(s))\nend\ncl = gmsh.model.occ.addCurveLoop([jellyfish])\nsurf = gmsh.model.occ.addPlaneSurface([cl])\ngmsh.model.occ.synchronize()\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", meshsize)\ngmsh.model.mesh.generate(2)\ngmsh.model.mesh.setOrder(2)\nmsh = Inti.import_mesh(; dim = 2)\ngmsh.finalize()","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We can now extract components of the mesh corresponding to the Omega and Gamma domains:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 2, msh)\nΓ = Inti.boundary(Ω)\nΩ_msh = view(msh, Ω)\nΓ_msh = view(msh, Γ)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"and visualize them:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using Meshes, GLMakie\nviz(Ω_msh; showsegments = true)\nviz!(Γ_msh; color = :red)\nMakie.current_figure() #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"To conclude the geometric setup, we need a quadrature for the volume and boundary:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Ω_quad = Inti.Quadrature(Ω_msh; qorder = 4)\nΓ_quad = Inti.Quadrature(Γ_msh; qorder = 6)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/#Integral-operators","page":"Poisson problem","title":"Integral operators","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We can now assemble the required volume potential. To obtain the value of the particular solution u_p on the boundary for the modified integral equation above we will need the volume integral operator mapping to points on the boundary, i.e. operator:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using FMM2D #to accelerate the maps\nop = Inti.Laplace(; dim = 2)\n# Newtonian potential mapping domain to boundary\nV_d2b = Inti.volume_potential(;\n op,\n target = Γ_quad,\n source = Ω_quad,\n compression = (method = :fmm, tol = 1e-12),\n correction = (method = :dim, maxdist = 5 * meshsize, target_location = :on),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We require also the boundary integral operators for the ensuing integral equation:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"# Single and double layer operators on Γ\nS_b2b, D_b2b = Inti.single_double_layer(;\n op,\n target = Γ_quad,\n source = Γ_quad,\n compression = (method = :fmm, tol = 1e-12),\n correction = (method = :dim,),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"note: Note\nIn this example we used the Fast Multipole Method (:fmm) to accelerate the operators, and the Density Interpolation Method (:dim) to correct singular and nearly-singular integral.","category":"page"},{"location":"pluto-examples/poisson/#Solving-the-linear-system","page":"Poisson problem","title":"Solving the linear system","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We are now in a position to solve the original Poisson problem, but for that we need to specify the functions f and g. In order to verify that our numerical approximation is correct, however, we will play a different game and specify instead a manufactured solution u_e from which we will derive the functions f and g:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"# Create a manufactured solution\nuₑ = (x) -> cos(2 * x[1]) * sin(2 * x[2])\nfₑ = (x) -> 8 * cos(2 * x[1]) * sin(2 * x[2]) # -Δuₑ\ng = map(q -> uₑ(q.coords), Γ_quad)\nf = map(q -> fₑ(q.coords), Ω_quad)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"With these, we can compute the right-hand-side of the integral equation for the homogeneous part of the solution:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"rhs = g - V_d2b * f\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"and solve the integral equation for the integral density function σ:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using IterativeSolvers, LinearAlgebra\nσ = gmres(-I / 2 + D_b2b, rhs; abstol = 1e-8, verbose = true, restart = 1000)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"With the density function at hand, we can now reconstruct our approximate solution:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"G = Inti.SingleLayerKernel(op)\ndG = Inti.DoubleLayerKernel(op)\n𝒱 = Inti.IntegralPotential(G, Ω_quad)\n𝒟 = Inti.IntegralPotential(dG, Γ_quad)\nu = (x) -> 𝒱[f](x) + 𝒟[σ](x)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"and evaluate it at any point in the domain:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"x = Inti.Point2D(0.1, 0.4)\nprintln(\"error at $x: \", u(x) - uₑ(x))","category":"page"},{"location":"pluto-examples/poisson/#Solution-evaluation-and-visualization","page":"Poisson problem","title":"Solution evaluation and visualization","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Although we have \"solved\" the problem in the previous section, using the anonymous function u to evaluate the field is neither efficient nor accurate when there are either many points to evaluate, or when they lie close to the domain Omega. The fundamental reason for this is the usual: the integral operators in the function u are dense matrices, and their evaluation inside or near to Omega suffers from inaccurate singular and near-singular quadrature.","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"To address this issue, we need to assemble accelerated and corrected versions of the integral operators. Let us suppose we wish to evaluate the solution u at all the quadrature nodes of Omega:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"V_d2d = Inti.volume_potential(;\n op,\n target = Ω_quad,\n source = Ω_quad,\n compression = (method = :fmm, tol = 1e-8),\n correction = (method = :dim,),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Likewise, we need operators mapping densities from our boundary quadrature to our mesh nodes:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"S_b2d, D_b2d = Inti.single_double_layer(;\n op,\n target = Ω_quad,\n source = Γ_quad,\n compression = (method = :fmm, tol = 1e-8),\n correction = (method = :dim, maxdist = 2 * meshsize, target_location = :inside),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We now evaluate the solution at all quadrature nodes and compare it to the manufactured:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"u_quad = V_d2d * f + D_b2d * σ\ner_quad = u_quad - map(q -> uₑ(q.coords), Ω_quad)\nprintln(\"maximum error at all quadrature nodes: \", norm(er_quad, Inf))\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Lastly, let us visualize the solution and the error on the mesh nodes using quadrature_to_node_vals:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"nodes = Inti.nodes(Ω_msh)\nu_nodes = Inti.quadrature_to_node_vals(Ω_quad, u_quad)\ner = u_nodes - map(uₑ, nodes)\ncolorrange = extrema(u_nodes)\nfig = Figure(; size = (800, 300))\nax = Axis(fig[1, 1]; aspect = DataAspect())\nviz!(Ω_msh; colorrange, color = u_nodes, interpolate = true)\ncb = Colorbar(fig[1, 2]; label = \"u\", colorrange)\n# plot error\nlog_er = log10.(abs.(er))\ncolorrange = extrema(log_er)\ncolormap = :inferno\nax = Axis(fig[1, 3]; aspect = DataAspect())\nviz!(Ω_msh; colorrange, colormap, color = log_er, interpolate = true)\ncb = Colorbar(fig[1, 4]; label = \"log₁₀|u - uₑ|\", colormap, colorrange)\nfig #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"TableOfContents()","category":"page"},{"location":"tutorials/getting_started/#Getting-started","page":"Getting started","title":"Getting started","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"note: Important points covered in this tutorial\nCreate a domain and its accompanying mesh\nSolve a basic boundary integral equation\nVisualize the solution","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"This first tutorial will be a guided tour through the basic steps of setting up a boundary integral equation and solving it using Inti.jl. ","category":"page"},{"location":"tutorials/getting_started/#Mathematical-formulation","page":"Getting started","title":"Mathematical formulation","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"We will consider the classic Helmholtz scattering problem in 2D, and solve it using a direct boundary integral formulation. More precisely, letting Omega subset mathbbR^2 be a bounded domain, and denoting by Gamma = partial Omega its boundary, we will solve the following Helmholtz problem:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"beginaligned\n Delta u + k^2 u = 0 quad textin quad mathbbR^2 setminus overlineOmega\n partial_nu u = g quad texton quad Gamma\n sqrtr left( fracpartial upartial r - i k u right) = o(1) quad textas quad r = boldsymbolx to infty\nendaligned","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"where g is the given boundary datum, nu is the outward unit normal to Gamma, and k is the constant wavenumber. The last condition is the Sommerfeld radiation condition, and is required to ensure the uniqueness of the solution; physically, it means that the solution sought should radiate energy towards infinity.","category":"page"},{"location":"tutorials/getting_started/#PDE,-geometry,-and-mesh","page":"Getting started","title":"PDE, geometry, and mesh","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"The first step is to define the PDE under consideration:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using Inti\nInti.stack_weakdeps_env!() # add weak dependencies \n# PDE\nk = 2π\nop = Inti.Helmholtz(; dim = 2, k)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Next, we generate the geometry of the problem. For this tutorial, we will manually create parametric curves representing the boundary of the domain using the parametric_curve function:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using StaticArrays # for SVector\n# Create the geometry as the union of a kite and a circle\nkite = Inti.parametric_curve(0.0, 1.0; labels = [\"kite\"]) do s\n return SVector(2.5 + cos(2π * s[1]) + 0.65 * cos(4π * s[1]) - 0.65, 1.5 * sin(2π * s[1]))\nend\ncircle = Inti.parametric_curve(0.0, 1.0; labels = [\"circle\"]) do s\n return SVector(cos(2π * s[1]), sin(2π * s[1]))\nend\nΓ = kite ∪ circle","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Inti.jl expects the parametrization of the curve to be a function mapping scalars to points in space represented by SVectors. The labels argument is optional, and can be used to identify the different parts of the boundary. The Domain object Γ represents the boundary of the geometry, and can be used to create a mesh:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"# Create a mesh for the geometry\nmsh = Inti.meshgen(Γ; meshsize = 2π / k / 10)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To visualize the mesh, we can load Meshes.jl and one of Makie's backends:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using Meshes, GLMakie\nviz(msh; segmentsize = 3, axis = (aspect = DataAspect(), ), figure = (; size = (400,300)))","category":"page"},{"location":"tutorials/getting_started/#Quadrature","page":"Getting started","title":"Quadrature","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Once the mesh is created, we can define a quadrature to be used in the discretization of the integral operators:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"# Create a quadrature\nQ = Inti.Quadrature(msh; qorder = 5)\nnothing # hide","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"A Quadrature is simply a collection of QuadratureNode objects:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Q[1]","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"In the constructor above we specified a quadrature order of 5, and Inti.jl internally picked a ReferenceQuadrature suitable for the specified order; for finer control, a quadrature rule can be specified directly.","category":"page"},{"location":"tutorials/getting_started/#Integral-operators","page":"Getting started","title":"Integral operators","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To continue, we need to reformulate the Helmholtz problem as a boundary integral equation. Among the plethora of options, we will use in this tutorial a simple direct formulation, which uses Green's third identity to relate the values of u and partial_nu u on Gamma:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" -fracu(boldsymbolx)2 + Du(boldsymbolx) = Spartial_nu u(boldsymbolx) quad boldsymbolx in Gamma","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Here S and D are the single- and double-layer operators, formally defined as:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" Ssigma(boldsymbolx) = int_Gamma G(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly) quad\n Dsigma(boldsymbolx) = int_Gamma fracpartial Gpartial nu_boldsymboly(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"where","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"G(boldsymbolx boldsymboly) = fraci4 H^(1)_0(kboldsymbolx -\nboldsymboly)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"is the fundamental solution of the Helmholtz equation, with H^(1)_0 being the Hankel function of the first kind. Note that G is singular when boldsymbolx = boldsymboly, and therefore the numerical discretization of S and D requires special care.","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To approximate S and D, we can proceed as follows:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"S, D = Inti.single_double_layer(;\n op,\n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,),\n)\nnothing # hide","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Much of the complexity involved in the numerical computation is hidden in the function above; later in the tutorials we will discuss in more details the options available for the compression and correction methods, as well as how to define custom kernels and operators. For now, it suffices to know that S and D are matrix-like objects that can be used to solve the boundary integral equation. For that, we need to provide the boundary data g.","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"tip: Fast algorithms\nPowered by external libraries, Inti.jl supports several acceleration methods for matrix-vector multiplication, including so far:Fast multipole method (FMM) mapsto correction = (method = :fmm, tol = 1e-8)\nHierarchical matrix (H-matrix) mapsto correction = (method = :hmatrix, tol = 1e-8)Note that in such cases only the matrix-vector product may not be available, and therefore iterative solvers such as GMRES are required for the solution of the resulting linear systems.","category":"page"},{"location":"tutorials/getting_started/#Source-term-and-solution","page":"Getting started","title":"Source term and solution","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"We are interested in the scattered field u produced by an incident plane wave u_i = e^i k boldsymbold cdot boldsymbolx, where boldsymbold is a unit vector denoting the direction of the plane wave. Assuming that the total field u_t = u_i + u satisfies a homogenous Neumann condition on Gamma, and that the scattered field u satisfies the Sommerfeld radiation condition, we can write the boundary condition as:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" partial_nu u = -partial_nu u_i quad boldsymbolx in Gamma","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"We can thus solve the boundary integral equation to find u on Gamma:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using LinearAlgebra\n# define the incident field and compute its normal derivative\nθ = 0\nd = SVector(cos(θ), sin(θ))\ng = map(Q) do q\n # normal derivative of e^{ik*d⃗⋅x}\n x, ν = q.coords, q.normal\n return -im * k * exp(im * k * dot(x, d)) * dot(d, ν)\nend ## Neumann trace on boundary\nu = (-I / 2 + D) \\ (S * g) # Dirichlet trace on boundary\nnothing # hide","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"note: Iterating over a quadrature\nIn computing g above, we used map to evaluate the incident field at all quadrature nodes. When iterating over Q, the iterator returns a QuadratureNode, and not simply the coordinate of the quadrature node. This is so that we can access additional information, such as the normal vector, at the quadrature node.","category":"page"},{"location":"tutorials/getting_started/#Integral-representation-and-visualization","page":"Getting started","title":"Integral representation and visualization","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Now that we know both the Dirichlet and Neumann data on the boundary, we can use Green's representation formula, i.e.,","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" mathcalDu(boldsymbolr) - mathcalSpartial_nu u(boldsymbolr) = begincases\n u(boldsymbolr) textif boldsymbolr in mathbbR^2 setminus overlineOmega\n 0 textif boldsymbolr in Omega\n endcases","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"where mathcalD and mathcalS are the double- and single-layer potentials defined as:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" mathcalSsigma(boldsymbolr) = int_Gamma G(boldsymbolr boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly) quad\n mathcalDsigma(boldsymbolr) = int_Gamma fracpartial Gpartial nu_boldsymboly(boldsymbolr boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"to compute the solution u in the domain:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nuₛ = x -> 𝒟[u](x) - 𝒮[g](x)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To wrap things up, let's visualize the scattered field:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"xx = yy = range(-5; stop = 5, length = 100)\nU = map(uₛ, Iterators.product(xx, yy))\nUi = map(x -> exp(im*k*dot(x, d)), Iterators.product(xx, yy))\nUt = Ui + U\nfig, ax, hm = heatmap(\n xx,\n yy,\n real(Ut);\n colormap = :inferno,\n interpolate = true,\n axis = (aspect = DataAspect(), xgridvisible = false, ygridvisible = false),\n)\nviz!(msh; segmentsize = 2)\nColorbar(fig[1, 2], hm; label = \"real(u)\")\nfig # hide","category":"page"},{"location":"tutorials/getting_started/#Accuracy-check","page":"Getting started","title":"Accuracy check","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"The scattering example above does not provide an easy way to check the accuracy of the solution. To do so, we can manufacture an exact solution and compare it to the solution obtained numerically, as illustrated below:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"# build an exact solution\nG = Inti.SingleLayerKernel(op)\ndG = Inti.DoubleLayerKernel(op)\nxs = map(θ -> 0.5 * rand() * SVector(cos(θ), sin(θ)), 2π * rand(10))\ncs = rand(ComplexF64, length(xs))\nuₑ = q -> sum(c * G(x, q) for (x, c) in zip(xs, cs))\n∂ₙu = q -> sum(c * dG(x, q) for (x, c) in zip(xs, cs))\ng = map(∂ₙu, Q) \nu = (-I / 2 + D) \\ (S * g)\nuₛ = x -> 𝒟[u](x) - 𝒮[g](x)\npts = [5*SVector(cos(θ), sin(θ)) for θ in range(0, 2π, length = 100)]\ner = norm(uₛ.(pts) - uₑ.(pts), Inf)\nprintln(\"maximum error on circle of radius 5: $er\")","category":"page"},{"location":"#Inti","page":"Home","title":"Inti","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = Inti","category":"page"},{"location":"","page":"Home","title":"Home","text":"(Image: Stable) (Image: Dev) (Image: Build Status) (Image: codecov) (Image: Aqua)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Inti.jl is a Julia library for the numerical solution of boundary and volume integral equations. It offers routines for assembling and solving the linear systems that result from applying the Nyström discretization method. Designed for flexibility and efficiency, the package currently supports the following features:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Specialized integration routines for computing singular and nearly-singular integrals.\nIntegrated support for acceleration routines, including the Fast Multipole Method (FMM) and Hierarchical Matrices, by wrapping external libraries.\nPredefined kernels and integral operators for partial differential equations (PDEs) commonly found in mathematical physics (e.g. Laplace, Helmholtz, Stokes).\nSupport for complex geometries in 2D and 3D, either through native parametric representations or by importing mesh files from external sources.\nEfficient construction of complex integral operators from simpler ones through lazy composition.","category":"page"},{"location":"#Installing-Julia","page":"Home","title":"Installing Julia","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Download Julia from julialang.org, or use juliaup installer. We recommend using the latest stable version of Julia, although Inti.jl should work with >=v1.9.","category":"page"},{"location":"#Installing-Inti.jl","page":"Home","title":"Installing Inti.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Inti.jl is registered in the Julia General registry and can be installed by launching a Julia REPL and typing the following command:","category":"page"},{"location":"","page":"Home","title":"Home","text":"]add Inti","category":"page"},{"location":"","page":"Home","title":"Home","text":"Alternatively, one can install the latest version of Inti.jl from the main branch using:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Pkg; Pkg.add(;url = \"https://github.com/IntegralEquations/Inti.jl\", rev = \"main\")","category":"page"},{"location":"","page":"Home","title":"Home","text":"Change rev if a different branch or a specific commit hash is desired.","category":"page"},{"location":"#Installing-weak-dependencies","page":"Home","title":"Installing weak dependencies","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Inti.jl comes with a set of optional dependencies that can be installed on demand. These provide additional features which can be useful in certain scenarios (e.g. visualization, meshing, acceleration). For convenience, Inti.jl provides the stack_weakdeps_env! function to install all the weak dependencies at once:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Inti\nInti.stack_weakdeps_env!(; verbose = false, update = true)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Note that the first time you run this command, it may take a while to download and compile the dependencies. Subsequent runs will be faster. If preferred, extensions can be manually controlled by Pkg.adding the desired packages from the list above.","category":"page"},{"location":"#Basic-usage","page":"Home","title":"Basic usage","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Inti.jl can be used to solve a variety of linear partial differential equations by recasting them as integral equations. The general workflow for solving a problem consists of the following steps:","category":"page"},{"location":"","page":"Home","title":"Home","text":" underbracefboxGeometry rightarrow fboxMesh_textbfpre-processing rightarrow fboxcolorredSolver rightarrow underbracefboxVisualization_textbfpost-processing","category":"page"},{"location":"","page":"Home","title":"Home","text":"Geometry: Define the domain of interest using simple shapes (e.g., circles, rectangles) or more complex CAD models.\nMesh: Create a mesh to approximate the geometry. The mesh is used to define a quadrature and discretize the boundary integral equation.\nSolver: With a mesh and an accompanying quadrature, Inti.jl's routines provide ways to assemble and solve the system of equations arising from the discretization of the integral operators. The core of the library lies in service of this step.\nVisualization: Visualize the solution using a plotting library such as Makie.jl, or export it to a file for further analysis.","category":"page"},{"location":"","page":"Home","title":"Home","text":"As a simple example illustrating the steps above, consider an interior Laplace problem, in two dimensions, with Dirichlet boundary conditions:","category":"page"},{"location":"","page":"Home","title":"Home","text":"beginaligned\nDelta u = 0 quad textin Omega \nu = g quad texton Gamma\nendaligned","category":"page"},{"location":"","page":"Home","title":"Home","text":"where Omega subset mathbbR^2 is a sufficiently smooth domain, and Gamma = partial Omega its boundary. A boundary integral reformulation can be achieved by e.g. searching for the solution u in the form of a single-layer potential:","category":"page"},{"location":"","page":"Home","title":"Home","text":"u(boldsymbolr) = int_Gamma G(boldsymbolrboldsymboly)sigma(boldsymboly) mathrmdGamma(boldsymboly)","category":"page"},{"location":"","page":"Home","title":"Home","text":"where sigma Gamma to mathbbR is an unknown density function, and G is the fundamental solution of the Laplace equation. This ansatz is, by construction, an exact solution to the PDE on Omega. Imposing the boundary condition on Gamma leads to the following integral equation:","category":"page"},{"location":"","page":"Home","title":"Home","text":" int_Gamma G(boldsymbolxboldsymboly)sigma(boldsymboly) mathrmdGamma(boldsymboly) = g(boldsymbolx) quad forall boldsymbolx in Gamma","category":"page"},{"location":"","page":"Home","title":"Home","text":"Expressing the problem above in Inti.jl looks like this:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Inti, LinearAlgebra, StaticArrays\n# create a geometry given by a function f : [0,1] → Γ ⊂ R^2. \ngeo = Inti.parametric_curve(0, 1) do s\n SVector(0.25, 0.0) + SVector(cos(2π * s) + 0.65 * cos(4π * s[1]) - 0.65, 1.5 * sin(2π * s))\nend\nΓ = Inti.Domain(geo)\n# create a mesh and quadrature\nmsh = Inti.meshgen(Γ; meshsize = 0.1)\nQ = Inti.Quadrature(msh; qorder = 5)\n# create the integral operators\nop = Inti.Laplace(;dim=2)\nS, _ = Inti.single_double_layer(;\n op, \n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,)\n)\n# manufacture a harmonic function (exact solution) and take its trace on Γ\nuₑ = x -> x[1] + x[2] + x[1]*x[2] + x[1]^2 - x[2]^2 - 2 * log(norm(x .- SVector(-0.5, -1.5)))\ng = map(q -> uₑ(q.coords), Q) # value at quad nodes\n# solve for σ\nσ = S \\ g\n# use the single-layer potential to evaluate the solution\n𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nuₕ = x -> 𝒮[σ](x)","category":"page"},{"location":"","page":"Home","title":"Home","text":"The function uₕ is now a numerical approximation of the solution to the Laplace equation, and can be evaluated at any point in the domain:","category":"page"},{"location":"","page":"Home","title":"Home","text":"pt = SVector(0.5, 0.1)\nprintln(\"Exact value at $pt: \", uₑ(pt))\nprintln(\"Approx. value at $pt: \", uₕ(pt))","category":"page"},{"location":"","page":"Home","title":"Home","text":"If we care about the solution on the entire domain, we can visualize it using:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Meshes, GLMakie # trigger the loading of some Inti extensions\nxx = yy = range(-2, 2, length = 100)\nfig = Figure(; size = (600,300))\ninside = x -> Inti.isinside(x, Q) \nopts = (xlabel = \"x\", ylabel = \"y\", aspect = DataAspect())\nax1 = Axis(fig[1, 1]; title = \"Exact solution\", opts...)\nh1 = heatmap!(ax1, xx,yy,(x, y) -> inside((x,y)) ? uₑ((x,y)) : NaN)\nviz!(msh; segmentsize = 3)\ncb = Colorbar(fig[1, 3], h1, size = 20, height = 200)\nax2 = Axis(fig[1, 2]; title = \"Approx. solution\", opts...)\nh2 = heatmap!(ax2, xx,yy, (x, y) -> inside((x,y)) ? uₕ((x,y)) : NaN, colorrange = cb.limits[])\nviz!(msh; segmentsize = 3)\nfig # hide","category":"page"},{"location":"","page":"Home","title":"Home","text":"info: Formulation of the problem as an integral equation\nGiven a PDE and boundary conditions, there are often many ways to recast the problem as an integral equation, and the choice of formulation plays an important role in the unique solvability, efficiency, and accuracy of the numerical solution. Inti.jl provides a flexible framework for experimenting with different formulations, but it is up to the user to choose the most appropriate one for their problem.","category":"page"},{"location":"","page":"Home","title":"Home","text":"While the example above is a simple one, Inti.jl can handle significantly more complex problems involving multiple domains, heterogeneous coefficients, vector-valued PDEs, and three-dimensional geometries. The best way to dive deeper into Inti.jl's capabilities is the tutorials section. More advanced usage can be found in the examples section.","category":"page"},{"location":"#Contributing","page":"Home","title":"Contributing","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"There are several ways to contribute to Inti.jl:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Reporting bugs: If you encounter a bug, please open an issue on the GitHub. If possible, please include a minimal working example that reproduces the problem.\nExamples: If you have a cool example that showcases Inti.jl's capabilities, consider submitting a PR to add it to the examples section.\nContributing code: If you would like to contribute code to Inti.jl, please fork the repository and submit a pull request. Feel free to open a draft PR early in the development process to get feedback on your changes.\nFeature requests: If you have an idea for a new feature or improvement, we would love to hear about it.\nDocumentation: If you find any part of the documentation unclear or incomplete, please let us know. Or even better, submit a PR with the improved documentation.","category":"page"},{"location":"#Acknowledgements","page":"Home","title":"Acknowledgements","text":"","category":"section"}] +[{"location":"tutorials/solvers/#Linear-solvers","page":"Linear solvers","title":"Linear solvers","text":"","category":"section"},{"location":"tutorials/solvers/","page":"Linear solvers","title":"Linear solvers","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/solvers/","page":"Linear solvers","title":"Linear solvers","text":"warning: Work in progress\nThis tutorial is still a work in progress. We will update it with more details and examples in the future.","category":"page"},{"location":"tutorials/solvers/","page":"Linear solvers","title":"Linear solvers","text":"Inti.jl does not provide its own linear solvers, but relies on external libraries such as IterativeSolvers.jl or the LinearAlgebra standard library for the solving the linear systems that arise in the discretization of integral equations.","category":"page"},{"location":"references/","page":"References","title":"References","text":"CurrentModule = Inti","category":"page"},{"location":"references/#References","page":"References","title":"References","text":"","category":"section"},{"location":"references/","page":"References","title":"References","text":"J.-C. Nédélec. Acoustic and electromagnetic equations: integral representations for harmonic problems. Vol. 144 (Springer, 2001).\n\n\n\nD. Colton and R. Kress. Integral equation methods in scattering theory (SIAM, 2013).\n\n\n\nM. Bebendorf. Hierarchical matrices (Springer, 2008).\n\n\n\nW. Hackbusch and others. Hierarchical matrices: algorithms and analysis. Vol. 49 (Springer, 2015).\n\n\n\nV. Rokhlin. Rapid solution of integral equations of classical potential theory. Journal of computational physics 60, 187–207 (1985).\n\n\n\nL. Greengard and V. Rokhlin. A fast algorithm for particle simulations. Journal of computational physics 73, 325–348 (1987).\n\n\n\nL. M. Faria, C. Pérez-Arancibia and M. Bonnet. General-purpose kernel regularization of boundary integral equations via density interpolation. Computer Methods in Applied Mechanics and Engineering 378, 113703 (2021).\n\n\n\nT. G. Anderson, M. Bonnet, L. M. Faria and C. Pérez-Arancibia. Fast, high-order numerical evaluation of volume potentials via polynomial density interpolation. Journal of Computational Physics, 113091 (2024).\n\n\n\n","category":"page"},{"location":"tutorials/layer_potentials/#Layer-potentials","page":"Layer potentials","title":"Layer potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"note: Important points covered in this tutorial\nNearly singular evaluation of layer potentials\nCreating a smooth domain with splines using Gmsh's API\nPlotting values on a mesh","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"In this tutorial we focus on evaluating the layer potentials given a source density. This is a common post-processing task in boundary integral equation methods, and while most of it is straightforward, some subtleties arise when the target points are close to the boundary (nearly-singular integrals).","category":"page"},{"location":"tutorials/layer_potentials/#Integral-potentials","page":"Layer potentials","title":"Integral potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"IntegralPotential represent the following mathematical objects:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"mathcalPsigma(boldsymbolr) = int_Gamma K(boldsymbolr boldsymbolr) sigma(boldsymbolr) dboldsymbolr","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"where K is the kernel of the operator, Gamma is the source's boundary, boldsymbolr not in Gamma is a target point, and sigma is the source density.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Here is a simple example of how to create a kernel representing a Laplace double-layer potential:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using Inti, StaticArrays, LinearAlgebra\n# define a kernel function\nfunction K(target,source)\n r = Inti.coords(target) - Inti.coords(source)\n ny = Inti.normal(source)\n return 1 / (2π * norm(r)^2) * dot(r, ny)\nend\n# define a domain\nΓ = Inti.parametric_curve(s -> SVector(cos(2π * s), sin(2π * s)), 0, 1) |> Inti.Domain\n# and a quadrature of Γ\nQ = Inti.Quadrature(Γ; meshsize = 0.1, qorder = 5)\n𝒮 = Inti.IntegralPotential(K, Q)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"If we have a source density sigma, defined on the quadrature nodes of Gamma, we can create a function that evaluates the layer potential at an arbitrary point:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"σ = map(q -> 1.0, Q)\nu = 𝒮[σ]","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"u is now an anonymous function that evaluates the layer potential at any point:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"r = SVector(0.1, 0.2)\n@assert u(r) ≈ -1 # hide\nu(r)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Although we created the single-layer potential for the Laplace kernel manually, it is often more convenient to use the single_layer_potential when working with a supported PDE, e.g.:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"op = Inti.Laplace(; dim = 2)\n𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"creates the single and double layer potentials for the Laplace equation in 2D.","category":"page"},{"location":"tutorials/layer_potentials/#Direct-evaluation-of-layer-potentials","page":"Layer potentials","title":"Direct evaluation of layer potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We now show how to evaluate the layer potentials of an exact solution on a mesh created through the Gmsh API. Do to so, let us first define the PDE:g","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using Inti, StaticArrays, LinearAlgebra, Meshes, GLMakie, Gmsh\n# define the PDE\nk = 4π\nop = Inti.Helmholtz(; dim = 2, k)","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We will now use the gmsh_curve function to create a smooth domain of a kite using splines:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"gmsh.initialize()\nmeshsize = 2π / k / 4\nkite = Inti.gmsh_curve(0, 1; meshsize) do s\n SVector(0.25, 0.0) + SVector(cos(2π * s) + 0.65 * cos(4π * s[1]) - 0.65, 1.5 * sin(2π * s))\nend\ncl = gmsh.model.occ.addCurveLoop([kite])\nsurf = gmsh.model.occ.addPlaneSurface([cl])\ngmsh.model.occ.synchronize()\ngmsh.model.mesh.generate(2)\nmsh = Inti.import_mesh(; dim = 2)\ngmsh.finalize()","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"tip: Tip\nThe GMSH API is a powerful tool to create complex geometries and meshes directly from Julia (the gmsh_curve function above is just a simple wrapper around some spline functionality). For more information, see the official documentation.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We can visualize the triangular mesh using:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using Meshes, GLMakie\n# extract the domain Ω from the mesh entities\nents = Inti.entities(msh)\nΩ = Inti.Domain(e->Inti.geometric_dimension(e) == 2, ents)\nviz(msh[Ω]; showsegments = true, axis = (aspect = DataAspect(), ))","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"For the purpose of testing the accuracy of the layer potential evaluation, we will construct an exact solution of the Helmholtz equation on the interior domain and plot it:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"# construct an exact interior solution as a sum of random plane waves\ndirs = [SVector(cos(θ), sin(θ)) for θ in 2π*rand(10)]\ncoefs = rand(ComplexF64, 10)\nu = (x) -> sum(c*exp(im*k*dot(x, d)) for (c,d) in zip(coefs, dirs))\ndu = (x,ν) -> sum(c*im*k*dot(d, ν)*exp(im*k*dot(x, d)) for (c,d) in zip(coefs, dirs))\n# plot the exact solution\nΩ_msh = view(msh, Ω)\ntarget = Inti.nodes(Ω_msh)\nviz(Ω_msh; showsegments = false, axis = (aspect = DataAspect(), ), color = real(u.(target)))","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Since u satisfies the Helmholtz equation, we know that the following representation holds:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"u(boldsymbolr) = mathcalSgamma_1 u(boldsymbolr) - mathcalDgamma_0 u(boldsymbolr) quad boldsymbolr in Omega","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"where gamma_0 u and gamma_1 u are the respective Dirichlet and Neumann traces of u, and mathcalS and mathcalD are the respective single and double layer potentials over Gamma = partial Omega.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Let's compare next the exact solution with the layer potential evaluation, based on a quadrature of Gamma:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"Γ = Inti.boundary(Ω)\nQ = Inti.Quadrature(view(msh,Γ); qorder = 5)\n# evaluate the layer potentials\n𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nγ₀u = map(q -> u(q.coords), Q)\nγ₁u = map(q -> du(q.coords, q.normal), Q)\nuₕ = x -> 𝒮[γ₁u](x) - 𝒟[γ₀u](x)\n# plot the error on the target nodes\ner_log10 = log10.(abs.(u.(target) - uₕ.(target)))\ncolorrange = extrema(er_log10)\nfig, ax, pl = viz(Ω_msh;\n color = er_log10,\n colormap = :viridis,\n colorrange,\n axis = (aspect = DataAspect(),),\n interpolate=true\n)\nColorbar(fig[1, 2]; label = \"log₁₀(error)\", colorrange)\nfig","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"We see a common pattern of potential evaluation: the error is small away from the boundary, but grows near it. This is due to the nearly-singular nature of the layer potential integrals, which can be mitigated by using a correction method that accounts for the singularity of the kernel as boldsymbolr to Gamma.","category":"page"},{"location":"tutorials/layer_potentials/#Near-field-correction-of-layer-potentials","page":"Layer potentials","title":"Near-field correction of layer potentials","text":"","category":"section"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"There are two cases where the direct evaluation of layer potentials is not recommended:","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"When the target point is close to the boundary (nearly-singular integrals).\nWhen evaluation at many target points is desired (computationally burdensome)and take advantage of an acceleration routine.","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"In such contexts, it is recommended to use the single_double_layer function (alternately, one can directly assemble an IntegralOperator) with a correction, for the first case, and/or a compression (acceleration) method, for the latter case, as appropriate. Here is an example of how to use the FMM acceleration with a near-field correction to evaluate the layer potentials::","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"using FMM2D\nS, D = Inti.single_double_layer(; op, target, source = Q,\n compression = (method = :fmm, tol = 1e-12),\n correction = (method = :dim, target_location = :inside, maxdist = 0.2)\n)\ner_log10_cor = log10.(abs.(S*γ₁u - D*γ₀u - u.(target)))\ncolorrange = extrema(er_log10) # use scale without correction\nfig = Figure(resolution = (800, 400))\nax1 = Axis(fig[1, 1], aspect = DataAspect(), title = \"Naive evaluation\")\nviz!(Ω_msh; color = er_log10, colormap = :viridis, colorrange,interpolate=true)\nax2 = Axis(fig[1, 2], aspect = DataAspect(), title = \"Nearfield correction\")\nviz!(Ω_msh; color = er_log10_cor, colormap = :viridis, colorrange, interpolate=true)\nColorbar(fig[1, 3]; label = \"log₁₀(error)\", colorrange)\nfig","category":"page"},{"location":"tutorials/layer_potentials/","page":"Layer potentials","title":"Layer potentials","text":"As can be seen, the near-field correction significantly reduces the error near the boundary, making if feasible to evaluate the layer potential near Gamma if necessary.","category":"page"},{"location":"tutorials/integral_operators/#Boundary-integral-operators","page":"Boundary integral operators","title":"Boundary integral operators","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"note: Important points covered in this tutorial\nDefine layer potentials and the four integral operators of Calderón calculus\nConstruct block operators\nSet up a custom kernel","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"A central piece of integral equation methods is the efficient and accurate computation of integral operators. In the first part of this tutorial we will cover how to assemble and manipulate the four integral operators of Calderón calculus, namely the single-layer, double-layer, hypersingular, and adjoint operators [1, 2], for some predefined kernels in Inti.jl. In the second part we will show how to extend the package to handle custom kernels.","category":"page"},{"location":"tutorials/integral_operators/#Predefined-kernels-and-integral-operators","page":"Boundary integral operators","title":"Predefined kernels and integral operators","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"To simplify the construction of integral operators for some commonly used PDEs, Inti.jl defines a few AbstractDifferentialOperators types. For each of these PDEs, the package provides a SingleLayerKernel, DoubleLayerKernel, HyperSingularKernel, and AdjointDoubleLayerKernel that can be used to construct the corresponding kernel functions, e.g.:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using Inti, StaticArrays, LinearAlgebra\nop = Inti.Helmholtz(; dim = 2, k = 2π)\nG = Inti.SingleLayerKernel(op)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Typically, we are not interested in the kernels themselves, but in the integral operators they define. Two functions, single_double_layer and adj_double_layer_hypersingular, are provided as a high-level syntax to construct the four integral operators of Calderón calculus:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Γ = Inti.parametric_curve(s -> SVector(cos(s), sin(s)), 0, 2π) |> Inti.Domain\nQ = Inti.Quadrature(Γ; meshsize = 0.1, qorder = 5)\nS, D = Inti.single_double_layer(; \n op, \n target = Q, \n source = Q, \n compression = (method = :none,), \n correction = (method = :dim,)\n)\nK, N = Inti.adj_double_layer_hypersingular(; \n op, \n target = Q, \n source = Q, \n compression = (method = :none,), \n correction = (method = :dim,)\n)\nnothing # hide","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Much goes on under the hood in the function above, and the sections on correction and compression methods will provide more details on the options available. The important thing to keep in mind is that S, D, K, and N are discrete approximations of the following (linear) operators:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"beginaligned\n Ssigma(boldsymbolx) = int_Gamma G(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly quad \n Dsigma(boldsymbolx) = mathrmpv int_Gamma fracpartial Gpartial nu_boldsymboly(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly \n Ksigma(boldsymbolx) = mathrmpv int_Gamma fracpartial Gpartial nu_boldsymbolx(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly quad\n Nsigma(boldsymbolx) = mathrmfp int_Gamma fracpartial^2 Gpartial nu_boldsymbolx partial nu_boldsymboly(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly\nendaligned","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The actual type of S, D, K, and N depends on the compression and correction methods. In the simple case above, these are simply matrices:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"@assert all(T -> T == Matrix{ComplexF64}, map(typeof, (S, D, K, N))) # hide\nmap(typeof, (S, D, K, N))","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"If we turn on a compression method, such as :fmm, the types may change into something different:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using FMM2D # will load the extension\nSfmm, Dfmm = Inti.single_double_layer(; \n op, \n target = Q, \n source = Q, \n compression = (method = :fmm, tol = 1e-10), \n correction = (method = :dim, )\n)\nKfmm, Nfmm = Inti.adj_double_layer_hypersingular(; \n op, \n target = Q, \n source = Q, \n compression = (method = :fmm, tol = 1e-10), \n correction = (method = :dim,)\n)\ntypeof(Sfmm)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"This is because the FMM method is used to approximate the matrix-vector in a matrix-free way: the only thing guaranteed is that S and D can be applied to a vector:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"x = map(q -> cos(q.coords[1] + q.coords[2]), Q)\n@assert norm(Sfmm*x - S*x, Inf) / norm(S*x, Inf) < 1e-8 # hide\nnorm(Sfmm*x - S*x, Inf)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The Sfmm object above in fact combines two linear maps:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Sfmm","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The FunctionMap computes a matrix-vector by performing a function call to the FMM2D library. The WrappedMap accounts for a sparse matrix used to correct for singular and nearly singular interactions. These two objects are added lazily using LinearMaps.","category":"page"},{"location":"tutorials/integral_operators/#Operator-composition","page":"Boundary integral operators","title":"Operator composition","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Effortlessly and efficiently composing operators is a powerful abstraction for integral equations, as it allows for the construction of complex systems from simple building blocks. To show this, let us show how to construct the Calderón projectors:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"beginaligned\nH = beginbmatrix\n -D S \n -N K\nendbmatrix \nendaligned","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"As is well-known [1, Theorem 3.1.3], the operators C_pm = I2 pm H are the projectors (i.e. C_pm^2 = C_pm):","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using LinearMaps\n# create the block operator\nH = [-Dfmm Sfmm; -Nfmm Kfmm]\nC₊ = I / 2 + H\nC₋ = I / 2 - H\n# define two density functions on Γ\nu = map(q -> cos(q.coords[1] + q.coords[2]), Q)\nv = map(q-> q.coords[1], Q)\nx = [u; v]\n# compute the error in the projector identity\ne₊ = norm(C₊*(C₊*x) - C₊*x, Inf)\ne₋ = norm(C₋*(C₋*x) - C₋*x, Inf)\n@assert e₊ < 1e-5 && e₋ < 1e-5 # hide\nprintln(\"projection error for C₊: $e₊\")\nprintln(\"projection error for C₋: $e₋\")","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"We see that the error in the projector identity is small, as expected. Note that such compositions are not limited to the Calderón projectors, and can be used e.g. to construct the combined field integral equation (CFIE), or to compose a formulation with an operator preconditioner.","category":"page"},{"location":"tutorials/integral_operators/#Custom-kernels","page":"Boundary integral operators","title":"Custom kernels","text":"","category":"section"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"So far we have focused on problems for which Inti.jl provides predefined kernels, and used the high-level syntax of e.g. single_double_layer to construct the integral operators. We will now dig into the details of how to set up a custom kernel function, and how to build an integral operator from it.","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"note: Integral operators coming from PDEs\nIf the integral operator of interest arises from a PDE, it is recommended to define a new AbstractDifferentialOperator type, and implement the required methods for SingleLayerKernel, DoubleLayerKernel, AdjointDoubleLayerKernel, and HyperSingularKernel. This will enable the use of the high-level syntax for constructing boundary integral operators, as well as the use of the compression and correction methods specific to integral operators arising from PDEs.","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"For the sake of simplicity, let us consider the following kernel representing the half-space Dirichlet Green function for Helmholtz's equation in 2D:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":" G_D(boldsymbolx boldsymboly) = fraci4 H^(1)_0(k boldsymbolx - boldsymboly) - fraci4 H^(1)_0(k boldsymbolx - boldsymboly^*)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"where boldsymboly^* = (y_1 -y_2). We can define this kernel as a","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using SpecialFunctions # for hankelh1\nfunction helmholtz_kernel(target, source, k)\n x, y = Inti.coords(target), Inti.coords(source)\n yc = SVector(y[1], -y[2])\n d, dc = norm(x-y), norm(x-yc)\n # the singularity at x = y needs to be handled separately, so just put a zero\n d == 0 ? zero(ComplexF64) : im / 4 * ( hankelh1(0, k * d) - hankelh1(0, k * dc))\nend","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Let us now consider the integral operator S defined by:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":" Ssigma(boldsymbolx) = int_Gamma G_D(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly quad boldsymbolx in Gamma","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"We can represent S by an IntegralOperator type:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"k = 50π\nλ = 2π/k\nmeshsize = λ / 10\ngeo = Inti.parametric_curve(s -> SVector(cos(s), 2 + sin(s)), 0, 2π)\nΓ = Inti.Domain(geo)\nmsh = Inti.meshgen(Γ; meshsize)\nQ = Inti.Quadrature(msh; qorder = 5)\n# create a local scope to capture `k`\nK = let k = k\n (t,q) -> helmholtz_kernel(t,q,k)\nend\nSop = Inti.IntegralOperator(K, Q, Q)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"note: Signature of custom kernels\nKernel functions passed to IntegralOperator should always take two arguments, target and source, which are both of QuadratureNode. This allows for extracting not only the coords of the nodes, but also the normal vector if needed (e.g. for double-layer or hypersingular kernels).","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The approximation of Sop now involves two steps:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"build a dense operator S₀ that efficiently computes the matrix-vector product Sop * x for any vector x\ncorrect for the inaccuracies of S₀ due to singular/nearly-singular interactions by adding to it a correction matrix δS","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"For the first step, we will use a hierarchical matrix:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using HMatrices\nS₀ = Inti.assemble_hmatrix(Sop; rtol = 1e-4)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"The correction matrix δS will be constructed using adaptive_correction:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"δS = Inti.adaptive_correction(Sop; tol = 1e-4, maxdist = 5*meshsize)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"How exactly one adds S₀ and δS to get the final operator depends on the intended usage. For instance, one can use the LinearMap type to simply add them lazily:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"using LinearMaps\nS = LinearMap(S₀) + LinearMap(δS)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"Or, one can add δS to S₀ to create a new object:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"S = S₀ + δS","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"or if performance/memory is a concern, one may want to directly add δS to S₀ in-place:","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"axpy!(1.0, δS, S₀)","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"All of these should give an identical matrix-vector product, but the latter two allow e.g. for the use of direct solvers though an LU factorization.","category":"page"},{"location":"tutorials/integral_operators/","page":"Boundary integral operators","title":"Boundary integral operators","text":"warning: Limitations\nIntegral operators defined from custom kernel functions do not support all the features of the predefined ones. In particular, some singular integration methods (e.g. the Density Interpolation Method) and acceleration routines (e.g. Fast Multipole Method) used to correct for singular and nearly singular integral operators, and to accelerate the matrix vector products, are only available for specific kernels. Check the corrections and compression for more details concerning which methods are compatible with custom kernels.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"(Image: Pluto notebook)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"begin\n import Pkg as _Pkg\n haskey(ENV, \"PLUTO_PROJECT\") && _Pkg.activate(ENV[\"PLUTO_PROJECT\"])\n using PlutoUI: TableOfContents\nend;","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Helmholtz-scattering","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Important points covered in this example\nCreating a geometry using the Gmsh API\nAssembling integral operators and integral potentials\nSetting up a sound-soft problem in both 2 and 3 spatial dimensions\nUsing GMRES to solve the linear system\nExporting the solution to Gmsh for visualization","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"In this tutorial we will show how to solve an acoustic scattering problem in the context of Helmholtz equation. We will focus on a smooth sound-soft obstacle for simplicity, and introduce along the way the necessary techniques used to handle some difficulties encountered. We will use various packages throughout this example (including of course Inti.jl); if they are not on your environment, you can install them using ] add in the REPL.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"In the following section, we will provide a brief mathematical description of the problem (valid in both 2 and 3 dimensions). We will tackle the two-dimensional problem first, for which we do not need to worry much about performance issues (e.g. compressing the integral operators). Finally, we present a three-dimensional example, where we will use HMatrices.jl to compress the underlying integral operators.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Sound-soft-problem","page":"Helmholtz scattering","title":"Sound-soft problem","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"This example concerns the sound-soft acoustic scattering problem. Mathematically, this means solving an exterior problem governed by Helmholtz equation (time-harmonic acoustics) with a Dirichlet boundary condition. More precisely, letting Omega subset mathbbR^d be a bounded domain, and denoting by Gamma = partial Omega its boundary, we wish to solve","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Delta u + k^2 u = 0 quad texton quad mathbbR^d setminus barOmega","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"subject to Dirichlet boundary conditions on Gamma","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tu(boldsymbolx) = g(boldsymbolx) quad textfor quad boldsymbolx in Gamma","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"and the Sommerfeld radiation condition at infinity","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tlim_boldsymbolx to infty boldsymbolx^(d-1)2 left( fracpartial upartial boldsymbolx - i k u right) = 0","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Here g is a (given) boundary datum, and k is the constant wavenumber.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"For simplicity, we will take Gamma circle/sphere, and focus on the plane-wave scattering problem. This means we will seek a solution u of the form u = u_s + u_i, where u_i is a known incident field, and u_s is the scattered field we wish to compute.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Complex geometries\nThe main reason for focusing on such a simple example is twofold. First, it alleviates the complexities associated with the mesh generation. Second, since exact solutions are known for this problem (in the form of a series), it is easy to assess the accuracy of the solution obtained. In practice, you can use the same techniques to solve the problem on more complex geometries by providing a .msh file containing the mesh.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Using the theory of boundary integral equations, we can express u_s as","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tu_s(boldsymbolr) = mathcalDsigma(boldsymbolr) - i k mathcalSsigma(boldsymbolr)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"where mathcalS is the so-called single layer potential, mathcalD is the double-layer potential, and sigma Gamma to mathbbC is a surface density. This is an indirect formulation (because sigma is an auxiliary density, not necessarily physical) commonly referred to as a combined field formulation. Taking the limit mathbbR^d setminus bar Omega ni x to Gamma, it can be shown that the following equation holds on Gamma:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"\tleft( fracmathrmI2 + mathrmD - i k mathrmS right)sigma = g","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"where mathrmI is the identity operator, and mathrmS and mathrmD are the single- and double-layer operators. This is the combined field integral equation that we will solve. The boundary data g is obtained by applying the sound-soft condition u=0 on Gamma, from which it readily follows that u_s = -u_i on Gamma.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We are now have the necessary background to solve this problem in both 2 and 3 spatial dimensions. Let's load Inti.jl as well as the required dependencies","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"using Inti\nusing LinearAlgebra\nusing StaticArrays\nusing Gmsh\nusing Meshes\nusing GLMakie\nusing SpecialFunctions\nusing GSL\nusing IterativeSolvers\nusing LinearMaps","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"and setup some of the (global) problem parameters:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"k = 4π\nλ = 2π / k\nqorder = 4 # quadrature order\ngorder = 2 # order of geometrical approximation\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Two-dimensional-scattering","page":"Helmholtz scattering","title":"Two-dimensional scattering","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We will use Gmsh API for creating .msh file containing the desired geometry and mesh. Here is a function to mesh the circle:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"function gmsh_circle(; name, meshsize, order = 1, radius = 1, center = (0, 0))\n try\n gmsh.initialize()\n gmsh.model.add(\"circle-mesh\")\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", meshsize)\n gmsh.option.setNumber(\"Mesh.MeshSizeMin\", meshsize)\n gmsh.model.occ.addDisk(center[1], center[2], 0, radius, radius)\n gmsh.model.occ.synchronize()\n gmsh.model.mesh.generate(1)\n gmsh.model.mesh.setOrder(order)\n gmsh.write(name)\n finally\n gmsh.finalize()\n end\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Let us now use gmsh_circle to create a circle.msh file. As customary in wave-scattering problems, we will choose a mesh size that is proportional to wavelength:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"name = joinpath(@__DIR__, \"circle.msh\")\nmeshsize = λ / 5\ngmsh_circle(; meshsize, order = gorder, name)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now import the file and parse the mesh and domain information into Inti.jl using the import_mesh function:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Inti.clear_entities!() # empty the entity cache\nmsh = Inti.import_mesh(name; dim = 2)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The code above will import the mesh with all of its geometrical entities. The dim=2 projects all points to two dimensions by ignoring the third component. To extract the domain Omega we need to filter the entities in the mesh; here we will simply filter them based on the geometric_dimension:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 2, Inti.entities(msh))","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"To solve our boundary integral equation usign a Nyström method, we actually need a quadrature of our curve/surface (and possibly the normal vectors at the quadrature nodes). Once a mesh is available, creating a quadrature object can be done via the Quadrature constructor, which requires passing a mesh of the domain that one wishes to generate a quadrature for:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Γ = Inti.boundary(Ω)\nΓ_msh = view(msh, Γ)\nQ = Inti.Quadrature(Γ_msh; qorder)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"tip: Views of a mesh\nIn Inti.jl, you can use domain to create a view of a mesh containing only the elements in the domain. For example view(msh,Γ) will return an SubMesh type that you can use to iterate over the elements in the boundary of the disk without actually creating a new mesh. You can use msh[Γ], or collect(view(msh,Γ)) to create a new mesh containing only the elements and nodes in Γ.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The object Q now contains a quadrature (of order 4) that can be used to solve a boundary integral equation on Γ. As a sanity check, let's make sure integrating the function x->1 over Q gives an approximation to the perimeter:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"@assert abs(Inti.integrate(x -> 1, Q) - 2π) < 1e-5","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"abs(Inti.integrate(x -> 1, Q) - 2π)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"With the Quadrature constructed, we now can define discrete approximation to the integral operators mathrmS and mathrmD as follows:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"op = Inti.Helmholtz(; k, dim = 2)\nS, D = Inti.single_double_layer(;\n op,\n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,),\n)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"There are two well-known difficulties related to the discretization of the boundary integral operators S and D:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The kernel of the integral operator is not smooth, and thus specialized quadrature rules are required to accurately approximate the matrix entries for which the target and source point lie close (relative to some scale) to each other.\nThe underlying matrix is dense, and thus the storage and computational cost of the operator is prohibitive for large problems unless acceleration techniques such as Fast Multipole Methods or Hierarchical Matrices are employed.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Inti.jl tries to provide a modular and transparent interface for dealing with both of these difficulties, where the general approach for solving a BIE will be to first construct a (possible compressed) naive representation of the integral operator where singular and nearly-singular integrals are ignored, followed by a the creation of a (sparse) correction intended to account for such singular interactions. See single_double_layer for more details on the various options available.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now combine S and D to form the combined-field operator:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"L = I / 2 + D - im * k * S\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"where I is the identity matrix. Assuming an incident field along the x_1 direction of the form u_i =e^ikx_1, the right-hand side of the equation can be construted using:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"uᵢ = x -> exp(im * k * x[1]) # plane-wave incident field\nrhs = map(Q) do q\n x = q.coords\n return -uᵢ(x)\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Iterating over a quadrature\nIn computing rhs above, we used map to evaluate the incident field at all quadrature nodes. When iterating over Q, the iterator returns a QuadratureNode, and not simply the coordinate of the quadrature node. This is so that you can access additional information, such as the normal vector, at the quadrature node.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now solve the integral equation using e.g. the backslash operator:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"σ = L \\ rhs\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The variable σ contains the value of the approximate density at the quadrature nodes. To reconstruct a continuous approximation to the solution, we can use single_double_layer_potential to obtain the single- and double-layer potentials, and then combine them as follows:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nuₛ = x -> 𝒟[σ](x) - im * k * 𝒮[σ](x)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The variable uₛ is an anonymous/lambda function representing the approximate scattered field.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"To assess the accuracy of the solution, we can compare it to the exact solution (obtained by separation of variables in polar coordinates):","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"function circle_helmholtz_soundsoft(pt; radius = 1, k, θin)\n x = pt[1]\n y = pt[2]\n r = sqrt(x^2 + y^2)\n θ = atan(y, x)\n u = 0.0\n r < radius && return u\n c(n) = -exp(im * n * (π / 2 - θin)) * besselj(n, k * radius) / besselh(n, k * radius)\n u = c(0) * besselh(0, k * r)\n n = 1\n while (abs(c(n)) > 1e-12)\n u +=\n c(n) * besselh(n, k * r) * exp(im * n * θ) +\n c(-n) * besselh(-n, k * r) * exp(-im * n * θ)\n n += 1\n end\n return u\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Here is the maximum error on some points located on a circle of radius 2:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"uₑ = x -> circle_helmholtz_soundsoft(x; k, radius = 1, θin = 0) # exact solution\ner = maximum(0:0.01:2π) do θ\n R = 2\n x = (R * cos(θ), R * sin(θ))\n return abs(uₛ(x) - uₑ(x))\nend\n@info \"maximum error = $er\"","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"@assert er < 1e-3","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"As we can see, the error is quite small! Let's use Makie to visualize the solution in this simple (2d) example:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"xx = yy = range(-4; stop = 4, length = 200)\nvals =\n map(pt -> Inti.isinside(pt, Q) ? NaN : real(uₛ(pt) + uᵢ(pt)), Iterators.product(xx, yy))\nfig, ax, hm = heatmap(\n xx,\n yy,\n vals;\n colormap = :inferno,\n interpolate = true,\n axis = (aspect = DataAspect(), xgridvisible = false, ygridvisible = false),\n)\nviz!(Γ_msh; color = :white, segmentsize = 5)\nColorbar(fig[1, 2], hm)\nfig","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"While here we simply used a heatmap to visualize the solution, more complex problems may require a mesh-based visualization, where we would first create a mesh for the places where we want to visualize the solution.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Before moving on to the 3D example let us simply mention that, besides the fact that an analytic solution was available for comparisson, there was nothing special about the unit disk (or the use of GMSH). We could have, for instance, replaced the disk by shapes created parametrically:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"let\n # vertices of an equilateral triangle centered at the origin with a vertex at (0,1)\n a, b, c = SVector(0, 1), SVector(sqrt(3) / 2, -1 / 2), SVector(-sqrt(3) / 2, -1 / 2)\n function circle_f(center, radius)\n return s -> center + radius * SVector(cospi(2 * s[1]), sinpi(2 * s[1]))\n end\n disk1 = Inti.parametric_curve(circle_f(a, 1 / 2), 0, 1)\n disk2 = Inti.parametric_curve(circle_f(b, 1 / 2), 0, 1)\n disk3 = Inti.parametric_curve(circle_f(c, 1 / 2), 0, 1)\n Γ = disk1 ∪ disk2 ∪ disk3\n msh = Inti.meshgen(Γ; meshsize)\n Γ_msh = view(msh, Γ)\n Q = Inti.Quadrature(Γ_msh; qorder)\n S, D = Inti.single_double_layer(;\n op,\n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,),\n )\n L = I / 2 + D - im * k * S\n rhs = map(q -> -uᵢ(q.coords), Q)\n σ = L \\ rhs\n 𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\n uₛ = x -> 𝒟[σ](x) - im * k * 𝒮[σ](x)\n vals = map(\n pt -> Inti.isinside(pt, Q) ? NaN : real(uₛ(pt) + uᵢ(pt)),\n Iterators.product(xx, yy),\n )\n colorrange = (-2, 2)\n fig, ax, hm = heatmap(\n xx,\n yy,\n vals;\n colormap = :inferno,\n colorrange,\n interpolate = true,\n axis = (aspect = DataAspect(), xgridvisible = false, ygridvisible = false),\n )\n viz!(Γ_msh; color = :black, segmentsize = 4)\n Colorbar(fig[1, 2], hm)\n fig\nend","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Near-field evaluation\nIn the example above we employed a naive evaluation of the integral potentials, and therefore the computed solution is expected to become innacurate near the obstacles. See the layer potential tutorial for more information on how to correct for this.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/#Three-dimensional-scattering","page":"Helmholtz scattering","title":"Three-dimensional scattering","text":"","category":"section"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We now consider the same problem in 3D. Unlike the 2D case, assembling dense matrix representations of the integral operators quickly becomes unfeasiable as the problem size increases. Inti adds support for compressing the underlying linear operators by wrapping external libraries. In this example, we will rely on HMatrices.jl to handle the compression.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"The visualization is also more involved, and we will use the Gmsh API to create a not only a mesh of the scatterer, but also of a punctured plane where we will visualize the solution. Here is the function that setups up the mesh:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"function gmsh_sphere(; meshsize, order = gorder, radius = 1, visualize = false, name)\n gmsh.initialize()\n gmsh.model.add(\"sphere-scattering\")\n gmsh.option.setNumber(\"Mesh.MeshSizeMax\", meshsize)\n gmsh.option.setNumber(\"Mesh.MeshSizeMin\", meshsize)\n sphere_tag = gmsh.model.occ.addSphere(0, 0, 0, radius)\n xl, yl, zl = -2 * radius, -2 * radius, 0\n Δx, Δy = 4 * radius, 4 * radius\n rectangle_tag = gmsh.model.occ.addRectangle(xl, yl, zl, Δx, Δy)\n outDimTags, _ =\n gmsh.model.occ.cut([(2, rectangle_tag)], [(3, sphere_tag)], -1, true, false)\n gmsh.model.occ.synchronize()\n gmsh.model.addPhysicalGroup(3, [sphere_tag], -1, \"omega\")\n gmsh.model.addPhysicalGroup(2, [dt[2] for dt in outDimTags], -1, \"sigma\")\n gmsh.model.mesh.generate(2)\n gmsh.model.mesh.setOrder(order)\n visualize && gmsh.fltk.run()\n gmsh.option.setNumber(\"Mesh.SaveAll\", 1) # otherwise only the physical groups are saved\n gmsh.write(name)\n return gmsh.finalize()\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"As before, lets write a file with our mesh, and import it into Inti.jl:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"name_sphere = joinpath(@__DIR__, \"sphere.msh\")\ngmsh_sphere(; meshsize = (λ / 5), order = gorder, name = name_sphere, visualize = false)\nmsh_3d = Inti.import_mesh(name_sphere; dim = 3)","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"tip: Tip\nIf you pass visualize=true to gmsh_sphere, it will open a window with the current model. This is done by calling gmsh.fltk.run(). Note that the main julia thread will be blocked until the window is closed.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Since we created physical groups in Gmsh, we can use them to extract the relevant domains Ω and Σ:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Ω_3d = Inti.Domain(e -> \"omega\" ∈ Inti.labels(e), Inti.entities(msh_3d))\nΣ_3d = Inti.Domain(e -> \"sigma\" ∈ Inti.labels(e), Inti.entities(msh_3d))\nΓ_3d = Inti.boundary(Ω_3d)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now create a quadrature as before","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Γ_msh_3d = view(msh_3d, Γ_3d)\nQ_3d = Inti.Quadrature(Γ_msh_3d; qorder)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"tip: Writing/reading a mesh from disk\nWriting and reading a mesh to/from disk can be time consuming. You can avoid doing so by using import_mesh without a file name to import the mesh from the current gmsh session without the need to write it to disk.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Next we assemble the integral operators, indicating that we wish to compress them using hierarchical matrices:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"using HMatrices\nop_3d = Inti.Helmholtz(; k, dim = 3)\nS_3d, D_3d = Inti.single_double_layer(;\n op = op_3d,\n target = Q_3d,\n source = Q_3d,\n compression = (method = :hmatrix, tol = 1e-4),\n correction = (method = :dim,),\n)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Here is how much memory it would take to store the dense representation of these matrices:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"mem = 2 * length(S_3d) * 16 / 1e9 # 16 bytes per complex number, 1e9 bytes per GB, two matrices\nprintln(\"memory required to store S and D: $(mem) GB\")","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Even for this simple example, the dense representation of the integral operators as matrix is already quite expensive!","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"note: Compression methods\nIt is worth mentioning that hierchical matrices are not the only way to compress such integral operators, and may in fact not even be the best for the problem at hand. For example, one could use a fast multipole method (FMM), which has a much lighter memory footprint. See the the tutorial on compression methods for more information.","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We will use the generalized minimal residual (GMRES) iterative solver, for the linear system. This requires us to define a linear operator L, approximating the combined-field operator, that supports the matrix-vector product. While it is possible to add two HMatrix objects to obtain a new HMatrix, this is somewhat more involved due to the addition of low-rank blocks (which requires a recompression). To keep things simple, we will use LinearMaps to lazily compose the operators:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"L_3d = I / 2 + LinearMap(D_3d) - im * k * LinearMap(S_3d)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We can now solve the linear system using GMRES solver:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"rhs_3d = map(Q_3d) do q\n x = q.coords\n return -uᵢ(x)\nend\nσ_3d, hist = gmres(\n L_3d,\n rhs_3d;\n log = true,\n abstol = 1e-6,\n verbose = false,\n restart = 100,\n maxiter = 100,\n)\n@show hist","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"As before, let us represent the solution using IntegralPotentials:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"𝒮_3d, 𝒟_3d = Inti.single_double_layer_potential(; op = op_3d, source = Q_3d)\nuₛ_3d = x -> 𝒟_3d[σ_3d](x) - im * k * 𝒮_3d[σ_3d](x)\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"To check the result, we compare against the exact solution obtained through a series:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"sphbesselj(l, r) = sqrt(π / (2r)) * besselj(l + 1 / 2, r)\nsphbesselh(l, r) = sqrt(π / (2r)) * besselh(l + 1 / 2, r)\nsphharmonic(l, m, θ, ϕ) = GSL.sf_legendre_sphPlm(l, abs(m), cos(θ)) * exp(im * m * ϕ)\nfunction sphere_helmholtz_soundsoft(xobs; radius = 1, k = 1, θin = 0, ϕin = 0)\n x = xobs[1]\n y = xobs[2]\n z = xobs[3]\n r = sqrt(x^2 + y^2 + z^2)\n θ = acos(z / r)\n ϕ = atan(y, x)\n u = 0.0\n r < radius && return u\n function c(l, m)\n return -4π * im^l * sphharmonic(l, -m, θin, ϕin) * sphbesselj(l, k * radius) /\n sphbesselh(l, k * radius)\n end\n l = 0\n for l = 0:60\n for m = -l:l\n u += c(l, m) * sphbesselh(l, k * r) * sphharmonic(l, m, θ, ϕ)\n end\n l += 1\n end\n return u\nend\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We will compute the error on some point on the sphere of radius 2:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"uₑ_3d = (x) -> sphere_helmholtz_soundsoft(x; radius = 1, k = k, θin = π / 2, ϕin = 0)\ner_3d = maximum(1:100) do _\n x̂ = rand(Inti.Point3D) |> normalize # an SVector of unit norm\n x = 2 * x̂\n return abs(uₛ_3d(x) - uₑ_3d(x))\nend\n@info \"error with correction = $er_3d\"","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"@assert er_3d < 1e-3","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"We see that, once again, the approximation is quite accurate. Let us now visualize the solution on the punctured plane (which we labeled as \"sigma\"). Since evaluating the integral representation of the solution at many points is expensive, we will use again use a method to accelerate the evaluation:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Σ_msh = view(msh_3d, Σ_3d)\ntarget = Inti.nodes(Σ_msh)\n\nS_viz, D_viz = Inti.single_double_layer(;\n op = op_3d,\n target,\n source = Q_3d,\n compression = (method = :hmatrix, tol = 1e-4),\n # correction for the nearfield (for visual purposes, set to `:none` to disable)\n correction = (method = :dim, maxdist = meshsize, target_location = :outside),\n)\n\nui_eval_msh = uᵢ.(target)\nus_eval_msh = D_viz * σ_3d - im * k * S_viz * σ_3d\nu_eval_msh = ui_eval_msh + us_eval_msh\nnothing #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"Finalize, we use Meshes.viz to visualize the scattered field:","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"nv = length(Inti.nodes(Γ_msh_3d))\ncolorrange = extrema(real(u_eval_msh))\ncolormap = :inferno\nfig_3d = Figure(; size = (800, 500))\nax_3d = Axis3(fig_3d[1, 1]; aspect = :data)\nviz!(Γ_msh_3d; colorrange, colormap, color = zeros(nv), interpolate = true)\nviz!(Σ_msh; colorrange, colormap, color = real(u_eval_msh))\ncb = Colorbar(fig_3d[1, 2]; label = \"real(u)\", colormap, colorrange)\nfig_3d #hide","category":"page"},{"location":"pluto-examples/helmholtz_scattering/","page":"Helmholtz scattering","title":"Helmholtz scattering","text":"TableOfContents()","category":"page"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"(Image: Pluto notebook)","category":"page"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"begin\n import Pkg as _Pkg\n haskey(ENV, \"PLUTO_PROJECT\") && _Pkg.activate(ENV[\"PLUTO_PROJECT\"])\nend;","category":"page"},{"location":"pluto-examples/toy_example/#Toy-example","page":"Toy example","title":"Toy example","text":"","category":"section"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"All examples in Inti.jl are autogenerated by executing the make.jl script in the docs folder. The workflow uses Pluto.jl to generate markdown files passed to Documenter.jl. The original pluto notebook files are downloadable from the example's page.","category":"page"},{"location":"pluto-examples/toy_example/","page":"Toy example","title":"Toy example","text":"using Inti","category":"page"},{"location":"docstrings/#Docstrings","page":"Docstrings","title":"Docstrings","text":"","category":"section"},{"location":"docstrings/","page":"Docstrings","title":"Docstrings","text":"CurrentModule = Inti","category":"page"},{"location":"docstrings/","page":"Docstrings","title":"Docstrings","text":"Modules = [Inti]","category":"page"},{"location":"docstrings/#Inti.Inti","page":"Docstrings","title":"Inti.Inti","text":"module Inti\n\nLibrary for solving integral equations using Nyström methods.\n\n\n\n\n\n","category":"module"},{"location":"docstrings/#Inti.COMPRESSION_METHODS","page":"Docstrings","title":"Inti.COMPRESSION_METHODS","text":"const COMPRESSION_METHODS = [:none, :hmatrix, :fmm]\n\nAvailable compression methods for the dense linear operators in Inti.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.CORRECTION_METHODS","page":"Docstrings","title":"Inti.CORRECTION_METHODS","text":"const CORRECTION_METHODS = [:none, :dim, :hcubature]\n\nAvailable correction methods for the singular and nearly-singular integrals in Inti.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.ENTITIES","page":"Docstrings","title":"Inti.ENTITIES","text":"const ENTITIES\n\nDictionary mapping EntityKey to GeometricEntity. Contains all entities created in a given session.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.SAME_POINT_TOLERANCE","page":"Docstrings","title":"Inti.SAME_POINT_TOLERANCE","text":"SAME_POINTS_TOLERANCE\n\nTwo points x and y are considerd the same if norm(x-y) ≤ SAME_POINT_TOLERANCE.\n\n\n\n\n\n","category":"constant"},{"location":"docstrings/#Inti.AbstractDifferentialOperator","page":"Docstrings","title":"Inti.AbstractDifferentialOperator","text":"abstract type AbstractDifferentialOperator{N}\n\nA partial differential operator in dimension N.\n\nAbstractDifferentialOperator types are used to define AbstractKernels related to fundamental solutions of differential operators.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.AbstractKernel","page":"Docstrings","title":"Inti.AbstractKernel","text":"abstract type AbstractKernel{T}\n\nA kernel functions K with the signature K(target,source)::T.\n\nSee also: SingleLayerKernel, DoubleLayerKernel, AdjointDoubleLayerKernel, HyperSingularKernel\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.AbstractMesh","page":"Docstrings","title":"Inti.AbstractMesh","text":"abstract type AbstractMesh{N,T}\n\nAn abstract mesh structure in dimension N with primite data of type T (e.g. Float64 for double precision representation).\n\nConcrete subtypes of AbstractMesh should implement ElementIterator for accessing the mesh elements.\n\nSee also: Mesh\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.AdjointDoubleLayerKernel","page":"Docstrings","title":"Inti.AdjointDoubleLayerKernel","text":"struct AdjointDoubleLayerKernel{T,Op} <: AbstractKernel{T}\n\nGiven an operator Op, construct its free-space adjoint double-layer kernel. This corresponds to the transpose(γ₁,ₓ[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the target variable.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.BlockArray","page":"Docstrings","title":"Inti.BlockArray","text":"struct BlockArray{T<:StaticArray,N,S} <: AbstractMatrix{T,N}\n\nA struct which behaves like an Array{T,N}, but with the underlying data stored as a Matrix{S}, where S::Number = eltype(T) is the scalar type associated with T. This allows for the use of blas routines under-the-hood, while providing a convenient interface for handling matrices over StaticArrays.\n\nusing StaticArrays\nT = SMatrix{2,2,Int,4}\nB = Inti.BlockArray{T}([i*j for i in 1:4, j in 1:4])\n\n# output\n\n2×2 Inti.BlockArray{SMatrix{2, 2, Int64, 4}, 2, Int64}:\n [1 2; 2 4] [3 4; 6 8]\n [3 6; 4 8] [9 12; 12 16]\n\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.DimParameters","page":"Docstrings","title":"Inti.DimParameters","text":"struct DimParameters\n\nParameters associated with the density interpolation method used in bdim_correction.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Domain","page":"Docstrings","title":"Inti.Domain","text":"struct Domain\n\nRepresentation of a geometrical domain formed by a set of entities with the same geometric dimension. For basic set operations on domains are supported (union, intersection, difference, etc), and they all return a new Domain object.\n\nCalling keys(Ω) returns the set of EntityKeys that make up the domain; given a key, the underlying entities can be accessed with global_get_entity(key).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Domain-Tuple{Function, Any}","page":"Docstrings","title":"Inti.Domain","text":"Domain([f::Function,] keys)\n\nCreate a domain from a set of EntityKeys. Optionally, a filter function f can be passed to filter the entities.\n\nNote that all entities in a domain must have the same geometric dimension.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Domain-Tuple{Function, Inti.AbstractMesh}","page":"Docstrings","title":"Inti.Domain","text":"Domain(f::Function, msh::AbstractMesh)\n\nCall Domain(f, ents) on ents = entities(msh).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.DoubleLayerKernel","page":"Docstrings","title":"Inti.DoubleLayerKernel","text":"struct DoubleLayerKernel{T,Op} <: AbstractKernel{T}\n\nGiven an operator Op, construct its free-space double-layer kernel. This corresponds to the γ₁ trace of the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative of the fundamental solution respect to the source variable.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Elastostatic","page":"Docstrings","title":"Inti.Elastostatic","text":"struct Elastostatic{N,T} <: AbstractDifferentialOperator{N}\n\nElastostatic operator in N dimensions: -μΔu - (μ+λ)∇(∇⋅u)\n\nNote that the displacement u is a vector of length N since this is a vectorial problem.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ElementIterator","page":"Docstrings","title":"Inti.ElementIterator","text":"struct ElementIterator{E,M} <: AbstractVector{E}\n\nStructure to lazily access elements of type E in a mesh of type M. This is particularly useful for LagrangeElements, where the information to reconstruct the element is stored in the mesh connectivity matrix.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.EmbeddedQuadrature","page":"Docstrings","title":"Inti.EmbeddedQuadrature","text":"struct EmbeddedQuadrature{L,H,D} <: ReferenceQuadrature{D}\n\nA quadrature rule for the reference shape D based on a high-order quadrature of type H and a low-order quadrature of type L. The low-order quadrature rule is embedded in the sense that its n nodes are exactly the first n nodes of the high-order quadrature rule.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.EntityKey","page":"Docstrings","title":"Inti.EntityKey","text":"EntityKey\n\nUsed to represent the key of a GeometricEntity, comprised of a dim and a tag field, where dim is the geometrical dimension of the entity, and tag is a unique integer identifying the entity.\n\nThe sign of the tag field is used to distinguish the orientation of the entity, and is ignored when comparing two EntityKeys for equality.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Fejer","page":"Docstrings","title":"Inti.Fejer","text":"struct Fejer{N}\n\nN-point Fejer's first quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ N-1.\n\nusing Inti\n\nq = Inti.Fejer(;order=10)\n\nInti.integrate(cos,q) ≈ sin(1) - sin(0)\n\n# output\n\ntrue\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Gauss","page":"Docstrings","title":"Inti.Gauss","text":"struct Gauss{D,N} <: ReferenceQuadrature{D}\n\nTabulated N-point symmetric Gauss quadrature rule for integration over D.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.GaussLegendre","page":"Docstrings","title":"Inti.GaussLegendre","text":"struct GaussLegendre{N,T}\n\nN-point Gauss-Legendre quadrature rule for integrating a function over [0,1]. Exactly integrates all polynomials of degree ≤ 2N-1.\n\nusing Inti\n\nq = Inti.GaussLegendre(;order=10)\n\nInti.integrate(cos,q) ≈ sin(1) - sin(0)\n\n# output\n\ntrue\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.GeometricEntity","page":"Docstrings","title":"Inti.GeometricEntity","text":"struct GeometricEntity\n\nGeometrical objects such as lines, surfaces, and volumes.\n\nGeometrical entities are stored in a global ENTITIES dictionary mapping EntityKey to the corresponding GeometricEntity, and usually entities are manipulated through their keys.\n\nA GeometricEntity can also contain a pushforward field used to parametrically represent the entry as the image of a reference domain (pushforward.domain) under some function (pushforward.parametrization).\n\nNote that entities are manipulated through their keys, and the GeometricEntity constructor returns the key of the created entity; to retrieve the entity, use the global_get_entity function.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.GeometricEntity-Tuple{String}","page":"Docstrings","title":"Inti.GeometricEntity","text":"GeometricEntity(shape::String [; translation, rotation, scaling, kwargs...])\n\nConstructs a geometric entity with the specified shape and optional parameters, and returns its key.\n\nArguments\n\nshape::String: The shape of the geometric entity.\ntranslation: The translation vector of the geometric entity. Default is SVector(0, 0, 0).\nrotation: The rotation vector of the geometric entity. Default is SVector(0, 0, 0).\nscaling: The scaling vector of the geometric entity. Default is SVector(1, 1, 1).\nkwargs...: Additional keyword arguments to be passed to the shape constructor.\n\nSupported shapes\n\nellipsoid\ntorus\nbean\nacorn\ncushion\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Helmholtz-Tuple{}","page":"Docstrings","title":"Inti.Helmholtz","text":"Helmholtz(; k, dim)\n\nHelmholtz operator in dim dimensions: -Δu - k²u.\n\nThe parameter k can be a real or complex number. For purely imaginary wavenumbers, consider using the Yukawa kernel.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.HyperRectangle","page":"Docstrings","title":"Inti.HyperRectangle","text":"struct HyperRectangle{N,T} <: ReferenceInterpolant{ReferenceHyperCube{N},T}\n\nAxis-aligned hyperrectangle in N dimensions given by low_corner::SVector{N,T} and high_corner::SVector{N,T}.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.HyperSingularKernel","page":"Docstrings","title":"Inti.HyperSingularKernel","text":"struct HyperSingularKernel{T,Op} <: AbstractKernel{T}\n\nGiven an operator Op, construct its free-space hypersingular kernel. This corresponds to the transpose(γ₁,ₓγ₁[G]), where G is the SingleLayerKernel. For operators such as Laplace or Helmholtz, this is simply the normal derivative respect to the target variable of the DoubleLayerKernel.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.IntegralOperator","page":"Docstrings","title":"Inti.IntegralOperator","text":"struct IntegralOperator{T} <: AbstractMatrix{T}\n\nA discrete linear integral operator given by\n\nIu(x) = int_Gamma_s K(xy)u(y) ds_y x in Gamma_t\n\nwhere Gamma_s and Gamma_t are the source and target domains, respectively.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.IntegralPotential","page":"Docstrings","title":"Inti.IntegralPotential","text":"struct IntegralPotential\n\nRepresent a potential given by a kernel and a quadrature over which integration is performed.\n\nIntegralPotentials are created using IntegralPotential(kernel, quadrature).\n\nEvaluating an integral potential requires a density σ (defined over the quadrature nodes of the source mesh) and a point x at which to evaluate the integral\n\nint_Gamma K(oldsymbolxoldsymboly)sigma(y) ds_y x not in Gamma\n\nAssuming 𝒮 is an integral potential and σ is a vector of values defined on quadrature, calling 𝒮[σ] creates an anonymous function that can be evaluated at any point x.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Kronrod","page":"Docstrings","title":"Inti.Kronrod","text":"struct Kronrod{D,N} <: ReferenceQuadrature{D}\n\nN-point Kronrod rule obtained by adding n+1 points to a Gauss quadrature containing n points. The order is either 3n + 1 for n even or 3n + 2 for n odd.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeCube","page":"Docstrings","title":"Inti.LagrangeCube","text":"const LagrangeSquare = LagrangeElement{ReferenceSquare}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeElement","page":"Docstrings","title":"Inti.LagrangeElement","text":"struct LagrangeElement{D,Np,T} <: ReferenceInterpolant{D,T}\n\nA polynomial p : D → T uniquely defined by its Np values on the Np reference nodes of D.\n\nThe return type T should be a vector space (i.e. support addition and multiplication by scalars). For istance, T could be a number or a vector, but not a Tuple.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeLine","page":"Docstrings","title":"Inti.LagrangeLine","text":"const LagrangeLine = LagrangeElement{ReferenceLine}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeSquare","page":"Docstrings","title":"Inti.LagrangeSquare","text":"const LagrangeSquare = LagrangeElement{ReferenceSquare}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeTetrahedron","page":"Docstrings","title":"Inti.LagrangeTetrahedron","text":"const LagrangeTetrahedron = LagrangeElement{ReferenceTetrahedron}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.LagrangeTriangle","page":"Docstrings","title":"Inti.LagrangeTriangle","text":"const LagrangeTriangle = LagrangeElement{ReferenceTriangle}\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Laplace-Tuple{}","page":"Docstrings","title":"Inti.Laplace","text":"Laplace(; dim)\n\nLaplace's differential operator in dim dimension: -Δu. ```\n\nNote the negative sign in the definition.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Mesh","page":"Docstrings","title":"Inti.Mesh","text":"struct Mesh{N,T} <: AbstractMesh{N,T}\n\nUnstructured mesh defined by a set of nodes(of typeSVector{N,T}`), and a dictionary mapping element types to connectivity matrices. Each columns of a given connectivity matrix stores the integer tags of the nodes in the mesh comprising the element.\n\nAdditionally, the mesh contains a mapping from EntityKeys to the tags of the elements composing the entity. This can be used to extract submeshes from a given mesh using e.g. view(msh,Γ) or msh[Γ], where Γ is a Domain.\n\nSee elements for a way to iterate over the elements of a mesh.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ModifiedHelmholtz","page":"Docstrings","title":"Inti.ModifiedHelmholtz","text":"const ModifiedHelmholtz\n\nType alias for the Yukawa operator.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.MultiIndex","page":"Docstrings","title":"Inti.MultiIndex","text":"MultiIndex{N}\n\nWrapper around NTuple{N,Int} mimicking a multi-index in ℤ₀ᴺ.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ParametricElement","page":"Docstrings","title":"Inti.ParametricElement","text":"ParametricElement{D,T,F} <: ReferenceInterpolant{D,T}\n\nAn element represented through a explicit function f mapping D into the element. For performance reasons, f should take as input a StaticVector and return a StaticVector or StaticArray.\n\nSee also: ReferenceInterpolant, LagrangeElement\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ParametricElement-Union{Tuple{T}, Tuple{N}, Tuple{Any, Inti.HyperRectangle{N, T}}} where {N, T}","page":"Docstrings","title":"Inti.ParametricElement","text":"ParametricElement(f, d::HyperRectangle)\n\nConstruct the element defined as the image of f over d.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.PolynomialSpace","page":"Docstrings","title":"Inti.PolynomialSpace","text":"struct PolynomialSpace{D,K}\n\nThe space of all polynomials of degree ≤K, commonly referred to as ℙₖ.\n\nThe type parameter D, of singleton type, is used to determine the reference domain of the polynomial basis. In particular, when D is a hypercube in d dimensions, the precise definition is ℙₖ = span{𝐱ᶿ : 0≤max(θ)≤ K}; when D is a d-dimensional simplex, the space is ℙₖ = span{𝐱ᶿ : 0≤sum(θ)≤ K}, where θ ∈ 𝐍ᵈ is a multi-index.\n\nSee also: monomial_basis, lagrange_basis\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Quadrature","page":"Docstrings","title":"Inti.Quadrature","text":"struct Quadrature{N,T} <: AbstractVector{QuadratureNode{N,T}}\n\nA collection of QuadratureNodes used to integrate over an AbstractMesh.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Quadrature-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.Quadrature","text":"Quadrature(Ω::Domain; meshsize, qorder)\n\nConstruct a Quadrature over the domain Ω with a mesh of size meshsize and quadrature order qorder.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.Quadrature-Union{Tuple{T}, Tuple{N}, Tuple{Inti.AbstractMesh{N, T}, Dict}} where {N, T}","page":"Docstrings","title":"Inti.Quadrature","text":"Quadrature(msh::AbstractMesh, etype2qrule::Dict)\nQuadrature(msh::AbstractMesh, qrule::ReferenceQuadrature)\nQuadrature(msh::AbstractMesh; qorder)\n\nConstruct a Quadrature for msh, where for each element type E in msh the reference quadrature q = etype2qrule[E] is used. When a single qrule is passed, it is used for all element types in msh.\n\nIf an order keyword is passed, a default quadrature of the desired order is used for each element type usig _qrule_for_reference_shape.\n\nFor co-dimension one elements, the normal vector is also computed and stored in the QuadratureNodes.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.QuadratureNode","page":"Docstrings","title":"Inti.QuadratureNode","text":"QuadratureNode{N,T<:Real}\n\nA point in ℝᴺ with a weight for performing numerical integration. A QuadratureNode can optionally store a normal vector.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceCube","page":"Docstrings","title":"Inti.ReferenceCube","text":"const ReferenceCube = ReferenceHyperCube{3}\n\nSingleton type representing the unit cube [0,1]³.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceHyperCube","page":"Docstrings","title":"Inti.ReferenceHyperCube","text":"struct ReferenceHyperCube{N} <: ReferenceShape{N}\n\nSingleton type representing the axis-aligned hypercube in N dimensions with the lower corner at the origin and the upper corner at (1,1,…,1).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceInterpolant","page":"Docstrings","title":"Inti.ReferenceInterpolant","text":"abstract type ReferenceInterpolant{D,T}\n\nInterpolanting function mapping points on the domain D<:ReferenceShape (of singleton type) to a value of type T.\n\nInstances el of ReferenceInterpolant are expected to implement:\n\nel(x̂): evaluate the interpolation scheme at the (reference) coordinate x̂ ∈ D.\njacobian(el,x̂) : evaluate the jacobian matrix of the interpolation at the (reference) coordinate x ∈ D.\n\nnote: Note\nFor performance reasons, both el(x̂) and jacobian(el,x̂) should take as input a StaticVector and output a static vector or static array.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceLine","page":"Docstrings","title":"Inti.ReferenceLine","text":"const ReferenceLine = ReferenceHyperCube{1}\n\nSingleton type representing the [0,1] segment.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceQuadrature","page":"Docstrings","title":"Inti.ReferenceQuadrature","text":"abstract type ReferenceQuadrature{D}\n\nA quadrature rule for integrating a function over the domain D <: ReferenceShape.\n\nCalling x,w = q() returns the nodes x, given as SVectors, and weights w, for performing integration over domain(q).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceShape","page":"Docstrings","title":"Inti.ReferenceShape","text":"abstract type ReferenceShape\n\nA fixed reference domain/shape. Used mostly for defining more complex shapes as transformations mapping an ReferenceShape to some region of ℜᴹ.\n\nSee e.g. ReferenceLine or ReferenceTriangle for some examples of concrete subtypes.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceSimplex","page":"Docstrings","title":"Inti.ReferenceSimplex","text":"struct ReferenceSimplex{N}\n\nSingleton type representing the N-simplex with N+1 vertices (0,...,0),(0,...,0,1),(0,...,0,1,0),(1,0,...,0)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceSquare","page":"Docstrings","title":"Inti.ReferenceSquare","text":"const ReferenceSquare = ReferenceHyperCube{2}\n\nSingleton type representing the unit square [0,1]².\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceTetrahedron","page":"Docstrings","title":"Inti.ReferenceTetrahedron","text":"struct ReferenceTetrahedron\n\nSingleton type representing the tetrahedron with vertices (0,0,0),(0,0,1),(0,1,0),(1,0,0)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.ReferenceTriangle","page":"Docstrings","title":"Inti.ReferenceTriangle","text":"struct ReferenceTriangle\n\nSingleton type representing the triangle with vertices (0,0),(1,0),(0,1)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.SingleLayerKernel","page":"Docstrings","title":"Inti.SingleLayerKernel","text":"struct SingleLayerKernel{T,Op} <: AbstractKernel{T}\n\nThe free-space single-layer kernel (i.e. the fundamental solution) of an Op <: AbstractDifferentialOperator.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Stokes-Tuple{}","page":"Docstrings","title":"Inti.Stokes","text":"Stokes(; μ, dim)\n\nStokes operator in dim dimensions: -μΔu + p u.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.SubMesh","page":"Docstrings","title":"Inti.SubMesh","text":"struct SubMesh{N,T} <: AbstractMesh{N,T}\n\nView into a parent mesh over a given domain.\n\nA submesh implements the interface for AbstractMesh; therefore you can iterate over elements of the submesh just like you would with a mesh.\n\nConstruct SubMeshs using view(parent,Ω::Domain).\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.TensorProductQuadrature","page":"Docstrings","title":"Inti.TensorProductQuadrature","text":"TensorProductQuadrature{N,Q}\n\nA tensor-product of one-dimension quadrature rules. Integrates over [0,1]^N.\n\nExamples\n\nqx = Inti.Fejer(10)\nqy = Inti.Fejer(15)\nq = Inti.TensorProductQuadrature(qx,qy)\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.VioreanuRokhlin","page":"Docstrings","title":"Inti.VioreanuRokhlin","text":"struct VioreanuRokhlin{D,N} <: ReferenceQuadrature{D}\n\nTabulated N-point Vioreanu-Rokhlin quadrature rule for integration over D.\n\n\n\n\n\n","category":"type"},{"location":"docstrings/#Inti.Yukawa-Tuple{}","page":"Docstrings","title":"Inti.Yukawa","text":"Yukawa(; λ, dim)\n\nYukawa operator, also known as modified Helmholtz, in dim dimensions: -Δu + λ²u.\n\nThe parameter λ is a positive number. Note the negative sign in front of the Laplacian.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Base.iterate","page":"Docstrings","title":"Base.iterate","text":"iterate(Ω::Domain)\n\nIterating over a domain means iterating over its entities.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti._copyto!-Tuple{AbstractMatrix{<:Number}, AbstractMatrix{<:StaticArraysCore.SMatrix}}","page":"Docstrings","title":"Inti._copyto!","text":"_copyto!(target,source)\n\nDefaults to Base.copyto!, but includes some specialized methods to copy from a Matrix of SMatrix to a Matrix of Numbers and viceversa.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._get_gauss_qcoords_and_qweights-Tuple{Type{<:Inti.ReferenceShape}, Any}","page":"Docstrings","title":"Inti._get_gauss_qcoords_and_qweights","text":"_get_gauss_and_qweights(R::Type{<:ReferenceShape{D}}, N) where D\n\nReturns the N-point symmetric gaussian qnodes and qweights (x, w) for integration over R.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._get_vioreanurokhlin_qcoords_and_qweights-Tuple{Type{<:Inti.ReferenceShape}, Any}","page":"Docstrings","title":"Inti._get_vioreanurokhlin_qcoords_and_qweights","text":"_get_vioreanurokhlin_qcoords_and_qweights(R::Type{<:ReferenceShape{D}}, N) where D\n\nReturns the N-point Vioreanu-Rokhlin qnodes and qweights (x, w) for integration over R.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._green_multiplier-Tuple{Symbol}","page":"Docstrings","title":"Inti._green_multiplier","text":"_green_multiplier(s::Symbol)\n\nReturn -1.0 if s == :inside, 0.0 if s == :outside, and -0.5 if s == :on; otherwise, throw an error. The orientation is relative to the normal of the bounding curve/surface.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._green_multiplier-Union{Tuple{N}, Tuple{StaticArraysCore.SVector, Inti.Quadrature{N}}} where N","page":"Docstrings","title":"Inti._green_multiplier","text":"_green_multiplier(x, quad)\n\nHelper function to help determine the constant σ in the Green identity S[γ₁u](x)\n\nD[γ₀u](x) + σ*u(x) = 0. This can be used as a predicate to determine whether a\n\npoint is inside a domain or not.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._meshgen-Tuple{Any, Inti.HyperRectangle, NTuple{N, T} where {N, T}}","page":"Docstrings","title":"Inti._meshgen","text":"_meshgen(f,d::HyperRectangle,sz)\n\nCreate prod(sz) elements of ParametricElement type representing the push forward of f on each of the subdomains defined by a uniform cartesian mesh of d of size sz.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._normal-Union{Tuple{StaticArraysCore.SMatrix{N, M}}, Tuple{M}, Tuple{N}} where {N, M}","page":"Docstrings","title":"Inti._normal","text":"_normal(jac::SMatrix{M,N})\n\nGiven a an M by N matrix representing the jacobian of a codimension one object, compute the normal vector.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti._qrule_for_reference_shape-Tuple{Any, Any}","page":"Docstrings","title":"Inti._qrule_for_reference_shape","text":"_qrule_for_reference_shape(ref,order)\n\nGiven a reference shape and a desired quadrature order, return an appropiate quadrature rule.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.acorn-Tuple{}","page":"Docstrings","title":"Inti.acorn","text":"acorn(; translation, rotation, scaling, labels)\n\nCreate an acorn entity in 3D, and apply optional transformations. Returns the key.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adaptive_correction-Tuple{Inti.IntegralOperator}","page":"Docstrings","title":"Inti.adaptive_correction","text":"adaptive_correction(iop::IntegralOperator; tol, maxdist = farfield_distance(iop; atol), maxsplit = 1000])\n\nGiven an integral operator iop, this function provides a sparse correction to iop for the entries i,j such that the distance between the i-th target and the j-th source is less than maxdist.\n\nChoosing maxdist is a trade-off between accuracy and efficiency. The smaller the value, the fewer corrections are needed, but this may compromise the accuracy. For a fixed quadrature, the size of maxdist has to grow as the tolerance tol decreases. The default [farfield_distance(iop; tol)](@ref) provides a heuristic to determine a suitablemaxdist`.\n\nThe correction is computed by using the adaptive_integration routine, with a tolerance atol and a maximum number of subdivisions maxsplit; see adaptive_integration for more details.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adaptive_integration-Tuple{Any, Inti.EmbeddedQuadrature}","page":"Docstrings","title":"Inti.adaptive_integration","text":"adaptive_integration(f, τ̂::RefernceShape; kwargs...)\nadaptive_integration(f, qrule::EmbeddedQuadrature; kwargs...)\n\nUse an adaptive procedure to estimate the integral of f over τ̂ = domain(qrule). The following optional keyword arguments are available:\n\natol::Real=0.0: absolute tolerance for the integral estimate\nrtol::Real=0.0: relative tolerance for the integral estimate\nmaxsplit::Int=1000: maximum number of times to split the domain\nnorm::Function=LinearAlgebra.norm: norm to use for error estimates\nbuffer::BinaryHeap: a pre-allocated buffer to use for the adaptive procedure (see allocate_buffer)\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adaptive_integration_singular-Tuple{Any, Inti.ReferenceHyperCube{1}, Any}","page":"Docstrings","title":"Inti.adaptive_integration_singular","text":"adaptive_integration_singular(f, τ̂, x̂ₛ; kwargs...)\n\nSimilar to adaptive_integration, but indicates that f has an isolated (integrable) singularity at x̂ₛ ∈ x̂ₛ.\n\nThe integration is performed by splitting τ̂ so that x̂ₛ is a fixed vertex, guaranteeing that f is never evaluated at x̂ₛ. Aditionally, a suitable change of variables may be applied to alleviate the singularity and improve the rate of convergence.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.adj_double_layer_hypersingular-Tuple{}","page":"Docstrings","title":"Inti.adj_double_layer_hypersingular","text":"adj_double_layer_hypersingular(; op, target, source, compression,\ncorrection)\n\nSimilar to single_double_layer, but for the adjoint double-layer and hypersingular operators. See the documentation of [single_double_layer] for a description of the arguments.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.allocate_buffer-Tuple{Any, Inti.EmbeddedQuadrature}","page":"Docstrings","title":"Inti.allocate_buffer","text":"allocate_buffer(f, quad::EmbeddedQuadrature)\n\nCreate the buffer needed for the call adaptive_integration(f, τ̂; buffer, ...).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.ambient_dimension","page":"Docstrings","title":"Inti.ambient_dimension","text":"ambient_dimension(x)\n\nDimension of the ambient space where x lives. For geometrical objects this can differ from its geometric_dimension; for example a triangle in ℝ³ has ambient dimension 3 but geometric dimension 2, while a curve in ℝ³ has ambient dimension 3 but geometric dimension 1.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.assemble_fmm-Tuple{Inti.IntegralOperator, Vararg{Any}}","page":"Docstrings","title":"Inti.assemble_fmm","text":"assemble_fmm(iop; atol)\n\nSet up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.\n\nwarning: FMMLIB2D\nFMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.assemble_hmatrix-Tuple","page":"Docstrings","title":"Inti.assemble_hmatrix","text":"assemble_hmatrix(iop[; atol, rank, rtol, eta])\n\nAssemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.\n\nSee the documentation of HMatrices for more details on usage and other keyword arguments.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.assemble_matrix-Tuple{Inti.IntegralOperator}","page":"Docstrings","title":"Inti.assemble_matrix","text":"assemble_matrix(iop::IntegralOperator; threads = true)\n\nAssemble a dense matrix representation of an IntegralOperator.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.bdim_correction-Tuple{Any, Any, Inti.Quadrature, Any, Any}","page":"Docstrings","title":"Inti.bdim_correction","text":"bdim_correction(op,X,Y,S,D; green_multiplier, kwargs...)\n\nGiven a op and a (possibly innacurate) discretizations of its single and double-layer operators S and D (taking a vector of values on Y and returning a vector on of values on X), compute corrections δS and δD such that S + δS and D + δD are more accurate approximations of the underlying single- and double-layer integral operators.\n\nSee [7] for more details on the method.\n\nArguments\n\nRequired:\n\nop must be an AbstractDifferentialOperator\nY must be a Quadrature object of a closed surface\nX is either inside, outside, or on Y\nS and D are approximations to the single- and double-layer operators for op taking densities in Y and returning densities in X.\ngreen_multiplier (keyword argument) is a vector with the same length as X storing the value of μ(x) for x ∈ X in the Green identity S\\[γ₁u\\](x) - D\\[γ₀u\\](x) + μ*u(x) = 0. See _green_multiplier.\n\nOptional kwargs:\n\nparameters::DimParameters: parameters associated with the density interpolation method\nderivative: if true, compute the correction to the adjoint double-layer and hypersingular operators instead. In this case, S and D should be replaced by a (possibly innacurate) discretization of adjoint double-layer and hypersingular operators, respectively.\nmaxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections when X and Y are different surfaces. When X === Y, this is not needed.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.bean-Tuple{}","page":"Docstrings","title":"Inti.bean","text":"bean(; translation, rotation, scaling, labels)\n\nCreate a bean entity in 3D, and apply optional transformations. Returns the key.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.boundary-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.boundary","text":"boundary(Ω::Domain)\n\nReturn the external boundaries of a domain.\n\nSee also: external_boundary, internal_boundary, skeleton.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.boundary_idxs-Tuple{Inti.LagrangeElement{Inti.ReferenceHyperCube{1}}}","page":"Docstrings","title":"Inti.boundary_idxs","text":"boundary_idxs(el::LagrangeElement)\n\nThe indices of the nodes in el that define the boundary of the element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.cart2sph-Tuple{Any, Any, Any}","page":"Docstrings","title":"Inti.cart2sph","text":"cart2sph(x,y,z)\n\nMap cartesian coordinates x,y,z to spherical ones r, θ, φ representing the radius, elevation, and azimuthal angle respectively. The convention followed is that 0 ≤ θ ≤ π and -π < φ ≤ π. Same as the cart2sph function in MATLAB.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.connectivity-Tuple{Inti.Mesh, DataType}","page":"Docstrings","title":"Inti.connectivity","text":"connectivity(msh::AbstractMesh,E::DataType)\n\nReturn the connectivity matrix for elements of type E in msh. The integer tags in the matrix refer to the points in nodes(msh)\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.coords-Tuple{T} where T","page":"Docstrings","title":"Inti.coords","text":"coords(q)\n\nReturn the spatial coordinates of q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.cushion-Tuple{}","page":"Docstrings","title":"Inti.cushion","text":"cushion(; translation, rotation, scaling, labels)\n\nCreate a cushion entity in 3D, and apply optional transformations. Returns the key.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.decompose","page":"Docstrings","title":"Inti.decompose","text":"decompose(s::ReferenceShape,x)\n\nDecompose an ReferenceShape into LagrangeElements so that x is a fixed vertex of the children elements.\n\nThe decomposed elements may be oriented differently than the parent, and thus care has to be taken regarding e.g. normal vectors.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.degree-Union{Tuple{Type{<:Inti.LagrangeElement{D, Np}}}, Tuple{Np}, Tuple{D}} where {D, Np}","page":"Docstrings","title":"Inti.degree","text":"degree(el::LagrangeElement)\ndegree(el::Type{<:LagrangeElement})\n\nThe polynomial degree el.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.dimension-Union{Tuple{Type{Inti.PolynomialSpace{D, K}}}, Tuple{K}, Tuple{D}} where {D, K}","page":"Docstrings","title":"Inti.dimension","text":"dimension(space)\n\nThe length of a basis for space; i.e. the number of linearly independent elements required to span space.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.dom2elt-Tuple{Inti.AbstractMesh, Inti.Domain, DataType}","page":"Docstrings","title":"Inti.dom2elt","text":"dom2elt(m::Mesh,Ω,E)::Vector{Int}\n\nCompute the element indices idxs of the elements of type E composing Ω.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.dom2qtags-Tuple{Inti.Quadrature, Inti.Domain}","page":"Docstrings","title":"Inti.dom2qtags","text":"dom2qtags(Q::Quadrature, dom::Domain)\n\nGiven a domain, return the indices of the quadratures nodes in Q associated to its quadrature.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.domain","page":"Docstrings","title":"Inti.domain","text":"domain(f)\n\nGiven a function-like object f: Ω → R, return Ω.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.domain-Tuple{Inti.AbstractMesh}","page":"Docstrings","title":"Inti.domain","text":"domain(msh::AbstractMesh)\n\nReturn a [Domain] containing of all entities covered by the mesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.domain-Tuple{Inti.Quadrature}","page":"Docstrings","title":"Inti.domain","text":"domain(Q::Quadrature)\n\nThe Domain over which Q performs integration.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.domain-Union{Tuple{Inti.ReferenceQuadrature{D}}, Tuple{D}} where D","page":"Docstrings","title":"Inti.domain","text":"domain(q::ReferenceQuadrature)\n\nThe domain of integratino for quadrature rule q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.element_types","page":"Docstrings","title":"Inti.element_types","text":"element_types(msh::AbstractMesh)\n\nReturn the element types present in the msh.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.elements","page":"Docstrings","title":"Inti.elements","text":"elements(msh::AbstractMesh [, E::DataType])\n\nReturn the elements of a msh. Passing and element type E will restricts to elements of that type.\n\nA common pattern to avoid type-instabilies in performance critical parts of the code is to use a function barrier, as illustrated below:\n\nfor E in element_types(msh)\n _long_computation(elements(msh, E), args...)\nend\n\n@noinline function _long_computation(iter, args...)\n for el in iter # the type of el is known at compile time\n # do something with el\n end\nend\n\nwhere a dynamic dispatch is performed only on the element types (typically small for a given mesh).\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.ellipsoid-Tuple{}","page":"Docstrings","title":"Inti.ellipsoid","text":"ellipsoid(; translation, rotation, scaling, labels)\n\nCreate an ellipsoid entity in 3D, and apply optional transformations. Returns the key of the created entity.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.ent2etags-Tuple{Inti.Mesh}","page":"Docstrings","title":"Inti.ent2etags","text":"ent2etags(msh::AbstractMesh)\n\nReturn a dictionary mapping entities to a dictionary of element types to element tags.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.entities-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.entities","text":"entities(Ω::Domain)\n\nReturn all entities making up a domain (as a set of EntityKeys).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.etype_to_nearest_points-Tuple{Any, Inti.Quadrature}","page":"Docstrings","title":"Inti.etype_to_nearest_points","text":"etype_to_nearest_points(X,Y::Quadrature; maxdist)\n\nFor each element el in Y.mesh, return a list with the indices of all points in X for which el is the nearest element. Ignore indices for which the distance exceeds maxdist.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.external_boundary-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.external_boundary","text":"external_boundary(Ω::Domain)\n\nReturn the external boundaries inside a domain. These are entities in the skeleton of Ω which are not in the internal boundaries of Ω.\n\nSee also: internal_boundary, skeleton.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.farfield_distance-Tuple{Inti.IntegralOperator}","page":"Docstrings","title":"Inti.farfield_distance","text":"farfield_distance(iop::IntegralOperator; tol, maxiter = 10)\nfarfield_distance(K, Q::Quadrature; tol, maxiter = 10)\n\nReturn an estimate of the distance d such that the (absolute) quadrature error of the integrand y -> K(x,y) is below tol for x at a distance d from the center of the largest element in Q; when an integral operator is passed, we have Q::Quadrature = source(iop) and K = kernel(iop).\n\nThe estimate is computed by finding the first integer n such that the quadrature error on the largest element τ lies below tol for points x satisfying dist(x,center(τ)) = n*radius(τ).\n\nNote that the desired tolerance may not be achievable if the quadrature rule is not accurate enough, or if τ is not sufficiently small, and therefore a maximum number of iterations maxiter is provided to avoid an infinite loops. In such cases, it is recommended that you either increase the quadrature order, or decrease the mesh size.\n\nNote: this is obviously a heuristic, and may not be accurate in all cases.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.fibonnaci_points_sphere-Tuple{Any, Any, Any}","page":"Docstrings","title":"Inti.fibonnaci_points_sphere","text":"fibonnaci_points_sphere(N,r,c)\n\nReturn N points distributed (roughly) in a uniform manner on the sphere of radius r centered at c.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.flip_normal-Tuple{Inti.QuadratureNode}","page":"Docstrings","title":"Inti.flip_normal","text":"flip_normal(q::QuadratureNode)\n\nReturn a new QuadratureNode with the normal vector flipped.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.gauss_curvature-Tuple{Inti.Quadrature}","page":"Docstrings","title":"Inti.gauss_curvature","text":"gauss_curvature(Q::Quadrature)\n\nCompute the gauss_curvature at each quadrature node in Q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.gauss_curvature-Tuple{Inti.ReferenceInterpolant, Any}","page":"Docstrings","title":"Inti.gauss_curvature","text":"gauss_curvature(τ, x̂)\n\nCalculate the Gaussian curvature of the element τ at the parametric coordinate x̂.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.geometric_dimension","page":"Docstrings","title":"Inti.geometric_dimension","text":"geometric_dimension(x)\n\nNNumber of degrees of freedom necessary to locally represent the geometrical object. For example, lines have geometric dimension of 1 (whether in ℝ² or in ℝ³), while surfaces have geometric dimension of 2.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.global_get_entity-Tuple{Inti.EntityKey}","page":"Docstrings","title":"Inti.global_get_entity","text":"global_get_entity(k::EntityKey)\n\nRetrieve the GeometricEntity corresponding to the EntityKey k from the global ENTITIES dictionary.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.gmsh_curve-Tuple","page":"Docstrings","title":"Inti.gmsh_curve","text":"gmsh_curve(f::Function, a, b; npts=100, meshsize = 0, tag=-1)\n\nCreate a curve in the current gmsh model given by {f(t) : t ∈ (a,b) } where f is a function from ℝ to ℝ^3. The curve is approximated by C² b-splines passing through npts equispaced in parameter space. If a meshsize is given, gmsh will use it when meshing the curve.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.hessian-Tuple{Inti.ReferenceInterpolant, Any}","page":"Docstrings","title":"Inti.hessian","text":"hesssian(el,x)\n\nGiven a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m × m matrix Aᵢⱼⱼ = ∂²fᵢ/∂xⱼ∂xⱼ. By default ForwardDiff is used to compute the hessian, but you should overload this method for specific f if better performance and/or precision is required.\n\nNote: both x and f(x) are expected to be of SVector type.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.image","page":"Docstrings","title":"Inti.image","text":"image(f)\n\nGiven a function-like object f: Ω → R, return f(Ω).\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.import_mesh-Tuple","page":"Docstrings","title":"Inti.import_mesh","text":"import_mesh(filename = nothing; dim=3)\n\nOpen filename and create a Mesh from the gmsh model in it.\n\nIf filename is nothing, the current gmsh model is used. Note that this assumes that the Gmsh API has been initialized through gmsh.initialize.\n\nPassing dim=2 will create a two-dimensional mesh by projecting the original mesh onto the x,y plane.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.integrate-Tuple{Any, Inti.Quadrature}","page":"Docstrings","title":"Inti.integrate","text":"integrate(f,quad::Quadrature)\n\nCompute ∑ᵢ f(qᵢ)wᵢ, where the qᵢ are the quadrature nodes of quad, and wᵢ are the quadrature weights.\n\nNote that you must define f(::QuadratureNode): use q.coords and q.normal if you need to access the coordinate or normal vector at que quadrature node.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.integrate-Tuple{Any, Inti.ReferenceQuadrature}","page":"Docstrings","title":"Inti.integrate","text":"integrate(f,q::ReferenceQuadrature)\nintegrate(f,x,w)\n\nIntegrate the function f using the quadrature rule q. This is simply sum(f.(x) .* w), where x and w are the quadrature nodes and weights, respectively.\n\nThe function f should take an SVector as input.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.integrate_with_error_estimate","page":"Docstrings","title":"Inti.integrate_with_error_estimate","text":"integrate_with_error_estimate(f, quad::EmbeddedQuadrature, norm = LinearAlgebra.norm)\n\nReturn I, E where I is the estimated integral of f over domain(quad) using the high-order quadrature and E is the error estimate obtained by taking the norm of the difference between the high and low-order quadratures in quad.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.integration_measure-Tuple{Any, Any}","page":"Docstrings","title":"Inti.integration_measure","text":"integration_measure(f, x̂)\n\nGiven the Jacobian matrix J of a transformation f : ℝᴹ → ℝᴺ compute the integration measure √det(JᵀJ) at the parametric coordinate x̂\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.interface_method-Tuple{DataType}","page":"Docstrings","title":"Inti.interface_method","text":"interface_method(x)\n\nA method of an abstract type for which concrete subtypes are expected to provide an implementation.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.internal_boundary-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.internal_boundary","text":"internal_boundary(Ω::Domain)\n\nReturn the internal boundaries of a Domain. These are entities in skeleton(Ω) which appear at least twice as a boundary of entities in Ω.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.interpolation_order-Tuple{Inti.ReferenceQuadrature{Inti.ReferenceHyperCube{1}}}","page":"Docstrings","title":"Inti.interpolation_order","text":"interpolation_order(qrule::ReferenceQuadrature)\n\nThe interpolation order of a quadrature rule is defined as the the smallest k such that there exists a unique polynomial in PolynomialSpace{D,k} that minimizes the error in approximating the function f at the quadrature nodes.\n\nFor an N-point Gauss quadrature rule on the segment, the interpolation order is N-1 since N points uniquely determine a polynomial of degree N-1.\n\nFor a triangular reference domain, the interpolation order is more difficult to define. An unisolvent three-node quadrature on the triangular, for example, has an interpolation order k=1 since the three nodes uniquely determine a linear polynomial, but a four-node quadrature may also have an interpolation order k=1 since for k=2 there are multiple polynomials that pass through the four nodes.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.jacobian-Tuple{Any, Any}","page":"Docstrings","title":"Inti.jacobian","text":"jacobian(f,x)\n\nGiven a (possibly vector-valued) functor f : 𝐑ᵐ → 𝐅ⁿ, return the n × m matrix Aᵢⱼ = ∂fᵢ/∂xⱼ. By default ForwardDiff is used to compute the jacobian, but you should overload this method for specific f if better performance and/or precision is required.\n\nNote: both x and f(x) are expected to be of SVector type.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.kress_change_of_variables-Tuple{Any}","page":"Docstrings","title":"Inti.kress_change_of_variables","text":"kress_change_of_variables(P)\n\nReturn a change of variables mapping [0,1] to [0,1] with the property that the first P-1 derivatives of the transformation vanish at x=0.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.kress_change_of_variables_periodic-Tuple{Any}","page":"Docstrings","title":"Inti.kress_change_of_variables_periodic","text":"kress_change_of_variables_periodic(P)\n\nLike kress_change_of_variables, this change of variables maps the interval [0,1] onto itself, but the first P derivatives of the transformation vanish at both endpoints (thus making it a periodic function).\n\nThis change of variables can be used to periodize integrals over the interval [0,1] by mapping the integrand into a new integrand that vanishes (to order P) at both endpoints.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.lagrange_basis-Tuple{Any, Inti.PolynomialSpace}","page":"Docstrings","title":"Inti.lagrange_basis","text":"lagrange_basis(nodes,[sp::AbstractPolynomialSpace])\n\nReturn the set of n polynomials in sp taking the value of 1 on node i and 0 on nodes j ≂̸ i for 1 ≤ i ≤ n.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.lagrange_basis-Union{Tuple{Inti.ReferenceQuadrature{D}}, Tuple{D}} where D","page":"Docstrings","title":"Inti.lagrange_basis","text":"lagrange_basis(qrule::ReferenceQuadrature)\n\nReturn a function L : ℝᴺ → ℝᵖ where N is the dimension of the domain of qrule, and p is the number of nodes in qrule. The function L is a polynomial in polynomial_space(qrule), and L(xⱼ)[i] = δᵢⱼ (i.e. the ith component of L is the ith Lagrange basis).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.lagrange_basis-Union{Tuple{Type{Inti.LagrangeElement{D, N, T}}}, Tuple{T}, Tuple{N}, Tuple{D}} where {D, N, T}","page":"Docstrings","title":"Inti.lagrange_basis","text":"lagrange_basis(E::Type{<:LagrangeElement})\n\nReturn the Lagrange basis B for the element E. Evaluating B(x) yields the value of each basis function at x.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.line-Tuple{Any, Any}","page":"Docstrings","title":"Inti.line","text":"line(a,b)\n\nCreate a [GeometricEntity] representing a straight line connecting points a and b. The points a and b can be either SVectors or a Tuple.\n\nThe parametrization of the line is given by f(u) = a + u(b - a), where 0 ≤ u ≤ 1.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.mean_curvature-Tuple{Inti.Quadrature}","page":"Docstrings","title":"Inti.mean_curvature","text":"mean_curvature(Q::Quadrature)\n\nCompute the mean_curvature at each quadrature node in Q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.mean_curvature-Tuple{Inti.ReferenceInterpolant, Any}","page":"Docstrings","title":"Inti.mean_curvature","text":"mean_curvature(τ, x̂)\n\nCalculate the mean curvature of the element τ at the parametric coordinate x̂.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.measure","page":"Docstrings","title":"Inti.measure","text":"measure(k::EntityKey, rtol)\n\nCompute the length/area/volume of the entity k using an adaptive quadrature with a relative tolerance rtol. Assumes that the entity has an explicit parametrization.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.meshgen","page":"Docstrings","title":"Inti.meshgen","text":"meshgen(Ω, n)\nmeshgen(Ω, n_dict)\nmeshgen(Ω; meshsize)\n\nGenerate a Mesh for the domain Ω where each curve is meshed using n elements. Passing a dictionary allows for a finer control; in such cases, n_dict[ent] should return an integer for each entity ent in Ω of geometric_dimension one.\n\nAlternatively, a meshsize can be passed, in which case, the number of elements is computed as so as to obtain an average mesh size of meshsize. Note that the actual mesh size may vary significantly for each element if the parametrization is far from uniform.\n\nThis function requires the entities forming Ω to have an explicit parametrization.\n\nwarning: Mesh quality\nThe quality of the generated mesh created using meshgen depends on the quality of the underlying parametrization. For complex surfaces, you are better off using a proper mesher such as gmsh.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.meshgen!-Tuple{Inti.Mesh, Inti.Domain, Int64}","page":"Docstrings","title":"Inti.meshgen!","text":"meshgen!(mesh,Ω,sz)\n\nSimilar to meshgen, but append entries to mesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.monomial_basis","page":"Docstrings","title":"Inti.monomial_basis","text":"monomial_basis(sp::PolynomialSpace)\n\nReturn a function f : ℝᴺ → ℝᵈ, where N is the dimension of the domain of sp containing a basis of monomials 𝐱ᶿ spanning the polynomial space PolynomialSpace.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.near_interaction_list-Union{Tuple{N}, Tuple{AbstractVector{<:StaticArraysCore.SVector{N}}, Inti.AbstractMesh{N}}} where N","page":"Docstrings","title":"Inti.near_interaction_list","text":"near_interaction_list(X,Y::AbstractMesh; tol)\n\nFor each element el of type E in Y, return the indices of the points in X which are closer than tol to the center of el.\n\nThis function returns a dictionary where e.g. dict[E][5] --> Vector{Int} gives the indices of points in X which are closer than tol to the center of the fifth element of type E.\n\nIf tol is a Dict, then tol[E] is the tolerance for elements of type E.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.new_tag-Tuple{Integer}","page":"Docstrings","title":"Inti.new_tag","text":"new_tag(dim)\n\nReturn a new tag for an entity of dimension dim so that EntityKey(dim, tag) is not already in ENTITIES.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.nodes-Tuple{Inti.SubMesh}","page":"Docstrings","title":"Inti.nodes","text":"nodes(msh::SubMesh)\n\nA view of the nodes of the parent mesh belonging to the submesh. The ordering is given by the nodetags function.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.nodetags-Tuple{Inti.SubMesh}","page":"Docstrings","title":"Inti.nodetags","text":"nodetags(msh::SubMesh)\n\nReturn the tags of the nodes in the parent mesh belonging to the submesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.normal-Tuple{Any, Any}","page":"Docstrings","title":"Inti.normal","text":"normal(el, x̂)\n\nReturn the normal vector of el at the parametric coordinate x̂.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.normal-Tuple{T} where T","page":"Docstrings","title":"Inti.normal","text":"normal(q)\n\nReturn the normal vector of q, if it exists.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.notimplemented-Tuple{}","page":"Docstrings","title":"Inti.notimplemented","text":"notimplemented()\n\nThings which should probably be implemented at some point.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.order-Union{Tuple{Inti.Fejer{N}}, Tuple{N}} where N","page":"Docstrings","title":"Inti.order","text":"order(q::ReferenceQuadrature)\n\nA quadrature of order p (sometimes called degree of precision) integrates all polynomials of degree ≤ p but not ≤ p + 1.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.order-Union{Tuple{Type{<:Inti.LagrangeElement{D, Np}}}, Tuple{Np}, Tuple{D}} where {D, Np}","page":"Docstrings","title":"Inti.order","text":"order(el::LagrangeElement)\n\nThe order of the element's interpolating polynomial (e.g. a LagrangeLine with 2 nodes defines a linear polynomial, and thus has order 1).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.parametric_curve-Union{Tuple{F}, Tuple{F, Real, Real}} where F","page":"Docstrings","title":"Inti.parametric_curve","text":"parametric_curve(f, a::Real, b::Real)\n\nCreate a [GeometricEntity] representing a parametric curve defined by the {f(t) | a ≤ t ≤ b}. The function f should map a scalar to an SVector.\n\nFlipping the orientation is supported by passing a > b.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.parametric_surface","page":"Docstrings","title":"Inti.parametric_surface","text":" parametric_surface(f, lc, hc, boundary = nothing; kwargs...)\n\nCreate a parametric surface defined by the function f over the rectangular domain defined by the lower corner lc and the upper corner hc. The optional boundary argument can be used to specify the boundary curves of the surface.\n\nArguments\n\nf: A function that takes two arguments x and y and returns a tuple (u, v) representing the parametric coordinates of the surface at (x, y).\nlc: A 2-element array representing the lower corner of the rectangular domain.\nhc: A 2-element array representing the upper corner of the rectangular domain.\nboundary: An optional array of boundary curves that define the surface.\n\nKeyword Arguments\n\nkwargs: Additional keyword arguments that can be passed to the GeometricEntity constructor.\n\nReturns\n\nThe key of the created GeometricEntity.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.polynomial_solutions_vdim","page":"Docstrings","title":"Inti.polynomial_solutions_vdim","text":"polynomial_solutions_vdim(op, order[, center])\n\nFor every monomial term pₙ of degree order, compute a polynomial Pₙ such that ℒ[Pₙ] = pₙ, where ℒ is the differential operator associated with op. This function returns {pₙ,Pₙ,γ₁Pₙ}, where γ₁Pₙ is the generalized Neumann trace of Pₙ.\n\nPassing a point center will shift the monomials and solutions accordingly.\n\n\n\n\n\n","category":"function"},{"location":"docstrings/#Inti.polynomial_space-Union{Tuple{Inti.ReferenceQuadrature{D}}, Tuple{D}} where D","page":"Docstrings","title":"Inti.polynomial_space","text":"polynomial_space(qrule::ReferenceQuadrature)\n\nReturn a PolynomialSpace associated with the interpolation_order of the quadrature nodes of qrule.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.qcoords-Tuple{Inti.ReferenceQuadrature}","page":"Docstrings","title":"Inti.qcoords","text":"qcoords(q)\n\nReturn the coordinate of the quadrature nodes associated with q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.quadrature_to_node_vals-Tuple{Inti.Quadrature, AbstractVector}","page":"Docstrings","title":"Inti.quadrature_to_node_vals","text":"quadrature_to_node_vals(Q::Quadrature, qvals::AbstractVector)\n\nGiven a vector qvals of scalar values at the quadrature nodes of Q, return a vector ivals of scalar values at the interpolation nodes of Q.mesh.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.qweights-Tuple{Inti.ReferenceQuadrature}","page":"Docstrings","title":"Inti.qweights","text":"qweights(q)\n\nReturn the quadrature weights associated with q.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.reference_nodes-Tuple{Inti.LagrangeElement}","page":"Docstrings","title":"Inti.reference_nodes","text":"reference_nodes(el::LagrangeElement)\nreference_nodes(::Type{<:LagrangeElement})\n\nReturn the reference nodes on domain(el) used for the polynomial interpolation. The function values on these nodes completely determines the interpolating polynomial.\n\nWe use the same convention as gmsh for defining the reference nodes and their order (see node ordering on gmsh documentation).\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.return_type-Tuple{Any, Vararg{Any}}","page":"Docstrings","title":"Inti.return_type","text":"return_type(f[,args...])\n\nThe type returned by f(args...), where args is a tuple of types. Falls back to Base.promote_op by default.\n\nA functors of type T with a knonw return type should extend return_type(::T,args...) to avoid relying on promote_op.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.rotation_matrix-Tuple{Any}","page":"Docstrings","title":"Inti.rotation_matrix","text":"rotation_matrix(rot)\n\nConstructs a rotation matrix given the rotation angles around the x, y, and z axes.\n\nArguments\n\nrot: A tuple or vector containing the rotation angles in radians for each axis.\n\nReturns\n\nR::SMatrix: The resulting rotation matrix.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.single_double_layer-Tuple{}","page":"Docstrings","title":"Inti.single_double_layer","text":"single_double_layer(; op, target, source::Quadrature, compression,\ncorrection, derivative = false)\n\nConstruct a discrete approximation to the single- and double-layer integral operators for op, mapping values defined on the quadrature nodes of source to values defined on the nodes of target. If derivative = true, return instead the adjoint double-layer and hypersingular operators (which are the derivative of the single- and double-layer, respectively).\n\nYou must choose a compression method and a correction method, as described below.\n\nCompression\n\nThe compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:\n\n(method = :none, ): no compression is performed, the resulting matrices are dense.\n(method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).\n(method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).\n\nCorrection\n\nThe correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:\n\n(method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.\n(method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesourcecurve/surface (which is assumed to be closed). Whentarget === source,target_location` is not needed.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.single_double_layer_potential-Tuple{}","page":"Docstrings","title":"Inti.single_double_layer_potential","text":"single_double_layer_potential(; op, source)\n\nReturn the single- and double-layer potentials for op as IntegralPotentials.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.skeleton-Tuple{Inti.Domain}","page":"Docstrings","title":"Inti.skeleton","text":"skeleton(Ω::Domain)\n\nReturn all the boundaries of the domain, i.e. the domain's skeleton.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.stack_weakdeps_env!-Tuple{}","page":"Docstrings","title":"Inti.stack_weakdeps_env!","text":"stack_weakdeps_env!(; verbose = false, update = false)\n\nPush to the load stack an environment providing the weak dependencies of Inti.jl. This allows benefiting from additional functionalities of Inti.jl which are powered by weak dependencies without having to manually install them in your environment.\n\nSet update=true if you want to update the weakdeps environment.\n\nwarning: Warning\nCalling this function can take quite some time, especially the first time around, if packages have to be installed or precompiled. Run in verbose mode to see what is happening.\n\nExamples:\n\nInti.stack_weakdeps_env!()\nusing HMatrices\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.standard_basis_vector-Union{Tuple{N}, Tuple{Any, Val{N}}} where N","page":"Docstrings","title":"Inti.standard_basis_vector","text":"standard_basis_vector(k, ::Val{N})\n\nCreate an SVector of length N with a 1 in the kth position and zeros elsewhere.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.svector-Union{Tuple{F}, Tuple{F, Any}} where F","page":"Docstrings","title":"Inti.svector","text":"svector(f,n)\n\nCreate an SVector of length n, computing each element as f(i), where i is the index of the element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.torus-Tuple{}","page":"Docstrings","title":"Inti.torus","text":"torus(; r, R, translation, rotation, scaling, labels)\n\nCreate a torus entity in 3D, and apply optional transformations. Returns the key. The parameters r and R are the inner and outer radii of the torus.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.uniform_points_circle-Tuple{Any, Any, Any}","page":"Docstrings","title":"Inti.uniform_points_circle","text":"uniform_points_circle(N,r,c)\n\nReturn N points uniformly distributed on a circle of radius r centered at c.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vdim_correction-Union{Tuple{SHIFT}, Tuple{Any, Any, Inti.Quadrature, Inti.Quadrature, Any, Any, Any}} where SHIFT","page":"Docstrings","title":"Inti.vdim_correction","text":"vdim_correction(op,X,Y,Y_boundary,S,D,V; green_multiplier, kwargs...)\n\nCompute a correction to the volume potential V : Y → X such that V + δV is a more accurate approximation of the underlying volume potential operator. The correction is computed using the (volume) density interpolation method.\n\nThis function requires a op::AbstractDifferentialOperator, a target set X, a source quadrature Y, a boundary quadrature Y_boundary, approximations S : Y_boundary -> X and D : Y_boundary -> X to the single- and double-layer potentials (correctly handling nearly-singular integrals), and a naive approximation of the volume potential V. The green_multiplier is a vector of the same length as X storing the value of μ(x) for x ∈ X in the Green identity (see _green_multiplier).\n\nSee [8] for more details on the method.\n\nOptional kwargs:\n\ninterpolation_order: the order of the polynomial interpolation. By default, the maximum order of the quadrature rules is used.\nmaxdist: distance beyond which interactions are considered sufficiently far so that no correction is needed. This is used to determine a threshold for nearly-singular corrections.\ncenter: the center of the basis functions. By default, the basis functions are centered at the origin.\nshift: a boolean indicating whether the basis functions should be shifted and rescaled to each element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vdim_mesh_center-Tuple{Inti.AbstractMesh}","page":"Docstrings","title":"Inti.vdim_mesh_center","text":"vdim_mesh_center(msh)\n\nPoint x which minimizes ∑ (x-xⱼ)²/r²ⱼ, where xⱼ and rⱼ are the circumcenter and circumradius of the elements of msh, respectively.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vertices-Tuple{Inti.LagrangeElement}","page":"Docstrings","title":"Inti.vertices","text":"vertices(el::LagrangeElement)\n\nCoordinates of the vertices of el.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.vertices_idxs-Tuple{Type{<:Inti.LagrangeElement{Inti.ReferenceHyperCube{1}}}}","page":"Docstrings","title":"Inti.vertices_idxs","text":"vertices_idxs(el::LagrangeElement)\n\nThe indices of the nodes in el that define the vertices of the element.\n\n\n\n\n\n","category":"method"},{"location":"docstrings/#Inti.volume_potential-Tuple{}","page":"Docstrings","title":"Inti.volume_potential","text":"volume_potential(; op, target, source::Quadrature, compression, correction)\n\nCompute the volume potential operator for a given PDE.\n\nArguments\n\nop: The PDE (Partial Differential Equation) to solve.\ntarget: The target domain where the potential is computed.\nsource: The source domain where the potential is generated.\ncompression: The compression method to use for the potential operator.\ncorrection: The correction method to use for the potential operator.\n\nReturns\n\nThe volume potential operator V that represents the interaction between the target and source domains.\n\nCompression\n\nThe compression argument is a named tuple with a method field followed by method-specific fields. It specifies how the dense linear operators should be compressed. The available options are:\n\n(method = :none, ): no compression is performed, the resulting matrices are dense.\n(method =:hmatrix, tol): the resulting operators are compressed using hierarchical matrices with an absolute tolerance tol (defaults to 1e-8).\n(method = :fmm, tol): the resulting operators are compressed using the fast multipole method with an absolute tolerance tol (defaults to 1e-8).\n\nCorrection\n\nThe correction argument is a named tuple with a method field followed by method-specific fields. It specifies how the singular and nearly-singular integrals should be computed. The available options are:\n\n(method = :none, ): no correction is performed. This is not recommented, as the resulting approximation will be inaccurate if the source and target are not sufficiently far apart.\n(method = :dim, maxdist, target_location): use the density interpolation method to compute the correction. maxdist specifies the distance between source and target points above which no correction is performed (defaults to Inf). target_location should be either :inside, :outside, or :on, and specifies where the targetpoints lie relative to the to thesource's boundary. Whentarget === source,target_location` is not needed.\n\nDetails\n\nThe volume potential operator is computed by assembling the integral operator V using the single-layer kernel G. The operator V is then compressed using the specified compression method. If no compression is specified, the operator is returned as is. If a correction method is specified, the correction is computed and added to the compressed operator.\n\n\n\n\n\n","category":"method"},{"location":"tutorials/compression_methods/#Compression-methods","page":"Compression methods","title":"Compression methods","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"note: Important points covered in this tutorial\nOverview of the compression methods available in Inti.jl\nDetails and limitations of the various compression methods\nGuideline on how to choose a compression method","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Inti.jl wraps several external libraries providing acceleration routines for integral operators. In general, acceleration routines have the signature assemble_*(iop, args...; kwargs...), and take an IntegralOperator as a first argument. They return a new object that represents a compressed version of the operator. The following methods are available:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_matrix: create a dense Matrix representation of the integral operator. Not really a compression method, but useful for debugging and small problems.\nassemble_hmatrix: assemble a hierarchical matrix representation of the operator using the HMatrices library.\nassemble_fmm: return a LinearMap object that represents the operator using the fast multipole method. This method is powered by the FMM2D, FMMLIB2D and FMM3D libraries, and is only available for certain kernels.","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"warning: Singular kernels\nAcceleration methods do not correct for singular or nearly-singular interactions. When the underlying kernel is singular, a correction is usually necessary in order to obtain accurate results (see the section on correction methods for more details).","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"To illustrate the use of compression methods, we will use the following problem as an example. Note that for such a small problem, compression methods are not likely not necessary, but they are useful for larger problems.","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"using Inti\nusing LinearAlgebra\n# define the quadrature\ngeo = Inti.GeometricEntity(\"ellipsoid\")\nΩ = Inti.Domain(geo)\nΓ = Inti.boundary(Ω)\nQ = Inti.Quadrature(Γ; meshsize = 0.4, qorder = 5)\n# create the operator\nop = Inti.Helmholtz(; dim = 3, k = 2π)\nK = Inti.SingleLayerKernel(op)\nSop = Inti.IntegralOperator(K, Q, Q)\nx = rand(eltype(Sop), length(Q))\nrtol = 1e-8\nnothing # hide","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"In what follows we compress Sop using the different methods available.","category":"page"},{"location":"tutorials/compression_methods/#Dense-matrix","page":"Compression methods","title":"Dense matrix","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_matrix","category":"page"},{"location":"tutorials/compression_methods/#Inti.assemble_matrix-tutorials-compression_methods","page":"Compression methods","title":"Inti.assemble_matrix","text":"assemble_matrix(iop::IntegralOperator; threads = true)\n\nAssemble a dense matrix representation of an IntegralOperator.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Typically used for small problems, the dense matrix representation converts the IntegralOperator into a Matrix object. The underlying type of the Matrix is determined by the eltype of the IntegralOperator, and depends on the inferred type of the kernel. Here is how assemble_matrix can be used:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Smat = Inti.assemble_matrix(Sop; threads=true)\n@assert Sop * x ≈ Smat * x # hide\ner = norm(Sop * x - Smat * x, Inf) / norm(Sop * x, Inf)\nprintln(\"Forward map error: $er\")","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Since the returned object is plain Julia Matrix, it can be used with any of the linear algebra routines available in Julia (e.g. \\, lu, qr, *, etc.)","category":"page"},{"location":"tutorials/compression_methods/#Hierarchical-matrix","page":"Compression methods","title":"Hierarchical matrix","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_hmatrix","category":"page"},{"location":"tutorials/compression_methods/#Inti.assemble_hmatrix-tutorials-compression_methods","page":"Compression methods","title":"Inti.assemble_hmatrix","text":"assemble_hmatrix(iop[; atol, rank, rtol, eta])\n\nAssemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.\n\nSee the documentation of HMatrices for more details on usage and other keyword arguments.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The hierarchical matrix representation is a compressed representation of the underlying operator; as such, it takes a tolerance parameter that determines the relative error of the compression. Here is an example of how to use the assemble_hmatrix method to compress the previous problem:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"using HMatrices\nShmat = Inti.assemble_hmatrix(Sop; rtol = 1e-8)\ner = norm(Smat * x - Shmat * x, Inf) / norm(Smat * x, Inf)\n@assert er < 10*rtol # hide\nprintln(\"Forward map error: $er\")","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"Note that HMatrices are said to be kernel-independent, meaning that they efficiently compress a wide range of integral operators provided they satisfy a certain asymptotic smoothness criterion (see e.g. [3, 4]).","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The HMatrix object can be used to solve linear systems, both iteratively through e.g. GMRES, or directly using an LU factorization.","category":"page"},{"location":"tutorials/compression_methods/#Fast-multipole-method","page":"Compression methods","title":"Fast multipole method","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"assemble_fmm","category":"page"},{"location":"tutorials/compression_methods/#Inti.assemble_fmm-tutorials-compression_methods","page":"Compression methods","title":"Inti.assemble_fmm","text":"assemble_fmm(iop; atol)\n\nSet up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.\n\nwarning: FMMLIB2D\nFMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.\n\n\n\n\n\n","category":"function"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The fast multipole method (FMM) is an acceleration technique based on an analytic multipole expansion of the kernel in the integral operator [5, 6]. It provides a very memory-efficient and fast way to evaluate certain types of integral operators. Here is how assemble_fmm can be used:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"using FMM3D\nSfmm = Inti.assemble_fmm(Sop; rtol = 1e-8)\ner = norm(Sop * x - Sfmm * x, Inf) / norm(Sop * x, Inf)\n@assert er < 10*rtol # hide\nprintln(\"Forward map error: $er\")","category":"page"},{"location":"tutorials/compression_methods/#Tips-on-choosing-a-compression-method","page":"Compression methods","title":"Tips on choosing a compression method","text":"","category":"section"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"The choice of compression method depends on the problem at hand, as well as on the available hardware. Here is a rough guide on how to choose a compression:","category":"page"},{"location":"tutorials/compression_methods/","page":"Compression methods","title":"Compression methods","text":"For small problems (say less than 5k degrees of freedom), use the dense matrix representation. It is the simplest and most straightforward method, and does not require any additional packages. It is also the most accurate since it does not introduce any approximation errors.\nIf the integral operator is supported by the assemble_fmm, and if an iterative solver is acceptable, use it. The FMM is a very efficient method for certain types of kernels, and can handle problems with up to a few million degrees of freedom on a laptop.\nIf the kernel is not supported by assemble_fmm, if iterative solvers are not an option, or if the system needs solution for many right-hand sides, use the assemble_hmatrix method. It is a very general method that can handle a wide range of kernels, and although assembling the HMatrix can be time and memory consuming (the complexity is still log-linear in the DOFs for many kernels of interest, but the constants can be large), the resulting HMatrix object is very efficient to use. For example, the forward map is usually significantly faster than the one obtained through assemble_fmm.","category":"page"},{"location":"tutorials/correction_methods/#Correction-methods","page":"Correction methods","title":"Correction methods","text":"","category":"section"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"warning: Work in progress\nThis tutorial is still a work in progress. We will update it with more details and examples in the future.","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"note: Important points covered in this tutorial\nOverview of the correction methods available in Inti.jl\nDetails and limitations of the various correction methods\nGuideline on how to choose a correction method","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"When the underlying kernel is singular, a correction is usually necessary in order to obtain accurate results in the approximation of the underlying integral operator by a quadrature. At present, Inti.jl provides the following functions to correct for singularities:","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"adaptive_correction\nbdim_correction\nvdim_correction","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"They have different strengths and weaknesses, and we will discuss them in the following sections.","category":"page"},{"location":"tutorials/correction_methods/","page":"Correction methods","title":"Correction methods","text":"note: High-level API\nNote that the single_double_layer, adj_double_layer_hypersingular, and volume_potential functions have high-level API with a correction keyword argument that allows one to specify the correction method to use when constructing the integral operators; see the documentation of these functions for more details.","category":"page"},{"location":"tutorials/correction_methods/#Adaptive-correction","page":"Correction methods","title":"Adaptive correction","text":"","category":"section"},{"location":"tutorials/correction_methods/#Boundary-density-interpolation-method","page":"Correction methods","title":"Boundary density interpolation method","text":"","category":"section"},{"location":"tutorials/correction_methods/#Volume-density-interpolation-method","page":"Correction methods","title":"Volume density interpolation method","text":"","category":"section"},{"location":"tutorials/correction_methods/#Martensen-Kussmaul-method","page":"Correction methods","title":"Martensen-Kussmaul method","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/#Geometry-and-meshes","page":"Geometry and meshes","title":"Geometry and meshes","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"note: Important points covered in this tutorial\nCombine simple shapes to create domains\nImport a mesh from a file\nIterative over mesh elements","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"In the getting started tutorial, we saw how to solve a simple Helmholtz scattering problem in 2D. We will now dig deeper into how to create and manipulate more complex geometrical shapes, as well the associated meshes.","category":"page"},{"location":"tutorials/geo_and_meshes/#Overview","page":"Geometry and meshes","title":"Overview","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.jl provides a flexible way to define geometrical entities and their associated meshes. Simply put, the GeometricEntity type is the atomic building block of geometries: they can represent points, curves, surfaces, or volumes. Geometrical entities of the same dimension can be combined to form Domain, and domains can be manipulated using basic set operations such union and intersection. Meshes on the other hand are collections of (simple) elements that approximate the geometrical entities. A mesh element is a just a function that maps points from a ReferenceShape to the physical space.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"In most applications involving complex three-dimensional surfaces, an external meshing software is used to generate a mesh, and the mesh is imported using the import_mesh function (which relies on Gmsh). The entities can then be extracted from the mesh based on e.g. their dimension or label. Here is an example of how to import a mesh from a file:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"using Inti\nusing Gmsh \nfilename = joinpath(Inti.PROJECT_ROOT,\"docs\", \"assets\", \"piece.msh\")\nmsh = Inti.import_mesh(filename)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"The imported mesh contains elements of several types, used to represent the segments, triangles, and tetras used to approximate the geometry:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.element_types(msh)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Note that the msh object contains all entities used to construct the mesh, usually defined in a .geo file, which can be extracted using the entities:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"ents = Inti.entities(msh)\nnothing # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Filtering of entities satisfying a certain condition, e.g., entities of a given dimension or containing a certain label, can also be performed in order to construct a domain:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"filter = e -> Inti.geometric_dimension(e) == 3\nΩ = Inti.Domain(filter, ents)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Domains can be used to index the mesh, creating either a new object containing only the necessary elements:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Γ = Inti.boundary(Ω)\nmsh[Γ]","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"or a SubMesh containing a view of the mesh:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Γ_msh = view(msh, Γ)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Finally, we can visualize the mesh using:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"using Meshes, GLMakie\nfig = Figure(; size = (800,400))\nax = Axis3(fig[1, 1]; aspect = :data)\nviz!(Γ_msh; showsegments = true, alpha = 0.5)\nfig","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"warning: Mesh visualization\nNote that although the mesh may be of high order and/or conforming, the visualization of a mesh is always performed on the underlying first order mesh, and therefore elements may look flat even if the problem is solved on a curved mesh.","category":"page"},{"location":"tutorials/geo_and_meshes/#Parametric-entities-and-meshgen","page":"Geometry and meshes","title":"Parametric entities and meshgen","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"In the previous section we saw an example of how to import a mesh from a file, and how to extract the entities from the mesh. For simple geometries for which an explicit parametrization is available, Inti.jl provides a way to create and manipulate geometrical entities and their associated meshes.","category":"page"},{"location":"tutorials/geo_and_meshes/#Parametric-curves","page":"Geometry and meshes","title":"Parametric curves","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"The simplest parametric shapes are parametric_curves, which are defined by a function that maps a scalar parameter t to a point in 2D or 3D space. Parametric curves are expected to return an SVector, and can be created as follows:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"using StaticArrays\nl1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₁\"])","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"The object l1 represents a GeometricEntity with a known push-forward map:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.pushforward(l1)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"For the sake of this example, let's create three more curves, and group them together to form a Domain:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"l2 = Inti.parametric_curve(x->SVector(1 + 0.1 * sin(2π * x), x), 0.0, 1.0, labels = [\"l₂\"])\nl3 = Inti.parametric_curve(x->SVector(1 - x, 1 - 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₃\"])\nl4 = Inti.parametric_curve(x->SVector(0.1 * sin(2π * x), 1 - x), 0.0, 1.0, labels = [\"l₄\"])\nΓ = l1 ∪ l2 ∪ l3 ∪ l4","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Domains for which a parametric representation is available can be passed to the meshgen function:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"msh = Inti.meshgen(Γ; meshsize = 0.05)\nnothing # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"We can use the Meshes.viz function to visualize the mesh, and use domains to index the mesh:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Γ₁ = l1 ∪ l3\nΓ₂ = l2 ∪ l4\nfig, ax, pl = viz(view(msh, Γ₁); segmentsize = 4, label = \"Γ₁\")\nviz!(view(msh, Γ₂); segmentsize = 4, color = :red, label = \"Γ₂\")\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Note that the orientation of the curve determines the direction of the normal vector. The normal points to the right of the curve when moving in the direction of increasing parameter t:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"pts, tangents, normals = Makie.Point2f[], Makie.Vec2f[], Makie.Vec2f[]\nfor l in [l1, l2, l3, l4]\n push!(pts, l(0.5)) # mid-point of the curve \n push!(tangents, vec(Inti.jacobian(l, 0.5)))\n push!(normals,Inti.normal(l, 0.5))\nend\narrows!(pts, tangents, color = :blue, linewidth = 2, linestyle = :dash, lengthscale = 1/4, label = \"tangent\")\narrows!(pts, normals, color = :black, linewidth = 2, linestyle = :dash, lengthscale = 1/4, label = \"normal\")\naxislegend()\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/#Parametric-surfaces","page":"Geometry and meshes","title":"Parametric surfaces","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Like parametric curves, parametric surfaces are defined by a function that maps a reference domain D subset mathbbR^2 to a surface in 3D space. They can be constructed using the parametric_surface function:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"# a patch of the unit sphere\nlc = SVector(-1.0, -1.0)\nhc = SVector(1.0, 1.0)\nf = (u,v) -> begin\n x = SVector(1.0, u, v) # a face of the cube\n x ./ sqrt(u^2 + v^2 + 1) # project to the sphere\nend\npatch = Inti.parametric_surface(f, lc, hc, labels = [\"patch1\"])\nΓ = Inti.Domain(patch)\nmsh = Inti.meshgen(Γ; meshsize = 0.1)\nviz(msh[Γ]; showsegments = true, figure = (; size = (400,400),))","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Since creating parametric surfaces that form a closed volume can be a bit more involved, Inti.jl provide a few helper functions to create simple shapes:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"fig = Figure(; size = (600,400))\nnshapes = Inti.length(Inti.PREDEFINED_SHAPES)\nncols = 3; nrows = ceil(Int, nshapes/ncols)\nfor (n,shape) in enumerate(Inti.PREDEFINED_SHAPES)\n Ω = Inti.GeometricEntity(shape) |> Inti.Domain\n Γ = Inti.boundary(Ω)\n msh = Inti.meshgen(Γ; meshsize = 0.1)\n i,j = (n-1) ÷ ncols + 1, (n-1) % ncols + 1\n ax = Axis3(fig[i,j]; aspect = :data, title = shape)\n hidedecorations!(ax)\n viz!(msh; showsegments = true)\nend\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"See GeometricEntity(shape::String) for a list of predefined geometries.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"warning: Mesh quality\nThe quality of the generated mesh created through meshgen depends heavily on the quality of the underlying parametrization. For surfaces containing a degenerate parametrization, or for complex shapes, one is better off using a suitable CAD (Computer-Aided Design) software in conjunction with a mesh generator.","category":"page"},{"location":"tutorials/geo_and_meshes/#Transfinite-domains","page":"Geometry and meshes","title":"Transfinite domains","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"It is possible to combine parametric curves/surfaces to form a transfinite domain where the parametrization is inherited from the curves/surfaces that form its boundary. At present, Inti.jl only supports transfinite squares, which are defined by four parametric curves:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"l1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₁\"])\nl2 = Inti.parametric_curve(x->SVector(1 + 0.1 * sin(2π * x), x), 0.0, 1.0, labels = [\"l₂\"])\nl3 = Inti.parametric_curve(x->SVector(1 - x, 1 - 0.1 * sin(2π * x)), 0.0, 1.0, labels = [\"l₃\"])\nl4 = Inti.parametric_curve(x->SVector(0.1 * sin(2π * x), 1 - x), 0.0, 1.0, labels = [\"l₄\"])\nsurf = Inti.transfinite_square(l1, l2, l3, l4; labels = [\"Ω\"])\nΩ = Inti.Domain(surf)\nmsh = Inti.meshgen(Ω; meshsize = 0.05)\nviz(msh; showsegments = true)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Note that the msh object contains all entities used to construct Ω, including the boundary segments:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Inti.entities(msh)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"This allows us to probe the msh object to extract e.g. the boundary mesh:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"viz(msh[Inti.boundary(Ω)]; color = :red)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"warning: Limitations\nAt present only the transfinite interpolation for the logically quadrilateral domains is supported. In the future we hope to add support for three-dimensional transfinite interpolation, as well as transfinite formulas for simplices.","category":"page"},{"location":"tutorials/geo_and_meshes/#Elements-of-a-mesh","page":"Geometry and meshes","title":"Elements of a mesh","text":"","category":"section"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"To iterate over the elements of a mesh, use the elements function:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"filename = joinpath(Inti.PROJECT_ROOT,\"docs\", \"assets\", \"piece.msh\")\nmsh = Inti.import_mesh(filename)\nents = Inti.entities(msh)\nΩ = Inti.Domain(e -> Inti.geometric_dimension(e) == 3, ents) \nels = Inti.elements(view(msh, Ω))\ncenters = map(el -> Inti.center(el), els)\nfig = Figure(; size = (800,400))\nax = Axis3(fig[1, 1]; aspect = :data)\nscatter!([c[1] for c in centers], [c[2] for c in centers], [c[3] for c in centers], markersize = 5)\nfig # hide","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"This example shows how to extract the centers of the tetrahedral elements in the mesh; and of course we can perform any desired computation on the elements.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"tip: Type-stable iteration over elements\nSince a mesh in Inti.jl can contain elements of various types, the elements function above is not type-stable. For a type-stable iterator approach, one should first iterate over the element types using element_types, and then use elements(msh, E) to iterate over a specific element type E.","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Under the hood, each element is simply a functor which maps points x̂ from a ReferenceShape into the physical space:","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"el = first(els)\nx̂ = SVector(1/3,1/3, 1/3)\nel(x̂)","category":"page"},{"location":"tutorials/geo_and_meshes/","page":"Geometry and meshes","title":"Geometry and meshes","text":"Likewise, we can compute the jacobian of the element, or its normal at a given parametric coordinate.","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"(Image: Pluto notebook)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"begin\n import Pkg as _Pkg\n haskey(ENV, \"PLUTO_PROJECT\") && _Pkg.activate(ENV[\"PLUTO_PROJECT\"])\n using PlutoUI: TableOfContents\nend;","category":"page"},{"location":"pluto-examples/poisson/#Poisson-Problem","page":"Poisson problem","title":"Poisson Problem","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"note: Important points covered in this example\nReformulating Poisson-like problems using integral equations\nUsing volume potentials\nCreating interior meshes using Gmsh","category":"page"},{"location":"pluto-examples/poisson/#Problem-definition","page":"Poisson problem","title":"Problem definition","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"In this example we will solve the Poisson equation in a domain Omega with Dirichlet boundary conditions on Gamma = partial Omega:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" beginalign*\n -Delta u = f quad textin quad Omega\n u = g quad texton quad Gamma\n endalign*","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"where f Omega to mathbbR and g Gamma to mathbbR are given functions. To solve this problem using integral equations, we split the solution u into a particular solution u_p and a homogeneous solution u_h:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" u = u_p + u_h","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"The function u_p is given by","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"u_p(boldsymbolr) = int_Omega G(boldsymbolr boldsymbolr) f(boldsymbolr) dboldsymbolr","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"with G the fundamental solution of -Delta.","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"The function u_h satisfies the homogeneous problem","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" beginalign*\n Delta u_h = 0 quad textin quad Omega \n u_h = g - u_p quad texton quad Gamma\n endalign*","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"which can be solved using the integral equation method. In particular, for this example, we employ a double-layer formulation:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"u_h(boldsymbolr) = int_Gamma G(boldsymbolr boldsymbolr) sigma(boldsymbolr) dboldsymbolr","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"where the density function sigma solves the integral equation","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":" -fracsigma(boldsymbolx)2 + int_Gamma partial_nu_boldsymbolyG(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmd s_boldsymboly = g(boldsymbolx) - u_p(boldsymbolx)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"In what follows we illustrate how to solve the problem in this manner.","category":"page"},{"location":"pluto-examples/poisson/#Geometry-and-mesh","page":"Poisson problem","title":"Geometry and mesh","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We use the Gmsh API to create a jellyfish-shaped domain and to generate a second order mesh of its interior and boundary:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using Inti, Gmsh\nmeshsize = 0.1\ngmsh.initialize()\njellyfish = Inti.gmsh_curve(0, 2π; meshsize) do s\n r = 1 + 0.3 * cos(4 * s + 2 * sin(s))\n return r * Inti.Point2D(cos(s), sin(s))\nend\ncl = gmsh.model.occ.addCurveLoop([jellyfish])\nsurf = gmsh.model.occ.addPlaneSurface([cl])\ngmsh.model.occ.synchronize()\ngmsh.option.setNumber(\"Mesh.MeshSizeMax\", meshsize)\ngmsh.model.mesh.generate(2)\ngmsh.model.mesh.setOrder(2)\nmsh = Inti.import_mesh(; dim = 2)\ngmsh.finalize()","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We can now extract components of the mesh corresponding to the Omega and Gamma domains:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 2, msh)\nΓ = Inti.boundary(Ω)\nΩ_msh = view(msh, Ω)\nΓ_msh = view(msh, Γ)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"and visualize them:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using Meshes, GLMakie\nviz(Ω_msh; showsegments = true)\nviz!(Γ_msh; color = :red)\nMakie.current_figure() #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"To conclude the geometric setup, we need a quadrature for the volume and boundary:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Ω_quad = Inti.Quadrature(Ω_msh; qorder = 4)\nΓ_quad = Inti.Quadrature(Γ_msh; qorder = 6)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/#Integral-operators","page":"Poisson problem","title":"Integral operators","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We can now assemble the required volume potential. To obtain the value of the particular solution u_p on the boundary for the modified integral equation above we will need the volume integral operator mapping to points on the boundary, i.e. operator:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using FMM2D #to accelerate the maps\nop = Inti.Laplace(; dim = 2)\n# Newtonian potential mapping domain to boundary\nV_d2b = Inti.volume_potential(;\n op,\n target = Γ_quad,\n source = Ω_quad,\n compression = (method = :fmm, tol = 1e-12),\n correction = (method = :dim, maxdist = 5 * meshsize, target_location = :on),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We require also the boundary integral operators for the ensuing integral equation:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"# Single and double layer operators on Γ\nS_b2b, D_b2b = Inti.single_double_layer(;\n op,\n target = Γ_quad,\n source = Γ_quad,\n compression = (method = :fmm, tol = 1e-12),\n correction = (method = :dim,),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"note: Note\nIn this example we used the Fast Multipole Method (:fmm) to accelerate the operators, and the Density Interpolation Method (:dim) to correct singular and nearly-singular integral.","category":"page"},{"location":"pluto-examples/poisson/#Solving-the-linear-system","page":"Poisson problem","title":"Solving the linear system","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We are now in a position to solve the original Poisson problem, but for that we need to specify the functions f and g. In order to verify that our numerical approximation is correct, however, we will play a different game and specify instead a manufactured solution u_e from which we will derive the functions f and g:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"# Create a manufactured solution\nuₑ = (x) -> cos(2 * x[1]) * sin(2 * x[2])\nfₑ = (x) -> 8 * cos(2 * x[1]) * sin(2 * x[2]) # -Δuₑ\ng = map(q -> uₑ(q.coords), Γ_quad)\nf = map(q -> fₑ(q.coords), Ω_quad)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"With these, we can compute the right-hand-side of the integral equation for the homogeneous part of the solution:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"rhs = g - V_d2b * f\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"and solve the integral equation for the integral density function σ:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"using IterativeSolvers, LinearAlgebra\nσ = gmres(-I / 2 + D_b2b, rhs; abstol = 1e-8, verbose = true, restart = 1000)\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"With the density function at hand, we can now reconstruct our approximate solution:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"G = Inti.SingleLayerKernel(op)\ndG = Inti.DoubleLayerKernel(op)\n𝒱 = Inti.IntegralPotential(G, Ω_quad)\n𝒟 = Inti.IntegralPotential(dG, Γ_quad)\nu = (x) -> 𝒱[f](x) + 𝒟[σ](x)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"and evaluate it at any point in the domain:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"x = Inti.Point2D(0.1, 0.4)\nprintln(\"error at $x: \", u(x) - uₑ(x))","category":"page"},{"location":"pluto-examples/poisson/#Solution-evaluation-and-visualization","page":"Poisson problem","title":"Solution evaluation and visualization","text":"","category":"section"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Although we have \"solved\" the problem in the previous section, using the anonymous function u to evaluate the field is neither efficient nor accurate when there are either many points to evaluate, or when they lie close to the domain Omega. The fundamental reason for this is the usual: the integral operators in the function u are dense matrices, and their evaluation inside or near to Omega suffers from inaccurate singular and near-singular quadrature.","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"To address this issue, we need to assemble accelerated and corrected versions of the integral operators. Let us suppose we wish to evaluate the solution u at all the quadrature nodes of Omega:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"V_d2d = Inti.volume_potential(;\n op,\n target = Ω_quad,\n source = Ω_quad,\n compression = (method = :fmm, tol = 1e-8),\n correction = (method = :dim,),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Likewise, we need operators mapping densities from our boundary quadrature to our mesh nodes:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"S_b2d, D_b2d = Inti.single_double_layer(;\n op,\n target = Ω_quad,\n source = Γ_quad,\n compression = (method = :fmm, tol = 1e-8),\n correction = (method = :dim, maxdist = 2 * meshsize, target_location = :inside),\n)","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"We now evaluate the solution at all quadrature nodes and compare it to the manufactured:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"u_quad = V_d2d * f + D_b2d * σ\ner_quad = u_quad - map(q -> uₑ(q.coords), Ω_quad)\nprintln(\"maximum error at all quadrature nodes: \", norm(er_quad, Inf))\nnothing #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"Lastly, let us visualize the solution and the error on the mesh nodes using quadrature_to_node_vals:","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"nodes = Inti.nodes(Ω_msh)\nu_nodes = Inti.quadrature_to_node_vals(Ω_quad, u_quad)\ner = u_nodes - map(uₑ, nodes)\ncolorrange = extrema(u_nodes)\nfig = Figure(; size = (800, 300))\nax = Axis(fig[1, 1]; aspect = DataAspect())\nviz!(Ω_msh; colorrange, color = u_nodes, interpolate = true)\ncb = Colorbar(fig[1, 2]; label = \"u\", colorrange)\n# plot error\nlog_er = log10.(abs.(er))\ncolorrange = extrema(log_er)\ncolormap = :inferno\nax = Axis(fig[1, 3]; aspect = DataAspect())\nviz!(Ω_msh; colorrange, colormap, color = log_er, interpolate = true)\ncb = Colorbar(fig[1, 4]; label = \"log₁₀|u - uₑ|\", colormap, colorrange)\nfig #hide","category":"page"},{"location":"pluto-examples/poisson/","page":"Poisson problem","title":"Poisson problem","text":"TableOfContents()","category":"page"},{"location":"tutorials/getting_started/#Getting-started","page":"Getting started","title":"Getting started","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"CurrentModule = Inti","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"note: Important points covered in this tutorial\nCreate a domain and its accompanying mesh\nSolve a basic boundary integral equation\nVisualize the solution","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"This first tutorial will be a guided tour through the basic steps of setting up a boundary integral equation and solving it using Inti.jl. ","category":"page"},{"location":"tutorials/getting_started/#Mathematical-formulation","page":"Getting started","title":"Mathematical formulation","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"We will consider the classic Helmholtz scattering problem in 2D, and solve it using a direct boundary integral formulation. More precisely, letting Omega subset mathbbR^2 be a bounded domain, and denoting by Gamma = partial Omega its boundary, we will solve the following Helmholtz problem:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"beginaligned\n Delta u + k^2 u = 0 quad textin quad mathbbR^2 setminus overlineOmega\n partial_nu u = g quad texton quad Gamma\n sqrtr left( fracpartial upartial r - i k u right) = o(1) quad textas quad r = boldsymbolx to infty\nendaligned","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"where g is the given boundary datum, nu is the outward unit normal to Gamma, and k is the constant wavenumber. The last condition is the Sommerfeld radiation condition, and is required to ensure the uniqueness of the solution; physically, it means that the solution sought should radiate energy towards infinity.","category":"page"},{"location":"tutorials/getting_started/#PDE,-geometry,-and-mesh","page":"Getting started","title":"PDE, geometry, and mesh","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"The first step is to define the PDE under consideration:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using Inti\nInti.stack_weakdeps_env!() # add weak dependencies \n# PDE\nk = 2π\nop = Inti.Helmholtz(; dim = 2, k)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Next, we generate the geometry of the problem. For this tutorial, we will manually create parametric curves representing the boundary of the domain using the parametric_curve function:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using StaticArrays # for SVector\n# Create the geometry as the union of a kite and a circle\nkite = Inti.parametric_curve(0.0, 1.0; labels = [\"kite\"]) do s\n return SVector(2.5 + cos(2π * s[1]) + 0.65 * cos(4π * s[1]) - 0.65, 1.5 * sin(2π * s[1]))\nend\ncircle = Inti.parametric_curve(0.0, 1.0; labels = [\"circle\"]) do s\n return SVector(cos(2π * s[1]), sin(2π * s[1]))\nend\nΓ = kite ∪ circle","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Inti.jl expects the parametrization of the curve to be a function mapping scalars to points in space represented by SVectors. The labels argument is optional, and can be used to identify the different parts of the boundary. The Domain object Γ represents the boundary of the geometry, and can be used to create a mesh:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"# Create a mesh for the geometry\nmsh = Inti.meshgen(Γ; meshsize = 2π / k / 10)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To visualize the mesh, we can load Meshes.jl and one of Makie's backends:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using Meshes, GLMakie\nviz(msh; segmentsize = 3, axis = (aspect = DataAspect(), ), figure = (; size = (400,300)))","category":"page"},{"location":"tutorials/getting_started/#Quadrature","page":"Getting started","title":"Quadrature","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Once the mesh is created, we can define a quadrature to be used in the discretization of the integral operators:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"# Create a quadrature\nQ = Inti.Quadrature(msh; qorder = 5)\nnothing # hide","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"A Quadrature is simply a collection of QuadratureNode objects:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Q[1]","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"In the constructor above we specified a quadrature order of 5, and Inti.jl internally picked a ReferenceQuadrature suitable for the specified order; for finer control, a quadrature rule can be specified directly.","category":"page"},{"location":"tutorials/getting_started/#Integral-operators","page":"Getting started","title":"Integral operators","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To continue, we need to reformulate the Helmholtz problem as a boundary integral equation. Among the plethora of options, we will use in this tutorial a simple direct formulation, which uses Green's third identity to relate the values of u and partial_nu u on Gamma:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" -fracu(boldsymbolx)2 + Du(boldsymbolx) = Spartial_nu u(boldsymbolx) quad boldsymbolx in Gamma","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Here S and D are the single- and double-layer operators, formally defined as:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" Ssigma(boldsymbolx) = int_Gamma G(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly) quad\n Dsigma(boldsymbolx) = int_Gamma fracpartial Gpartial nu_boldsymboly(boldsymbolx boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"where","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"G(boldsymbolx boldsymboly) = fraci4 H^(1)_0(kboldsymbolx -\nboldsymboly)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"is the fundamental solution of the Helmholtz equation, with H^(1)_0 being the Hankel function of the first kind. Note that G is singular when boldsymbolx = boldsymboly, and therefore the numerical discretization of S and D requires special care.","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To approximate S and D, we can proceed as follows:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"S, D = Inti.single_double_layer(;\n op,\n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,),\n)\nnothing # hide","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Much of the complexity involved in the numerical computation is hidden in the function above; later in the tutorials we will discuss in more details the options available for the compression and correction methods, as well as how to define custom kernels and operators. For now, it suffices to know that S and D are matrix-like objects that can be used to solve the boundary integral equation. For that, we need to provide the boundary data g.","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"tip: Fast algorithms\nPowered by external libraries, Inti.jl supports several acceleration methods for matrix-vector multiplication, including so far:Fast multipole method (FMM) mapsto correction = (method = :fmm, tol = 1e-8)\nHierarchical matrix (H-matrix) mapsto correction = (method = :hmatrix, tol = 1e-8)Note that in such cases only the matrix-vector product may not be available, and therefore iterative solvers such as GMRES are required for the solution of the resulting linear systems.","category":"page"},{"location":"tutorials/getting_started/#Source-term-and-solution","page":"Getting started","title":"Source term and solution","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"We are interested in the scattered field u produced by an incident plane wave u_i = e^i k boldsymbold cdot boldsymbolx, where boldsymbold is a unit vector denoting the direction of the plane wave. Assuming that the total field u_t = u_i + u satisfies a homogenous Neumann condition on Gamma, and that the scattered field u satisfies the Sommerfeld radiation condition, we can write the boundary condition as:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" partial_nu u = -partial_nu u_i quad boldsymbolx in Gamma","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"We can thus solve the boundary integral equation to find u on Gamma:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"using LinearAlgebra\n# define the incident field and compute its normal derivative\nθ = 0\nd = SVector(cos(θ), sin(θ))\ng = map(Q) do q\n # normal derivative of e^{ik*d⃗⋅x}\n x, ν = q.coords, q.normal\n return -im * k * exp(im * k * dot(x, d)) * dot(d, ν)\nend ## Neumann trace on boundary\nu = (-I / 2 + D) \\ (S * g) # Dirichlet trace on boundary\nnothing # hide","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"note: Iterating over a quadrature\nIn computing g above, we used map to evaluate the incident field at all quadrature nodes. When iterating over Q, the iterator returns a QuadratureNode, and not simply the coordinate of the quadrature node. This is so that we can access additional information, such as the normal vector, at the quadrature node.","category":"page"},{"location":"tutorials/getting_started/#Integral-representation-and-visualization","page":"Getting started","title":"Integral representation and visualization","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"Now that we know both the Dirichlet and Neumann data on the boundary, we can use Green's representation formula, i.e.,","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" mathcalDu(boldsymbolr) - mathcalSpartial_nu u(boldsymbolr) = begincases\n u(boldsymbolr) textif boldsymbolr in mathbbR^2 setminus overlineOmega\n 0 textif boldsymbolr in Omega\n endcases","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"where mathcalD and mathcalS are the double- and single-layer potentials defined as:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":" mathcalSsigma(boldsymbolr) = int_Gamma G(boldsymbolr boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly) quad\n mathcalDsigma(boldsymbolr) = int_Gamma fracpartial Gpartial nu_boldsymboly(boldsymbolr boldsymboly) sigma(boldsymboly) mathrmds(boldsymboly)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"to compute the solution u in the domain:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nuₛ = x -> 𝒟[u](x) - 𝒮[g](x)","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"To wrap things up, let's visualize the scattered field:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"xx = yy = range(-5; stop = 5, length = 100)\nU = map(uₛ, Iterators.product(xx, yy))\nUi = map(x -> exp(im*k*dot(x, d)), Iterators.product(xx, yy))\nUt = Ui + U\nfig, ax, hm = heatmap(\n xx,\n yy,\n real(Ut);\n colormap = :inferno,\n interpolate = true,\n axis = (aspect = DataAspect(), xgridvisible = false, ygridvisible = false),\n)\nviz!(msh; segmentsize = 2)\nColorbar(fig[1, 2], hm; label = \"real(u)\")\nfig # hide","category":"page"},{"location":"tutorials/getting_started/#Accuracy-check","page":"Getting started","title":"Accuracy check","text":"","category":"section"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"The scattering example above does not provide an easy way to check the accuracy of the solution. To do so, we can manufacture an exact solution and compare it to the solution obtained numerically, as illustrated below:","category":"page"},{"location":"tutorials/getting_started/","page":"Getting started","title":"Getting started","text":"# build an exact solution\nG = Inti.SingleLayerKernel(op)\ndG = Inti.DoubleLayerKernel(op)\nxs = map(θ -> 0.5 * rand() * SVector(cos(θ), sin(θ)), 2π * rand(10))\ncs = rand(ComplexF64, length(xs))\nuₑ = q -> sum(c * G(x, q) for (x, c) in zip(xs, cs))\n∂ₙu = q -> sum(c * dG(x, q) for (x, c) in zip(xs, cs))\ng = map(∂ₙu, Q) \nu = (-I / 2 + D) \\ (S * g)\nuₛ = x -> 𝒟[u](x) - 𝒮[g](x)\npts = [5*SVector(cos(θ), sin(θ)) for θ in range(0, 2π, length = 100)]\ner = norm(uₛ.(pts) - uₑ.(pts), Inf)\nprintln(\"maximum error on circle of radius 5: $er\")","category":"page"},{"location":"#Inti","page":"Home","title":"Inti","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"CurrentModule = Inti","category":"page"},{"location":"","page":"Home","title":"Home","text":"(Image: Stable) (Image: Dev) (Image: Build Status) (Image: codecov) (Image: Aqua)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Inti.jl is a Julia library for the numerical solution of boundary and volume integral equations. It offers routines for assembling and solving the linear systems that result from applying the Nyström discretization method. Designed for flexibility and efficiency, the package currently supports the following features:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Specialized integration routines for computing singular and nearly-singular integrals.\nIntegrated support for acceleration routines, including the Fast Multipole Method (FMM) and Hierarchical Matrices, by wrapping external libraries.\nPredefined kernels and integral operators for partial differential equations (PDEs) commonly found in mathematical physics (e.g. Laplace, Helmholtz, Stokes).\nSupport for complex geometries in 2D and 3D, either through native parametric representations or by importing mesh files from external sources.\nEfficient construction of complex integral operators from simpler ones through lazy composition.","category":"page"},{"location":"#Installing-Julia","page":"Home","title":"Installing Julia","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Download Julia from julialang.org, or use juliaup installer. We recommend using the latest stable version of Julia, although Inti.jl should work with >=v1.9.","category":"page"},{"location":"#Installing-Inti.jl","page":"Home","title":"Installing Inti.jl","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Inti.jl is registered in the Julia General registry and can be installed by launching a Julia REPL and typing the following command:","category":"page"},{"location":"","page":"Home","title":"Home","text":"]add Inti","category":"page"},{"location":"","page":"Home","title":"Home","text":"Alternatively, one can install the latest version of Inti.jl from the main branch using:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Pkg; Pkg.add(;url = \"https://github.com/IntegralEquations/Inti.jl\", rev = \"main\")","category":"page"},{"location":"","page":"Home","title":"Home","text":"Change rev if a different branch or a specific commit hash is desired.","category":"page"},{"location":"#Installing-weak-dependencies","page":"Home","title":"Installing weak dependencies","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Inti.jl comes with a set of optional dependencies that can be installed on demand. These provide additional features which can be useful in certain scenarios (e.g. visualization, meshing, acceleration). For convenience, Inti.jl provides the stack_weakdeps_env! function to install all the weak dependencies at once:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Inti\nInti.stack_weakdeps_env!(; verbose = false, update = true)","category":"page"},{"location":"","page":"Home","title":"Home","text":"Note that the first time you run this command, it may take a while to download and compile the dependencies. Subsequent runs will be faster. If preferred, extensions can be manually controlled by Pkg.adding the desired packages from the list above.","category":"page"},{"location":"#Basic-usage","page":"Home","title":"Basic usage","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Inti.jl can be used to solve a variety of linear partial differential equations by recasting them as integral equations. The general workflow for solving a problem consists of the following steps:","category":"page"},{"location":"","page":"Home","title":"Home","text":" underbracefboxGeometry rightarrow fboxMesh_textbfpre-processing rightarrow fboxcolorredSolver rightarrow underbracefboxVisualization_textbfpost-processing","category":"page"},{"location":"","page":"Home","title":"Home","text":"Geometry: Define the domain of interest using simple shapes (e.g., circles, rectangles) or more complex CAD models.\nMesh: Create a mesh to approximate the geometry. The mesh is used to define a quadrature and discretize the boundary integral equation.\nSolver: With a mesh and an accompanying quadrature, Inti.jl's routines provide ways to assemble and solve the system of equations arising from the discretization of the integral operators. The core of the library lies in service of this step.\nVisualization: Visualize the solution using a plotting library such as Makie.jl, or export it to a file for further analysis.","category":"page"},{"location":"","page":"Home","title":"Home","text":"As a simple example illustrating the steps above, consider an interior Laplace problem, in two dimensions, with Dirichlet boundary conditions:","category":"page"},{"location":"","page":"Home","title":"Home","text":"beginaligned\nDelta u = 0 quad textin Omega \nu = g quad texton Gamma\nendaligned","category":"page"},{"location":"","page":"Home","title":"Home","text":"where Omega subset mathbbR^2 is a sufficiently smooth domain, and Gamma = partial Omega its boundary. A boundary integral reformulation can be achieved by e.g. searching for the solution u in the form of a single-layer potential:","category":"page"},{"location":"","page":"Home","title":"Home","text":"u(boldsymbolr) = int_Gamma G(boldsymbolrboldsymboly)sigma(boldsymboly) mathrmdGamma(boldsymboly)","category":"page"},{"location":"","page":"Home","title":"Home","text":"where sigma Gamma to mathbbR is an unknown density function, and G is the fundamental solution of the Laplace equation. This ansatz is, by construction, an exact solution to the PDE on Omega. Imposing the boundary condition on Gamma leads to the following integral equation:","category":"page"},{"location":"","page":"Home","title":"Home","text":" int_Gamma G(boldsymbolxboldsymboly)sigma(boldsymboly) mathrmdGamma(boldsymboly) = g(boldsymbolx) quad forall boldsymbolx in Gamma","category":"page"},{"location":"","page":"Home","title":"Home","text":"Expressing the problem above in Inti.jl looks like this:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Inti, LinearAlgebra, StaticArrays\n# create a geometry given by a function f : [0,1] → Γ ⊂ R^2. \ngeo = Inti.parametric_curve(0, 1) do s\n SVector(0.25, 0.0) + SVector(cos(2π * s) + 0.65 * cos(4π * s[1]) - 0.65, 1.5 * sin(2π * s))\nend\nΓ = Inti.Domain(geo)\n# create a mesh and quadrature\nmsh = Inti.meshgen(Γ; meshsize = 0.1)\nQ = Inti.Quadrature(msh; qorder = 5)\n# create the integral operators\nop = Inti.Laplace(;dim=2)\nS, _ = Inti.single_double_layer(;\n op, \n target = Q,\n source = Q,\n compression = (method = :none,),\n correction = (method = :dim,)\n)\n# manufacture a harmonic function (exact solution) and take its trace on Γ\nuₑ = x -> x[1] + x[2] + x[1]*x[2] + x[1]^2 - x[2]^2 - 2 * log(norm(x .- SVector(-0.5, -1.5)))\ng = map(q -> uₑ(q.coords), Q) # value at quad nodes\n# solve for σ\nσ = S \\ g\n# use the single-layer potential to evaluate the solution\n𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)\nuₕ = x -> 𝒮[σ](x)","category":"page"},{"location":"","page":"Home","title":"Home","text":"The function uₕ is now a numerical approximation of the solution to the Laplace equation, and can be evaluated at any point in the domain:","category":"page"},{"location":"","page":"Home","title":"Home","text":"pt = SVector(0.5, 0.1)\nprintln(\"Exact value at $pt: \", uₑ(pt))\nprintln(\"Approx. value at $pt: \", uₕ(pt))","category":"page"},{"location":"","page":"Home","title":"Home","text":"If we care about the solution on the entire domain, we can visualize it using:","category":"page"},{"location":"","page":"Home","title":"Home","text":"using Meshes, GLMakie # trigger the loading of some Inti extensions\nxx = yy = range(-2, 2, length = 100)\nfig = Figure(; size = (600,300))\ninside = x -> Inti.isinside(x, Q) \nopts = (xlabel = \"x\", ylabel = \"y\", aspect = DataAspect())\nax1 = Axis(fig[1, 1]; title = \"Exact solution\", opts...)\nh1 = heatmap!(ax1, xx,yy,(x, y) -> inside((x,y)) ? uₑ((x,y)) : NaN)\nviz!(msh; segmentsize = 3)\ncb = Colorbar(fig[1, 3], h1, size = 20, height = 200)\nax2 = Axis(fig[1, 2]; title = \"Approx. solution\", opts...)\nh2 = heatmap!(ax2, xx,yy, (x, y) -> inside((x,y)) ? uₕ((x,y)) : NaN, colorrange = cb.limits[])\nviz!(msh; segmentsize = 3)\nfig # hide","category":"page"},{"location":"","page":"Home","title":"Home","text":"info: Formulation of the problem as an integral equation\nGiven a PDE and boundary conditions, there are often many ways to recast the problem as an integral equation, and the choice of formulation plays an important role in the unique solvability, efficiency, and accuracy of the numerical solution. Inti.jl provides a flexible framework for experimenting with different formulations, but it is up to the user to choose the most appropriate one for their problem.","category":"page"},{"location":"","page":"Home","title":"Home","text":"While the example above is a simple one, Inti.jl can handle significantly more complex problems involving multiple domains, heterogeneous coefficients, vector-valued PDEs, and three-dimensional geometries. The best way to dive deeper into Inti.jl's capabilities is the tutorials section. More advanced usage can be found in the examples section.","category":"page"},{"location":"#Contributing","page":"Home","title":"Contributing","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"There are several ways to contribute to Inti.jl:","category":"page"},{"location":"","page":"Home","title":"Home","text":"Reporting bugs: If you encounter a bug, please open an issue on the GitHub. If possible, please include a minimal working example that reproduces the problem.\nExamples: If you have a cool example that showcases Inti.jl's capabilities, consider submitting a PR to add it to the examples section.\nContributing code: If you would like to contribute code to Inti.jl, please fork the repository and submit a pull request. Feel free to open a draft PR early in the development process to get feedback on your changes.\nFeature requests: If you have an idea for a new feature or improvement, we would love to hear about it.\nDocumentation: If you find any part of the documentation unclear or incomplete, please let us know. Or even better, submit a PR with the improved documentation.","category":"page"},{"location":"#Acknowledgements","page":"Home","title":"Acknowledgements","text":"","category":"section"}] } diff --git a/dev/tutorials/compression_methods/index.html b/dev/tutorials/compression_methods/index.html index bcbf9c1a..95f284a1 100644 --- a/dev/tutorials/compression_methods/index.html +++ b/dev/tutorials/compression_methods/index.html @@ -11,12 +11,12 @@ K = Inti.SingleLayerKernel(op) Sop = Inti.IntegralOperator(K, Q, Q) x = rand(eltype(Sop), length(Q)) -rtol = 1e-8

In what follows we compress Sop using the different methods available.

Dense matrix

Inti.assemble_matrixFunction
assemble_matrix(iop::IntegralOperator; threads = true)

Assemble a dense matrix representation of an IntegralOperator.

source

Typically used for small problems, the dense matrix representation converts the IntegralOperator into a Matrix object. The underlying type of the Matrix is determined by the eltype of the IntegralOperator, and depends on the inferred type of the kernel. Here is how assemble_matrix can be used:

Smat = Inti.assemble_matrix(Sop; threads=true)
+rtol = 1e-8

In what follows we compress Sop using the different methods available.

Dense matrix

Inti.assemble_matrixFunction
assemble_matrix(iop::IntegralOperator; threads = true)

Assemble a dense matrix representation of an IntegralOperator.

source

Typically used for small problems, the dense matrix representation converts the IntegralOperator into a Matrix object. The underlying type of the Matrix is determined by the eltype of the IntegralOperator, and depends on the inferred type of the kernel. Here is how assemble_matrix can be used:

Smat = Inti.assemble_matrix(Sop; threads=true)
 er = norm(Sop * x - Smat * x, Inf) / norm(Sop * x, Inf)
-println("Forward map error: $er")
Forward map error: 1.0628179476665614e-14

Since the returned object is plain Julia Matrix, it can be used with any of the linear algebra routines available in Julia (e.g. \, lu, qr, *, etc.)

Hierarchical matrix

Inti.assemble_hmatrixFunction
assemble_hmatrix(iop[; atol, rank, rtol, eta])

Assemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.

See the documentation of HMatrices for more details on usage and other keyword arguments.

source

The hierarchical matrix representation is a compressed representation of the underlying operator; as such, it takes a tolerance parameter that determines the relative error of the compression. Here is an example of how to use the assemble_hmatrix method to compress the previous problem:

using HMatrices
+println("Forward map error: $er")
Forward map error: 9.256264614296266e-15

Since the returned object is plain Julia Matrix, it can be used with any of the linear algebra routines available in Julia (e.g. \, lu, qr, *, etc.)

Hierarchical matrix

Inti.assemble_hmatrixFunction
assemble_hmatrix(iop[; atol, rank, rtol, eta])

Assemble an H-matrix representation of the discretized integral operator iop using the HMatrices.jl library.

See the documentation of HMatrices for more details on usage and other keyword arguments.

source

The hierarchical matrix representation is a compressed representation of the underlying operator; as such, it takes a tolerance parameter that determines the relative error of the compression. Here is an example of how to use the assemble_hmatrix method to compress the previous problem:

using HMatrices
 Shmat = Inti.assemble_hmatrix(Sop; rtol = 1e-8)
 er = norm(Smat * x - Shmat * x, Inf) / norm(Smat * x, Inf)
-println("Forward map error: $er")
Forward map error: 2.051060830481776e-8

Note that HMatrices are said to be kernel-independent, meaning that they efficiently compress a wide range of integral operators provided they satisfy a certain asymptotic smoothness criterion (see e.g. [3, 4]).

The HMatrix object can be used to solve linear systems, both iteratively through e.g. GMRES, or directly using an LU factorization.

Fast multipole method

Inti.assemble_fmmFunction
assemble_fmm(iop; atol)

Set up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.

FMMLIB2D

FMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.

source

The fast multipole method (FMM) is an acceleration technique based on an analytic multipole expansion of the kernel in the integral operator [5, 6]. It provides a very memory-efficient and fast way to evaluate certain types of integral operators. Here is how assemble_fmm can be used:

using FMM3D
+println("Forward map error: $er")
Forward map error: 2.395040782211143e-8

Note that HMatrices are said to be kernel-independent, meaning that they efficiently compress a wide range of integral operators provided they satisfy a certain asymptotic smoothness criterion (see e.g. [3, 4]).

The HMatrix object can be used to solve linear systems, both iteratively through e.g. GMRES, or directly using an LU factorization.

Fast multipole method

Inti.assemble_fmmFunction
assemble_fmm(iop; atol)

Set up a 2D or 3D FMM for evaluating the discretized integral operator iop associated with the op. In 2D the FMM2D or FMMLIB2D library is used (whichever was most recently loaded) while in 3D FMM3D is used.

FMMLIB2D

FMMLIB2D does no checking for if the targets and sources coincide, and will return Inf values if iop.target !== iop.source, but there is a point x ∈ iop.target such that x ∈ iop.source.

source

The fast multipole method (FMM) is an acceleration technique based on an analytic multipole expansion of the kernel in the integral operator [5, 6]. It provides a very memory-efficient and fast way to evaluate certain types of integral operators. Here is how assemble_fmm can be used:

using FMM3D
 Sfmm = Inti.assemble_fmm(Sop; rtol = 1e-8)
 er = norm(Sop * x - Sfmm * x, Inf) / norm(Sop * x, Inf)
-println("Forward map error: $er")
Forward map error: 1.439239611850339e-10

Tips on choosing a compression method

The choice of compression method depends on the problem at hand, as well as on the available hardware. Here is a rough guide on how to choose a compression:

  1. For small problems (say less than 5k degrees of freedom), use the dense matrix representation. It is the simplest and most straightforward method, and does not require any additional packages. It is also the most accurate since it does not introduce any approximation errors.
  2. If the integral operator is supported by the assemble_fmm, and if an iterative solver is acceptable, use it. The FMM is a very efficient method for certain types of kernels, and can handle problems with up to a few million degrees of freedom on a laptop.
  3. If the kernel is not supported by assemble_fmm, if iterative solvers are not an option, or if the system needs solution for many right-hand sides, use the assemble_hmatrix method. It is a very general method that can handle a wide range of kernels, and although assembling the HMatrix can be time and memory consuming (the complexity is still log-linear in the DOFs for many kernels of interest, but the constants can be large), the resulting HMatrix object is very efficient to use. For example, the forward map is usually significantly faster than the one obtained through assemble_fmm.
+println("Forward map error: $er")
Forward map error: 1.4642155346045475e-10

Tips on choosing a compression method

The choice of compression method depends on the problem at hand, as well as on the available hardware. Here is a rough guide on how to choose a compression:

  1. For small problems (say less than 5k degrees of freedom), use the dense matrix representation. It is the simplest and most straightforward method, and does not require any additional packages. It is also the most accurate since it does not introduce any approximation errors.
  2. If the integral operator is supported by the assemble_fmm, and if an iterative solver is acceptable, use it. The FMM is a very efficient method for certain types of kernels, and can handle problems with up to a few million degrees of freedom on a laptop.
  3. If the kernel is not supported by assemble_fmm, if iterative solvers are not an option, or if the system needs solution for many right-hand sides, use the assemble_hmatrix method. It is a very general method that can handle a wide range of kernels, and although assembling the HMatrix can be time and memory consuming (the complexity is still log-linear in the DOFs for many kernels of interest, but the constants can be large), the resulting HMatrix object is very efficient to use. For example, the forward map is usually significantly faster than the one obtained through assemble_fmm.
diff --git a/dev/tutorials/correction_methods/index.html b/dev/tutorials/correction_methods/index.html index dfacce78..98739ec9 100644 --- a/dev/tutorials/correction_methods/index.html +++ b/dev/tutorials/correction_methods/index.html @@ -1,2 +1,2 @@ -Correction methods · Inti.jl

Correction methods

Work in progress

This tutorial is still a work in progress. We will update it with more details and examples in the future.

Important points covered in this tutorial
  • Overview of the correction methods available in Inti.jl
  • Details and limitations of the various correction methods
  • Guideline on how to choose a correction method

When the underlying kernel is singular, a correction is usually necessary in order to obtain accurate results in the approximation of the underlying integral operator by a quadrature. At present, Inti.jl provides the following functions to correct for singularities:

They have different strengths and weaknesses, and we will discuss them in the following sections.

High-level API

Note that the single_double_layer, adj_double_layer_hypersingular, and volume_potential functions have high-level API with a correction keyword argument that allows one to specify the correction method to use when constructing the integral operators; see the documentation of these functions for more details.

Adaptive correction

Boundary density interpolation method

Volume density interpolation method

Martensen-Kussmaul method

+Correction methods · Inti.jl

Correction methods

Work in progress

This tutorial is still a work in progress. We will update it with more details and examples in the future.

Important points covered in this tutorial
  • Overview of the correction methods available in Inti.jl
  • Details and limitations of the various correction methods
  • Guideline on how to choose a correction method

When the underlying kernel is singular, a correction is usually necessary in order to obtain accurate results in the approximation of the underlying integral operator by a quadrature. At present, Inti.jl provides the following functions to correct for singularities:

They have different strengths and weaknesses, and we will discuss them in the following sections.

High-level API

Note that the single_double_layer, adj_double_layer_hypersingular, and volume_potential functions have high-level API with a correction keyword argument that allows one to specify the correction method to use when constructing the integral operators; see the documentation of these functions for more details.

Adaptive correction

Boundary density interpolation method

Volume density interpolation method

Martensen-Kussmaul method

diff --git a/dev/tutorials/geo_and_meshes/06758f52.png b/dev/tutorials/geo_and_meshes/06758f52.png deleted file mode 100644 index a5b386b4..00000000 Binary files a/dev/tutorials/geo_and_meshes/06758f52.png and /dev/null differ diff --git a/dev/tutorials/geo_and_meshes/19f16ee7.png b/dev/tutorials/geo_and_meshes/19f16ee7.png new file mode 100644 index 00000000..2d469213 Binary files /dev/null and b/dev/tutorials/geo_and_meshes/19f16ee7.png differ diff --git a/dev/tutorials/geo_and_meshes/1e26d839.png b/dev/tutorials/geo_and_meshes/1e26d839.png deleted file mode 100644 index 91c07368..00000000 Binary files a/dev/tutorials/geo_and_meshes/1e26d839.png and /dev/null differ diff --git a/dev/tutorials/geo_and_meshes/27c94bd6.png b/dev/tutorials/geo_and_meshes/27c94bd6.png new file mode 100644 index 00000000..a461d80f Binary files /dev/null and b/dev/tutorials/geo_and_meshes/27c94bd6.png differ diff --git a/dev/tutorials/geo_and_meshes/3166a00b.png b/dev/tutorials/geo_and_meshes/3166a00b.png new file mode 100644 index 00000000..28868b71 Binary files /dev/null and b/dev/tutorials/geo_and_meshes/3166a00b.png differ diff --git a/dev/tutorials/geo_and_meshes/3a92ac05.png b/dev/tutorials/geo_and_meshes/3a92ac05.png new file mode 100644 index 00000000..23910f2d Binary files /dev/null and b/dev/tutorials/geo_and_meshes/3a92ac05.png differ diff --git a/dev/tutorials/geo_and_meshes/61ed0170.png b/dev/tutorials/geo_and_meshes/61ed0170.png deleted file mode 100644 index 7e978ee3..00000000 Binary files a/dev/tutorials/geo_and_meshes/61ed0170.png and /dev/null differ diff --git a/dev/tutorials/geo_and_meshes/80eccad5.png b/dev/tutorials/geo_and_meshes/80eccad5.png new file mode 100644 index 00000000..53631c04 Binary files /dev/null and b/dev/tutorials/geo_and_meshes/80eccad5.png differ diff --git a/dev/tutorials/geo_and_meshes/b73b6410.png b/dev/tutorials/geo_and_meshes/b73b6410.png deleted file mode 100644 index 8bdfa8f4..00000000 Binary files a/dev/tutorials/geo_and_meshes/b73b6410.png and /dev/null differ diff --git a/dev/tutorials/geo_and_meshes/e198da05.png b/dev/tutorials/geo_and_meshes/e198da05.png deleted file mode 100644 index 1c849cb5..00000000 Binary files a/dev/tutorials/geo_and_meshes/e198da05.png and /dev/null differ diff --git a/dev/tutorials/geo_and_meshes/index.html b/dev/tutorials/geo_and_meshes/index.html index 6905de2b..61810ef1 100644 --- a/dev/tutorials/geo_and_meshes/index.html +++ b/dev/tutorials/geo_and_meshes/index.html @@ -3,14 +3,14 @@ using Gmsh filename = joinpath(Inti.PROJECT_ROOT,"docs", "assets", "piece.msh") msh = Inti.import_mesh(filename)
Inti.Mesh{3, Float64} containing:
-	 852 elements of type Inti.LagrangeElement{Inti.ReferenceHyperCube{1}, 2, StaticArraysCore.SVector{3, Float64}}
+	 126 elements of type StaticArraysCore.SVector{3, Float64}
 	 14035 elements of type Inti.LagrangeElement{Inti.ReferenceSimplex{3}, 4, StaticArraysCore.SVector{3, Float64}}
 	 7156 elements of type Inti.LagrangeElement{Inti.ReferenceSimplex{2}, 3, StaticArraysCore.SVector{3, Float64}}
-	 126 elements of type StaticArraysCore.SVector{3, Float64}

The imported mesh contains elements of several types, used to represent the segments, triangles, and tetras used to approximate the geometry:

Inti.element_types(msh)
KeySet for a Dict{DataType, Matrix{Int64}} with 4 entries. Keys:
-  Inti.LagrangeElement{Inti.ReferenceHyperCube{1}, 2, StaticArraysCore.SVector{…
+	 852 elements of type Inti.LagrangeElement{Inti.ReferenceHyperCube{1}, 2, StaticArraysCore.SVector{3, Float64}}

The imported mesh contains elements of several types, used to represent the segments, triangles, and tetras used to approximate the geometry:

Inti.element_types(msh)
KeySet for a Dict{DataType, Matrix{Int64}} with 4 entries. Keys:
+  StaticArraysCore.SVector{3, Float64}
   Inti.LagrangeElement{Inti.ReferenceSimplex{3}, 4, StaticArraysCore.SVector{3,…
   Inti.LagrangeElement{Inti.ReferenceSimplex{2}, 3, StaticArraysCore.SVector{3,…
-  StaticArraysCore.SVector{3, Float64}

Note that the msh object contains all entities used to construct the mesh, usually defined in a .geo file, which can be extracted using the entities:

ents = Inti.entities(msh)

Filtering of entities satisfying a certain condition, e.g., entities of a given dimension or containing a certain label, can also be performed in order to construct a domain:

filter = e -> Inti.geometric_dimension(e) == 3
+  Inti.LagrangeElement{Inti.ReferenceHyperCube{1}, 2, StaticArraysCore.SVector{…

Note that the msh object contains all entities used to construct the mesh, usually defined in a .geo file, which can be extracted using the entities:

ents = Inti.entities(msh)

Filtering of entities satisfying a certain condition, e.g., entities of a given dimension or containing a certain label, can also be performed in order to construct a domain:

filter = e -> Inti.geometric_dimension(e) == 3
 Ω = Inti.Domain(filter, ents)
Domain with 4 entities
  EntityKey: (3, 3) => Inti.GeometricEntity with labels String[]
  EntityKey: (3, 6) => Inti.GeometricEntity with labels String[]
@@ -22,7 +22,7 @@
 fig = Figure(; size = (800,400))
 ax = Axis3(fig[1, 1]; aspect = :data)
 viz!(Γ_msh; showsegments = true, alpha = 0.5)
-fig
Example block output
Mesh visualization

Note that although the mesh may be of high order and/or conforming, the visualization of a mesh is always performed on the underlying first order mesh, and therefore elements may look flat even if the problem is solved on a curved mesh.

Parametric entities and meshgen

In the previous section we saw an example of how to import a mesh from a file, and how to extract the entities from the mesh. For simple geometries for which an explicit parametrization is available, Inti.jl provides a way to create and manipulate geometrical entities and their associated meshes.

Parametric curves

The simplest parametric shapes are parametric_curves, which are defined by a function that maps a scalar parameter t to a point in 2D or 3D space. Parametric curves are expected to return an SVector, and can be created as follows:

using StaticArrays
+fig
Example block output
Mesh visualization

Note that although the mesh may be of high order and/or conforming, the visualization of a mesh is always performed on the underlying first order mesh, and therefore elements may look flat even if the problem is solved on a curved mesh.

Parametric entities and meshgen

In the previous section we saw an example of how to import a mesh from a file, and how to extract the entities from the mesh. For simple geometries for which an explicit parametrization is available, Inti.jl provides a way to create and manipulate geometrical entities and their associated meshes.

Parametric curves

The simplest parametric shapes are parametric_curves, which are defined by a function that maps a scalar parameter t to a point in 2D or 3D space. Parametric curves are expected to return an SVector, and can be created as follows:

using StaticArrays
 l1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = ["l₁"])
EntityKey: (1, 199) => Inti.GeometricEntity with labels ["l₁"]

The object l1 represents a GeometricEntity with a known push-forward map:

Inti.pushforward(l1)
(domain = Inti.HyperRectangle{1, Float64}([0.0], [1.0]), parametrization = Inti.var"#120#122"{Main.var"Main".var"#3#4"}(Main.var"Main".var"#3#4"()))

For the sake of this example, let's create three more curves, and group them together to form a Domain:

l2 = Inti.parametric_curve(x->SVector(1 + 0.1 * sin(2π * x), x), 0.0, 1.0, labels = ["l₂"])
 l3 = Inti.parametric_curve(x->SVector(1 - x, 1 - 0.1 * sin(2π * x)), 0.0, 1.0, labels = ["l₃"])
 l4 = Inti.parametric_curve(x->SVector(0.1 * sin(2π * x), 1 - x), 0.0, 1.0, labels = ["l₄"])
@@ -30,7 +30,7 @@
  EntityKey: (1, 201) => Inti.GeometricEntity with labels ["l₃"]
  EntityKey: (1, 202) => Inti.GeometricEntity with labels ["l₄"]
  EntityKey: (1, 199) => Inti.GeometricEntity with labels ["l₁"]
- EntityKey: (1, 200) => Inti.GeometricEntity with labels ["l₂"]

Domains for which a parametric representation is available can be passed to the meshgen function:

msh = Inti.meshgen(Γ; meshsize = 0.05)

We can use the Meshes.viz function to visualize the mesh, and use domains to index the mesh:

Γ₁ = l1 ∪ l3
+ EntityKey: (1, 200) => Inti.GeometricEntity with labels ["l₂"]

Domains for which a parametric representation is available can be passed to the meshgen function:

msh = Inti.meshgen(Γ; meshsize = 0.05)

We can use the Meshes.viz function to visualize the mesh, and use domains to index the mesh:

Γ₁ = l1 ∪ l3
 Γ₂ = l2 ∪ l4
 fig, ax, pl = viz(view(msh, Γ₁); segmentsize = 4,  label = "Γ₁")
 viz!(view(msh, Γ₂); segmentsize = 4, color = :red, label = "Γ₂")
Example block output

Note that the orientation of the curve determines the direction of the normal vector. The normal points to the right of the curve when moving in the direction of increasing parameter t:

pts, tangents, normals = Makie.Point2f[], Makie.Vec2f[], Makie.Vec2f[]
@@ -62,19 +62,19 @@
       ax = Axis3(fig[i,j]; aspect = :data, title = shape)
       hidedecorations!(ax)
       viz!(msh; showsegments = true)
-end
Example block output

See GeometricEntity(shape::String) for a list of predefined geometries.

Mesh quality

The quality of the generated mesh created through meshgen depends heavily on the quality of the underlying parametrization. For surfaces containing a degenerate parametrization, or for complex shapes, one is better off using a suitable CAD (Computer-Aided Design) software in conjunction with a mesh generator.

Transfinite domains

It is possible to combine parametric curves/surfaces to form a transfinite domain where the parametrization is inherited from the curves/surfaces that form its boundary. At present, Inti.jl only supports transfinite squares, which are defined by four parametric curves:

l1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = ["l₁"])
+end
Example block output

See GeometricEntity(shape::String) for a list of predefined geometries.

Mesh quality

The quality of the generated mesh created through meshgen depends heavily on the quality of the underlying parametrization. For surfaces containing a degenerate parametrization, or for complex shapes, one is better off using a suitable CAD (Computer-Aided Design) software in conjunction with a mesh generator.

Transfinite domains

It is possible to combine parametric curves/surfaces to form a transfinite domain where the parametrization is inherited from the curves/surfaces that form its boundary. At present, Inti.jl only supports transfinite squares, which are defined by four parametric curves:

l1 = Inti.parametric_curve(x->SVector(x, 0.1 * sin(2π * x)), 0.0, 1.0, labels = ["l₁"])
 l2 = Inti.parametric_curve(x->SVector(1 + 0.1 * sin(2π * x), x), 0.0, 1.0, labels = ["l₂"])
 l3 = Inti.parametric_curve(x->SVector(1 - x, 1 - 0.1 * sin(2π * x)), 0.0, 1.0, labels = ["l₃"])
 l4 = Inti.parametric_curve(x->SVector(0.1 * sin(2π * x), 1 - x), 0.0, 1.0, labels = ["l₄"])
 surf = Inti.transfinite_square(l1, l2, l3, l4; labels = ["Ω"])
 Ω = Inti.Domain(surf)
 msh = Inti.meshgen(Ω; meshsize = 0.05)
-viz(msh; showsegments = true)
Example block output

Note that the msh object contains all entities used to construct Ω, including the boundary segments:

Inti.entities(msh)
KeySet for a Dict{Inti.EntityKey, Dict{DataType, Vector{Int64}}} with 5 entries. Keys:
+viz(msh; showsegments = true)
Example block output

Note that the msh object contains all entities used to construct Ω, including the boundary segments:

Inti.entities(msh)
KeySet for a Dict{Inti.EntityKey, Dict{DataType, Vector{Int64}}} with 5 entries. Keys:
   EntityKey: (1, 309) => Inti.GeometricEntity with labels ["l₃"]
   EntityKey: (1, 308) => Inti.GeometricEntity with labels ["l₂"]
   EntityKey: (1, 310) => Inti.GeometricEntity with labels ["l₄"]
   EntityKey: (1, 307) => Inti.GeometricEntity with labels ["l₁"]
-  EntityKey: (2, 102) => Inti.GeometricEntity with labels ["Ω"]

This allows us to probe the msh object to extract e.g. the boundary mesh:

viz(msh[Inti.boundary(Ω)]; color = :red)
Example block output
Limitations

At present only the transfinite interpolation for the logically quadrilateral domains is supported. In the future we hope to add support for three-dimensional transfinite interpolation, as well as transfinite formulas for simplices.

Elements of a mesh

To iterate over the elements of a mesh, use the elements function:

filename = joinpath(Inti.PROJECT_ROOT,"docs", "assets", "piece.msh")
+  EntityKey: (2, 102) => Inti.GeometricEntity with labels ["Ω"]

This allows us to probe the msh object to extract e.g. the boundary mesh:

viz(msh[Inti.boundary(Ω)]; color = :red)
Example block output
Limitations

At present only the transfinite interpolation for the logically quadrilateral domains is supported. In the future we hope to add support for three-dimensional transfinite interpolation, as well as transfinite formulas for simplices.

Elements of a mesh

To iterate over the elements of a mesh, use the elements function:

filename = joinpath(Inti.PROJECT_ROOT,"docs", "assets", "piece.msh")
 msh = Inti.import_mesh(filename)
 ents = Inti.entities(msh)
 Ω = Inti.Domain(e -> Inti.geometric_dimension(e) == 3, ents)
@@ -82,9 +82,9 @@
 centers = map(el -> Inti.center(el), els)
 fig = Figure(; size = (800,400))
 ax = Axis3(fig[1, 1]; aspect = :data)
-scatter!([c[1] for c in centers], [c[2] for c in centers], [c[3] for c in centers], markersize = 5)
Example block output

This example shows how to extract the centers of the tetrahedral elements in the mesh; and of course we can perform any desired computation on the elements.

Type-stable iteration over elements

Since a mesh in Inti.jl can contain elements of various types, the elements function above is not type-stable. For a type-stable iterator approach, one should first iterate over the element types using element_types, and then use elements(msh, E) to iterate over a specific element type E.

Under the hood, each element is simply a functor which maps points from a ReferenceShape into the physical space:

el = first(els)
+scatter!([c[1] for c in centers], [c[2] for c in centers], [c[3] for c in centers], markersize = 5)
Example block output

This example shows how to extract the centers of the tetrahedral elements in the mesh; and of course we can perform any desired computation on the elements.

Type-stable iteration over elements

Since a mesh in Inti.jl can contain elements of various types, the elements function above is not type-stable. For a type-stable iterator approach, one should first iterate over the element types using element_types, and then use elements(msh, E) to iterate over a specific element type E.

Under the hood, each element is simply a functor which maps points from a ReferenceShape into the physical space:

el = first(els)
 x̂ = SVector(1/3,1/3, 1/3)
 el(x̂)
3-element StaticArraysCore.SVector{3, Float64} with indices SOneTo(3):
   0.18054505519780645
  -0.14630186206938434
-  0.09955887205829847

Likewise, we can compute the jacobian of the element, or its normal at a given parametric coordinate.

+ 0.09955887205829847

Likewise, we can compute the jacobian of the element, or its normal at a given parametric coordinate.

diff --git a/dev/tutorials/getting_started/index.html b/dev/tutorials/getting_started/index.html index a4beb315..5c52d4c1 100644 --- a/dev/tutorials/getting_started/index.html +++ b/dev/tutorials/getting_started/index.html @@ -72,4 +72,4 @@ uₛ = x -> 𝒟[u](x) - 𝒮[g](x) pts = [5*SVector(cos(θ), sin(θ)) for θ in range(0, 2π, length = 100)] er = norm(uₛ.(pts) - uₑ.(pts), Inf) -println("maximum error on circle of radius 5: $er")
maximum error on circle of radius 5: 6.875677849137738e-11
+println("maximum error on circle of radius 5: $er")
maximum error on circle of radius 5: 4.8614979179442054e-11
diff --git a/dev/tutorials/integral_operators/index.html b/dev/tutorials/integral_operators/index.html index 949a64ec..d47fd625 100644 --- a/dev/tutorials/integral_operators/index.html +++ b/dev/tutorials/integral_operators/index.html @@ -156,4 +156,4 @@ max number of elements per leaf: 2468041 depth of tree: 0 compression ratio: 15.270194 -

All of these should give an identical matrix-vector product, but the latter two allow e.g. for the use of direct solvers though an LU factorization.

Limitations

Integral operators defined from custom kernel functions do not support all the features of the predefined ones. In particular, some singular integration methods (e.g. the Density Interpolation Method) and acceleration routines (e.g. Fast Multipole Method) used to correct for singular and nearly singular integral operators, and to accelerate the matrix vector products, are only available for specific kernels. Check the corrections and compression for more details concerning which methods are compatible with custom kernels.

+

All of these should give an identical matrix-vector product, but the latter two allow e.g. for the use of direct solvers though an LU factorization.

Limitations

Integral operators defined from custom kernel functions do not support all the features of the predefined ones. In particular, some singular integration methods (e.g. the Density Interpolation Method) and acceleration routines (e.g. Fast Multipole Method) used to correct for singular and nearly singular integral operators, and to accelerate the matrix vector products, are only available for specific kernels. Check the corrections and compression for more details concerning which methods are compatible with custom kernels.

diff --git a/dev/tutorials/layer_potentials/6c26d330.png b/dev/tutorials/layer_potentials/6c26d330.png deleted file mode 100644 index 3290d8fc..00000000 Binary files a/dev/tutorials/layer_potentials/6c26d330.png and /dev/null differ diff --git a/dev/tutorials/layer_potentials/86493074.png b/dev/tutorials/layer_potentials/86493074.png new file mode 100644 index 00000000..c5ca4e33 Binary files /dev/null and b/dev/tutorials/layer_potentials/86493074.png differ diff --git a/dev/tutorials/layer_potentials/a9b241ca.png b/dev/tutorials/layer_potentials/a9b241ca.png new file mode 100644 index 00000000..36a0dafe Binary files /dev/null and b/dev/tutorials/layer_potentials/a9b241ca.png differ diff --git a/dev/tutorials/layer_potentials/d9ee0647.png b/dev/tutorials/layer_potentials/d9ee0647.png new file mode 100644 index 00000000..fbc75838 Binary files /dev/null and b/dev/tutorials/layer_potentials/d9ee0647.png differ diff --git a/dev/tutorials/layer_potentials/ebed9353.png b/dev/tutorials/layer_potentials/ebed9353.png deleted file mode 100644 index 63844ced..00000000 Binary files a/dev/tutorials/layer_potentials/ebed9353.png and /dev/null differ diff --git a/dev/tutorials/layer_potentials/ecffc3fd.png b/dev/tutorials/layer_potentials/ecffc3fd.png deleted file mode 100644 index a3a1afd3..00000000 Binary files a/dev/tutorials/layer_potentials/ecffc3fd.png and /dev/null differ diff --git a/dev/tutorials/layer_potentials/index.html b/dev/tutorials/layer_potentials/index.html index 65ff0f22..8a7fbb81 100644 --- a/dev/tutorials/layer_potentials/index.html +++ b/dev/tutorials/layer_potentials/index.html @@ -11,7 +11,7 @@ # and a quadrature of Γ Q = Inti.Quadrature(Γ; meshsize = 0.1, qorder = 5) 𝒮 = Inti.IntegralPotential(K, Q)
Inti.IntegralPotential{typeof(Main.K), Inti.Quadrature{2, Float64}}(Main.K,  Quadrature with 378 quadrature nodes)

If we have a source density $\sigma$, defined on the quadrature nodes of $\Gamma$, we can create a function that evaluates the layer potential at an arbitrary point:

σ = map(q -> 1.0, Q)
-u = 𝒮[σ]
#231 (generic function with 1 method)

u is now an anonymous function that evaluates the layer potential at any point:

r = SVector(0.1, 0.2)
+u = 𝒮[σ]
#233 (generic function with 1 method)

u is now an anonymous function that evaluates the layer potential at any point:

r = SVector(0.1, 0.2)
 u(r)
-0.9999999999999994

Although we created the single-layer potential for the Laplace kernel manually, it is often more convenient to use the single_layer_potential when working with a supported PDE, e.g.:

op = Inti.Laplace(; dim = 2)
 𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)
(Inti.IntegralPotential{Inti.SingleLayerKernel{Float64, Inti.Laplace{2}}, Inti.Quadrature{2, Float64}}(Inti.SingleLayerKernel{Float64, Inti.Laplace{2}}(Laplace operator in 2 dimensions: -Δu),  Quadrature with 378 quadrature nodes), Inti.IntegralPotential{Inti.DoubleLayerKernel{Float64, Inti.Laplace{2}}, Inti.Quadrature{2, Float64}}(Inti.DoubleLayerKernel{Float64, Inti.Laplace{2}}(Laplace operator in 2 dimensions: -Δu),  Quadrature with 378 quadrature nodes))

creates the single and double layer potentials for the Laplace equation in 2D.

Direct evaluation of layer potentials

We now show how to evaluate the layer potentials of an exact solution on a mesh created through the Gmsh API. Do to so, let us first define the PDE:g

using Inti, StaticArrays, LinearAlgebra, Meshes, GLMakie, Gmsh
 # define the PDE
@@ -28,10 +28,10 @@
 msh = Inti.import_mesh(; dim = 2)
 gmsh.finalize()
Info    : Meshing 1D...
 Info    : Meshing curve 1 (BSpline)
-Info    : Done meshing 1D (Wall 0.0111653s, CPU 0.011169s)
+Info    : Done meshing 1D (Wall 0.0115573s, CPU 0.01156s)
 Info    : Meshing 2D...
 Info    : Meshing surface 1 (Plane, Frontal-Delaunay)
-Info    : Done meshing 2D (Wall 0.0684235s, CPU 0.068412s)
+Info    : Done meshing 2D (Wall 0.0649873s, CPU 0.06498s)
 Info    : 505 nodes 911 elements
Tip

The GMSH API is a powerful tool to create complex geometries and meshes directly from Julia (the gmsh_curve function above is just a simple wrapper around some spline functionality). For more information, see the official documentation.

We can visualize the triangular mesh using:

using Meshes, GLMakie
 # extract the domain Ω from the mesh entities
 ents = Inti.entities(msh)
@@ -44,7 +44,7 @@
 # plot the exact solution
 Ω_msh = view(msh, Ω)
 target = Inti.nodes(Ω_msh)
-viz(Ω_msh; showsegments = false, axis = (aspect = DataAspect(), ), color = real(u.(target)))
Example block output

Since u satisfies the Helmholtz equation, we know that the following representation holds:

\[u(\boldsymbol{r}) = \mathcal{S}[\gamma_1 u](\boldsymbol{r}) - \mathcal{D}[\gamma_0 u](\boldsymbol{r}), \quad \boldsymbol{r} \in \Omega\]

where $\gamma_0 u$ and $\gamma_1 u$ are the respective Dirichlet and Neumann traces of $u$, and $\mathcal{S}$ and $\mathcal{D}$ are the respective single and double layer potentials over $\Gamma := \partial \Omega$.

Let's compare next the exact solution with the layer potential evaluation, based on a quadrature of $\Gamma$:

Γ = Inti.boundary(Ω)
+viz(Ω_msh; showsegments = false, axis = (aspect = DataAspect(), ), color = real(u.(target)))
Example block output

Since u satisfies the Helmholtz equation, we know that the following representation holds:

\[u(\boldsymbol{r}) = \mathcal{S}[\gamma_1 u](\boldsymbol{r}) - \mathcal{D}[\gamma_0 u](\boldsymbol{r}), \quad \boldsymbol{r} \in \Omega\]

where $\gamma_0 u$ and $\gamma_1 u$ are the respective Dirichlet and Neumann traces of $u$, and $\mathcal{S}$ and $\mathcal{D}$ are the respective single and double layer potentials over $\Gamma := \partial \Omega$.

Let's compare next the exact solution with the layer potential evaluation, based on a quadrature of $\Gamma$:

Γ = Inti.boundary(Ω)
 Q = Inti.Quadrature(view(msh,Γ); qorder = 5)
 # evaluate the layer potentials
 𝒮, 𝒟 = Inti.single_double_layer_potential(; op, source = Q)
@@ -62,7 +62,7 @@
     interpolate=true
 )
 Colorbar(fig[1, 2]; label = "log₁₀(error)", colorrange)
-fig
Example block output

We see a common pattern of potential evaluation: the error is small away from the boundary, but grows near it. This is due to the nearly-singular nature of the layer potential integrals, which can be mitigated by using a correction method that accounts for the singularity of the kernel as $\boldsymbol{r} \to \Gamma$.

Near-field correction of layer potentials

There are two cases where the direct evaluation of layer potentials is not recommended:

  1. When the target point is close to the boundary (nearly-singular integrals).
  2. When evaluation at many target points is desired (computationally burdensome)and take advantage of an acceleration routine.

In such contexts, it is recommended to use the single_double_layer function (alternately, one can directly assemble an IntegralOperator) with a correction, for the first case, and/or a compression (acceleration) method, for the latter case, as appropriate. Here is an example of how to use the FMM acceleration with a near-field correction to evaluate the layer potentials::

using FMM2D
+fig
Example block output

We see a common pattern of potential evaluation: the error is small away from the boundary, but grows near it. This is due to the nearly-singular nature of the layer potential integrals, which can be mitigated by using a correction method that accounts for the singularity of the kernel as $\boldsymbol{r} \to \Gamma$.

Near-field correction of layer potentials

There are two cases where the direct evaluation of layer potentials is not recommended:

  1. When the target point is close to the boundary (nearly-singular integrals).
  2. When evaluation at many target points is desired (computationally burdensome)and take advantage of an acceleration routine.

In such contexts, it is recommended to use the single_double_layer function (alternately, one can directly assemble an IntegralOperator) with a correction, for the first case, and/or a compression (acceleration) method, for the latter case, as appropriate. Here is an example of how to use the FMM acceleration with a near-field correction to evaluate the layer potentials::

using FMM2D
 S, D = Inti.single_double_layer(; op, target, source = Q,
     compression = (method = :fmm, tol = 1e-12),
     correction = (method = :dim, target_location = :inside, maxdist = 0.2)
@@ -75,4 +75,4 @@
 ax2 = Axis(fig[1, 2], aspect = DataAspect(), title = "Nearfield correction")
 viz!(Ω_msh; color = er_log10_cor, colormap = :viridis, colorrange, interpolate=true)
 Colorbar(fig[1, 3]; label = "log₁₀(error)", colorrange)
-fig
Example block output

As can be seen, the near-field correction significantly reduces the error near the boundary, making if feasible to evaluate the layer potential near $\Gamma$ if necessary.

+figExample block output

As can be seen, the near-field correction significantly reduces the error near the boundary, making if feasible to evaluate the layer potential near $\Gamma$ if necessary.

diff --git a/dev/tutorials/solvers/index.html b/dev/tutorials/solvers/index.html index 1deaec97..393b326a 100644 --- a/dev/tutorials/solvers/index.html +++ b/dev/tutorials/solvers/index.html @@ -1,2 +1,2 @@ -Linear solvers · Inti.jl

Linear solvers

Work in progress

This tutorial is still a work in progress. We will update it with more details and examples in the future.

Inti.jl does not provide its own linear solvers, but relies on external libraries such as IterativeSolvers.jl or the LinearAlgebra standard library for the solving the linear systems that arise in the discretization of integral equations.

+Linear solvers · Inti.jl

Linear solvers

Work in progress

This tutorial is still a work in progress. We will update it with more details and examples in the future.

Inti.jl does not provide its own linear solvers, but relies on external libraries such as IterativeSolvers.jl or the LinearAlgebra standard library for the solving the linear systems that arise in the discretization of integral equations.