solprop.backends.memory#

Estimate a run’s persistent memory before a backend allocates it.

A backend names every buffer it holds in memory_ledger(), but only once it exists, and by then it has allocated them. estimate_memory() returns the same bytes from the translated problem alone, grouped as ledger_groups() groups a constructed backend’s ledger, so that a run can be admitted against a budget before it allocates (the admission itself is the driver’s, solprop.driver.memory).

The route groups are the general modal route’s memory budget, per backend, checked against every backend’s ledger to the byte:

general_maps

The general modal route’s two execution maps and per-node tensor, 2 S + 648 Q with S = 16 Q C M n_omega one dense map; a moving guide’s maps are s d coefficients each over s sections of d terms, and a stage evaluates one of each, 2 s d S + 2 S + 648 Q, and native holds a trial’s weights too, 8 n_stages s d.

general_reference

The record’s resident E/H reference, its backward partners, the reciprocal Gram and the coordinate dual, 6 n_z S + 16 n_z M^2 n_omega. Already resident when the estimate is formed: the translated problem holds it.

general_chunk

The node-chunk buffers and the per-channel vectors, sized by the node chunk K of general_node_chunk().

general_delayed

What a delayed (tensor Raman) response adds per chunk node, and its kernels.

coupled_flow

The coupled flow’s blocks, B = 16 A N^2 each over the A active bins, with the eigenbasis and formation scratch. On Kokkos the Schur factors of the bins the eigenbasis does not take are held too; how many is decided per bin, so the estimate decides it as the adapter does (the one quantity it computes rather than reads off a shape, with temporaries of the flow’s own block size).

adams_history

The Adams march’s history (SEP 16 phase 3b), S one state’s bytes and q the method’s order: on the host engine (numpy_ref, numba_cpu) the store of q + 2 rows, with a varying operator’s state and rate rows beside it in the exponential formulation, and numba_cpu’s moment scratch of MAX_ADAMS_ORDER + 3 rows; native’s resident engine holds 2 (q + 2) + 4 rows and the norm’s per-bin scale, and with a varying operator 3 (q + 2) + 3 more rows and the rate’s term weights. Interpolating save planes adds q + 6 rows for dense observation, and another q + 2 with a varying operator.

base is every other row: the state, the stages, the propagators and the transform buffers of the legacy mode-set, free-space and spatial routes. It is the reference’s own rows (reference_base_memory(), which numpy_ref’s ledger calls), exact on numpy_ref; on numba_cpu and Kokkos it is the reference’s count of those buffers, not the backend’s own, plus each compiled backend’s far-field band partials (far_field_memory(), to the byte) and SEP 6’s dispersive response’s (dispersive_memory()): on the fused node path its filtered fields, node slots, products and their spectra, each compiled backend’s ledger rows to the byte; on the Cartesian, line and planar routes the buffers per node the node budget admits for it, times the node chunk, as the general route’s chunk counts its own. On twelve measured cases (without the response) numba_cpu’s own base is 0.975 to 1.046 times the reference’s count, and Kokkos’s 1.05 to 2.48 times (up to 5.4 MB more on the varying wall), because the native base also counts the backend’s host copies of the problem’s tables, its statistics buffers and a device copy of each z table. For SEP 27’s held field-current source, the estimate replaces the reference’s allowance with the selected compiled backend’s current buffers, projection rows, spectra and tables. FFT-provider allocator growth remains a measured ledger row.

What is not counted is named on every estimate (MemoryEstimate.not_counted).

Before translation. The general route’s reference and maps are the largest allocations of a large guide, and the route builds them when the simulation is constructed, so an estimate of the translated problem comes after them. estimate_guide_memory() returns the general_reference, general_maps and general_chunk groups from the guide’s declared shape and the grid alone – its channels, its node rule, the grid’s bins and time samples, and a moving guide’s sections and degree – equal to the byte to what estimate_memory() returns for those groups once the problem exists, so that the driver admits a guide before its reference is built.

Module Attributes

ESTIMATED_BACKENDS

The backends estimate_memory() has formulas for.

MEMORY_GROUPS

The groups of an estimate and of a grouped ledger, in this order.

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).

RESIDENT_CHANNELS

The most channels of a coupled block native's resident Adams engine steps.

Functions

