Scene

Reference for Scene, SceneModule, Domain, GridSpec, BoundarySpec, SubpixelSpec, MaterialRegion and the mesh refinement specs.

This page covers the declarative scene types in witwin.maxwell (import as import witwin.maxwell as mw). You need them to set the simulation domain, the Yee grid, the boundary conditions and the subpixel averaging policy, and to collect structures, sources, monitors and ports into one object that you pass to Simulation.

A Scene holds declarations only. The solver compiles it into a grid when you call Simulation.prepare() or Simulation.run(). All lengths are in metres.

import witwin.maxwell as mw
 
scene = mw.Scene(
    domain=mw.Domain(bounds=((-0.64, 0.64), (-0.64, 0.64), (-0.64, 0.64))),
    grid=mw.GridSpec.uniform(0.025),
    boundary=mw.BoundarySpec.pml(num_layers=8),
    subpixel_samples=mw.SubpixelSpec(samples=3, averaging="polarized"),
    device="cuda",
)
scene.add_structure(
    mw.Structure(
        geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(0.24, 0.24, 0.24)),
        material=mw.Material(eps_r=4.0),
        name="dielectric_box",
    )
)

For a task-oriented walk-through of grids and boundaries, see Set up grids and boundaries and Grid, units and boundaries.

Scene#

class Scene(
    domain=None, grid=None, boundary=None, *,
    structures=None, thin_wires=None, sources=None, monitors=None,
    ports=None, networks=(), lumped_elements=(), circuits=(),
    material_regions=None, electrostatic_terminals=None, charge_densities=None,
    metadata=None, device="cuda", verbose=False, lazy_meshgrid=True,
    subpixel_samples=1, symmetry=None,
)

The container for one 3D simulation setup. Every add_* method validates its argument, appends it, and returns the same Scene, so you can chain calls.

domainDomain | Nonedefault None

The physical simulation region. None uses Domain(bounds=((-1.0, 1.0), (-1.0, 1.0), (-1.0, 1.0))). PML cells are added outside these bounds; they never eat into the domain.

gridGridSpec | Nonedefault None

The Yee grid. None uses GridSpec.uniform(0.05).

boundaryBoundarySpec | Nonedefault None

Boundary conditions on the six domain faces. None uses BoundarySpec.none().

structuresIterable[Structure] | Nonedefault None

Initial structures. Each entry must be a witwin.core.Structure (exported as mw.Structure), otherwise TypeError. See Geometry.

thin_wiresIterable[ThinWire] | Nonedefault None

Initial subgrid thin wires, added through add_thin_wire. See Thin wires.

sourcesIterable | Nonedefault None

Initial sources. No type check is done at this point. See Sources.

monitorsIterable | Nonedefault None

Initial monitors. No type check is done at this point. See Monitors.

portsIterable | Nonedefault None

Initial ports, added through add_port after the structures, so a TerminalPort can resolve against them. See Ports and lumped elements.

networksIterable[NetworkBlock]default ()

Embedded N-port networks, added through add_network after the ports. See Networks and rational fitting.

lumped_elementsIterabledefault ()

Standalone Resistor, Capacitor and Inductor elements, added through add_lumped_element.

circuitsIterable[Circuit]default ()

Circuits, added through add_circuit. See Circuits and ESD.

material_regionsIterable[MaterialRegion] | Nonedefault None

Density-parameterized design regions. See MaterialRegion.

electrostatic_terminalsIterable | Nonedefault None

ElectrostaticTerminal conductors for the electrostatic solver. See Electrostatics.

charge_densitiesIterable | Nonedefault None

ChargeDensity sources for the electrostatic solver.

metadatadict | Nonedefault None

Free-form metadata, stored as a copy in scene.metadata.

devicestr | torch.devicedefault 'cuda'

Device for compiled grid tensors. With a CUDA device and no CUDA available, the constructor raises RuntimeError. Pass "cpu" for scene-only or CPU post-processing work; the FDTD and FDFD solvers still require a CUDA scene.

verbosebooldefault False

Verbose flag stored on the scene.

lazy_meshgridbooldefault True

When True, the prepared scene builds the full 3D coordinate meshgrid only when something asks for it. False builds it during preparation.

subpixel_samplesint | tuple[int, int, int] | SubpixelSpecdefault 1

