Skip to content

Driving the solver (C++ API)

The declarative files — pdemodel.txt (the model) and pdeapp.txt (the app) — describe what to solve. This page covers the imperative side: the ExasimSolver C++ class, which lets your own program construct the solver, step it, and exchange data at a mesh interface. It is the contract you build against when embedding Exasim in a larger program.

Everything here is in <exasim/ExasimSolver.hpp> (pulled in by <exasim/ExasimSolverSetup.hpp>). The skeleton below is illustrative scaffolding, not a complete coupled solve.

Lifecycle

A run is a fixed sequence of calls. For an ordinary, self-contained run you do not write them out — the RunExasimSolver helper in ExasimSolverSetup.hpp does it for you (this is what every usage-mode main.cpp calls):

Step Method Purpose
1 InitializeEnvironment(argc, argv, comm) MPI / Kokkos init
2 ParseInputs(argc, argv) read pdeapp.txt
3 SetModelDefinition(...) attach the model provider (done by ConfigureModelDefinitions)
4 InitializeModels() build preprocessing data, allocate solution
5 Solve() run to completion (time stepping / steady / pseudo-time)
6 Finalize() flush output, finalize MPI / Kokkos
#include <exasim/ExasimSolverSetup.hpp>
#include <exasim/builtinlibprovider.hpp>

ExasimSolver solver;
return RunExasimSolver(solver, argc, argv, comm);   // steps 1–6

To drive the solver yourself, use InitializeExasimSolver(...) instead — it runs steps 1–4 and stops, leaving the solver ready to step:

ExasimSolver solver;
int err = InitializeExasimSolver(solver, argc, argv, comm);   // steps 1–4
// ... drive the solver ...
solver.Finalize();                                            // step 6

Stepping

Rather than the all-in-one Solve(), advance the solver explicitly:

Method Behavior
Solve() run the configured problem to completion
RunTimeDependent(start, steps) advance steps time steps starting at step start
RunSteady() solve the steady problem
RunPseudoTime() pseudo-time continuation to steady state

Per-model overloads (RunTimeDependent(modelnumber, ...), Solve(modelnumber), …) drive one model when several are configured.

Interface exchange

These methods let an outside driver read and write data on a designated mesh interface (an interface is a boundary, selected by its boundary tag ibc):

Method Purpose
IntializeMeshInterface(modelnumber, ncuext, ncuint, ibc, comperm, offset, comm) declare the interface and the field layout exchanged across it
std::vector<ExasimPoint> getInterfacePoints() const the coordinates of the interface points (hand these to the other side so it produces matching data)
void getInterfaceFluxes(std::vector<dstype>& send) const read out the interface fluxes Exasim computed (to send to the other side)
void setInterfaceFluxes(const std::vector<dstype>& recv) inject the interface fluxes provided by the other side

The interface-setup parameters:

Parameter Meaning
ncuext number of components of the external field received
ncuint number of components of the internal field sent
ibc boundary tag identifying the interface
comperm interface flux component map
offset component offset into the auxiliary odg (v) array where the exchanged field lands

ExasimPoint is a plain { dstype x, y, z; } (with dstype = double or float per the build). The send/recv vectors are flat dstype arrays laid out over the interface points and components.

Sub-iteration: save and restore state

To iterate a coupling step to convergence before advancing, checkpoint the solver, retry with updated interface data, and restore until converged:

Method Purpose
SaveState(modelnumber) checkpoint the current solution
RestoreState(modelnumber) roll back to the last checkpoint
ClearSavedState(modelnumber) discard the checkpoint

Skeleton driver

A generic outer loop: discover the interface, then for each coupling step exchange data and sub-iterate to convergence. The partner is abstract — replace exchange_with_partner with however your program moves the flux vectors.

#include <exasim/ExasimSolverSetup.hpp>
#include <exasim/builtinlibprovider.hpp>
#include <vector>

int main(int argc, char** argv)
{
    MPI_Comm comm = MPI_COMM_WORLD;

    ExasimSolver solver;
    if (int err = InitializeExasimSolver(solver, argc, argv, comm)) return err;

    // Declare the interface on model 0 (boundary tag ibc, field layout).
    const int model = 0, ncuext = 1, ncuint = 1, ibc = 1, comperm = 0, offset = 0;
    solver.IntializeMeshInterface(model, ncuext, ncuint, ibc, comperm, offset, comm);

    // The other side needs our interface coordinates to produce matching data.
    std::vector<ExasimPoint> pts = solver.getInterfacePoints();
    /* exchange_with_partner(pts); */

    std::vector<dstype> send, recv;
    int start = 0, steps = 1;

    for (int step = 0; step < num_coupling_steps; ++step) {
        solver.SaveState(model);

        for (int sub = 0; sub < max_subiters; ++sub) {
            /* recv = exchange_with_partner(send); */   // get the partner's fluxes
            solver.setInterfaceFluxes(recv);            // inject them

            solver.RunTimeDependent(start, steps);      // advance one window

            solver.getInterfaceFluxes(send);            // read ours back out
            if (/* converged */ true) break;
            solver.RestoreState(model);                 // else retry the window
        }

        solver.ClearSavedState(model);
        start += steps;
    }

    return solver.Finalize();
}