Local co-refinement of two domains¶
LocalCoRefinement<d1, d2, spacedim> adjusts two meshes before application
finite-element spaces are constructed. The first domain is space, the second
is embedded. These are roles, not restrictions on intrinsic dimension: for
example, both <2, 1, 2> and <1, 2, 2> are supported.
The utility retains imported local fields while refining and partitions
fullydistributed meshes only after all adjustments have finished. It does not
transfer an application’s solution or refine an already finalized domain.
Uniform triangle and tetrahedron meshes use matching reference-cell mappings.
Set their Domain backend to fullydistributed; the serial preparation mesh
can be refined before distribution. Simplex preparation disables deal.II’s
hypercube smoothing rules. Imported point and cell fields follow refinement.
Ordinary usage¶
This example assumes that line.vtk contains an array named thickness.
#include <immersx/grid/local_co_refinement.h>
ImmersX::DomainParameters<2> space_parameters("/Space/Domain/");
ImmersX::DomainParameters<1, 2> embedded_parameters("/Embedded/Domain/");
embedded_parameters.name_of_grid = "/absolute/path/to/line.vtk";
embedded_parameters.read_vtk_fields = true;
ImmersX::RefinementParameters<1> refinement;
// Parse the parameter file after constructing the parameter objects.
refinement.refinement_strategy = "both";
refinement.lower_factor = 0.5;
refinement.upper_factor = 2.0;
ImmersX::Domain<2> space(space_parameters);
ImmersX::Domain<1, 2> embedded(embedded_parameters);
const auto report =
ImmersX::LocalCoRefinement<2, 1, 2>(refinement).run(space, embedded);
// Both meshes are finalized now. Attach application FE spaces and access
// imported fields only after run().
const auto thickness = embedded.field("thickness");
The domains and the utility keep references to their parameter objects; those
objects must outlive them. Both domains must have congruent MPI communicators.
Call run() collectively, with the same parameters on every rank.
Do not call make_grid() before run(): make_grid() prepares and finalizes
immediately. An already finalized domain is rejected rather than overwritten.
To modify the meshes before adjustment, explicitly call
space.prepare_grid() and embedded.prepare_grid(true), modify their
refinement_triangulation() objects, then call run(). The true keeps the
embedded preparation mesh serial even when its final backend is distributed.
Do not attach application DoFHandlers to this temporary preparation mesh.
The Coral registry provides the same workflow as a
Co-refine domains operation on two owning domain handles, with a separate
report output and explicit downstream FE-space construction.
Using a mesh owned by a Problem¶
Domain can also observe an existing parallel triangulation:
// Construct the Problem before the Domain: it owns the triangulation.
Problem problem;
ImmersX::Domain<2> domain(domain_parameters, problem.triangulation());
The Problem and the parameter object must outlive domain. Domain stores an
ObserverPointer to the mesh. Its usual constructor also creates a
unique_ptr owning the mesh; the external-mesh constructor leaves that owner
empty and takes the MPI communicator from the supplied triangulation.
A populated external mesh is not generated, scaled, or refined by the
constructor. With Read fields = true, the constructor imports the configured
VTK fields onto that mesh, which must match the input mesh. Access a property
with domain.field("a0"). On a parallel::distributed::Triangulation, retained
fields follow refinement and coarsening through triangulation signals without
rereading the file. Attach Domain before those mesh changes. The owner remains
responsible for transferring its own solution.
For an empty external mesh, use make_grid() or the preparation/finalization
workflow above. This also supports fullydistributed meshes, for which the
existing property transfer occurs on the serial preparation mesh before
partitioning. Observing a mesh does not add adaptive-refinement support to
fullydistributed triangulations.
If the owner has already read a serial coarse mesh, call
domain.prepare_grid(coarse_mesh). Domain uses the supplied coarse geometry
and preserves its material and boundary ids. It imports the configured
properties before refinement and uses the same finalize_grid() transfer.
The final observed mesh must still be empty.
Partition the embedded mesh with the background¶
Set the partitioning policy in the same refinement parameter group:
subsection Local refinement parameters
set Embedded partitioning policy = immersed
end
The default is default, which keeps each Domain’s ordinary partitioning.
With immersed, co-refinement finalizes the background first, then uses
ImmersedRepartitioner, also used by ReducedPoisson, to assign each embedded
cell to the rank owning the background cell containing its center. If the center
belongs to cells on several ranks, the lowest rank is selected. This aligns
ownership at cell centers; an embedded cell may still cross a partition boundary.
The embedded mesh must lie inside the background mesh.
This option requires a full-dimensional background (d1 == spacedim) and a
fullydistributed embedded Domain. One-dimensional Domains always use this
backend. For higher dimensions, set Triangulation type = fullydistributed in
the embedded Domain’s parameter group. Both supported background backends work.
Unsupported dimension/backend combinations are rejected before mesh preparation.
Imported point and cell fields are transferred after the final ownership is
established, so the option does not require reading the input again. It applies
only to LocalCoRefinement::run(), before application FE spaces exist; the raw
adjust_grids() compatibility entry point only adjusts refinement.
For a custom deal.II repartitioning policy, call Domain::prepare_grid(), adjust
the preparation mesh, then call Domain::finalize_grid(policy). This overload
requires a fullydistributed Domain. It evaluates the policy collectively once
and transfers retained input fields onto that final partition. Neither the
Domain nor its triangulation retains a reference to the policy.
What happens, in order¶
Initial refinement. Each empty domain reads or generates its mesh, imports the requested VTK fields, applies scaling, and applies its own
Initial refinement. Explicitly prepared domains skip this preparation.Pre-refinement. Globally refine space and embedded by their respective pre-refinement cycle counts.
Local adjustment. Construct two Boost rtrees through deal.II’s
GridTools::Cache, query intersecting cell bounding boxes, and repeatedly refine according to the selected strategy. The caches update after mesh changes; queries never reuse cell sizes from before a refinement.Space post-refinement. For each requested cycle, refine every space cell whose bounding box intersects an embedded cell bounding box, then repeat the entire local adjustment. This is local, not global refinement of space. It runs regardless of the strategy.
Embedded post-refinement. Globally refine embedded by its requested cycle count, then repeat the entire local adjustment once more. This also runs regardless of the strategy.
Finalization. Finalize space first, then partition embedded with the selected policy and redistribute its retained input fields onto the final mesh.
Thus space does not mean embedded is untouched: embedded’s initial,
pre-refinement, and post-refinement cycles still apply. Likewise, embedded
still allows the specified space pre/post cycles. Strategy controls only the
ratio-driven local adjustment.
The size criterion¶
For each intersecting bounding-box pair, let h_space and h_embedded be the
diagonals of the axis-aligned cell bounding boxes. They are not cell volumes,
edge lengths, or a measure of the portion of an embedded cell inside space.
The same open interval applies to every strategy:
lower_factor * h_space < h_embedded < upper_factor * h_space
Strategy |
Ratio-driven refinement |
|---|---|
|
Refine space when |
|
Refine embedded when |
|
Execute a space marking/refinement pass, query the updated trees, then execute an embedded marking/refinement pass. Repeat the two passes until neither marks any cell. |
Equality triggers refinement. Cells are never coarsened. A pair of boxes that touches at an edge or vertex counts as intersecting. Bounding boxes can also intersect when the actual cells do not, especially for oblique segments. All such pairs participate in the criterion; this is intentionally not an exact cell-intersection algorithm.
The two factors keep the same meaning when the strategy changes. If the size
ratio were instead written with inverted roles, its equivalent bounds would
be 1 / upper_factor and 1 / lower_factor, not the original factors.
Limits and the report¶
Factors must be finite and satisfy 0 < lower_factor < upper_factor.
Prefer upper_factor > 2 * lower_factor for dyadic isotropic refinement. A
narrower interval may miss every attainable ratio and make both alternate
refinements until the level cap is reached. A wider interval still does not
guarantee that every pair can satisfy the bounds: one cell may intersect
several cells with incompatible sizes.
Max refinement level limits only ratio-driven local adjustment. It does
not limit Initial refinement, global pre-refinement, local space
post-refinement, or global embedded post-refinement. Those requested cycles
can raise cells above the cap; the following ratio-driven adjustment then
cannot refine those cells further.
run() returns a LocalCoRefinementReport with global counts:
intersecting_pairs: all final intersecting bounding-box pairs;lower_violations: pairs at or below the lower bound;upper_violations: pairs at or above the upper bound;satisfied(): both violation counts are zero.
Remaining violations are not a fatal error. The utility prints a final summary and returns their counts. They can result from the chosen single-mesh strategy, the level cap, or incompatible intersecting sizes. Zero intersecting pairs gives a satisfied report with zero pairs; it does not prove that the embedded mesh lies inside the space domain. The report describes the final meshes after post-refinement and its subsequent corrections, not just the first local adjustment.
Parameters¶
The parameter group is Local refinement parameters, under the path of the
owning parameter configuration. For existing tensor-product coupling files it
remains under Tensor product coupling.
subsection Local refinement parameters
set Refinement strategy = both
set Lower factor = 0.5
set Upper factor = 2
set Max refinement level = 10
set Space pre-refinement cycles = 0
set Embedded pre-refinement cycles = 0
set Space post-refinement cycles = 0
set Embedded post-refinement cycles = 0
set Refinement factor = 0
end
These are the defaults except that the default strategy is space.
Refinement factor is a deprecated compatibility input for the old single-mesh
configuration. Its default is now zero, which disables it. When positive:
with
space, it replacesLower factorby1 / Refinement factor;with
embedded, it replacesUpper factorbyRefinement factor;with
both, it is rejected: specify the two bounds explicitly.
The other bound remains unchanged and the resulting interval must be valid.
The new criterion is strict, so equality at the replaced bound now triggers
refinement too. New files should leave this compatibility input at zero.
The zero-dimensional point-cloud workflow retains its separate, unchanged
RefinementParameters<0> and its original Refinement factor semantics;
there is no second mesh to co-refine there.
Fields, ids, and parallel execution¶
Input point fields are represented by FE_Q(1) and interpolated onto children.
Input cell fields use FE_DGQ(0) and retain their parent’s value on each child,
including jumps between adjacent cells. All catalog entries and vector
components are retained. Material, boundary, and manifold ids follow deal.II
refinement and mesh copying; manifold objects are cloned onto the final mesh.
The imported fields must be enabled in each domain whose data should be retained. Boundary and material id selections are independent of field retention. The original input file is never reread onto the refined final mesh. There is no averaging of different cell-property values during finalization.
The embedded preparation mesh is replicated serially on every MPI rank. When space is distributed, ranks query their locally owned space cells and union embedded refinement flags across ranks before refining. Synchronizing only a stopping condition would produce different embedded meshes on different ranks; the utility synchronizes the actual cell flags too.
For a fullydistributed domain, mesh construction and all refinement stages
remain serial and replicated until finalization. This also applies to
one-dimensional domains, for which Domain uses the fullydistributed backend.
It requires memory for the complete preparation mesh on each rank.
A serial preparation mesh destined for parallel::distributed is finalized by
reconstructing its coarse mesh and replaying its hierarchy: deal.II’s p4est
backend cannot directly copy a refined serial triangulation. Fields are
redistributed only after the matching final hierarchy exists.
Existing raw-triangulation coupling code calls adjust_grids(), which delegates
to the same adjustment algorithm. Its transfer hooks must already be attached;
it cannot adjust an already finalized fullydistributed space mesh. New
application code should use the domain-level utility and its delayed
finalization rather than that compatibility entry point.
Use both in elasticity and reduced Poisson¶
The current elasticity (with Coupling type = TensorProduct) and
reduced_poisson applications both use ReducedCoupling. Its preprocessing
hook runs the same adjustment algorithm before application DoFs and particle
search structures are built. The representative mesh remains serial until
all refinement passes finish; its imported properties are transferred on each
pass and then redistributed with the final mesh. No separate implementation
of both is needed in either solver.
In both application parameter files, select:
subsection Tensor product coupling
subsection Local refinement parameters
set Refinement strategy = both
set Lower factor = 0.5
set Upper factor = 2
set Refinement factor = 0
set Max refinement level = 6
end
end
Set the deprecated Refinement factor to zero when adapting an old file that
explicitly sets it to one. These settings control initial co-refinement, not
the later physics-driven Refinement and remeshing / Strategy. The current
raw-triangulation application path requires a distributed background mesh;
for elasticity, select Triangulation type = distributed. Delaying a
fullydistributed background mesh requires the domain-level API above.
The fullydistributed representative mesh is already partitioned after
co-refinement in both applications. Zero-dimensional point inputs have no
second mesh and do not support both.
Two executable examples use the same input with two unequal segments, an
affine point field height, and a discontinuous cell field radius. They use
Thickness = radius and nonzero reduced data derived from height:
cmake --build build-debug --target elasticity_debug reduced_poisson_debug -j
build-debug/elasticity_debug \
build-debug/gtests/parameters/elasticity_co_refinement_both.prm
build-debug/reduced_poisson_debug \
build-debug/gtests/parameters/reduced_poisson_co_refinement_both.prm
LocalCoRefinement.BOTH_ElasticityApplication and
LocalCoRefinement.BOTH_ReducedPoissonApplication load these generated input
files and exercise setup, assembly, and a nonzero solve in serial and MPI.
They require refinement of both meshes, not just acceptance of the parameter.
The elasticity test also checks the final diameter bounds and the imported
point/cell values independently of the refinement algorithm.
Examples from a test¶
Every image below is an output of
LocalCoRefinement.DocumentationExamples in
gtests/local_co_refinement_01.cc. The test constructs the same random binary
tree geometry with std::mt19937 seed 258, runs the utility for each
configuration, compares its report with an independent all-pairs oracle, and
writes final meshes in VTK, images in SVG, and counts in summary.csv.
The figures are not made by a separate Python refinement implementation.
The box is a Triangulation<2, 2> of [0, 1]^2. The green branches form the
geometry of a Triangulation<1, 2> with 30 initial segments. deal.II’s
codimension-one orientation checks reject the triple junctions of this tree.
The example therefore gives geometrically coincident junction endpoints
distinct vertex indices. It tests geometric co-refinement, not continuity
or flow conservation at a degree-three junction. The test constructs that mesh
directly in the embedded domain’s serial preparation phase, without passing
its coincident endpoints through a mesh reader’s cleanup step.
Gray lines show space-cell boundaries; green circles show embedded vertices.
Both domain Initial refinement counts are zero. Both final backends are
fullydistributed. The examples use the following explicit settings:
Example |
Strategy |
Lower / upper |
Pre space / embedded |
Post space / embedded |
Cap |
|---|---|---|---|---|---|
Space |
|
0.5 / 2 |
1 / 0 |
0 / 0 |
7 |
Embedded |
|
0.5 / 2 |
4 / 0 |
0 / 0 |
7 |
Both |
|
0.5 / 2 |
2 / 0 |
0 / 0 |
7 |
Wider interval |
|
0.2 / 3 |
2 / 0 |
0 / 0 |
7 |
Pre/post cycles |
|
0.5 / 2 |
2 / 1 |
1 / 1 |
7 |
Capped |
|
0.5 / 2 |
0 / 2 |
0 / 0 |
1 |
Space and embedded¶
Space: local refinement concentrates around branches. Embedded has no ratio-driven refinement, so upper-bound violations remain where its segments intersect small space cells.¶
Embedded: the four space pre-refinement cycles stay uniform. Long branches are split where required by the upper bound. Lower-bound violations remain near short segments because space cannot be corrected by this strategy.¶
Both and a wider interval¶
Both: space passes run before embedded passes, and each embedded pass sees the updated space sizes.¶
Wider interval: the same input and pre-refinement with lower 0.2 and upper 3 permit a larger difference between the two mesh sizes.¶
Pre/post cycles and a limiting cap¶
Pre/post cycles: embedded receives one global pre pass; space receives one local post pass; embedded receives one global post pass. Ratio corrections run after the post stages too.¶
Capped: embedded pre-refinement produces small segments but space cannot refine beyond level one in ratio-driven passes. The report retains the unsatisfied lower-bound pairs instead of claiming success.¶
The exact cell and violation counts are published directly from the test:
example |
strategy |
lower |
upper |
space_pre |
embedded_pre |
space_post |
embedded_post |
cap |
space_cells |
embedded_cells |
pairs |
lower_violations |
upper_violations |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
space |
space |
0.5 |
2 |
1 |
0 |
0 |
0 |
7 |
145 |
30 |
124 |
0 |
27 |
embedded |
embedded |
0.5 |
2 |
4 |
0 |
0 |
0 |
7 |
256 |
36 |
133 |
26 |
0 |
both |
both |
0.5 |
2 |
2 |
0 |
0 |
0 |
7 |
169 |
42 |
133 |
0 |
0 |
both-wide |
both |
0.2 |
3 |
2 |
0 |
0 |
0 |
7 |
76 |
30 |
96 |
0 |
0 |
both-post |
both |
0.5 |
2 |
2 |
1 |
1 |
1 |
7 |
1042 |
178 |
527 |
0 |
0 |
both-capped |
both |
0.5 |
2 |
0 |
2 |
0 |
0 |
1 |
4 |
120 |
130 |
130 |
0 |
Regenerate the published figures¶
From the repository root:
cmake --build build-debug --target gtests_debug -j
GTEST_FILTER='LocalCoRefinement.DocumentationExamples' \
"$PWD/build-debug/gtests/gtests_debug"
python3 scripts/update_local_co_refinement_figures.py \
build-debug/test_output/local-co-refinement/examples
./scripts/build_doc.sh
The test writes only to TEST_OUTPUT_DIR in the build tree. The update script
copies just its six SVG figures and its CSV into the documentation assets;
the VTK meshes remain in the build tree for inspection. To check that published
assets still match fresh test outputs without modifying them, add --check to
the update-script command. Run the test before publishing figures; do not use
outputs from a failed run.