Memory planning#

The mesh is only part of a time-domain simulation’s memory requirement. Analysis setup constructs material operators and estimates a stable time step before allocating the fields. Temporary setup arrays can exceed the memory held during time integration.

AnalysisTD.estimate_memory() and AnalysisScatteringTD.estimate_memory() report allocation budgets without building material matrices, solving ports, computing a CFL eigenvalue, attaching monitors or creating project files:

estimate = analysis.estimate_memory(excited=["input"])
print(estimate)

excited= applies to scattering analyses. General TD analyses represent one run and omit it. The returned MemoryEstimate exposes byte counts through mesh_bytes, fields_bytes, coefficients_bytes, phases and monitors. Printed units are binary (GiB = 2³⁰ bytes, MiB = 2²⁰ bytes, KiB = 2¹⁰ bytes), matching tools such as free -h.

Phase budgets#

Each phase states simultaneous RAM and, for a GPU scenario, VRAM storage. RAM numbers include the mesh already in memory. Do not sum the phase budgets or add the mesh again. Sequential scattering runs reuse solver storage; raw monitor output on disk is multiplied by the number of excited channels. Results retained by multiple in-memory runs can increase RAM and leave the upper budget unresolved.

Field storage follows the staggered Yee shapes and requested precision, including the MAGNELIO_PRECISION default. CFL and material operator calculations remain in double precision. The CFL budget includes the current explicit sparse curl construction, its Python index lists, conversion temporaries, the absolute-value matrix copy and Lanczos vectors. A cached CFL eigenvalue removes the corresponding construction budget.

Ranges model allocation paths in the current implementation, not measured RSS or guaranteed process-memory limits. Conservative workspaces cover material/operator construction, field updates and CPML. CAD/native objects, Python metadata, allocator pools and checkpoint/I/O workspaces are excluded. Mesh arrays and views sharing a backing allocation are counted once.

Port/element mode construction, sparse factorisation and convolution histories, source state, dispersive/SIBC auxiliary states and monitor types other than rectangular time/frequency field monitors currently have no allocation model. When present, they are listed explicitly and the upper budget is None; known phase budgets still identify costly setup stages. peak_ram_bytes and peak_vram_bytes are consequently optional; minimum_peak_ram_bytes exposes the largest modelled lower endpoint. A missing upper endpoint must not be treated as zero.

An unresolved backend="auto" uses a CPU scenario without probing CUDA. Use estimate(backend="cupy") to budget the GPU scenario independently. This does not change the backend used by run().

Time monitor recordings#

Every rectangular time monitor reports bytes_per_snapshot for the requested raw staggered components. A slice, line or plane therefore uses its own component shapes, including boundary samples.

Schedule

Recording budget per run

times=[...]

At most the requested number of snapshots

interval=..., finite step bound and known dt

Snapshot count through that bound

interval=..., finite step bound but unknown dt

At most one snapshot per step

interval=..., no resolved bound

Unknown total, plus bytes per simulated second

With a known time step the estimator also coalesces explicit times that would be consumed in the same solver step. Early termination can reduce the recording below every stated count. For interval recording the growth rate uses the requested schedule; when the time step is known, it is capped at one snapshot per step.

Supply the same explicit limits intended for the run:

estimate = analysis.estimate_memory(total_time_steps=200_000)
estimate = analysis.estimate_memory(max_time_steps=500_000)

The automatic runtime cap depends on prepared ports and waveforms. The estimator leaves it unresolved instead of performing that setup. A physical duration requires the step to account for rounding and the monitor’s half-step acceptance window:

estimate = analysis.estimate_memory(t_end=20e-9, dt=1e-12)

dt= is an assumed scenario, not a solver override. If omitted, a cached CFL eigenvalue supplies the normal-accuracy step; otherwise no new eigenvalue is computed. Without a known step, a duration alone cannot certify the number of rounded recordings. t_end is a run option on AnalysisTD; on a scattering estimate it is only a planning scenario.

Without project=, time snapshots accumulate in RAM, including a possible second copy when frames are stacked. With project=, the raw total belongs to disk storage; RAM holds the pending batch and its stacking copy, at most 100 snapshots between the current solver’s flushes. monitor_disk_bytes is the raw field-monitor payload across all runs, excluding geometry, mesh files, port signals, checkpoints and HDF5 overhead.

Frequency monitors hold one complex128 array per recorded component and frequency, independently of run length and field precision. They continue to need those accumulators in RAM on a project-backed run.

The returned MemoryEstimate.n_runs counts sequential runs; collections of actual runs remain runs. The method is estimate_memory() on both AnalysisTD and AnalysisScatteringTD. Its dt, backend and t_end assumptions describe an estimate scenario and do not configure a later run.