solprop.backends.protocol#
The seam: what a backend is, and what comes back from one.
Everything that touches the propagated state during a run is reached through this one
protocol, and only the backends know how the state is laid out, transformed and stepped
(docs/dev/backends.md section 1). The protocol is drawn at the level of operations
on the state rather than at the array-namespace level, because an xp-style
abstraction would force every backend to keep the array compiler’s structure —
out-of-place transforms, separate scaling passes, materialised stages, one reduction per
statistic — and that structure is precisely what a fast backend exists to change.
A backend is constructed from a validated
solprop.translate.problem.KernelProblem, takes host arrays and returns host
arrays; its buffers, layout and plans are private, and the state is never a parameter of
Backend.propagate_to() or Backend.observe(). Selection by name and the
availability rules are in solprop.backends.registry.
Sign convention. Every complex array crossing this seam, in either direction, is in
the FFT convention of docs/dev/conventions.md section 2, with the phase origin
at the first time sample: spectral_convention = "fft". The output layer conjugates
on the way to disk and the processing accessors on the way to a user. Backends
never conjugate.
Classes
|
What every execution backend provides, and nothing more. |
|
Advance compatible loaded members and return their separate observations. |
|
The state at a save plane, on the host. |
|
What the stepper did between two observations. |
|
The statistics a backend evaluated between two observations. |
|
What one call to |
- class solprop.backends.protocol.Backend(*args, **kwargs)[source]#
Bases:
ProtocolWhat every execution backend provides, and nothing more.
docs/dev/backends.mdsection 1. A backend is constructed from a validatedsolprop.translate.problem.KernelProblem; the numbers are the contract, and a backend is correct when it reproduces thenumpy_reforacle to the stated bound.- Attributes:
- namestr
The registry name, for example
"numpy_ref".- max_batchint
Largest batch axis this backend steps at once; 1 for a backend that steps one problem.
Examples
>>> from solprop.backends.protocol import Backend >>> [name for name in sorted(dir(Backend)) if not name.startswith("_")][:4] ['available_device_memory', 'close', 'memory_ledger', 'metadata'] >>> [name for name in sorted(dir(Backend)) if not name.startswith("_")][4:7] ['observe', 'propagate_to', 'restore_call_ledgers'] >>> [name for name in sorted(dir(Backend)) if not name.startswith("_")][7:] ['set_initial', 'set_progress', 'stats']
- available_device_memory()[source]#
Return currently free device bytes, or None for a host backend.
- Returns:
- int or None
Free device memory excluding live allocations and allocator reserves. Host memory is admitted independently by the controller.
- memory_ledger()[source]#
Return the size of every buffer the backend holds.
- Returns:
- dict
Buffer name to bytes, including transform work areas. Every buffer is named even when it is empty.
- metadata()[source]#
Return what this execution is, for the run’s record.
A saved run has to say what produced it, not only what was computed: the thread configuration alone moved one step by a factor of nine (
docs/dev/backends.mdsection 3.4), so a result whose execution is not recorded is not reproducible. The driver writes this intosolprop.output.schema.RunMetadata.backend, which is outside the resume key – a run continued on the other backend is the same run.- Returns:
- dict
JSON-serialisable scalars keyed by name.
backendandpropagator_modeare always present; pools, plan flags and wisdom digests are a backend’s own.
- observe(*, field=True)[source]#
Return the state at the current position, as host arrays.
- Parameters:
- fieldbool, optional
Whether the field and the noise phase cross to the host;
Falseobserves the step history alone (SEP 22 section 5.1).
- Returns:
- Observation
The field, the noise phase and the step history since the last call.
- propagate_to(z)[source]#
Step the state to a position through the shared step loop.
- Parameters:
- zfloat
The save plane to reach, m. Steps land on it exactly.
- Returns:
- StepSummary
What the call cost and achieved.
- restore_call_ledgers(logical, atomic=None)[source]#
Restore cumulative budgets after loading a saved state.
- Parameters:
- logicalmapping
Persisted logical response counters, or empty for ordinary RK runs.
- atomicmapping or None, optional
Persisted provider attempts. Numerical history always restarts fresh.
- set_initial(field, noise, *, z=0.0)[source]#
Load a state and the noise realisation at a position, host to device, once.
- Parameters:
- fieldnumpy.ndarray
The state in the canonical layout, FFT convention.
- noisesolprop.noise.NoiseRealisation or None
The realisation to carry along the run, or
None.- zfloat, optional
The position the state belongs to, m, inside the operator table. Zero is the entrance; a positive value is a resume from a plane the run reached before, so the backend takes its position – and its place in a replayed schedule – from here. A state past the entrance already carries an
input-model noise realisation, and a backend must not add one to it a second time. The step controller starts afresh either way, so an adaptive run resumed agrees with an uninterrupted one to the controller’s tolerance and not bitwise, while a replayed schedule agrees bitwise on a backend that recycles no stage and to round-off on one that does (docs/dev/stepper.mdsection 3).
- set_progress(reporter)[source]#
Attach the reporter the step loop calls as it goes.
The driver attaches one before the first plane and never changes it mid-run. A backend that steps on the host calls
step()once per attempted step, including the rejected ones; a backend whose loop is on a device may call it once per plane instead (docs/dev/backends.mdsection 1). Nothing a reporter does may change the propagation: a run with one is bitwise the run without.- Parameters:
- reportersolprop.log.ProgressReporter
Where the step events go.
solprop.log.NullReporteris the way to turn them off.
- class solprop.backends.protocol.BatchBackend(*args, **kwargs)[source]#
-
Advance compatible loaded members and return their separate observations.
Construct through
solprop.backends.registry.select_batch()after loading each member’s initial state and noise through the ordinary backend seam. Successful construction transfers ownership of those backends to the batch. Their parameter tables may differ, but grids, fixed schedules, save planes and statistics layouts must match.max_batchrecords the constructed count.observe_membersreplacesobservefor batch output: each member keeps its own error history and periodic rows. No device array crosses this interface. The common maximum error norm is diagnostic and never changes a fixed schedule.
- class solprop.backends.protocol.Observation(z, field, noise_phase, step_history, n_steps, n_rejected, step_stats=<factory>)[source]#
Bases:
objectThe state at a save plane, on the host.
Section 9 of
docs/dev/kernel-contract.md. Host arrays only: device arrays never cross the seam. The field is in the lab frame and the FFT convention (spectral_convention = "fft"), so the output layer conjugates it on the way to disk and the processing accessors on the way to a user.- Attributes:
- zfloat
Position of the plane, m.
- fieldnumpy.ndarray or None
The state, complex128, shape
(n_batch, n_modes, n_pol, n_omega), in the square root of watts;Nonefor an observation that asked for no field (observe(field=False), SEP 22 section 5.1).- noise_phasenumpy.ndarray or None
Im Phi(omega, z)in radians, same shape, when a noise model is active and the field is observed, so that processing can restore the shot-noise background the propagating field never carried.Noneotherwise.- step_historyStepHistory
What the stepper did since the previous observation.
- n_stepsint
Steps taken since the previous observation, accepted and rejected together.
- n_rejectedint
Rejected steps among them.
- step_statsStepStats
The rows a periodic statistics subset produced between the previous observation and this one, empty unless the run asked for one. Separate from
step_historybecause the two are sampled differently: the history is every attempt, these are everyperiod-th accepted step.
- Raises:
- BackendError
If a field is not four-dimensional and finite, a noise phase is given without a field or does not match it, the step counts disagree with the history, or a statistics row points past the history it indexes.
Examples
>>> import numpy as np >>> from solprop.backends.protocol import Observation, StepHistory >>> history = StepHistory(np.array([1.0]), np.array([1.0]), np.array([0.3]), ... np.array([True]), np.array([0.02])) >>> plane = Observation(1.0, np.zeros((1, 1, 1, 8), complex), None, history, 1, 0) >>> plane.z, plane.field.flags.writeable (1.0, False)
- require_field()[source]#
Return the observed field, refusing an observation that asked for none.
- Returns:
- numpy.ndarray
The read-only field, shape
(n_batch, n_modes, n_pol, n_omega).
- Raises:
- BackendError
If the observation carries no field (
observe(field=False)).
Examples
>>> import numpy as np >>> from solprop.backends.protocol import Observation, StepHistory >>> empty = StepHistory(np.empty(0), np.empty(0), np.empty(0), ... np.empty(0, bool), np.empty(0)) >>> Observation(0.0, np.ones((1, 1, 1, 2), complex), None, empty, 0, 0 ... ).require_field().shape (1, 1, 1, 2)
- step_history: StepHistory#
- class solprop.backends.protocol.StepHistory(z, dz, error_norm, accepted, wall_time)[source]#
Bases:
objectWhat the stepper did between two observations.
Recorded whether the run was adaptive or replayed a fixed schedule, because the embedded error estimate costs nothing extra and is computed either way (
docs/dev/stepper.mdsection 8): a replayed schedule too coarse for the tolerance it is scaled by is then reported rather than silent.- Attributes:
- znumpy.ndarray
Position at the end of each step, m, float64, shape
(n_steps,).- dznumpy.ndarray
Length of each step, m, float64, shape
(n_steps,), positive.- error_normnumpy.ndarray
The scaled embedded error norm of each step, float64, shape
(n_steps,). For a clipped or stretched step this is the equivalent norm of the controller’s proposal, which is what the controller’s state is (solprop.stepper.controller.PIController.equivalent_norm()).- acceptednumpy.ndarray
Whether each step was accepted, Boolean, shape
(n_steps,).- wall_timenumpy.ndarray
Seconds elapsed at the end of each step, measured from the previous observation, float64, shape
(n_steps,), non-decreasing.
- Raises:
- BackendError
If the arrays are not all the same length, a step length is not positive, or an entry is not finite.
Examples
>>> import numpy as np >>> from solprop.backends.protocol import StepHistory >>> history = StepHistory(np.array([0.1, 0.2]), np.array([0.1, 0.1]), ... np.array([0.4, 0.5]), np.array([True, True]), ... np.array([0.01, 0.02])) >>> history.n_steps, history.n_rejected (2, 0)
- accepted: Bool[ndarray, 'n_accepted']#
- dz: Float[ndarray, 'n_dz']#
- error_norm: Float[ndarray, 'n_norm']#
- property n_rejected: int[source]#
Return the number of rejected steps.
- Returns:
- int
How many entries of
acceptedare false.
Examples
>>> import numpy as np >>> from solprop.backends.protocol import StepHistory >>> StepHistory(np.array([0.1]), np.array([0.1]), np.array([2.0]), ... np.array([False]), np.array([0.5])).n_rejected 1
- property n_steps: int[source]#
Return the number of steps recorded, accepted and rejected together.
- Returns:
- int
The length of
z.
Examples
>>> import numpy as np >>> from solprop.backends.protocol import StepHistory >>> StepHistory(*(np.zeros(0) for _ in range(3)), ... np.zeros(0, bool), np.zeros(0)).n_steps 0
- wall_time: Float[ndarray, 'n_wall']#
- z: Float[ndarray, 'n_steps']#
- class solprop.backends.protocol.StepStats(z, step, values=<factory>)[source]#
Bases:
objectThe statistics a backend evaluated between two observations.
The channel of
docs/dev/kernel-contract.mdsection 9. A run whosesolprop.stats.StatsSpecnames aperiodicsubset gets one row everyperiodaccepted steps, evaluated inside the backend’s own step loop from the state that step just committed, and drained here at each save plane. Evaluating every statistic on every accepted step costs about a fifth of a step; a subset and a period are the two knobs that make the same information affordable.Empty when the run asked for no periodic subset, which is the default.
- Attributes:
- znumpy.ndarray
Position of each row, m, float64, shape
(n_rows,), increasing.- stepnumpy.ndarray
Index of the accepted step each row was taken after, into the same observation’s
StepHistory, int64, shape(n_rows,), increasing. The history counts rejected attempts too, so this is notrow * period.- valuescollections.abc.Mapping
The statistics, keyed by catalogue name, each with a leading row axis and then the leading batch axis every statistic crossing this seam carries:
(n_rows, n_batch, *shape).
- Raises:
- BackendError
If the two index arrays disagree in length, are not increasing, or a value’s leading axis is not the row count.
Examples
>>> import numpy as np >>> from solprop.backends.protocol import StepStats >>> rows = StepStats(np.array([0.1, 0.2]), np.array([0, 1]), ... {"energy": np.ones((2, 1, 1, 1))}) >>> rows.n_rows, rows.names (2, ('energy',)) >>> StepStats.empty().n_rows 0
- classmethod empty()[source]#
Return the channel a run without a periodic subset carries.
- Returns:
- StepStats
No rows and no statistics.
- property n_rows: int[source]#
Return how many rows were taken since the previous observation.
- Returns:
- int
The row count.
- property names: tuple[str, ...][source]#
Return the statistics the channel carries, sorted.
- Returns:
- tuple of str
The catalogue names.
- step: Int[ndarray, 'n_rows_step']#
- z: Float[ndarray, 'n_rows']#
- class solprop.backends.protocol.StepSummary(z, n_steps, n_rejected, max_norm, mean_norm, wall_time, met_tolerance)[source]#
Bases:
objectWhat one call to
Backend.propagate_to()cost and achieved.A handful of host scalars, so that a driver can report progress and warn about a tolerance that was not met without pulling the whole step history across the seam every plane.
- Attributes:
- zfloat
Position reached, m, which is the plane that was asked for.
- n_stepsint
Steps taken, accepted and rejected together.
- n_rejectedint
Rejected steps among them.
- max_normfloat
Largest scaled error norm over the accepted steps; 0 when none were taken.
- mean_normfloat
Mean scaled error norm over the accepted steps.
- wall_timefloat
Seconds the call took.
- met_tolerancebool
Whether every accepted step scored at or below one. False on a replayed schedule too coarse for its tolerance, which the driver logs (
stepper.mdsection 8).
- Raises:
- BackendError
If a count is negative, more steps were rejected than taken, or a norm or time is negative or not finite.
Examples
>>> from solprop.backends.protocol import StepSummary >>> StepSummary(1.0, 42, 0, 0.7, 0.4, 0.31, True).met_tolerance True