solprop.backends.driver#
The step loop every backend runs, with the kernels left abstract.
March, replay, landing, controller bookkeeping, history, the noise model and the seam’s
set_initial/propagate_to/observe/close do not depend on how a stage is
evaluated: they touch the state and StepDriver._trial_step and nothing
else. Written once here, they are the same
step control on every backend, so a change to docs/dev/stepper.md section 3 is made
in one place and a disagreement between two backends can only be the kernels.
Every backend runs this loop, the reference included: the oracle is the passes and
the per-element arithmetic, not the march, and a march written twice has to be kept
identical by hand.
What a backend supplies is _trial_step, _apply_mask and stats, plus
_finish when it has boundary treatments of its own; two optional hooks,
_begin_plane and _on_accepted_step, are where a backend scores whatever a plane
or an accepted step leaves behind. The march owns the periodic statistics schedule.
Its default storage hooks call stats and retain host rows; a device backend can
override the three storage hooks to retain device rows until observation.
Module Attributes
How many ulps of slack a march allows itself when deciding it has arrived. |
|
Rows a plane's statistics buffer holds before it first has to grow. |
Functions
|
Return whether a march at |
|
Refuse, by name, each schema-12 general-modal leaf a backend cannot execute. |
|
Return how far a replayed schedule's edge may sit from a target it lands on, m. |
Classes
|
The interaction-picture step loop, with the stage evaluation left to a subclass. |
- solprop.backends.driver.INITIAL_STEP_STATS_CAPACITY = 64#
Rows a plane’s statistics buffer holds before it first has to grow.
- solprop.backends.driver.LANDING_ULPS = 8#
How many ulps of slack a march allows itself when deciding it has arrived.
The same guard
solprop.stepper.schedule.landing()uses to decide that a position has already reached a target, so the march stops exactly where landing would refuse to take another step rather than one round-off-sized sliver short of it.
- class solprop.backends.driver.StepDriver(problem, *, grid=None, modes=None, profiles=None)[source]#
-
The interaction-picture step loop, with the stage evaluation left to a subclass.
A subclass supplies
_trial_stepand_apply_maskand gets the whole ofsolprop.backends.protocol.Backend’s marching behaviour: adaptive stepping with the controller ofdocs/dev/stepper.mdsection 3, replay of a contract’s fixed schedule, landing exactly on save planes and hard kinks, the step history, and both noise models,inputandmodified.The controller’s state is its own proposal, never the clipped or stretched step that was executed, and what the history and a rejection see is the norm the proposal would have scored while what decides acceptance is the error the executed step committed. Both are section 3’s, and both matter: the first costs a run hundreds of steps if it is got wrong, the second throws away short landing steps that were accurate.
- Parameters:
- problemsolprop.translate.problem.KernelProblem
The validated problem to step. The subclass refuses what it cannot execute before calling this constructor.
- gridsolprop.grid.TimeGrid, optional
The grid the state lives on, which the statistics need and the contract does not carry.
- modescollections.abc.Sequence, optional
The propagated modes, in state order, for the statistics that need them.
- profilescollections.abc.Mapping, optional
The z-profiles keyed by statistic name.
- Attributes:
- namestr
The registry name, set by the subclass and used in every log line.
- max_batchint
How many problems the backend steps at once.
- propagator_default{‘tabulate’, ‘recompute’}
What this backend resolves the contract’s
"auto"propagator mode to. The default suits a backend that evaluates two complex exponentials per stage; one that takes both signs from a singleexpandsincosoverrides it.- helperssolprop._helpers.Helpers
The implementation of the per-stage and per-step scalar evaluations – the stage tables’ Clenshaw rows and the Adams basis – which the shared driver, march and engines pass down rather than choosing one (SEP 29 section 6.6). Each backend sets its own:
PLAINfor the NumPy reference,NUMBA(the default) fornumba_cpu, the C++ bindings for the native backend.
- available_device_memory()[source]#
Return None for host execution; device backends report free bytes.
- Returns:
- int or None
None for the shared host implementation.
- held_observation()[source]#
Return the state the low-storage march held at its last field-free plane.
SEP 22, Q14: a low-storage step accepts in place, so a step abandoned in flight leaves a state that is neither of its ends. When the last plane kept no field, this is what the driver checkpoints instead: the state as it was observed there, with an empty step history (the plane’s own was saved with it) and its noise phase.
- Returns:
- Observation or None
The held plane, or
Nonewhen nothing is held: another march, a plane that kept its field since, or a fresh load.
- Raises:
- BackendError
If the backend is closed.
- helpers: Helpers = Helpers(name='numba')#
- march_export_refusal()[source]#
Return why this backend’s Adams march cannot carry its history, or
None.- Returns:
- str or None
The reason (
solprop.backends.multistep.AdamsMarch.export_refusal()), orNonefor a march that exports and installs its history and for the Runge-Kutta and low-storage marches, which keep none.
- march_snapshot()[source]#
Return the Adams march’s state at the accepted position, for a resume.
- Returns:
- solprop.output.schema.MarchSnapshot or None
The snapshot, or
Nonefor the Runge-Kutta and low-storage marches and wherever the Adams march has nothing a resume could continue (solprop.backends.multistep.AdamsMarch.snapshot()).
- Raises:
- BackendError
If a low-storage step was abandoned in flight.
- metadata()[source]#
Return what this execution was, for the run’s record.
A saved run has to say what produced it: the thread configuration alone was measured to move one step by a factor of nine, so a result whose execution is not recorded is not reproducible. The base record is the name and the resolved propagator mode and, for Adams runs, cumulative logical source calls; the low-storage march’s evaluations are physical,
source_evaluations, neverresponse_calls(SEP 24 section 5); a backend with pools, plans or wisdom adds them.- Returns:
- dict
JSON-serialisable scalars, keyed by name.
- observe(*, field=True)[source]#
Return the state at the current position, as host arrays.
The field is in the lab frame and the FFT convention (
spectral_convention = "fft"): the output layer conjugates it on the way to disk and the processing accessors on the way to a user. The step history covers everything since the previous call.With
field=False(SEP 22 section 5.1) neither the field nor the noise phase crosses to the host – a device backend copies nothing – and the observation carriesNonefor both. Nothing scans the field for finiteness then: the step loop accepts only finite error norms and a replay raises on a non-finite one. The spatial collar diagnostic, which reads the field, is not evaluated; the backend counts such planes inmetadata()and logs the first. Under the low-storage march the state is held, one volume, so that a step abandoned in flight can still be checkpointed at this plane (held_observation()).- Parameters:
- fieldbool, optional
Whether the field and the noise phase cross to the host. Default
True.
- Returns:
- Observation
The field, the noise phase and the step history.
- Raises:
- BackendError
If the backend is closed or no initial state was loaded.
- propagate_to(z)[source]#
Step the state to a position, landing on it exactly.
Adaptive by default and a replay when the contract carries a schedule. Either way the embedded estimate is computed and recorded every step, so a schedule too coarse for the tolerance it is scaled by is reported rather than silent (
docs/dev/stepper.mdsection 8). The low-storage march has no estimate: it replays its schedule and records 0.0, “not estimated” (SEP 24 section 6.3).- Parameters:
- zfloat
The save plane to reach, m, at or ahead of the current position and inside the operator table.
- Returns:
- StepSummary
What the call cost and achieved.
- Raises:
- BackendError
If the backend is closed, no initial state was loaded, a low-storage step was abandoned in flight, or
zis behind the current position or outside the table.- ConvergenceError
If the controller asks for a step below the contract’s
dz_min, or a replayed step’s error norm, or the low-storage march’s state, is not finite.
- restore_call_ledgers(logical, atomic=None)[source]#
Restore shared Adams call counts without restoring numerical history.
Non-TDSE backends inherit logical budget persistence while refusing atomic counts. The caller first loads the saved field, which resets Adams history; the next source evaluation charges startup against these cumulative counts.
- Parameters:
- logicalmapping
Persisted logical response counts; empty on ordinary RK runs.
- atomicmapping or None, optional
Persisted atomic counts; absent on non-TDSE runs.
- Raises:
- BackendError
If this backend is handed counters it does not implement.
- restore_march(snapshot)[source]#
Continue the Adams march from a snapshot at the loaded position.
Called after
set_initial()at the snapshot’s position. The state is replaced by the snapshot’s own, exactly as the interrupted run held it.- Parameters:
- snapshotsolprop.output.schema.MarchSnapshot
The snapshot a file holds.
- Returns:
- str or None
Nonewhen the history was continued, otherwise why the march starts afresh, as a resume did before snapshots were kept.
- set_initial(field, noise, *, z=0.0)[source]#
Load a state and the noise realisation at a position.
The state is masked to the active band once here, as the scaffold does: the operator and the prefactor are exactly zero outside it, so out-of-band content would neither propagate nor decay and would sit in the error norm for the whole run. The traditional
inputnoise model adds its realisation to the state here and never again; themodifiedmodel keeps it apart and adds it at every stage, so the propagating field never carries it.A positive z is a resume: the state came back from a save plane the run already reached, so it already carries whatever the
inputmodel added at the entrance and must not be given a second realisation, and a replayed schedule continues from the edge that plane sits on. The controller starts afresh fromdz_initeither way (docs/dev/stepper.mdsection 3).Everything else a march reads is restored too, so that a backend loaded again after a run – a
SimulationRunner’s, or after a failed run – repeats a fresh backend’s run (SEP 22 section 4.7): a dense observation’s look-ahead, an Adams march’s call ledger and last-rejection flag and a low-storage march’s source-evaluation count here, and the quadrature alarm, whose value the entrance plane’squadrature_errorreads before any march, in the overrides of the backends that keep one. A resume restores its ledger afterwards (restore_call_ledgers()).Loading is the one entry that clears a low-storage step abandoned in flight (SEP 24 section 6.2): the state it holds is replaced, and the march’s flow table is rebuilt from the next step.
- Parameters:
- fieldnumpy.ndarray
The state in the canonical layout, complex, shape
(n_batch, n_modes, n_pol, n_omega), in the square root of watts and the FFT convention.- noisesolprop.noise.NoiseRealisation or None
The realisation to carry along the run, or
Noneto take the one the problem was assembled with.- zfloat, optional
The position the state belongs to, m. Zero, the entrance, by default.
- Raises:
- BackendError
If the backend is closed, the field does not match the contract’s shape, z lies outside the operator table, or a schedule is being replayed and z is not one of its edges.
- set_progress(reporter)[source]#
Attach the reporter every attempted step is reported to.
The next call to
propagate_to()announces the run to it. Attaching a reporter changes nothing the propagation computes – the calls are observers, made after the step is decided – so a run with one is bitwise the run without.- Parameters:
- reportersolprop.log.ProgressReporter
Where the step events go;
solprop.log.NullReporterturns them off.
- abstractmethod stats(spec)[source]#
Compute the requested statistics in one fused pass.
Declared here, not only on
solprop.backends.protocol.Backend, because the shared step loop calls it through the default row-storage hook. A device backend shares its statistic evaluator between this method and its device row-storage hook.- Parameters:
- specsolprop.stats.StatsSpec
Which statistics and which windows.
- Returns:
- dict
Small host arrays keyed by statistic name, each with a leading batch axis.
- solprop.backends.driver.reached(position, target)[source]#
Return whether a march at
positionhas arrived attarget.- Parameters:
- positionfloat
Current position, m.
- targetfloat
Position being marched to, m.
- Returns:
- bool
True when no further step is due.
Examples
>>> from solprop.backends.driver import reached >>> reached(0.5, 1.0), reached(1.0, 1.0) (False, True)
- solprop.backends.driver.refuse_general_leaves(problem, backend, *, admits=())[source]#
Refuse, by name, each schema-12 general-modal leaf a backend cannot execute.
The four optional leaves of the general modal route arrived at kernel schema 12 with no backend behind them. A schema check alone cannot stand in for this: a separately built backend compares against solprop’s imported constant, so a freshly built wheel would accept a problem carrying a representation it does not implement and silently run the old physical model. Each leaf is therefore named here, one refusal each, and each check is deleted only when that backend executes that exact representation.
Every backend in this repository names all four leaves and admits through the shared list in
solprop.backends.numpy_ref.admission, so this refusal guards a separately built backend. A backend that executes a leaf names it in admits and checks the exact representation it supports itself: admitting a leaf is a statement about one combination of contents, not about the field being present.- Parameters:
- problemsolprop.translate.problem.KernelProblem
The problem to check.
- backendstr
The backend’s registry name, used in the refusal.
- admitssequence of str, optional
Dotted leaf names this backend executes, for example
("transform.modal_tables",). Empty by default: a backend refuses every one until it has a kernel for it.
- Raises:
- BackendError
Naming the backend and the leaf it cannot execute.
- solprop.backends.driver.replay_tolerance(schedule, position)[source]#
Return how far a replayed schedule’s edge may sit from a target it lands on, m.
A schedule’s edges are the running sum of its steps, and a sum of
nrounded steps can drift from the position they were chosen to reach by aboutnulps:FixedSchedule(np.full(160, L / 160))ends 12 ulps short ofL. The slack is thereforeLANDING_ULPSor the step count, whichever is larger, in ulps of the larger of the position and the schedule’s length – still far inside the1e-9of the length thatvalidate()allows an edge to miss a save plane by, so no step is ever skipped.- Parameters:
- schedulesolprop.stepper.schedule.FixedSchedule
The schedule being replayed.
- positionfloat
The position the edge is compared with, m.
- Returns:
- float
The slack, m.
Examples
>>> import numpy as np >>> from solprop.backends.driver import replay_tolerance >>> from solprop.stepper.schedule import FixedSchedule >>> schedule = FixedSchedule(np.full(160, 1e-3 / 160)) >>> bool(1e-3 - schedule.z_end <= replay_tolerance(schedule, 1e-3)) True