Simulation

Reference for Simulation, its FDTD and FDFD constructors, run-time and spectral configs, multi-GPU FDTD and resumable checkpoints.

This page covers how you turn a Scene into a solver run with witwin.maxwell (import as import witwin.maxwell as mw). It lists the Simulation constructors and methods, the configuration objects they accept, the multi-GPU FDTD descriptor, and the checkpoint type for resuming an FDTD run. A run returns a Result.

import witwin.maxwell as mw
 
sim = mw.Simulation.fdtd(
    scene,
    frequencies=(1.5e9,),
    run_time=mw.TimeConfig.auto(steady_cycles=20, transient_cycles=15),
    spectral_sampler=mw.SpectralSampler(window="hanning"),
)
result = sim.run()

Both solvers need a CUDA scene (Scene(device="cuda"), the default). For the choice between methods, see FDTD and FDFD.

Simulation#

class Simulation(*, scene, method, frequencies, config=None, excitations=None, metadata=None)

A solver run description. Build it with Simulation.fdtd or Simulation.fdfd; the keyword-only constructor is what they call.

sceneScene | SceneModulerequired

The scene. A SceneModule is converted with to_scene() now and again at every prepare() and run().

methodSimulationMethod | strrequired

"fdtd" or "fdfd".

frequenciestuple[float, ...]required

Frequencies in Hz. Must not be empty.

configFDTDConfig | FDFDConfig | Nonedefault None

Solver configuration. None uses FDTDConfig() or FDFDConfig() to match method; a mismatched type raises TypeError.

excitationsPortExcitation | Iterable[PortExcitation] | PortSweep | Nonedefault None

RF port drives (FDTD only). See Ports and lumped elements.

metadatadict | Nonedefault None

Free-form metadata, stored as a copy.

Raises: ValueError for empty frequencies, port excitations with FDFD, a ThinWire with FDFD, or an embedded network with FDFD (NotImplementedError); TypeError for a scene that is neither Scene nor SceneModule.

Simulation attributes#

AttributeTypeContent
sceneSceneThe resolved scene. Refreshed from a SceneModule at each prepare() and run().
scene_inputScene or SceneModuleWhat you passed.
scene_moduleSceneModule or NoneThe module, when you passed one.
methodSimulationMethodFDTD or FDFD.
frequenciestuple[float, ...]Requested frequencies in Hz.
frequencyfloatfrequencies[0].
configFDTDConfig or FDFDConfigSolver configuration.
excitationstuple[PortExcitation, ...]Direct port excitations; empty for a PortSweep.
port_sweepPortSweep or NoneThe sweep, when you passed one.
has_trainable_parametersboolTrue when the scene holds any tensor with requires_grad=True (module parameters, density, geometry, material perturbation, circuit, wire or RF parameters). A trainable run goes through the adjoint path.
metadatadictStored metadata.

Simulation.fdtd#

Simulation.fdtd(
    scene, *,
    frequencies=None, frequency=None,
    run_time=None, absorber=AbsorberKind.CPML, spectral_sampler=None, cpml_config=None,
    enable_plot=False, full_field_dft=False,
    shutoff=0.0, shutoff_check_interval=100, cuda_graph=True,
    excitations=None, parallel=None, initial_condition=None,
) -> Simulation

A time-domain run. Arguments other than scene, frequencies, frequency and excitations fill an FDTDConfig.

sceneScene | SceneModulerequired

The scene.

frequenciesfloat | Iterable[float] | Nonedefault None

Output frequencies in Hz for spectral (DFT) accumulation. Pass this or frequency, not both.

frequencyfloat | Nonedefault None

A single output frequency in Hz.

run_timeTimeConfig | Nonedefault None

Step count and time step. None uses TimeConfig.auto().

absorberAbsorberKind | strdefault AbsorberKind.CPML

Absorber used on PML faces. See AbsorberKind.

spectral_samplerSpectralSampler | Nonedefault None

Window and normalization of the spectral accumulation. None uses SpectralSampler().

cpml_configdict | Nonedefault None

CPML profile overrides. See FDTDConfig.

enable_plotbooldefault False

Enable the solver's live plotting. Not allowed with parallel.

full_field_dftbooldefault False

