Bramble.jl GitHub

Geometry tutorial#

What you will learn. How to describe where a problem lives: a set, the names of its boundary pieces, and the domain that bundles both.

What you need first. Only a working install, see the home page.

Where next. The mesh tutorial turns a domain into a grid of points.


A first domain: a rod#

Take a rod of length 10, held at a fixed temperature at its left end and insulated at the right end. The geometry is an interval, and the two ends need names so that a condition can later be attached to each:

1D Rod Domain
1D Rod Domain

using Bramble
import Bramble: point, center, projection, topo_dim

rod = domain(interval(0.0, 10.0), :dirichlet => :left, :neumann => :right)

dim(rod), center(rod), collect(labels(rod))
(1, (5.0,), [:dirichlet, :neumann])

interval builds the closed interval \([0, 10]\). Each label => side pair is a marker: a name for a piece of the boundary. domain bundles the set with its markers, and a Domain is what mesh takes. dim is the dimension of the space the rod lives in, and center its midpoint.

Without markers, domain(interval(0.0, 10.0)) labels the whole external boundary :boundary.

Try this

Replace the interval by interval(0.0, 10.0) × interval(0.0, 1.0) and keep the two markers. dim becomes 2, and :left and :right still name the two short sides of a 2D strip, but in 3D they are the y faces.


Sets in more than one dimension#

The tensor product operator × (\times<tab>) builds hyper-rectangles from intervals:

I = interval(0.0, 1.0)
Ω_2d = I × I
Ω_3d = interval(-1.0, 1.0) × interval(0.0, 2.0) × interval(0.0, 0.5)
CartesianProduct{3,Float64}:
  x: [-1.0, 1.0]
  y: [0.0, 2.0]
  z: [0.0, 0.5]

A set answers geometric questions. ∈ accepts a number in 1D, and in any dimension a tuple or vector of length dim:

X = interval(0.0, 2.0) × interval(-1.0, 1.0)

dim(X), extrema(X), extrema(X, 1), center(X)
(2, ((0.0, 2.0), (-1.0, 1.0)), (0.0, 2.0), (1.0, 0.0))
0.5 ∈ I, 1.5 ∈ I, (0.5, 0.5) ∈ Ω_2d, (1.2, 0.3) ∈ Ω_2d
(true, false, true, false)

extrema(X, 1) is the range along the first axis, and projection(X, 1) is that axis as an interval.

Try this

Evaluate (1.2, 0.3) ∈ X and (1.2, 1.3) ∈ X. The second point leaves the box through the top.


Naming boundary pieces#

A marker is a :label => identifier pair. The identifier is one boundary symbol, a tuple of them, or a predicate. Boundary symbols are coordinate-aligned (:xmin, :xmax, :ymin, :ymax, and so on, with aliases such as :left and :top), and the geometry reference lists them all.

A channel with inflow on the left, outflow on the right and no-slip walls on top and bottom:

2D Channel Flow Domain
2D Channel Flow Domain

geom = interval(0.0, 5.0) × interval(0.0, 1.0)

channel = domain(
    geom,
    :inflow => :left,
    :outflow => :right,
    :wall => (:top, :bottom)
)

dim(channel), center(channel), collect(labels(channel))
(2, (2.5, 0.5), [:inflow, :outflow, :wall])

The tuple (:top, :bottom) gives one name to two faces. Markers are only names at this stage: no boundary condition is attached until a problem is posed on the mesh built from the domain, see the form tutorial.

Try this

Change :wall => (:top, :bottom) to :top_wall => :top, :bottom_wall => :bottom, and print collect(labels(channel)) again. Two names let a later problem treat the two walls differently.

A predicate marks any subset, not only a face. Its argument is the point p; a marker set built over a time interval takes (p, t) predicates:

using LinearAlgebra

m_cond = markers(
    geom,
    :inflow => :left,
    :hot_spot => (p -> p[1] > 2.5 && p[2] ≈ 0.0)
)

m_time = markers(
    geom,
    interval(0.0, 10.0),
    :moving_source => ((p, t) -> norm(p .- [t, 0.5]) < 0.2)
)

collect(labels(m_cond)), m_time(1.5)      # the second is the marker set frozen at t = 1.5
([:inflow, :hot_spot], Bramble.EvaluatedDomainMarkers{Bramble.DomainMarkers{Tuple{}, Tuple{}, Tuple{Bramble.Marker{Main.var"#5#6"}}, Tuple{}}, Float64}(DomainMarkers(1 total), 1.5))

labels returns one tuple of every label, symbol markers first, then tuple markers, then predicate markers, then the three labels of each polygon or level-set marker, built without allocating. The helpers label_symbols, label_tuples and label_conditions behind it are internal.


A domain in three dimensions#

The same two ideas, a set and named faces, carry over unchanged. A heat sink, heated below, cooled above and insulated on its four sides:

3D Heat Sink Domain
3D Heat Sink Domain

sink = domain(
    interval(0.0, 2.0) × interval(0.0, 2.0) × interval(0.0, 1.0),
    :heat_source => :zmin,
    :convection => :zmax,
    :insulated => (:xmin, :xmax, :ymin, :ymax)
)

center(sink), collect(labels(sink))
((1.0, 1.0, 0.5), [:heat_source, :convection, :insulated])

A Domain forwards the geometric queries of the previous sections to its set:

Ω = domain(
    I × I,
    :dirichlet => (:left, :right),
    :neumann => (:top, :bottom)
)

dim(Ω), center(Ω), (0.5, 0.5) ∈ Ω, Bramble.is_collapsed(Ω)
(2, (0.5, 0.5), true, false)

Bramble.set (private, hence the prefix) returns the underlying set.