Simulation contracts

These interfaces define the reusable simulation lifecycle. Read the architecture, application pipeline, and 0.1 → 0.2 migration for ownership and control flow before using the exact declarations.

pfc::sim::SimulationDriver

Thin time loop over Time plus a physics step. See include/openpfc/kernel/simulation/simulation_driver.hpp (pfc::sim::run).

pfc::Time

class Time

Public Functions

inline Time(const std::array<double, 3> &time)

Construct a new Time object with the specified time interval and default save interval.

The save interval is set to the same value as the time step.

Parameters:

time – An array containing the start time, end time, and time step in that order

inline Time(const std::array<double, 3> &time, double saveat)

Construct a new Time object with the specified time interval and default save interval (euler integrator).

The save interval is set to the same value as the time step.

Parameters:

time – An array containing the start time, end time, and time step in that order

inline double get_t0() const

Get the start time.

Returns:

The start time

inline double get_t1() const

Get the end time.

Returns:

The end time

inline double get_dt() const

Get the time step.

Returns:

The time step

inline void set_dt(double dt)

Set the time step.

Sets a new time step size for adaptive time-stepping. The time step must be positive to ensure meaningful time integration.

This method enables runtime adjustment of the recommended / policy dt. It does not change accepted simulation time; adaptive drivers must advance time via commit_attempt (or fixed-step next).

See also

get_dt() - query the current time step

See also

set_increment() - fixed-dt reconstruction helper (not adaptive commit)

Note

Changing dt does not rewrite past accepted time. Use begin_attempt / commit_attempt for clipped attempts.

Parameters:

dt – [in] The new time step size (must be > 0)

Throws:

std::invalid_argument – If dt <= 0

Post:

get_dt() returns the new dt value

Post:

get_accepted_time() is unchanged

inline int get_increment() const

Get the current time increment.

Returns:

Current increment

inline int get_step_count() const

Get the number of steps completed so far.

Self-documenting alternative to get_increment() for integrator authors querying progress (checkpoint/restart, adaptive step rejection logic, output scheduling): “increment” reads as a small delta, not a count, whereas “step count” states the intent directly. Returns the same underlying value as get_increment().

Returns:

Current step count

inline void increment_step_success()

Record a successful adaptive step attempt.

Increments the accepted-attempt counter. Unlike next(), this does not advance simulation time; it only updates attempt statistics for adaptive time-stepping algorithms.

Note

When used with TimeStateGuard, call this before commit() (or outside an uncommitted guard); otherwise the guard restores the counter on destruction.

Post:

get_accepted_steps() returns previous value + 1

inline void increment_step_rejection()

Record a rejected adaptive step attempt.

Increments the rejected-attempt counter. Unlike next(), this does not advance simulation time; it only updates attempt statistics.

Note

TimeStateGuard also restores accepted/rejected counters unless commit() was called. Call this after an uncommitted guard’s scope ends (or outside the guard) so the rejection count is kept.

Post:

get_rejected_steps() returns previous value + 1

inline int get_accepted_steps() const

Get the number of accepted adaptive step attempts.

Returns:

Accepted step count (starts at 0)

inline int get_rejected_steps() const

Get the number of rejected adaptive step attempts.

Returns:

Rejected step count (starts at 0)

inline int get_stage() const

Get the current stage index within this time step.

Stage tracking is independent of get_increment(): stages range from 0 to get_stage_count() - 1 and describe progress within a single multi-stage step (e.g. RK2/RK4), while the increment counts completed time steps.

Returns:

Current stage index (0-based)

inline void set_stage(int stage)

Set the current stage index within this time step.

Multi-stage steppers should call this before computing each stage.

Parameters:

stage – [in] New stage index (must satisfy 0 <= stage < get_stage_count())

Throws:

std::invalid_argument – If stage is out of range

inline int get_stage_count() const

Get the total number of stages in the current time step.

Returns:

Stage count (always >= 1)

inline void set_stage_count(int stage_count)

Set the total number of stages for this time step.

Multi-stage steppers should call this once during initialization to configure how many stages each step has (e.g. 2 for RK2, 4 for RK4).

Parameters:

stage_count – [in] New stage count (must be >= 1)

Throws:

std::invalid_argument – If stage_count < 1

inline double get_accepted_time() const noexcept

Get the accepted simulation time (read-only)

