Bramble.jl GitHub

Meshes#

Mesh types and constructors#

AbstractMeshType, MeshMarkers and submeshes are private; see the mesh internals page.

Bramble.Mesh1D

source
Mesh1D{BT, CI, VT, T} <: AbstractMeshType{1}

One-dimensional grid discretizing a 1D CartesianProduct interval.

The geometry lives in an immutable Mesh1DState (points, cell boundaries half_pts, cell measures half_spacings, backward spacings, indices, set, backend, version, uniformity flag and marker words); every mutation (set_points!, set_markers!, refinement) replaces it whole, except a set_markers! that keeps the label set, which rewrites the marker words in place. The mesh itself keeps the public marker dictionary and the label-to-column table of the state's marker words.

Fields

  • markers: MeshMarkers dictionary mapping symbols to BitVector indicators.

  • marker_ids: Dict{Symbol, Int}, the column of each label in the state's word matrix.

  • state: the Mesh1DState holding everything else, read through the accessors (points, spacings, ...).

  • marker_stamp: a counter value drawn afresh at construction and at every marker replacement, so a cached marker read can tell the markers changed.

  • predicate_labels: the labels set_markers! defined by a predicate of position, whose sets a point change leaves stale (see change_points!).

  • parent_predicate_labels: the same labels of the MeshnD this mesh is a submesh of, stamped by the parent's set_markers!; the parent's sets depend on this axis, so moving it alone is refused.

See also: MeshnD, mesh, AbstractMeshType.

Bramble.MeshnD

source
MeshnD{D, BT, CI, SM, T} <: AbstractMeshType{D}

Structured multi-dimensional tensor-product mesh for spatial dimensions \(D \in \{2, 3\}\).

Constructed as a Cartesian product of 1D submeshes (Mesh1D). Coordinate points are evaluated on demand from the tensor-product submeshes.

Type parameters

  • D: Spatial dimension (2 or 3).

  • BT <: Backend: Computational linear algebra backend.

  • CI <: CartesianIndices{D}: Cartesian index space.

  • SM <: Tuple: Tuple of 1D submeshes (Mesh1D).

  • T: Coordinate element type (Float64, Float32, etc.).

Fields

  • set: Multi-dimensional geometric CartesianProduct domain.

  • markers: MeshMarkers dictionary mapping symbols to BitVector indicators.

  • indices: Multi-dimensional CartesianIndices{D} for the grid.

  • backend: Linear algebra Backend.

  • submeshes: Tuple of D Mesh1D objects along each coordinate axis.

  • marker_ids: Dict{Symbol, Int}, the column of each label in words.

  • words: Matrix{UInt64}, the marker bits, one column of BitVector chunks per label.

  • uid: an identity unique to this mesh, kept across its mutations.

  • marker_stamp: a counter value drawn afresh at construction and at every marker replacement.

  • predicate_labels: the labels set_markers! defined by a predicate of position, whose sets a point change leaves stale (see change_points!).

A MeshnD stores no state of its own: its submeshes can be mutated directly, so the MeshnDState the walk reads is built on demand by _walk_mesh.

Examples

using Bramble: point
# Create a 2D mesh with 20×30 grid points
X = domain(interval(0, 1) × interval(0, 2))
Ωₕ = mesh(X, (20, 30), (true, false))

# Access submeshes
x_mesh = Ωₕ(1)  # 1D mesh along x-axis
y_mesh = Ωₕ(2)  # 1D mesh along y-axis

# Query a specific point coordinate
point(Ωₕ, (10, 15))  # returns (x₁₀, y₁₅)

See also: Mesh1D, submeshes, mesh.

Bramble.mesh

source
mesh(Wₕ::AbstractSpaceType) -> AbstractMeshType

Returns the underlying mesh object associated with the function space Wₕ.

A domain with polygon markers (:P => polygon(V), see Geometry) gets a grid fitted to them: every vertex coordinate is a grid line, and every crossing of a grid line with an oblique edge adds a line, until each crossing is a node. The number of points along an axis may therefore exceed npts. See mesh for the tolerances and the errors, and the mesh internals page for the closure.

