Coral backend

ImmersX can build optional plugins for Coral, the graph runtime used by dealiiX-platform. Coral is not required by the ordinary ImmersX library or by its applications.

Configure and build

Point CMake at the installed Coral package and leave the optional backend enabled:

cmake -S . -B build-coral \
  -DCMAKE_BUILD_TYPE=Debug \
  -Dcoral_DIR=/Users/heltai/dualistic/coral/inst/lib/cmake/coral \
  -DIMMERSX_BUILD_CORAL_PLUGINS=ON
cmake --build build-coral -j

The option defaults to ON, but plugin targets are created only when CMake finds a compatible shared coral::core target. To build the ordinary ImmersX library without Coral, use:

cmake -S . -B build \
  -DCMAKE_BUILD_TYPE=Debug \
  -DIMMERSX_BUILD_CORAL_PLUGINS=OFF

DebugRelease requires both Debug and Release configurations from Coral. If the package exposes only one configuration, CMake disables the Coral plugins and prints a warning instead of mixing ABI variants.

Plugins and dimensions

The build produces one plugin for each ambient dimension:

build-coral/coral/libcoral_backend_immersx_1_debug.so
build-coral/coral/libcoral_backend_immersx_2_debug.so
build-coral/coral/libcoral_backend_immersx_3_debug.so

Each plugin registers only intrinsic dimensions valid for its ambient space:

plugin

registered (dim, spacedim) pairs

1D

(1,1)

2D

(1,2), (2,2)

3D

(1,3), (2,3), (3,3)

The current stable problem names are ImmersX::Poisson<dim,spacedim>, ImmersX::ElasticStatic<dim,spacedim>, and ImmersX::Elastodynamics<dim,spacedim>. Their parameter objects and lifecycle operations are registered under the same dimension-qualified naming scheme.

To inspect a registry:

DYLD_LIBRARY_PATH=/Users/heltai/dualistic/coral/inst/lib \
  /Users/heltai/dualistic/coral/inst/bin/coral \
  -p build-coral/coral/libcoral_backend_immersx_2_debug.so \
  register --registry-path /tmp/immersx-coral-2d-registry.json

On Linux, use LD_LIBRARY_PATH in place of DYLD_LIBRARY_PATH.

Poisson graph

The checked-in graph and parameter file are installed below share/immersx/coral/examples:

poisson_2d.json
poisson_2d.prm

The graph makes the mutable lifecycle explicit:

parameters -> initialize -> Poisson -> make_grid -> setup_fe
                                      -> setup_system -> assemble -> solve -> output

The edges between mutating operations are required. Coral may execute independent nodes concurrently, so a graph must pass the object through each mutation when the order matters.

The plugin initialization block enables MPI. Run the graph with the same MPI launcher and runtime library used by the deal.II installation, for example:

cd /path/to/installed/examples
mpirun -np 1 env \
  DYLD_LIBRARY_PATH=/Users/heltai/dualistic/coral/inst/lib:/path/to/immersx/lib \
  THREADS=1 \
  /Users/heltai/dualistic/coral/inst/bin/coral \
  -p /path/to/immersx/lib/immersx/coral/libcoral_backend_immersx_2_debug.so \
  run poisson_2d.json --graph poisson_2d.dot

The parameter file path is resolved by Coral from the process working directory. Therefore the example command is run from the directory containing the installed graph and parameter file. Output remains controlled by the parameter file.

If an Initialize parameters node refers to a parameter file that does not exist, the plugin creates its parent directories, declares all connected ParameterAcceptor objects, and writes a documented ParameterHandler file using DefaultStyle. Rank zero performs the write and the other ranks wait before parsing it. The graph then parses the generated file and continues in the same execution with the default values. An existing file is still parsed normally, so invalid or unreadable files remain errors. A subsequent dealiiX-platform run can detect the persistent file and stage it in its run directory.

The same pass-through lifecycle is used by the checked-in static-elasticity and standalone-elastodynamics graphs:

elastic_static_2d.json       elastic_static_2d.prm
elastodynamics_2d.json       elastodynamics_2d.prm

They use the 2D plugin and expose the parameter bundle, problem construction, and ordered mutating operations as separate nodes.

When deal.II has SUNDIALS support, the 2D plugin also provides an IDA-backed execution seam:

ida_elastodynamics_2d.json       ida_elastodynamics_2d.prm

ImmersX::IDAElastodynamics<2,2> keeps IDA state initialization, accepted state handoff, and output callbacks inside the binding. The graph exposes only run, finiteness, and current-time nodes.

The 2D and 3D fiber examples are also installed. Their graph exposes the composition explicitly:

FiberReinforcedElastodynamicsGraph
        |
        +--> matrix Problem ------+
        |                         |
        +--> embedded fiber ------+--> velocity-continuity Interaction
        |                                               |
        +--> Fiber execution adapter -------------------+
                                                        |
                                                       IDA