Returns the stored accepted clock. Unchanged for the duration of an active attempt (begin_attempt … commit_attempt / reject_attempt).

Returns:

Accepted simulation time

inline double get_current() const

Get the current (accepted) simulation time.

Returns the stored accepted time (m_accepted_time), clamped to t1 if needed. Prefer get_accepted_time for adaptive drivers; this alias preserves the historical name used by Simulator and writers.

See also

get_accepted_time() - explicit accepted-time accessor

See also

get_increment() - get the number of steps taken

See also

next() - advance to next time step (fixed dt)

See also

done() - check if t_current >= t1

Note

Accepted time advances on next or commit_attempt only. set_dt does not rewrite this value.

Returns:

Current simulation time, clamped to [t0, t1]

inline double get_saveat() const

Get the time interval for saving data.

Returns:

The save interval

inline sim::steppers::RKIntegratorMethod method() const noexcept

Get the integrator method.

Returns:

The integrator method

inline void set_method(sim::steppers::RKIntegratorMethod method) noexcept

Set the integrator method (JSON simulator.integrator.method).

inline void set_increment(int increment)

Set the current time increment (fixed-dt reconstruction helper).

Sets the committed step count and reconstructs accepted time as min(t0 + increment * dt, t1). This preserves restart / rewind helpers that assume a constant dt. It is not the adaptive commit path — use commit_attempt after a clipped attempt instead.

The increment must be non-negative so that get_current() stays in [t0, t1] and done() / do_save() remain meaningful.

Parameters:

increment – The current time increment (number of completed steps from t0, i.e. same convention as after next()).

Throws:

std::invalid_argument – if increment < 0

Post:

get_increment() == increment

Post:

get_accepted_time() == min(t0 + increment * dt, t1)

inline void set_saveat(double saveat)

Set the time interval for saving data.

Parameters:

saveat – The save interval

inline void next()

Advance to the next time step (fixed dt)

Advances accepted time by dt (clamped to t1) and increments the step counter by 1. For adaptive clipped intervals, use begin_attempt / commit_attempt instead.

See also

get_increment() - query current step number

See also

get_current() - accepted simulation time

See also

done() - check completion status

See also

commit_attempt() - advance by a clipped attempted interval

Post:

get_increment() returns previous value + 1

Post:

get_accepted_time() returns previous value + dt (clamped to t1)

inline double clip_attempt_dt(double candidate_dt) const

Clip a candidate step interval against terminal and output bounds.

Returns an attempted dt such that accepted_time + attempted_dt does not exceed t1. When saveat > 0, also does not pass the next output-alignment time without landing on it.

Alignment uses the same tolerance family as do_save (1e-9): next_save = ceil((accepted + 1e-9) / saveat) * saveat. When both the terminal bound and saveat constrain the step, the most restrictive (minimum) wins. If next_save >= t1, only the terminal bound matters. When saveat <= 0, output-alignment clipping is skipped.

Parameters:

candidate_dt – [in] Proposed step size (must be > 0)

Throws:

std::invalid_argument – If candidate_dt <= 0

Returns:

Clipped attempted interval

inline bool attempt_active() const noexcept

Whether an attempt transaction is currently open.

inline double get_attempted_dt() const

Clipped interval for the active attempt.

Throws:

std::logic_error – If no attempt is active

inline void begin_attempt(double candidate_dt)

Begin an attempt: clip candidate_dt and leave accepted time unchanged.

Parameters:

candidate_dt – [in] Proposed step size (must be > 0)

Throws:
  • std::logic_error – If an attempt is already active

  • std::invalid_argument – If candidate_dt <= 0 (via clip_attempt_dt)

Post:

attempt_active() is true

Post:

get_accepted_time() is unchanged

Post:

get_attempted_dt() == clip_attempt_dt(candidate_dt)

inline void commit_attempt()

Commit the active attempt: advance accepted time by attempted_dt.

Does not auto-call increment_step_success (counters stay caller-owned).

Throws:

std::logic_error – If no attempt is active

Post:

get_accepted_time() advanced by the attempted interval (clamped to t1)

Post:

get_increment() increased by 1

Post:

attempt_active() is false

inline void reject_attempt()

Reject the active attempt without changing accepted time.

Does not auto-call increment_step_rejection (counters stay caller-owned).

Throws:

std::logic_error – If no attempt is active

Post:

get_accepted_time() and get_increment() unchanged

