solprop.backends.memory#

Byte counts and sizes the backends share for their memory ledgers.

A backend names every buffer it holds in memory_ledger(). numpy_ref’s ledger takes its rows from reference_base_memory() and adams_memory(), and mlx sizes its compiled graphs from mlx_graph_bytes() and mlx_stage_bytes(). The native adapter decides its low-storage registers and z-table slots here too.

Module Attributes

MLX_GRAPH_BUDGET

Bytes a step's lazy graph may hold before mlx evaluates stages as it forms them.

LOCAL_FLUX_STATISTICS

The spatial statistics that synthesise the magnetic closure in time (SEP 34).

Functions

adams_memory(problem)

Return the Adams march's history rows numpy_ref holds for a problem.

low_storage_flows(problem, backend)

Return how many flow volumes a compiled backend's low-storage march holds.

low_storage_stage_register(problem, backend)

Return whether a backend's low-storage march holds the stage register C.

mlx_bounded(stages, stage)

Return whether mlx evaluates each stage as it is formed.

mlx_graph_bytes(problem)

Return the mlx backend's live-graph row for a problem, bytes.

mlx_rows(problem, rows)

Return the mlx backend's device rows beside the oracle's host rows.

mlx_stage_bytes(problem)

Return one stage's live-graph bytes on the mlx backend.

reference_base_memory(problem, *, ...)

Return the reference backend's rows outside the general route and the flow.

single_precision_rows(problem, rows)

Return the reference's base rows for a single-precision problem.

z_table_slots(problem)

Return the position slots the native CPU build holds for a z table.

solprop.backends.memory.LOCAL_FLUX_STATISTICS = frozenset({'beam_radius', 'fwhm_r', 'on_axis_intensity', 'peak_intensity'})#

The spatial statistics that synthesise the magnetic closure in time (SEP 34).

solprop.backends.memory.MLX_GRAPH_BUDGET = 1073741824#

Bytes a step’s lazy graph may hold before mlx evaluates stages as it forms them.

Below it the whole step is one graph, evaluated with its norm; above it each stage and each running integral is evaluated as it is formed, which bounds the live graph to one stage (SEP 33 section 6.3).

solprop.backends.memory.adams_memory(problem)[source]#

Return the Adams march’s history rows numpy_ref holds for a problem.

Empty without an Adams integrator, and on the general path, whose history is a list of arrays.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

Returns:
dict

Row name to bytes.

Examples

>>> from solprop.backends.memory import adams_memory
>>> from solprop.grid import EnvelopeGrid
>>> from solprop.nonlinear.gnlse import problem_from
>>> from solprop.pulses import SechPulse
>>> from solprop.stepper import PIController
>>> from solprop.stepper.multistep import Multistep
>>> 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,
...                    integrator=Multistep(order=4))
>>> adams_memory(problem) == {"adams_store": 6 * 16 * grid.n_omega}
True
solprop.backends.memory.low_storage_flows(problem, backend)[source]#

Return how many flow volumes a compiled backend’s low-storage march holds.

The one predicate the engines share (SEP 24 section 4.3): with propagator_mode="auto" resolved for this march as StepDriver._propagator_mode resolves it on a CPU, a tabulated constant operator – every separable one included, whose phi_representation is "separable" – holds one volume, exp(h_t D / 2); a tabulated varying operator holds the step’s P and Q; recomputing holds none. A native device build resolves "auto" to recomputing and holds none, which this CPU count over-states by the table.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

backendstr

A registry name; every backend that runs the march counts the same.

Returns:
int

0, 1 or 2 volumes of one state’s size.

Examples

>>> from solprop.backends.memory import low_storage_flows
>>> 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)
>>> low_storage_flows(problem, "native")  # a constant operator's table
1
solprop.backends.memory.low_storage_stage_register(problem, backend)[source]#

Return whether a backend’s low-storage march holds the stage register C.

SEP 24 Q4: Kokkos, from phase 3, elides it on the Cartesian and line routes where a pass reads its input once – a material response and no in-stage absorber – since each finish can write the next pass’s input straight into the transverse scratch. Under an in-stage absorber the exchange re-forms the input from C for the absorber’s evaluation, so it is held; the modal and planar routes and numpy_ref hold it.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

backendstr

A registry name.

Returns:
bool

Whether C is held, one state’s bytes.

Examples

>>> from solprop.backends.memory import low_storage_stage_register
>>> 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)
>>> low_storage_stage_register(problem, "native")  # a modal route holds C
True
solprop.backends.memory.mlx_bounded(stages, stage)[source]#