Accumulate the DFT of the full 3D field. With more than one frequency and no LumpedPort in the scene, the solver turns this on regardless, which costs memory per frequency.

shutofffloatdefault 0.0

Relative electric-energy threshold for stopping early. 0.0 disables it. Must be 0 or more. Early stop is skipped when a source keeps running (CW) or has an unknown duration.

shutoff_check_intervalintdefault 100

Steps between shutoff checks. Must be greater than 0.

cuda_graphbooldefault True

Capture the field-update core into a CUDA graph, with an eager fallback. Forced to False when parallel is set.

excitationsPortExcitation | Iterable[PortExcitation] | PortSweep | Nonedefault None

Drives for LumpedPort, TerminalPort or WavePort ports. Each port may appear once. A PortSweep runs one column per port and fills Result.network.

parallelFDTDParallelConfig | Nonedefault None

Split one solve across several GPUs. See FDTDParallelConfig.

initial_conditionElectrostaticInitialCondition | Nonedefault None

Electrostatic pre-bias for the electric field at step 0. Not supported with parallel, trainable scenes, or Bloch boundaries. See Electrostatics.

Returns: a Simulation with method=SimulationMethod.FDTD.

Raises: ValueError when both or neither of frequencies and frequency are given, or for invalid config values (see FDTDConfig).

sim = mw.Simulation.fdtd(
    scene,
    frequency=1.0e9,
    run_time=mw.TimeConfig(time_steps=4000),
    absorber="stablepml",
)

Simulation.fdfd#

Simulation.fdfd(scene, *, frequency=None, solver=None, enable_plot=False, verbose=False) -> Simulation

A single-frequency frequency-domain run with a sparse iterative solver.

Warning

FDFD is limited and experimental. It supports linear isotropic or diagonal-electric media with none or PML boundaries on uniform grids. It rejects nonuniform grids, symmetry, thin wires, port excitations, circuits and embedded networks. Use FDTD for anything else.

sceneScene | SceneModulerequired

The scene.

frequencyfloatrequired

Solve frequency in Hz. None raises ValueError.

solverGMRES | Nonedefault None

Iterative solver settings. None uses GMRES().

enable_plotbooldefault False

Enable solver plotting.

verbosebooldefault False

Print solver progress.

Returns: a Simulation with method=SimulationMethod.FDFD.

sim = mw.Simulation.fdfd(scene, frequency=1.0e9, solver=mw.GMRES(tol=1e-6, preconditioner="jacobi"))
result = sim.run()

Simulation.batch#

Simulation.batch(simulations, *, execution, parameters=None, variant_ids=None, variation=None, outputs=None) -> BatchSimulation

Static method. Plans and runs supported same-scene variants together through native CUDA cohorts. execution is a BatchExecution. The result types and all arguments are documented in Batch and execution.

Warning

Batch execution is experimental in 0.5.1.

batch = mw.Simulation.batch(
    [sim],
    execution=mw.BatchExecution(devices=("cuda:0",), batch_size=4),
    variation=mw.BatchVariation(sources=("drive",)),
)

Simulation.electrostatic#

Simulation.electrostatic(scene, *, boundary=None, solver=None) -> ElectrostaticSimulation

Static method. Builds a Laplace or Poisson solve whose run() returns a Result with method="electrostatic". boundary is an ElectrostaticBoundarySpec, independent of Scene.boundary; solver is an ElectrostaticSolverConfig. See Electrostatics.

Simulation.capacitance#

Simulation.capacitance(scene, *, terminals=None, reference=None, boundary=None, solver=None, truncation_estimate=None) -> CapacitanceSimulation

Static method. Builds an N-terminal capacitance-matrix extraction whose run() returns a Result with method="capacitance" and a result.capacitance accessor. terminals selects and orders the electrostatic terminals (default: all); reference names the grounded return conductor. A TruncationEstimate in truncation_estimate adds one enlarged-box solve to report the domain-truncation error. See Electrostatics.

Simulation.run#

Simulation.run() -> Result

Validates the scene against the method and runs it. For a SceneModule input it rebuilds the scene first. A trainable scene runs through the adjoint path, so you can call backward() on a loss built from the result. See Result and Differentiability.