adams_memory(problem, backend)

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

dispersive_memory(problem, backend)

Return the buffers SEP 6's dispersive response holds on a backend, by row.

estimate_guide_memory(guide, grid, *, z_max, ...)

Return a general modal guide's route groups from its declared shape alone.

estimate_memory(problem, backend)

Return the persistent bytes a backend will allocate for a problem.

far_field_memory(problem, backend)

Return the far-field pass's band partials on a backend, by row (SEP 23).

flux_closure_memory(problem, backend)

Return the magnetic closure's transverse volume a backend holds (SEP 34).

held_current_memory(problem)

Count Numba's held-current buffers before allocating its workspace.

ledger_groups(ledger)

Group a constructed backend's ledger as estimate_memory() groups it.

low_storage_flow_scratch(problem, backend)

Return whether a backend's low-storage march holds one flow scratch volume.

low_storage_flows(problem, backend)

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

low_storage_memory(problem, backend)

Return the persistent bytes a run would take under the low-storage march.

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.

Classes

MemoryEstimate(backend, groups[, ...])

A run's persistent bytes on one backend, by group, before it allocates them.

solprop.backends.memory.ESTIMATED_BACKENDS = ('numpy_ref', 'numba_cpu', 'native', 'mlx')#

The backends estimate_memory() has formulas for.

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.MEMORY_GROUPS = ('base', 'general_maps', 'general_reference', 'general_chunk', 'general_delayed', 'coupled_flow', 'adams_history')#

The groups of an estimate and of a grouped ledger, in this order.

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).

class solprop.backends.memory.MemoryEstimate(backend, groups, not_counted=(), resident=0)[source]#

Bases: object

A run’s persistent bytes on one backend, by group, before it allocates them.

Attributes:
backendstr

The registry name the estimate is for.

groupscollections.abc.Mapping

Bytes by group, every one of MEMORY_GROUPS present, non-negative.

not_countedtuple of str

What the estimate leaves out, each in words (the backend’s measured planning heap, per-stage transients, a noise field loaded later).

residentint

Bytes of the estimate already allocated when it was formed: the general route’s reference, which the translated problem holds. Non-negative.

Examples

>>> from solprop.backends.memory import MemoryEstimate
>>> estimate = MemoryEstimate("numpy_ref", {"base": 2048, "coupled_flow": 512})
>>> estimate.total, estimate.largest(1)
(2560, (('base', 2048),))
backend: str#
groups: Mapping[str, int]#
largest(count=3)[source]#

Return the largest non-empty groups, largest first.

Parameters:
countint, optional

How many to return at most, positive.

Returns:
tuple of (str, int)

Group names and bytes; ties keep MEMORY_GROUPS’ order.

not_counted: tuple[str, ...] = ()#
resident: int = 0#
property total: int[source]#

Return the bytes of every group together.

Returns:
int

The sum of groups, bytes.

solprop.backends.memory.RESIDENT_CHANNELS = 16#

The most channels of a coupled block native’s resident Adams engine steps.

solprop_native.backend.ADAMS_MAX_CHANNELS; a larger block takes the host engine.

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

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

The rows of the adams_history group, which the backends’ ledgers report and estimate_memory() sums (module docstring). 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.

backendstr

One of ESTIMATED_BACKENDS.

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, "numpy_ref") == {"adams_store": 6 * 16 * grid.n_omega}
True
solprop.backends.memory.dispersive_memory(problem, backend)[source]#

Return the buffers SEP 6’s dispersive response holds on a backend, by row.

Rows of the base group (decided by John Travers, 2026-10-08, SEP 6 Phase 2c, open question 3), empty without the response and on numpy_ref (whose pass allocates per call).

  • The fused node path (mode-averaged, multimode, radial): the compiled backends’ own rows, to the byte – one structure on both (SEP 6, Phases 2a and 3a). Each group’s filtered fields over the modal columns, unless a lone symmetric response filters the stage’s own spectrum in place (no in-stage absorber reads the bare field); the node slots of a chunk, one beside other responses or two for the polynomial, none alone; the products and, with an embedded rule, its products, with their spectra, unless the lone product is the stage’s own polarisation (all but a field grid’s analytic family).

  • The Cartesian, line and planar routes: on native its rows, to the byte (SEP 6 Phase 3b): a chunk’s products and their spectra over its temporal rows, unless the lone product is the chunk’s own polarisation; a lone symmetric response filters in place, the native in-stage absorber being a pass of its own; free space’s filtered fields are blocks of its products, the planar route’s are its own, with one node slot (symmetric) or two (polynomial) of a block. On numba_cpu, the complex-equivalent buffers per node and component the node budget admits for the response (dispersive_node_buffers()) times the chunk, an upper bound of its holdings.

  • The general modal route: on native its rows, to the byte: per chunk of the route’s own (which counts the response, general_dispersive_buffers()), the filtered fields and their spectra, the products and theirs. On numba_cpu the route’s chunk group counts them instead (_route_bytes).

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