Subpixel material averaging. An int or a 3-tuple sets the samples per cell and keeps the default arithmetic / staircase policy. Pass a SubpixelSpec to choose the averaging and PEC handling. See SubpixelSpec.

symmetrytuple | Nonedefault None

Mirror symmetry per axis, as three entries for x, y and z. Each entry is None, "PEC", "PMC", or a (mode, face) pair such as ("PMC", "high"). A bare mode puts the symmetry plane on the low face. face names the domain face that carries the mirror plane, that is, which half of the full domain was folded away. FDTD requires the boundary on a symmetry face to be none or pml, and no PML layers are placed on that face. FDFD rejects symmetry.

Raises: TypeError for a structure that is not a Structure; RuntimeError when the requested CUDA device is unavailable; plus any error raised by the add_* methods for the initial collections.

Scene attributes#

AttributeTypeContent
domain, grid, boundaryDomain, GridSpec, BoundarySpecThe resolved arguments, with defaults applied.
structures, sources, monitorslistDeclared objects in insertion order.
ports, networks, lumped_elements, circuitslistRF declarations.
material_regionslist[MaterialRegion]Design regions.
electrostatic_terminals, charge_densitieslistElectrostatic declarations.
thin_wirestuple[ThinWire, ...]Read-only view of the declared thin wires (property).
subpixelSubpixelSpecThe normalized averaging policy.
symmetrytupleThree entries, each None or (mode, face) with mode "PEC" or "PMC".
metadata, device, verbose, lazy_meshgridThe stored constructor values. device is a string such as "cuda" or "cpu".

Scene properties#

PropertyReturnsNotes
subpixel_samplestuple[int, int, int]Samples per cell from subpixel.
dx, dy, dzfloatGrid step in metres. Raises ValueError on a GridSpec.custom or GridSpec.auto grid.
grid_spacingtuple[float, float, float]Same as grid.spacing; raises ValueError on a nonuniform grid.
domain_sizefloatLargest domain extent over the three axes, in metres.
boundary_typestrDisplay label: "None", "PML", "Periodic", "Bloch", "PEC", "PMC" or "Mixed".
bc_typestrAlias of boundary_type.
pml_thicknessintboundary.num_layers when any face is PML, else 0.
pml_strengthfloatboundary.strength when any face is PML, else 0.0.
bloch_wavevectortuple[float, float, float]Bloch wavevector in rad/m, or zeros without Bloch faces. Raises ValueError when it is "auto" (resolved only at prepare time).
bloch_phase_factorstuple[complex, complex, complex]exp(1j * k * L) per Bloch axis, 1 elsewhere. Same "auto" restriction.
has_symmetryboolTrue when any axis declares a symmetry plane.

Note

boundary_type has no label for a mur boundary and raises KeyError when every face is mur. Use scene.boundary.kind instead.

Scene.add_structure#

Scene.add_structure(structure) -> Scene

Appends a Structure (geometry plus material). When structures overlap, the one with the higher priority wins; at equal priority the one added later wins. Structures with enabled=False are skipped by the material compiler.

Raises: TypeError if structure is not a witwin.core.Structure.

Scene.add_source#

Scene.add_source(source) -> Scene

Appends a source without type checking. See Sources.

Scene.add_monitor#

Scene.add_monitor(monitor) -> Scene

Appends a monitor without type checking. See Monitors.

scene.add_source(
    mw.PointDipole(
        position=(0.0, 0.0, -0.20),
        polarization="Ez",
        source_time=mw.GaussianPulse(frequency=1.5e9, fwidth=0.5e9),
        name="dipole",
    )
).add_monitor(
    mw.PlaneMonitor(name="field_xz", axis="y", position=0.0, fields=("Ex", "Ez"), frequencies=(1.5e9,))
)

Scene.add_port#

Scene.add_port(port) -> Scene

Appends a ModePort, LumpedPort, TerminalPort or WavePort. A TerminalPort without a wire binding is resolved against the structures already in the scene, so add structures first. A LumpedPort without a wire binding must have both terminals inside the domain bounds.

Raises: TypeError for any other type; ValueError for a duplicate port name or a terminal outside the domain.

Warning

ModePort is experimental. See Ports and lumped elements.

Scene.add_thin_wire#

Scene.add_thin_wire(wire) -> Scene