Return whether mlx evaluates each stage as it is formed.

Parameters:
stagesint

The tableau’s stage count.

stageint

One stage’s live-graph bytes.

Returns:
bool

True when a whole step’s graph would exceed MLX_GRAPH_BUDGET.

solprop.backends.memory.mlx_graph_bytes(problem)[source]#

Return the mlx backend’s live-graph row for a problem, bytes.

Parameters:
problemsolprop.translate.problem.KernelProblem

A single-precision problem on a fibre, radial or Cartesian route.

Returns:
int

mlx_step_graph of mlx_rows().

solprop.backends.memory.mlx_rows(problem, rows)[source]#

Return the mlx backend’s device rows beside the oracle’s host rows.

SEP 33 section 6.3: the device copies of the narrowed tables, the operator’s and the prefactor’s terms as double-float pairs, and the live graph. Where a step’s graph would exceed MLX_GRAPH_BUDGET the backend evaluates each stage as it is formed and each running integral as it is scanned, so at most one stage’s working rows (_PER_STAGE_ROWS, twice, for the graph’s temporaries), a scan’s 2 ceil(log2 n_to) + 4 arrays over the node chunk, and the stages and their propagators are live at once; below it, a whole step’s seven stages of rows are, the scans’ levels released as they go. Measured against MLX’s own peak.

Parameters:
problemsolprop.translate.problem.KernelProblem

A single-precision problem on a fibre, radial or Cartesian route.

rowsdict

Its single-precision reference rows.

Returns:
dict

mlx_tables, mlx_terms and mlx_step_graph, bytes.

solprop.backends.memory.mlx_stage_bytes(problem)[source]#

Return one stage’s live-graph bytes on the mlx backend.

Parameters:
problemsolprop.translate.problem.KernelProblem

A single-precision problem on a fibre, radial or Cartesian route.

Returns:
int

Twice the per-stage working rows, with a scan’s levels.

solprop.backends.memory.reference_base_memory(problem, *, tabulating, embedded, noise, statistics_weight, spatial_absorber)[source]#

Return the reference backend’s rows outside the general route and the flow.

numpy_ref’s memory_ledger returns these rows. What one step has resident at its peak (the reference allocates its buffers per pass); the space-domain rows are sized by transform.node_chunk, not by the node count.

Method-aware (SEP 24 section 6.5). The Runge-Kutta march holds the tableau’s stage rows, two accumulators and the propagator pair or the six fractions’ pairs. The low-storage march holds none of them: two registers beside the state (register_accumulator and register_stage) and, tabulating, the flow table in propagators – one volume of exp(h D / 2) on a constant operator, the step’s P and Q on a varying one, nothing when recomputing.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

tabulatingbool

Whether the Runge-Kutta march’s propagators are tabulated (six distinct exponent pairs held) or recomputed (one pair); the low-storage march counts its flows by low_storage_flows().

embeddedbool

Whether the quadrature has an embedded coarse projection of its own.

noisebool

Whether a noise field is loaded.

statistics_weightint

Bytes of the statistics power weight, non-negative.

spatial_absorberint

Bytes of the composed radial absorber matrix, non-negative.

Returns:
dict

Buffer name to bytes, every row named even when it is zero.

solprop.backends.memory.single_precision_rows(problem, rows)[source]#

Return the reference’s base rows for a single-precision problem.

SEP 33 section 6.1: the element sizes follow the precision. Every buffer the single-precision path holds in binary32 is half the reference’s; the rows of _BINARY64_ROWS stay as they are; the narrowed copies of the source tables and the binary64 state the statistics widen are added.

Parameters:
problemsolprop.translate.problem.KernelProblem

A single-precision problem.

rowsdict

The reference’s rows, bytes.

Returns:
dict

The rows, bytes, with single_tables and single_widened_state added.

solprop.backends.memory.z_table_slots(problem)[source]#

Return the position slots the native CPU build holds for a z table.

SEP 29 section 6.6: the native backend evaluates a z table at each native call’s distinct positions into term slots of the state’s size, which the stage weights then select. A Runge-Kutta trial reads the step’s start, its stage nodes and its end, so the tableau’s distinct fractions with 0 and 1; the low-storage march reads the start, the midpoint and the end, and a constant operator’s table interval, five at most; an Adams call reads two.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

Returns:
int

The slot count, at least two; Phi’s and the prefactor’s tables share them.

Examples

>>> from solprop.backends.memory import z_table_slots
>>> 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)
>>> z_table_slots(problem)  # DOPRI5: 0, 1/5, 3/10, 4/5, 8/9 and 1
6