Skip to content

Latest commit

 

History

History
975 lines (870 loc) · 272 KB

File metadata and controls

975 lines (870 loc) · 272 KB

Feature List

This document tracks the current user-visible capabilities of the maxwell package.

Maintenance Rule

  • Update this file whenever a new user-visible feature, public API capability, or meaningful workflow is added, removed, or materially changed.
  • Keep entries focused on capabilities that a user can discover through the public package, examples, or standard solver workflows.

Packaging And Platform Support

  • Prebuilt CUDA 12.8 wheels for CPython 3.10-3.14 on Linux x86_64 and Windows x86_64.
  • Each wheel includes one Python-independent LibTorch Stable ABI FDTD library targeting PyTorch 2.10 or newer; the same binary is CI load-tested with PyTorch 2.10, 2.11, and 2.12 across CPython 3.10-3.14.
  • Native CUDA images cover compute capabilities 7.0, 7.5, 8.0, 8.6, 8.9, 9.0, 10.0, 10.1, and 12.0, with compute 12.0 PTX retained for forward-compatible Blackwell execution.
  • Linux release wheels are repaired and validated as manylinux_2_35_x86_64 artifacts before publication.

Public API Model

  • Declarative simulation workflow: Scene -> Simulation -> Result
  • PyTorch-native scene-module workflow: SceneModule -> Simulation -> Result, with automatic to_scene() normalization inside Simulation
  • Top-level package exports for Maxwell-specific public objects under maxwell, including the full-featured public Material, the experimental RF/array contracts AxisPath, LumpedPort, TerminalRef, TerminalPort, WaveModeSpec, WavePort, Resistor, Capacitor, Inductor, SeriesRLC, ParallelRLC, PortExcitation, PortData, NetworkData, AntennaData, Ludwig3, BeamWeights, EmbeddedElementPatternData, ArrayBasisData, BeamData, BeamCodebook, MaxHoldComposite, MultipathEnvironment, MIMOData, PowerLossMonitor, and PowerLossData, the experimental linear-circuit graph contracts Circuit, CircuitNode, PortBinding, MNAConfig, independent/controlled sources, mutual inductors, scheduled ideal switches, transient source waveforms, and detached FDTDResumeCheckpoint, and the experimental subgrid-wire contracts ThinWire, WireConductor, WireEnd, WireMonitor, and WireData
  • Public simulation entrypoints: Simulation.fdfd(...), Simulation.fdtd(...), and run(...)
  • Typed simulation configuration records and enums exposed as FDFDConfig, FDTDConfig, TimeConfig, SpectralSampler, SimulationMethod, SpectralWindowKind, and AbsorberKind
  • Public frequency vocabulary uses frequency= for scalar selection and frequencies= for one-or-many target frequencies; freqs is not exposed in public Maxwell APIs
  • Public result container with structured field/material access (result.E.x, result.materials.eps.scalar), frequency-scoped selection through Result.at(...), monitor access, stats, plotting, and save support; FDTD lumped-port runs expose live torch-native PortData through result.port(name) / result.ports, circuit-coupled runs expose live device-resident CircuitData through result.circuit(name) / result.circuits, closed Huygens surfaces and driven ports feed typed antenna metrics through result.antenna(...), declared loss volumes resolve through result.power_loss(...) / result.monitor(name), and thin-wire monitors return device-resident, frequency-first WireData, while detached Result v2 snapshots embed versioned CPU port, circuit, and wire-monitor payloads