Appends a ThinWire. Thin wires are kept apart from volume structures and are an FDTD-only model.

Raises: TypeError if wire is not a ThinWire; ValueError for a duplicate wire name.

Scene.add_lumped_element#

Scene.add_lumped_element(element) -> Scene

Appends a standalone Resistor, Capacitor or Inductor with 3D coordinate terminals. Both terminals must lie inside the domain, and they must differ along exactly one axis (an axis-aligned path).

Raises: TypeError for another type or for CircuitNode terminals (add those through Circuit.add); ValueError for a duplicate name, a terminal outside the domain, or a path that is not axis-aligned.

Scene.add_network#

Scene.add_network(network) -> Scene

Attaches a passive NetworkBlock to ports that are already in the scene. Each connected port must be a LumpedPort or TerminalPort, and a port can belong to one network only.

Raises: TypeError for a non-NetworkBlock or a non-terminal target port; ValueError for a duplicate network name, an unknown port, a WavePort target, or a port already connected to another network.

Scene.add_circuit#

Scene.add_circuit(circuit) -> Scene

Appends a Circuit. Names are compared without case.

Raises: TypeError if circuit is not a Circuit; ValueError for a duplicate name.

Scene.add_material_region#

Scene.add_material_region(material_region) -> Scene

Appends a MaterialRegion. No type check is done.

Scene.add_electrostatic_terminal#

Scene.add_electrostatic_terminal(terminal) -> Scene

Registers an equipotential conductor for the electrostatic solver. Terminals live in their own collection and never enter Scene.ports.

Raises: TypeError if terminal is not an ElectrostaticTerminal; ValueError for a duplicate name.

Scene.add_charge_density#

Scene.add_charge_density(charge_density) -> Scene

Registers a volumetric free-charge source for the electrostatic solver.

Raises: TypeError if the argument is not a ChargeDensity.

Scene.clone#

Scene.clone(**overrides) -> Scene

Returns a new Scene with the same declarations. Keyword arguments replace constructor arguments by name, for example scene.clone(boundary=mw.BoundarySpec.periodic()). When device differs from the current device, tensors inside the declarations are copied to the new device; the source scene is not changed.

cpu_copy = scene.clone(device="cpu")

Scene.resolved_sources#

Scene.resolved_sources() -> list

Returns the sources the compilers see. A source that defines resolve(scene) is replaced by its result, and each ModePort adds the mode source it generates.

Scene.resolved_monitors#

Scene.resolved_monitors() -> list

Returns the monitors the compilers see. A monitor that defines resolved_monitors() is expanded, and each ModePort adds its mode monitor.

Scene.boundary_face_kind#

Scene.boundary_face_kind(axis, side) -> str

Returns the boundary kind on one face. axis is "x", "y" or "z"; side is "low" or "high".

Scene.pml_thickness_for_face#

Scene.pml_thickness_for_face(axis, side) -> int

Returns the PML layer count on one face, or 0 when the face is not PML or carries a symmetry plane. axis must be lowercase "x", "y" or "z".

Scene compile methods#

These methods run the solver preparation layer for one part of the scene and return the compiled records. The scene itself is not changed. Use them to inspect or validate declarations before a run; Simulation calls the same code itself.

MethodCompiles
compile_ports(*, device=None)Port geometry.
compile_waveports(*, device=None)RF wave-port cross sections, without solving their modes.
compile_lumped_elements(*, device=None)Standalone R, L and C elements.
compile_networks(*, dt, device=None)Embedded networks for a time step dt in seconds.
compile_circuits()Circuit topology.
compile_thin_wires(*, device=None)Thin wires.
compile_wire_monitors(*, device=None)Thin-wire monitor sampling.
compile_array_monitors(**kwargs)Closed-surface sampling for an array basis sweep. Pass monitor= to compile one ClosedSurfaceMonitor; otherwise every one in the scene. Raises ValueError when the scene has none.
compile_electrostatics(boundary=None, *, dtype=torch.float64)The electrostatic block (permittivity, free charge, terminal masks).

Scene.to_tidy3d#

Scene.to_tidy3d(*, frequencies=None, run_time=None, **kwargs) -> tidy3d.Simulation

Converts the scene to a Tidy3D simulation. frequencies (Hz) sets monitor and source frequencies; run_time (seconds) is estimated from the domain size when omitted; extra keyword arguments go to tidy3d.Simulation. Requires the tidy3d package. See Adapters.

