Bramble.jl GitHub

Inner products and norms#

Three families of weights, each with its own inner product:

Inner product Weight at a point Use
innerₕ the cell measure, the product of the per-axis cell widths the discrete \(L^2\) product, normₕ
inner₊ the backward spacing along the chosen direction (zero at the first node), the cell width across the others pairs with the backward differences for summation by parts, norm₊
inner_Γ the product of the transverse half-spacings, so the sum is a \((D-1)\)-dimensional boundary integral surface integrals over marked faces

The weight vectors come from weights, named by Bramble.Innerh and Bramble.Innerplus.

Bramble.innerₕ

source
innerₕ(uₕ::VectorElement, vₕ::VectorElement; markers = ()) -> Number

Returns the discrete \(L^2\) inner product of the grid functions uₕ and vₕ, weighting each point by its cell measure.

  • 1D case

\[(\textrm{u}_h, \textrm{v}_h)_h \vcentcolon = \sum_{i=1}^N |\square_{i}| \textrm{u}_h(x_i) \textrm{v}_h(x_i)\]
  • 2D case

\[(\textrm{u}_h, \textrm{v}_h)_h \vcentcolon = \sum_{i=1}^{N_x} \sum_{j=1}^{N_y} |\square_{i,j}| \textrm{u}_h(x_i,y_j) \textrm{v}_h(x_i,y_j)\]
  • 3D case

\[(\textrm{u}_h, \textrm{v}_h)_h \vcentcolon = \sum_{i=1}^{N_x} \sum_{j=1}^{N_y} \sum_{l=1}^{N_z} |\square_{i,j,l}| \textrm{u}_h(x_i,y_j,z_l) \textrm{v}_h(x_i,y_j,z_l)\]

On a CompositeGridSpace it is the inner product of the product space, the sum of the component-wise products:

\[(\textrm{u}_h, \textrm{v}_h)_h = \sum_{c=1}^{N_c} (\textrm{u}_h^{(c)}, \textrm{v}_h^{(c)})_h\]

which is the only meaning it can have, so there is nothing ambiguous about accepting one. The two grid functions must have the same number of components.

uₕ and vₕ must live on the same mesh, the same object or one with the same points (two gridspace calls over one mesh qualify); otherwise an ArgumentError is thrown, since the weights are read from space(uₕ). The same holds for every inner product in this file.

markers restricts the sum to the union of the labelled regions' points (a masked sum of the same cell measures, not a surface integral; see the note above _combined_mask).

