solprop.backends.numpy_ref.backend#

The reference backend: the step loop, the seam and the refusals.

One NumpyRefBackend steps one mode-averaged problem with the Kerr term, in plain NumPy, calling the named passes of solprop.backends.numpy_ref.passes in the order of docs/dev/architecture.md section 8. The step control follows docs/dev/stepper.md section 3 exactly: the controller’s proposal is its state, the executed step’s own norm decides acceptance, and the equivalent norm of the proposal is what the history and a rejection see.

The contract is consumed only through its evaluation functions – solprop.translate.linop.delta_phi(), solprop.translate.linop.unitary_phase(), solprop.translate.responses.prefactor_at(), and the scalar tables responses.density and transform.taper_scale – never through the coefficient arrays, so the representation of the z tables can change without touching a backend.

Module Attributes

QUADRATURE_ALARM

Relative quadrature error above which the alarm is logged, once per plane.

Classes

BlockResidual(flow)

R_s(z) state for a held flow's positions (SEP 16 phases 5 and 6).

NumpyRefBackend(problem, *[, grid, modes, ...])

Propagate one Kerr and plasma problem with interaction-picture DOPRI5.

SourceTables(synthesis, projection, ...)

The problem's tables a source evaluation multiplies by.

class solprop.backends.numpy_ref.backend.BlockResidual(flow)[source]#

Bases: object

R_s(z) state for a held flow’s positions (SEP 16 phases 5 and 6).

A history sample’s residual is formed against the section of every trial it serves, about six; its action depends only on the section and the sample’s flow weights, so each pair’s action is formed once and kept, the least recently used dropped first: each trial’s endpoint, at a new position, is used once.

Parameters:
flow_CoupledFlow

The held flow.

class solprop.backends.numpy_ref.backend.NumpyRefBackend(problem, *, grid=None, modes=None, profiles=None, tdse_binding=None)[source]#

Bases: StepDriver[Complex[ndarray, 'n_batch n_modes n_pol n_omega']]

Propagate one Kerr and plasma problem with interaction-picture DOPRI5.

The oracle of docs/dev/backends.md section 2. It implements solprop.backends.protocol.Backend and steps a single problem (max_batch = 1) on either grid: mode-averaged or multimode on the fixed transverse quadrature, scalar or vector-isotropic, with the Kerr term, the delayed Raman term and the plasma. Mode-averaged propagation is the one-node case of the same pipeline and not a second code path.

Refusals. A problem is refused by name, with solprop.exceptions.BackendError, when its schema_version is not the one this backend knows, when its flags declare a spectral response, when a Raman kernel meets two polarisation components, when the transform and the state would both carry the transverse components, or when its batch axis is larger than one. A term silently dropped would run, converge and be wrong.

Convention. Every array held or returned is in the FFT convention of docs/dev/conventions.md section 2; the backend never conjugates. The output layer converts on the way to disk and the processing accessors on the way to a user.

The grid. stats() delegates to solprop.stats.collect(), which needs a solprop.grid.TimeGrid; the kernel contract carries the array group solprop.translate.masks.Grids and no grid object, so the grid is supplied here instead. This is a known gap in the contract.

Parameters:
problemsolprop.translate.problem.KernelProblem

The validated kernel problem. validate() is called again here: validation is the backend’s defence, not the caller’s courtesy.

gridsolprop.grid.TimeGrid, optional

The grid the state lives on, needed only by stats().

modescollections.abc.Sequence, optional

The propagated modes, in state order, needed only by the zdw statistic.

profilescollections.abc.Mapping, optional

The z-profiles keyed by statistic name, needed only by the profile statistics.

tdse_bindingsolprop.translate.tdse.TDSEBinding or None, optional

Driver-owned persistent atomic plans, required exactly when the problem declares a TDSE response. The driver closes this binding after the backend.

Raises:
BackendError

Naming what the backend refuses.

Examples

>>> from solprop.backends.numpy_ref import NumpyRefBackend
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.pulses import SechPulse
>>> from solprop.stepper import PIController
>>> from solprop.translate.problem import assemble
>>> grid = EnvelopeGrid.from_limits(1.0, 835e-9, (500e-9, 2e-6), 1e-12)
>>> gnlse = problem_from([0.0, 0.0, -1.183e-26], omega_ref=grid.omega0,
...                      gamma=0.11, shock=False)
>>> start = SechPulse(wavelength=835e-9, width=100e-15, peak_power=9.8).build(grid)
>>> problem = assemble(grid, gnlse.mode, [gnlse.kerr], start,
...                    controller=PIController(rtol=1e-8, atol=1e-16),
...                    save_planes=[0.01], dz_max=0.01, dz_init=1e-3,
...                    density=1.0, shock=False, prefactor_index=1.0)
>>> backend = NumpyRefBackend(problem, grid=grid)
>>> backend.set_initial(problem.initial.field0, None)
>>> summary = backend.propagate_to(0.01)
>>> summary.z, summary.n_rejected, summary.met_tolerance
(0.01, 0, True)
>>> backend.observe().field.shape
(1, 1, 1, 1024)
>>> backend.close()
close()[source]#