SceneModule#

class SceneModule(torch.nn.Module)

A torch.nn.Module that builds a Scene. Subclass it, hold trainable torch.nn.Parameter tensors as attributes, and implement to_scene(). When you pass a SceneModule to Simulation.fdtd or Simulation.fdfd, the simulation calls to_scene() again at each prepare() and run(), and treats the run as trainable when any module parameter has requires_grad=True. See Differentiability.

SceneModule.to_scene#

SceneModule.to_scene() -> Scene

Returns the scene for the current parameter values. The base class raises NotImplementedError.

import torch
import witwin.maxwell as mw
 
class DensityScene(mw.SceneModule):
    def __init__(self):
        super().__init__()
        self.rho = torch.nn.Parameter(torch.full((2, 2, 2), 0.5))
 
    def to_scene(self):
        return mw.Scene(
            domain=mw.Domain(bounds=((-0.5, 0.5), (-0.5, 0.5), (-0.5, 0.5))),
            grid=mw.GridSpec.uniform(0.25),
        ).add_material_region(
            mw.MaterialRegion(
                name="lens",
                geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(0.5, 0.5, 0.5)),
                density=self.rho,
                eps_bounds=(1.0, 5.0),
            )
        )

Domain#

class Domain(bounds)

The physical simulation box. Immutable.

boundstuple of three (start, end) pairsrequired

((x_min, x_max), (y_min, y_max), (z_min, z_max)) in metres. Each pair must have end strictly greater than start. Values are stored as floats.

Raises: ValueError if there are not three pairs, a pair does not have two values, or an end is not greater than its start.

domain = mw.Domain(bounds=((-0.3, 0.3),) * 3)

Domain.from_domain_range#

Domain.from_domain_range(domain_range) -> Domain

Builds a Domain from six values (x_min, x_max, y_min, y_max, z_min, z_max). Raises ValueError for any other length.

Domain.domain_range#

Domain.domain_range -> tuple[float, float, float, float, float, float]

Property. The bounds flattened to (x_min, x_max, y_min, y_max, z_min, z_max).

GridSpec#

class GridSpec(
    dx, dy, dz,
    x_coords=None, y_coords=None, z_coords=None,
    min_steps_per_wavelength=None, wavelength=None, max_ratio=None,
    override_structures=(), layer_refinement=None,
)

The Yee grid description. Immutable. Build it with one of the classmethods below rather than the constructor. There are three kinds of grid:

KindBuilt byResolved
Uniform or anisotropicGridSpec.uniform, GridSpec.anisotropicOne step per axis.
Custom (nonuniform)GridSpec.customExplicit node coordinates per axis.
Auto (nonuniform)GridSpec.autoNode coordinates generated at prepare time.

Note

FDFD supports uniform and anisotropic grids only. A custom or auto grid with Simulation.fdfd raises NotImplementedError. Use FDTD for nonuniform grids.

dxfloat | Nonerequired

Step along x in metres, or None for a custom or auto grid. dy and dz work the same way.

x_coordsnumpy.ndarray | Nonedefault None

Node coordinates along x for a custom grid. y_coords and z_coords work the same way.

min_steps_per_wavelengthfloat | Nonedefault None

Set by GridSpec.auto. A non-None value marks the grid as auto.

wavelengthfloat | Nonedefault None

Meshing wavelength in metres for an auto grid.

max_ratiofloat | Nonedefault None

Maximum ratio between adjacent cell sizes for an auto grid.

override_structurestuple[MeshOverrideStructure, ...]default ()

Local maximum-step regions for an auto grid.

layer_refinementLayerRefinementSpec | Nonedefault None

Minimum cell count across thin layers for an auto grid.

GridSpec.uniform#

GridSpec.uniform(dl) -> GridSpec

Same step dl (metres) on all three axes. At prepare time each axis gets ceil(span / dl) cells spread evenly over the domain span, so the actual step can be slightly smaller than dl when the span is not a multiple of it.

grid = mw.GridSpec.uniform(0.025)

GridSpec.anisotropic#

GridSpec.anisotropic(dx, dy, dz) -> GridSpec

A uniform grid with a different step per axis, in metres.

GridSpec.custom#