backendstr

One of ESTIMATED_BACKENDS.

Returns:
dict

Row name to bytes.

solprop.backends.memory.estimate_guide_memory(guide, grid, *, z_max, backend, n_stages=7)[source]#

Return a general modal guide’s route groups from its declared shape alone.

Nothing is solved or allocated: the sizes are the guide’s channels, its node rule’s nodes, the grid’s coarse bins, fine bins and time samples, and, for a moving guide, the sections its path keeps up to z_max and their degree. The groups are the three the route fixes before translation – general_reference (the six maps of the dense reference: E and H, their backward partners, the reciprocal Gram and the coordinate dual), general_maps and general_chunk – each equal to the byte to estimate_memory()’s on the translated problem; the others are zero here and named in MemoryEstimate.not_counted.

The backward partners are counted. Whether a basis carries them is a property of the provider’s solved sample, which this does not form. Both providers this package ships return them; a provider that returns none is estimated high by those rows, two thirds of the fields again, and may be refused here where its translated estimate would be admitted.

Parameters:
guidesolprop.modes.guide.ModalGuide

A guide built by solprop.modes.guide.ModalGuide.from_provider().

gridsolprop.grid.TimeGrid

The run’s field grid.

z_maxfloat

Where the run ends, m, positive: the snapshot’s reference is stored at the entrance and at the tables’ far end, and a moving guide keeps the sections its path needs to reach it.

backendstr

A registry name, one of ESTIMATED_BACKENDS, or "auto".

n_stagesint, optional

The tableau’s stage count, which sizes Kokkos’s per-stage weights on a moving guide; the default tableau’s by default.

Returns:
MemoryEstimate

The three route groups, nothing resident.

Raises:
BackendError

If the backend has no formulas here, or the guide declares no sampling.

Examples

The reference a real guide’s route holds, estimated before it is built:

>>> from solprop.backends.memory import estimate_guide_memory
>>> from solprop.grid import FieldGrid
>>> from solprop.processing._examples import example_general_run
>>> run, tables, guide = example_general_run()
>>> grid = FieldGrid.from_limits(1e-5, 1.03e-6, (1.0e-6, 1.06e-6), 100e-15)
>>> estimate = estimate_guide_memory(guide, grid, z_max=1e-5, backend="numpy_ref")
>>> held = sum(
...     array.nbytes
...     for array in (
...         tables.electric, tables.magnetic, tables.backward_electric,
...         tables.backward_magnetic, tables.reciprocal_gram,
...         tables.coordinate_dual,
...     )
... )
>>> estimate.groups["general_reference"] == held
True
solprop.backends.memory.estimate_memory(problem, backend)[source]#

Return the persistent bytes a backend will allocate for a problem.

Construction-free: nothing the backend would allocate is allocated here. The route groups are the formulas of the module docstring for the named backend; the base group is the reference’s count on every backend.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated, validated problem.

backendstr

A registry name, one of ESTIMATED_BACKENDS, or "auto", resolved as solprop.backends.registry.resolve_name() resolves it.

Returns:
MemoryEstimate

Bytes by group, what is not counted, and what is already resident.

Raises:
BackendError

If the backend has no formulas here.

Examples

>>> from solprop.backends.memory import estimate_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.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)
>>> estimate = estimate_memory(problem, "numpy_ref")
>>> estimate.total == estimate.groups["base"] > 0
True
solprop.backends.memory.far_field_memory(problem, backend)[source]#

Return the far-field pass’s band partials on a backend, by row (SEP 23).