The graph-facing FiberMatrixProblem<dim>, FiberEmbeddedProblem<dim>, and FiberVelocityContinuity<dim> nodes are small Coral façades over the existing ImmersX Problems and make_continuity_constraint() helper. The execution adapter owns the IDA state and accepted-state output; when SUNDIALS is not available, it uses the existing fixed-step fallback. The driver remains the native owner of the physical assembly and writes matrix, fiber, and multiplier outputs under the configured output directory.

The 2D plugin also exposes the bulk/embedded Poisson workflow through generic Problem, finite-element-space, Constraint, and LinearAdapter nodes. Its example is:

coupled_poisson_2d.json
coupled_poisson_2d.prm

The graph constructs two existing PoissonSolver Problems, creates the multiplier finite-element space on the embedded mesh, adds both Problems and a continuity Constraint to one LinearAdapter, transfers the solved fields back to the Problems, writes bulk, embedded, and multiplier output, and checks the composed residual. The old standalone ImmersX::CoupledPoisson<2> façade is not part of the 2D registry.

Primitive scalar solve

The 2D plugin also exposes the lower-level generic path. The checked-in example poisson_primitives_2d.json creates an owning Domain, creates an owning FiniteElementSpace directly from that domain, selects boundary conditions for a field, registers the constrained field as an algebraic execution field, and adds the weak term

weak_term(gradient(u), gradient(test(u)))

to one LinearAdapter. The same path is available directly in C++:

Domain<2> domain(domain_parameters);
domain.make_grid();
FiniteElementSpace<2> space(domain.triangulation(), space_parameters);
BoundaryConditions<2> boundary_conditions(space);
dealii::Functions::ConstantFunction<2> boundary_value(1.0);
boundary_conditions.add_dirichlet(0, boundary_value);
boundary_conditions.update(0.);

const auto unregistered = space.view().field("u");
const auto registration =
  adapter.add(algebraic_field(unregistered), "solution");
const auto u = registration.fields();
adapter.add_preconditioner(
  u.id(), [](const auto &matrix, const auto &reinit_vector) {
    return make_amg_preconditioner(matrix, reinit_vector);
  });
adapter.add(weak_term(gradient(u), gradient(test(u))), "laplace");

auto state = adapter.make_state();
adapter.solve(state);

The Coral expression binding follows the same compositional grammar as the C++ API:

Field -> Test -> FE operation
Field -> FE operation
FE expression + test-side FE expression -> Weak term

For the Poisson primitive graph, the variational path is therefore:

Field -> Gradient
Field -> Test -> Gradient
Gradient + Test Gradient -> Weak term

The Coral primitive Register algebraic field registers the same AMG local preconditioner automatically. This makes the default automatic linear adapter policy usable for the checked-in graph.

Test, Value, Gradient, Divergence, Symmetric gradient, and Curl are operation families registered for the supported scalar/vector overloads. The node signatures remain concrete C++ value types, while their metadata keeps the graph vocabulary at the operation-family level. Weak term consumes the outputs of those expression nodes directly; it does not encode a Poisson- or Laplace-specific shortcut. The current dealiiX-platform UI may still display concrete overloads separately until it groups nodes using the shared operation metadata.

Boundary conditions follow the same explicit data-flow style:

Field -> Boundary conditions -> Dirichlet boundary condition
Constant function + boundary id + Boundary conditions
    -> Dirichlet boundary condition
Boundary conditions + Field -> Apply boundary conditions -> Field

Boundary conditions creates the persistent condition set associated with a field. Constant function is a reusable boundary-function node, and Dirichlet boundary condition returns an updated condition set. Apply boundary conditions evaluates that set and passes the constrained field to subsequent execution nodes. Boundary-function nodes are owned by the condition set, so the graph does not depend on the lifetime of a temporary C++ function object. The specialized Set constant Dirichlet boundary condition node has been removed; there is no compatibility alias for it.

The three concrete function sources converge on the same handle:

using FunctionHandle =
  std::shared_ptr<const dealii::Function<spacedim>>;

Coral transports that handle in ImmersX::Function<spacedim>, whose value member has type FunctionHandle. In addition to Constant function, the Parsed function operation has two variants:

  • Expression accepts exactly const std::string &expression and constructs a scalar dealii::FunctionParser<spacedim> with default_variable_names() + ",t". It supplies the constants string E=<value>,PI=<value> using dealii::numbers::E, dealii::numbers::PI, and std::numeric_limits<double>::max_digits10 precision.

  • Parameterized uses the existing ImmersX::ModulatedParsedFunction<spacedim>. The concrete object is a ParameterAcceptor derived type, so it can be connected to the generic Initialize parameters operation before it is converted to the common function handle. Its parameters are Function constants, Function expression, Variable names, Modulation frequency, and Phase shift.