A domain with a level-set marker (:D => levelset(φ), see Geometry; one per domain, none beside a polygon marker) gets a grid fitted to the curve φ = 0 instead: a polygon inscribed in the curve has its vertices on nodes and each edge a cell side or a cell diagonal, so the labels D, D_boundary and D_exterior and polygon_spaces work as for a polygon. The curve must lie strictly inside the box, be orthogonally convex and be star-shaped about the minimum of φ. mesh lists the errors; the mesh internals page says how the labels are set.

On a fitted mesh, hₘₐₓ is no guide to the h of an error plot: the lines the closure adds can sit very close to the seed lines and to each other. Take instead the largest gap between neighbouring lines that hold nodes of the closed domain Ω̄, the interior and the boundary nodes: for the disk, maximum(diff(filter(t -> -1 <= t <= 1, z))) over the points z of each axis, and the larger of the two axes. This is the spacing of the disk example, and the level-set convergence study measures it the same way.

Points and spacings#

host_points, host_spacings, forward_spacings, half_spacings, host_half_spacings, stepsize, locate_cell and cell_measures are private; see the mesh internals page.

Bramble.npoints

source
npoints(Ωₕ::AbstractMeshType) -> Int
npoints(Ωₕ::AbstractMeshType, ::Type{Tuple}) -> NTuple{D, Int}

Return the total number of points in Ωₕ. When passing Tuple as the second argument, returns a tuple with the number of points along each dimension.

Bramble.points

source
points(Ωₕ::AbstractMeshType) -> Union{Vector, NTuple}

Return the coordinates of the mesh points.

  • For 1D meshes (Mesh1D), returns a coordinate vector AbstractVector{T} (a Vector on the host) of length \(N_x\).

  • For nD meshes (MeshnD), returns an NTuple{D, AbstractVector{T}} (Vectors on the host) containing the 1D coordinate vectors along each axis.

See also: point.

Bramble.half_points

source
half_points(Ωₕ::AbstractMeshType)

Return the cell boundaries (half-points) along each axis: N + 1 values, \(x_{1/2} = x_1\), \(x_{i+1/2} = (x_i + x_{i+1})/2\) for \(i = 1, \dots, N-1\), and \(x_{N+1/2} = x_N\).

Bramble.half_point

source
half_point(Ωₕ::AbstractMeshType, idx)

Return the cell boundary (half-point) coordinate corresponding to index idx.

Bramble.spacing

source
spacing(Ωₕ::AbstractMeshType, idx)
spacing(Ωₕ::AbstractMeshType, idx, dim::Int)

Return the backward spacing \(h_i = x_i - x_{i-1}\) at index idx (for \(i=1\), returns \(x_2 - x_1\)). For nD meshes, returns a tuple of backward spacings along each axis; passing dim queries only that axis directly, without building and discarding the other D - 1 components.

Bramble.forward_spacing

source
forward_spacing(Ωₕ::AbstractMeshType, idx)
forward_spacing(Ωₕ::AbstractMeshType, idx, dim::Int)

Return the forward spacing \(h_{i+1} = x_{i+1} - x_i\) at index idx (for \(i=N\), returns \(x_N - x_{N-1}\)). For nD meshes, returns a tuple of forward spacings along each axis; passing dim queries only that axis directly, without building and discarding the other D - 1 components.

Bramble.half_spacing

source
half_spacing(Ωₕ::AbstractMeshType, idx)

Return the cell width (half-spacing) at index idx.

Bramble.spacings

source
spacings(Ωₕ::Mesh1D) -> AbstractVector

Return the cached vector of backward spacings, where spacings(Ωₕ)[i] is spacing(Ωₕ, i). Recomputed by set_points! whenever the grid points change.

spacings(Ωₕ::MeshnD{D}) -> NTuple{D, AbstractVector}

Return the per-axis backward spacings as an NTuple{D} of vectors, where spacings(Ωₕ)[d][i] is spacing(Ωₕ(d), i).

See also: half_spacings, cell_measures.

Bramble.hₘₐₓ

source
hₘₐₓ(Ωₕ::AbstractMeshType) -> Real