A row of the base group, to the byte: numba_cpu’s statistics_partials, the partials of solprop.backends.spatial.far_field_partials() and their band sum; Kokkos’s far_field_partials, the partials alone (its native pass adds the bands as it finishes them), without the W = 1 slot for on_axis_intensity, which Kokkos forms in its own pass; empty on numpy_ref, whose pass allocates per call.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

backendstr

One of ESTIMATED_BACKENDS.

Returns:
dict

Row name to bytes.

solprop.backends.memory.flux_closure_memory(problem, backend)[source]#

Return the magnetic closure’s transverse volume a backend holds (SEP 34).

A row of the base group, to the byte: on Kokkos a Cartesian, line or planar run whose own request has a local flux statistic holds the closure’s transverse volume beside the transform’s scratch, one state’s bytes. Its time rows and per-node magnitudes are chunk-sized statistics buffers, which the reference’s count already stands for; numpy_ref and numba_cpu form the closure per call.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

backendstr

One of ESTIMATED_BACKENDS.

Returns:
dict

Row name to bytes.

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

Count Numba’s held-current buffers before allocating its workspace.

Parameters:
problemKernelProblem

Translated problem. Without field plasma every row is zero.

Returns:
dict

Array bytes, excluding provider-owned FFT plan storage measured on construction. Includes the shared derivative table once.

solprop.backends.memory.ledger_groups(ledger)[source]#

Group a constructed backend’s ledger as estimate_memory() groups it.

Parameters:
ledgercollections.abc.Mapping

What solprop.backends.protocol.Backend.memory_ledger() returned, on any of ESTIMATED_BACKENDS.

Returns:
dict

Bytes for every one of MEMORY_GROUPS, and measured: the planning heap rows (*_heap_growth), measured at construction and not estimated.

Examples

>>> from solprop.backends.memory import ledger_groups
>>> ledger_groups({"state": 16, "general_electric": 32, "fft_plan_heap_growth": 8})
{'base': 16, 'general_maps': 0, 'general_reference': 32, 'general_chunk': 0, 'general_delayed': 0, 'coupled_flow': 0, 'adams_history': 0, 'measured': 8}
solprop.backends.memory.low_storage_flow_scratch(problem, backend)[source]#

Return whether a backend’s low-storage march holds one flow scratch volume.

numba_cpu’s modal and general routes, recomputing, form the step’s flows P (read by pass 1) and Q (passes 3 and 4) one after the other in one reusable volume, rather than as transients outside the ledger. The separable kinds form them per element, and native forms a modal route’s from the phase terms per element, so neither holds it; numpy_ref allocates its flows per call, as it allocates every transient.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem.

backendstr

A registry name.

Returns:
bool

Whether the scratch is held, one state’s bytes, in the propagators row.

Examples

>>> import dataclasses
>>> from solprop.backends.memory import low_storage_flow_scratch
>>> 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)
>>> recomputing = dataclasses.replace(
...     problem,
...     stepper=dataclasses.replace(problem.stepper, propagator_mode="recompute"),
... )
>>> low_storage_flow_scratch(recomputing, "numba_cpu")
True
>>> low_storage_flow_scratch(problem, "numba_cpu")  # the table is held instead
False
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 and the estimate 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: the estimate is then conservative.

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

Return the persistent bytes a run would take under the low-storage march.

The same run’s estimate_memory() with its Adams history dropped and its march’s stepping rows – stage rows, accumulators and propagators – replaced by the low-storage march’s two registers and flow table (SEP 24 section 6.6), the table taken as propagator_mode="auto" takes it for that march: the figure the admission names beside its refusal of an Adams history that does not fit.

Parameters:
problemsolprop.translate.problem.KernelProblem

The translated problem, under any integrator.

backendstr

A registry name, one of ESTIMATED_BACKENDS, or "auto".

Returns:
int

Bytes.

Examples

>>> from solprop.backends.memory import estimate_memory, low_storage_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.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_memory(problem, "numba_cpu") < estimate_memory(
...     problem, "numba_cpu").total
True
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, decided in phase 2: numba_cpu (and 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, "numba_cpu")  # 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 in tests/backends/test_mlx.py.

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.

One definition of the base group: numpy_ref’s memory_ledger returns these rows and estimate_memory() sums them. 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