The same handle type can therefore be consumed by boundary conditions today and by KnownTerm/RHS graph operations when those operations are exposed in Coral.

The boundary helper rebuilds hanging-node and Dirichlet constraints on the owning space. For an inhomogeneous boundary, the linear weak-term assembly includes the corresponding affine contribution in the residual. Output uses the generic OutputHandler and the semantic field name; the example writes output/solution.pvd.

The 3D plugin provides the corresponding Poisson-to-elasticity example:

coupled_poisson_elasticity_3d.json
coupled_poisson_elasticity_3d.prm

ImmersX::CoupledPoissonElasticity<3> uses the existing value, scaled TensorProductLift, normal, weak_term, and LinearAdapter components. Its graph exposes the coupled residual, pressure-scale, and traction-balance diagnostics after the native Poisson and elasticity outputs are written.

The 3D plugin also provides a point-backed ReducedPoisson example:

reduced_poisson_3d.json       reduced_poisson_3d.prm

ImmersX::ReducedPoissonWorkflow<3> delegates to the existing ReducedPoisson<3,3,0,3> implementation. The binding supplies one representative point at (0.5, 0.5, 0.5), so the installed graph does not depend on a source-tree VTK point-cloud file. It exposes the reduced DoF count, coupling-matrix norm, solution norms, and a finiteness check after run.

Local co-refinement

The plugins expose Co-refine domains for every pair of positive intrinsic dimensions up to the plugin’s ambient dimension, including reversed space and embedded roles. This operation calls LocalCoRefinement<d1,d2,spacedim>::run(); it does not duplicate the refinement algorithm.

Use this graph order:

Initialize parameters (both domains, refinement, FE space, adapter, output)
  |
  +-> Create domain (space) ----+
  +-> Create domain (embedded) -+-> Co-refine domains
  +-> refinement parameters ---+       |
                                       +-> space -> Create finite element space
                                       +-> embedded
                                       +-> report

Connect ImmersX::RefinementParameters<d2> to the third input. Its constructor accepts a subsection path, for example /Co-refinement/, so independent co-refinement operations can use separate parameter groups. The default C++ constructor still uses Local refinement parameters.

The three outputs, in order, are the updated owning space handle (port 0), the updated owning embedded handle (port 1), and an ImmersX::LocalCoRefinementReport (port 2). Both domains are finalized before any output becomes available. Do not insert Generate domain before this operation: it finalizes the mesh too early. Do not reuse a finalized domain in another co-refinement operation or attach application FE spaces before refinement.

Report operations expose the global intersecting-pair, lower-violation, and upper-violation counts, and Co-refinement satisfied returns a Boolean. Remaining violations are nonfatal. Connect the report to Require satisfied co-refinement only when the graph should fail on remaining violations. Neither satisfaction check proves geometric coverage: zero intersecting pairs also satisfy the bounds.

The installed example consists of:

local_co_refinement_2d.json
local_co_refinement_2d.prm
local_co_refinement_space_2d.vtk
local_co_refinement_embedded_2d.vtk

It uses both with bounds 0.5 and 2, and unequal embedded segments that require refinement of both meshes. Both final backends are fullydistributed. After refinement, the graph reads the embedded discontinuous radius field and uses the retained affine bulk rhs field in the primitive Poisson solve. The explicit report check requires both size bounds to hold. Run it from the installed examples directory with the same Coral command shown above, replacing poisson_2d.json with local_co_refinement_2d.json. Use the same graph on every MPI rank and THREADS=1 for collective execution.

See Local co-refinement for bounds, level caps, pre/post-refinement, and field-transfer semantics.

Tests

When the Coral executable target is available, CTest registers one registry test for each plugin. These tests verify the dimension policy, stable problem names, and common elementary types:

ctest --test-dir build-coral -R 'ImmersX.Coral.Registry' \
  --output-on-failure

The numerical graph tests are opt-in because they need a working MPI runtime in addition to the Coral package:

cmake -S . -B build-coral \
  -DCMAKE_BUILD_TYPE=Debug \
  -Dcoral_DIR=/Users/heltai/dualistic/coral/inst/lib/cmake/coral \
  -DIMMERSX_ENABLE_CORAL_GRAPH_TESTS=ON
ctest --test-dir build-coral -R 'ImmersX.Coral.Graph' \
  --output-on-failure

The tests check the generated graphviz files and the configured native output for Poisson, static elasticity, elastodynamics, both coupled workflows, fiber reinforcement, and ReducedPoisson. The co-refinement graph runs with one rank and, when an MPI launcher is available, two ranks.

The registry tests do not replace numerical application tests. Graph execution also requires a working MPI runtime because the current distributed ImmersX problems use MPI_COMM_WORLD during construction.