Skip to content

couple

Runs one problem split across two solvers and repeats the exchange until both sides agree.

Group: Two solvers on one problem.

Parameters

Parameter Type Required Default
participants string yes
max_iter integer no 50
tol number no 1e-06
accelerator string no 'auto'
theta number no 0.5
monolithic string no ''
probe boolean no True
critic_approved boolean no False
noise_replicates integer no 0
noise_floor number no 0.0
noise_block integer no 3
history_path string no ''
iface_level integer no 0
pde_sources string no ''

What the model reads

The text below is the tool's own description, exactly as the AI model receives it.

Show the full description
GENERAL partitioned multi-code coupling — works for ANY physics/coupling.

Have an independent critic review the setup before coupling; pass
critic_approved=True only after that review (every simulation must be
critic-reviewed first).

Unlike coupled_solve (legacy, fixed toy geometries), this is physics-agnostic:
you write one self-contained solver script per subdomain/participant and openPASO
runs the fixed-point iteration, relaxation, convergence-or-fail, AND the
silent-wrong validation a partitioned coupling needs, because a partitioned
coupling's characteristic failure is not a crash — it is a clean, converged,
confidently wrong number. openPASO checks, and reports in the verdict:
  * convergence, and per-block convergence (a large settled block, e.g. force,
    cannot hide a small moving one, e.g. displacement, inside one global norm);
  * finiteness of every exchanged array, including coordinates and fluxes;
  * interface flux balance, naming SIGN-CONVENTION and UNIT-MISMATCH signatures;
  * that every participant exited 0 — a diverged solver often writes its last
    iterate and then aborts;
  * that every participant's output actually MOVED when its imports moved —
    the test for a participant that exits 0 having done nothing, or re-serves
    a cached answer; such a run "converges" at iteration 2 with residual 0;
  * that the coupling graph is wired as declared — an `imports_from` name that
    matches no participant is REFUSED, not silently dropped into a one-way run;
  * whether the two interface discretisations match;
  * and, when you pass `monolithic`, that the coupled answer equals an
    independent un-split solve of the same problem. That last one is the only
    check that can catch a consistent unit error or a wrongly applied interface
    sign, so when it is not supplied the verdict SAYS it was not run.

PARTICIPANT CONTRACT — each iteration the driver, per participant:
  1. writes <work_dir>/imports.json = {partner_name: InterfaceData} (boundary
     data this participant consumes; empty on iteration 1).
  2. runs your `command` in <work_dir>.
  3. reads <work_dir>/exports.json = the InterfaceData your script produced on
     the shared interface.
Your script decides HOW to apply imports (Dirichlet/Neumann/Robin/traction/
flux/...) and WHAT to export — opaque to the driver, so it generalizes.

InterfaceData JSON shape (read imports, write exports):
  {"field_name": str, "n_points": N, "coordinates": [[x,y(,z)],...],
   "values": [...], "normal_fluxes": [...]  # optional, for conservation check}

THE DRIVER IS JACOBI, NOT GAUSS-SEIDEL: within one iteration every
participant reads the PREVIOUS iteration's exports, and the driver relaxes
EVERY participant's export vector. A two-participant Dirichlet-Neumann loop
therefore relaxes twice per cycle and converges geometrically — it does not
finish in one step even for a linear problem. theta=1.0 (no relaxation)
oscillates forever on a balanced interface; start at theta=0.5.

FLUX SIGN: export `normal_fluxes` with respect to YOUR OWN outward normal.
The two normals are anti-parallel, so the two participants' fluxes carry
OPPOSITE signs and their sums cancel. The Dirichlet value you APPLY is the
same number on both sides — the opposite rule. Getting this wrong is the
single most common cause of the flux-balance finding.

Args:
    participants: JSON list of {"name", "command":[argv...], "work_dir",
      "imports_from":[partner names], "timeout": seconds}. Every name in
      `imports_from` must be another participant's name.
    max_iter, tol: iteration controls.
    accelerator: "auto" (default: Aitken for a single-field exchange, Anderson for a
        multi-field one -- resolved from the first exports and reported in `theta.mode`),
        "aitken" (theta recomputed each iteration from the residual
        or "anderson" (Anderson mixing / interface quasi-Newton on the whole interface state, window 5:
        measured on a three-component thermo-elastic exchange to cut the iteration count several-fold)
      history, starting at `theta`) or "constant" (theta held at `theta` for
      the whole run). There is no per-field or per-participant theta.
    theta: the relaxation factor. Under-relaxation (theta < 1) is what makes
      a Dirichlet-Neumann or FSI coupling converge at all when the physical
      stiffness/density ratio makes the un-relaxed iteration diverge; 0.5 is
      a neutral default, not a recommendation for your problem.
    noise_replicates: for a STOCHASTIC participant (DSMC / Monte-Carlo /
      any sampled estimator). Use 4 or more — the floor is itself an
      estimate and three samples is a bad one. It makes the driver run
      every participant that many times on the SAME imports and MEASURE
      the residual noise floor — the residual a perfectly converged run
      would still report. Convergence is then judged against
      max(tol, floor), over a block mean, so a correct stochastic
      coupling is no longer reported as a failure just because tol sits
      under the sampler's own scatter. 0 (the default) switches the whole
      branch off. Deterministic participants measure a floor of exactly 0
      and are unaffected.
    noise_floor: declare a floor instead of measuring one (or raise a
      measured one), if you established it independently.
    noise_block: how many consecutive residuals must AVERAGE below the
      criterion before the run stops. Only in effect when a non-zero
      floor is; a single residual dipping into the noise means nothing.
    probe: after the iteration settles, spend ONE extra solve per
      participant perturbing its final imports and measuring how far its
      answer moves. This is the only check here that can tell a solver
      which reads its boundary data from one that merely looks as if it
      does; turn it off only if that solve is genuinely unaffordable, and
      the verdict will then record that the question was not asked.
    monolithic: OPTIONAL JSON {"command":[argv...], "work_dir": str,
      "timeout": int} — a solve of the SAME problem un-split, in ONE code,
      which writes <work_dir>/monolithic.json in InterfaceData shape on the
      same interface. Supplying it is the strongest verification available
      here and needs no external benchmark.
                history_path: optional ABSOLUTE CSV path. When set, openPASO writes its
                    measured finite residuals there as
                    ``iteration,interface_residual``. Use the path of the per-level
                    residual-history file your task names; never retype the returned history.

iface_level: optional level number stamped into the suggested_filename of the interface_csv blocks the reply carries on convergence (each participant's own final interface data, ready to save verbatim).

pde_sources: OPTIONAL, public-only. JSON {"A": {"source": "<the forcing/coefficient you actually implemented>", "task_source": "<the task's stated source, verbatim>"}, "B": {...}}. When supplied, openPASO compares the two PUBLIC strings and flags a mismatch — a silent wrong forcing (right shape, wrong function) converges cleanly to a different answer and no self-consistency check can see it. Never required; openPASO reads no reference solution and never supplies the equation for you.

Returns: JSON with converged, iterations, residual, per-block residuals,
    exports, the coupling graph, per-participant responsiveness and exit
    codes, a `validation` block, a `checks_not_run` block, and the verdict.
    A coupling that failed any check is reported as NOT VERIFIED, never as a
    trustworthy result — and one that could not be fully checked says so.
    With the stochastic branch on it also returns `noise_floor`,
    `tol_effective` and `stopped_at_noise_floor`. READ `noise_floor`
    BEFORE JUDGING CONVERGENCE: any tolerance applied to a result that
    carries one — including an acceptance tolerance — must be at least
    that floor, or it is measuring the sampler rather than the coupling.