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.
The scene. A SceneModule is converted with to_scene() now and again at every prepare() and run().
"fdtd" or "fdfd".
Frequencies in Hz. Must not be empty.
NoneSolver configuration. None uses FDTDConfig() or FDFDConfig() to match method; a mismatched type raises TypeError.
NoneRF port drives (FDTD only). See Ports and lumped elements.
NoneFree-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#
| Attribute | Type | Content |
|---|---|---|
scene | Scene | The resolved scene. Refreshed from a SceneModule at each prepare() and run(). |
scene_input | Scene or SceneModule | What you passed. |
scene_module | SceneModule or None | The module, when you passed one. |
method | SimulationMethod | FDTD or FDFD. |
frequencies | tuple[float, ...] | Requested frequencies in Hz. |
frequency | float | frequencies[0]. |
config | FDTDConfig or FDFDConfig | Solver configuration. |
excitations | tuple[PortExcitation, ...] | Direct port excitations; empty for a PortSweep. |
port_sweep | PortSweep or None | The sweep, when you passed one. |
has_trainable_parameters | bool | True 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. |
metadata | dict | Stored 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,
) -> SimulationA time-domain run. Arguments other than scene, frequencies, frequency and excitations fill an FDTDConfig.
The scene.
NoneOutput frequencies in Hz for spectral (DFT) accumulation. Pass this or frequency, not both.
NoneA single output frequency in Hz.
NoneStep count and time step. None uses TimeConfig.auto().
AbsorberKind.CPMLAbsorber used on PML faces. See AbsorberKind.
NoneWindow and normalization of the spectral accumulation. None uses SpectralSampler().
NoneCPML profile overrides. See FDTDConfig.
FalseEnable the solver's live plotting. Not allowed with parallel.
FalseAccumulate 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.
0.0Relative 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.
100Steps between shutoff checks. Must be greater than 0.
TrueCapture the field-update core into a CUDA graph, with an eager fallback. Forced to False when parallel is set.
NoneDrives for LumpedPort, TerminalPort or WavePort ports. Each port may appear once. A PortSweep runs one column per port and fills Result.network.
NoneSplit one solve across several GPUs. See FDTDParallelConfig.
NoneElectrostatic 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) -> SimulationA 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.
The scene.
Solve frequency in Hz. None raises ValueError.
NoneIterative solver settings. None uses GMRES().
FalseEnable solver plotting.
FalsePrint 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) -> BatchSimulationStatic 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) -> ElectrostaticSimulationStatic 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) -> CapacitanceSimulationStatic 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() -> ResultValidates 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 | PreparedWavePortExcitationRuns 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 type | When | Methods |
|---|---|---|
PreparedSimulation | Ordinary FDTD or FDFD run. | run(*, resume_from=None), run_until(step) |
PreparedNetworkSweep | excitations is a PortSweep. | run(*, execution=None) |
PreparedWavePortExcitation | One PortExcitation on a WavePort. | run() |
Each returned object keeps simulation and scene (or solver) attributes.
PreparedSimulation.run
PreparedSimulation.run(*, resume_from=None) -> ResultExecutes 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) -> FDTDResumeCheckpointAdvances 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) -> ResultRuns 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) -> ResultModule-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.
NoneExplicit 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.
20Periods of the slowest frequency added after the transient. Must be finite and 0 or more.
15Minimum transient length in periods of the slowest frequency. Must be finite and 0 or more.
NoneExplicit time step in seconds. None lets the solver choose. Must be positive and finite.
NonePeak 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) -> TimeConfigAutomatic 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.
SpectralWindowKind.HANNINGWindow 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.
FalseDivide 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.
TimeConfig.auto()Must be a TimeConfig, otherwise TypeError.
SpectralSampler()Must be a SpectralSampler, otherwise TypeError.
AbsorberKind.CPMLNormalized to an AbsorberKind; an unknown name raises ValueError.
{}Overrides merged over the CPML defaults below. With absorber="stablepml", the stable profile then replaces grading_order, kappa_max, alpha_max and reflection.
NoneStep stride for adjoint checkpoints. Must be greater than 0 when set. Not exposed by Simulation.fdtd; set it on sim.config.
0.0Must be 0 or more.
100Must be greater than 0.
NoneMust 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:
| Key | Default | Meaning |
|---|---|---|
grading_order | 3.0 | Polynomial order of the conductivity ramp. |
kappa_max | 1.0 | Maximum coordinate stretching. |
alpha_max | 0.0 | Maximum complex-frequency shift. |
reflection | 1e-6 | Target reflection used to size the conductivity. |
memory_mode | "auto" | Auxiliary memory layout: "auto", "dense" or "slab". |
dense_memory_limit_mib | 64.0 | Limit 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 = 64FDFDConfig#
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.
5000Maximum iterations.
1e-6Convergence tolerance.
200GMRES restart length (Arnoldi iterations per cycle).
'gmres'One of "gmres", "cg", "bicgstab", "tfqmr", "idr", "sqmr", "direct".
'jacobi'One of "none", "jacobi", "ssor", "ilu", "ams".
'single'Working precision of the iterative solve: "single" or "double".
0.8Relaxation 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)| Member | Value |
|---|---|
SimulationMethod.FDTD | "fdtd" |
SimulationMethod.FDFD | "fdfd" |
str(member) returns the value.
SpectralWindowKind#
class SpectralWindowKind(str, Enum)| Member | Value |
|---|---|
SpectralWindowKind.NONE | "none" |
SpectralWindowKind.HANNING | "hanning" |
SpectralWindowKind.RAMP | "ramp" |
AbsorberKind#
class AbsorberKind(str, Enum)The absorber used on faces whose boundary kind is "pml".
| Member | Value | Meaning |
|---|---|---|
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.
Ordered CUDA devices with explicit indices, such as ("cuda:0", "cuda:1"). At least two, all unique.
'x'Must be "x".
'auto'Halo exchange transport: "auto", "cuda_p2p" (in-process) or "nccl" (one process per GPU).
TrueRequest 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.
FalseGather full fields onto result_device after the run.
NoneDevice that receives results. None uses devices[0]. Must be one of devices.
1800.0Collective 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.
| Field | Type | Content |
|---|---|---|
step | int | Step at which the state was captured. |
total_steps | int | Total step count of the run. |
dt | float | Time step in seconds. |
physics | FDTDCheckpointState | Field and auxiliary physics tensors with their schema. |
signature | dict | Execution fingerprint checked on resume. |
auxiliary | dict | Extra state such as accumulators. |
FDTDResumeCheckpoint.schema_version is 4.
FDTDResumeCheckpoint.save#
FDTDResumeCheckpoint.save(path) -> NoneWrites the checkpoint with torch.save, after moving all tensors to the CPU. Creates missing parent folders.
FDTDResumeCheckpoint.load#
FDTDResumeCheckpoint.load(path, *, map_location="cpu") -> FDTDResumeCheckpointClass 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.