Application adapters

An adapter turns an evaluated dip::Environment into input files for a specific downstream program. SNT loads the project or DIPH5 snapshot, resolves the adapter’s output plan, checks every output path and collision, and writes the files. The adapter decides which values to use, how to validate them, and the names and formats of the resulting files. A single adapter can register zero, one, or many files.

Adapter-owned output plans

An adapter may declare native mappings before writing files. Override describe_outputs(env, outputs) to add mappings from real parameter paths with outputs.add_node(...), or from typed calculated values with outputs.add_value(...). Each mapping has a stable ID, target, native key, active decision, and optional rule label, dependencies, and declaration origin. Use replace_node or replace_value when a profile intentionally changes a mapping with the same ID. Accidental duplicate IDs and duplicate active native keys within a target are rejected. Tags may help the adapter discover parameter groups; they do not define output policy.

Override plan_resolved(env, outputs, context) to format the selected mappings and register native files. Existing adapters implementing only plan(env, context) continue to work. For example:

from scinumtools3.dip import Adapter

class SolverAdapter(Adapter):
    def describe_outputs(self, env, outputs):
        outputs.add_node(env, "run.steps", "settings", "Steps", "run.steps",
                         active=False, origin="base")
        outputs.replace_node(env, "run.steps", "settings", "Steps", "run.steps",
                             active=True, origin="profile")

    def plan_resolved(self, env, outputs, context):
        lines = [f"{item.key}={item.value}" for item in outputs.select("settings", True)]
        context.add_text("control.in", "\n".join(lines) + "\n")

The adapter evaluates its policy against the final environment and records the resulting typed values and decisions in the plan. A DIPH5 snapshot written by an adapter run stores that resolved plan. A later run_adapter_snapshot() passes the saved plan to plan_resolved without rerunning describe_outputs; this preserves profile choices even when the original profile files are unavailable. The plan is inspectable through Environment.output_plan after loading the snapshot. Derived mappings remain outside the parameter tree and Parameter guide. Call resolve_output_plan(env, adapter) to inspect the same validated plan before writing any native files.

For AI-assisted setup of an established simulation code, DIPL can describe run settings and references to initial-condition inputs. An adapter can check code-specific requirements and generate the program’s normal parameter file. These checks catch interface errors; they do not establish that the setup is physically appropriate.

Derive from snt::dip::Adapter and register text, binary, or streamed content in plan(). All paths are relative to the output directory.

#include <snt/dip/adapter.h>
#include <snt/dip/cursor.h>

class SolverAdapter : public snt::dip::Adapter {
public:
    void plan(const snt::dip::Environment& env,
              snt::dip::AdapterContext& context) const override {
        auto steps = env["run.steps"].as<std::int64_t>();
        context.add_text("control.nml", "&run\n steps=" + std::to_string(steps) + "\n/\n");
        context.add_binary("marker.bin", {0x53, 0x4e, 0x54});
        context.add_stream("times.dat", [steps](std::ostream& out) {
            for (std::int64_t i = 0; i < steps; ++i) out << i << '\n';
        });
    }
};

auto files = snt::dip::run_adapter_project("DIPfile", SolverAdapter{},
                                            "solver-inputs", "run.diph5");

run_adapter() accepts an existing environment. run_adapter_snapshot() loads a DIPH5 file before planning. The optional last argument saves a DIPH5 snapshot alongside the outputs. Each runner returns the written paths in registration order, followed by the snapshot when requested.

The runner validates every registered path before calling any stream writer. It rejects absolute paths, . and .. components, duplicate paths, file/directory conflicts, and symbolic links. By default, it also rejects existing files. Output files are first written in a staging directory, so a failing writer leaves the destination unchanged.

To regenerate inputs in the same directory, opt in to replacing files registered for this run:

using snt::dip::ExistingOutputPolicy;

snt::dip::run_adapter_project("DIPfile", SolverAdapter{}, "solver-inputs",
                              "run.diph5", ExistingOutputPolicy::ReplaceRegistered);

The optional snapshot follows the same policy. Unregistered files are preserved. The runner finishes writing all staged outputs before replacing existing regular files and restores prior files if publication fails. Python exposes ExistingOutputPolicy.ReplaceRegistered through the existing_output_policy argument of all three runner functions. The default in both languages is Reject. The adapter still decides which files to register and how to format them.

To also remove files generated by an earlier run that are no longer planned, use ExistingOutputPolicy::SyncRegistered in C++ or ExistingOutputPolicy.SyncRegistered in Python. This mode writes .snt-adapter-manifest in the output directory to record the registered paths, including the optional snapshot. On the next sync run, it replaces current registered files and removes only stale paths from that manifest. Unregistered files remain untouched. The manifest and file changes are staged and restored together if publication fails. The first sync run has no prior record, so it does not remove files left by earlier ReplaceRegistered runs.

Adapters can use ordinary environment access, selection, and table inspection inside plan(). A DIPH5 run can use only the values and provenance retained in that snapshot. Application-specific validation belongs in the adapter.

See the runnable AdapterOutputs example for a C++ adapter producing a namelist, binary marker, and streamed data, and a Python adapter producing JSON and CSV. The Python integration guide shows the subclassing pattern.

The complete declarations and signatures are in the C++ adapter API and Python DIP API.