Raises: ValueError, TypeError or NotImplementedError for unsupported feature combinations, for example a circuit with FDFD, more than one circuit, trainable hard dielectric breakdown, a WavePort excitation with a non-matched source impedance, or a trainable multi-GPU run outside the supported medium and monitor set. RuntimeError when CUDA is unavailable.

Simulation.prepare#

Simulation.prepare() -> PreparedSimulation | PreparedNetworkSweep | PreparedWavePortExcitation

Runs the same validation as run(), compiles the scene and builds the solver, but does not step. Use it to surface errors early, to reuse the built solver once, or to stop and resume an FDTD run.

Returned typeWhenMethods
PreparedSimulationOrdinary FDTD or FDFD run.run(*, resume_from=None), run_until(step)
PreparedNetworkSweepexcitations is a PortSweep.run(*, execution=None)
PreparedWavePortExcitationOne PortExcitation on a WavePort.run()

Each returned object keeps simulation and scene (or solver) attributes.

PreparedSimulation.run

PreparedSimulation.run(*, resume_from=None) -> Result

Executes the prepared solver. A PreparedSimulation can run only once; a second call raises RuntimeError, so call Simulation.prepare() again for a fresh solver. Pass an FDTDResumeCheckpoint as resume_from to continue a run from that step.

Raises: TypeError for resume_from with FDFD; NotImplementedError for resume_from on a multi-GPU or trainable run; ValueError when the checkpoint does not match the prepared solver.

PreparedSimulation.run_until

PreparedSimulation.run_until(step) -> FDTDResumeCheckpoint

Advances exactly step FDTD steps and returns a detached checkpoint. This consumes the prepared simulation.

Raises: TypeError for FDFD or a non-integer step; ValueError unless 0 is at most step and step is less than the total step count; NotImplementedError for a multi-GPU or trainable run.

checkpoint = sim.prepare().run_until(1000)
checkpoint.save("run.pt")
 
checkpoint = mw.FDTDResumeCheckpoint.load("run.pt")
result = sim.prepare().run(resume_from=checkpoint)

PreparedNetworkSweep.run

PreparedNetworkSweep.run(*, execution=None) -> Result

Runs the port sweep. Pass a MultiGPUExecution as execution to spread the excitation columns over several GPUs; this is rejected together with parallel or a trainable scene. See Batch and execution.

run#

run(simulation) -> Result

Module-level shortcut for simulation.run().

Raises: TypeError if simulation is not a Simulation.

result = mw.run(mw.Simulation.fdtd(scene, frequency=1.5e9))

TimeConfig#

class TimeConfig(time_steps=None, steady_cycles=20, transient_cycles=15, dt=None, max_electric_field=None)

FDTD step count and time step. Immutable.

When time_steps is None, the solver sizes the run from the slowest frequency it sees: the simulation frequencies, the monitor frequencies and the source frequencies. Let T be that period. The transient is the larger of transient_cycles * T and ten domain crossings at the speed of light (based on the largest domain extent). The run covers the larger of that transient and the latest source settling time plus the propagation time, then adds steady_cycles * T.

time_stepsint | Nonedefault None

Explicit number of steps. None derives it as above. Must be a positive integer. Required when a source uses a callable waveform, because its duration is unknown.

steady_cyclesintdefault 20

Periods of the slowest frequency added after the transient. Must be finite and 0 or more.

transient_cyclesintdefault 15

Minimum transient length in periods of the slowest frequency. Must be finite and 0 or more.

dtfloat | Nonedefault None

Explicit time step in seconds. None lets the solver choose. Must be positive and finite.

max_electric_fieldfloat | Nonedefault None

Peak electric field in V/m that the time step must stay stable for, used with second-order or negative Kerr nonlinear media. If the field exceeds the bound during the run, the solver raises RuntimeError and asks for a larger value. Must be positive and finite.

Raises: ValueError for values outside the ranges above.

TimeConfig.auto#

TimeConfig.auto(steady_cycles=20, transient_cycles=15) -> TimeConfig

Automatic step count with the given cycle counts.

run_time = mw.TimeConfig.auto(steady_cycles=30, transient_cycles=20)
fixed = mw.TimeConfig(time_steps=4000, dt=4.0e-12)

SpectralSampler#