GridSpec.custom(x_coords, y_coords, z_coords) -> GridSpec

A nonuniform grid from explicit node (cell-boundary) coordinates. Each argument is a 1D array-like or torch.Tensor with at least two finite, strictly increasing values, in metres. The first and last node on each axis must match the domain bounds (to 1e-9 of the span); otherwise preparation raises ValueError.

Raises: ValueError for an array that is not 1D, has fewer than two nodes, holds non-finite values, or is not strictly increasing.

import numpy as np
 
x = np.linspace(-0.3, 0.3, 61)
grid = mw.GridSpec.custom(x, x, x)

GridSpec.auto#

GridSpec.auto(
    min_steps_per_wavelength=10, wavelength=None, max_ratio=1.4,
    override_structures=(), layer_refinement=None,
) -> GridSpec

An adaptive nonuniform grid that the solver generates at prepare time. On each axis, every face of a structure or override bounding box becomes a cell boundary. Each interval between faces gets a target step of wavelength / (n * min_steps_per_wavelength), where n is the largest refractive index in that interval, reduced further by overrides and layer refinement. Cells are then graded so adjacent cells never differ by more than max_ratio.

min_steps_per_wavelengthfloatdefault 10

Cells per wavelength in the material. Must be greater than 0.

wavelengthfloat | Nonedefault None

Free-space meshing wavelength in metres. None derives it from the highest source frequency in the scene (the characteristic frequency for broadband pulses). If no source carries a frequency, preparation raises ValueError.

max_ratiofloatdefault 1.4

Maximum size ratio between adjacent cells. Must be greater than 1.

override_structuresIterable[MeshOverrideStructure]default ()

Regions with a local maximum step.

layer_refinementLayerRefinementSpec | Nonedefault None

Minimum cell count across thin intervals between structure faces.

Raises: ValueError for a non-positive min_steps_per_wavelength or wavelength, or max_ratio of 1 or less; TypeError for wrong override or refinement types.

box = mw.Box(position=(0.0, 0.0, 0.0), size=(0.1, 0.1, 0.1))
grid = mw.GridSpec.auto(
    min_steps_per_wavelength=20,
    override_structures=(mw.MeshOverrideStructure(geometry=box, dl=0.01),),
    layer_refinement=mw.LayerRefinementSpec(min_cells=3),
)

GridSpec properties and methods#

MemberReturnsNotes
is_customboolTrue when any axis has explicit coordinates.
is_autoboolTrue for a GridSpec.auto grid.
spacingtuple[float, float, float](dx, dy, dz). Raises ValueError on a custom or auto grid.
is_uniformboolTrue when the three steps are equal. Raises ValueError on a custom or auto grid.
min_spacingtuple[float, float, float]Smallest step per axis. Raises ValueError on an auto grid before preparation.
axis_coords(axis)numpy.ndarray or NoneCustom node coordinates for "x", "y" or "z", or None on a uniform grid.

MeshOverrideStructure#

class MeshOverrideStructure(geometry, dl)

A maximum mesh step enforced inside the axis-aligned bounding box of a geometry. Use it in GridSpec.auto(override_structures=...). Immutable.

geometrygeometryrequired

Any geometry from Geometry. Only its bounding box is used.

dlfloat | tuple[float, float, float]required

Maximum step in metres, as one value for all axes or (dx, dy, dz). Every value must be greater than 0.

Raises: ValueError if geometry is None, dl has the wrong length, or a value is not positive.

LayerRefinementSpec#

class LayerRefinementSpec(min_cells=2, axes=None)

A minimum cell count across thin intervals bounded by structure faces. Use it in GridSpec.auto(layer_refinement=...). Immutable.

min_cellsintdefault 2

Minimum number of cells across each interval. Must be 1 or more.

axestuple[str, ...] | Nonedefault None

Axes to refine, from "x", "y", "z". None refines all three.

Raises: ValueError for min_cells below 1 or an unknown axis name.

LayerRefinementSpec.covers#

LayerRefinementSpec.covers(axis) -> bool

Returns True when axes is None or contains axis.

BoundaryKind#

BoundaryKind = Literal["none", "pml", "periodic", "bloch", "pec", "pmc", "mur"]

A type alias for the face boundary kinds. Strings are case-insensitive when you pass them to BoundarySpec.