Return the maximum diagonal stepsize across all cells in the mesh:

\[h_{\max} = \max_{\mathbf{i}} \| (h_{1, i_1}, \dots, h_{D, i_D}) \|_2.\]

Bramble.hₘᵢₙ

source
hₘᵢₙ(Ωₕ::AbstractMeshType) -> Real

Return the diagonal of the smallest cell in the mesh, the counterpart of hₘₐₓ.

  • In 1D, \(\min_i (x_i - x_{i-1})\).

  • In nD,

\[h_{\min} = \min_{\mathbf{i}} \| (h_{1, i_1}, \dots, h_{D, i_D}) \|_2.\]

This is a diagonal rather than an edge length, so that hₘₐₓ and hₘᵢₙ measure the same kind of quantity. For the smallest extent along one coordinate, query that submesh directly: hₘᵢₙ(Ωₕ(i)).

Bramble.normal_vector

source
normal_vector(Ωₕ::AbstractMeshType{D}, symbol::Symbol) -> NTuple{D, eltype(Ωₕ)}
normal_vector(::Val{D}, symbol::Symbol) -> NTuple{D, Float64}

Return the outward unit normal vector associated with a standard boundary facet label (:xmin, :xmax, :ymin, :ymax, :zmin, :zmax) or legacy viewpoint alias (:left, :right, :bottom, :top, :front, :back). The mesh form returns the mesh's element type, eltype(Ωₕ), and the Val form, which has no mesh to take a type from, returns Float64.

Conventions

  • 1D:

    • :xmin, :left \(\to (-1.0)\)

    • :xmax, :right \(\to (+1.0)\)

  • 2D:

    • :xmin, :left \(\to (-1.0, 0.0)\)

    • :xmax, :right \(\to (+1.0, 0.0)\)

    • :ymin, :bottom \(\to (0.0, -1.0)\)

    • :ymax, :top \(\to (0.0, +1.0)\)

  • 3D:

    • :xmin, :back \(\to (-1.0, 0.0, 0.0)\)

    • :xmax, :front \(\to (+1.0, 0.0, 0.0)\)

    • :ymin, :left \(\to (0.0, -1.0, 0.0)\)

    • :ymax, :right \(\to (0.0, +1.0, 0.0)\)

    • :zmin, :bottom \(\to (0.0, 0.0, -1.0)\)

    • :zmax, :top \(\to (0.0, 0.0, +1.0)\)

See also: boundary_symbols.

normal_vector(Wₕ::ScalarGridSpace{D}, marker::Symbol) -> NTuple{D, VectorElement}

Returns the outward unit normal of the boundary face marker selects, as one grid function per coordinate: component d holds that face's normal component d at every point of the face and zero everywhere else.

marker is resolved as inner_Γ resolves its labels, so the two always agree on the face. A label the mesh holds as a marker is read off its marked points, which means that under domain(S, :left => :ymin) the label :left is the ymin face. Any other label is a face name (:xmin…:zmax or a viewpoint alias), whose normal is normal_vector(mesh(Wₕ), marker). A normal belongs to one face. So a marker that covers several faces (:boundary), or only part of one, throws an ArgumentError.

The components are zero off the face rather than the face's normal everywhere, so a product against one restricts to the face without a second mask.

Examples

Ωₕ = mesh(domain(interval(0.0, 1.0) × interval(0.0, 1.0)), (5, 5), (true, true))
Wₕ = gridspace(Ωₕ)
nₕ = Bramble.normal_vector(Wₕ, :ymin)
(sum(parent(nₕ[1])), sum(parent(nₕ[2])))

# output

(0.0, -5.0)

See also: normal_vector, inner_Γ

Bramble.cell_measure

source
cell_measure(Ωₕ::AbstractMeshType, idx) -> Real

Return the control volume (length, area, or volume) of the cell centered at index idx:

\[\operatorname{meas}(\square_{\mathbf{i}}) = \prod_{d=1}^D h_{d, i_d+1/2}.\]

Bramble.is_uniform

source
is_uniform(Ωₕ::AbstractMeshType; tol = nothing) -> Bool