class SpectralSampler(window=SpectralWindowKind.HANNING, normalize_source=False)

How FDTD monitors accumulate spectra.

windowSpectralWindowKind | strdefault SpectralWindowKind.HANNING

Window for the DFT: "none", "hanning" or "ramp", case-insensitive. The window applies only when every source is CW; then the measurement starts after the transient. With any pulsed source, the full response is accumulated without a window.

normalize_sourcebooldefault False

Divide monitor spectra by the incident source spectrum. All sources must share one source spectrum, and circuit-driven fields are not supported; otherwise NotImplementedError.

Raises: ValueError for an unknown window name.

FDTDConfig#

class FDTDConfig(
    run_time=TimeConfig.auto(), spectral_sampler=SpectralSampler(),
    enable_plot=False, full_field_dft=False, absorber=AbsorberKind.CPML, cpml_config={},
    adjoint_checkpoint_stride=None, shutoff=0.0, shutoff_check_interval=100,
    cuda_graph=True, parallel=None, initial_condition=None,
)

The FDTD configuration that Simulation.fdtd builds; sim.config holds it. Each default object is created fresh per instance. Fields shared with Simulation.fdtd have the same meaning there.

run_timeTimeConfigdefault TimeConfig.auto()

Must be a TimeConfig, otherwise TypeError.

spectral_samplerSpectralSamplerdefault SpectralSampler()

Must be a SpectralSampler, otherwise TypeError.

absorberAbsorberKind | strdefault AbsorberKind.CPML

Normalized to an AbsorberKind; an unknown name raises ValueError.

cpml_configdictdefault {}

Overrides merged over the CPML defaults below. With absorber="stablepml", the stable profile then replaces grading_order, kappa_max, alpha_max and reflection.

adjoint_checkpoint_strideint | Nonedefault None

Step stride for adjoint checkpoints. Must be greater than 0 when set. Not exposed by Simulation.fdtd; set it on sim.config.

shutofffloatdefault 0.0

Must be 0 or more.

shutoff_check_intervalintdefault 100

Must be greater than 0.

parallelFDTDParallelConfig | Nonedefault None

Must be an FDTDParallelConfig or None. When set, cuda_graph becomes False, and enable_plot=True raises ValueError.

enable_plot, full_field_dft, cuda_graph and initial_condition are as in Simulation.fdtd.

CPML keys:

KeyDefaultMeaning
grading_order3.0Polynomial order of the conductivity ramp.
kappa_max1.0Maximum coordinate stretching.
alpha_max0.0Maximum complex-frequency shift.
reflection1e-6Target reflection used to size the conductivity.
memory_mode"auto"Auxiliary memory layout: "auto", "dense" or "slab".
dense_memory_limit_mib64.0Limit in MiB for choosing dense memory in "auto" mode, further capped by free GPU memory.
sim = mw.Simulation.fdtd(scene, frequency=1e9, cpml_config={"memory_mode": "slab"})
sim.config.adjoint_checkpoint_stride = 64

FDFDConfig#

class FDFDConfig(solver=GMRES(), enable_plot=False, verbose=False)

The FDFD configuration that Simulation.fdfd builds. solver must be a GMRES instance, otherwise TypeError.

GMRES#

class GMRES(
    max_iter=5000, tol=1e-6, restart=200, solver_type="gmres",
    preconditioner="jacobi", precision="single", ssor_omega=0.8,
)

Iterative linear solver settings for FDFD. Immutable. Despite the class name, solver_type selects the method.

max_iterintdefault 5000

Maximum iterations.

tolfloatdefault 1e-6

Convergence tolerance.

restartintdefault 200

GMRES restart length (Arnoldi iterations per cycle).

solver_typestrdefault 'gmres'

One of "gmres", "cg", "bicgstab", "tfqmr", "idr", "sqmr", "direct".

preconditionerstrdefault 'jacobi'

One of "none", "jacobi", "ssor", "ilu", "ams".

precisionstrdefault 'single'

Working precision of the iterative solve: "single" or "double".

ssor_omegafloatdefault 0.8

Relaxation factor for the "ssor" preconditioner.

The values are checked when the solver is built; an unknown solver_type, preconditioner or precision raises an error then. After the solve, result.solver_stats reports the settings, converged, solver_info and final_residual.

