Execution resources#
Budget memory before a large propagation or scan. The state size grows with time samples, spatial nodes, modes and polarisations; saved planes can add a substantial output cost. Choose an adequate model and resolution first, then select a backend and thread settings suitable for the machine.
Backend choice and scan workers#
backend="auto" resolves to the native backend when installed and to Numba otherwise. It does
not silently retry another backend if the chosen one refuses a representation.
Use an explicit name for a scan spanning machines with different installations.
The resolved backend and runtime settings are recorded with the output.
Thread and runtime settings must be in place before the relevant libraries load. Avoid giving every process all CPU threads: budget workers and threads together. See the scan executor guidance and tuning recipes.
Memory#
The general modal route holds dense frequency-dependent maps and the guide’s stored E/H reference for the whole run: \(16\,Q\,C\,M\,n_\omega\) bytes per map, for \(Q\) nodes, \(C\) components, \(M\) channels and \(n_\omega\) frequency bins, up to fourteen such maps on a constant guide. That grows quickly: the graded-index worked example (412 nodes, four channels, 2049 bins) holds 2.3 GB. A coherent wall adds the flow’s per-section matrices.
Before a backend allocates anything, the driver estimates the run’s persistent bytes
from the same formulas the backends’ ledgers use
(estimate_memory(), equal to each ledger to the byte) and
admits the run against a budget
(admit_memory()). Undeclared, the budget is four fifths of
the memory available — the smallest of the physical memory, a Linux cgroup limit and a
Slurm allocation — and a run over it is warned about; a declared budget refuses a
run over it, unless it is prefixed warn:. One case is refused even undeclared: a run
that would fit but for the Adams method’s history is refused before it starts, naming
RungeKutta(), which keeps none
(choosing a stepper); warn: or "none"
lets it try anyway. A budget is a byte count or a number with a
unit ("16 GiB", "12e9", "64 GB"), or "none" for no check. Declare it for one run
with Simulation.run(memory_budget=...), or for every run in a process, including the
prop_* functions and scan workers, through the environment:
from solprop.driver import Simulation
simulation = Simulation.from_capillary(
125e-6,
1e-3,
"He",
1.0,
lambda0=800e-9,
wavelength_limits=(200e-9, 2e-6),
trange=100e-15,
fwhm=10e-15,
energy=1e-9,
n_save=2,
backend="numba_cpu",
)
budgeted = simulation.run(memory_budget="warn:8 GiB", progress="none")
print(budgeted.metadata.backend.settings.get("memory"))
SOLPROP_MEMORY_BUDGET="64 GiB" uv run python my_run.py # or "warn:64 GiB", or "none"
The prop_* functions have no memory_budget= keyword, so the environment variable is
their only way to declare a budget; likewise field retention (fields=,
field_planes=) is set on SimulationSettings or
through run_scan(), not as a prop_* keyword.
The estimate is recorded in the file’s backend record under memory, with its groups
(base, general_maps, general_reference, general_chunk, coupled_flow,
adams_history, …), so
you can see which one to shrink: fewer channels or nodes for the maps and the reference,
a narrower frequency window for all of them. Nothing is ever approximated to fit. Not
counted, and named on every estimate: FFTW’s planning heap and per-stage transients.
The output. A run kept in memory (output=None or a
MemoryOutput) holds every plane it saves, allocated at
the start for the entrance and every save plane, so the admission adds it as the group
output: the field, the noise phase and the statistics of each plane
(plane_bytes()). 201 saved planes of a large grid is not
small; fewer planes (n_save) or a file (output="run.h5"), which writes each plane out
and is charged nothing, shrink it. The step history, which grows with the steps taken,
is not counted.
The propagator table. propagator_mode on
SimulationSettings decides whether the stage
propagators are kept for the whole step ("tabulate") or recomputed in each stage
("recompute"), which changes the memory held in the base group, not the method. Its
default, "auto", leaves the choice to the backend and records what was resolved in the
backend record: numpy_ref tabulates, numba_cpu and native recompute, and a
free-space or planar run always recomputes and refuses "tabulate". When base is what
does not fit, propagator_mode="recompute" is one of the remedies the refusal names.
Operators tabulated along z. A linear operator or nonlinear prefactor that varies
along z but cannot be expanded in the mode set’s scalar parameter paths is tabulated
along z instead (varying operators). On a
large multimode set that table can run to gigabytes, and, like the problem’s other
tables, it is not in the estimate. numpy_ref and numba_cpu run it; native refuses it
(backends).
A general modal guide is admitted twice. Its route — the dense reference and the two
execution maps, nearly all of a large guide’s memory — is built when the simulation is
constructed, before run can see a translated problem. So
from_waveguide() admits the guide first
(admit_guide_memory()), under the same budget and the same
refuse-or-warn rule: general_reference, general_maps and general_chunk are estimated
from the guide’s declared shape alone — its channels, its node rule’s nodes, the grid’s
bins and time samples, a moving guide’s sections and degree
(estimate_guide_memory()) — and equal to the byte what
the translated estimate later finds for those groups. A guide over a budget declared to
refuse is refused before any of it is allocated; from_waveguide(memory_budget=...)
declares that budget, and otherwise SOLPROP_MEMORY_BUDGET or the machine’s default
applies. The whole run is admitted again by run.
The estimate’s groups, the backends’ memory ledgers and the formulas behind them are in the backend design.