Check whether the mesh has uniform spacing, within a numerical tolerance.

When tol is not given, the tolerance is _default_is_uniform_tol(eltype(Ωₕ), h₁, n, mag), with mag the larger end-point magnitude, which keeps today's 1e-10 for a Float64 mesh near the origin and widens it for Float32 or far from the origin (gpena/Bramble.jl#307, #492); see that function for the measurement behind the constant. Passing tol explicitly uses it verbatim, with no further scaling.

On a Mesh1D, the default-tolerance (tol === nothing) answer is the uniform flag its Mesh1DState stores, computed whenever the points change (gpena/Bramble.jl#332, #437): O(1), and the mesh is never written. Passing tol explicitly always rescans the spacings.

points returns the coordinate vector, or a tuple of them above 1D. A single coordinate comes from point(Ωₕ, idx) or from indexing. Above 1D idx is a CartesianIndex (or i, j in brackets; point also takes a tuple of per-axis indices); a linear index is not accepted:

Ωₕ_fig[3], point(Ωₕ_fig, 3), Ωₕ_2d[2, 5], point(Ωₕ_2d, CartesianIndex(2, 5))
(0.6666666666666666, 0.6666666666666666, (0.1111111111111111, 0.42105263157894735), (0.1111111111111111, 0.42105263157894735))
half_points(Ωₕ_fig), half_point(Ωₕ_fig, 3)
([0.0, 0.16666666666666666, 0.5, 0.8333333333333333, 1.0], 0.5)
Function Meaning
spacing(Ωₕ, i) backward spacing \(h_i = x_i - x_{i-1}\) (\(x_2 - x_1\) at \(i = 1\))
forward_spacing(Ωₕ, i) forward spacing \(h_{i+1} = x_{i+1} - x_i\)
half_spacing(Ωₕ, i) cell width \(h_{i+1/2} = (h_i + h_{i+1})/2\), half the end spacing at \(i = 1\) and \(i = N\)
cell_measure(Ωₕ, idx) measure of the control volume at idx: the product of the per-axis cell widths
hₘₐₓ(Ωₕ) largest cell diagonal, the largest spacing in 1D

A 1D mesh stores its backward spacings, so spacings hands back the whole vector and the accessors index it. The cache is rebuilt by set_points!, and so by iterative_refinement! and change_points! as well:

spacings(Ωₕ_fig), spacings(Ωₕ_fig)[3] == spacing(Ωₕ_fig, 3)
([0.3333333333333333, 0.3333333333333333, 0.3333333333333333, 0.33333333333333337], true)
hₘₐₓ(Ωₕ_fig), cell_measure(Ωₕ_2d, (3, 4))
(0.33333333333333337, 0.011695906432748537)

boundary_indices(Ωₕ) gives the boundary facets separately, as a tuple of index sets.

Mesh indexing and boundaries#

Bramble.indices

source
indices(Ωₕ::AbstractMeshType) -> CartesianIndices

Return the CartesianIndices associated with the points of mesh Ωₕ.

Bramble.boundary_indices

source
boundary_indices(idxs::CartesianIndices{D}) -> NTuple{2D, CartesianIndices{D}}
boundary_indices(Ωₕ::AbstractMeshType{D}) -> NTuple{2D, CartesianIndices{D}}

Return all boundary facets of a CartesianIndices domain or mesh Ωₕ as a tuple of CartesianIndices.

Bramble.interior_indices

source
interior_indices(indices::CartesianIndices{D}) -> CartesianIndices{D}
interior_indices(Ωₕ::AbstractMeshType{D}) -> CartesianIndices{D}

Compute the CartesianIndices representing the interior of a domain or mesh, excluding all boundary points. Dimensions with a length of one or less remain unchanged.

Bramble.is_boundary_index

source
is_boundary_index(idxs::CartesianIndices{D}, idx) -> Bool
is_boundary_index(Ωₕ::AbstractMeshType, idx) -> Bool

Determine whether index idx lies on the boundary of idxs or mesh Ωₕ.

Bramble.index_in_marker

source
index_in_marker(Ωₕ::AbstractMeshType, label::Symbol) -> BitVector

Return the BitVector indicator associated with marker label in mesh Ωₕ.

If label is not directly found in the mesh markers and is a face name, its coordinate-aligned or viewpoint boundary alias (#152's spellings: :xmin ↔ :left in 2D, :xmin ↔ :back in 3D) is consulted. The aliased marker answers for label only when it marks at least one point and every point it marks lies on label's face (an open face too); otherwise label means its own face, and a new BitVector marking that face is returned. So on domain(S, :left => :ymin), :left gives the ymin points and :xmin the xmin points, and on domain(S, :left => x -> false), :left marks nothing and :xmin still gives the xmin points. A label that is neither a marker nor a face name whose alias is a marker throws a KeyError.

The BitVector is a read-only view of the mesh's labels. Change labels with markers! or set_markers!, which update the marker state assembly reads; an edit made in place is not seen by assembly.

Immutable mesh state#

A mesh keeps its geometry in an immutable state. A Mesh1D holds one Mesh1DState, and a MeshnD builds a MeshnDState on demand from its submeshes' states. The state holds plain arrays and isbits values: the points and spacings, the indices, the backend, a version, whether the points are uniform, the marker bits as one word matrix and an identity that survives every mutation. The mesh itself keeps the public marker dictionary returned by markers. iterative_refinement!, change_points! and set_points! replace the state or update its arrays in place, and change the version. A state taken before the call is stale, and so is a grid space built on the mesh: its inner products and norms throw until gridspace(Ωₕ) is called again. See the mesh internals page for the state types.

The dictionary that markers(Ωₕ) returns is read-only. Editing it in place is unsupported: the word matrix assembly reads is not rebuilt, and a label added that way has no column. To change the labels use markers! or set_markers! (both private; see the same internals page). The three-argument forms, change_points!(Ωₕ, markers(Ω), pts) and iterative_refinement!(Ωₕ, markers(Ω)), re-evaluate them at the new points. The two-argument change_points!(Ωₕ, pts) keeps the face labels, which are index-based. It and set_points! refuse a mesh carrying a label defined by a predicate of position, whose set would go stale, and a move of a submesh of one; change_points!(Ωₕ, markers(Ω), pts) on the whole mesh is the way to move it.

Mesh adaptation and mutation#

Bramble.iterative_refinement!

source
iterative_refinement!(Ωₕ::AbstractMeshType, [domain_markers::DomainMarkers]; warn_marker_mismatch = true) -> Nothing

Refine the mesh Ωₕ in-place by halving each existing cell (inserting new points at midpoints). If domain markers are supplied, they are re-evaluated onto the refined grid points. warn_marker_mismatch is passed on to set_markers! then.

Without domain_markers, any custom marker Ωₕ carries beyond :boundary/:interior has no domain here to re-derive it from, so this throws an ArgumentError rather than silently dropping it. Pass domain_markers (the same ones the mesh was built with, or equivalent) to keep them.

On a multi-dimensional mesh, refine the whole mesh, not one of its submeshes: iterative_refinement!(Ωₕ(1)) changes that axis's point count but not the parent's index set or markers, and gridspace(Ωₕ) then throws an ArgumentError instead of building on the inconsistent grid.

Invalidates every ScalarGridSpace already built on Ωₕ

Bumps Ωₕ's mesh version (gpena/Bramble.jl#221; see set_points!, whose geometry update this shares). A ScalarGridSpace built with gridspace(Ωₕ) before this call keeps weights computed from the old grid; innerₕ, every inner₊*, and every norm through it now throw naming the mismatch instead of silently computing a wrong answer. Call gridspace(Ωₕ) again afterward.

Bramble.change_points!

source
change_points!(Ωₕ::AbstractMeshType, [domain_markers::DomainMarkers], pts; warn_marker_mismatch = true) -> Nothing

Update the coordinates of mesh Ωₕ in-place using new point coordinates in pts, recalculating all cached half-points and cell spacings. With domain_markers, every marker is re-evaluated at the new points, and warn_marker_mismatch is passed on to set_markers!.

Without domain_markers, a label defined by a predicate of position (:c => x -> x[1] < 0.5) would keep the set the old points gave it, so a mesh carrying one is refused with an ArgumentError before anything changes: call change_points!(Ωₕ, markers(Ω), pts) instead. Face labels (:l => :left) are index-based, stay valid when points move, and are accepted.

Invalidates every ScalarGridSpace already built on Ωₕ

Bumps Ωₕ's mesh version (gpena/Bramble.jl#221; see set_points!, which this goes through). A ScalarGridSpace built with gridspace(Ωₕ) before this call keeps weights computed from the old grid; innerₕ, every inner₊*, and every norm through it now throw naming the mismatch instead of silently computing a wrong answer. Call gridspace(Ωₕ) again afterward.

Bramble.set_points!

source
set_points!(Ωₕ::Mesh1D, pts::AbstractVector) -> Nothing

Override the grid coordinates in Ωₕ. Recalculates cached spacings, half_points, and half_spacings, in place when the point count is unchanged, then replaces the mesh's Mesh1DState with one carrying the new version and the uniformity of the new points.

The points must be finite, strictly increasing once converted to the mesh's element type and at least one, and a collapsed mesh (a degenerate interval) takes a single point; anything else throws an ArgumentError naming the first offending point, before anything changes.

A new point count also rebuilds the :boundary and :interior markers (and the marker words assembly reads) for the new grid. A mesh carrying any other label has no domain here to re-evaluate it onto the new points, so such a resize throws an ArgumentError before anything changes. Build mesh(Ω, length(pts)) and call change_points!(Ωₕ, markers(Ω), pts) instead. A call that keeps the point count leaves every marker alone, so a label defined by a predicate of position would keep the set the old points gave it: a mesh carrying one is refused with an ArgumentError, again naming change_points!(Ωₕ, markers(Ω), pts). Face labels are index-based and stay valid.

Bumps Ωₕ's mesh version (gpena/Bramble.jl#221): every ScalarGridSpace already built on Ωₕ – via gridspace, directly or as a leaf of a CompositeGridSpace – keeps its own weights, precomputed from the mesh before this call. Its innerₕ/inner₊*/norms now throw naming the mismatch instead of silently computing against stale weights; call gridspace(Ωₕ) again for a space that reads the mutated mesh. This is also what change_points! goes through, so the same applies to it.

Ghost meshes#

GhostMesh is public and not exported: write Bramble.GhostMesh. A space built with ghosts holds one; see Grid spaces.

Bramble.GhostMesh

source
GhostMesh{D, M, P} <: AbstractMeshType{D}

A physical mesh with one ghost point beyond each node of the facets it names.

GhostMesh(Ωₕ; ghosts = :boundary) pads Ωₕ on every boundary facet, ghosts = (:xmin, :ymax) only on the named ones (canonical names or their aliases), ghosts = () on none. The ghost beyond node x₁ sits at x₁ - h₂ and the one beyond xₙ at xₙ + hₙ, read from the physical mesh's points whenever they are asked for, so the mesh follows change_points! of Ωₕ.

Its points, spacings and indices are those of the padded mesh, npoints(G, Tuple) the padded sizes and G(i) the padded axis submesh; the labels and facet queries (boundary_indices, index_in_marker, the surface weights) name the physical nodes. The ghosts are numbered 1:nghosts(G): facets in the order xmin, xmax, ymin, ymax, zmin, zmax (named ones only), each facet's ghosts in the column-major order of its nodes. A padded point outside along two or more axes is absent (Bramble.is_absent) and has no ghost number. Bramble.ghost_index and Bramble.boundary_node map in O(1) between a facet node and its ghost.

After iterative_refinement! of Ωₕ the ghost mesh is stale: its points, spacings, submeshes and cell lookup throw an ArgumentError, while its counts and index sets keep the sizes it was built with. Build a new one.

Examples

Ωₕ = mesh(domain(interval(0.0, 1.0) × interval(0.0, 2.0)), (5, 7), (false, true))
G = Bramble.GhostMesh(Ωₕ; ghosts = :boundary)
npoints(G, Tuple)            # (7, 9)
Bramble.nghosts(G)           # 24