SimulationMethod#

class SimulationMethod(str, Enum)
MemberValue
SimulationMethod.FDTD"fdtd"
SimulationMethod.FDFD"fdfd"

str(member) returns the value.

SpectralWindowKind#

class SpectralWindowKind(str, Enum)
MemberValue
SpectralWindowKind.NONE"none"
SpectralWindowKind.HANNING"hanning"
SpectralWindowKind.RAMP"ramp"

AbsorberKind#

class AbsorberKind(str, Enum)

The absorber used on faces whose boundary kind is "pml".

MemberValueMeaning
AbsorberKind.CPML"cpml"Convolutional PML with the profile from cpml_config. Default.
AbsorberKind.STABLE_PML"stablepml"CPML with a higher grading order and a larger complex-frequency shift (grading_order=5.0, kappa_max=5.0, alpha_max=0.24, reflection=1e-9), for better late-time and low-frequency stability.
AbsorberKind.PML"pml"Graded-conductivity absorber without auxiliary memory.
AbsorberKind.ABSORBER"absorber"Graded-conductivity absorber without auxiliary memory.

The multi-GPU adjoint supports cpml and stablepml only.

FDTDParallelConfig#

class FDTDParallelConfig(
    devices, decomposition_axis="x", transport="auto", overlap=True,
    gather_fields=False, result_device=None, timeout_s=1800.0,
)

Splits one FDTD solve across several CUDA devices along x. Pass it as Simulation.fdtd(parallel=...). Immutable. The constructor checks structure only; CUDA availability, peer access and memory are checked at prepare time. For running many independent simulations instead, see Batch and execution and Batch and multi-GPU runs.

devicestuple[str | torch.device, ...]required

Ordered CUDA devices with explicit indices, such as ("cuda:0", "cuda:1"). At least two, all unique.

decomposition_axisstrdefault 'x'

Must be "x".

transportstrdefault 'auto'

Halo exchange transport: "auto", "cuda_p2p" (in-process) or "nccl" (one process per GPU).

overlapbooldefault True

Request overlap of halo exchange with computation. The solver uses a serialized schedule when this is False, with the NCCL transport, and in some other configurations such as CPML faces.

gather_fieldsbooldefault False

Gather full fields onto result_device after the run.

result_devicestr | torch.device | Nonedefault None

Device that receives results. None uses devices[0]. Must be one of devices.

timeout_sfloatdefault 1800.0

Collective timeout in seconds for the NCCL transport. Ignored by cuda_p2p. Must be positive.

Raises: TypeError for non-CUDA devices or wrong field types; ValueError for fewer than two or duplicate devices, a device without an index, another axis, an unknown transport, a result_device outside devices, or a non-positive timeout.

sim = mw.Simulation.fdtd(
    scene,
    frequency=1e9,
    parallel=mw.FDTDParallelConfig(devices=("cuda:0", "cuda:1")),
)

FDTDResumeCheckpoint#

class FDTDResumeCheckpoint(step, total_steps, dt, physics, signature, auxiliary)

Detached forward state of an FDTD run, for resuming a prepared run. You normally get one from PreparedSimulation.run_until(step) rather than building it. Immutable. Resume requires a single-GPU run without trainable parameters, and the target simulation must match the one that produced the checkpoint.

FieldTypeContent
stepintStep at which the state was captured.
total_stepsintTotal step count of the run.
dtfloatTime step in seconds.
physicsFDTDCheckpointStateField and auxiliary physics tensors with their schema.
signaturedictExecution fingerprint checked on resume.
auxiliarydictExtra state such as accumulators.

FDTDResumeCheckpoint.schema_version is 4.

FDTDResumeCheckpoint.save#

FDTDResumeCheckpoint.save(path) -> None

Writes the checkpoint with torch.save, after moving all tensors to the CPU. Creates missing parent folders.

FDTDResumeCheckpoint.load#

FDTDResumeCheckpoint.load(path, *, map_location="cpu") -> FDTDResumeCheckpoint

Class method. Reads a checkpoint with torch.load(..., weights_only=True).

Raises: ValueError when the payload is not a checkpoint mapping, has another data_type or schema_version, or misses required keys.