Post:

attempt_active() is false

inline operator double() const

Conversion operator to retrieve the current time as a double value.

Returns:

The current time as a double value

Friends

inline friend std::ostream &operator<<(std::ostream &os, const Time &t)

Overloaded stream insertion operator to print the Time object.

Parameters:
  • os – The output stream

  • t – The Time object to be printed

Returns:

The output stream

pfc::FieldModifier

class FieldModifier

Subclassed by pfc::Constant, pfc::FileReader, pfc::RandomSeeds, pfc::SeedGrid, pfc::SingleSeed

Public Functions

inline void set_field_names(std::vector<std::string> names)

Declare every field this modifier may write (for Simulator checks).

inline const std::string &get_field_name() const

Get the name of the field this modifier operates on.

Returns the (first) field name set via set_field_name(); sessions use it to look up the target field on SimulationState before calling apply().

See also

set_field_name()

Note

Returns “default” if field name was never explicitly set

inline virtual void set_mpi_comm(MPI_Comm)

Optional MPI communicator for modifiers that use collectives (MPI-IO, reductions). Default is a no-op; FileReader and app-local front-tracking BCs override.

inline virtual nlohmann::json checkpoint_state() const

Restart state for this modifier, or null for a stateless modifier.

Sessions capture this on every rank when publishing a checkpoint. Stateful implementations must return the same state on each rank and override restore_checkpoint_state, rejecting null when their state is required.

inline virtual void restore_checkpoint_state(const nlohmann::json &state)

Restore saved state; the default accepts only a stateless record.

inline virtual void apply(const SimulationContext &simulation_context, pfc::field::FieldOutput<double> field, const Domain &domain, const Box3i &box, double time)

Apply the field modification with explicit simulation context.

The default implementation ignores the context and calls apply(field, domain, box, time). Override this when the modifier needs simulation_context.mpi_comm().

virtual void apply(pfc::field::FieldOutput<double> field, const Domain &domain, const Box3i &box, double time = 0.0) = 0

Apply the modification to a host field over box (pure virtual)

Parameters:
  • field – Mutable host view with box voxel count (x-fastest, halo 0). A std::vector<double> lvalue or Field::output() converts implicitly; for a device Field use apply_field_modifier (kernel/simulation/apply_field_modifier.hpp), which brackets the host mirror.

  • domain – Global geometry (origin, spacing, size)

  • box – Local owned index box

  • time – Current simulation time (typically 0 for ICs)

virtual ~FieldModifier() = default

Destructor for the FieldModifier class.

The destructor is declared as default, allowing proper destruction of derived classes.

pfc::ResultsWriter

class ResultsWriter

Subclassed by pfc::FileResultsWriter

Public Functions

ResultsWriter() = default

Default-construct a writer.

File sinks take a path pattern on FileResultsWriter / BinaryWriter / VTKWriter. Non-file sinks need no dummy filename.

virtual ~ResultsWriter() noexcept(false) = default

Virtual destructor.

Marked noexcept(false) to allow subclasses to throw on MPI cleanup failures (fail-closed policy).

inline virtual void set_geometry(const std::array<double, 3> &origin, const std::array<double, 3> &spacing)

Configure the physical geometry of the grid.

set_domain() says how many cells there are and who owns which; this says how big a cell is and where the grid starts. Formats that record physical coordinates need both — a .vti whose Spacing does not match the run’s dx renders at the wrong scale in ParaView, and every length measured off it is wrong by the same factor.

The default is a deliberate no-op so that index-only sinks (BinaryWriter, in-memory writers) need not implement it. Override it wherever the file format has a place to put the numbers.

See also

pfc::apply_writer_domain - forwards Domain geometry to both calls

Parameters:
  • origin – [in] Physical coordinate of global grid index (0, 0, 0)

  • spacing – [in] Physical cell size along each axis

virtual MPI_Status write(int increment, pfc::field::FieldView<std::complex<double>> data) = 0

Write a complex-valued field to file at specified time step.

Writes the local portion of a complex field view (complex doubles) to the output file. Useful for storing Fourier coefficients or k-space data.

See also

FFT::forward() - produces the complex hat from the real field

Note

Complex field size is typically ~50% of real field size (r2c symmetry).

Parameters:
  • increment – [in] Time step or frame number

  • data – [in] Local complex field data

Returns:

MPI_Status Information about the write operation