KindMeaning
"none"No absorber or periodic wrap; the grid is not extended.
"pml"Absorbing layer. num_layers cells are added outside the domain on that face.
"periodic"Periodic wrap. Must be set on both faces of an axis.
"bloch"Periodic wrap with a phase shift from bloch_wavevector. Must be set on both faces of an axis.
"pec"Perfect electric conductor wall.
"pmc"Perfect magnetic conductor wall.
"mur"First-order Mur absorbing boundary (FDTD). It cannot be combined with Bloch runs.

BoundarySpec.kind can also hold "mixed" when the faces do not all share one kind.

BoundarySpec#

class BoundarySpec(
    kind="none", num_layers=0, strength=0.0, bloch_wavevector=(0.0, 0.0, 0.0),
    x=None, y=None, z=None,
    x_low=None, x_high=None, y_low=None, y_high=None, z_low=None, z_high=None,
)

Boundary conditions on the six domain faces. Immutable. The classmethods below cover the common cases; use BoundarySpec.faces for per-axis or per-face control.

Resolution order: kind sets all faces, an axis override (x, y, z) replaces both faces of that axis, and a face override (x_low, ...) replaces one face. After construction, kind holds the common kind or "mixed", and num_layers and strength are reset to 0 when no face is PML.

kindBoundaryKinddefault 'none'

Default kind for all faces.

num_layersintdefault 0

PML thickness in cells. Must be greater than 0 when any face is PML.

strengthfloatdefault 0.0

PML strength parameter used to scale the absorber profile. Must be 0 or more.

bloch_wavevectortuple[float, float, float] | 'auto'default (0.0, 0.0, 0.0)

Bloch wavevector (kx, ky, kz) in rad/m. Each Bloch axis applies the phase exp(1j * k * L) across its domain length L. "auto" derives it at prepare time from a CW plane-wave TFSF slab source and requires at least one Bloch axis. Reset to zeros when no face is Bloch.

xBoundaryKind | tuple[BoundaryKind, BoundaryKind] | Nonedefault None

Override for both x faces, as one kind or a (low, high) pair. y and z work the same way.

x_lowBoundaryKind | Nonedefault None

Override for one face. x_high, y_low, y_high, z_low and z_high work the same way.

Raises: ValueError for an unknown kind, negative num_layers or strength, a PML face with num_layers of 0, "auto" without a Bloch axis, a Bloch or periodic kind on only one face of an axis, or (in a mixed spec) a Bloch axis whose wavevector component is zero.

BoundarySpec.pml#

BoundarySpec.pml(num_layers=15, strength=1e6) -> BoundarySpec

PML on all six faces.

boundary = mw.BoundarySpec.pml(num_layers=8)

BoundarySpec.none#

BoundarySpec.none() -> BoundarySpec

Kind "none" on all faces.

BoundarySpec.periodic#

BoundarySpec.periodic() -> BoundarySpec

Periodic on all faces.

BoundarySpec.bloch#

BoundarySpec.bloch(wavevector) -> BoundarySpec

Bloch on all faces with bloch_wavevector=wavevector (rad/m, or "auto").

BoundarySpec.pec#

BoundarySpec.pec() -> BoundarySpec

PEC on all faces.

BoundarySpec.pmc#

BoundarySpec.pmc() -> BoundarySpec

PMC on all faces.

BoundarySpec.mur#

BoundarySpec.mur() -> BoundarySpec

First-order Mur absorbing boundary on all faces.

BoundarySpec.faces#

BoundarySpec.faces(
    *, default="none", num_layers=0, strength=0.0, bloch_wavevector=(0.0, 0.0, 0.0),
    x=None, y=None, z=None,
    x_low=None, x_high=None, y_low=None, y_high=None, z_low=None, z_high=None,
) -> BoundarySpec

Keyword-only builder for mixed boundaries. default maps to kind; the other arguments match the constructor.

boundary = mw.BoundarySpec.faces(
    default="pml",
    num_layers=8,
    x="bloch",
    y="periodic",
    bloch_wavevector=(5.0, 0.0, 0.0),
)

BoundarySpec.with_faces#

BoundarySpec.with_faces(
    *, x=None, y=None, z=None,
    x_low=None, x_high=None, y_low=None, y_high=None, z_low=None, z_high=None,
) -> BoundarySpec