Release the buffers. Idempotent.

helpers = Helpers(name='plain')#
max_batch = 1#
memory_ledger()[source]#

Return the size of every buffer the backend works in, in bytes.

The reference backend allocates its stage and transform buffers per pass instead of keeping a preallocated pool, so this reports what one step has resident at its peak – which is what the optimised backends hold for the whole run, and what the budget of docs/dev/architecture.md section 8 is measured against. Every buffer is named even when it is empty.

The space-domain entries are sized by transform.node_chunk and not by the node count, which is the whole point of the chunk loop (architecture.md section 13): holding (n_to, n_pol n_nodes) for every node at once would be 210 MB against 45 MB on the comb-to-continuum target.

Returns:
dict

Buffer name to bytes.

metadata()[source]#

Return execution facts and cumulative logical and atomic call ledgers.

Returns:
dict

Provider-free, serializable facts. A PEC predictor is explicitly labelled and is never reported as an accepted-field diagnostic.

name = 'numpy_ref'#
propagator_default = 'tabulate'#
restore_call_ledgers(logical, atomic=None)[source]#

Restore cumulative counters while leaving the Adams history empty.

Parameters:
logicalmapping

Persisted logical call counters and accepted-order record.

atomicmapping or None, optional

Persisted atomic attempts; unused reservations are released.

Raises:
BackendError

If a ledger is incompatible with this run.

set_initial(field, noise, *, z=0.0)[source]#

Load a state; an Adams march restarts its history from it.

Parameters:
fieldnumpy.ndarray

The state in the canonical layout.

noisesolprop.noise.NoiseRealisation or None

The realisation to carry, or None for the problem’s own.

zfloat, optional

The position the state belongs to, m; positive for a resume, which starts the history afresh unless restore_march() then continues it.

stats(spec)[source]#

Collect the requested statistics at the current position.

The statistics of the saved field go to solprop.stats.collect(), one call per batch member, stacked along the leading batch axis the protocol promises; there is exactly one definition of each in the package and that is it. The statistics solprop.stats.StatDefinition marks backend are of quantities a saved plane does not carry – the electron density, the peak ionisation rate, the on-axis intensity, the quadrature error – and are formed here from the backend’s own buffers.

Parameters:
specsolprop.stats.StatsSpec

Which statistics and which windows.

Returns:
dict

Small host arrays keyed by statistic name, each with a leading batch axis.

Raises:
BackendError

If the backend is closed, no initial state was loaded, no grid was given at construction, or a backend statistic needs something this run does not have.

solprop.backends.numpy_ref.backend.QUADRATURE_ALARM = 1e-06#

Relative quadrature error above which the alarm is logged, once per plane.

The fixed rule integrates the Kerr quartic of a mode set to the reference’s own rounding – 2e-14 at n_r = 32 for four HE_1m modes (docs/guide/waveguides/mode-sets.md) – so anything a millionth of the polarisation is not the rule converging slowly but the integrand being unresolved, which no step size can fix. It also sits at the loose end of the tolerances a run asks the controller for, so an alarm means the quadrature has become the largest error in the step. The number is a judgement, not a measurement.

class solprop.backends.numpy_ref.backend.SourceTables(synthesis, projection, t_window, t_window_derivative, omega_window, temporal_rate)[source]#

Bases: object

The problem’s tables a source evaluation multiplies by.

Read through this record rather than off the problem so that the single-precision path can hold them narrowed (SEP 33 section 4.2) while the passes stay the same.

Attributes:
synthesisnumpy.ndarray

transform.synthesis.

projectionnumpy.ndarray

transform.projection.

t_windownumpy.ndarray

grids.t_window_fine.

t_window_derivativenumpy.ndarray

grids.t_window_fine_derivative.

omega_windownumpy.ndarray

grids.omega_window.

temporal_ratenumpy.ndarray or None

responses.temporal_absorber_fine.

classmethod of(problem)[source]#

Return the problem’s own tables.

Parameters:
problemsolprop.translate.problem.KernelProblem

The problem.

Returns:
SourceTables

References to its arrays, uncopied.

omega_window: Float[ndarray, 'n_omega']#
projection: Float[ndarray, 'n_modes n_columns']#
synthesis: Float[ndarray, 'n_rows n_modes_s']#
t_window: Float[ndarray, 'n_to']#
t_window_derivative: Float[ndarray, 'n_to']#
temporal_rate: Float[ndarray, 'n_to'] | None#