Quick Start

Build your first scene and run a simulation with WiTwin Maxwell

Overview#

Every WiTwin Maxwell workflow follows the same pattern:

  1. Define a Scene with domain, grid, and boundary
  2. Add structures, sources, and monitors
  3. Create a Simulation and call sim.run()
  4. Read the result

Your First Simulation#

The example below places a dielectric cube in a domain, adds a point dipole source and a probe monitor, runs an FDTD simulation, and backpropagates from the probe value to the cube position.

import torch
import witwin.maxwell as mw
 
# Trainable cube position
box_x = torch.tensor(0.10, device="cuda", requires_grad=True)
box_position = torch.stack((box_x, box_x.new_tensor(0.0), box_x.new_tensor(0.06)))
 
# Build scene
scene = mw.Scene(
    domain=mw.Domain(bounds=((-0.24, 0.24), (-0.24, 0.24), (-0.24, 0.24))),
    grid=mw.GridSpec.uniform(0.12),
    boundary=mw.BoundarySpec.pml(num_layers=2, strength=1.0),
    device="cuda",
    subpixel_samples=5,
)
 
scene.add_structure(
    mw.Structure(
        name="cube",
        geometry=mw.Box(position=box_position, size=(0.18, 0.18, 0.18)),
        material=mw.Material(eps_r=20.0),
    )
)
 
scene.add_source(
    mw.PointDipole(
        position=(0.0, 0.0, -0.06),
        polarization="Ez",
        width=0.04,
        source_time=mw.GaussianPulse(
            frequency=1.0e9,
            fwidth=0.25e9,
            amplitude=50.0,
        ),
    )
)
 
scene.add_monitor(mw.PointMonitor("probe", (0.0, 0.0, 0.06), fields=("Ez",)))
 
# Run simulation
sim = mw.Simulation.fdtd(
    scene,
    frequencies=[1.0e9],
    run_time=mw.TimeConfig(time_steps=32),
    spectral_sampler=mw.SpectralSampler(window="none"),
    full_field_dft=True,
)
 
result = sim.run()
 
# Backpropagate from probe
probe = result.monitor("probe")["data"]
loss = torch.abs(probe) ** 2
loss.backward()
 
print("probe =", probe)
print("d(loss)/d(box_x) =", box_x.grad)

What Just Happened#

  • Scene holds the domain, grid, boundary, structures, sources, and monitors
  • Domain defines the simulation volume bounds
  • GridSpec.uniform() sets the spatial resolution
  • BoundarySpec.pml() adds perfectly matched layer absorbing boundaries
  • PointDipole injects an EM source with a Gaussian pulse waveform
  • PointMonitor records the field at a specific point
  • Simulation.fdtd() configures and dispatches the FDTD solver
  • result.monitor("probe") reads the recorded field data
  • loss.backward() propagates gradients back to box_x

Plotting Field Slices#

After running a simulation you can plot a vertical field slice:

result.plot.field(axis="y", position=0.0, component="abs", field_log_scale=True)

Next Steps#