Returns a copy with the given axis or face overrides applied on top of the current face kinds. num_layers, strength and bloch_wavevector carry over.

BoundarySpec query methods#

MemberReturnsNotes
face_kindstupleProperty. ((x_low, x_high), (y_low, y_high), (z_low, z_high)) kinds.
axis_face_kinds(axis)tuple[str, str](low, high) kinds of one axis.
axis_kind(axis)strThe shared kind of both faces, or "mixed".
face_kind(axis, side)strKind of one face; side is "low" or "high".
uses_kind(kind)boolTrue when any face uses kind.
pml_layers_for_face(axis, side)intnum_layers on a PML face, else 0.
bloch_phase_factors(domain_range)tuple[complex, complex, complex]Phase factor per axis for a six-value domain range. Raises ValueError for an "auto" wavevector.

axis is "x", "y" or "z"; other values raise ValueError.

SubpixelSpec#

class SubpixelSpec(samples=(1, 1, 1), averaging="arithmetic", pec="staircase")

The subpixel material averaging policy. Pass it as Scene(subpixel_samples=...). Immutable. The defaults reproduce plain arithmetic averaging with staircase PEC edges.

samplesint | tuple[int, int, int]default (1, 1, 1)

Samples per cell along each axis. An int applies to all axes. Every value must be 1 or more; bool is rejected.

averagingstrdefault 'arithmetic'

How permittivity and permeability are blended per Yee component. "arithmetic": volume-fraction mixing. "polarized": harmonic mean along the interface normal and arithmetic mean along the interface (Kottke-style).

pecstrdefault 'staircase'

How FDTD treats PEC material edges. "staircase": hard edges. "conformal": fractional partial-fill edges.

Raises: ValueError for other averaging or pec values, or sample counts below 1; TypeError for a bool sample count.

Note

The benchmark scenes keep pec="staircase". Their comments report that conformal PEC adds spurious absorption to lossless PEC scatterers, so opt in to "conformal" per scene and check the result.

MaterialRegion#

class MaterialRegion(
    name, geometry, density,
    basis="density", bounds=(0.0, 1.0), eps_bounds=(1.0, 1.0), mu_bounds=(1.0, 1.0),
    filter_radius=None, projection_beta=None, symmetry=None,
)

A density-parameterized design region for inverse design. The density tensor is a texture over the region box; the solver normalizes it, optionally filters and projects it, and maps it linearly to permittivity and permeability. Gradients flow back to density when it requires grad. Immutable. See Differentiability.

namestrrequired

Region name. Batch parameters refer to regions by this name.

geometryBoxrequired

Region support. The material compiler accepts a Box only and raises ValueError for other geometry.

densitytorch.Tensorrequired

3D tensor of shape (Nx, Ny, Nz). It is sampled trilinearly over the box, so its shape does not need to match the Yee grid.

basisstrdefault 'density'

Must be "density".

boundstuple[float, float]default (0.0, 1.0)

Raw density range (lower, upper). The density is rescaled from this range to 0 to 1 and clamped. upper must be greater than lower.

eps_boundstuple[float, float]default (1.0, 1.0)

Relative permittivity at normalized density 0 and 1. Values in between are linear.

mu_boundstuple[float, float]default (1.0, 1.0)

Relative permeability at normalized density 0 and 1.

filter_radiusfloat | Nonedefault None

Radius in metres of a box (average-pool) filter applied to the normalized density. The kernel spans 2 * ceil(radius / step) + 1 cells per axis. Must be greater than 0 when set.

projection_betafloat | Nonedefault None

Sharpness of a tanh projection around 0.5 applied after filtering. Must be greater than 0 when set.

symmetrystr | Nonedefault None

Stored on the region. The 0.5.1 material compiler does not read it.

Raises: ValueError for a basis other than "density", a density that is not 3D, bounds with upper not greater than lower, or a non-positive filter_radius or projection_beta; TypeError when density is not a torch.Tensor.

import torch
 
scene.add_material_region(
    mw.MaterialRegion(
        name="design",
        geometry=mw.Box(position=(0.0, 0.0, 0.0), size=(0.5, 0.5, 0.5)),
        density=torch.full((20, 20, 20), 0.5, device="cuda", requires_grad=True),
        eps_bounds=(1.0, 4.0),
        projection_beta=8.0,
    )
)