A complex uₕ is conjugated, as in every inner product in this file (gpena/Bramble.jl#644): each term is \(\overline{\textrm{u}_h(x_i)} \textrm{v}_h(x_i)\), so innerₕ(uₕ, uₕ) is real and non-negative. The result has the element type of the data promoted with the mesh's: complex for complex data, a Dual for ForwardDiff.Dual data.

Examples

using Bramble
Wₕ = gridspace(mesh(domain(interval(0.0, 1.0)), 101))
uₕ = Rₕ(Wₕ, x -> 1.0)
isapprox(innerₕ(uₕ, uₕ), 1.0; atol = 1.0e-12) && isapprox(normₕ(uₕ), 1.0; atol = 1.0e-12)

# output
true
innerₕ(left::LazyOp{D}, right::LazyOp{D}; markers = ()) -> LazyOp{D}

Constructs a symbolic \(L^2\) inner product between left and right: a LinearProduct (source × test) if left is source-only (_is_source_only: a source, or a source wrapped in differences/averages/shifts/jumps/restrictions/scales, never a trial function), or a BilinearProduct (trial × test) otherwise.

This applies specifically when left is a LazyOp: a bare Function/Number/VectorElement is unconditionally a source, so those overloads build a LinearProduct directly. When left arrives already wrapped (πₕ(uₕ) or D₋ₓ(πₕ(uₕ))), this check ensures the correct linear AST node is constructed.

A composite VectorElement source (or a tuple of sources, one per component) with an unindexed composite test function v expands per component: innerₕ(fₕ, v) is Σᵢ innerₕ(fₕ(i), v(i)). An indexed v(i) in that position throws an ArgumentError, as it names one component while the call sums over all of them. For a single component write innerₕ(fₕ(i), v(i)). inner₊, inner₊ₓ, inner₊ᵧ and inner₊₂ take both the VectorElement and the tuple source; inner_Γ takes the composite VectorElement source only. All of them refuse the indexed v(i).

markers restricts the assembled term to the union of the labelled regions: a mask on which grid points it contributes to at all.

Bramble.inner_Γ

source
inner_Γ(uₕ::VectorElement, vₕ::VectorElement, labels::Symbol...) -> Number

Returns the \((D-1)\)-dimensional boundary integral of uₕ * vₕ over the grid faces labels name,

\[(\textrm{u}_h, \textrm{v}_h)_\Gamma \vcentcolon = \int_\Gamma \textrm{u}_h \textrm{v}_h \, ds \approx \sum_{I \in \Gamma_h} \omega(I) \textrm{u}_h(I) \textrm{v}_h(I)\]

with the lumped surface weight \(\omega\) described in mesh/queries.jl: the sum, over the normal directions of the surface pieces through a point, of the product of the transverse half-spacings. In 1D a face is a point of measure 1, so inner_Γ(uₕ, vₕ, :boundary) is u(x₀)v(x₀) + u(x_N)v(x_N), with no spacing anywhere – the correct pairing for a 1D Neumann term, and not comparable dimensionally with the 2D and 3D cases.

A complex uₕ is conjugated and the result has the data's element type, as for innerₕ.

Not the same quantity as innerₕ(uₕ, vₕ; markers = labels). That is a masked sum of the existing cell measures (a \(D\)-dimensional quantity restricted to a set of points), and it scales like h, vanishing under refinement: 0.125 on a 5×5 mesh of the unit square restricted to :bottom, against 1.0 here on the same mesh and region, on every mesh.

A label is read from its definition. One the mesh holds as a marker is read off its marked points. It selects each coordinate face whose points off its edges and corners are all marked, and several markers select the union of their faces. A user-named whole face (:inflow => :xmin), an open face (x -> x[1] == 1 && 0 < x[2] < 1) and two markers that each cover half of a face all qualify. So does :boundary, which a mesh holds by default; a domain that redefines it integrates its definition, as the mesh warns when it is built. A label the mesh does not hold is a face name (the canonical :xmin…:zmax, :boundary, or a viewpoint alias). Under domain(S, :left => :ymin), :left is therefore the bottom edge. normal_vector(Wₕ, label) resolves a label by this same rule. On an axis with one point, a label's marked points cover both faces of that axis, so such a label throws here (see the refusals below); the face name (:ymin) is the way to name one of them.

Markers that select no face, or that mark a point on no selected face, throw an ArgumentError: part of a face, an interior interface or a staircase is a genuinely more general surface – the weight stops factorising where the surface is cut at an interior transverse index, and needs the explicit one-sided face sum – and is refused rather than silently given the factorised weight. So is a face set holding both faces of an axis with two points or fewer, since every grid point lies on it. A DimensionMismatch is thrown when the storage of uₕ or vₕ does not hold one value per point of the mesh of space(uₕ), and an ArgumentError when the two live on different meshes, as for innerₕ.

The symbolic twin, for use inside a form, is inner_Γ(g, v; markers = …) (operators/inner.jl). It takes its regions as a keyword, to sit beside innerₕ; this one keeps the positional labels it has always had. It does not refuse a face set holding both faces of an axis with two points or fewer: it assembles every face selected.

Examples

Ωₕ = mesh(domain(interval(0.0, 1.0) × interval(0.0, 1.0)), (5, 5), (true, true))
Wₕ = gridspace(Ωₕ)
uₕ = Rₕ(Wₕ, x -> 1.0)

# the length of the bottom edge, on this mesh and on every refinement of it
inner_Γ(uₕ, uₕ, :bottom)

# output

1.0

See also: innerₕ

inner_Γ(left::LazyOp{D}, right::LazyOp{D}; markers) -> LazyOp{D}
inner_Γ(g::Union{Function, Number, VectorElement}, v::LazyOp{D}; markers) -> LazyOp{D}

Constructs a symbolic \((D-1)\)-dimensional surface integral over the grid faces markers names, the term a natural boundary condition is written with:

\[\int_{\Gamma_N} g\, v \, ds \qquad\text{and}\qquad \int_{\Gamma_R} \beta\, u\, v \, ds\]

A source on the left gives a LinearForm contribution (the Neumann flux vector); a trial function gives a BilinearForm one (the Robin boundary mass). The weight is the same lumped surface weight the numeric inner_Γ uses, so uᵀ A v equals inner_Γ(uₕ, vₕ, markers...) for the bilinear mass term and real data. The numeric inner_Γ conjugates its first argument, so for complex data uᴴ A v is the match.

markers is a keyword here, matching innerₕ, where the numeric twin takes its labels positionally. A label is read from its definition when form is called, as the numeric one reads it: one the mesh holds as a marker selects the whole coordinate faces its points mark, and any other label is a face name (:boundary, :xmin…:zmax, or a viewpoint alias). Markers that are not whole faces, and an unknown label, throw an ArgumentError at form. Unlike the numeric one, it accepts a face set holding both faces of an axis with two points or fewer, and assembles every face selected: on an axis with one point, a marker covers both faces of it, and so selects what :boundary does.

The assembled block carries a structural entry at every point the surrounding operators reach, not only on Γ: the weight is zero off the surface rather than the entry being absent. For a Robin term added to a Laplacian, which is what this is for, those entries are in the pattern already.

Examples

# -Δu = f with a Neumann flux g on the right edge and Robin data on the top one
a = form(Wₕ, Wₕ, (u, v) -> inner₊(∇ₕ(u), ∇ₕ(v)) + inner_Γ(β * u, v; markers = (:ymax,)))
l = form(Wₕ, v -> innerₕ(fₕ, v) + inner_Γ(g, v; markers = (:xmax,)))

See also: innerₕ, inner₊

inner_Γ(F::NormalContraction, v; markers) -> LazyOp

The surface integral of a normal flux, \(\int_\Gamma (\mathbf{F} \cdot \boldsymbol{\eta})\, v\, ds\).

Expanded here, at the builder, into one ordinary product per direction, each carrying that direction's signed transverse measure. The term is the sum a caller would otherwise write out, so nothing downstream of form learns a new node.

inner_Γ(g * η[d], v; markers) -> LazyOp

The surface integral \(\int_\Gamma g\, \eta_d\, v\, ds\): the direction-d term of inner_Γ(dot(F, η), v), so summing it over d with g = F[d] gives that integral. A bare component η[d] is 1 * η[d], and a linear combination of such products expands to the sum of their terms, so inner_Γ(f1 * ηₓ + f2 * ηᵧ, v) is inner_Γ(dot((f1, f2), η), v).

ηₓ, ηᵧ = η
l = form(Wₕ, v -> inner_Γ(f * ηₓ, v; markers = (:xmax,)))
a = form(Wₕ, Wₕ, (u, v) -> inner_Γ(u * ηᵧ, v; markers = (:boundary,)))
inner_Γ(F::FacetOperator, v; markers) -> LazyOp
inner_Γ(u, F::FacetOperator; markers) -> LazyOp

The surface integral of a facet operator such as ∂ₙ(u), facet by facet: one product per facet markers selects, weighted by that facet's measure, so a corner node gets a term from each selected facet it lies on. With a facet operator on both sides, the products pair the two on the same facet. markers is required, as for the plain inner_Γ: an empty selection is an ArgumentError, and markers = :boundary selects every facet.

Bramble.η

source
η

The outward unit normal, as a symbol usable inside a form.

Only meaningful contracted against a vector-valued quantity whose values are known – a flux field, dot(F, η) – and only inside inner_Γ, which is where a normal has a face to be normal to.

l = form(Wₕ, v -> inner_Γ(dot(Fₕ, η), v; markers = (:xmax,)))

The normal derivative of an unknown is ∂ₙ, which needs ghost points.

See also: inner_Γ, normal_vector, ∂ₙ

Bramble.skew_symmetric

source
skew_symmetric(A) -> Function
skew_symmetric(wₕ) -> Function

The skew-symmetric part of the operator A, as a form body:

\[a(u, v) = \tfrac{1}{2}\,(A u, v)_h - \tfrac{1}{2}\,(u, A v)_h\]

The assembled matrix is \(\tfrac{1}{2}(HA - (HA)^{\!\top})\), exactly skew-symmetric, so \(u^{\!\top} M u = 0\) to machine precision and a scheme whose mass matrix is H conserves \(\Vert u \Vert_h\) – which is what a split form is written to achieve. It holds on any mesh and under any boundary treatment, where the conservative/advective split it replaces holds up to boundary terms that have to cancel.

A is a function of one symbolic argument: skew_symmetric(x -> wₕ * Dcₓ(x)). Given a grid function or a tuple of them instead, A is the advection operator those coefficients define, \(\sum_d w_d \, \textrm{Dc}_{x_d}\), built on the centered difference because that is the one whose symmetric part the split is meant to remove.

The result is a two-argument function, which is what form takes:

using Bramble: skew_symmetric
a = form(Wₕ, Wₕ, skew_symmetric(wₕ))
a = form(Wₕ, Wₕ, (u, v) -> inner₊(∇ₕ(u), ∇ₕ(v)) + skew_symmetric(wₕ)(u, v))

For a nonlinear problem the coefficients are the current iterate, so the form is rebuilt per step with wₕ refreshed; the construction does not care where they came from.

See also: innerₕ, Dcₓ

Bramble.inner₊

source
inner₊(uₕ::VectorElement, vₕ::VectorElement, ::Val{S}; markers = ()) -> Number

Returns the discrete inner product of the grid functions uₕ and vₕ weighted by the staggered set S ⊆ 1:D, weights(space(uₕ), Val(S)): entry I weighs \(\prod_{d \in S} h_d(I_d) \cdot \prod_{d \notin S} h_d(I_d + 1/2)\) (gpena/Bramble.jl#115, #234).

Val(()) is innerₕ, and Val((1,))/Val((2,))/Val((3,)) are inner₊ₓ/inner₊ᵧ/inner₊₂ respectively – those four functions are aliases of this one, sharing its implementation, and return the identical number to it because weights(Wₕ, Val(S)) returns the very same stored SeparableWeights for those four S, not a recomputed one. Every other S – a pair, or the full 1:D combination – reduces against a lazily-computed SeparableWeights instead, through the _dot/_dot_masked specializations above, so no O(n^D) vector is ever materialised for it.

An S that is not a repeat-free subset of 1:D throws an ArgumentError, as the symbolic inner₊(p, q, Val(S)) does.

markers restricts the sum to the union of the labelled regions' points, as it does for innerₕ (a masked sum of the weight above, not a surface integral).

A complex uₕ is conjugated and the result has the data's element type, as for innerₕ.

Defined for grid functions of a ScalarGridSpace only, the same restriction every other inner₊* in this file has: a composite grid function has no single weighting of its own; take a scalar component of it with components first, which is itself a scalar grid function and is accepted.

Examples

using Bramble
Ωₕ = mesh(domain(interval(0.0, 1.0) × interval(0.0, 1.0)), (6, 5), (true, true))
Wₕ = gridspace(Ωₕ)
uₕ = Rₕ(Wₕ, x -> 1.0)
inner₊(uₕ, uₕ, Val(())) == innerₕ(uₕ, uₕ)

# output
true

See also: inner₊, weights, SeparableWeights.

inner₊(uₕ::VectorElement, vₕ::VectorElement, d; markers = ()) -> Number

Returns inner₊ₓ/inner₊ᵧ/inner₊₂(uₕ, vₕ; markers), selected by d: an Integer (1, 2 or 3) or a Symbol (:x, :y or :z) (gpena/Bramble.jl#341).

This is the preferred spelling: inner₊(uₕ, vₕ, :x) over inner₊ₓ(uₕ, vₕ), which stays reachable as a plain alias.

An Integer outside 1:3 throws a BoundsError; a Symbol that is not :x/:y/:z throws an ArgumentError.

inner₊ also destructures and indexes like the vectorial operator aliases (gpena/Bramble.jl#340): ix, iy, iz = inner₊ binds inner₊ₓ, inner₊ᵧ, inner₊₂ (the very same function objects, not copies), and inner₊[1]/inner₊[:x] (through length/firstindex/lastindex/getindex/iteration) index into that same triple.

Examples

using Bramble
Ωₕ = mesh(domain(interval(0.0, 1.0) × interval(0.0, 1.0)), (6, 5), (true, true))
Wₕ = gridspace(Ωₕ)
uₕ = Rₕ(Wₕ, x -> 1.0)
inner₊(uₕ, uₕ, :x) == inner₊(uₕ, uₕ, 1)

# output
true

See also: inner₊ₓ, inner₊ᵧ, inner₊₂.

inner₊(uₕ::VectorElement, vₕ::VectorElement) -> Number
inner₊(uₕ::VectorElement, vₕ::VectorElement, ::Type{Tuple}) -> Tuple
inner₊(uₕ::NTuple{D, VectorElement}, vₕ::NTuple{D, VectorElement}) -> Number

Returns the discrete modified \(L^2\) inner product of the grid functions uₕ and vₕ.

If the Tuple argument is given, it returns a D-tuple of all \(\textrm{inner}_{x_i,+}\) applied to its input arguments, where D is the topological dimension of the mesh associated with the elements.

If NTuples of VectorElement are passed as input arguments, it returns the sum of all inner products \((\textrm{u}_h[i],\textrm{v}_h[i])_{+x_i}\).

For VectorElements, the definition is given by

  • 1D case

\[(\textrm{u}_h, \textrm{v}_h)_+ \vcentcolon = \sum_{i=1}^{N_x} h_{i} \textrm{u}_h(x_i) \textrm{v}_h(x_i)\]
  • 2D case

\[(\textrm{u}_h, \textrm{v}_h)_+ \vcentcolon = (\textrm{u}_h, \textrm{v}_h)_{+x} + (\textrm{u}_h, \textrm{v}_h)_{+y}\]
  • 3D case

\[(\textrm{u}_h, \textrm{v}_h)_+ \vcentcolon = (\textrm{u}_h, \textrm{v}_h)_{+x} + (\textrm{u}_h, \textrm{v}_h)_{+y} + (\textrm{u}_h, \textrm{v}_h)_{+z}.\]

See the definitions of inner₊ₓ, inner₊ᵧ, and inner₊₂ for more details.

Defined for grid functions of a ScalarGridSpace only, and for tuples whose entries are such grid functions. A grid function of a composite grid space raises a MethodError; take a scalar component of it with components first, which is itself a scalar grid function and is accepted.

inner₊(left::LazyOp{1}, right::LazyOp{1}; markers = ())
inner₊(left::NTuple{D,LazyOp{D}}, right::NTuple{D,LazyOp{D}}; markers = ()) where D

Constructs a symbolic modified \(L^2_+\) inner product between left and right.

markers restricts the sum as it does for innerₕ.

inner₊(left::BackwardDifference{D,Dim}, right::BackwardDifference{D,Dim}) -> LazyOp{D}

inner₊ of two backward differences taken along the same direction, which is the weight the product carries: InnerPlus{Dim}.

In one dimension there is only one direction, so inner₊(left, right) already answers. Above one dimension a bare inner₊ of two operators names no direction, and the weights are directional: the direction is read off the nodes. This is what makes inner₊(D₋ₓ(u), D₋ₓ(v)) mean what it reads as.

Backward differences only, as everywhere inner₊ meets a difference: the weights are those of the summation-by-parts identity, which pairs them with a backward difference.

Constructs a LinearProduct (source × test) if left is source-only (_is_source_only), or a BilinearProduct otherwise, matching innerₕ.

markers restricts the sum as it does for innerₕ.

inner₊(left::LazyOp{D}, right::LazyOp{D}) -> LazyOp{D}

Symbolic inner₊ of two operators neither of which names a direction.

In one dimension there is only one direction to name, so this is the product, with weight InnerPlus{1}. Above one dimension the weights are directional and nothing here supplies the direction, so it throws an ArgumentError.

markers restricts the sum as it does for innerₕ.

inner₊(left::LazyOp{D}, right::BackwardDifference{D,Dim}) -> LazyOp{D}
inner₊(left::BackwardDifference{D,Dim}, right::LazyOp{D}) -> LazyOp{D}

inner₊ where one side is a backward difference and the other is not: the difference names the direction, so the product carries InnerPlus{Dim}.

This is what inner₊(u, D₋ₓ(v)) means: the common form, and the one the coupled pressure-velocity terms are written in, inner₊(p, D₋ₓ(v[1])) with p a symbolic scalar field. It is not restricted to indexed leaves: a plain TrialFunction reads the direction off the difference just as an IndexedTrialFunction does.

Backward differences only, as everywhere inner₊ meets a difference.

A Function, Number or VectorElement on the left is the source it stands for and reads its direction the same way, so above one dimension inner₊(f, v) throws.

A LinearProduct if left is source-only (_is_source_only), a BilinearProduct otherwise, matching innerₕ.

markers restricts the sum as it does for innerₕ.

inner₊(left::BackwardDifference{D,Dim1}, right::BackwardDifference{D,Dim2}) where {D,Dim1,Dim2}

Rejects inner₊ of two backward differences taken along different directions.

Each side names a direction and they disagree, so there is no one weight the product carries. This also has to be written out rather than left to dispatch: with a difference on either side, the two single-sided methods above tie, and the pair would be an ambiguity rather than an error the caller can read.

inner₊(left::Tuple{Vararg{Tuple}}, right::Tuple{Vararg{Tuple}})

Vector-field inner₊: sums per-component inner products. Used when left and right are tuples of gradient tuples, e.g. inner₊(∇ₕ(components(u)), ∇ₕ(components(v))) where u is a velocity. Each element pair (left[k], right[k]) is a D-tuple of LazyOp (a gradient), which dispatches to the existing inner₊(::NTuple{D,LazyOp}, ::NTuple{D,LazyOp}).

inner₊(∇ₕ(u), ∇ₕ(v)) on a composite u and v builds the same sum, in the same order. There ∇ₕ returns rows labelled by component (Bramble._GradientRows), and the sum records those labels, so a coefficient captured over it leaves form one concrete type in 3D as well. One side labelled and the other a plain tuple takes this method.

The rows may differ in type (∇ₕ((u(1) + u(2), u(2)))), but both sides need the same number of them, or it throws a DimensionMismatch naming both counts.

This overload is intentionally restricted to tuples of tuples so it does not interfere with inner₊(NTuple{D,VectorElement}, NTuple{D,VectorElement}) handled by the @generated method in inner_product.jl. That restriction is what separates this file's symbolic family from that file's numeric one; it is asserted in test/form/inner_products.jl, testset "Symbolic and numeric families stay apart".

markers restricts the whole sum, not each component separately, as it does for innerₕ.

inner₊(left::LazyOp{D}, right::LazyOp{D}, ::Val{S}; markers = ()) where {D, S}

Constructs the symbolic modified \(L^2_+\) inner product staggered in the direction set S ⊆ 1:D (gpena/Bramble.jl#115, #234): the weight at grid index I is \(\prod_{d \in S} h_d(I_d) \cdot \prod_{d \notin S} h_d(I_d + 1/2)\), matching weights(Wₕ, Val(S)).

This is the general entry point innerₕ (S = ()) and inner₊ₓ/inner₊ᵧ/inner₊₂ (S a singleton) are aliases of: for those two shapes it builds exactly the same InnerH/InnerPlus node they do, not a new node under a shared name, so a term written either way folds the same way in the simplifier and resolves to the same which(inner₊, ...).file. Every other S (|S| ≥ 2) builds an InnerPlusSet node, whose weight at assembly comes from the lazy SeparableWeights weights(Wₕ, Val(S)) returns for those sets.

S must be a subset of 1:D with no axis repeated, and is accepted in any order – Val((1,2)) and Val((2,1)) build the identical node. Its entries may be any integer type but Bool (Val((Int32(1), Int32(2))) builds the node Val((1, 2)) does); a Bool entry throws an ArgumentError.

Constructs a LinearProduct if left is source-only (_is_source_only), a BilinearProduct otherwise, matching innerₕ.

markers restricts the sum as it does for innerₕ.

Examples

inner₊(u, v, Val((1, 2)))      # xy-edge centres in 3D, an ε_{12}-style placement
inner₊(u, v, Val((1, 2, 3)))   # cell centres in 3D, a divₕ-style placement
inner₊(u, v, Val(()))          # what innerₕ(u, v) is
inner₊(u, v, Val((1,)))        # what inner₊ₓ(u, v) is
inner₊(left::LazyOp{D}, right::LazyOp{D}, d; markers = ()) where D

Constructs inner₊ₓ/inner₊ᵧ/inner₊₂(left, right; markers), selected by d: an Integer (1, 2 or 3) or a Symbol (:x, :y or :z) (gpena/Bramble.jl#341).

This is the preferred spelling: inner₊(D₋ₓ(u), D₋ₓ(v), :x) over inner₊ₓ(D₋ₓ(u), D₋ₓ(v)), which stays reachable as a plain alias.

An Integer outside 1:3 throws a BoundsError; a Bool, or a Symbol that is not :x/:y/:z, throws an ArgumentError, as does a direction beyond D (inner₊(u, v, 2) in 1D).

The numeric twin, for the same selector against grid functions rather than operators, is inner₊(uₕ, vₕ, d) (src/space/inner_product.jl), which also documents how inner₊ destructures and indexes (ix, iy, iz = inner₊, inner₊[:x]) into the same three aliases this method dispatches to.

Bramble.inner₊ₓ

source
inner₊ₓ(uₕ::VectorElement, vₕ::VectorElement; markers = ()) -> Number

Returns the discrete modified \(L^2\) inner product of the grid functions uₕ and vₕ associated with the first variable.

For VectorElements, it is defined as

  • 1D case

\[(\textrm{u}_h, \textrm{v}_h)_+ \vcentcolon = \sum_{i=1}^{N_x} h_{i} \textrm{u}_h(x_i) \textrm{v}_h(x_i)\]
  • 2D case

\[(\textrm{u}_h, \textrm{v}_h)_{+x} \vcentcolon = \sum_{i=1}^{N_x}\sum_{j=1}^{N_y} h_{x,i} h_{y,j+1/2} \textrm{u}_h(x_i,y_j) \textrm{v}_h(x_i,y_j)\]
  • 3D case

\[(\textrm{u}_h, \textrm{v}_h)_{+x} \vcentcolon = \sum_{i=1}^{N_x}\sum_{j=1}^{N_y}\sum_{l=1}^{N_z} h_{x,i} h_{y,j+1/2} h_{z,l+1/2} \textrm{u}_h(x_i,y_j,z_l) \textrm{v}_h(x_i,y_j,z_l).\]

Defined for grid functions of a ScalarGridSpace only. A grid function of a composite grid space is rejected at dispatch; take a scalar component of it with components first, which is itself a scalar grid function and is accepted.

markers restricts the sum as it does for innerₕ (a masked sum, not a surface integral).

inner₊ₓ(left::LazyOp{D}, right::LazyOp{D}) where D
inner₊ᵧ(left::LazyOp{D}, right::LazyOp{D}) where D
inner₊₂(left::LazyOp{D}, right::LazyOp{D}) where D

Constructs directional modified \(L^2_+\) inner products in x, y, and z directions.

A LinearProduct if left is source-only (_is_source_only), a BilinearProduct otherwise, exactly as innerₕ decides.

markers restricts the sum as it does for innerₕ.

A direction beyond D throws an ArgumentError at the call: inner₊ᵧ in 1D, inner₊₂ below 3D.

Bramble.inner₊ᵧ

source
inner₊ᵧ(uₕ::VectorElement, vₕ::VectorElement; markers = ()) -> Number

Returns the discrete modified \(L^2\) inner product of the grid functions uₕ and vₕ associated with the second variable, the \(y\) direction.

For VectorElements, it is defined as

  • 2D case

\[(\textrm{u}_h, \textrm{v}_h)_{+y} \vcentcolon = \sum_{i=1}^{N_x}\sum_{j=1}^{N_y} h_{x,i+1/2} h_{y,j} \textrm{u}_h(x_i,y_j) \textrm{v}_h(x_i,y_j)\]
  • 3D case

\[(\textrm{u}_h, \textrm{v}_h)_{+y} \vcentcolon = \sum_{i=1}^{N_x}\sum_{j=1}^{N_y}\sum_{l=1}^{N_z} h_{x,i+1/2} h_{y,j} h_{z,l+1/2} \textrm{u}_h(x_i,y_j,z_l) \textrm{v}_h(x_i,y_j,z_l).\]

Defined for grid functions of a ScalarGridSpace only. A grid function of a composite grid space is rejected at dispatch; take a scalar component of it with components first, which is itself a scalar grid function and is accepted.

markers restricts the sum as it does for innerₕ (a masked sum, not a surface integral).

Bramble.inner₊₂

source
inner₊₂(uₕ::VectorElement, vₕ::VectorElement; markers = ()) -> Number

Returns the discrete modified \(L^2\) inner product of the grid functions uₕ and vₕ associated with the z variable

\[(\textrm{u}_h, \textrm{v}_h)_{+z} \vcentcolon = \sum_{i=1}^{N_x}\sum_{j=1}^{N_y}\sum_{l=1}^{N_z} h_{x,i+1/2} h_{y,j+1/2} h_{z,l} \textrm{u}_h(x_i,y_j,z_l) \textrm{v}_h(x_i,y_j,z_l).\]

Defined for grid functions of a ScalarGridSpace only. A grid function of a composite grid space is rejected at dispatch; take a scalar component of it with components first, which is itself a scalar grid function and is accepted.

markers restricts the sum as it does for innerₕ (a masked sum, not a surface integral).

Bramble.normₕ

source
normₕ(uₕ::VectorElement) -> Number

Returns the discrete \(L^2\) norm of the grid function uₕ, defined as

\[\Vert \textrm{u}_h \Vert_h \vcentcolon = \sqrt{(\textrm{u}_h, \textrm{u}_h)_h}\]

On a CompositeGridSpace it is the norm of the product space, which follows from the inner product there: the square root of the sum of the components' squared norms.

The result has the real type of the element type (gpena/Bramble.jl#644): a Float64 for Float64 or ComplexF64 data on a Float64 mesh, a Dual for ForwardDiff.Dual data. The inner product of a complex uₕ with itself is real up to a zero imaginary part, which is dropped before the square root.

On a GhostGridSpace it is the norm over the physical nodes, the first npoints(Ωₕ) degrees of freedom: the ghost values never enter, whatever they hold. That holds for gridspace(Ωₕ; ghosts) and for a constrained space whose relations carry no data and fix no node. A constrained space whose degrees of freedom omit the fixed nodes or whose relations carry data has no grid function in a vector of its degrees of freedom: normₕ throws an ArgumentError, and normₕ(Bramble.lift(Cₕ, u)) is the norm of the lifted grid function. On a composite of ghost spaces it is the square root of the sum of the components' squared norms over their physical nodes.

On the trial space U of Bramble.polygon_spaces it is the norm over Ω̄ₕ of the grid function that is uₕ at the interior nodes, its degrees of freedom, and 0 at the boundary nodes: each interior node weighs its dual cell, and no exterior node enters. The error of a solve is normₕ(element(U, u) - Rₕ(U, u_exact)); the norm of Bramble.lift(U, u) would sum the (3.2) extension at the exterior nodes, which holds no value of the solution. The test space W throws an ArgumentError.

Bramble.norm₁ₕ

source
norm₁ₕ(uₕ::VectorElement) -> Number

Returns the discrete version of the standard \(H^1\) norm of VectorElement uₕ.

\[\Vert \textrm{u}_h \Vert_{1h} \vcentcolon = \sqrt{ \Vert \textrm{u}_h \Vert_h^2 + |\textrm{u}_h|_{1h}^2 }\]

with \(|\textrm{u}_h|_{1h} = \Vert \nabla_h \textrm{u}_h \Vert_+\) (see snorm₁ₕ).

Built from the squared quantities directly: taking normₕ and snorm₁ₕ and squaring them back up would compute two square roots only to undo them.

On a CompositeGridSpace it is the norm of the product space, as for normₕ: the square root of the sum of the components' squared norms.

The result has the real type of the element type, as for normₕ.

On a GhostGridSpace it is the norm of the physical grid function, as for snorm₁ₕ: the ghost values never enter. On a composite of ghost spaces it is the norm of the product space of those grid functions. On the trial space of Bramble.polygon_spaces it reads the nodes of Ω̄ₕ, as normₕ and snorm₁ₕ do.

Bramble.snorm₁ₕ

source
snorm₁ₕ(uₕ::VectorElement) -> Number

Returns the discrete \(H^1\) seminorm of the grid function uₕ,

\[|\textrm{u}_h|_{1h} \vcentcolon = \Vert \nabla_h \textrm{u}_h \Vert_+\]

so that snorm₁ₕ(uₕ) == norm₊(∇ₕ(uₕ)) in one, two and three dimensions. The argument is the grid function itself; the backward gradient is taken internally, and without materialising it, so this allocates nothing.

See also: norm₁ₕ, norm₊, ∇ₕ.

On a CompositeGridSpace it is the seminorm of the product space, as for normₕ: the square root of the sum of the components' squared seminorms.

The result has the real type of the element type, as for normₕ: each difference δ contributes abs2(δ).

On a GhostGridSpace it is the seminorm of the physical grid function, the first npoints(Ωₕ) degrees of freedom on the physical mesh Ωₕ: the ghost values never enter, and a space whose degrees of freedom are no grid function throws, as for normₕ. On a composite of ghost spaces it is the seminorm of the product space of those grid functions. On the trial space of Bramble.polygon_spaces it is the seminorm of the grid function on Ω̄ₕ that normₕ reads, uₕ at the interior nodes and 0 at the boundary nodes: it sums every grid edge with both ends interior or on the boundary, and no edge joins an interior node to an exterior one.

Bramble.norm₊

source
norm₊(uₕ::VectorElement) -> Number
norm₊(uₕ::NTuple{D, VectorElement}) -> Number

Returns the discrete modified \(L^2\) norm of the grid function uₕ. It also accepts an NTuple of VectorElements.

For VectorElements uₕ, it is defined as

\[\Vert \textrm{u}_h \Vert_+ = \sqrt{(\textrm{u}_h,\textrm{u}_h)_+}.\]

and for NTuples of VectorElements it returns

\[\Vert \textrm{u}_h \Vert_+ \vcentcolon = \sqrt{ \sum_{i=1}^D(\textrm{u}_h[i],\textrm{u}_h[i])_{+,x_i}}.\]

Defined for grid functions of a ScalarGridSpace only, and for tuples whose entries are such grid functions. A grid function of a composite grid space raises a MethodError; take a scalar component of it with components first, which is itself a scalar grid function and is accepted.

The result has the real type of the element type, as for normₕ.

Bramble.norminf

source
norminf(uₕ::VectorElement) -> Real
norminf(uₕ::NTuple{D, VectorElement}) -> Real

Returns the discrete maximum norm of the grid function uₕ, defined as

\[\Vert \textrm{u}_h \Vert_{h,\infty} \vcentcolon = \max_{I} \vert \textrm{u}_h(I) \vert\]

Unlike the \(L^2\)-type norms, this one carries no quadrature weight, so the same expression serves a ScalarGridSpace and a CompositeGridSpace: the maximum over a composite's degrees of freedom is the maximum over its components. On an NTuple of grid functions – what the vectorial aliases such as ∇ₕ return – it is the maximum over every entry of every component.

The element type comes from the data, not from the space, so a ForwardDiff.Dual-valued grid function returns a Dual.

On a GhostGridSpace it is the maximum over the physical nodes, the first npoints(Ωₕ) degrees of freedom: the ghost values never enter, and a space whose degrees of freedom are no grid function throws an ArgumentError, as for normₕ. The lifted grid function Bramble.lift(Cₕ, u) lives on the padded space, so norminf of it reads the whole padded box, ghosts included. On a composite of ghost spaces, Cₕ × Cₕ or Cₕ^N, it is the maximum over the components, each read so. On the trial space of Bramble.polygon_spaces it is the maximum over the interior nodes, the degrees of freedom, as for normₕ.

See also: normₕ, norm₁ₕ

LinearAlgebra.norm — Tuple{Bramble.VectorElement, AbstractString}

source
norm(uₕ::VectorElement, kind::AbstractString) -> Number

Returns the discrete norm of uₕ that kind names: "h" is normₕ, "1h" is norm₁ₕ and "∞" is norminf. Any other kind throws an ArgumentError.

Every kind returns the real type of the data's element type promoted with the mesh's, so the result type does not depend on kind: for "∞" this converts norminf, which keeps the data's own type, so a Float32 or Int element on a Float64 mesh gives a Float64 for every kind, and so does a ComplexF64 element.

The one-argument norm(uₕ) is unchanged: a VectorElement is an AbstractVector, so it is still LinearAlgebra's Euclidean norm of the values, which carries no quadrature weight.