Scene Construction

  • Domain with explicit 3D bounds and from_domain_range(...)
  • GridSpec.uniform(dl) for isotropic grids, with dl treated as a maximum requested step: each axis uses ceil(span / dl) physical cells and redistributes them uniformly as span / count (the same rule as Tidy3D; for example, 1.28 m at dl=0.025 m becomes 52 cells at approximately 0.024615 m); the resolved node array is endpoint-inclusive (count + 1 Yee cell boundaries spanning the Domain exactly, identical to Tidy3D's exported grid boundaries and to the GridSpec.custom convention; on periodic/Bloch axes the last node is the wrap image of the first)
  • GridSpec.anisotropic(...) for per-axis constant dx, dy, dz
  • GridSpec.custom(x_coords, y_coords, z_coords) for nonuniform (graded) Yee grids from explicit node coordinates: any 1D float array-likes (list / NumPy / torch), validated per axis (strictly increasing, finite, at least two nodes) and stored as read-only float64 masters; a uniform axis is expressed by uniformly spaced coords, and the Domain bounds must equal the coordinate extents
  • GridSpec.auto(min_steps_per_wavelength=..., wavelength=None, max_ratio=..., override_structures=..., layer_refinement=...) for adaptive (AutoGrid) meshing: at prepare time every structure and MaterialRegion AABB face snaps to a cell boundary, each face-bounded interval is stepped at wavelength / (n_max * min_steps_per_wavelength) using the maximum overlapping refractive index at the meshing wavelength (material regions contribute sqrt(max(eps_bounds) * max(mu_bounds))), intervals are filled with uniform or geometrically graded cells with a global adjacent-cell ratio bound of max_ratio, and the result materializes through the same nonuniform-grid path as GridSpec.custom (single downstream representation); the meshing wavelength defaults to the highest source characteristic frequency in the scene (broadband pulses mesh for their upper spectral content, matching FDTD auto_dt), or an explicit wavelength=; under PML faces the outermost absorber cells are uniformized piecewise between structure faces, preserving face snapping and in-band refinement targets
  • MeshOverrideStructure(geometry, dl) mesh overrides for GridSpec.auto: a scalar or per-axis (dx, dy, dz) maximum step enforced inside the geometry's axis-aligned bounding box
  • LayerRefinementSpec(min_cells=..., axes=None) for GridSpec.auto: any structure-bounded interval thinner than min_cells local target steps receives at least min_cells cells along the covered axes
  • GridSpec.is_custom, GridSpec.is_auto, GridSpec.min_spacing, and GridSpec.axis_coords(...) for nonuniform-grid introspection
  • GridSpec.spacing returns (dx, dy, dz) for uniform specs, with GridSpec.is_uniform for uniform-grid checks; both raise on GridSpec.custom and GridSpec.auto, as do the scalar Scene.dx/dy/dz / Scene.grid_spacing properties (use scene.x/y/z, the half-grid coordinates, or grid.min_spacing instead)
  • BoundarySpec.none(), BoundarySpec.pml(...), BoundarySpec.periodic(), BoundarySpec.bloch(...), BoundarySpec.pec(), and BoundarySpec.pmc(); Domain.bounds always describes the physical domain, and PML cells are appended outside those bounds during scene preparation with the physical edge-cell spacing (matching Tidy3D)
  • Per-face boundary configuration through BoundarySpec.faces(...) and direct BoundarySpec(kind=..., x=..., x_low=..., ...) overrides, including global defaults plus per-axis or per-face specialization
  • BoundarySpec.bloch_wavevector="auto" marker for solver-resolved Bloch phase workflows during FDTD preparation
  • Public BoundaryKind literal type for boundary-mode selection across declarative scene APIs
  • Shared witwin.core.Structure records pairing geometry and material, with priority, enabled, and tags
  • Geometry.with_material(...) convenience path returns a shared Structure
  • MaterialRegion for density-based PyTorch-native material overlays: Box occupancy participates in the same per-subsample arithmetic or polarized interface averaging as an equivalent Structure, each Yee axis blends from its own current background toward the density-defined scalar eps/mu, density textures are sampled differentiably on-device, and the compiled model retains base/design tensors plus the nominal design mask
  • Scene assembly with Scene.add_structure(...), Scene.add_source(...), Scene.add_monitor(...), and Scene.add_material_region(...)
  • Experimental linear-circuit declarations through Scene.add_circuit(...) and Scene.compile_circuits(): a Circuit owns deterministic node/device order with a unique ground node, reuses the public Resistor / Capacitor / Inductor types with circuit-node terminals, binds only existing lumped/terminal EM ports, and performs explicit floating-network, source-loop, branch-dependency, and dense-size diagnostics without a CPU solver fallback
  • Restricted linear netlist import through Circuit.from_spice(...) / parse_spice(...), covering R/L/C/K, independent and E/G/F/H controlled sources, safe parameter arithmetic, sandboxed includes, flattened subcircuits, .ic, and PULSE/SIN/PWL waveforms; unknown devices/directives and executable expression syntax are hard errors, and Circuit.to_spice() provides deterministic canonical serialization
  • Experimental nonlinear circuit devices (Phase 0, standalone): device-terminal nonlinear elements Diode (controlled Shockley junction with ideality, series resistance, and junction capacitance parameters), PiecewiseLinearIV, PolynomialIV, and VoltageDependentCapacitor (declared as a single-valued charge Q(V)), each admitted through Circuit.add(...) under the shared circuit-device contract. compile_nonlinear_devices(...) groups same-signature devices into fixed-shape CompiledNonlinearDevice batches exposing analytic conduction current/conductance and stored charge/capacitance, and the newton_solve(...) GPU Newton core solves the NonlinearMNASystem residual/Jacobian with a dual convergence gate (an iterate is accepted only when both the scaled KCL residual and the Newton update meet their own tolerances, so residual-only false convergence is impossible), stable expm1/pnjlim junction limiting that keeps hard forward drive finite, and a backtracking line search; a non-convergent or rootless system fails closed deterministically within NonlinearSolveConfig.max_iterations (raise by default, record_and_stop for diagnostics) with node/residual/iteration-trajectory localization, never returning an unconverged or non-finite iterate. Diode/behavioral I-V devices register as DC-connecting for floating-network diagnostics while the charge-only capacitor stays DC-open. Transistor surfaces BJT/MOSFET are reserved and fail closed until the independent Phase 5 go/no-go gate. FDTD coupling, adjoints, and benchmarks are later slices.
  • Experimental standalone nonlinear circuit transient (Phase 1, no FDTD coupling): run_nonlinear_transient(...) runs Newton in the transient loop over a fixed or non-uniform time grid, folding each device's stored-charge companion into the residual/Jacobian with the same trapezoidal (2/dt) / backward-Euler (1/dt) bilinear-transform convention as the linear reactive elements (_charge_companion / advance_charge_state, so the diode junction_capacitance and VoltageDependentCapacitor Q(V) are consumed and the charge history advances only on an accepted step). The transient runs a backward-Euler first step (trapezoidal startup) so a prescribed non-DC initial_state does not carry the i_cap^0 = 0 first-step artifact. solve_dc_operating_point(...) finds the operating point with a geometric gmin continuation ladder ending exactly at gmin=0 (no residual artificial conductance), treating capacitors as open at DC. A step in which a PiecewiseLinearIV device crosses a knot is re-solved with a local backward-Euler companion so the trapezoidal reactive companion does not ring on the conduction-slope kink, and that step's KCL residual and charge-history advance are taken under the same backward-Euler companion it was solved with. Under failure='record_and_stop' a non-convergent step (or DC start) truncates the trajectory rather than committing the unconverged iterate: converged=False with stopped_step recording the failing index. multistart_dc(...) sweeps seeds and deduplicates the converged DC operating points into a MultistartReport that distinguishes a unique operating point from multistability on an N-shaped negative-differential-resistance device (a benign non-convergence maps to -1; a non-finite device evaluation propagates as a device-model error rather than being masked). Verified CPU/fp64: diode DC I-V vs analytic Shockley < 1e-5 across a bias sweep; half-wave and center-tapped full-wave rectifier steady-ripple output vs a frozen oversampled backward-Euler reference < 1% normalized RMS; every converged transient step meets the KCL residual tolerance; a linear-Q(V) capacitor discharge reproduces the analytic RC decay < 1e-4.
  • Experimental standalone CUDA linear-MNA runtime through compile_mna_system(...): double- or single-precision DC operating points and trapezoidal/backward-Euler transients cover shared R/L/C devices, independent and E/G/F/H controlled sources, mutual inductance, scheduled switches, constrained or zero DAE initialization, local backward-Euler damping at startup/breakpoints, reusable piecewise factorizations, trainable CUDA parameters, and torch-native CircuitData node voltages, physical branch currents, device powers, energy residuals, and solver diagnostics
  • Experimental strong FDTD/circuit co-simulation for one circuit bound to one or more lumped/terminal ports through the ordinary Scene -> Simulation.fdtd(...) -> Result flow: compile_coupled_mna_system(...) condenses every exact Yee edge Norton operator into one GPU MNA/DAE solve at the same midpoint, supports controlled sources across bound ports and PULSE/SIN/PWL schedules, reuses the existing compiled terminal geometry and polarity, scatters solved currents before tail corrections, reuses piecewise LU factors and preallocated solve buffers, vectorizes fixed R/C networks, and publishes consistent port and CircuitData samples. Fixed built-in source/switch schedules support factor-class CUDA Graph replay, and compile_batched_mna_factors(...) provides an internal fixed-shape [batch, unknown, unknown] cached GPU solve primitive. CircuitData.save/load uses detached safe tensor-only snapshots; Result v2 persistence is a detached CPU snapshot that must be loaded only from trusted sources. PreparedSimulation.run_until(...) plus a fresh single-GPU prepare().run(resume_from=...) restores physical fields/CPML, port accumulators, circuit histories and result prefixes under a strict execution fingerprint. Spatial multi-GPU forward runs assign each wholly shard-owned bound port to one owner and the circuit graph to the owner of the minimum global reference edge, exchange one voltage/current scalar pair per remote port per step, keep same-shard ports on a zero-P2P fast path, and move final PortData/CircuitData once to result_device; full distributed field-shard checkpoint replay and distributed gradients remain deferred. Direct excitation or PortSweep of a bound port, a nonzero DC port current inconsistent with the zero initial Yee field, multi-circuit execution, resumable full-field/observer runs, and one port spanning an x split fail explicitly.
  • Experimental single-device FDTD thin-wire workflow keeps immutable, physical-radius ThinWire centerlines separate from volume Structure geometry through Scene.add_thin_wire(...) / read-only Scene.thin_wires; PEC straight, bent, branched, named-junction, and closed-loop paths compile into one cached energy-paired current/charge graph and run through native CUDA sampling, recurrence, and sorted-transpose deposition without thin-cylinder voxelization. Physical polyline spans own circuit state while cell-local WireFragment records own conservative arbitrary-direction coupling, including uniform/custom/auto grids and legal interior paths under real-periodic boundaries; face-touching wraps and Bloch paths fail closed. Open and named-PEC-grounded endpoints are supported. Standard LumpedPort and TerminalPort declarations bind to wire nodes or a removed feed gap through WireNodeRef / WirePortBinding, retain Yee-staggered voltage/current phasors, and share the ordinary Result.port(...) and RF adjoint path. WireMonitor returns frequency-first device-resident current/charge/loss selections as differentiable WireData; wire I/q participate in schema-v2 checkpoint/replay, and native standard/CPML reverse paths provide exact recurrence/deposition transposes plus fixed-stencil coordinate, physical-radius, local isotropic host-material, gap-weight, and source-amplitude gradients. The solver reports compressed state and a conservative joint Maxwell/wire CFL adjustment; differentiable runs require a fixed step below that joint bound. Spatial multi-GPU forward runs assign each cell-local sampling/deposition Yee edge to its owning shard and the whole compressed I/q recurrence plus every WireMonitor to the single owner of the minimum global sampling edge (declaration-order independent), reduce per-segment EMF partials to that owner with a deterministic rank-ordered sum, advance I/q owner-only, broadcast the advanced current back so every owned edge deposits exactly once, and move final WireData once to result_device: a physical segment may span any number of shards, its fragments split at each partition boundary, and the shared joint Maxwell/wire dt is taken from the full-domain single-GPU wire runtime so the step matches bitwise. A wire crossing the x split reproduces the single-GPU forward bitwise at a short horizon (deterministic reductions and per-edge deposition are bit-exact) while over long runs individual field phases drift by chaotic float32 halo-curl accumulation as in any distributed FDTD, yet the discrete lossless-loop energy invariant across the split stays tight. A trainable wire under multi-GPU (the Phase 7 adjoint bridge does not checkpoint or replay wire state), a distributed CPML or Mur absorbing boundary, and a wire mixed with an embedded network or lumped circuit fail closed at prepare. Finite-conductor wires now have a first-class material law WireConductor.finite(conductivity, permeability=...) and a per-unit-length series-impedance model (witwin.maxwell.compiler.wire_impedance): the exact analytic solid-round-wire skin-effect impedance Z'(omega) = m/(2 pi a sigma) . I0(ma)/I1(ma) with the exact DC resistance 1/(pi a^2 sigma), and a passive rational auxiliary-differential-equation fit of the excess impedance that reuses the shared network rational-fitting stack (fit_rational, RationalModel.to_state_space, bilinear discretize, and the pole-aware positive-real passivity certificate) rather than duplicating any pole fitting. The analytic AC resistance meets the 2% analytic gate against an independent evaluation and the ADE realizes the broadband impedance with a numerically certified non-growing discretization (not positive-real by construction: the improper skin-effect impedance carries an out-of-band direct term, so dissipativity is asserted per segment by the checked in-band positive-resistance condition plus a combined current/ADE spectral-radius < 1 certificate, not guaranteed by construction). A finite-conductor wire now runs: the passive series impedance folds into the wire current update as an energy-consistent trapezoidal companion (L dI/dt + R_dc I + ADE-excess = emf + potential drop) whose PEC limit is the byte-identical lossless leapfrog, and the build adaptively selects the highest fit order whose combined current/ADE companion is certified non-growing (physical in-band positivity plus spectral radius < 1) or fails closed. The ohmic_loss monitor emits real per-frequency dissipation 0.5 Re(Z'(f)) length |I(f)|^2 (PEC segments report exactly zero), consistent with the recurrence's own dissipation. A finite conductor's field-coupled reverse/adjoint replay and checkpoint/resume fail closed (the ADE loss-state transpose and checkpoint schema, the field-coupled conductivity current sensitivity dI/dsigma, and distributed wire reverse/gradient communication remain deferred). The deterministic conductivity adjoint of the dissipation channel ships separately (see the next entry).
  • Finite-conductor thin-wire conductivity adjoint (dissipation channel): the exact closed-form conductivity sensitivity of the analytic scaled-Bessel internal impedance, d Z'(omega)/d sigma = (i omega mu)/(4 pi sigma) . (1 - (I0(ma)/I1(ma))^2) (witwin.maxwell.compiler.wire_impedance.internal_impedance_conductivity_gradient), whose DC limit is exactly d R_dc/d sigma = -1/(pi a^2 sigma^2). A PyTorch-native autograd path (witwin.maxwell.fdtd.wire_lossy.analytic_ac_resistance) makes a scalar conductivity leaf differentiate the per-unit-length AC resistance Re(Z'(f; sigma)), so an ohmic-dissipation objective 0.5 . Re(Z'(f; sigma)) . length . |I(f)|^2 has an exact conductivity gradient (validated against a float64 central difference of the analytic model, no rational fit involved), and LossySegmentModel.conductivity_ac_resistance_gradient(frequencies) returns the per-segment d Re(Z')/d sigma for the built model. This deterministic channel deliberately avoids the field-coupled current sensitivity, which stays fail closed because the recurrence's ADE coefficients come from a nondeterministic shared rational fit. A distributed (multi-GPU) forward carrying a finite-conductor wire fails closed rather than silently running it as a lossless PEC wire, because the distributed owner runtime builds only the lossless current update.
  • Scene-level port assembly with type and unique-name validation through Scene.add_port(...); ModePort continues to materialize through Scene.resolved_sources() / Scene.resolved_monitors(), while axis-aligned LumpedPort and TerminalPort declarations compile through Scene.compile_ports() into the same sparse Yee E-line/H-loop geometry without adding a second solver entrypoint. The first TerminalPort contract resolves two uniquely named, unrotated PEC Box structures across facing surfaces, uses the center of their transverse footprint overlap for voltage integration, and constructs its current contour from that overlap at the explicit reference plane
  • Phase-0 RF conventions use explicit negative-to-positive voltage paths, right-hand-rule current loops, peak phasors with exp(-i*omega*t), average power 0.5*Re(V*conj(I)), and complex-reference Kurokawa power waves. PortData keeps an explicit frequency axis and reports V/I, a/b, impedance, incident/reflected/accepted power, return loss, and VSWR; NetworkData fixes [frequency, output_port, input_port] ordering, complex z0[frequency, port], valid excitation columns, differentiable S/Z/Y conversion, and reference-impedance renormalization through torch.linalg.solve
  • Experimental single-device N-port workflow: PortSweep executes one deterministic FDTD run per selected RF input, internally matches inactive ports, preserves the declared port order, rejects incomplete or inconsistent columns, and returns the complete complex matrix through Result.network. NetworkData additionally supports differentiable reference-plane shifts and power-preserving single-ended/mixed-mode conversion. NetworkData.from_touchstone(...) strictly reads common Touchstone 1.x/2.0 RI/MA/DB S/Z/Y data with Hz/kHz/MHz/GHz units, explicit 2-port ordering, Full/Lower/Upper matrices, per-port reference impedances, source comments and line-numbered diagnostics; complete N-port networks write detached S/Z/Y data in the same formats
  • Experimental network rational-model workflow: NetworkData.validate_physicality(...) reports sampled Kurokawa passivity and an explicitly finite-band causality heuristic, while NetworkData.fit_rational(...) lowers complete S/Y/Z sweeps to a shared-pole real RationalModel. Public fit/state-space records provide auditable RMS/max error, unstable-pole and passivity diagnostics, pole-aware in-band interval verification, real A/B/C/D realization with Nports * order states, bilinear discretization with a strict |z| < 1-1e-7 gate, persistence, and differentiable pre-fitted residues/direct terms; automatic pole relocation and passivity enforcement reject trainable inputs instead of detaching them
  • Experimental N-port network embedding: NetworkBlock / TouchstoneNetwork connect passive pre-fitted or prepare-time-fitted networks to ordered named LumpedPort / resolved TerminalPort terminals through the existing Scene -> Simulation -> Result path. The fixed-shape GPU runtime applies a prepare-time pivoted-LU factorization for the coupled same-step direct-feedthrough solve, advances all state on device, supports independent CUDA Graph replay, and returns ordered tensor-native EmbeddedNetworkData voltage/current, signed per-port power, network-total absorbed/generated power, state norm, model identity, warnings, and provenance through result.embedded_network(name). Optional bounded per-port delay_seconds= (explicit or automatically extracted from scattering phase) de-embeds a passive rational S core and realizes integer plus fractional bidirectional power-wave delays with fixed GPU ring storage, integrated FDTD full-path < 3 deg phase / < 2% response gates, a < 0.02 re-embedding gate, and typed fit diagnostics. Pre-fitted RationalModel residues/direct terms and nearby supported material parameters differentiate through checkpoint/replayed network state and the native CUDA Maxwell adjoint; explicit delay state, trainable poles/proportional terms, direct trainable state-space matrices, and WavePort fail explicitly. Spatial multi-GPU forward runs assign each wholly shard-owned connected terminal to one owner and the network graph to the owner of the minimum global reference edge, advance the sole authoritative state recurrence and same-step LU feedthrough solve on that owner, exchange one voltage/current scalar pair per remote connected port per step, keep same-shard ports on a zero-P2P fast path, and move final PortData/EmbeddedNetworkData once to result_device; one connected terminal spanning an x split, a scene mixing an embedded network with a bound circuit, more than one embedded network, an explicit per-port network delay on the distributed path, and trainable multi-GPU network embedding fail explicitly, and the distributed field-shard/adjoint boundaries remain deferred. A PortExcitation, PortSweep, or port.termination aimed at a network-connected terminal, and a requested output frequency outside a network's fitted band under extrapolation='reject', fail identically on the single-device and multi-GPU paths rather than being silently dropped. Embedded diagnostics persist through ordinary and sharded Result.save/load
  • Experimental single-device FDTD lumped-port workflow: Simulation.fdtd(..., excitations=PortExcitation(...)) applies a device-resident implicit-midpoint field/circuit correction and accumulates stagger-aware V/I phasors; passive port terminations support series/parallel RLC combinations, standalone R/C/L elements use Scene.add_lumped_element(...) / Scene.compile_lumped_elements(), explicit feed-gap paths may bridge two distinct PEC terminal surfaces while embedded/same-conductor paths are rejected, pulsed port-only runs use a full step-zero spectral window, and all per-step indices, state, source evaluation, and DFT weights stay on the target device. The per-step series-port update runs an allocation-free in-place kernel schedule (no per-step device allocation, host<->device copy, or scalar sync), passive terminations skip the per-step drive-waveform evaluation, and the per-step energy/branch diagnostics are opt-in and off by default while the port voltage/current observers keep full fidelity
  • Unified port-field coupling convention: every model that couples a lumped device, an MNA circuit, or a fitted Touchstone/state-space network to the Yee grid drives the coupling from the same trapezoidal half-step interface voltage 0.5 * (V_after_previous + V_free) (the field free voltage averaged with the previous step's post-step port voltage). This is the unconditionally stable convention shared across the native lumped runtime, the strongly coupled MNA co-simulation, and the embedded-network solve, so cross-model agreement (an RLC termination expressed as a native lumped device, as an MNA circuit, or as a fitted one-port network) is exact at the shared convention rather than only close; a refinement-stability regression gate pins it by keeping a long fitted-network run finite and bounded where the older free-voltage interface went conditionally unstable
  • PortData.save/load and NetworkData.save/load use an explicit schema version, preserve port ordering/conventions/metadata, and intentionally restore detached inference tensors rather than a live autograd graph
  • Experimental single-device antenna workflow: Result.antenna(surface=..., driven_port=...) converts every requested frequency of a first-class closed Huygens monitor into a full-sphere far field and returns AntennaData with fixed [frequency, theta, phi] tensors, radiated/accepted/incident power, directivity, gain, realized gain, radiation/mismatch/system efficiency, EIRP, Ludwig-3 co/cross polarization, axial ratio, applied phase center/frame provenance, and the device-resident equivalent surface currents. Accepted and incident powers are taken directly from the named PortData; field and port paths remain differentiable
  • Experimental array analytical contract: power-normalized complex embedded element patterns use fixed [frequency, port, theta, phi] ordering and combine [port], exact [frequency, port], or batched [beam, frequency, port] incident power-wave weights entirely in PyTorch. The result reports S @ a, per-port and total incident/reflected/accepted power, masked active reflection and complex-reference active impedance, total complex far field, realized gain, and EIRP without invoking a solver. Scene.compile_array_monitors(...) freezes closed-surface, angular-grid, port-order, and phase-center provenance and rejects independent-source, nonlinear, or time-varying scenes before superposition
  • Experimental full-wave array basis extraction: an FDTD PortSweep retains compact closed-surface columns and measured incident power waves without N copies of full solver fields; Result.array_basis(...) produces content-fingerprinted, safely persistent embedded patterns and a Hermitian closed-surface Poynting power operator. Lumped/terminal ports support direct simultaneous LTI excitations for raw-complex broadside/endfire validation, WavePort sweeps support multifrequency modal-channel extraction, and ArrayBasisData.combine(...) reports active network quantities, absolute radiated power, efficiency, realized gain, and EIRP with zero solver reruns
  • Experimental array codebook and scan workflows: BeamCodebook names a set of [beam, port] or [beam, frequency, port] incident power-wave vectors over one basis, BeamCodebook.from_scan_angles(...) builds progressive-phase steering weights from explicit element positions and a scan-angle list, and ArrayBasisData.combine(codebook) evaluates every beam's active network quantities, far field, realized gain, and EIRP in one batched call. Combining any number of beams (64/256/1024) executes zero additional field-solver time steps (asserted by a solver step-counter fingerprint), and BeamData.max_hold(metric=...) reduces the beam axis to a per-direction envelope with a subgradient-carrying value and a non-differentiable winning-beam index; the content fingerprint (ArrayBasisData.cache_key) is weight-invariant so any beam vector reuses the same basis while geometry/material/port/frequency changes invalidate it
  • Experimental array MIMO metrics: MultipathEnvironment carries the angular power spectrum, cross-polar ratio, and polarization correlation with explicit integration weights, and ArrayBasisData.mimo(environment) integrates the dual-polarized embedded patterns into a Hermitian positive-semidefinite complex correlation matrix, envelope correlation coefficient (ECC), apparent diversity gain, and mean effective gain (Taga/Vaughan convention). ArrayBasisData.ecc_from_scattering() provides the distinct lossless S-parameter ECC approximation (Blanch form) and fails closed on non-passive or fully-reflective columns it cannot approximate (it cannot detect ohmic loss, which is invisible in S alone). All metrics stay in the torch autograd graph
  • Experimental array gradients: complex incident power-wave weights are fully differentiable through combine() and the batched codebook/max-hold/MIMO metrics without any solver rerun (validated against high-precision gradcheck); scene/material/geometry gradients through the basis are delivered by ArrayBasisData.scene_gradient_vjp(...) (see the dedicated subsection below)
  • Shared PowerLossMonitor / PowerLossData contract for explicitly named conduction, electric/magnetic dispersion, nonlinear, circuit, surface, and wire channels. Static bulk electric conduction is computed automatically as peak-phasor 0.5*sigma_e*|E|^2 on sparse Yee electric edges with nonuniform dual control volumes; explicit physical producers may supply volume W/m3, surface W/m2, line W/m, or integrated W channels. Results retain the explicit frequency axis, global Yee IDs, optional occupancy/material/geometry IDs, integration measures, autograd provenance, and a source-result fingerprint without inventing unavailable loss components
  • Experimental single-device RF adjoint workflow: active LumpedPort / TerminalPort, finite positive SeriesRLC or standalone R/C/L branches, and ordinary-Y embedded rational networks checkpoint and replay their auxiliary state alongside the native CUDA Maxwell reverse. Analytic local circuit/network VJPs cover port voltage/current, accepted/incident/reflected/available power, source amplitude, R/L/C values, rational residues/direct terms, material tensors, and supported smooth MaterialRegion parameters. Fixed single-mode WavePort direct and sweep outputs differentiate through eligible material/design regions, while NetworkData algebra and AntennaData derived metrics remain torch-native
  • Experimental single-GPU FDTD/circuit adjoint: checkpoint replay rebuilds each strongly coupled MNA step with torch-native transpose solves and propagates through direct, SPICE-expression, or SceneModule-derived R/L/C and independent-source waveform tensors, bound-port material/geometry inputs, port outputs, and every live CircuitData node/branch, device-power, energy-balance, and tensor-diagnostic output. Direct and parsed derived tensors follow eager PyTorch semantics, so repeated finite-difference or optimization evaluations rebuild/reparse the Circuit (normally in SceneModule.to_scene()) instead of retaining a consumed graph or mutating an already materialized descendant. The adjoint requires the coupled DC solution and companion histories to be exactly zero, rejects a tensor seed at the initial CircuitData sample, trainable DC source values or circuit initial conditions, trainable port reference impedance, and distributed execution, and keeps differentiable circuit runs off the RC/CUDA-Graph forward fast paths so no no-grad cache can hide a semantic dependency
  • Scene.clone(...) for scene-preserving validation, benchmarking, and device-transfer workflows
  • Scene(...) defaults to device="cuda" and requires an explicit device="cpu" override for scene-only CPU workflows
  • Optional domain symmetry on Scene(symmetry=(..., ..., ...)) with per-axis None / "PEC" / "PMC" on either domain face, via bare mode strings (low face by default) or explicit (mode, face) pairs such as ("PEC", "high"), folding the domain about any axis at either face with matching result-side expansion
  • Optional subpixel material averaging via Scene(subpixel_samples=...), accepting int, (sx, sy, sz), or a SubpixelSpec(samples=..., averaging=..., pec=...) policy object; exact interface samples receive differentiable half occupancy so odd and even sample counts agree without a one-sided material bias, while periodic image unions preserve both endpoint continuity and structures that cross into the opposite-side interior
  • SubpixelSpec(averaging="polarized") enables Kottke normal-projection subpixel averaging (harmonic permittivity/permeability along the interface normal, arithmetic tangentially) for eps and mu components, flowing through both FDFD and FDTD from the shared material compiler; multi-sample cells integrate each material component and its reciprocal separately before the normal projection, so nearly binary sub-samples retain the intended harmonic normal response instead of collapsing toward the arithmetic mean
  • SubpixelSpec(pec="conformal") enables stable partial-fill conformal PEC edge treatment for in-domain Material.pec() structures in FDTD. The per-edge fill is the geometric coverage fraction of each Yee E edge (the PEC signed distance interpolated between the edge's two endpoint nodes), so it has compact support: exactly 0 on an edge the conductor surface does not reach and exactly 1 on an edge wholly inside it. A conductor face parallel to the grid therefore cuts no tangential edge and conformal reproduces the staircase mask bit for bit; only genuinely cut (curved / oblique) surfaces take a fractional open fraction
  • Subpixel averaging (samples > 1, arithmetic and polarized) is generalized to nonuniform grids: the per-sub-sample offsets are per-node fields scaled by the local Yee dual-cell width (d{axis}_dual64), so GridSpec.auto / GridSpec.custom combine with subpixel averaging instead of failing through the scalar Scene.dx; every accumulated channel (eps/mu/sigma_e/off-diagonal components, Kerr chi3, chi2, TPA, the modulation quadratures and per-node frequency, and dispersive-pole weights) is offset consistently, and on a uniform grid the offsets reduce bit-exactly to the scalar-spacing path (a GridSpec.custom built from a uniform scene's node masters reproduces the GridSpec.uniform subpixel model channel-for-channel)
  • The compiled PEC node occupancy (read by the mode solver, the modal ports, the terminal-contact checks and the material summaries) is generalized to nonuniform grids: its smoothing width is a per-node field, half the local Yee dual-cell width (min over axes), instead of the single global 0.5*min(min_spacing) that let a fine feature anywhere shrink the smoothing in a locally coarse region; the occupancy stays differentiable in the wall geometry, and on a uniform grid the width reduces bit-exactly to 0.5*spacing (a GridSpec.custom built from a uniform scene's node masters reproduces the GridSpec.uniform PEC occupancy exactly). The conformal FDTD edge fill is computed separately from the signed distance directly on each Yee edge, so it is grid-spacing-exact on GridSpec.auto / GridSpec.custom without any smoothing width
  • Solver-side compiled scene inspection via Simulation.prepare(), where prepared.solver.scene materializes Yee-grid dimensions, lazy meshgrid allocation, material compilation, and orthogonal material cross sections without storing that state on the public Scene

Geometry

  • Analytic geometry primitives: Box, Sphere, Cylinder, Ellipsoid, Cone, Pyramid, Prism, Torus, HollowBox
  • Extruded polygon geometry via PolySlab(vertices, bounds, axis, sidewall_angle, reference_plane) with Tidy3D-style linear sidewall taper and even-odd interior rule for non-convex cross-sections, plus ComplexPolySlab(loops, ...) for self-intersecting and multi-loop (hole-carving) cross-sections
  • Shared core geometry constructors use position=... consistently across analytic primitives and Mesh
  • Shared core geometry constructors default to device=None, while Scene(...) owns device placement and defaults to CUDA
  • Rotation support for applicable analytic geometries
  • Axis selection for directional geometries such as Cylinder, Cone, Pyramid, Prism, and Torus
  • Triangle mesh geometry via Mesh(vertices, faces, ...)
  • OBJ mesh loading via Mesh.from_obj(...)
  • Geometry.to_mesh(...) returns torch-native vertex and face tensors, with faces standardized to torch.int64
  • Mesh transforms: recentering, scaling, rotation, translation
  • Mesh topology inspection: vertex count, face count, boundary edge count, non-manifold edge count, degenerate face count, inconsistent edge-orientation count, watertight flag
  • Mesh fill modes: auto, solid, and surface

Materials

  • Public Material(eps_r, mu_r, sigma_e, name=None) in witwin.maxwell, extending the shared witwin.core.Material contract with Maxwell-specific constitutive behavior
  • Optional electric and magnetic dispersive pole models on Material: DebyePole, DrudePole, and LorentzPole
  • Dispersive poles expose susceptibility(angular_frequency) and susceptibility_at_freq(frequency) for explicit frequency-domain evaluation
  • Spatially-varying custom dispersive poles CustomDebyePole, CustomDrudePole, and CustomLorentzPole: the oscillator-strength parameter (delta_eps for Debye/Lorentz, plasma_frequency for Drude) is a 3D torch tensor mapped over the structure's Box extent with the same lower-inclusive/upper-exclusive node coverage and trilinear resampling as MaterialRegion.density, composed multiplicatively with the structure's soft geometry occupancy; the compiler lowers each custom pole to its peak-strength scalar reference pole plus a per-cell amplitude grid, so the existing native CUDA ADE kernels run unchanged and per-cell chi(x, omega) = weight(x) * chi_ref(omega) is exact
  • Custom poles expose susceptibility(angular_frequency) / susceptibility_at_freq(frequency) returning the per-cell complex susceptibility grid, plus reference_pole() and amplitude() for the compile-layer lowering; FDTD auto-dt uses the peak (worst-case) pole parameters
  • PerturbationMedium(base, perturbation=..., eps_sensitivity=...) shifts a base Material's permittivity by an external perturbation field, eps(x) = eps_base + eps_sensitivity * perturbation(x) (for example a temperature or carrier-density map): the 3D torch perturbation grid maps over the structure's Box extent exactly like MaterialRegion.density, composes with the soft geometry occupancy, and is applied at compile time before ADE templating so dispersive bases see the shifted eps_inf; the map is PyTorch-native and differentiable, and a perturbation tensor with requires_grad=True is discovered as a trainable material input by the FDTD/FDFD gradient bridges (validated against per-element finite differences)
  • Axis-aligned diagonal anisotropy through DiagonalTensor3 for epsilon_tensor, mu_tensor, and sigma_e_tensor, plus symmetric positive-definite off-diagonal electric permittivity through Tensor3x3
  • A DiagonalTensor3 epsilon_tensor composes with electric dispersive poles in the same Material: each Yee axis carries its own background permittivity eps_inf_i while the shared isotropic pole susceptibility disperses all axes, giving the per-axis frequency permittivity eps_i(omega) = eps_inf_i + chi(omega) (birefringent background with material dispersion), validated against the analytic per-axis pole susceptibility and differentiable through the FDTD adjoint
  • A full off-diagonal Tensor3x3 epsilon_tensor composes with electric dispersive poles in the same Material (a rotated birefringent dispersive crystal): the poles enter isotropically so the lab-frame response is eps(omega) = eps_inf_tensor + chi(omega) * I, and the FDTD forward applies the same per-edge instantaneous inverse permittivity tensor eps_inf^-1 to both curl(H) (diagonal effective permittivity plus native off-diagonal coupling kernels) and the ADE polarization current (diagonal subtraction against eps_eff plus native off-diagonal current-coupling kernels), which diagonalizes exactly in the crystal principal frame and reproduces the ordinary and extraordinary indices n_o(omega) = sqrt(eps_ordinary + chi(omega)) and n_e(omega) = sqrt(eps_extraordinary + chi(omega)), validated within 2% by propagating both eigen-polarizations of a 45-degree rotated uniaxial Lorentz slab at two frequencies; this combination is FDTD forward-only, its adjoint stays guarded
  • A full off-diagonal Tensor3x3 epsilon_tensor structure may overlap the CPML absorber (an anisotropic crystal reaching the boundary): a dedicated native CUDA aniso CPML kernel coordinate-stretches every off-diagonal curl(H) derivative with a per-direction psi memory owned by the target Yee edge (the two transverse directions use the E-field node profiles, the edge's own half-point direction uses the H-field profile), which reduces exactly to the raw off-diagonal update outside the absorber and, inside it, restores the stability and absorption that the un-stretched coupling loses; validated by a homogeneous rotated birefringent crystal filling the domain whose post-pulse residual field energy stays within 2x of the isotropic-PML baseline (the un-stretched coupling diverges instead). The split-field graded-sigma absorbers (absorber="pml"/"absorber") have no per-direction auxiliary memory to stretch the coupling, so an anisotropic overlap there is rejected with a physics-worded error; the adjoint likewise rejects an anisotropic structure overlapping the CPML because the reverse replay does not yet stretch the off-diagonal coupling
  • A full off-diagonal Tensor3x3 epsilon_tensor composes with electric conductivity (sigma_e, or a DiagonalTensor3 sigma_e_tensor) in the same Material (a lossy anisotropic crystal): the FDTD forward folds the loss through the exact per-edge semi-implicit tensor inverse B = dt * (eps_inf + dt/2 * diag(sigma))^-1, giving the update E^{n+1} = E^n + B . (curl(H) - sigma . E^n) with the diagonal decay 1 - sigma_i * B_ii and a native off-diagonal conduction-current subtraction -B_ij * sigma_j E_j^n that couples the transverse components; for an isotropic conductivity this diagonalizes in the crystal principal frame, so a uniform field decays at exactly the analytic per-axis semi-implicit rate (validated to 1e-3, including the off-diagonal-generated cross component) and a plane wave through the slab absorbs at the analytic extraordinary complex index n = sqrt(eps_e + i sigma/(omega eps0)) (transmitted amplitude within 5%); this combination is FDTD forward-only, its adjoint stays guarded
  • A DiagonalTensor3 mu_tensor composes with magnetic dispersive poles in the same Material (the magnetic mirror of the diagonal-anisotropic electric-dispersion combination): each Yee edge carries its own background permeability mu_inf_i while the shared isotropic magnetic pole susceptibility disperses all axes, giving the per-axis frequency permeability mu_i(omega) = mu_inf_i + chi_m(omega) (a birefringent magnetic background with material dispersion), validated against the analytic per-axis magnetic pole susceptibility read from the magnetic ADE state; a full off-diagonal Tensor3x3 magnetic tensor remains unsupported as a static tensor field, while a non-reciprocal off-diagonal permeability is instead provided by the dedicated GyromagneticFerrite material (below), which carries the gyrotropy in a local magnetization state rather than by widening mu_tensor
  • GyromagneticFerrite first-class SI material for DC-biased ferrites (the framework's first non-reciprocal medium): GyromagneticFerrite(eps_r, saturation_magnetization, bias_field=(x,y,z), gilbert_damping, gyromagnetic_ratio, mu_infinity, sigma_e) defines a gyromagnetic (Polder) permeability from a linearized Landau-Lifshitz-Gilbert model, validated for saturation_magnetization > 0, bias_field != 0, gilbert_damping >= 0, and gyromagnetic_ratio > 0; GyromagneticFerrite.from_cgs(saturation_4piMs_gauss=..., bias_Oe=...) accepts CGS datasheet quantities and records the exact SI conversion in cgs_conversion, and GyromagneticFerrite.from_resonance(resonance_frequency=..., saturation_magnetization=..., linewidth=...) back-computes the bias and Gilbert damping from the gyromagnetic resonance; permeability_tensor_at_freq(frequency) / polder_tensor(angular_frequency) return the torch-native, differentiable complex 3x3 lab-frame Polder tensor mu_r = mu*(I - b b^T) + mu_infinity*(b b^T) + i*kappa*[b]_x (exp(-i*omega*t) convention, Hermitian when lossless and passive-absorptive when gilbert_damping > 0, off-diagonal kappa and the Faraday rotation flipping sign under bias reversal while the diagonal is unchanged); the full sign/unit conventions, LLG->Polder derivation, implicit-midpoint discretization, discrete-energy passivity proof, and frozen acceptance budget are recorded in docs/reference/ferrite-physics-contract.md. Scene.compile_gyromagnetic_materials(dt=...) lowers every ferrite structure into a CompiledGyromagneticLayout structure-of-arrays (active-cell indices, raw occupancy, per-cell bias unit vector and right-handed orthonormal local basis with an axis-aligned z/x/y fast path, omega_0/omega_m/gilbert_damping/mu_infinity, the dt-independent 2x2 magnetization-ADE matrices, and -- once dt is bound -- the implicit-midpoint Cayley propagator that is orthogonal when lossless and a strict contraction when lossy), with an explicit staircase partial-fill (the anti-symmetric gyrotropic tensor is never scalar-averaged) and a full off-diagonal mu_r(f) per-cell tensor accessor CompiledGyromagneticLayout.permeability_tensor(frequency); the layout is cached per scene and serializable, and its per-cell state-space / propagator matrices agree bit-for-bit with the Phase-0 verification oracle. The FDTD time-domain solver runs a ferrite scene directly through the standard Scene -> Simulation -> Result path: the ferrite compiles as its diagonal background and the non-reciprocal off-diagonal permeability is produced by a local magnetization-ADE advanced at each ferrite cell (gyromagnetic_enabled), inserted into the magnetic update as H -= dM/mu_infinity (the magnetic mirror of the electric-side full-anisotropy correction). The forward advances an arbitrary bias -- an axis-aligned z/x/y fast path (where the two transverse magnetic components co-locate on the shared Yee overlap) plus a general per-cell path for an oblique or spatially-mixed bias -- is unconditionally stable and passive (the discrete magnetic energy does not grow at zero damping, monotonically decays for positive Gilbert damping), reproduces the frozen discrete Polder response and its bias-reversal non-reciprocity, composes with PML and electric conductivity, adds zero operations to a ferrite-free scene, and is CUDA-graph capturable (no per-step allocation or host synchronization); the field<->magnetization coupling is the implicit midpoint of the coupled system (the magnetization is driven by the time-centred pre/post-update H and the correction feeds back simultaneously via a precomputed per-cell 2x2 inverse), which is discretely non-growing in a lossless closed cavity unlike an explicit pre-update-advance / post-update-correct split; a general (non-axis-aligned) uniform bias and a scene mixing bias directions (mixed axes, opposed signs on one axis such as a +z/-z latching circulator, or differing magnitudes/materials) are supported through a per-cell general-bias path -- a pure coordinate rotation of the same contracted implicit-midpoint update (identity collocation reused), where the purely-local magnetization ADE makes a mixed-bias scene the direct sum of independent per-cell passive blocks with correct per-region handedness -- while only a Bloch-periodic ferrite run still fails closed (the real magnetization-ADE correction cannot carry the complex Bloch phase), and every non-FDTD-forward consumer of a ferrite scene fails closed rather than silently simulating a reciprocal medium: the frequency-domain (FDFD) solve, the differentiable adjoint (no reverse gyromagnetic core yet), and the multi-GPU distributed solve all reject a ferrite, while single-device checkpoint/resume round-trips the magnetization state
  • General and mixed bias gyromagnetic ferrite forward (FDTD): an arbitrary uniform bias direction (not just a grid axis) now runs directly through Scene -> Simulation -> Result. The general-bias update is a pure per-cell coordinate rotation of the same linearized-LLG implicit-midpoint (Cayley) update the axis-aligned path uses -- no new integrator and no new coefficients -- gathering the transverse RF drive h_u = u·H, h_v = v·H from all three lab H components (columns u,v of the per-cell right-handed local frame [u|v|w], w = b̂) and scattering the back-reaction dM = dm_u·u + dm_v·v onto all three, with identity collocation reused from the axis-aligned slice; because [u|v]ᵀ[u|v] = I, the general path reduces to the axis-aligned fast path bit-for-bit for an axis-aligned bias (verified on both the magnetization ADE and the real CUDA field update), and it matches the frozen discrete Polder oracle for b̂ = (1,1,1)/√3 to reference_polder_rtol. A spatially mixed-bias scene -- multiple ferrite structures with different bias axes, opposed signs on one axis (e.g. a +z/-z latching circulator), or differing magnitudes/materials -- is supported through the same per-cell path: the magnetization ADE is purely local (fields couple only through the ordinary reciprocal Yee curl), so a mixed-bias scene is the exact direct sum of the independent single-material runs (verified bit-for-bit) with each region precessing around its own at the correct handedness. The forward is stable and passive for an oblique bias (energy-envelope non-growth in a closed lossless cavity) and reverses its Faraday-rotation (gyrotropic) direction under bias reversal while leaving the co-polarized response unchanged. Only a Bloch-periodic ferrite still fails closed (the real magnetization-ADE correction cannot carry the complex Bloch phase); the FDFD, adjoint, and multi-GPU consumers remain fail-closed as before.
  • Material.relative_permeability(frequency) for isotropic magnetic dispersion evaluation
  • Instantaneous isotropic Kerr nonlinearity on Material(kerr_chi3=...)
  • Instantaneous second- and third-order nonlinearity through NonlinearSusceptibility(chi2=..., chi3=...) composed via Material(nonlinearity=...) (single descriptor or a tuple): chi2 adds the per-component P_i = eps0 * chi2 * E_i^2 (second-harmonic generation, optical rectification) and chi3 the isotropic P_i = eps0 * chi3 * |E|^2 * E_i, lowering to exactly the same runtime channel as Material(kerr_chi3=...) (the two are equivalent and additive, validated bitwise against the Kerr fast path); multiple descriptors sum, and nonlinear media may coexist with dispersive or anisotropic materials in other structures of the same scene (FDTD only)
  • A single Material may carry both instantaneous nonlinearity (chi2 / chi3 / TwoPhotonAbsorption) and electric dispersive poles (Debye / Drude / Lorentz), the combination required for chi2 second-harmonic generation where the dispersion sets n(w) vs n(2w) and hence the phase mismatch dk = (2w/c) * (n(2w) - n(w)): the runtime subtracts the ADE polarization current against the same field-dependent effective permittivity eps_eff = eps_lin + eps0 * (chi2 * E_i + chi3 * |E|^2) that the nonlinear displacement-current term uses (so the instantaneous nonlinear index shift and the dispersive response stay consistent), validated against the analytic sinc(dk*L/2) phase-matching factor (linear-in-L phase-matched SHG growth vs Maker-fringe suppression in a mismatched dispersive slab); this same-material combination is forward-only, its FDTD adjoint stays guarded (FDTD only)
  • Two-photon absorption through TwoPhotonAbsorption(beta, n0=None) composed via Material(nonlinearity=...): the TPA coefficient beta [m/W] of dI/dz = -beta * I^2 enters the FDTD update as a field-dependent conductivity sigma_NL(x, t) = (4/3) * beta * (n0 * eps0 * c0)^2 * |E(x, t)|^2 folded into the semi-implicit lossy decay term per step (the 4/3 factor matches the cycle-averaged CW dissipation to alpha = beta * I); n0 defaults to sqrt(eps_r) of the host material, multiple descriptors sum, and TPA composes freely with chi2/chi3 and static conductivity in the same material (validated by the intensity-dependent saturable transmission trend)
  • Convenience constructors Material.debye(...), Material.drude(...), and Material.lorentz(...)
  • Experimental linear-gain media via gain-signed (negative oscillator strength, delta_eps < 0) LorentzPole / Material.lorentz(...), gated behind an explicit allow_gain=True opt-in so an accidental sign flip still raises; opting in emits a warnings.warn stability notice because gain media can violate the usual FDTD Courant/stability margins
  • Space-time permittivity modulation through ModulationSpec(frequency, amplitude, phase=0.0) composed via Material(modulation=...): the static permittivity becomes eps(x, t) = eps_static(x) * (1 + amplitude(x) * cos(2*pi*frequency*t + phase(x))) for non-reciprocal devices, isolators, and frequency conversion; amplitude (modulation depth in [0, 0.5)) and phase [rad] are scalars or 3D grids mapped over the structure's Box extent (same node-coverage/trilinear-resampling convention as MaterialRegion.density, enabling traveling-wave phase(x) profiles), the compiler rasterizes the quadrature fields amplitude*cos(phase) / amplitude*sin(phase) once, and the FDTD runtime applies the charge-conserving update E_new = decay * (m_prev/m_next) * E_old + (curl/m_next) * curl(H) inside dedicated native CUDA E-update kernel variants (updateElectricFieldE*Modulated3D plus dense- and compressed/slab-CPML counterparts) driven by a per-edge modulation angular-frequency field plus a persistent two-element device clock (so no coefficient tensors or host phase scalars are rebuilt per step, CUDA Graph capture remains valid, and a single Scene may hold several distinct modulation frequencies at once — each modulated structure stamps its own angular frequency onto the cells it covers, letting disjoint structures modulate independently); a modulated slab reproduces the expected omega +/- Omega sidebands in the transmitted spectrum, two disjoint slabs driven at Omega_A and Omega_B produce both sideband pairs at their own frequencies, and the compressed (slab) CPML memory mode reproduces the dense CPML modulated run bit-for-bit
  • Space-time modulation composes with electric/magnetic dispersion (Debye / Drude / Lorentz poles) and with the instantaneous nonlinear channels (kerr_chi3 / chi2 / TwoPhotonAbsorption) in the same Material or across separate structures in a Scene (the electro-optic-modulator edge, e.g. a Pockels-modulated dispersive crystal): the scalar modulation factor m(x, t) scales the eps_inf background only, so the ADE polarization current is folded through the same per-step 1/m_next factor as curl(H) (a dedicated native CUDA applyPolarizationCurrentModulated3D kernel divides the dispersive current by eps_inf * m_next, reducing bit-for-bit to the plain subtraction where the modulation depth vanishes), and the field-dependent nonlinear decay/curl coefficients feed the modulated E update directly; the modulated dispersive solve reduces to the pure-dispersive solve as the depth vanishes (within O(depth)), generates the omega +/- Omega sidebands on top of a dispersive slab, converts a smaller fraction of carrier to sidebands than a non-dispersive medium of the same static index (the poles are not modulated), and the modulated Kerr solve reduces to the pure-modulation solve as chi3 vanishes; this combination is FDTD forward-only, its adjoint stays guarded, and a modulated anisotropic tensor or a modulated static conductivity remains rejected with a physics-worded error
  • The pairwise composability of the FDTD material and environmental features is a documented, validated contract (tests/materials/combinations/test_combination_matrix.py, the P5.2 combination matrix): across the material axes {dispersive, diagonal-aniso, full-aniso, nonlinear (chi2 / chi3 / TwoPhotonAbsorption), modulated, sigma_e} plus the environmental axes {Bloch boundary, CPML overlap}, every physically meaningful ordered pair compiles and runs to finite fields, while the combinations still out of reach raise a physics-worded error (never "not implemented yet") instead of producing silently wrong physics — nonlinear / full-aniso / modulated media under complex Bloch fields (the real-valued update kernels are undefined on phase-shifted complex fields), a modulated or nonlinear anisotropic permittivity tensor (would need a per-step re-inversion of a coupled 3x3 tensor), a modulated static conductivity, and a full off-diagonal tensor overlapping a split-field (non-CPML) PML. Diagonal and full anisotropy are not an independent pair: a permittivity is either diagonal or full and both occupy the single epsilon_tensor slot
  • Material.sellmeier(b_coefficients, c_coefficients, eps_inf=1.0, ...) for lossless Sellmeier dielectrics (for example Schott BK7 glass), which lowers each B_i * lambda^2 / (lambda^2 - C_i) term to a zero-damping LorentzPole with delta_eps = B_i and resonance_frequency = c / sqrt(C_i); c_coefficients are the squared resonance wavelengths in SI meters^2
  • Frequency-dependent material evaluation through the shared material compiler for single-frequency workflows, including isotropic sigma_e
  • Component-aware material compilation keeps scalar summary tensors for visualization / compatibility while exposing explicit per-axis material grids for solver backends and Result.material(...)
  • Density-based MaterialRegion compilation using native PyTorch tensor interpolation, optional box filtering, and optional projection
  • Vacuum background by default (eps_r = 1, mu_r = 1)
  • Multi-structure material composition on the scene grid
  • Structure overlap resolution uses Structure.priority first, then append order among equal-priority structures
  • Occupancy-based material blending on the scene grid, with SDF-driven soft occupancy for shared Box, Sphere, Cylinder, Torus, and HollowBox
  • Phase-1.5 primitive SDF coverage for shared Ellipsoid, Cone, Pyramid, and Prism
  • Differentiable SDF occupancy for PolySlab and ComplexPolySlab, with gradients through polygon vertices, axis bounds, and sidewall angle
  • Differentiable mesh occupancy compilation through native-CUDA mesh signed-distance evaluation, including forward/backward distance-sign queries for watertight solid fill, geometry-state-aware static mesh SDF caching, cached BVH acceleration for larger static CUDA meshes, and shared surface-band modes
  • Supersampled voxel averaging for smoother material interfaces on partial cells
  • Polarized (Kottke normal-projection) subpixel averaging for eps and mu, using SDF-gradient interface normals estimated by central finite differences on the node grid (differentiable in geometry parameters), reducing normal-field error at high-contrast interfaces
  • In-domain perfect-electric-conductor material via Material.pec(name=None) / Material.is_pec, compiled to a differentiable union PEC occupancy grid and enforced in FDTD through per-edge open-fraction coefficient scaling (staircase hard edge, or conformal partial fill from the per-edge geometric coverage fraction, which reduces to the staircase mask bit for bit wherever the conductor surface does not cut the edge)
  • Zero-thickness conductive 2D sheets via Medium2D(sigma_s=...) [S]: attached to a Structure whose geometry is an axis-aligned Box with exactly one zero-size axis, the sheet snaps to the nearest node plane along its normal and lowers to the equivalent volumetric conductivity sigma_s / dcell on the two tangential Yee-edge conductivity components of that single layer (the normal component is untouched); sheet contributions are additive with bulk conductivity and other sheets, and Medium2D.sheet_conductivity(omega) / sheet_conductivity_at_freq(f) expose the analytic sheet conductivity for validation
  • Graphene(chemical_potential=..., scattering_time=..., temperature=300.0, include_interband=False) 2D sheet with the Kubo surface conductivity (chemical_potential in eV, temperature in K, scattering_time in s): the intraband (Drude-like) term sigma_s(omega) = A / (1/tau - i*omega) lowers to a tangential-only Drude pole on the snapped Yee layer (eps0*omega_p^2 = A/dcell); with include_interband=True the T>0 principal-value interband term (hbar*omega -> 2*|mu_c| absorption edge) is fitted at construction to a small set of Lorentz sheet terms (delta_eps = strength / (eps0*dcell)) and lowered to tangential Lorentz poles, matching the analytic Kubo conductivity to within a few percent across the optical band; both are advanced by the existing native CUDA Drude/Lorentz ADE kernels, and the intraband rate plus interband resonances feed the FDTD auto-dt bound
  • Medium2D.sheet_lorentz_terms() exposes resonant (strength, omega_0, gamma) surface-conductivity terms (each -i*omega*strength*omega_0^2/(omega_0^2 - omega^2 - i*gamma*omega) [S]); unlike Drude sheet terms these represent a capacitive below-edge reactance (Im(sigma) < 0) and lower to volumetric Lorentz poles, and are additive with the static and Drude sheet channels in sheet_conductivity(omega)
  • LossyMetalMedium(conductivity=...) good-conductor with a normal-incidence surface-impedance (Leontovich) boundary runtime: the metal interior is masked and each step the two tangential E faces are updated from the vacuum-side tangential H via the resistive Leontovich relation E_t = R * (n x H), with the surface resistance R = sqrt(omega0*mu0/(2*sigma)) evaluated at the operating frequency (the reactive part of Z_s is intentionally omitted: its explicit time-derivative overwrite is non-passive and unstable, and for a good conductor it shifts |Gamma| by < 1.3e-4); coverage is evaluated against the physical Domain.bounds, so a half-space that ends at the physical boundary remains valid when PreparedScene appends external PML cells, and either low-side or high-side illuminated faces use the actual geometry-face Yee node. The resolved skin-depth interior never needs meshing (validated to <5% of the analytic reflection at >=10x fewer cells than a resolved volumetric metal). Analytic helpers surface_impedance(omega) / surface_impedance_at_freq(f) / skin_depth(f) remain exposed for design
  • The LossyMetalMedium surface-impedance boundary is generalized from axis-aligned metal Box faces to any staircased (voxelized) good conductor: a non-Box conductor (a curved cylinder/sphere, all six exposed-face orientations, mixed orientations in one scene) is staircased from its node occupancy into masked per-face Leontovich writes, with each exposed voxel face illuminated only when its vacuum-side node lies inside the physical domain. Edges and corners are handled by the standard per-face-independent surface update (no new edge physics). This is validated at two levels beyond the flat-plate analytic reflection: (1) a wave-level skin-effect attenuation benchmark (benchmark/scenes/rf/lossy_waveguide_attenuation.py, RESULTS row rf/lossy_waveguide_attenuation) where a lossy-wall TE10 rectangular waveguide's conductor attenuation alpha, extracted from the two-line |S21| ratio of a short and a long guide, tracks the analytic TE10 alpha_c (Pozar 3.96) to a fraction of a percent across the band while a PEC-wall guide of the same geometry gives alpha ~ 0; and (2) a resolved-conductor physics gate (tests/validation/physics/test_sibc_cylinder_convergence.py) where the power absorbed by a staircased lossy-metal cylinder (skin depth unmeshed) reproduces the power absorbed by the same cylinder as a fully-resolved volumetric conductor (skin depth meshed on a grid-converged fine reference) within a documented tolerance, an order of magnitude closer than a PEC cylinder. True oblique/conformal (non-staircase) SIBC, a rotated metal Box, a generic rational SurfaceImpedanceMedium on a curved conductor, and Bloch + SIBC remain fail-closed with a physical reason
  • Validation note (no new adapter capability): the pre-existing LossyMetalMedium surface export through Scene.to_tidy3d() (see the reference-backend adapter section) was exercised as an external reference for the lossy-waveguide attenuation benchmark. No adapter code changed; the forward-mode attenuation cross-check is recorded honestly (the external RF surface-impedance export under-applies the wall loss at a coarse export grid, a documented adapter-fidelity gap, while the analytic alpha_c and the FDTD two-line alpha remain the binding references)

Sources

  • CW, GaussianPulse, and RickerWavelet source-time definitions
  • CustomSourceTime arbitrary temporal waveform from a sampled (times, amplitudes) table or a callable fn, evaluated on the solver time grid through the Python scalar injection path (no CUDA kernel change)
  • PointDipole source definition with source_time and selectable profile="gaussian"|"ideal"
  • PointDipole, ModeSource, ModeMonitor, and ModePort use position=... as the public spatial-location argument
  • PointDipole Gaussian profiles are normalized by the local Yee control volumes so their integrated SI current moment is invariant to width and grid spacing
  • PointDipole(profile="ideal") deposits the same unit current moment onto the neighboring Yee samples with linear position-preserving weights
  • PlaneWave soft source for analytical plane-wave injection on an auto-placed source plane; zero-phase periodic boundaries are supported on transverse axes, while periodicity along the propagation direction, a nonzero tangential phase advance, and Bloch boundaries require the TFSF path
  • PlaneWave soft source uses a single-plane directional E/H equivalent-current injector whose absolute incident-power scale is derived from first principles (surface-equivalence unit forward gain and the Yee numerical wave impedance eta0): unit source amplitude radiates unit time-averaged power over the full computational aperture, including external PML, with the analytic 1/sqrt(A*cos(theta)/(2*eta0)) scale and the derived Yee half-cell Poynting factor 1/sqrt(cos(k_normal*dl_normal/2)); there is no empirical calibration factor, and absolute native-monitor power matches analytic within 2% across frequencies and grid spacings
  • The soft PlaneWave numerical-dispersion phase correction is local to the launch footprint on nonuniform (graded) grids: the discrete-dispersion phase velocity that time-aligns the Yee E/H source planes (and phases the aperture for oblique incidence) is solved from the launch-plane cell spacing along the injection axis and the physical-aperture mean spacing along each tangential axis, instead of the global-minimum spacing whose finest cell can sit far from the source. On a grid whose interior is uniform along an axis, that axis returns its exact spacing, so the correction is bit-for-bit the previous global-minimum result and uniform grids are unchanged; on a ~2.6x-graded propagation axis with the source in the coarse region (~6 cells/wavelength there) the injected wavefront's phase-velocity error drops from ~4% to 0 (an exact match to the launch cell's own numerical wavenumber)
  • GaussianBeam soft source for analytical Gaussian-beam injection with configurable waist and focus
  • AstigmaticGaussianBeam soft source for elliptical Gaussian-beam injection with independent per-axis waist (w0_u, w0_v) and per-axis focal offsets focus_u/focus_v, reducing exactly to GaussianBeam for isotropic inputs, normalized to one watt, and using the launch plane as the common pulsed-time origin so a remote waist does not truncate the pulse before t=0
  • UniformCurrentSource(size, polarization, source_time, center=...) for a uniform additive electric current filling an axis-aligned box region, deposited by the geometric overlap of the source box with each Yee component's dual control volume
  • CustomCurrentSource(current_dataset, source_time) injects arbitrary volume electric-current (Jx/Jy/Jz) and magnetic-current (Mx/My/Mz) data through component-aware Yee windows and trilinear sampling, retaining the source's endpoint extension onto the covering discretized grid; singleton dataset axes map to the nearest corresponding node- or half-step Yee sample instead of collapsing staggered components to an empty window
  • CustomFieldSource(field_dataset, source_time) replays a tangential E/H distribution on one discrete TFSF face with the correct physical-H/update-equation sign conversion, producing the directional equivalent currents J = n x H, M = -n x E
  • Experimental ModeSource soft source for axis-aligned FDTD waveguide launching, using a full-vector generalized 2D eigenmode solve with adjoint forward/backward Yee differences on structured apertures to prevent odd/even checkerboard copies, a centered TE/TM parity basis for uniform metallic guides, deterministic sparse initialization and smoothness tie-breaking, deterministic requested-polarization rotation inside numerically degenerate eigenspaces, power-inner-product duplicate rejection, and requested-polarization-family ordering for stable physical mode_index selection. For a guided (non-TEM) mode request on a closed metallic aperture, a transverse null-space branch at beta = k0 (a plane-wave-like profile with no transverse cutoff) is rejected structurally -- by a block-averaged, anti-checkerboard transverse-envelope test combined with beta^2 ~ k0^2 -- so the guided mode is returned at every grid tier instead of the spurious beta = k0 eigenvalue; the rejection is gated on the requested wave family, so beta = k0 for a doubly-connected TEM line (solved on the separate electrostatic path) is unchanged. Full-plane component profiles retain zero-valued boundary nodes and use discrete one-watt Poynting normalization on transverse Yee planes, normal half-cell power compensation on resolved grids, and leapfrog-aligned propagation delays across staggered E/H launch faces in forward, replay, and adjoint execution. Dense and sparse forward backends coexist with the retained torch-differentiable scalar path for trainable scenes, while modal results expose per-candidate eigenpair/divergence/power/polarization/checkerboard diagnostics and the normalized power-overlap matrix
  • Experimental ModeSource / ModeMonitor / ModePort now accept broadband (GaussianPulse / Ricker) as well as CW soft injection (the guided profile is solved once at the waveform center frequency and driven by the native time-shifted surface kernel), complex/lossy eigenmodes (a complex-symmetric scalar solve exposing effective_index_complex / beta_complex, matched to ~4e-5 against the analytic lossy-slab dispersion), diagonal-anisotropic apertures solved with their true per-axis eps/mu tensors instead of an isotropic average, and bent (curved-waveguide) ports via the Heiblum-Harris conformal map (bend_radius / bend_axis, threaded through mode_spec so the S-parameter / mode-overlap postprocess reconstructs the same bent reference mode)
  • Experimental TFSF(bounds=...) injection descriptor for PlaneWave, GaussianBeam, and AstigmaticGaussianBeam, with validated axis-aligned PlaneWave support for CW and GaussianPulse, and validated CW oblique PlaneWave support
  • Experimental TFSF.slab(axis="x"|"y"|"z", bounds=...) injection descriptor for grating-oriented PlaneWave workflows on any normal axis (both CW and broadband GaussianPulse / RickerWavelet source times), spanning the transverse Bloch unit cell (the two axes transverse to the chosen normal axis) during solver preparation for periodic grating illumination; the two slab faces normal to axis are selected automatically and the oblique polarization is resolved from the incident direction, so the forward stepping, mixed Bloch/CPML update, and reverse-time grating adjoint all dispatch on the resolved single PML (normal) axis rather than assuming z. A pulsed source injects its per-cell time-delayed surface currents into the split real/imag Bloch field with the boundary wrap phase, so a single broadband run covers the band at a fixed transverse Bloch wavevector (automatic wavevector resolution still requires CW because it maps a single frequency to a wavevector)
  • Non-periodic (PML/absorbing transverse boundary) TFSF.slab injection for a normally-incident PlaneWave (CW or pulsed): the two-face slab fills the whole transverse cross section, injecting a plane wave that propagates along the slab axis with the scattered-field region confined outside the two axis-normal faces (the standard 1D-style reflection/transmission setup through a layered stack). It reuses the axis-aligned auxiliary-line provider, so the forward and adjoint runtimes need no slab-specific branch; oblique incidence under non-periodic transverse boundaries is rejected with a physics-worded error (use Bloch transverse boundaries for the grating slab)
  • CUDA PlaneWave TFSF forward stepping uses native CUDA auxiliary-line updates and fused patch-application kernels to reduce per-step launch overhead
  • GaussianBeam TFSF remains experimental and currently uses the analytical profile provider rather than the future angular-spectrum / discrete-face engine
  • Polarization specified by field name ("Ex", "Ey", "Ez") or explicit 3-vector
  • Source amplitude and phase carried by source_time; spatial sources keep width / beam parameters and optional name
  • Multiple sources per scene
  • Source compilation uses compile_fdfd_sources(...) / compile_fdtd_sources(...) list-based interfaces across both solvers

Grating TFSF Example

import witwin.maxwell as mw

scene = mw.Scene(
    domain=mw.Domain(bounds=((-0.5, 0.5), (-0.5, 0.5), (-1.0, 1.0))),
    grid=mw.GridSpec.uniform(0.05),
    boundary=mw.BoundarySpec.faces(
        default="pml",
        num_layers=12,
        strength=1.0,
        x="bloch",
        y="bloch",
        z="pml",
        bloch_wavevector="auto",
    ),
)

scene.add_source(
    mw.PlaneWave(
        direction=(0.25, 0.0, 0.9682458366),
        polarization=(1.0, 0.0, -0.2581988897),
        source_time=mw.CW(frequency=200e12),
        injection=mw.TFSF.slab(axis="z", bounds=(-0.4, 0.4)),
    )
)

scene.add_monitor(mw.FluxMonitor("transmission", axis="z", position=0.7))

result = mw.Simulation.fdtd(scene, frequencies=[200e12]).run()

Monitors

  • PointMonitor for point sampling of selected electric or magnetic field components
  • PlaneMonitor for orthogonal plane sampling of selected electric or magnetic field components
  • FinitePlaneMonitor for first-class finite rectangular plane sampling with explicit position=(x, y, z) and size=(sx, sy, sz) on a zero-thickness axis
  • ClosedSurfaceMonitor for first-class finite closed Huygens-surface workflows, including ClosedSurfaceMonitor.box(...) and custom multi-face axis-aligned surfaces built from FinitePlaneMonitor faces
  • FluxMonitor for plane-integrated power / flux extraction from tangential E/H fields; spectral flux planes snap to the nearest tangential-E Yee plane, symmetrically average the straddling H planes, crop external-PML samples back to the physical Domain.bounds, and integrate cell-centred transverse samples with full control-volume widths (including endpoint cells), avoiding arbitrary-position phasor attenuation and one-cell aperture undercounting
  • Spectral point / plane / flux / closed-surface monitors accumulate the running-DFT with the physical Yee time stagger: electric observers sample at the plain step phase while magnetic observers carry an extra -1/2 step (-0.5*omega*dt) retard, so the time-averaged Poynting cross term S = 1/2 Re(E x H*) sees the correct +1/2-step E/H offset; the FDTD adjoint seed schedule applies the exact transpose of this per-field phase
  • Near-to-far-field (Stratton-Chu / NF2FF) surface quadrature integrates each equivalent-current sample over its full primal Yee control volume (cell-centred midpoint rule), so on a closed Huygens box the control volumes tile the box surface to machine precision and the radiated far field is box-size independent (surface-equivalence invariant), giving correct directional far fields, RCS, and closed-surface radiated power
  • FieldTimeMonitor for raw time-domain FDTD field recording (point, plane, or volume region) with start / stop / interval sampling controls, returning the sample-time tensor t and per-component GPU buffers through Result.monitor(...)
  • FluxTimeMonitor for instantaneous time-domain Poynting flux (E x H) recorded on an axis-aligned plane with start / stop / interval sampling controls, returning the sample-time tensor t and the flux time series through Result.monitor(...); external-PML transverse samples receive zero integration weight so the default infinite plane remains the physical-domain aperture
  • Experimental ModeMonitor for first-class modal decomposition on an axis-aligned port plane, reusing the current ModeSource mode specification and returning forward / backward modal amplitudes and power through Result.monitor(...)
  • Experimental DiffractionMonitor for grating diffraction-order decomposition on an axis-aligned periodic unit-cell plane, returning per-order (m, n) complex tangential amplitudes, per-order Poynting power, propagating / evanescent flags, and diffraction angles through Result.monitor(...), with reciprocal lattice and light-cone cutoff derived from the transverse periods and the resolved Bloch wavevector
  • PermittivityMonitor and MediumMonitor for first-class point / plane / volume sampling of the compiled material properties (eps, plus mu and sigma_e for MediumMonitor), resolved from the compiled material tensors at result time through Result.monitor(...) without requesting any time stepping or extra DFT frequencies
  • Experimental DipoleEmissionMonitor for the power a named PointDipole delivers to the field (P = -(1/2) Re(conj(J) . E)), formed from the co-located electric-field DFT at the dipole cell and the injected source current spectrum, returning per-frequency power_delivered through Result.monitor(...); combine a structured run with a vacuum run through maxwell.postprocess.purcell_factor(...) to obtain the Purcell factor / local density of states
  • Optional per-monitor frequencies= on plane and modal monitors / ports
  • Named monitor results returned through the unified Result object
  • Multi-component plane monitors with aggregated Result.monitor(...) payloads and collocated tangential grids for postprocessing workflows
  • Scene.resolved_monitors() expands ClosedSurfaceMonitor into its underlying finite face monitors while preserving the public Scene -> Simulation -> Result workflow
  • Multi-frequency monitor output with Result.monitor(name, frequency=...) / freq_index=... selection
  • Optional compute_flux=True on PlaneMonitor to emit integrated Poynting flux / power per frequency
  • Result.raw_monitor(...) to access the underlying point / plane payload directly when a first-class modal monitor resolves to a higher-level modal result

Ports

  • Experimental ModePort scene object that declaratively couples an optional ModeSource excitation with a first-class ModeMonitor, so modal ports still flow through the same Scene -> Simulation -> Result public architecture
  • ModePort(source_time=...) materializes a named ModeSource plus a named modal monitor; ModePort(source_time=None) acts as a monitor-only modal port
  • ModePort(monitor_offset=...) can separate the launch plane from the sampled monitor plane along the port normal without introducing a second public solver entrypoint
  • Experimental RF WavePort / WaveModeSpec declarations freeze an axis-aligned aperture, explicit propagation direction and coincident reference plane, deterministic per-port mode identities, a tangential polarization seed, and peak-phasor impedance definitions. Scene.compile_waveports() snaps the aperture and optional TEM/hybrid voltage path and current contour to one Yee grid. Simulation.fdtd(...) supports both a named single-mode PortExcitation(..., mode_name=...) and independent-column PortSweep; the latter returns a complete multimode NetworkData, while both return modal PortData with explicit [M, F] propagation constants, characteristic impedances, and tracking confidence. Cross-frequency assignment combines propagation constants and modal overlap, including degenerate-subspace alignment. Uniform conductor-backed TEM apertures use a device-resident electrostatic mode solve and one-watt power-consistent V/I normalization; TE uses Z=omega*mu/beta, TM uses Z=beta/(omega*epsilon), and hybrid modes explicitly select voltage/current or power normalization. WavePort mode solves and FDTD columns execute sequentially on one CUDA device

FDFD Solver Workflow

Incomplete / experimental: FDFD is not feature-complete and is not a numerical reference for FDTD. The entries below describe only its currently implemented subset; use FDTD for the validated general workflow.

  • Frequency-domain simulation through Simulation.fdfd(...)
  • CUDA-only solver execution; Simulation.fdfd(...) requires Scene(device="cuda")
  • Single-frequency public wrapper with frequency=
  • Dispersive-material support via effective complex epsilon_r(omega) at the simulation frequency
  • Isotropic conductive-material support via sigma_e folded into effective complex epsilon_r(omega)
  • Axis-aligned diagonal electric anisotropy and diagonal sigma_e_tensor support in the Yee-grid operator
  • Per-face none / pml boundary selection, including one-sided and mixed-axis PML layouts
  • Explicit fast-fail validation for magnetic response, Kerr/nonlinear media, full off-diagonal Tensor3x3 permittivity, and in-domain PEC: FDFD static parity for these (and FDFD nonuniform grids) is deferred by user decision (2026-07-11), so the guards state an honest deferral and route to FDTD, which models them
  • Configurable GMRES settings via GMRES(max_iter, tol, restart, solver_type, preconditioner)
  • GPU-native preconditioners for the iterative solvers: none, jacobi (default), ssor (relaxation via GMRES(ssor_omega=...), default 0.8), ilu (ILU(0); unstable on the indefinite curl-curl operator — see benchmark/FDFD_PERFORMANCE.md), and ams (experimental in-repo Hiptmair–Xu auxiliary-space preconditioner with geometric multigrid; measured non-contractive on the indefinite time-harmonic operator — see the module docstring for its scope)
  • Double-precision iterative solves via GMRES(precision="double"): the Krylov recurrences and preconditioner run in complex128 while assembly and returned fields stay complex64; removes the float32 round-off stagnation (measured: sqmr+ssor converges to 1e-7 at 48³ where single precision stalls at ~2e-2)
  • Supported solver_type values: gmres, cg, direct, plus in-repo GPU Krylov engines bicgstab, tfqmr, idr (IDR(s)), and sqmr (simplified QMR; exploits the complex-symmetric system)
  • The FDFD system is assembled in a symmetrized UPML formulation (exactly complex-symmetric via a diagonal similarity), which also measures far lower PML reflection than the previous discretization; the adjoint solve reuses the forward factorization/preconditioner as a consequence
  • direct solver backed by NVIDIA cuDSS (pip install witwin-maxwell[direct]): factorize-once / solve-many with the factorization cached on the prepared solver and reused across source changes; complex64 LU plus iterative refinement
  • Direct-solve reuse composes with solver.set_frequency(...), which releases the stale factorization automatically
  • Typed FDFD solver configuration through FDFDConfig(solver=..., enable_plot=..., verbose=...)
  • Prepared execution via Simulation.prepare() before running
  • Differentiable FDFD: scenes with trainable parameters (SceneModule parameters, MaterialRegion densities, trainable geometry tensors) route through an adjoint gradient bridge, so loss.backward() on Result fields propagates to material and geometry inputs; the adjoint solve reuses the cached system state (with the direct backend, a cached transpose factorization)
  • System-matrix caching on the prepared solver: repeated solve() calls reuse the assembled matrix across source changes
  • Frequency switching on the prepared solver via solver.set_frequency(...), reusing compiled material components when all materials are non-dispersive
  • Unified Result output containing Ex, Ey, and Ez
  • Solver stats including convergence flag, solver info, residual, and solver configuration

FDTD Solver Workflow

  • Time-domain simulation through Simulation.fdtd(...)
  • CUDA-only solver execution; Simulation.fdtd(...) requires Scene(device="cuda")
  • Compiled native CUDA kernels are the only FDTD runtime backend; the Torch reference path has been retired from the runtime and relocated to the CUDA parity tests
  • Engineering-preview multi-GPU joint solves through public FDTDParallelConfig and Simulation.fdtd(..., parallel=...): Python remains the control plane, and DistributedFDTD owns an independent distributed coordinator/time loop for x-slab ownership, halos, streams, and events. The runtime is split into a rank-local ShardEngine (deterministic local-scene build from the partition plan, local solver, per-step phases, owned-energy scalar, local monitor/DFT payloads) and a coordinator that expresses every cross-rank operation through transport primitives (exchange_electric/exchange_magnetic, reduce_owned_energy, gather_component_slabs, gather_monitor_payloads, gather_stats), so the same coordinator drives both the in-process CUDA P2P transport and a one-process-per-GPU NCCL transport: the per-step time loop is branch-free over the transport primitives, while construction, validation, and result-gather scope do branch on transport kind (rank-local engine build, a narrower NCCL capability envelope, and sized point-to-point vs. peer-copy gather). Single- and multi-GPU execution share the same native CUDA numerical core; the six bounded standard real-field operations and legacy full-domain wrappers invoke the same implementations with different x ranges
  • Monitor-first multi-GPU result semantics: gather_fields=False returns supported point/time and assembled plane/flux/mode payloads on result_device without a global field, while gather_fields=True preflights and gathers global electric last-step or DFT fields. y/z-normal PlaneMonitor/FinitePlaneMonitor/FluxMonitor/ModeMonitor payloads are tiled across owned x intervals; x-normal planes have one owner, with an explicit guard for ordinary Ex planes exactly on an internal split. Gathered Result.save/load and manifest-based save_sharded/load_sharded provide eager or lazy/gathered persistence without restoring live transport state
  • Experimental multi-GPU FDTD joint-solve adjoint for a trainable Box MaterialRegion density on the pure real standard (open/PEC) path: a trainable-density Simulation.fdtd(..., parallel=...) scene routes through a distributed gradient bridge that captures per-shard forward checkpoints, runs one transposed reverse step per forward step (Phase 1 on every shard, a transposed magnetic halo, Phase 2, a transposed electric halo, Phase 3, then the per-shard source-term eps gradient), and gathers the per-shard grad_eps owned slices into a single global tensor so the existing single-GPU material pullback runs once on the logical scene. loss.backward() on a point-monitor-spectrum or full-field-DFT objective propagates to the density. Two-GPU acceptance covers 1-vs-2-GPU objective and gradient parity (point-monitor-spectrum and full-field-DFT objectives), central finite differences on density texels on the x-split and interior to each shard with the source and objective on the interface node, a checkpoint-capture stream-ordering contract, and bitwise-reproducible gathered grad_eps. Trainable geometry/material-perturbation/circuit/RF-port parameters, the legacy graded-sigma absorbers ("pml"/"absorber"), dispersive/conductive/nonlinear/anisotropic/modulated media, field shutoff, multi-source normalization, non-Box density regions, and tiled plane/flux/mode-seeded objectives are rejected at prepare before any distributed allocation. (The CPML/stable-PML absorbing update is now a supported distributed-adjoint capability — see the Track E3 distributed CPML-trainable adjoint entry below — which supersedes the earlier envelope that rejected all absorbing boundaries)
  • Single-node one-process-per-GPU NCCL halo transport primitive (fdtd/distributed/nccl_transport.py, transport="nccl" shape): a rank-local transport launched by torchrun that exchanges contiguous Yee x-plane halos with its chain neighbours through torch.distributed.batch_isend_irecv (forward electric/magnetic and their transposed reverse-halo adjoint accumulations into preallocated staging with deterministic ordered add_), reduces a rank-local scalar with all_reduce, verifies cross-rank device homogeneity, and tears the process group down deterministically. Preflight fails fast on a missing/torchrun-mismatched launcher environment, a mismatched world size, non-Linux platforms, and an adopted process group whose world size, rank, or backend does not match the rank's expectation (rather than binding to a mismatched group; backend validation accepts a composite cuda:nccl device-backend spec and fails closed otherwise). A two-rank torchrun worker asserts bitwise halo round-trips, adjoint accumulation, ghost-zeroing, endpoint-ghost negative invariants, the matching-group adopt path, the shared-group teardown/vanished-group guard, and scalar all-reduce. The transport also exposes the engine-based coordinator primitives (exchange_electric/exchange_magnetic over the local engine's contiguous Yee planes on its compute stream, reduce_owned_energy via all_reduce, and a sized point-to-point gather_component_slabs that stitches each rank's owned x-slab onto rank 0 using the deterministic partition layouts). An end-to-end one-process-per-GPU transport="nccl" forward solve is now qualified on two ranks: each rank builds its own ShardEngine, the coordinator runs the serialized time loop over the NCCL transport, and rank 0 gathers the global full-field DFT (Ex/Ey/Ez) and matches an independent single-GPU reference at the same tolerances as the in-process CUDA P2P leg (rtol 5e-5 / atol 5e-6), verified by a two-rank torchrun conformance worker. A rank-death failure-matrix test confirms a dead peer surfaces as a bounded nonzero exit (launcher failure propagation / ProcessGroupNCCL watchdog timeout bounded by FDTDParallelConfig.timeout_s) rather than a hang. Without a torchrun launcher environment, transport="nccl" still raises the explicit launcher error from both DistributedFDTD and the public Simulation.prepare() path and never falls back to the in-process CUDA P2P transport (P2P monkeypatched to fail if reached). The NCCL forward path fails closed on monitors, coupled circuit/network/wire/port scenes, trainable density (adjoint), and field shutoff (each supported today only on transport="cuda_p2p"), and multi-node execution remains out of scope.
  • Qualification remains limited to two RTX A6000 GPUs connected by NV4 and a guarded real-valued subset: end-to-end NCCL is qualified for the two-rank standard forward field solve only (no NCCL monitor gather, adjoint, coupled circuit/network/wire, or shutoff, and no three/four-GPU or multi-node NCCL); no three/four-GPU or PCIe-only qualification, Nsight trace, distributed CPML-trainable adjoint or tiled-monitor adjoint seed scatter, peer-aware CUDA Graph, advanced source families, x periodic/Bloch/symmetry, nonlinear/full-off-diagonal media, or SIBC claim is made. The final CUDA 13 acceptance observed 1.97385x strong speedup for the 257^3 vacuum case and exact single/two-GPU diagnostics, while the 129 x 65 x 65 case remained below break-even; see the FDTD multi-GPU joint-solve guide for the complete support matrix, persistence contract, test checkpoints, and benchmark table
  • Reverse-halo (adjoint transpose) exchanges on the one-process-per-GPU NCCL transport (fdtd/distributed/nccl_transport.py): the transport now exposes the engine-based prepare_adjoint_staging / exchange_magnetic_adjoint / exchange_electric_adjoint coordinator primitives alongside the forward halos, so a future distributed reverse driver can express its transposed Yee x-halos through the same branch-free primitive contract the forward loop uses. Each reverse exchange ships the rank-local ghost adjoint plane to its chain neighbour and zeroes it (preserving the ghost-adjoint-zero invariant the fused reverse kernels rely on) while receiving the opposite neighbour's ghost adjoint plane into a preallocated per-kind staging plane and accumulating it into the owned edge cell/node with a deterministic ordered add_ (no atomics, fixed rank order), introducing no per-step allocation. A two-rank torchrun worker proves the reverse exchanges are the exact discrete transpose of the forward halos: it forms <A x, y> and <x, A^T y> for both the magnetic and electric halos (each inner product's two halves live on opposite ranks and are combined by an all_reduce), asserts bitwise equality (pure-copy halos ⇒ atol == 0), checks the ghost-zeroing invariant, and pins bitwise determinism across repeats; an env-gated falsification path that drops one accumulated owner makes the identity assertion fire on both halos (launcher asserts a nonzero exit), so the gate is not vacuous. The end-to-end one-process-per-GPU NCCL trainable-density reverse (per-rank distributed replay + reverse + grad_eps gather + rank-0 pullback) remains fail-closed (transport="nccl" trainable density still raises and points at transport="cuda_p2p"); this transport-level transpose contract is its verified foundation.
  • Opt-in per-rank step-rate instrumentation for distributed FDTD workers (fdtd/distributed/instrumentation.py, StepRateInstrument): an env-gated timing hook (WITWIN_FDTD_STEP_TIMING, off by default; WITWIN_FDTD_STEP_TIMING_DIR selects the output directory) that brackets a rank's time loop (loop_begin / step_begin / step_end / loop_end) and, at finalize, writes one machine-readable step_timing_rank{r}.json per rank (schema witwin.fdtd.step_timing/1: per-step wall mean/median/min/max/p95 in ms and aggregate steps-per-second, plus rank/world-size/device/step-count metadata). The disabled path is zero-cost: with the env var unset the bracket calls return immediately and never synchronize the device, so wrapping a production-shaped loop adds no per-step work — a unit test injects a counting synchronize stand-in and asserts zero synchronizations across a full disabled loop (and that no artifact is written), while the enabled path synchronizes exactly 2 + 2·steps times to make each recorded interval reflect completed GPU work rather than launch latency. The one-process-per-GPU NCCL forward worker runs the instrument as an opt-in collective pass after its parity solve (a no-op, byte-identical to the un-instrumented run, when the env var is unset), so an exclusive-GPU window can flip the env var and collect per-rank JSON; no wall-clock number is asserted by any test (only the zero-cost-off invariant and the JSON schema), and shared-GPU timing values are never recorded as claims.

  • Ensemble multi-GPU execution through public mw.MultiGPUExecution.ensemble(devices=..., placement=..., fail_fast=...) and mw.run_many(simulations, execution=...): a set of independent Simulation objects is distributed across CUDA devices and returns a submission-ordered ResultSequence (results[i] is the outcome of simulations[i]). A deterministic DevicePool leases the first free device in declaration order (one large solver per GPU by default) so no task runs on a non-leased device and no device is over-subscribed; each task prepares and runs in its leased device context rather than materializing a full scene on a coordinator GPU. A per-task memory-estimation preflight fails a task before it runs when it cannot fit any leased device rather than migrating after OOM. Failures are structured, order-preserving DistributedFailure entries carrying the task index, device, kind (capacity / runtime / cancelled) and original exception chain: with fail_fast=False other independent tasks still complete and no exception is swallowed, and there is no cross-task state or gradient bleed. RF N-port sweeps reuse the same executor — a PortSweep NetworkRunManifest expands into one independent single-active-port column per port, distributed over the pool and reassembled by the same matrix assembler into an identical ordered NetworkData matrix as serial execution, with per-column device provenance in Result.solver_stats["ensemble"]. ExecutionRecord carries per-task wall/device timing measurement hooks, but interpreting them as a task-level speedup is reserved for an exclusive-GPU window and is not asserted. Ensemble + trainable (adjoint through run_many), ensemble + FDTDParallelConfig joint solve on the same Simulation, SceneModule inputs, and two tasks sharing one Scene object fail explicitly

  • Single- or multi-frequency DFT extraction through frequency= or frequencies=[...]

  • Source temporal frequency (source_time.frequency) remains distinct from simulation / monitor extraction frequencies; Maxwell does not infer extraction frequencies implicitly

  • ADE-based electric and magnetic dispersive-material updates for Debye, Drude, and Lorentz media

  • Static isotropic or diagonal electric conductivity (sigma_e) in the time-domain update, folded into the per-component lossy-dielectric Ca/Cb coefficients via the standard semi-implicit trapezoidal scheme so simple lossy dielectrics no longer require a fitted Drude pole

  • Static magnetic conductivity Material(sigma_m=...) [Ohm/m], the magnetic dual of sigma_e: the magnetic conduction current sigma_m * H on Faraday's law folds semi-implicitly into the per-component H-update decay/curl coefficients Da = (1 - h)/(1 + h), Db = (dt/mu)/(1 + h) with h = 0.5 * sigma_m * dt / mu (the exact mirror of the sigma_e E-update fold), reducing to the lossless leapfrog where sigma_m = 0 and composing multiplicatively with the CPML split-field decay; a magnetically-lossy slab attenuates a plane wave at the analytic rate alpha = (omega/c) Im(sqrt(1 + i sigma_m/(omega mu0))) (within 2%), and a matched lossy layer satisfying the impedance-match condition sigma_m/mu0 = sigma_e/eps0 reflects below a same-thickness PML baseline (an unmatched electric-only layer of the same thickness reflects more). sigma_m folds into eps-independent H coefficients that the reverse replay already reads, so it is transparent to the FDTD adjoint (an eps design differentiates alongside a sigma_m structure); FDFD and Tidy3D export reject it (magnetically-lossy media are outside their model) rather than silently dropping it

  • Axis-aligned diagonal anisotropy for electric and magnetic material tensors on the Yee grid, plus native CUDA full-tensor electric-permittivity corrections for symmetric positive-definite Tensor3x3 media; a diagonal-anisotropic epsilon_tensor combines with electric ADE dispersive poles in the same material, the per-axis eps_Ex/Ey/Ez background feeding each axis's shared pole susceptibility so the effective per-axis permittivity is eps_inf_i + chi(omega), and symmetrically a diagonal-anisotropic mu_tensor combines with magnetic ADE dispersive poles, the per-axis mu_Hx/Hy/Hz background feeding each axis's shared magnetic pole so the effective per-axis permeability is mu_inf_i + chi_m(omega)

  • Nonuniform (GridSpec.custom) Yee grids run on the single native CUDA kernel path through per-axis primal/dual spacing arrays, so a uniform grid is just the constant-array special case (a GridSpec.custom built from a uniform scene's node masters reproduces the GridSpec.uniform run bitwise); covers all field-update variants (standard, CPML dense/compressed, Bloch, mixed Bloch+PML), physical-depth-graded CPML profiles with per-side layer thicknesses, per-face local-cell Mur deltas, physically placed sources and monitors via the float64 coordinate masters, and dt from the per-axis minimum spacing

  • The reverse-time FDTD adjoint is the exact transpose of the nonuniform forward stencils (each reverse term reuses the same per-axis spacing element as its forward counterpart), so design-region gradients work unchanged on graded meshes

  • Instantaneous isotropic electric Kerr nonlinearity with GPU-resident dynamic update coefficients

  • General instantaneous nonlinearity through a dedicated native CUDA coefficient kernel: when a chi2 or two-photon-absorption channel is present the per-step pre-pass recomposes both the decay and curl coefficients from eps_eff = eps_lin + eps0 * (chi2 * E_i + chi3 * |E|^2) and sigma = sigma_static + tpa_sigma * |E|^2 through the semi-implicit lossy scheme (collocating off-axis field components onto each Yee edge exactly like the Kerr path, and reproducing the static coefficients exactly when the nonlinear channels vanish); pure-chi3 scenes keep the existing curl-only Kerr fast path

  • When an instantaneous nonlinearity and electric ADE dispersion coexist (same or separate structures), the per-step ADE polarization-current subtraction reuses each Yee edge's field-dependent dynamic curl coefficient instead of the static inverse permittivity, so the dispersive current is divided by the same effective permittivity eps_eff the nonlinear displacement-current term used that step (identical to the linear dt / eps_lin subtraction wherever the nonlinear channels vanish); this makes chi2 second-harmonic generation phase-match correctly through the material's own n(w)/n(2w) dispersion

  • Automatic run length estimation with TimeConfig.auto(...)

  • Explicit run-step control with TimeConfig(time_steps=...)

  • Opt-in auto-shutoff early termination stops the forward loop once relative electric-field energy decays below Simulation.fdtd(..., shutoff=..., shutoff_check_interval=100) (default shutoff=0.0 disables it), with a settling/DFT/observer-aware floor so continuously driven CW and spectral runs are never truncated, plus a planned-window normalization restore so an early-stopped run's DFT/monitor spectra match the full-length run; Result.stats() reports shutoff, shutoff_triggered, shutoff_step, and steps_run

  • Automatic dt tightening for broadband source-time objects such as GaussianPulse and RickerWavelet, and for electric or magnetic dispersive material poles such as Drude, Debye, and Lorentz media

  • CPML absorber configuration through BoundarySpec.pml(...) plus typed Simulation.fdtd(...) config (absorber=..., cpml_config=...); the propagating-wave default is an impedance-matched cubic conductivity ramp (kappa=1, alpha=0, target reflection 1e-6) calibrated for the common 8-12-cell layers, while custom complex-frequency stretching and stablepml remain available for low-frequency or late-time-stability workloads

  • CPML auxiliary psi storage auto-selects between a dense fast path and slab-allocated low-memory storage, with cpml_config={"memory_mode": "auto"|"dense"|"slab"} and optional dense_memory_limit_mib tuning

  • Additional PML-face absorber variants selectable through Simulation.fdtd(..., absorber=...): "absorber" (adiabatic graded-conductivity layer with no auxiliary psi memory) and "stablepml" (a CPML profile tuned for late-time stability with a higher grading order and larger complex-frequency shift), alongside the existing "cpml" and "pml" options

  • First-order Mur absorbing boundary as a per-face BoundarySpec.mur() kind, applied by a native CUDA kernel after the electric update that updates persistent per-face boundary buffers in place (no per-step host arithmetic or allocation, so it is captured into the tail CUDA graph)

  • Non-absorbing FDTD boundary conditions: periodic, Bloch phase-shifted periodic, PEC, and PMC

  • Per-face FDTD boundary selection across pml, periodic, pec, pmc, and none, including mixed-axis combinations such as periodic-in-y plus PML-in-x/z

  • Grating-oriented PlaneWave TFSF slab forward stepping on any normal axis (the single PML axis with the two transverse axes Bloch), under both CW and broadband (GaussianPulse / RickerWavelet) source times, including explicit Bloch wavevectors and solver-resolved automatic Bloch phase from the incident CW plane wave

  • Mixed Bloch + CPML FDTD stepping and native CUDA adjoints support every single-PML-axis / two-Bloch-axes permutation (absorbing axis x, y, or z). The complex reverse propagates both Bloch phase wraps and CPML recursive state, and the any-axis grating TFSF reverse composes the same native specialization with its auxiliary-state pullback; material gradients are validated against central differences for non-z as well as z layouts

  • Any-axis / either-face domain symmetry (PEC/PMC image plane on the chosen low or high face) combined with absorbing boundaries through Scene(symmetry=...) with BoundarySpec.pml(...); point-like sources in the folded-away half are rejected with a clear error

  • Spectral window and normalization configuration through SpectralSampler(window=..., normalize_source=...)

  • Pulse-driven spectral extraction starts at the transient without steady-state apodization for GaussianPulse and RickerWavelet, while CW extraction still skips startup transients

  • Optional prepared execution via Simulation.prepare()

  • Optional full-field DFT output, including simultaneous accumulation of multiple target frequencies in one run

  • Single-frequency FDTD runs that do not request full-field DFT now return the last-step Yee fields instead of auto-enabling full-domain DFT work

  • Full-field FDTD DFT results stay in native PyTorch tensors through Simulation / Result, with NumPy conversion deferred to plotting, export, and validation boundaries; Result.stats() reports full-field and observer DFT sample counts alongside elapsed time, ms/step, and steps/s

  • Selective monitor/observer extraction for point and plane monitors

  • Simultaneous multi-frequency point / plane observer accumulation in one run

  • Point, plane, and flux monitor payloads stay torch-native through Result.monitor(...), including multi-frequency selection

  • First-class modal monitor and port results remain torch-native through Result.monitor(...), while the underlying raw plane payload remains available through Result.raw_monitor(...)

  • GPU-accelerated soft source injection for PointDipole, PlaneWave, GaussianBeam, and experimental ModeSource

  • Reverse-time FDTD adjoints for trainable material, density, and supported analytic-geometry inputs through Scene / SceneModule -> Simulation -> Result, including point/surface/current/field/mode sources, TFSF sources, electric conductivity, diagonal and supported full electric anisotropy, general instantaneous chi2/chi3/two-photon-absorption media, and electric plus magnetic Debye/Drude/Lorentz ADE state. Magnetic ADE carries both real and imaginary state under Bloch boundaries. Multiple sources retain independent waveform replay for multi-port objectives

  • Internal checkpoint/replay support for adjoint-enabled FDTD runs, including CPML auxiliary state replay, TFSF auxiliary-line replay, and Bloch real/imag field checkpoints

  • Adjoint gradient pullback from Yee-grid coefficients through Scene.compile_material_components() into trainable material-graph inputs, including exact per-axis attribution for diagonal anisotropy and native CUDA pullback of supported off-diagonal electric coupling; trainable geometry on the full tensor structure remains guarded because the off-diagonal coefficients have no geometry-gradient channel

  • Solver stats including time steps, dt, absorber, requested frequencies, per-frequency DFT sample counts, elapsed time, milliseconds per step, and steps per second

  • Native CUDA extension builds on Windows can discover and load the Visual Studio x64 build environment automatically for accelerated FDTD kernels

  • Native CUDA extension platform wheels include the packaged FDTD CUDA extension, so ordinary pip install witwin-maxwell users can load the accelerated FDTD runtime without compiling locally

  • Native CUDA extension builds resolve conda-distributed torch import libraries automatically, and WITWIN_MAXWELL_FDTD_CUDA_PREBUILT=1 loads an already-built extension from the configured build directory without invoking the build toolchain (required under profilers such as Nsight Systems)

  • Native CUDA CPML field updates skip full-volume coefficient reads when the decay/curl coefficient tensors are spatially uniform, detected automatically once per solve (about 1.4x faster forward stepping on homogeneous scenes)

  • Default-on CUDA-graph capture of the per-step FDTD work (Simulation.fdtd(..., cuda_graph=...), default True; pass cuda_graph=False to force eager stepping): the magnetic/electric Yee updates (with CPML) are captured as one graph, and the post-source tail (PEC clamp, Mur ABC, and a GPU-driven running DFT) as a second graph, collapsing per-step kernel-launch and host overhead. The running DFT is driven from a precomputed device weight table indexed by a device step counter (no per-step host arithmetic or host->device transfer), so it captures cleanly and stays bit-identical to the host path (single- and multi-frequency); this GPU table path now also drives eager (non-captured) runs whenever the table can be built, with the per-step host path kept only for the complex-field (split-field Bloch) DFT the table cannot express. Capture now extends past plain real-field scenes to every per-step-deterministic field update, each proven bit-exact against the eager path: linear electric and magnetic ADE dispersion (Debye/Drude/Lorentz eps-poles and mu-poles, whose pole buffers stay a fixed point at zero field through warmup), instantaneous nonlinearity (Kerr chi3, chi2, and two-photon absorption, whose dynamic curl/decay coefficients recompute in place from the live field with no host input), complex split-field Bloch scenes including the mixed Bloch+CPML layout (one PML axis, two Bloch axes, imaginary fields and their psi memory snapshotted alongside the real path), and reference-provider TFSF plane-wave scenes (plane_wave_axis_aligned / plane_wave_ref_x_ez plus the non-periodic normal-incidence slab, which read the device-resident auxiliary incident line at fixed integer indices while the host waveform is evaluated eagerly outside the captured block). The classes that stay eager decline for a stated reason rather than silently: the oblique interpolated-aux plane wave (plane_wave_aux) accumulates through non-deterministic cross-warp atomic adds, so bit-exact capture is impossible; host-waveform sources (the discrete-CW / analytic-beam / grating-CW providers and soft magnetic surface sources) evaluate the incident waveform on the host each step and inject it as a launch scalar a captured graph would freeze; and time-modulated scenes carry per-step host phase scalars. Byte-for-byte identical to the eager path when declined (the adjoint bridge runs its own checkpointed forward loop and never sets use_cuda_graph), and multi-GPU (parallel=) runs always keep eager stepping regardless of the default. Concurrent ensemble execution keeps eager stepping too: CUDA-graph capture is a process-global operation (one capture at a time, and PyTorch's default capture mode aborts synchronizing calls in every other thread), so any execute_plan / run_many / ensemble network sweep whose plan can run more than one task at a time suspends capture for the whole plan, while a plan limited to one task at a time keeps the graph default. Capture is bound to the solver's own device, so runs on a GPU other than the process default capture and replay correctly rather than recording an empty graph. Most effective in the launch-bound small/adaptive-grid regime (measured ~8.25x/step on an 18^3 launch-bound dispersive GridSpec.auto scene on an RTX 5080, 0.338 -> 0.041 ms/step, graph-on bit-exact, and negligible on large compute-bound uniform grids). On a plain uniform vacuum scene on an RTX A6000 the same shape is measured end to end (docs/assessments/cuda-graph-throughput-2026-07-22.json): +106.8% at 48^3, +2.70% at 64^3, +1.60% at 96^3, +0.94% at 128^3, +0.55% at 160^3, +0.12% at 288^3, with peak allocated memory +8.1% to +21.1%; eager stepping bottoms out at a CPU launch-bound floor of ~0.165 ms/step, so everything at or above 64^3 is GPU-bound and the graph gain there is under 3%; Result.stats()["cuda_graph_active"] / ["tail_graph_active"] report whether capture engaged

  • Differentiable FDTD is CUDA-only: preparation requires a CUDA scene and the packaged compiled extension, selects one internal native specialization, and fails before forward stepping when the capability is unavailable. There is no adjoint backend environment selector, analytic Torch reverse, autograd-VJP fallback, or CPU differentiation path

  • Native reverse specializations cover standard, CPML, conductive, nonlinear, anisotropic, Bloch, electric/magnetic ADE, mixed Bloch+CPML, TFSF, and grating-TFSF configurations. Python remains responsible only for checkpoint/replay orchestration, kernel launches, gradient ownership, and PyTorch autograd integration; per-cell reverse numerical operators execute in compiled CUDA kernels and accept strided tensor views

  • FDTD result stats include CPML auxiliary-memory mode and allocated-versus-dense psi byte counts

  • Support for odd grid sizes in Yee-component field outputs

  • The experimental multi-GPU FDTD joint-solve adjoint for a trainable Box MaterialRegion density now extends past the open/PEC standard path to the CPML absorbing update (this supersedes the earlier "any absorbing PML boundary rejected" envelope for the CPML/stable-PML families). A trainable-density Simulation.fdtd(..., parallel=..., absorber="cpml") (or "stablepml") scene reverses through the distributed bridge with psi-carrying forward replay half-steps and a psi-aware reverse loop (CPML electric phase, transposed magnetic halo, CPML magnetic phase, transposed electric halo, then the per-shard source-term eps gradient), carrying the twelve CPML psi memory cotangents seeded to zero and using no psi halo: the x-CPML regions are pinned to the outer shards (asserted at prepare) so every cross-interface curl coupling rides the existing Yee field halos through the adj_d folds. Two-GPU acceptance covers 1-vs-2-GPU objective (bit-identical) and gradient parity (rel ~1e-7), cross-shard interface finite differences, a load-bearing no-psi-halo confirmation (no-op'ing either field halo drives the parity gate red), and bitwise-reproducible gathered grad_eps. Only the legacy graded-sigma absorbers ("pml"/"absorber") stay rejected for a trainable distributed run, and dispersive/conductive/nonlinear/anisotropic/modulated media, field shutoff, multi-source normalization, non-Box density regions, and tiled plane/flux/mode-seeded objectives remain rejected at prepare before any distributed allocation
  • The DistributedFDTD constructor's static-capability guard fails closed on every trainable channel it cannot reverse, as defense in depth, even when the solver is constructed directly and the public Simulation validator is bypassed: trainable geometry/material-perturbation/circuit/RF-port parameters and a trainable Box density resolving to a graded-sigma absorber all raise at construction (before any hardware allocation) rather than run a forward whose backward the reverse-support guard would later refuse. The same rejections are enforced at the public Simulation.fdtd(..., parallel=...) boundary
  • Forward one-process distributed monitor gather assembles supported monitor payloads on result_device without materializing a global field. The tiled-plane seam ownership rule is owned-exclusive: each global x index belongs to exactly one shard's owned_global_slice, so each shard contributes only its owned strip (halo/ghost x cells cropped away by the owned-local slice) and the seam sample between two shards is assembled exactly once; the flux quadrature is recomputed on the result device from the merged owned components (cell-center weights derived from the reassembled global coordinates) and cropped to the physical domain, so no seam cell weight is double-counted. Point monitors and x-normal planes have a single owner; y/z-normal PlaneMonitor/FinitePlaneMonitor/FluxMonitor/ModeMonitor planes tile along the x split. A double-counted owned strip is rejected by the owned-slice overlap guard (with the strictly-increasing-coordinate guard as a second line). Closed-surface, diffraction, time-domain flux, non-point time, material (permittivity/medium), and breakdown monitors remain fail-closed

Result Handling

  • Structured field lookup through result.E.x, result.E.y, result.H.z, and frequency/symmetry-scoped result views from Result.at(...)
  • Monitor lookup through Result.monitor(...)
  • Raw monitor lookup through Result.raw_monitor(...)
  • Result.monitor(...) and Result.raw_monitor(...) reassemble first-class ClosedSurfaceMonitor payloads from the resolved finite face monitors, and finite-plane payloads expose cropped coordinates plus face metadata
  • Frequency selection on structured field and monitor views through Result.at(frequency=...) or Result.at(freq_index=...)
  • Structured material lookup through result.materials.eps.scalar, result.materials.eps.x, and result.materials.mu.z
  • Low-level tensor escape hatches through Result.tensor(...) and Result.material(...)
  • Optional symmetry-expanded field and material access via Result.at(expand_symmetry=True)
  • Summary metadata through Result.stats()
  • Field and material plotting through Result.plot.field(...) and Result.plot.material(...)
  • Serialized save via Result.save(...)
  • Access to raw backend output through Result.raw_output

Postprocessing

  • maxwell.postprocess.PlanarEquivalentCurrents for planar Huygens-surface current data
  • maxwell.postprocess.EquivalentCurrentsSurface for multi-plane or closed-surface Huygens current collections
  • maxwell.postprocess.equivalent_surface_currents_from_fields(...) to build equivalent currents from tangential E/H plane fields
  • maxwell.postprocess.equivalent_surface_currents_from_monitor(...) to bridge a plane or first-class ClosedSurfaceMonitor on Result directly into planar or multi-surface equivalent currents
  • maxwell.postprocess.equivalent_surface_currents_from_monitors(...) to assemble multi-plane or closed-surface Huygens currents directly from multiple PlaneMonitor outputs, with shared or per-monitor tangential cropping for finite closed surfaces
  • maxwell.postprocess.SurfaceEquivalentCurrents and maxwell.postprocess.equivalent_surface_currents_from_surface_samples(points, normals, areas, E, H, ...) build equivalent currents on a genuinely curved (arbitrary-normal) Huygens surface -- a sphere, ellipsoid, or any voxelized/staircased closed surface -- forming J = n_hat x H, M = -n_hat x E per quadrature point with its own outward normal, then feed the same Stratton-Chu / NF2FF propagators as the axis-aligned box-face path (a spherical Huygens surface reproduces a Hertzian dipole far field to < 1e-3 relative L2 error with radius-independent surface invariance)
  • maxwell.postprocess.StrattonChuPropagator for exact near-field propagation from planar equivalent currents
  • StrattonChuPropagator defaults to CUDA and requires an explicit device="cpu" override for CPU postprocessing
  • maxwell.postprocess.NearFieldFarFieldTransformer for planar near-field to far-field projection
  • Background-aware closed-surface Huygens NF2FF: a box embedded in a homogeneous non-vacuum exterior auto-samples the exterior material just outside every face and radiates its equivalent currents into that medium instead of vacuum, using the background wavenumber k_b = n_b * omega / c (n_b = sqrt(eps_r*mu_r)) and intrinsic impedance eta_b = eta0 * sqrt(mu_r/eps_r), with the near-field Stratton-Chu kernel scaling i*omega*eps -> i*omega*eps0*eps_r and i*omega*mu -> i*omega*mu0*mu_r; the vacuum path (n_b = 1) stays bit-identical, and a layered-exterior RCS matches the analytic reference within about 4% while the former vacuum kernel distorts it by more than 15%
  • Postprocessing outputs stay torch-native end-to-end: equivalent currents, Stratton-Chu propagation, NFFT far fields, directivity, bistatic RCS, and flux-derived S-parameters preserve torch.Tensor results and autograd instead of detaching through NumPy
  • maxwell.postprocess.compute_bistatic_rcs(...) and transform_to_bistatic_rcs(...) for bistatic RCS derived from NFFT far fields and PlaneWave incident amplitude
  • maxwell.postprocess.compute_directivity(...) for radiation intensity, total radiated power, directivity, gain, radiation efficiency, and 3 dB beamwidth extraction from NFFT far fields
  • maxwell.postprocess.compute_s_parameters(...) for broadband S11 / S21 extraction from flux-monitor results using reference-run or explicit incident-power normalization
  • compute_s_parameters(incident_power="auto") now normalizes broadband (pulsed GaussianPulse / Ricker) PlaneWave sources without a second reference run: it reconstructs the injected incident spectrum by reproducing the monitor's own windowed running DFT of the known waveform and divides by the forward flux |E_inc(f)|^2 / (2*eta0) * area per frequency, taking the illuminated cross-section from the TFSF-aperture area (box mode) or the monitor plane (soft source / full-width slab), so a single pulsed run yields per-frequency-normalized S-parameters (matching the measured incident flux within about 5-7% on a well-resolved grid)
  • Experimental maxwell.postprocess.compute_mode_overlap(...) for plane-monitor or first-class ModeMonitor / ModePort modal decomposition against an axis-aligned scalar ModeSource reference, returning forward/backward modal amplitudes and power fractions
  • The modal S-parameter pipeline (the compute_mode_overlap complex forward-amplitude extraction driving the solved guided dispersion beta(f)) reconstructs an all-pass ring-resonator S21 spectrum against the closed-form coupled-mode transfer function H = (t - a e^{i phi}) / (1 - t a e^{i phi}) to within 3% in magnitude and phase across a full free spectral range, including on resonance (tests/postprocess/scattering/test_ring_resonator_s21.py)
  • Experimental maxwell.postprocess.purcell_factor(structured, vacuum, name) for the vacuum-normalized Purcell factor / local density of states of a DipoleEmissionMonitor, validated under tests/monitors/emission/test_dipole_emission_monitor.py against the analytic dipole-near-PEC-mirror curve (perpendicular orientation) and unity vacuum emission
  • Experimental maxwell.postprocess.compute_diffraction_orders(...) and enumerate_diffraction_orders(...) for grating diffraction-order decomposition on a periodic unit-cell plane; regression-tested under tests/monitors/diffraction/test_diffraction_monitor.py for order-cutoff / light-cone physics, Parseval consistency between the summed order powers and the direct plane-flux integral, specular dominance at normal incidence, grating-driven redistribution of transmitted power into the +/-1 orders, and cross-validation of the transmission/reflection coefficients against the independent Yee-grid FluxMonitor primitive (with an xfail tripwire documenting that absolute T + R = 1 energy conservation is not recoverable under the FDTD spectral-monitor CW normalization)
  • End-to-end validation coverage for first-class finite closed-surface Huygens workflows in homogeneous free space: rectangular-box dipole near-field reconstruction/directivity, non-rectangular orthogonal-surface dipole directivity, and TFSF Rayleigh-sphere bistatic RCS are regression-tested against analytic or direct-FDTD references under tests/validation/physics/test_postprocess_end_to_end_validation.py
  • First-class closed-surface postprocessing now rejects layered or otherwise inhomogeneous exterior media explicitly instead of silently applying a free-space Green's function outside its validity range

SAR & Exposure Analysis (Phase 0+1)

  • Material(mass_density=...) adds a tissue mass density in kg/m^3 (a positive scalar or a per-cell 3D grid spanning the structure Box extent). None (the default) excludes the material from SAR; a SAR request covering an electrically lossy material without a density fails closed. Convenience flags Material.has_mass_density and Material.is_electrically_lossy.
  • Scene.compile_mass_density() rasterizes tissue mass onto material cell centers (the node grid) through the same soft-occupancy provenance the EM material compiler uses, returning CompiledMassDensity(rho_cell, occupancy, tissue_id, cell_volume, tissue_names); rho_cell is the occupancy-weighted effective density (true tissue density = rho_cell / occupancy) and the discrete tissue_id labels are stop-grad.
  • Result.sar(monitor, averaging=..., normalization=...) reduces a PowerLossMonitor's absorbed-power density and the tissue mass model to point SAR. It is a pure result-domain reduction: it never runs a solver and fails explicitly when the named monitor, the frequency-domain fields, the tissue densities, or an electric volumetric loss channel are missing.
  • Point SAR consumes the shared PowerLossData electric-loss volumetric density (conduction / electric_dispersion / nonlinear channels) colocated from the staggered Yee electric edges to material cell centers by a power-conserving half-weight scatter, so the region volume integral of absorbed power closes exactly against the edge-integrated channel power. SAR is reported per channel and as a total, in W/kg, with NaN at cells whose tissue fill is below a documented occupancy epsilon (no air-mass makeup, no zero-fill).
  • SARResult carries typed point-SAR fields, per-tissue statistics (absorbed power, mean/max SAR, tissue mass, cell count), coordinates, frequencies, the validity mass model, and full provenance (units, field/phasor convention, channel list, collocation description, grid hash, occupancy epsilon, averaging profile). Point SAR, the effective mass density, and per-tissue statistics stay in autograd (no hidden detach); discrete tissue/validity masks are stop-grad by design.
  • SARAveraging(mass, profile, connectivity, boundary_policy, min_tissue_fraction) is the validated, serializable averaging request; the single versioned profile is "cubical-prefix-v1" (IEEE/IEC-inspired but explicitly not certified). PowerNormalization.source/.accepted_power/.input_power describe multiplicative power scaling (source-amplitude scaling is available now; accepted/input-power scaling resolves in the normalization stage). SARResult.save(...) / SARResult.load(...) persist the SAR payload as typed data.

Mass-averaged SAR (Phase 2)

  • Result.sar(monitor, averaging=SARAveraging(mass=(1e-3, 10e-3))) computes local mass-averaged SAR under the "cubical-prefix-v1" profile: for each target averaging mass and every candidate tissue center cell, the smallest symmetric axis-aligned cube whose enclosed tissue mass reaches the target is selected, and the averaged SAR is enclosed_absorbed_power / enclosed_mass recorded with the ACTUAL enclosed mass. Enclosed cube quantities are read in O(1) from 3D inclusive prefix sums (integral images), so the search costs one lookup per half-width rather than an O(k^3) window sum.
  • Validity rules are enforced and reported as a mask (NaN, never silently padded): boundary_policy="strict-interior" marks any center whose cube would clip the monitor region invalid; a cube whose tissue fill fraction is below min_tissue_fraction (default 0.1, the "no air-mass makeup" rule) is invalid; a target mass unreachable within the interior cube is invalid; averaged SAR is reported only at tissue-bearing center cells.
  • SARResult.averaged_sar(mass) returns the [F, nx, ny, nz] mass-averaged SAR field and SARResult.averaging_masses lists the computed target masses. SARResult.peak(mass) returns a typed SARPeak (per-frequency peak value, center index and physical position, actual enclosed mass, cube half-width in cells, and physical cube edge length per axis); peak(...) and averaged_sar(...) fail closed when no averaging request was made or an unrequested mass is asked for.
  • Fixed-window mass-averaged SAR (enclosed power over enclosed mass at the selected cube) stays in autograd; only the discrete half-width search and validity masks are stop-grad. The argmax peak center is not differentiable by design (the reported peak value still carries the field's graph); a differentiable soft_peak surrogate is a later stage.
  • "cubical-prefix-v1" differs from IEEE/IEC 62704-1 (documented, not certified): the averaging cube is a symmetric index-space cube with no cube-face expansion asymmetry, and there is no tissue-connectivity flood fill (connectivity="cube" only; other values raise). On a nonuniform grid the cube is symmetric in index space rather than a physical cube. The averaged fields and peaks ride the existing SARResult.save/load typed serialization.

Normalization, multi-source combination, and differentiable peak (Phase 3 + Phase 4 slice)

  • PowerNormalization.accepted_power(port, watts) now resolves against the run: it reads the named port's measured accepted power at each SAR frequency and scales SAR by watts / measured (a per-frequency scale that keeps the port's autograd graph). Fails closed when the port is absent, when a SAR frequency is not present in the port spectrum, or when the measured accepted power is not strictly positive. PowerNormalization.source(amplitude) scales by amplitude**2 (exact square law). PowerNormalization.input_power(watts) fails closed with a clear message because this build exposes no total injected source-power diagnostic.
  • combine_coherent_sar(results, monitor=..., weights=..., averaging=..., normalization=...) coherently combines same-frequency source runs by summing their complex electric spectra (with optional complex per-run weights) before the loss is formed, then performs a single SAR reduction, so field interference is captured exactly (in-phase sources add, opposite-phase sources cancel). Differentiable in the per-run fields; validates identical monitor, grid, and frequencies.
  • combine_incoherent_sar(sar_results, averaging=...) combines source runs in the power domain by summing their absorbed-power densities, re-forming point SAR as power / rho, and recomputing per-tissue statistics; pass averaging to recompute cubical-prefix-v1 mass-averaged SAR and peaks on the combined power. Validates identical grid hash, tissue model, frequencies, field convention, loss channels, and power normalization across operands and fails closed on any mismatch.
  • SARResult.soft_peak(temperature, mass=...) is a differentiable, explicitly non-regulatory peak surrogate: a temperature-weighted softmax over the valid mass-averaged cube centers that approaches the hard peak(...) as the temperature drops while staying in autograd (unlike the argmax peak). The mass argument is optional when a single averaging mass was computed.
  • SAR reduction provenance now records the resolved power_scale (scalar or per-frequency), the per-axis dual cell sizes, and, for combined results, a combination descriptor (mode coherent/incoherent, operand count, coherent weights). Point SAR, fixed-cube averaged SAR, region statistics, and normalization are validated as differentiable in the field amplitude and in a per-cell density grid by central-difference gradient gates.

Incident power density monitor

  • IncidentPowerDensityMonitor(name, axis=, position=, frequencies=, normal_direction=, spatial_average=) is a plane monitor variant for exposure incident power density. It carries the tangential E/H fields required to form the time-averaged normal Poynting component S.n = 0.5*Re((E x conj(H)).n_hat) on the plane, reusing the same power-conserving plane-flux machinery as FluxMonitor. It reports whatever field is present at its plane; place it in the incident region (ahead of any scatterer) to read exposure incident power density.
  • Result.incident_power_density(monitor, spatial_average=...) is a pure result-domain reduction returning a typed IncidentPowerDensity: the signed per-cell normal_poynting, its magnitude power_density = |S.n| in W/m^2, and the plane-integrated flux (W) that is identically equal to the co-located FluxMonitor integral (both use the shared plane_normal_poynting helper). Fails closed when the named monitor is not an IncidentPowerDensityMonitor. Plane-wave gates verify |S| = |E|^2/(2*eta) exactly.
  • spatial_average (a moving-window area in m^2, e.g. 4e-4 for a 4 cm^2-class window) requests an area-weighted moving-window average of |S.n| under the versioned spatial-average-v1 window: an axis-aligned square of side sqrt(area) centred on each cell, computed in O(N) from 2D inclusive prefix sums, truncated to in-domain cells at the plane edge (edge_policy="truncate"). The window is an engineering convenience for exposure reporting and is explicitly not a certified standards averaging area; its area, side, shape, edge policy, and certified: False are recorded in the result provenance.

Canonical phantom benchmark family

  • A redistributable canonical-geometry SAR benchmark family under benchmark/scenes/sar/ (uniform_lossy_cube, layered_slab, one_gram_cube, antenna_near_phantom), each exposing a build_scene(...) builder plus a ScenarioDefinition. Tissue dielectric/mass values are the published-class canonical numbers (900 MHz) documented in _tissue.py; no licensed anatomical model is distributed. Gates: the synthetic one_gram_cube is a hand-computable 1 g average (27 cells weigh exactly 1 g; averaged SAR equals the analytic point SAR); the plane-wave uniform_lossy_cube and layered_slab runs check exact absorbed-power conservation closure, monotone mass averaging (10 g peak <= 1 g peak <= point peak), the peak sitting in the front skin layer (not low-loss fat), and golden regression anchors.
  • Recorded design blocker: antenna_near_phantom (a driven half-wave dipole near a tissue block) cannot run in the current build because the FDTD port machinery fails closed on a conductive (lossy) background — both the thin-wire runtime and the lumped-port runtime require a conductance-aware update coefficient that does not yet exist. The scene ships with its fail-closed behaviour gated; the accepted-power -> SAR -> 1 g/10 g -> save/load chain is validated on synthetic ports and on the plane-wave phantom scenes.

Phantom convergence, conservation, and RESULTS integration (H3b)

  • benchmark.scenes.sar.layered_slab.build_conservation_scene(dx=..., device=...) is a periodic-transverse variant of the layered slab for a clean 1-D power-conservation balance. Under normal incidence on an infinite planar slab the physically correct transverse boundary is periodic (not PML), which makes the field transverse-uniform so the net power balance reduces to two z-planes: P_absorbed = flux(z_in, +z) - flux(z_out, +z). The absorbed power measured this way (surface E x H) is independent of the volume sigma |E|^2 integral that SAR is built from, so their agreement is a wave-level conservation check rather than a self-consistency identity. The shipped build_scene (PML on every face, golden-anchored) is unchanged.
  • A three-grid convergence / conservation suite (tests/sar/test_phantom_convergence.py) verifies the layered-slab surface-vs-volume absorbed-power closure (residual ~17% at dx=4 mm, shrinking monotonically to ~12.5% at dx=3 mm) and records the pointwise peak 1 g / 10 g SAR at three grids. The peak carries documented grid sensitivity (a source-normalized plane wave delivers a grid-dependent incident power density, and the peak is a pointwise max over a thin under-resolved skin layer), so the convergent, gate-bearing observable is the conservation closure, not the pointwise peak.
  • python -m benchmark sar [scenes...] runs the SAR phantom exposure validation harness (benchmark/sar_validation.py): it drives the phantom family through the public Scene -> Simulation -> Result path and writes a ## SAR exposure validation section to benchmark/RESULTS.md plus a machine-readable JSON artifact per scene under docs/assessments/sar-phantom-validation/. Each row self-labels its gate class with the verbatim docs/reference/gate-classification.md taxonomy: sar/layered_slab is the binding wave-level conservation gate, sar/one_gram_cube is an analytic-identity, sar/uniform_lossy_cube is a supporting self-consistency analytic-identity, and sar/antenna_near_phantom is reported blocked. No external reference-solver run backs this family (every row analytic-only); the conservation law and analytic values are the binding first-line references.

Tidy3D Adapter

  • Scene.to_tidy3d(frequencies=..., run_time=..., **kwargs) converts a maxwell Scene to a tidy3d.Simulation for cloud execution and cross-validation
  • Adapter module at maxwell/adapters/tidy3d.py with optional tidy3d import
  • GaussianPulse export reproduces Maxwell's delayed real waveform exactly: the envelope delay maps to Tidy3D's offset, DC removal is disabled, and the carrier phase is converted as 2*pi*f*delay - phase - pi/2 to account for Tidy3D's Re[i exp(i*phase) exp(-i*omega*t)] convention. CW export supplies a deterministic fwidth = 0.1 * frequency turn-on bandwidth so the Tidy3D source has a stable finite ramp that is tracked by benchmark cache keys
  • PlaneWave export places the source inside the physical interior and uses Tidy3D's infinite-plane aperture convention instead of a finite rectangular source patch
  • Point and plane field-monitor export preserves the exact public fields=(...) component selection instead of accepting Tidy3D's default six-component payload
  • TFSF export preserves physical incident-field amplitude: Maxwell's SourceTime.amplitude is a V/m electric field, while Tidy3D's TFSF has a fixed 1 W/um^2 incident normalization, so the adapter converts through the corresponding sqrt(2/(c*eps0)) V/um field rather than treating the two amplitudes as dimensionless equivalents
  • Supported mappings: Domain, GridSpec, BoundarySpec (uniform or per-face PML/periodic/PEC/PMC/Bloch export), Structure (Box/Sphere/Cylinder/Cone), spatially-uniform unfiltered MaterialRegion with mu_r = 1, Material (simple conductive / Drude / Lorentz / Debye / mixed PoleResidue with mu_r = 1, plus Material.pec() mapped to Tidy3D's dedicated PECMedium, Medium2D/Graphene sheets mapped to Tidy3D's Medium2D, and LossyMetalMedium mapped to Tidy3D's LossyMetalMedium), PointDipole, PlaneWave, GaussianBeam, ModePort (through its resolved ModeSource/ModeMonitor), PointMonitor, PlaneMonitor, FinitePlaneMonitor, FluxMonitor, and translation-invariant symmetry
  • Cone export preserves the shared primitive's apex-based placement: the Tidy3D tapered cylinder is centered half a height above the apex, expands toward the positive axis from a top-reference radius, and rotated cones fall back to an exact triangle-mesh export
  • Explicit Bloch-boundary export converts Maxwell's physical wavevector components in radians/metre to Tidy3D's dimensionless k * period / (2*pi) convention independently on each axis; unresolved automatic Bloch metadata is still rejected rather than exported with an ambiguous period
  • A dispersive Material (Debye / Drude / Lorentz / mixed poles) that also carries a static electric conductivity sigma_e exports as a single Tidy3D PoleResidue: the dispersion is taken from the equivalent pole-residue of the base dispersive medium and the conductivity is folded in as a zero-frequency pole with residue sigma_e / (2 * eps0), which reproduces the physical loss term +i * sigma_e / (omega * eps0) under Tidy3D's e^{-i*omega*t} convention (validated against Material.relative_permittivity and the analytic conductivity term via eps_model)
  • Electric conductivity export uses the correct Tidy3D micrometre units: the SI sigma_e [S/m] is divided by the metre-to-Tidy3D length scale so Medium.conductivity [S/um] yields the physical eps'' = sigma_e / (omega * eps0)
  • Instantaneous Kerr (chi3) and two-photon-absorption nonlinearity export onto Tidy3D's chi3-family NonlinearSpec: kerr_chi3 / NonlinearSusceptibility(chi3=...) map to td.NonlinearSusceptibility and TwoPhotonAbsorption(beta, n0) maps to td.TwoPhotonAbsorption, attached via the medium's nonlinear_spec (also on the dispersive Drude/Lorentz/Debye/PoleResidue export so a same-material nonlinear + dispersive, optionally conductive, medium exports as one Tidy3D medium). Both frameworks share P_NL = eps0 * chi3 * |E|^2 * E, so chi3 [m^2/V^2] scales by length_scale^2 to Tidy3D's [um^2/V^2] (verified via Tidy3D's own n2 = 3/(4 n0^2 eps0 c0) chi3 relation reproducing the SI n2 in [um^2/W]), TPA beta [m/W] scales by length_scale to [um/W], and n0 (defaulting to sqrt(eps_r)) is forwarded explicitly. Second-order (chi2 / SHG) susceptibility is rejected with a physics-worded error because Tidy3D's public nonlinear API is the chi3/Kerr/TPA family only
  • Anisotropic Material export: an axis-aligned DiagonalTensor3 epsilon_tensor maps to a Tidy3D AnisotropicMedium of three per-axis isotropic media, carrying per-axis conductivity (scalar sigma_e or a DiagonalTensor3 sigma_e_tensor, divided by the metre-to-Tidy3D length scale) and isotropic electric dispersion (the shared Debye/Drude/Lorentz/mixed poles reused per axis over the per-axis eps_inf background), verified against the physical per-axis permittivity eps_axis + i * sigma_axis / (omega * eps0) via AnisotropicMedium.eps_diagonal; a symmetric-positive-definite full off-diagonal Tensor3x3 epsilon_tensor maps row-for-row to a non-dispersive FullyAnisotropicMedium with a scaled diagonal conductivity tensor. A magnetic mu_tensor and a full off-diagonal tensor combined with dispersive poles are rejected with physics-worded errors (Tidy3D anisotropic media are electric-only with mu_r = 1, and FullyAnisotropicMedium is strictly non-dispersive)
  • Medium2D sheet export: a zero-thickness Medium2D maps to a Tidy3D Medium2D whose two tangential surface media reproduce Medium2D.sheet_conductivity(omega) exactly. A surface conductance is in siemens [S], unit-system independent, so it is NOT length-scaled: a static sheet maps to td.Medium(conductivity=sigma_s); the Graphene intraband Kubo channel (a single Drude sheet term weight/(rate - i*omega)) maps to a td.Drude with plasma_frequency = sqrt(weight/eps0), gamma = rate; and any combination (static + Drude + fitted interband Lorentz sheet terms) folds into one td.PoleResidue. The exported Medium2D.sigma_model(omega) is validated to equal sheet_conductivity_at_freq for static, Graphene intraband (inductive Im(sigma) > 0), and Graphene interband (below-edge capacitive Im(sigma) < 0) sheets
  • LossyMetalMedium export maps to Tidy3D's LossyMetalMedium (surface-impedance boundary condition): the SI bulk conductivity [S/m] is divided by the metre-to-Tidy3D length scale to [S/um] (the volumetric sigma_e convention), so the exported Leontovich surface impedance Z_s(omega) = (1 - i) * sqrt(omega*mu0/(2*sigma)) [ohm] matches LossyMetalMedium.surface_impedance (validated against the real Tidy3D fit, including the inductive Im(Z_s) < 0 sign). Tidy3D vector-fits Z_s(omega) over a required frequency_range, so the export frequencies must be passed to Scene.to_tidy3d() (a single operating frequency is widened into a non-degenerate fit band); omitting them raises a clear ValueError
  • Time-modulated Material export: a scalar-depth ModulationSpec(frequency, amplitude, phase) on a non-dispersive isotropic medium maps to a Tidy3D Medium(modulation_spec=ModulationSpec(permittivity=SpaceTimeModulation(...))). maxwell's dimensionless depth is converted to Tidy3D's absolute permittivity deviation via A_s = eps_static * amplitude (Tidy3D's SpaceModulation amplitude is an absolute delta_eps, not a fraction), the modulation frequency becomes the ContinuousWaveTimeModulation.freq0, and the phase is negated (phi_s = -phase) because Tidy3D's e^{-i*omega*t} time factor is the conjugate of maxwell's +phase convention; the exported delta_eps(t) = eps_static * amplitude * cos(2*pi*f*t + phase) is validated against the real Tidy3D amp_time(t)/amp_space at multiple times (and the depth < 0.5 cap keeps the modulated permittivity positive for Tidy3D's own validator). A modulated Material that also carries dispersion or an instantaneous nonlinearity is rejected with a physics-worded error (Tidy3D modulates only the non-dispersive eps_inf/conductivity, whereas maxwell folds the same per-step factor through the dispersive polarization current and the Kerr/chi2/TPA coefficient), as is a spatially-varying (3D-tensor) amplitude/phase profile (defined relative to the owning structure's Box, not resolvable into Tidy3D absolute coordinates at material-conversion time)
  • Custom dispersive pole and PerturbationMedium export: a spatially-uniform custom pole (CustomDebyePole/CustomDrudePole/CustomLorentzPole) lowers to its scalar reference pole (the peak equals the uniform value, so the lowering is exact) and exports through the ordinary Drude/Lorentz/Debye/PoleResidue path; a spatially-uniform PerturbationMedium lowers to a constant background shift eps_base + eps_sensitivity * value applied to the scalar eps_r (or to each DiagonalTensor3 principal axis), carrying the base's poles/conductivity/nonlinearity/modulation through unchanged (validated so the exported eps_model reproduces the equivalent scalar-pole Material.relative_permittivity, and the perturbed background shift is exactly eps_sensitivity * value against the unperturbed export). A spatially-varying custom pole or perturbation has no homogeneous Tidy3D equivalent (its per-cell grid is defined relative to the owning structure's Box and cannot be resolved into Tidy3D absolute coordinates at material-conversion time, which would need a CustomPoleResidue/CustomMedium on a SpatialDataArray) and is rejected with a physics-worded error
  • Geometry export beyond the analytic primitives: Ellipsoid (non-spherical), Torus, Pyramid, Prism, and HollowBox tessellate to a Tidy3D TriangleMesh built from the primitive's own to_mesh() surface (vertices carry the structure position/rotation and scale by the metre-to-Tidy3D length factor; the integer face table is length-scale-invariant), so a primitive with no analytic Tidy3D counterpart round-trips geometrically instead of raising. An isotropic Ellipsoid still exports as Tidy3D's exact analytic Sphere. A PolySlab that is un-rotated, origin-centred, and vertical (sidewall_angle = 0) maps to the faithful Tidy3D PolySlab primitive (polygon vertices + axial slab_bounds + axis + reference_plane, all length-scaled); a tapered, rotated, or offset slab is baked into a TriangleMesh (its to_mesh already applies the taper and transform) so no geometry is silently dropped
  • Source export beyond point/plane/beam sources: ModeSource maps to Tidy3D's ModeSource with a ModeSpec that requests both polarization families through mode_index and applies a ModeSortSpec derived from the requested tangential polarization fraction; AstigmaticGaussianBeam maps to td.AstigmaticGaussianBeam at the same launch plane and waist distances; UniformCurrentSource, CustomFieldSource, and CustomCurrentSource carry the required SI-to-micrometre amplitude scaling; and a PlaneWave/GaussianBeam injected as TFSF(...) maps to Tidy3D's dedicated td.TFSF
  • Custom source export: CustomFieldSource and CustomCurrentSource map to Tidy3D's CustomFieldSource/CustomCurrentSource with a FieldDataset of ScalarFieldDataArray components on coordinates relative to the source center and a single injection frequency (taken from the attached source_time), matching Tidy3D's convention; field sources keep the tangential E/H component names, while current sources reuse Tidy3D's E/H dataset slots for J/M (Jx..Jz -> Ex..Ez, Mx..Mz -> Hx..Hz), validated against the real Tidy3D validators (single-frequency, interpolatable) and by reconstructing the maxwell absolute coordinates from center + relative offset
  • Nonuniform grid export: a GridSpec.custom or GridSpec.auto scene grid exports as per-axis Tidy3D CustomGridBoundaries carrying the exact Yee node coordinates (metre coordinates scaled by the metre-to-Tidy3D length factor), so the Tidy3D simulation discretizes on the same grid maxwell uses and the two solvers compare cell-for-cell instead of across two independent meshers. A GridSpec.auto grid is first resolved through maxwell's own mesher (resolve_auto_grid), which honours the index-aware step targets, override_structures, and layer_refinement that a lossy parameter map onto Tidy3D's AutoGrid would drop; the resolved nodes reproduce the maxwell mesh exactly. Uniform / per-axis-anisotropic grids continue to export as td.UniformGrid. Verified against the real Tidy3D Simulation.grid.boundaries: the in-domain Yee boundaries equal the exported node coordinates (Tidy3D adds PML cells only outside the domain)
  • Monitor export beyond point/plane/flux monitors: ModeMonitor maps to Tidy3D's dedicated ModeMonitor with a ModeSpec that resolves both polarization families and applies the requested polarization-fraction ordering through ModeSortSpec; DiffractionMonitor maps to td.DiffractionMonitor with td.inf transverse extent; PermittivityMonitor maps to td.PermittivityMonitor; and time-domain field/flux monitors map without requiring export frequencies. Maxwell time-monitor start/stop step indices are converted to physical seconds using the exported scene's Courant step, while interval remains a sample-step stride

GDS Adapter

  • maxwell.adapters.gds.from_gds(path, layer=..., bounds=..., ...) imports GDS layout polygons as PolySlab geometries with cell selection (explicit name or unique top-level cell), layer/datatype filtering, recursive reference flattening, extrusion parameters (axis, bounds, sidewall_angle, reference_plane), and unit conversion from the file's user unit to metres (overridable via length_scale)
  • maxwell.adapters.gds.to_gds_file(geometries, path, ...) writes PolySlab / ComplexPolySlab cross-sections as GDS polygons on a chosen layer/datatype (multi-loop geometry is even-odd merged so holes survive as keyhole cuts), with the library unit derived from length_scale (micrometre user units by default)
  • Adapter module at maxwell/adapters/gds.py with optional gdstk import

Tidy3D Benchmarking

  • benchmark/ package as the unified Maxwell-vs-Tidy3D benchmarking entrypoint
  • Benchmark material/source diagnostics sample the actual Tidy3D-exported geometry envelope on Maxwell's grid and plot its XOR against the public geometry, rather than labeling a cloned Maxwell material grid as Tidy3D; incident-power normalization uses boundary-matched PML or transverse-periodic vacuum references and scales physical FluxData by the plane-wave source amplitude squared
  • One-file-per-scenario definitions organized under benchmark/scenes/dipole/, benchmark/scenes/planewave/, and benchmark/scenes/media/, with the campaign scenarios in benchmark/scenes/planned.py and the per-family feature-coverage scenarios in benchmark/scenes/coverage/
  • Predefined ~128^3-grid benchmark scenarios covering vacuum dipoles, plane-wave slab/sphere scattering, dispersive resonators, multi-dielectric scenes, and dipole_dielectric_sphere
  • 53-scenario feature-coverage benchmark suite under benchmark/scenes/coverage/ giving every directly comparable Tidy3D-exportable feature at least one load-bearing scenario, exported as COVERAGE_SCENARIOS and registered into the same duplicate-checked SCENARIOS table:
    • coverage/sources.py: gaussian_beam_normal, gaussian_beam_defocused (focused/defocused GaussianBeam), planewave_cw, dipole_cw_vacuum (CWtd.ContinuousWave), tfsf_vacuum, tfsf_dielectric_sphere (TFSF total/scattered-field split), mode_source_higher_order (mode_index=1), magnetic_current_vacuum (magnetic volume current → td.MagneticDipole/H* mapping), and ricker_axis_x_anisotropic (Ricker waveform, negative-x injection, and eps_zz)
    • coverage/media.py: drude_slab, lorentz_slab, lorentz_two_pole_slab, drude_lorentz_slab (multi-family pole folding to PoleResidue), debye_sphere, sellmeier_sphere, diag_aniso_sphere, pec_sphere (curved-interface dispersion, anisotropy, and PEC), lossy_metal_slab_high_sigma (surface-impedance/SIBC at 100× conductivity), kerr_slab_strong, modulated_slab_phase, static_medium2d_sheet, material_region_slab, and dispersive_kerr_slab
    • coverage/boundaries.py: pml_thin, pml_slab_through (material-loaded absorber), periodic_slab, bloch_oblique_te (in-plane polarization at 35°), symmetry_pec_center, symmetry_pmc_center (translation-invariant Scene(symmetry=...) reduction), mixed_faces (per-face periodic/PEC/PML mix), and asymmetric_boundary_faces
    • coverage/grid_geometry.py: cylinder_scatter, cone_scatter, ellipsoid_scatter, pyramid_scatter, prism_scatter, hollow_box_scatter, polyslab_pentagon (analytic, mesh, and PolySlab geometry export paths), autogrid_slab, nonuniform_custom_grid, anisotropic_uniform_grid (auto, graded-custom, and per-axis uniform GridSpec), explicit_mesh_scatter, and autogrid_override_refinement
    • coverage/postprocess.py: rcs_pec_sphere, rcs_dielectric_box (bistatic RCS on curved and faceted scatterers), directivity_two_dipoles (array-factor directivity), mode_monitor_straight_wg, mode_monitor_two_planes (single-mode n_eff and modal propagation ratio), mode_port_straight_wg, diffraction_normal_orders (0th and ±1st order efficiency), point_monitor_probe, permittivity_monitor_slab, and time_monitor_vacuum
    • Point probes, two-plane mode monitors, ports, permittivity monitors, and time monitors contribute dedicated scalar observables instead of being incidental monitors beside a plane-field comparison
  • P3-media Tidy3D cross-validation scenarios under benchmark/scenes/media/ exercising the phase-3 material physics that has a Tidy3D export equivalent: debye_slab (Debye dispersion → td.Debye), sigma_e_drude_slab (Drude + static conductivity → one td.PoleResidue), anisotropic_slab (diagonal anisotropy → AnisotropicMedium), kerr_slab (Kerr χ³ → NonlinearSpec), modulated_slab (time modulation → Medium + ModulationSpec), and graphene_sheet (Graphene/Medium2D sheet → td.Medium2D)
  • Per-medium validation-coverage gate tests/validation/benchmark/test_media_validation_coverage.py backed by the benchmark/media_coverage.py registry: it discovers every public is_*/has_* capability flag on media.py (introspection, not a hardcoded class list) and fails if any capability lacks a declared validation path — a Tidy3D benchmark scenario, an FDFD cross-check, or a documented analytic-reference test — then verifies each claim (Tidy3D-path media must export through the adapter and reproduce Material.relative_permittivity for the clean-identity cases; non-equivalent fallback media must genuinely raise on export, proving the analytic fallback is justified). The per-capability path table is recorded in benchmark/RESULTS.md under ## Validation coverage
  • HDF5-based Tidy3D reference caching under benchmark/cache/
  • Benchmark cache validation keyed to the exported Tidy3D scene configuration, with explicit mode, mesh, material-dispersion, directional-source, TFSF-source, source-time, and auto-grid export contract revisions for changes that alter SaaS results without changing declarative scene data (auto grids export Maxwell's own resolved mesh, so mesher changes invalidate their references loudly), so stale reference data is regenerated automatically; time-monitor caches also preserve their physical t coordinate
  • Benchmark-side Maxwell and Tidy3D runs share the same physical Domain.bounds, both append PML outside it, and field/flux comparisons crop the external absorber samples back to the common physical domain during analysis
  • Shared benchmark scene helpers derive safe flux-monitor positions directly from the physical domain
  • Error metrics: relative L2 error, best-fit-complex-scale shape L2, relative L-infinity error, normalized cross-correlation, and incident-power-normalized flux error; directional soft-surface sources remove one unit-modulus global source-reference phase (never amplitude) and use source geometry/direction to exclude only the discrete source sheet and upstream half-space. TFSF incident-power normalization is analytic in SI units (0.5 * |E0|^2 * aperture * |direction_normal| / eta0) for both box and slab apertures, instead of inferring power from differently normalized cache payloads
  • Scenario-specific scalar comparisons share the same extracted solver data and report cavity resonance, grating diffraction efficiency by order, closed-surface dipole directivity/beamwidth, and sphere bistatic RCS alongside the coordinate-aligned field slices; Tidy3D and Maxwell closed-surface fields feed the same SI-unit near-to-far postprocessor so postprocessing differences are not mixed with solver differences
  • Cached Tidy3D field payloads are filtered to the component set declared by the corresponding public Maxwell monitor before field or scalar postprocessing, preventing undeclared near-zero components exposed by Tidy3D's six-field monitor payload from being selected as observables
  • Coordinate-aware plane-field alignment and interpolation onto the Tidy3D reference grid for cross-solver field comparison
  • Flux benchmark comparison re-integrates Maxwell monitor fields over the same physical aperture used by the Tidy3D reference export with Yee cell-centred control-volume weights
  • Material and source comparison plots align slices by physical coordinates, use the same soft PlaneWave injection-plane placement as the runtime/Tidy3D export, and geometry voxelization uses boundary tolerances to avoid one-pixel drift on benchmark domains
  • Auto-generated Maxwell-vs-Tidy3D permittivity/source comparison plots plus Ex/Ey/Ez field comparisons on x/y/z cut planes under benchmark/plots/; multi-frequency scenes display the worst-L2 frequency rather than silently fixing the image to the first channel, and every field panel reports its absolute peak so an auto-scaled near-zero cross-polarized component cannot be mistaken for a dominant-field failure. Each compared monitor also gets a coordinate-aligned complex-field diagnostic with globally phase-aligned magnitude/real/phase differences and center-line magnitude/real/phase traces; phase is shown only on the reference's significantly excited support, suppressing meaningless weak-field phase noise while retaining boundary-reflection and propagation-phase diagnostics. Frequency-converting scenes additionally emit one spectral electric-field row per requested frequency, using a single carrier-derived global phase factor for every sideband so relative modulation phase is never fitted away
  • Scalar benchmark observables produce paired visual comparisons as well as Markdown rows: complex S-parameters use magnitude/phase spectra, cavity resonances include the normalized point-probe spectrum and interpolated peak, and diffraction efficiency, bistatic RCS, and antenna directivity/beamwidth use solver-paired charts. Diffraction charts additionally report each order's absolute efficiency error in percentage points, annotate the retained relative error, and show the full order-distribution total-variation distance so near-zero orders cannot look dominant solely because of a small denominator. Time monitors retain solver-native physical time coordinates, interpolate normalized traces only over their common physical window, report zero-lag waveform L2 plus a bounded lag diagnostic, and generate paired physical-time trace plots; they are never stretched independently onto an artificial [0, 1] sample axis
  • Auto-updated benchmark summary in benchmark/RESULTS.md, grouped by benchmark scene folder with per-metric better-direction and target-range guidance plus FDTD ms/step, steps/s, total DFT samples, and driver-level peak GPU memory for Torch and CuPy allocations; each completed scenario is persisted incrementally so a later scenario failure does not discard earlier results
  • The generated summary always discloses what it did not measure: a ## Registered scenarios with no measured row section lists every scenario registered in benchmark/runner.SCENARIOS that carries no row in the tables above (with its solver and description), so a family that cannot execute on the generating host -- a missing optional dependency, a deliberate deferral, an aborted run -- is visible as unmeasured instead of silently absent. The section states in-line that such a row is not a pass, a fail, or a tolerance waiver but the absence of evidence, and because it is generated it cannot rot out of the file on the next regeneration
  • Benchmark runs enable source-spectrum normalization whenever all sources share one waveform, preserving user amplitude/phase while removing the arbitrary pulse envelope spectrum in the same convention as Tidy3D
  • Validation-campaign controls for low-cost reference generation and solver filtering: python -m benchmark --references-only --campaign-only, targeted cache replacement with --references-only --refresh-references <scenarios...>, --historical-only, --solver fdtd|fdfd, cache-key inventory, per-frequency field metrics (printed and persisted, with worst-frequency aggregation), incident-power-normalized flux errors, and a per-task Tidy3D cost ceiling
  • Unified FDFD validation scenarios use backend-supported CW point dipoles and compare normalized |Ex| patterns for dielectric, conductive, dispersive Drude, and diagonal-anisotropic material responses against same-scene FDTD references; the campaign gate compiles every declared FDFD source so unsupported pseudo-scenarios cannot count as coverage
  • RF port validation harness via python -m benchmark rf [scenes...] over the six benchmark/scenes/rf/ scenes, with the binding metric measured from a real FDTD Scene -> Simulation -> Result run wherever the two-port bench yields a usable S-matrix (never from the 2D mode eigensolve). Both wave benches are terminated by running their conductors/walls THROUGH the computational PML to the padded grid edges (the grid appends PML nodes outside the declared bounds, so 2*(DOMAIN + num_layers*dx) is required and is verified against the prepared PEC occupancy, not the scene constant), and the network S-matrix is assembled by solving B = S*A across the drive columns (the correct extraction whenever the passive port carries an incident wave; the per-drive b/a ratio is the diagonal special case), recording the incident-matrix condition number per frequency. Current honest status (audit S1, round 4): coax_thru is a wave-level PASS -- terminated air-line TEM two-port with a_passive/a_driven 0.17 (bench-quality diagnostic), |S11| < 0.02, |S21| ~ 1, max singular value ~1.0, cond(A) ~ 1.2, and beta from arg(S21)/L within 0.83% of k0; the wave-level precondition is extraction conditioning plus post-solve passivity, and coax reciprocity is annotated as symmetric-trivial (mirror-symmetric fixture). rectangular_waveguide is a wave-level PASS on the Yee-staggered transverse full-vector operator: the selector injects a clean full-grid TE10 (sin(pi y/a)-correlation 1.0000), the terminated two-port S is well conditioned (cond(A) ~ 1.1) and passive (max singular value ~1.001), and beta from arg(S21)/L tracks the analytic dispersion sqrt(k0^2 - (pi/a)^2) to ~0.05% (median, interior band) inside a pre-registered 1% gate, with a fail-closed sin(pi y/a)-correlation regression guard (< 0.9 -> BLOCKED). A one-shot external-reference-solver cross-check (a TE10 ModeSource-driven guide, cloud run) independently confirms the same beta(omega) to ~1.2% median. lumped_open_short_match is a wave-level PASS (rebuilt as a coax short-open-load calibration bench whose TEM feed is coupled to a de-embedded load plane, so the three standards are mutually distinguishable: matched |Gamma| <= -20 dB, short/open |Gamma| ~ 1, open in the +1 class and short in the -1 class after short-referenced de-embedding); microstrip_two_port and differential_pair are BLOCKED (contour-snap error first, and WaveModeSpec('tem') categorically inapplicable to their inhomogeneous substrate+air cross-sections); series_parallel_rlc is a wave-level PASS (rebuilt with the RLC as an in-line coax two-terminal element carrying the full axial line current, so the series |S11| notch tracks f0 = 1/(2*pi*sqrt(L C)) with a documented ~13% parasitic downshift). See the e2a-rf-scenes subsection for the rebuilds. Modal-eigensolve quantities, when reported, are labelled modal-eigensolve supporting evidence and never gate. Each scene emits a machine-readable artifact (grid convergence, extraction conditioning/passivity, the a_passive/a_driven diagnostic, and per-tier complex S(f) and port a/b so a frequency can be recomputed by hand; verbatim taxonomy gate classes and a separate status field) under docs/assessments/rf-wave-validation-2026-07-18/ and an ## RF wave-level validation section in benchmark/RESULTS.md. python -m benchmark.rf_tidy3d_references performs a real adapter-driven external-reference generation attempt (export -> runnable gate -> cost estimate/budget -> one cloud run -> .h5 cache), and records reference: pending-generation with the concrete reason when an export is not runnable or a cloud run fails, never fabricating a comparison, while the analytic reference keeps binding (see the e2c-rf-scenes subsection)
  • Wave-level RF gate tests under tests/rf/wave_validation/ replacing the retired plan-01 algebraic-identity gates: a propagating matched-load |S11| gate on a rectangular waveguide (matched thru reflects far less than a PEC short, measured from fields, with the short as falsification), and an asymmetric two-port reciprocity + field-derived power-balance gate (different port impedances/geometry so S12 == S21 is physics not symmetry, with injected non-reciprocity and gain errors as falsification). The series-RLC companion-impedance formula check is retained but re-labelled analytic-identity (non-gating); the wave-level RLC resonance gap has since been closed by the rebuilt in-line coax RLC bench (see the RF wave-level bench fixes section)

Performance Benchmarking

  • FDFD solver performance benchmark via python -m benchmark.fdfd_performance, sweeping cubic grid sizes (default 32³ through 128³) on a canonical dipole + dielectric-cube scene, with --solver/--precond/--precision axes
  • Per-size metrics: matrix assembly time, solve time, operator matvec count, convergence flag, explicit relative residual, and CuPy peak-GPU-memory high-water mark
  • Results written to benchmark/FDFD_PERFORMANCE.md with raw JSON runs archived under benchmark/cache/fdfd_performance/
  • Three-level dielectric-sphere FDTD grid-refinement study via python -m benchmark.grid_convergence, with coordinate-aligned pairwise complex-field errors, observed convergence order, performance counters, Markdown results, and raw JSON archives

Validation and Reference Utilities

  • Shared material compiler used by scene construction and backend preparation
  • Reference-alignment helpers for FDTD field comparisons
  • Error metrics for comparison against reference fields
  • Direct physics validation suites under tests/validation/physics/ for vacuum plane-wave and dipole correctness, dielectric slab energy balance, boundary-condition validation (periodic, Bloch, PEC, PMC, mixed periodic + PML, CPML), and TFSF leakage/scatter validation for both axis-aligned and oblique CW plane waves
  • Broadband-vs-CW grating acceptance (tests/validation/physics/test_bloch_broadband_vs_cw.py): a single broadband GaussianPulse grating run reproduces the per-frequency monochromatic CW complex transmission of a thin non-resonant metasurface layer within 2% at three band frequencies, compared at normal incidence (the k_bloch -> 0 limit, the regime where the CW reference settles to an extractable steady state), and the genuine oblique complex-field Bloch broadband injection confines the incident pulse to the total-field slab at each frequency
  • Dispersive-metasurface Bloch acceptance (tests/validation/physics/test_dispersive_metasurface_bloch.py): a patterned (half-cell bar) Lorentz metasurface runs forward under oblique Bloch and, at unit Bloch phase (k L = 2*pi), reproduces its real-field periodic-equivalent run to float32 round-off; FDFD is unavailable as an independent cross-check because the FDFD runtime rejects periodic/Bloch faces, so the periodic-equivalent unit-phase reference is the exact cross-check of record
  • Validation workflows emit representative electric-field and centerline plots under tests/test_output/validation/ in addition to pass/fail assertions
  • Test coverage for public API, scene construction, mesh geometry, material compilation, CPML, observer extraction, and FDFD/FDTD consistency
  • Guard-convergence gates in tests/api/public/test_guard_census.py: an AST census caps the number of NotImplementedError capability guards in witwin/ against a committed budget (contract guards excluded via a documented list), and a phrase gate fails if any NotImplementedError in the public forward path (media.py, compiler/, fdtd/runtime/, fdtd/boundary/, scene.py, simulation.py) carries a bare deferral phrase (not implemented yet / not supported yet / in v1) instead of a physical or mathematical reason; modules reworded by a later phase are allowlisted with the owning phase named

Known Limitations

  • The experimental lumped-port runtime currently executes one active PortExcitation per FDTD run; automatic N-port sweeps are introduced by the network phase. Port excitation waveforms must be device-native CW, Gaussian-pulse, or Ricker forms, a time-domain source impedance must be real and positive, and the native FDTD adjoint does not yet replay port/RLC auxiliary state
  • Nonuniform (GridSpec.custom) grids are FDTD-only in v1: FDFD and Tidy3D export both raise NotImplementedError for custom grids; GridSpec.auto resolves to a nonuniform grid at prepare time and inherits the same restrictions (FDFD unsupported, TFSF/mode-plane region-uniform bounds apply)
  • On nonuniform grids, TFSF-kind injections (PlaneWave TFSF box, grating TFSF slab) and ModeSource / ModeMonitor / ModePort accept a bounded amount of grading over the injection region / mode plane rather than requiring exact uniformity: a perfectly uniform region returns its exact cell spacing bit-for-bit (uniform grids unchanged), a mildly graded region is accepted using the region-mean spacing as the single effective delta, and an over-graded region raises with the predicted error quoted in the message. The TFSF bound caps the leading numerical-dispersion phase-velocity spread (k0*d)^2/24 across the region (finest-to-coarsest cell) at 1e-3 for the vacuum injection wave k0 = omega/c (empirically the total-field/scattered-field leakage is about 15 x that spread, so within-bound grading keeps the null below ~1.5%); the mode-plane bound caps the fractional transverse-spacing variation (d_max - d_min)/d_mean at 1e-2, the leading consistency error of the single-spacing 2D finite-difference mode operator. Soft (non-TFSF) surface sources and PointDipole are fully generalized, and the soft PlaneWave numerical-dispersion phase correction uses the spacing local to the launch footprint (launch-plane cell along the injection axis, physical-aperture mean along each tangential axis), which is exact on uniform grids and tracks the launch cell's own numerical wavenumber on graded ones
  • On nonuniform grids the subpixel averaging window per node is the symmetric Yee dual-cell width (0.5*(primal_left + primal_right)), i.e. a node-centered average; it reduces to the cell spacing on a uniform grid but is an approximation of a cell-exact quadrature on strongly graded meshes where the two neighbor cells differ substantially
  • Polarized subpixel averaging is applied to eps and mu only; sigma_e, Kerr chi3, and dispersive-pole weights stay arithmetic (the normal-projection rule is not well defined for conductivity/nonlinearity, and only the static base permittivity/permeability becomes normal-aware for dispersive media)
  • The polarized rule is baked at grid nodes and both runtimes keep their existing arithmetic node-to-Yee-edge averaging, so a residual O(dx) half-cell smoothing of the normal-aware correction remains versus evaluating the rule exactly at each Yee-edge location; this residual is far below the arithmetic/staircase error it replaces
  • Conformal PEC (SubpixelSpec(pec="conformal")) is FDTD-only: FDFD raises NotImplementedError for in-domain PEC materials, and the FDTD scheme is the stable partial-fill (edge-fraction E-suppression) variant of the Dey-Mittra family; classical area-scaled Dey-Mittra (H-side 1/A_open with an area floor), contour-path H-loop averaging, and surface-impedance boundaries remain future work. Two consequences of the E-suppression formulation are documented rather than fixed. (1) The open fraction multiplies the electric update every step, so a fill f on a cut edge is an effective conductivity eps*f/dt there and a lossless PEC scatterer picks up spurious absorption: a closed PEC cavity holding a PEC sphere retains 0.45 of its energy after 5200 source-free steps under conformal versus 1.00 under staircase. (2) A flat wall parallel to the grid cuts no tangential edge, so conformal does not place such a wall sub-cell -- it reproduces staircase exactly there (that case needs the area-scaled magnetic update). staircase remains the default everywhere, including the benchmark harness
  • Subpixel averaging normals and PEC fill fractions reuse per-structure signed-distance fields, so accuracy is bounded by SDF quality for primitives with kinked distance fields (faces/edges/corners)
  • Material.orientation remains unsupported. Full off-diagonal Tensor3x3 permittivity is FDTD-only and cannot currently combine with Bloch fields, Kerr media, or polarized subpixel averaging; it now composes with electric dispersive poles (a rotated birefringent dispersive crystal) and with electric conductivity (a lossy anisotropic crystal) in the same material, both FDTD forward-only. Static full-tensor scenes are differentiable through the FDTD adjoint bridge (a design differentiated through the static off-diagonal coupling), but a trainable geometry on the tensor structure itself, anisotropic mu_tensor media, the full-tensor + electric-dispersion combination, and the full-tensor + conductivity combination stay guarded because the off-diagonal coupling, magnetic reverse updates, and coupled-tensor ADE/conduction reverse carry no off-diagonal material gradient channel.
  • A single Material may combine instantaneous nonlinearity (kerr_chi3 / NonlinearSusceptibility / TwoPhotonAbsorption) with electric dispersive poles (for chi2 SHG phase matching) but not with anisotropic tensors; separate structures in the same scene may also mix them freely. Nonlinear media are excluded from Bloch / complex-field runs, full off-diagonal anisotropy, and FDFD, but instantaneous nonlinearity (Kerr chi3 / chi2 / two-photon absorption) is now captured by the forward CUDA graph (the dynamic coefficient recompute carries no per-step host input); Tidy3D export covers the chi3-family (chi3 Kerr and two-photon absorption) but rejects chi2 (no Tidy3D public equivalent). The FDTD adjoint bridge covers pure instantaneous Kerr (chi3), chi2, and two-photon-absorption media (the general nonlinear coefficient recompute is replayed differentiably), but still rejects nonlinear media combined with static conductivity or with electric dispersion in the same material (the reverse replay divides the ADE polarization current by the static rather than the field-dependent effective permittivity, so the same-material nonlinear + dispersion combination is forward-only). NonlinearSusceptibility is scalar (tensorial chi2 / chi3 are not supported)
  • Linear-gain support is limited to allow_gain=True gain-signed LorentzPole media (a documented negative-oscillator-strength path); saturable / rate-equation (2-level, 4-level) gain is a follow-up and is not implemented, gain runs carry no automatic stability guarantee, and the FDTD adjoint bridge does not cover gain media
  • Experimental DiffractionMonitor is v1-limited: it assumes the monitor plane spans one full transverse unit cell (periods taken from the transverse domain lengths), targets FDTD CW grating scenes with transverse Bloch/periodic boundaries, and reports per-order power as a share of the transmitted plane flux. Absolute per-order diffraction efficiency normalized against a separately measured incident power is not asserted, because the FDTD spectral-monitor CW normalization is steady-state/ratio-based rather than an absolute cross-run power balance
  • Experimental DipoleEmissionMonitor is v1-limited: it reports power_delivered from the single co-located dipole cell (a point approximation of the profile-weighted current integral) and requires FDTD PointDipole sources; the Purcell factor is only obtained by explicitly normalizing against a separate vacuum run through maxwell.postprocess.purcell_factor(...), because the discrete effective source volume that sets the absolute power scale has no reliable closed form, so a lone run returns purcell_factor=None
  • FDFD supports electric anisotropy only; static magnetic media and magnetic dispersion still fail explicitly
  • FDTD supports static electric conductivity (sigma_e) in the forward update but still lacks static magnetic conductivity (sigma_m); the FDTD adjoint bridge now differentiates real-field sigma_e scenes by routing them to the torch-VJP reverse with a differentiable semi-implicit lossy-coefficient replica, but still rejects sigma_e combined with nonlinear media (the instantaneous nonlinear kernel folds the loss into a field-dependent coefficient) or on Bloch boundaries (the complex-field replay carries the loss on the real update only)
  • Mixed Bloch boundary configurations outside the single-PML-axis / two-Bloch-axes family (any one absorbing axis with the other two Bloch) still fail explicitly: a layout that mixes Bloch and PML on the same axis, or leaves more than one absorbing axis, is rejected with a physics-worded error
  • TFSF slab runtime support covers CW and broadband PlaneWave grating slabs on any normal axis (axis="x"|"y"|"z", Bloch on the two transverse axes) and normally-incident PlaneWave slabs with non-periodic transverse boundaries; oblique-incidence non-periodic slabs, non-PlaneWave slab sources, and broadband automatic fixed-angle Bloch workflows still fail explicitly because automatic Bloch phase is single-frequency metadata
  • Automatic Bloch wavevectors are solver-preparation metadata and are rejected by unresolved Tidy3D export
  • FDFD currently supports per-face boundary mixing only for none and pml
  • Domain symmetry (Scene(symmetry=...)) is FDTD-only (FDFD rejects it), applies a single image plane per axis at the chosen low/high face, and requires the user to supply the pre-folded half/quarter domain with a physically symmetric source layout. A configured PML on the symmetry face contributes zero layers, high-face uniform grids anchor their last sample to the image plane, and point dipoles exactly on an image plane receive the Yee-control-volume scaling needed to reproduce the expanded full-domain source. Tidy3D encodes symmetry about the domain center and has no per-face selection, so direct export is allowed only when structures/material regions and plane-wave sources are translation-invariant along each symmetry axis; localized or axis-varying symmetry scenes raise NotImplementedError instead of silently changing the image-plane location
  • The first-order BoundarySpec.mur() absorbing boundary leaves a higher reflected residual than a PML absorber (single-snapshot residual energy roughly an order of magnitude above a reflecting PEC box, versus about two orders for PML), applies only to the real electric field so it cannot be combined with Bloch complex-field runs, and has no second-order variant in v1
  • The "absorber" and "stablepml" variants share the existing PML face plumbing (BoundarySpec.pml(...)); "stablepml" overrides the CPML grading/alpha profile, so a user-supplied cpml_config does not override the stable-profile grading keys
  • Tidy3D export rejects anisotropy, magnetic dispersion, and Kerr media explicitly in v1. The FDTD adjoint bridge supports diagonal (DiagonalTensor3) epsilon anisotropy, scenes containing static magnetic-dispersive (mu-pole) media, and pure instantaneous Kerr (chi3) media, and chi2 / two-photon-absorption media (through a differentiable replica of the general nonlinear coefficient kernel), but still rejects full off-diagonal (Tensor3x3) permittivity, mu_tensor anisotropy, and trainable geometry on magnetic-dispersive structures (there is no mu material-gradient channel) explicitly
  • Custom spatially-varying dispersive poles vary only the oscillator strength per cell (delta_eps / plasma_frequency); the rate parameters (tau, gamma, resonance_frequency) stay spatially uniform per pole so the ADE recursion constants remain scalar. They require Box structure geometry, Material.relative_permittivity/relative_permeability raise for custom poles (evaluate the compiled scene model instead, so GridSpec.auto meshes such structures from their static eps_r), and Tidy3D export lowers a spatially-uniform custom pole to its equivalent homogeneous pole while rejecting a spatially-varying one with a physics-worded error (no Tidy3D absolute-coordinate resolution of the box-relative grid at material-conversion time)
  • Medium2D sheets require an axis-aligned unrotated Box geometry with exactly one zero-size axis, snap to the nearest node plane along the sheet normal (staircase placement, no sub-cell interpolation); they export to Tidy3D's Medium2D (surface conductivity cross-validated against sheet_conductivity) but are rejected by the FDTD/FDFD adjoint bridges
  • Graphene(include_interband=True) fits the T>0 interband Kubo conductivity to Lorentz sheet terms only below the absorption edge (hbar*omega -> 2*|mu_c|); the fit targets and is validated over that optical band, so above-edge behavior (the universal e^2/4*hbar real-conductivity plateau) is extrapolated rather than fitted, and the constructor raises a clear ValueError if an operating point (e.g. |mu_c| not large compared to kB*T) cannot be fitted within the band tolerance
  • Generalized axis-aligned surface-impedance boundary: compile_surface_impedance_layout extracts every illuminated axis-aligned face of every surface-impedance metal into a CompiledSurfaceImpedanceLayout (CompiledSurfaceMetal / CompiledSurfaceFace), replacing the single-plane v1 restriction. Finite blocks (all exposed faces), mid-domain double-sided plates, multiple metals, and multiple orientations are all supported; a face is illuminated when its box side does not sit flush against the physical Domain.bounds (external PML cells never turn a boundary-backed metal into an interior face). Per-face area sums and a deterministic edge/corner owner order (minimum-rank owner writes last, so a shared corner edge has one deterministic owner) are computed in the layout; a PEC or 2D sheet coincident with a surface, or two different surface materials on one interface plane, fail closed through the single capability funnel as contradictory owners. Oblique/rotated or curved (conformal) surfaces and Bloch-periodic runs still fail closed with a physics-worded NotImplementedError naming the phase that lifts them
  • Narrowband good-conductor LossyMetalMedium is realized on that unified layout as an order-0 (pure-resistance) surface: Z_s = R = sqrt(omega0*mu0/(2*sigma)) evaluated at the operating frequency, no auxiliary state. A full-plane order-0 face uses the fused native CUDA kernel (E_t = sign*R*(n x H)), which is bit-identical to the generic scalar D*(sign*H) path (multiplication by sign = +/-1 is exact), so the fused kernel is a pure optimization of the same contract; a finite (sub-plane) face writes only its transverse window. The reactive part of Z_s is omitted for the narrowband path (its explicit derivative overwrite is non-passive; it shifts |Gamma| by < 1.3e-4 for a good conductor). The public surface_impedance(omega) helper and Tidy3D export still expose the full complex Z_s(omega0) = (1 + j)*R; Tidy3D export maps LossyMetalMedium to Tidy3D's own LossyMetalMedium
  • Broadband generic surface-impedance runtime: a SurfaceImpedanceMedium carrying a passive RationalSurfaceImpedance is stepped by a native per-edge Z-form auxiliary-differential-equation (ADE). The surface Z_s(omega) is resampled over its declared frequency_range and refit as a passive Z-form rational through the shared fitter, then discretized with the bilinear (trapezoidal, |z| < 1) transform (fit_surface_impedance); passivity is a compile exit gate (a fit accurate but not certified passive is rejected before any step). Each surface edge advances x <- A x + B u with output E = C x + D u, u = sign*(n x H) -- exactly the shared DiscreteStateSpaceNetwork.step. The broadband good-conductor reflection matches the analytic Leontovich value within the frozen SurfaceAcceptanceBudget (analytic_reflection_relative_error), the surface is dissipative (|Gamma| < 1, min_local_surface_dissipation >= 0), the result is stable and consistent across three grid-refinement levels at moderate fit order and adequate resolution, and the torch-native per-edge ADE state advance is CUDA-graph capturable. Stability holds in the moderate-order / adequate-resolution regime the acceptance suite covers: the compile passivity certificate bounds the continuous surface response, not the coupled surface-plus-Yee discrete stability, so a certified-passive high-order Z-form fit at coarse resolution (e.g. order ~10 near ~40 cells/wavelength) can still diverge -- use a moderate fit order at adequate cells-per-wavelength. The FDTD adjoint bridge and multi-GPU distributed path reject any surface-impedance medium (the surface update and its ADE state advance carry no reverse gradient channel and no sharded owner yet); interoperability export of a generic SurfaceImpedanceMedium fails closed (no finite bulk-permittivity equivalent). Public model layer: SurfaceImpedanceModel / RationalSurfaceImpedance (with .fit) / SurfaceImpedanceMedium, all fail-closed unless stable, certified passive over the band, and scalar (1x1) or tangential (2x2); surface_impedance(omega) returns Z_s regardless of the internal admittance/impedance representation. A runtime-independent torch reference oracle (analytic Fresnel/Leontovich with oblique TE/TM, the surface power-balance identity, and the discrete surface power form with the edge/corner unique-owner assembly proven nonnegative) pins the numerical contract
  • Staircased (voxelized) surface-impedance boundary: a good-conductor LossyMetalMedium on any non-Box geometry (a curved Cylinder/Sphere, mixed orientations in one scene) is staircased from its node occupancy -- a node is metal when its center is inside the geometry, and every axis-aligned voxel face on the metal/vacuum boundary becomes a Leontovich surface-impedance face (_compile_voxel_surface_metal). Faces of one orientation at a node plane are grouped into a boolean transverse mask; the metal interior is terminated as a good conductor via the node->edge occupancy fill (the PEC-staircase treatment), and each face writes E_t = R*(n_hat x H) on its exposed footprint with a static-shape, CUDA-graph-capturable torch.where. The same axis permutation of a plate scene reproduces the field to near-bitwise agreement (an exact Yee symmetry; the staircase adds only a ~1e-4 hard-threshold residual), the stateless resistive update stays finite and bounded on mixed-orientation and curved surfaces over long runs across the good-conductor regime, and a flat plate assembled entirely from voxel faces reproduces the analytic Leontovich reflection to <1% at 1/2/3 GHz versus |Gamma| ~ 0.999 for a PEC plate. The staircase wires the narrowband good conductor (order-0 resistance) only; a generic rational surface on a curved conductor, a rotated Box (grid-unaligned oblique normal), and Bloch runs still fail closed through the single capability funnel, which now funnels only the true conformal/oblique and Bloch cases -- the remaining SIBC gap is the non-staircase conformal surface, not the staircased curved conductor
  • PerturbationMedium perturbs the electric permittivity only (no mu/sigma_e sensitivity channels yet), requires Box structure geometry, raises from relative_permittivity()/evaluate_at_frequency() because the shifted permittivity is spatially varying (GridSpec.auto meshes such structures from the base eps_r), and Tidy3D export lowers a spatially-uniform perturbation to a homogeneous permittivity shift eps_base + eps_sensitivity*value (preserving the base's dispersion/anisotropy) while rejecting a spatially-varying one with a physics-worded error
  • Space-time modulation (ModulationSpec) is FDTD-only and modulates the electric permittivity only (no mu/sigma_e modulation); a modulated Material now composes with electric/magnetic dispersive poles and with the instantaneous nonlinear channels (kerr_chi3 / chi2 / TwoPhotonAbsorption), both in the same Material and across separate structures in a scene, but a modulated Material still cannot carry an anisotropic tensor (the scalar modulation factor has no defined per-axis/crystal-tensor action) or a static sigma_e (the semi-implicit loss fold would need to track the modulated eps_inf * m(t)), and a modulated scene still cannot mix with a fully off-diagonal anisotropic structure. A scene may hold several distinct modulation frequencies at once (each modulated structure stamps its own angular frequency onto the cells it covers; overlapping differently-modulated structures resolve to the last structure's frequency, mirroring the quadrature-overlap rule, and the modulation depth cap below 0.5 stays per-structure because each cell carries at most one frequency), grid-valued amplitude/phase require Box structure geometry, Bloch / complex-field runs are rejected, CUDA Graph capture uses the device-resident modulation clock, and the FDTD adjoint bridge rejects modulated media explicitly. Tidy3D export supports a scalar-depth non-dispersive modulated medium (mapped to Tidy3D's ModulationSpec) and rejects modulated dispersive/nonlinear media and spatially-varying (3D-tensor) modulation profiles with physics-worded errors. GridSpec.auto and static/frequency evaluation use the unmodulated carrier permittivity eps_static
  • Experimental ModeSource, ModeMonitor, and ModePort now cover CW and broadband (GaussianPulse / Ricker) soft injection / monitoring, complex/lossy eigenmodes (a complex-symmetric solve exposing effective_index_complex / beta_complex), diagonal-anisotropic apertures (true per-axis eps/mu), non-unit mu_r planes, and bent (curved-waveguide) ports (bend_radius / bend_axis), and export to Tidy3D's ModeSource / ModeMonitor (see the Tidy3D Adapter section). The remaining genuine limitations are: the injection / monitor plane must be axis-aligned, so truly oblique or staircased angled planes are rejected by the plane-normal contract (the well-posed cylindrical-bend case is handled instead through bend_radius / bend_axis); and the differentiable mode-solve path is Hermitian only, so gradients through a lossy (complex, non-Hermitian) permittivity are unavailable even though the forward complex solve and every real-permittivity gradient are supported. RF LumpedPort now has a separate Phase-0 public/discrete contract; its FDTD source/load coupling, terminal ports, and RF wave ports are delivered by the subsequent RF runtime phases instead of being inferred from ModePort
  • Experimental compute_mode_overlap(...) currently expects aligned tangential E/H fields on the target port plane and inherits the same axis-aligned modal-source assumptions as the current ModeSource implementation
  • Raw single-plane postprocessing from PlaneMonitor data is still not a general 3D radiator/scatterer workflow; use FinitePlaneMonitor / ClosedSurfaceMonitor for the validated near/far/RCS/directivity path
  • First-class closed-surface postprocessing currently requires a homogeneous exterior medium immediately outside every face; layered or otherwise inhomogeneous exteriors are rejected explicitly
  • Result.antenna(...) currently requires that homogeneous exterior to be lossless and one driven excitation column; the default full-sphere grid keeps the frequency dimension explicit. surface_currents are the electric/magnetic Huygens equivalent currents sampled on the named near-field surface, not an inferred sub-cell conductor-current distribution
  • Automatic PowerLossMonitor production currently covers static bulk electric conductivity from full-field FDTD spectra. Two-dimensional sheets are rejected because volume lowering discards their surface identity, and magnetic conductivity is rejected because the automatic path has no frequency-domain H loss channel. ADE, nonlinear, circuit, surface, and wire mechanisms appear only when their owning physics supplies explicit density or integrated power; absent channels are never reported as zero
  • RF differentiation is intentionally narrower than RF forward execution: differentiable lumped runs reject ParallelRLC, observer-only contour ports, open internal resistance, trainable source/reference impedance, lumped PortSweep, and conductive, dispersive, nonlinear, modulated, full-anisotropy, Bloch, or other complex-field coupling. Differentiable WavePort runs require one fixed mode and fixed amplitude, keep the design away from the aperture and adjacent launch plane, and cannot mix with lumped ports or standalone R/C/L elements
  • Curved (arbitrary-normal) closed-surface far-field / near-field propagation is now supported and validated on the radiation side (SurfaceEquivalentCurrents / equivalent_surface_currents_from_surface_samples radiate given surface fields on a sphere, ellipsoid, or voxelized closed surface, checked against a Hertzian dipole to < 1e-3). The remaining gap is the monitor side: sampling FDTD near fields onto an off-Yee-grid curved surface (trilinear interpolation of the DFT'd fields) is still future work, so the FDTD monitor path continues to serve axis-aligned box and staircased polyhedral surfaces only
  • The soft-plane-wave absolute incident-power calibration is resolved: the amplitude scale is derived (surface-equivalence unit forward gain, Yee numerical impedance eta0) rather than fitted, the two former empirical constants (_PLANE_WAVE_POWER_CALIBRATION, _PLANE_WAVE_DELAY_CALIBRATION_S) are removed, and the absolute power is validated against analytic unit power within 2% across three frequencies and two spacings
  • UniformCurrentSource, CustomCurrentSource, and CustomFieldSource inject additive electric/magnetic currents in the forward pass and are covered by the FDTD adjoint: a scene driven by them differentiates the design region through the injected field (the source J/M/field datasets themselves are fixed forward inputs). CustomFieldSource additionally requires a planar (single-sample-axis) FieldDataset, injects toward +normal only, and reproduces the plane wave to discretization-limited tolerance (finite-aperture edge diffraction and half-cell E/H staggering leave a small backward and cross-polarized residual)
  • CustomSourceTime v1 is limited to the Python scalar soft/uniform injection path: it is supported on PointDipole but rejected on PlaneWave, GaussianBeam, AstigmaticGaussianBeam, and ModeSource (which all require the native time-shifted surface kernel; ModeSource now accepts CW / GaussianPulse / Ricker there and rejects only CustomSourceTime); callable fn waveforms need an explicit characteristic_frequency and are not supported in the FDTD adjoint (only sampled (times, amplitudes) tables are), and custom-waveform samples are not differentiable

Electrostatics (experimental)

  • Dedicated cell-centred finite-volume electrostatic (Laplace/Poisson) solver reached through Simulation.electrostatic(scene, boundary=..., solver=...).run(), returning a standard Result(method="electrostatic") whose typed output is result.electrostatic (an ElectrostaticResultData). It is an independent DC PDE runtime, not a low-frequency full-wave approximation, and keeps the Scene -> Simulation -> Result contract.
  • New public objects ElectrostaticTerminal (equipotential conductor: potential=, grounded=True, or floating charge=), ChargeDensity (volumetric free charge in C/m^3), ElectrostaticBoundarySpec (per-face dirichlet / neumann / symmetry, with grounded_box(), dirichlet(value), and neumann() constructors), and ElectrostaticSolverConfig (tolerance, max_iterations, dtype; float64 by default).
  • Terminals live in a solver-specific Scene.add_electrostatic_terminal(...) collection (and free charge in Scene.add_charge_density(...)) that never enters the RF Scene.ports set, so an equipotential constraint is never reinterpreted as an RF impedance/power-wave port.
  • The operator uses harmonic-mean interface permittivity on each Yee face so the discrete Gauss law closes by construction; interior fixed-potential conductors are imposed by symmetric projection and solved with a GPU Jacobi-preconditioned conjugate gradient in float64. The solve records iteration count, relative residual, and the per-cell discrete-Gauss error, and raises with diagnostics on non-convergence.
  • ElectrostaticResultData exposes the cell-centred potential [V], colocated E [V/m] and D [C/m^2] fields, relative permittivity, per-cell free charge and volume, field energy [J], per-terminal conductor charge via terminal_charge(name), the residual, and the Gauss error. Validated against parallel-plate, coaxial-cylinder, and concentric-sphere analytic solutions with three-level grid convergence, discrete Gauss closure, and the 0.5 integral(E.D) = 0.5 sum(V Q) energy identity.
  • Floating conductors with a prescribed total charge (ElectrostaticTerminal(..., charge=)) are resolved by exact linear superposition: the solver runs one base solve (fixed electrodes on, floating conductors grounded) plus one unit solve per floating conductor, then solves a small dense k x k system so each floating conductor reaches the equipotential level that reproduces its prescribed charge. Multiple floating conductors, mixed fixed/floating scenes, and charge-neutral floating shields (which float to a consistent intermediate potential) are supported; a charged floating conductor in a fully insulated (pure-Neumann) enclosure is rejected as charge-incompatible, and an all-floating insulated problem is gauge-fixed by mean(phi)=0.
  • N-terminal Maxwell capacitance matrix extraction via Simulation.capacitance(scene, terminals=..., reference=..., boundary=..., solver=...).run(), returning a standard Result(method="capacitance") with typed result.capacitance (a CapacitanceData). It drives each active (non-reference) terminal to 1 V with the rest grounded, reusing one compiled operator across excitations, and reports the raw matrix with no silent symmetrization. CapacitanceData exposes matrix, terminal_order, reference, charges, per-excitation energy, reciprocity_error (max|C - C^T| / max|C|), and row_sum_error (charge conservation under an insulating boundary), plus derived capacitance(a,b), mutual_capacitance(a,b), capacitance_to_reference(a), and two_terminal_capacitance(a,b) accessors. Validated by sphere-in-grounded-shell analytic capacitance with grid convergence, three-terminal matrix symmetry / positive-diagonal / non-positive-off-diagonal sign structure, 0.5 V^T C V vs field-energy equivalence, terminal-reordering invariance, and insulating-boundary row-sum conservation.
  • Fail-closed in this stage: grid-extending Scene boundaries (PML/periodic), PEC-material dielectrics (use a terminal), dispersive/tensor/complex permittivity, gauge-singular pure-Neumann problems with no conductor, incompatible floating-charge constraints, and capacitance requests with no charge return path (no reference terminal and no Dirichlet boundary) are all rejected with clear messages rather than silently mishandled.
  • Differentiable electrostatics (implicit differentiation): the fixed-potential electrostatic solve and the capacitance extraction are PyTorch-native differentiable in the cell permittivity, the volumetric free charge, and the terminal voltages. The reduced solve is wrapped in a torch.autograd.Function whose forward runs Jacobi-PCG under no_grad (no autograd tape) and whose backward solves the adjoint system on the same SPD reduced operator/preconditioner (A^T lambda = dL/dphi, closed-form pinned-cell multiplier) and forms the parameter gradients as a residual vector-Jacobian product, so no hand-coded dA/d(eps) stencil is needed. Result quantities (energy, per-terminal terminal_charge, the fields) and every capacitance-matrix entry (CapacitanceData.matrix) therefore backpropagate to the compiled cell-permittivity tensor, the compiled volumetric free-charge tensor, and the compiled fixed-potential tensor. Through the public Scene API this reaches ChargeDensity(density=<tensor>) directly (its magnitude is preserved as a live tensor and flows to the free charge). Material permittivity and ElectrostaticTerminal.potential are real-scalar scene inputs coerced to float on construction, so differentiating with respect to a permittivity or a terminal voltage means driving the corresponding compiled epsilon_r / fixed_value tensor (the level the gradient gates exercise), not those scalar constructor fields. Verified by central-difference gates (relative error < 1e-4, float64) on dC_ij/d(eps), an interior-potential probe d(phi)/d(eps) (the non-variational eps->phi path), d(energy)/d(free_charge) (a pure-implicit functional), d(energy)/d(eps_region), and d(phi)/d(V_terminal) (the pinned-cell adjoint path). Gradients through the floating-conductor prescribed-charge superposition solve are not implemented and fail closed (a floating terminal combined with a differentiable permittivity/charge raises rather than returning silently wrong gradients).
  • Anisotropic (full symmetric-positive-definite tensor) static permittivity: a Structure whose Material carries epsilon_tensor=DiagonalTensor3(...) or a full epsilon_tensor=Tensor3x3(((exx,exy,exz),(exy,eyy,eyz),(exz,eyz,ezz))) now compiles into a per-cell relative-permittivity tensor field and solves through Simulation.electrostatic(...) and Simulation.capacitance(...), extending the previous isotropic-only scalar operator. The compiler validates each material tensor as real, symmetric, and positive-definite (an asymmetric or indefinite tensor is rejected as a physically invalid lossless permittivity), and an isotropic scalar or an isotropic diagonal tensor keeps the exact scalar code path unchanged.
  • The SPD tensor operator keeps the existing conservative two-point harmonic-mean face flux for the diagonal permittivity entries and adds a symmetric cross-derivative coupling for the off-diagonal entries, derived as the gradient of a discrete quadratic energy W(phi) = 0.5 phi^T A phi. The operator is therefore symmetric to floating-point tolerance (verified as a dense-matrix property and by random-vector <Ax,y> = <x,Ay>), positive-definite (positive symmetric eigenvalues / strictly positive field energy for a nonzero potential), and reduces exactly to the isotropic/per-axis face-flux path when the off-diagonal entries vanish (the cross operator maps every field to exactly zero). field_energy(phi) equals 0.5 phi^T A phi including the cross terms, so the 0.5 integral(E.D) energy identity and the discrete Gauss law continue to close. ElectrostaticResultData.D is formed from the full tensor contraction D_i = eps0 sum_j eps_ij E_j, and the result carries the compiled epsilon_tensor field.
  • Verified by a rotated-frame method-of-manufactured-solutions convergence study (a constant SPD tensor diag(1,2,3) rotated 30 deg / 45 deg so all off-diagonal entries are active) showing second-order spatial convergence over a three-level grid refinement, a discrete Gauss-closure gate, and an anisotropic three-terminal Maxwell-capacitance reciprocity gate (reciprocity_error < 1e-6 for a rotated anisotropic dielectric with asymmetrically placed electrodes; a mirror-symmetric two-terminal cell would report a symmetric matrix even with a broken operator, so the asymmetric placement genuinely probes the cross-flux symmetrization).
  • Fail-closed in this stage: a differentiable (trainable) tensor-permittivity or a trainable free charge alongside a tensor dielectric is rejected, because the off-diagonal cross-flux has no reverse-mode (implicit-diff) VJP yet, so the tensor solve is forward-only and refuses to silently detach a gradient. The cross-flux boundary treatment at a Dirichlet wall is first-order for a field with a strong tangential gradient at the wall (the interior scheme is second-order); improving that boundary layer and the open-boundary / truncation study are stage H2b.
  • Controlled open-boundary handling via domain extension (stage H2b): an open (infinite-domain) electrostatic boundary fails closed with a clear message (there is no exact radiation condition on the scalar potential at a finite Cartesian face; a boundary-element open boundary is a later phase). Instead, an isolated structure is modelled by enlarging the grounded-box enclosure until the capacitance converges. Simulation.capacitance(scene, ..., truncation_estimate=mw.TruncationEstimate(padding_cells=N)) is an explicit opt-in that runs one additional enlarged grounded-box solve (the interior grid is held byte-identical; the grounded box is pushed out by N whole cells on every side of every axis) and reports the finite-enclosure truncation error on result.capacitance.truncation_estimate (a TruncationReport): the base and enlarged capacitance matrices, their difference, max_relative_delta (the relative sensitivity of the extracted capacitance to the enclosure size), the effective base/enlarged enclosure sizes, and a 1/L Richardson extrapolation richardson_matrix to the infinite-domain limit with its residual richardson_max_relative_shift. No second solve runs unless truncation_estimate is passed; it requires a Dirichlet (grounded-box) enclosure and a uniform grid, and fails closed with a ValueError otherwise. Verified by a two-axis domain-extension convergence study (self-capacitance of an isolated conductor decreasing monotonically toward a stable Richardson C_inf as the enclosure grows at fixed grid, and Cauchy-convergent under grid refinement at fixed enclosure size).
  • Differentiability disposition (stage H2b, decided): electrostatic gradients through a full anisotropic (tensor) dielectric are deferred to the plan's differentiability phase, so a trainable input under a tensor dielectric fails closed on both the Simulation.electrostatic(...) and Simulation.capacitance(...) public paths rather than returning a silently detached (wrong) gradient; the isotropic implicit-differentiation backward is unchanged and fully supported.

ESD excitation waveforms and terminal injection (stress-only)

  • Capability level is stress-only: these objects reproduce standard ESD current waveforms, inject them into a terminal port as an ideal (prescribed) current, and report local field stress, port V/I, charge, and action integral. They do NOT model source-impedance networks, discharge-gun geometry, arc channels, or device-failure probability, and a standard waveform class name does not by itself constitute standard certification.
  • ESDWaveform.iec_61000_4_2(level_voltage, discharge="contact", standard_revision=...) builds the IEC 61000-4-2 contact-discharge first-transient current as a four-parameter two-term Heidler sum (n = 1.8) with numerically computed per-term peak-normalization factors. Amplitudes scale linearly with the level voltage; the rise/decay time constants are voltage independent. standard_revision must name a supported revision ("ed2-contact", also the documented default when omitted) and is recorded in provenance. Only discharge="contact" is supported in this phase (air discharge is deferred, fail-closed). The current is torch-native (current(t) accepts a torch tensor).
  • MeasuredWaveform(time, values, units="A", bandwidth=..., provenance=...) wraps user-measured current samples (strictly increasing time, amperes) with the same diagnostics/resampling interface and metadata provenance.
  • waveform.diagnostics() returns an ESDDiagnostics with peak current, current at 30 ns and 60 ns, 10-90% rise time, total charge integral i dt, action integral integral i^2 dt, and peak time; all match independent numerical quadrature of the same analytic current to tight tolerance.
  • waveform.resample_to_grid(dt, t_end=...) performs charge-conserving binned integration onto an dt-spaced grid (per-bin mean current (1/dt) integral_bin i dt, NOT naive point sampling), returning an ESDResampledWaveform whose charge_ratio is 1 by construction at every dt and whose aliasing_metric (action-integral error) decreases toward zero as dt refines.
  • ESDCurrentSource(name, port=<TerminalPort name>, waveform=..., direction="+") binds a waveform to a resolved TerminalPort and lowers (via Scene.resolved_sources()) to a uniform additive current source over the port gap footprint (an ideal current injection; the current density integrates to the target port current, polarization aligned to the port voltage-path axis and signed toward the positive terminal). Source-impedance networks are out of scope (Phase 3). Scenes with no ESD source are unaffected (the source lowering is a pure per-source expansion).
  • Result.esd_waveform(name) returns a typed ESDPortRecord (target diagnostics, the charge-conserving projection of the injected current onto the run time grid, and full provenance including standard revision, level voltage, capability level, and model version); Result.esd_waveform_names() lists ESD sources. For an ideal current injection into a capacitive terminal gap the measured gap voltage is the time integral of the injected current (V = Q/C), so its time derivative tracks the target current waveform. The record also carries a measured port record surfaced from the run's recorded terminal-port V/I (PortData) when present, enabling a target-vs-measured check; for the Phase-1 ideal-current injection path no terminal-port recorder runs, so measured is None by design (documented on the accessor) and the injected current on the run grid is the resampled record.

Circuit-driven ESD through the standard source network (stress-only)

  • Capability level is stress-only and the source network is a circuit approximation of the standard network, NOT discharge-gun geometry, calibration-target, or system certification. This closes the Phase-3 gap of driving an ESD discharge through a source-impedance NETWORK rather than the ideal current injection of ESDCurrentSource: the versioned ESD current waveform drives a time-dependent voltage source inside an MNA circuit whose Thevenin source impedance is the standard 330 ohm discharge resistor shunted by the 150 pF storage capacitance, and the network output node is bound to a scene TerminalPort/LumpedPort through the existing strong (same-step Schur) FDTD+MNA coupling, so the delivered discharge current is shaped by the source network and the device-under-test back-reacts on the generator.
  • waveform.to_circuit_waveform(t_end=..., samples=..., scale=...) resamples any ESDWaveform/MeasuredWaveform onto a PiecewiseLinearWaveform MNA source table (t, scale * i(t)) over the support (or [start, t_end]); the dense table reproduces the smooth analytic current within tabulation error and its trapezoidal impulse converges to the analytic value. scale maps the ampere-valued standard current onto the circuit-source quantity (a generator Thevenin resistance for a voltage source, or 1.0 for a current source).
  • ESDVoltageSource(name, port=<port name>, waveform=..., discharge_resistance=330.0, storage_capacitance=150e-12) is the source-network ESD excitation. build_circuit(t_end=..., circuit_name=...) assembles the standard 330 ohm / 150 pF generator Circuit (a VoltageSource driven by discharge_resistance * i_esd(t) so the network short-circuit current equals the standard waveform, in series with the discharge resistor and the storage capacitor to ground), binds the named port, and stamps the generator provenance on circuit.metadata['esd_generator']. Attach it via Scene(circuits=(...)) and run the standard Scene -> Simulation.fdtd -> Result flow. ESD_STANDARD_DISCHARGE_RESISTANCE / ESD_STANDARD_STORAGE_CAPACITANCE expose the default element values.
  • Result.esd_generator(name) returns the generator-network provenance (versioned waveform metadata -- standard, revision, level voltage, model version -- plus the source-network element values and the source_impedance_network injection tag), and Result.esd_generator_names() lists circuit-driven generators; the coupled port voltage/current time series lives on the associated CircuitData (result.circuit(name)).
  • Validation coverage: an RC-load analytic cross-check (the coupled FDTD+MNA port voltage/current, ESD waveform through the 330 ohm/150 pF network into a resistive load, reproduced by a fully independent scipy state-equation integration of the hand-derived series-loop ODE with the measured/fit EM one-port; observed port-voltage cross-check rel. error ~7.8e-4 vs a 1e-2 gate, with a storage-capacitance-perturbation falsification driving it to ~4.5e-2); a coupled global energy-conservation gate in a closed PEC box (S_source = dU_field + dU_circuit + D_circuit closing to ~1.3e-4 of throughput with the raw-field/port-work field-link at ~5.2e-4, and a throughput-channel-imbalance falsification); generator provenance ride-through; and a circuit-driven end-to-end run (electrostatic pre-bias + circuit-driven ESD through the TerminalPort + non-feedback BreakdownMonitor stress). Known limitation (fail-closed, documented): the strong FDTD+MNA port coupling does not support conductive media, so the dynamic conductive DielectricBreakdown feedback cannot ride the circuit-driven port path in the current runtime (it stays on the ideal-current-injection path); the conductive combination raises NotImplementedError at prepare() and is pinned by a test.

Non-feedback dielectric-stress and component-rating monitors (stress-only)

  • Capability level is stress-only: these monitors accumulate auditable field- and port-stress statistics during a standard FDTD run and compare them against declared material/component envelopes. They perform NO feedback into the field solve, do NOT switch conductivity, and do NOT model arc channels, latch-up, or device-failure probability. A recorded threshold exceedance is a stress indicator, never a failure prediction. Scenes with no such monitor are unaffected: adding a BreakdownMonitor produces bitwise-identical fields (verified) and there is no per-step cost when none is present.
  • BreakdownMonitor(name, region=... | position=..., size=..., quantities=("electric_field","exposure","dissipated_energy"[, "damage"]), critical_field=..., minimum_duration=0.0, damage_exponent=None) accumulates, per cell and entirely on device (no host sync in the step loop), the running max|E|, the exceedance time integral H(|E| - critical_field) dt (Heaviside convention H(0) = 1, i.e. |E| >= critical_field counts), the longest contiguous exceedance duration, and an optional damage integral integral (|E|/critical_field)^k dt accrued only while exceeding. The scalar |E| is colocated onto Yee cell centers with the energy-consistent averaging (each component averaged along its two node-staggered axes); partial voxels are weighted by target-material occupancy only.
  • Result.breakdown(name) returns a typed BreakdownStressData with peak_field, peak_index, exceedance_duration, longest_exceedance_duration, qualifying_cell_count, occupancy-weighted exceedance_volume_time, optional damage_volume, per-cell device maps (max_field_map, exceedance_time_map, longest_exceedance_map, damage_map, qualifying_mask), a locations() index list of sustained-stress cells (longest contiguous run reaching minimum_duration), and provenance recording thresholds, the colocation convention, occupancy policy, model version, and capability level. Result.breakdown_names() lists breakdown records. No print-based event logs.
  • ComponentRating(voltage=None, current=None, energy=None, pulse_width=None, model=None) declares an absolute-maximum envelope (at least one limit required; each populated limit must be positive), with model recorded in provenance. ComponentStressMonitor(name, port=..., rating=..., voltage_series=..., current_series=...) binds a rating to recorded port time-series monitors. The bound port name is validated against the scene ports at prepare/compile time, so a typo'd port fails closed rather than binding silently.
  • Result.component_stress(name) reduces the bound V(t)/I(t) series into a typed ComponentStressData: power = V*I, cumulative dissipated energy integral P dt (trapezoidal), peak voltage/current/power, total energy, a coarse measured pulse width, and an exceedance summary (rated vs measured, exceeded flag, margin) per channel versus the rating envelope. The float32 device reduction matches an independent float64 reference to tight tolerance. The bound voltage and current series must share a common time grid: mismatched sample counts or differing time axes are rejected (the voltage axis is never silently adopted).

Electrostatic pre-bias initial condition (DC-seeded FDTD)

  • ElectrostaticInitialCondition.from_result(dc_result, tolerance=1e-3) turns a Result(method="electrostatic") into an FDTD pre-bias, consumed by Simulation.fdtd(scene, ..., initial_condition=...). It maps the DC solution onto the Yee grid by interpolating the cell-centred electrostatic potential onto the primary nodes and taking Yee edge differences E = -grad(phi); because the injected field is the discrete gradient of a nodal scalar, its discrete Yee curl is exactly zero, so H (and the CPML memory) start at zero and a lossless interior cell begins in a discrete FDTD steady state (H stays zero, E stays constant with no source). The initial magnetic field is zero (electrostatics carries no H pre-bias).
  • The electrostatic grid must be node-identical to the FDTD scene grid; a mismatch fails closed with a clear message. Compatible boundaries are the grid-non-extending ones (electrostatic BoundarySpec.none() pairs with an FDTD PEC/PMC/periodic boundary); a PML boundary extends the FDTD grid and is therefore rejected by the grid-identity check.
  • After mapping, the run reports a dimensionless discrete-Gauss consistency residual gauss_residual (the Yee divergence of eps_fdtd * E_init versus the electrostatic free-charge density at the shared interior nodes, excluding conductor-adjacent nodes whose induced surface charge is not a free-charge source, normalised by the peak displacement-flux scale). Injection fails closed when the residual exceeds tolerance (documented default 1e-3), so a grossly inconsistent mapping is never silently seeded; a nonuniform / dielectric-interface pre-bias that carries a genuinely larger residual must raise the tolerance explicitly.
  • Fail-closed capability guards (Simulation.fdtd(initial_condition=...)): a non-FDTD method, a distributed / multi-GPU run, a trainable / adjoint run, and a Bloch-periodic (complex-field) run are each rejected rather than silently dropping or mis-seeding the pre-bias. The validated case is a charged parallel-plate configuration whose plates are PEC-material structures.
  • Result.electrostatic_prebias surfaces the pre-bias provenance on the run result (DC solve iterations/residual/energy, per-terminal charges, the mapped-field gauss_residual, and the tolerance). Validation coverage: exact Yee-shape mapping, zero-discrete-curl of the mapped field, uniform-field recovery, grid-mismatch and PML-extension rejection, the fail-closed capability guards, an FDTD steady-state hold of a charged-plate pre-bias (bit-exact interior drift) with a checkerboard-corruption falsification, the Gauss-residual gate, and a cross-feature end-to-end run (pre-bias + IEC 61000-4-2 ESD terminal injection + DielectricBreakdown + BreakdownMonitor).

Deterministic Dynamic Dielectric Breakdown (experimental)

Capability level: deterministic-breakdown, uncalibrated (a comparison / conductive-path model, not a validated arc or device-failure predictor).

  • DielectricBreakdown(critical_field, post_breakdown_conductivity, minimum_duration=0.0, model="field_duration", state="latching", recovery=None, damage_parameters=None, ramp_time=None) is a public material descriptor composed into a scalar Material(breakdown=...). A breakdown-capable FDTD cell flips from intact to conducting once its energy-consistent cell-center field magnitude |E| stays at or above critical_field for a contiguous minimum_duration; on trigger the cell conductivity ramps linearly from its base value toward post_breakdown_conductivity over ramp_time (default 10 time steps) and, under state="latching", stays conducting for the rest of the run. The conduction enters the standard semi-implicit lossy electric update, so it is unconditionally stable in the target conductivity; a prepare-time warning fires when 0.5*sigma*dt/eps grows large enough that the conducting cell becomes a poorly time-resolved PEC-like reflector.
  • v1 supports exactly model="field_duration" and state="latching"; any other model/state and the reserved recovery / damage_parameters fields fail closed at construction. A Material carrying a breakdown descriptor must be a scalar isotropic bulk medium: PEC, anisotropic-tensor, dispersive, instantaneous-nonlinear, time-modulated, and 2D-sheet materials are rejected.
  • Scenes with no breakdown material take the existing FDTD code path with zero added machinery (bitwise-identical fields): a cheap structure pre-scan gates all breakdown allocation, so a breakdown-free prepare never compiles the per-node layout. A scene whose breakdown cells never reach threshold is bitwise-identical to the same scene without the descriptor.
  • Overlapping structures resolve breakdown capability last-writer-wins in the same priority order as the material compiler: a later, higher-priority non-breakdown structure that overwrites a breakdown region strips the breakdown descriptor from the cells it claims, so an overwritten cell never triggers a phantom breakdown.
  • Result.breakdown_data returns a typed BreakdownResultData (present only for breakdown scenes): a deterministic event log ordered by (step, cell_index) of BreakdownEvent(step, time, cell_index, position, material_id, field_before, state_before, state_after, deposited_energy_at_trigger), a per-cell final-state mask, per-cell cumulative breakdown-dissipated energy integral(sigma_breakdown * |E|^2 dV dt), and the total dissipated energy. Result.breakdown_events exposes the event tuple directly. Events are collected in a bounded preallocated GPU buffer (capacity = breakdown-capable cell count, the exact latching upper bound) and transferred to host only at run end; a capacity overflow is a hard error, never a silent drop.
  • Deterministic single-GPU only this round: trainable scenes are rejected at prepare() (the hard field-duration/latching switch is non-differentiable at the trigger time; a smooth surrogate is deferred), the frequency-domain solver is rejected (no dynamic conductivity update), and multi-GPU runs are rejected at prepare. Breakdown scenes run on the eager step path (no CUDA-graph capture of the coefficient-mutating step).
  • Validation coverage (tests/breakdown/): manufactured golden trigger step (trigger_step = ceil(minimum_duration/dt) - 1), contiguous-timer reset, trigger-time dt convergence with a reported staircase error band, breakdown-dissipation closure against the analytic integral(sigma_breakdown*|E|^2 dV dt), below-threshold six-field bitwise parity, no-breakdown plain-scene determinism and never-compiles-layout gates, structure-overlap last-writer-wins clearing, event-log determinism, and the trainable / multi-GPU / unsupported-model / buffer-overflow fail-closed guards.

Differentiable smooth breakdown-risk surrogate (non-physical, non-regulatory)

Capability level: differentiable-surrogate (non-physical, non-regulatory). This is an optimization objective ONLY, not a breakdown model, a failure predictor, or a regulatory quantity. The hard field-duration/latching DielectricBreakdown feedback stays non-differentiable and trainable scenes that enable it are still rejected at prepare(); this surrogate is a separate non-feedback path that never drives the field solve.

  • SmoothBreakdownRisk(critical_field, width, reduction="sum", temperature=None, damage_exponent=None) defines a smooth, monotone functional of a recorded cell-center |E|(t) series. Its evaluate(e_magnitude_series, dt, occupancy=None) computes, entirely in torch (autograd-preserving, no host sync), the soft exceedance p = sigmoid((|E| - critical_field)/width), the per-cell soft dwell soft_duration = sum_t p dt, and reduces the occupancy-weighted per-cell dwell to a scalar risk (reduction="sum" = soft over-stress dose in seconds, "mean", or "softmax" = temperature-weighted differentiable worst-cell dwell). An optional damage_exponent adds an auxiliary soft_damage map without changing the primary risk. evaluate_from_components(ex, ey, ez, dt) colocates raw node-overhang Yee E blocks through the exact energy-consistent averaging the BreakdownMonitor uses (colocate_electric_magnitude, now batched over a leading time axis), so the surrogate reads the same |E| as the physical stress accumulator, only softened.
  • Result/monitor consumers receive a typed SmoothBreakdownRiskData (risk scalar, per-cell soft_duration_map, optional soft_damage_map, diagnostic peaks, and provenance carrying non_physical=True, non_regulatory=True, the sigmoid-margin definition, and the surrogate model version). The class name, capability_level, and provenance make the non-physical, non-regulatory nature unmistakable.
  • Validation coverage (tests/breakdown/test_smooth_breakdown_risk.py): gradient flows from a source-amplitude and a material-screening parameter through a small differentiable |E|(t) scene to the risk scalar matching central differences to < 1e-4 in float64 (with opposite-sign source vs material sensitivities), autograd reaches torch.nn.Parameter leaves, monotone increase of risk in source amplitude, risk collapse to numerically zero far below threshold (margin ~ -50 widths), batched colocation matching the per-step reduction and the analytic uniform-field magnitude, softmax reduction bracketed between the mean and the hard per-cell peak, occupancy zeroing, and fail-closed config/input validation. Recorded falsifications: detaching the field (kills the gradient), and a sigmoid sign flip (breaks both monotonicity and the far-below-threshold vanishing).

Network Cascade, Termination, and Passivity Cross-Checks (experimental)

  • NetworkData.cascade(other, port_map=...) connects any set of ports of one N-port network to ports of another N-port network and reduces the joined ports out, returning a reduced NetworkData. The general multiport star connection is implemented from first principles as S'_EE = S_EE + S_EC * P * (I - S_CC * P)^-1 * S_CE (batched over frequency, differentiable, no third-party dependency). Connected ports must share a real reference impedance and the two networks must share a frequency grid; complex reference impedances, mismatched impedances, duplicate connections, empty maps, duplicate result names, and fully-connected (zero external port) results fail closed. Remaining ports keep this network's ports first, then other's. Cascading through an ideal matched thru returns the original network and cascading two attenuators adds their attenuation in dB.
  • NetworkData.terminate(port, gamma=... | impedance=...) closes one port with a reflection coefficient or a load impedance (scalar or per-frequency), returning the reduced network over the remaining ports via S'_KK = S_KK + S_Kp * gamma * (1 - S_pp * gamma)^-1 * S_pK. For a two-port terminated on its second port this equals the closed-form input reflection S11 + S12*gamma*S21/(1 - S22*gamma). Both helpers preserve autograd through the connection algebra and record their operation in the network transform history.
  • Independent raw-sample cross-check (tests/rf/network/test_network_cascade_crosscheck.py): a bare three-port FDTD device S is measured by a port sweep, then a network's raw Touchstone samples (read directly, not the rational fit) are connected across two device ports with NetworkData.cascade; the resulting input reflection matches the same network embedded in the time domain (rational fit + state-space stepping) to < 1e-5 across the band on a lossy and a reactive network. The reference path (raw samples + connection algebra) shares no code with the embedded path.
  • Multi-scenario passivity/conservation gates (tests/rf/network/test_network_conservation.py): for a lossy two-port, a reactive two-port, and a four-port embedded run, each FDTD run gates terminal power balance (field-solve V + solved I vs field-solve V + model admittance), time-domain passivity (negligible generated energy and non-negative running cumulative net energy sampled over the pulse), and time-domain stability (net energy convergence and dynamic-state ring-down between run lengths T and 2T).

Embedded-Network Coupling Fixed-Cost Reduction and Delay Checkpoint/Resume

  • The delay-free embedded-network same-step coupling solve now applies the constant direct-loop operator through two precomputed composite matvecs, branch_current = (M^-1 C) @ state + (M^-1 D) @ v, instead of a per-step sequential pivoted-LU triangular substitution. The composite operators are formed once at prepare time by LU-solving the constant C and D against the loop matrix M = I + D * diag(Z_f) (not by naive matrix inversion), so the eager and CUDA-graph coupling paths become bitwise-identical and the per-step kernel-launch count of the connected 8-port/order-32 feedback block drops from 78 to 27 (65%). The op-stream before/after evidence is a reproducible artifact (docs/assessments/e4-network-coupling-op-stream-2026-07-19.json) and gate (tests/rf/network/test_network_coupling_op_stream.py); numerics are unchanged within the direct loop's floating-point roundoff bound and the existing multiport/ill-conditioned parity gates.
  • Embedded networks with explicit port delay now checkpoint and resume correctly: the frozen FDTD checkpoint schema captures the bidirectional reference-plane rings, the Thiran fractional-delay filter memory, and the shared ring cursor, so run_until(k) + run(resume_from=...) reproduces the uninterrupted delayed-network fields and diagnostics bit-for-bit (tests/rf/network/test_network_delay_checkpoint.py). Previously a resumed delayed network silently restarted its reference planes from zero.
  • Differentiable adjoints of explicit-delay embedded networks remain fail-closed with a precise rejection reason (the bidirectional ring couples steps up to max_delay_steps apart, possibly across checkpoint segments, and the fractional-delay filter is an IIR recurrence, neither of which the segment-local network pullback reverses); forward runs including checkpoint/resume are fully supported.
  • WavePort embedding stays fail-closed with an accurate rejection message: an embedded state-space network couples through a scalar voltage/current terminal on a single lumped Yee edge (LumpedPort or resolved TerminalPort), but a WavePort is a modal port with no scalar time-domain terminal (V, I) contract. This is a missing design contract, not a bug.

RF wave-level bench fixes (WavePort monitor passthrough, lumped calibration/RLC rebuild)

  • User-declared monitors (e.g. PlaneMonitor) now ride through WavePort direct excitations and PortSweep Results instead of being silently dropped. A direct PortExcitation of a WavePort is a single drive column, so its user monitors map unambiguously to that excitation and appear on Result.monitor(...) identical to a plain FDTD run of the injected mode. A PortSweep drives one channel per column: the flat top-level Result.monitors carries the first drive channel (recorded in Result metadata as user_monitor_drive_channel / user_monitor_frequency), and per-drive / per-frequency field payloads are preserved column-by-column in Result.array_run_data.column_results. The internal per-port ModeMonitors that extract the S-matrix stay hidden. This unblocks field-level inspection/falsification of the RF wave benches.
  • benchmark/scenes/rf/lumped_open_short_match.py (coax_sol_scene) is rebuilt as a coax one-port short-open-load (SOL) calibration bench on the proven air coax line: a TEM WavePort feed launches down the line to a de-embedded load plane terminated by a matched (reflectionless coax-through-PML, presenting Z0), a short (PEC plug), or an open (truncated inner rod below the outer-guide TM01 cutoff). The feed is now coupled to the load, so the three standards are mutually distinguishable (matched |Gamma| <= -20 dB; short/open |Gamma| ~ 1; open in the +1 class and short in the -1 class after short-referenced de-embedding), fixing the retired decoupled bench that read identical Gamma for every load. The open-end fringe capacitance shift is measured and documented.
  • benchmark/scenes/rf/series_parallel_rlc.py (series_rlc_scene) is rebuilt to insert the series/parallel RLC as an in-line two-terminal element in the coax inner conductor ahead of a matched continuation, so the element carries the full axial line current and its resonance controls the feed reflection. The series |S11| notch tracks the analytic f0 = 1/(2*pi*sqrt(L C)) (f_res*sqrt(C) constant to ~1%, moving by the analytic 1/sqrt(C) ratio under a +/-20% C change) and the parallel anti-resonance peak moves monotonically with C, fixing the retired parasitic-dominated bench whose peak did not track C. The consistent ~13% parasitic downshift of the absolute resonance is measured and documented.

FDTD antenna benchmark scenes with real Result.antenna end-to-end gates

  • benchmark/scenes/antenna/half_wave_dipole.py (half_wave_dipole_scene) builds a center-fed thin-wire half-wave dipole (two collinear PEC arms joined by a node-bound LumpedPort gap feed) enclosed by a ClosedSurfaceMonitor NF2FF box. A real FDTD Scene -> Simulation -> Result run consumed through Result.antenna(...) -- with NO monkeypatched surface currents or far field -- reproduces the canonical dipole: E-plane sin^2(theta)-pattern correlation

    = 0.99 (measured 0.996 at the design frequency), peak directivity in the 2.15 dBi class (measured 2.19 dBi, analytic 2.156), and radiated-vs-accepted power closure < 8% (measured ~4%). The input resistance sweeps through the thin-dipole 73 Ohm radiation-resistance class within the band (measured 20 -> 88 Ohm, crossing 73 Ohm, with samples inside 60-90 Ohm). The input reactance carries a large positive delta-gap feed offset (the FDTD electrical resonance sits above the physical half-wave frequency); this is documented and deliberately not gated, rather than hidden.

  • benchmark/scenes/antenna/patch.py (patch_antenna_scene) builds a probe-fed rectangular microstrip patch on a FINITE grounded dielectric slab (finite substrate + ground so the NF2FF box lies in a homogeneous air exterior; an infinite substrate running into the PML would leave no valid Huygens surface). All critical planes (ground z=0, patch underside z=h, feed terminals) land on exact Yee nodes via integer-cell GridSpec.custom coordinates (arange*dx), because GridSpec.uniform's float ceil cell count can overshoot by one cell. The real Result.antenna(...) pipeline runs end to end and returns valid AntennaData (six air-exterior faces per frequency, finite gains, positive radiated power, radiated-vs-accepted power closure). The matched-broadside TM010 resonance and the D >= 5 dBi gate are a DOCUMENTED GAP recorded as a strict xfail: the probe on this thick finite-ground slab is reactance-dominated (|Gamma| ~ 1) and the pattern is off-broadside; feed/ground redesign and the external-reference cross-check are deferred to stage E2c.
  • tests/rf/antenna/test_antenna_benchmark_e2e.py drives both scenes through the real (non-monkeypatched) Result.antenna path on CUDA and enforces the gates above; the unit-level synthetic-surface reduction tests in test_result_antenna.py are retained as the fast kernel coverage.

External-reference generation wiring and antenna benchmark registration (M3)

  • python -m benchmark.rf_tidy3d_references [scene ...] now performs a real adapter-driven external-reference-solver generation attempt for the RF / antenna scenes instead of only stamping pending markers. For each target it exports the Scene through Scene.to_tidy3d, gates on the export being physically runnable (at least one source), and only then estimates the cloud cost, enforces the per-scene FlexCredit budget, runs one cloud job, extracts the monitors, and writes the .h5 cache plus a .generated.json record with the task id and cost. A non-runnable export (or any cloud failure) is recorded fail-closed as a reference: pending-generation marker carrying the concrete reason; it never fabricates a numerical cross-reference, and the analytic reference keeps binding.
  • The four owner-authorized targets (rf/coax_thru, rf/lumped_open_short_match, antenna/half_wave_dipole, antenna/patch) currently fail-close at the runnable gate with sources=0: their excitation is port-driven (a WavePort TEM launch under PortSweep / PortExcitation, or a LumpedPort wire-gap / probe feed), and the adapter's source conversion has no port/lumped mapping, so the exported reference simulation has nothing to drive. Generation is refused BEFORE any cloud cost is incurred (zero FlexCredits spent); mapping port/lumped excitation to the reference solver is a deferred adapter feature. The per-scene outcome is recorded in an ## RF / antenna external reference generation section of benchmark/RESULTS.md.
  • The FDTD antenna scenes are now registered in the RF validation harness: python -m benchmark rf antenna/half_wave_dipole antenna/patch runs the real Result.antenna path and writes an ## Antenna wave-level validation section to benchmark/RESULTS.md (each scene family owns its own section, so an antenna-only run does not overwrite the RF section). antenna/half_wave_dipole is a radiation-physics PASS (broadside directivity ~2.19 dBi, E-plane sin^2 pattern, radiated-vs-accepted power closure, radiation resistance through the 73 Ohm class); antenna/patch is a pipeline pass with a documented off-broadside physics gap.
  • tests/rf/wave_validation/test_rf_reference_generation.py covers the M3 wiring: the four targets export source-less and fail-close without fabricating a cache, and the runnable -> cloud -> cache branch is proven reachable (gate forced open with a stubbed cloud run) so the wiring is not a vacuous always-pending stub.

Yee-staggered transverse full-vector mode operator (internal, E1a)

  • The transverse full-vector mode eigensolver gains a genuinely Yee-staggered operator builder (_build_yee_transverse_operator_sparse) that keeps each transverse electric component on its own Yee location (Eu on the u-half / v-node grid, Ev on the u-node / v-half grid) — the exact 2D restriction of the 3D Yee cell. Longitudinal Ez/Hz are eliminated analytically to give a real symmetric P et = beta^2 et eigenproblem on et = (Eu, Ev), with the metallic walls treated symmetrically by construction (Dirichlet for the tangential component, natural/Neumann for the normal component). For a homogeneous cross-section it reproduces the closed-form discrete waveguide eigenpairs to machine precision (TE10/TE20 sin-profile correlation >= 0.9999 and checkerboard_fraction < 0.05, versus the 0.51–0.59 correlation cap and > 0.35 checkerboard of the legacy centered branch), and its per-component stencil graph is connected (no odd/even sublattice decoupling). Non-magnetic (mu = 1) dielectric cross-sections only; diagonal per-component permittivity is supported at the correct Yee sample locations for the inhomogeneous (hybrid-mode) case. This builder is not yet wired into the mode selector (that integration, plus the microstrip / differential-pair hybrid gates and un-xfailing the TE10 selector pins, is the E1b deliverable).

Yee-staggered transverse operator wired into the WavePort / ModeSource selector (E1b)

  • The homogeneous non-magnetic full-vector mode path (hollow metallic waveguides, uniformly dielectric-filled guides, and free-space WavePort apertures) now solves on the Yee-staggered transverse operator end to end: the selector interpolates the reconstructed transverse fields onto the aperture node grid, so a closed metallic guide returns a genuine TE10 whose Ez profile is a clean full-grid sin(pi y/a) (sin-correlation >= 0.9999 across the dx = 0.05 … 0.01 tiers and at 6 fc), replacing the retired centered branch whose sublattice decoupling capped the correlation at 0.51–0.59. The propagation constant, sin-profile, and modal wave impedance now match the analytic TE10 references, and the hardened selector filters (transverse-null-space beta -> k0 rejection, checkerboard/duplicate diagnostics, forward-power and requested-polarization family selection, fail-closed raise on a genuinely absent requested mode) are preserved. A uniformly dielectric-filled aperture carries its real permittivity (the operator equals the vacuum operator plus a scalar (eps_r - 1) k0^2 shift, so beta^2 = eps_r k0^2 - kc^2, not the vacuum value). Routing to this path requires mu = 1: inhomogeneous (dielectric-graded) and magnetic (mu_r != 1, uniform or graded) cross-sections continue on the diagonal-anisotropic operator, which threads mu through the eliminated longitudinal fields.
  • The transverse operator's inhomogeneous hybrid capability is validated directly: a half-filled parallel-plate cross-section reproduces the 1D slab-loaded LSE Sturm-Liouville spectrum to machine precision and converges to the analytic transverse-resonance (k1 cot(k1 d) + k2 cot(k2 (a-d)) = 0) propagation constant, with the mode uniform along the plates, Ev-polarized, and concentrated in the high-permittivity region.

Interior-PEC masking on the staggered mode operator and quasi-static line modes (F2a)

  • The Yee-staggered transverse mode operator now supports interior perfect-conductor masking: a transverse-field sample whose staggered Yee location falls inside a conductor is eliminated (Dirichlet 0) with the same symmetric row/column removal as the outer metallic walls, and a longitudinal node inside a conductor drops from the eps_ww divergence coupling — no penalty terms, no operator asymmetry. Conductor occupancy is rasterized onto the three staggered component grids with the same node→half placement the dielectric staggering uses, and a connectivity check reports the number of conductor-free regions and distinct conductors and fails closed on a degenerate pinch (a conductor-free node fully surrounded by conductor). This serves the guided (non-TEM, hybrid) interior-PEC modes; validated against a PEC-septum half-guide whose transverse cutoff rises analytically from k~c = pi/2 to k~c = pi when the septum splits the guide (masked beta within 0.5% of sqrt(k0^2 - pi^2)). The curl-curl beta^2 operator does carry the gradient TEM branch in its spectrum (a curl-free -grad(phi) field with div(eps grad phi) = 0 is an exact eigenvector at beta^2 = eps k0^2); the shipped occupancy rasterization (threshold 0.5) eliminates the conductor-surface straddling normal-E samples where the TEM field energy concentrates, so the TEM branch is absent from this masked reduced operator by that masking choice rather than as a structural property. A TEM request on the masked operator therefore fails closed and routes to the quasi-static engine (a strictly-interior keep-straddle rasterization would recover the exact TEM eigenvalue but would leave one-cell-thick conductor sheets unmasked).
  • A quasi-static electrostatic line-mode engine solves the TEM/quasi-TEM interior-PEC transmission-line families (coax, microstrip, differential pair) via the capacitance ratio eps_eff = C / C0 from a variable-coefficient div(eps grad phi) = 0 solve, with beta = k0 sqrt(eps_eff). Boundary-connected conductors are grounded and each isolated interior conductor is driven by a caller-supplied potential (single-signal lines, or even [1,1] / odd [1,-1] for a pair). Verified: a uniform coax returns eps_eff = eps_r exactly and beta = k0 sqrt(eps_r) matching the legacy electrostatic path; a shielded microstrip (eps_r = 4, W/h = 2) returns eps_eff within 0.7% of the Hammerstad–Jensen (1980) closed form (pre-registered 3% gate); a differential pair returns two distinct, physical even/odd modes (even eps_eff > odd) whose potential fields are mirror-symmetric / antisymmetric about the pair centreline.

Production quasi-TEM wave-level benches (microstrip / differential pair) and patch feed diagnosis (F2b)

  • The inhomogeneous interior-PEC quasi-TEM mode is now wired into the production WavePort path: when the closed-form uniform-fill TEM solve fails closed on a substrate+air cross-section, a non-magnetic aperture falls through to the quasi-static electrostatic line-mode engine (mode_solver_kind = "quasistatic_line_torch", eps_eff = C/C0), while a uniform (air) line keeps the closed-form electrostatic solve and a magnetic inhomogeneous line re-raises. The drive potentials for a coupled aperture are selected from the isolated-conductor count and mode index (single-signal [1]; even [1,1] / odd [1,-1] for a two-signal pair).
  • python -m benchmark rf rf/microstrip_two_port and rf/differential_pair are unblocked (were BLOCKED). Both scenes were rebuilt on the coax_thru precedent: the measurement ports sit near the origin so the single-precision current-contour planes stay on the Yee half-grid, the ground/substrate/strips run through the computational PML so the launched waves terminate, and integer-cell node arrays (GridSpec.custom(arange*dx)) put every conductor face and contour on exact Yee nodes/half-nodes. The terminated two-/four-port yields a well-conditioned (cond(A) ~ 1.2-1.3) S-matrix (B = S*A extraction); against the shared 1.10 passivity precedent the microstrip two-port is passive (max sv ~1.09, recorded gap) while the differential pair's max sv ~1.18 exceeds it and is recorded fail, not forced. The differential pair converts to mixed-mode with |Sdd21| != |Scc21| (genuine even/odd coupling) and |Sdc21| ~ 0 (mirror-symmetry). The absolute quasi-TEM eps_eff carries a documented, first-order resolution gap (the thin substrate under-loads the discrete field at feasible dx, converging toward Hammerstad with aperture resolution) — recorded, not forced to pass. Gates: tests/rf/wave_validation/test_microstrip_diffpair_wave_level.py.
  • The probe-fed patch antenna feed gained a galvanic PEC probe via (patch underside to a single-cell lumped-port gap above ground), cutting the feed reactance ~5x. A wide-band diagnosis (recorded in the F2b acceptance doc) shows the patch still does not resonate at feasible resolution (Re(Zin) < 4 Ohm, no resonance peak across 2-8 GHz, capacitive reactance, broadside-null via-monopole pattern); the matched-broadside TM010 gate stays a fail-closed strict xfail pending a wire-bound clean-gap feed + larger finite ground + on-resonance drive.

Rectangular-waveguide wave-level PASS and external-reference cross-check (integration)

  • python -m benchmark rf rf/rectangular_waveguide is now a committed wave-level PASS on the Yee-staggered transverse operator. The terminated hollow-guide TE10 two-port S-matrix is assembled by solving B = S*A across the drive columns and gated on extraction conditioning (cond(A) <= 10) plus post-solve passivity (max singular value <= 1.05), then beta(omega) from arg(S21)/L is compared against the analytic TE10 dispersion beta = sqrt(k0^2 - (pi/a)^2) across 11 frequencies above cutoff. Measured (dx=0.02): sin(pi y/a)-correlation 1.0000, cond(A) ~ 1.09, max singular value ~1.0007, |S11| best ~2e-4, |S21| ~ 1, and beta median relative error ~0.05% against a pre-registered 1% tolerance (all three grid tiers pass). The mode-shape correlation check is retained as a fail-closed regression guard (< 0.9 records BLOCKED rather than reporting a spurious S-matrix). Committed gate: tests/rf/wave_validation/test_waveguide_wave_level.py (conditioning + passivity + beta, plus a reference-plane-length falsification).
  • The external-reference-solver generation path (python -m benchmark.rf_tidy3d_references) gains a runnable rf/rectangular_waveguide target: a TE10 ModeSource-driven guide with two ModeMonitor planes exports through the interoperability adapter with a genuine reference source (sources=1), so one cloud job produces an S-parameter cross-reference. The reference forward-mode-amplitude phase constant confirms the analytic TE10 beta(omega) to ~1.2% median over the band; the analytic dispersion remains the binding first-line reference. The remaining port/lumped-driven RF/antenna targets still fail-close at the sources=0 runnable gate (deferred adapter source mapping) and spend no cloud credits.

Coupled FDTD + circuit (MNA) energy-conservation validation (F1a)

  • Validation evidence, not a new public feature: a multi-scenario coupled FDTD + MNA global energy-residual suite (tests/rf/circuits/test_circuit_conservation.py) closes the whole-system energy balance source injected = delta EM stored + circuit dissipated + circuit stored for three strongly coupled scenarios driven from an in-circuit source in a closed (PEC, zero-outflow) vacuum box: (a) a resistive load on a driven lumped terminal port, (b) a resonant series RLC assembled from MNA primitives (not the native SeriesRLC), and (c) a controlled-source (VCVS) network. The EM-stored term is measured from the raw Yee E/H fields (0.5 eps E^2 + 0.5 mu H(n-1/2).H(n+1/2)), independent of the port record; the circuit terms come from the MNA companion state. The suite annotates gate classes honestly — the source/dissipation/circuit-store channels are consistency-class (Tellegen / companion algebra) and the genuine two-sided content is the field-link equality delta EM stored == -(port work) (raw fields vs MNA V/I record, no shared code) — and each headline channel has a recorded falsification. Pre-registered tolerances: global residual <= 5e-3 of throughput, field-link residual <= 2e-2 of peak field energy.
  • Validation evidence, not a new public feature (F1b, independent circuit cross-check): tests/rf/circuits/test_circuit_independent_crosscheck.py characterizes an EM one-port (open, PML-terminated box with a LumpedPort) by a passive port-admittance sweep, fits the measured Y_em(f) to a low-order stable rational (fit_rational, data-fitting only), derives the series-loop equivalent-circuit ODE state equations by hand, and integrates them with scipy.integrate.solve_ivp for a different drive/resistance than the sweep. The independent transient reproduces the coupled FDTD + MNA run's port voltage to ~1.2e-5 (relative, headline gate) with no shared runtime code between the two paths, lifting the coupled circuit transient off the consistency class. A recorded falsification perturbs the MNA field-port companion conductance and shows the cross-check goes red.

RF port / lumped-feed excitation mapping through the reference adapter (F2c)

  • The interoperability adapter (Scene.to_tidy3d) now maps RF port excitations to reference-solver drive constructs, so port-driven scenes export as genuinely runnable reference simulations (previously they exported with sources = 0 and fail-closed at the generation runnable gate). A WavePort TEM aperture maps to a reference modal launch (ModeSource) of its fundamental mode plus a receiving ModeMonitor at every wave-port aperture, so a terminated port line exports with S-parameter monitors. A LumpedPort delta-gap feed — wire-bound (dipole wire-gap) or coordinate-bound (patch probe) — maps to its equivalent current injection: a UniformCurrentSource electric-current filament spanning the feed gap along the negative→positive voltage-path axis, interpreted as a unit total feed current. The near-field-to-far-field ClosedSurfaceMonitor box lowers to its six face field monitors through the existing monitor path, so the antenna near-field surface exports intact. A single export uses one documented drive convention (drive the first declared port, index 0); a full N-port scattering reference is one export per driven port. Ports carry no source_time, so the adapter synthesizes a broadband Gaussian drive covering the requested frequency band. Gates: tests/api/adapters/tidy3d/test_port_source_mapping.py.
  • The external-reference generation path (python -m benchmark.rf_tidy3d_references) now generates all five RF/antenna reference caches — rf/rectangular_waveguide, rf/coax_thru, rf/lumped_open_short_match, antenna/half_wave_dipole, antenna/patch — one authorized cloud job each (0.025 FlexCredits each), with task ids and costs recorded in benchmark/RESULTS.md and the F2c acceptance doc. A new --from-markers mode rebuilds the RESULTS.md aggregate table from the on-disk generation markers without any cloud call, so per-scene runs can be re-aggregated without re-spending. The analytic transmission-line / waveguide / dipole references remain the binding first-line gate.

Array scene-parameter gradient VJP through the embedded-pattern basis (F3a)

  • ArrayBasisData.scene_gradient_vjp(columns=..., weights=..., parameters=..., objective=...) aggregates the per-column adjoints of the linear beam combine E = sum_n w_n e_n back onto trainable scene parameters (a design Box / MaterialRegion density), delivering scene/material/geometry gradients that the retained detached-column basis could not. The caller re-runs each drive column's forward under autograd and passes the resulting live embedded-pattern columns ((e_theta_n, e_phi_n) of shape [F, T, P]); the method forms the combined far field, takes the combined-field cotangent cot_E = autograd.grad(L, E), seeds each column with the derived weight conjugation conj(w_n) * cot_E (= w_n^* . (dL/dE)^*, exactly the PyTorch complex-product backward of the combine), and sums the per-column vector-Jacobian products in a caller-controllable deterministic reduction_order. The seeded per-column sum is bit-identical to end-to-end autograd of the combined objective for a fixed order and agrees across orders to floating-point round-off; a central-difference gate on a genuine two-column FDTD array (trainable MaterialRegion density, NF2FF far-field columns) matches the aggregated gradient at the FDTD-adjoint tolerance. Weights accept a frequency-flat [N] or exact [F, N] incident power-wave vector; either an objective callable on the combined field or pre-computed field_cotangents may drive the VJP. Weight gradients through combine() are unchanged (regression-gated). Detached columns, a no-contribution parameter set, batched [B, F, N] weights, wrong column counts, and shape/dtype/device mismatches fail closed with ValueError/TypeError. The 2-GPU ensemble aggregation of the same per-column VJPs is the F3b follow-on.

2-GPU ensemble aggregation of the array scene-gradient VJP (F3b)

  • aggregate_scene_gradient_vjp(basis, columns=..., parameters=..., weights=..., objective=...) reduces per-column scene-gradient VJPs whose columns may live on different devices, returning an AggregatedSceneGradient (the reduced gradient plus provenance: the fixed reduction order, the reduction device, the per-column devices, and the port order). The combined field and its cotangent are formed on the reduction device from detached column values; each column is seeded with conj(w_n) * cot_E on its own device, back-propagated there, moved to the reduction device, and summed in a fixed public-port order. parameters is a per-column sequence of trainable leaf(s) — the same object for every column in the single-device case, or a per-device replica of the shared design in the multi-GPU case; the summed per-column VJPs are the gradient of that shared design. The reduction reproduces the single-device ArrayBasisData.scene_gradient_vjp bit-for-bit for the same order.
  • ensemble_scene_gradient_vjp(basis, column_forward=..., weights=..., execution=mw.MultiGPUExecution.ensemble(devices=...), objective=...) distributes the per-column forwards over the ensemble device pool as independent tasks (each column_forward(index, device) runs column index's forward on device under autograd and returns its live embedded far-field column plus the trainable leaf(s)), then reduces with aggregate_scene_gradient_vjp. The per-column adjoint is not routed through run_many (which refuses trainable simulations); the pool only places and orders the forwards, and the seeded backward + deterministic reduction run on the caller thread. 1-GPU-vs-2-GPU aggregated-gradient parity is bitwise on homogeneous GPUs (measured maxabsdiff 0 on both a synthetic float64 map and a real two-column FDTD array with a trainable MaterialRegion density split one column per GPU). A non-MultiGPUExecution execution, a failed column forward, detached columns, an inconsistent per-column parameter structure, and the F3a validation contracts all fail closed. The NCCL joint-solve adjoint remains out of scope (independent Simulations only); no timing/throughput scaling is claimed.

Edge-native per-Yee-component material sampling and conformal-PEC benchmark default (F4)

  • FDTD material coefficients are now sampled edge-native: the diagonal background permittivity / permeability and the static electric / magnetic conductivities are evaluated directly at each Yee component's own staggered location (Ex/Ey/Ez edges, Hx/Hy/Hz faces), with the SDF occupancy, the interface normal, and any MaterialRegion density sampled there and the polarized (Kottke) or arithmetic subpixel blend formed at that location. This replaces the previous node-centered blend followed by an arithmetic node->edge average (the "smear"), which applied the interface operator at the wrong place and then interpolated it. The node-centered model is still produced as the canonical representation for summaries, monitors, the mode solver, and the SAR / mass models; only the FDTD update coefficients switch to the edge fields. This is the standard path for isotropic, axis-aligned diagonal-anisotropic, and PerturbationMedium families (with or without static conductivity, dispersion, nonlinearity, or modulation layered on the edge-native background). Full off-diagonal anisotropy, 2D sheets, and surface-impedance metals fail closed to the node->edge path (unchanged capability scope; no guard added or removed).
  • The differentiable material VJP follows the forward: when edge-native sampling is active the permittivity sensitivity back-propagates directly through the edge fields (no node->edge transpose), so geometry, region-density, and diagonal-anisotropy gradients stay consistent by construction.
  • The benchmark harness default is pec="staircase" (F4 briefly made it pec="conformal"; that was reverted in K1 after the conformal PEC edge fill was given compact support and the residual spurious absorption on cut edges was measured). Scenes that want the conformal boundary opt in per scene with an explicit SubpixelSpec. Dielectric scenes are unaffected either way.

Multi-GPU NCCL trainable-density adjoint (one-process-per-GPU)

  • A trainable Box-MaterialRegion density scene now backpropagates over a single-node one-process-per-GPU NCCL launch (transport="nccl", torchrun --nproc-per-node=<gpus>), not only the in-process transport="cuda_p2p" runtime. The per-rank collective reverse driver (run_nccl_distributed_reverse) runs the distributed forward with per-rank checkpoints, replays each checkpoint segment with NCCL forward-replay dict halos, seeds a separable local objective per rank (a point monitor is owned by exactly one rank; every other rank seeds zero and receives adjoint only through the transposed NCCL halos), runs the transposed reverse with this track's NCCL adjoint halos, gathers the per-rank grad_eps owned slabs to rank 0, and runs the single-GPU material pullback once on rank 0. The open-boundary standard update and the x-CPML absorbing update (including objectives whose sensitivity threads the CPML psi memory recursion) are both supported.
  • Objective + gathered-grad_eps gradient parity against the single-process single-GPU adjoint is gated on two processes for the open-boundary standard geometry and the psi-active x-CPML geometry (loss rtol 5e-5 / atol 5e-6; grad rtol 1e-4 with an atol floor 1e-6*max|grad|), the gathered grad_eps is bitwise reproducible across repeated backward passes, and an unsupported-adjoint scene (e.g. a trainable density on a legacy graded-sigma absorber) is rejected cleanly and symmetrically on every rank without deadlocking.
  • The reverse gradient is load-safe: the headline parity gates (standard / x-CPML / seam-spanning plane) hold at the same honest 1e-4-class tolerance while both GPUs are saturated by a co-tenant burner (a committed stress gate spawns the load), and a rank-0-only gather-capacity failure raises collectively on every rank without hanging. This closes a caching-allocator cross-stream reuse hazard in which the reverse/replay NCCL halos previously ran on a non-default stream while their per-step adjoint planes were allocated on the default stream, deterministically corrupting the partition-seam gradient under concurrent GPU load; the halos now run on the current (default) stream so allocation-stream == use-stream.
  • The forward-only NCCL fence still rejects a trainable/monitor scene on the plain NCCL forward path; the relaxation is internal to the verified adjoint driver.
  • A y/z-normal PlaneMonitor objective is now also supported on the NCCL adjoint driver: the plane is tiled across the x seam, so a sum|spectrum|^2 objective restricted to each rank's owned plane strip (the forward monitor-merge owned-local x slice) is separable -- the world sum reproduces the single-process full-plane objective with every seam cell counted on exactly one rank, and each rank seeds only its owned strip (no cross-rank cotangent scatter). Two-process acceptance gates the owned-strip objective and its gathered grad_eps against the single-GPU full-plane adjoint on a plane spanning the seam, plus a seam-ownership falsification (summing each rank's full local strip double-counts the live seam cell and reddens parity). Flux / mode / finite-plane / x-normal plane objectives stay fail-closed (they need seam-crossing tangential-field assembly whose cotangent scatter is not wired), and the in-process transport="cuda_p2p" bridge continues to reject every tiled monitor.
  • The NCCL adjoint driver reproduces the single-GPU reference to ~2e-7 of scale both on exclusive GPUs and under a saturating co-tenant, so no shared-GPU caveat applies to it. The in-process transport="cuda_p2p" bridge is now load-safe too: its own cross-stream drift was a distinct hazard from the NCCL path -- a checkpoint-capture happens-before race, not the allocator-reuse class. The mid-forward checkpoint clone read the persistent field storage on the device default stream while the forward field updates run on each shard's compute stream, so nothing ordered the next update after the clone and under load the update tore the snapshot, drifting the replayed seam gradient (~8e-2) while the forward output stayed bitwise clean. Cloning the checkpoint on the shard's compute stream serializes previous-update -> clone -> next-update on one stream, restoring ~2e-7 parity under a saturating co-tenant burner. A committed stressed gate holds the standard and x-CPML 1-vs-2-GPU parity over six rounds under load at the unchanged honest tolerances, with a falsification that reverts the clone to the default stream and shows the seam drift return. See docs/assessments/i1-p2p-race-acceptance-2026-07-21.md.

Conformal PEC edge fill with compact support (K1)

  • SubpixelSpec(pec="conformal") now derives its per-edge fill from the geometric coverage fraction of each Yee E edge — the union PEC signed distance interpolated between the edge's two endpoint nodes — instead of the two-node average of the tanh-smoothed node occupancy. Because the conformal open fraction multiplies the electric update every step, a fill f is an effective conductivity eps*f/dt on that edge, so the fill must be zero wherever the conductor is not. The new fill is exactly 0 on an edge the surface does not reach and exactly 1 on an edge wholly inside, which removes the multi-cell lossy shell the smoothed occupancy painted around every conductor (5298 vacuum edges per component on a 0.2 m cube at dx = 0.02) and restores the hard short on the face itself (the previous 0.53 fill left a 405 ohm/sq sheet reflecting ~32% instead of ~100%). A grid-aligned PEC slab under conformal now reproduces the staircase result bit for bit, at both the compiled-mask level and the field level.
  • Capability change: conformal no longer places a flat, grid-parallel wall sub-cell — such a wall cuts no tangential edge, so conformal is exactly staircase there. Sub-cell resolution of genuinely cut (curved / oblique) conductors is retained and improved: the compiled conformal mask reproduces a sphere's volume to 0.74% where staircase gives 1.85% (r = 0.11, dx = 0.02). Placing a flat grid-parallel wall sub-cell requires the area-scaled (Dey–Mittra) magnetic update, which remains future work.
  • Documented residual: the soft short is still lossy on genuinely cut edges. A closed PEC cavity holding a PEC sphere retains 0.450 of its energy after 5200 source-free steps under conformal versus 1.000 under staircase (the pre-fix smoothed-occupancy path retained 0.125). staircase is therefore the default everywhere, including the benchmark harness; conformal is opt-in per scene.
  • Gates: tests/materials/compiler/test_pec_conformal_alignment.py (compiled masks, CPU) and tests/validation/physics/test_pec_conformal.py (field level, CUDA). Evidence and falsifications in docs/assessments/k1-conformal-pec-fix-2026-07-22.md.

Distribution

  • GitHub Actions builds Linux CUDA wheels inside manylinux_2_28 and Windows wheels on Windows Server 2022. Release fatbins include native SM87 SASS alongside the maintained CUDA 12.8 architecture set.
  • A witwin-maxwell-v* tag runs the complete Stable ABI compatibility matrix, publishes the verified wheel and source artifacts through trusted publishing, and creates the corresponding release with those artifacts attached.