Set up grids and boundaries
Choose a cell size, use nonuniform and refined grids, and set PML, periodic, Bloch, PEC and symmetry boundaries for common problem types.
This guide gives the grid and boundary settings for the problem types you meet most often. For the reasoning behind them, read Grid, units and boundaries. All lengths are in metres.
Pick a cell size#
Start from the shortest wavelength in the densest material, lambda_min = c / (f_max * n_max), and use 10 to 20 cells across it. Refine further where fields change fast: metal edges, gaps, thin layers.
import witwin.maxwell as mw
f_max = 3e9 # highest frequency of interest, Hz
n_max = 2.0 # largest refractive index in the scene (eps_r = 4)
lambda_min = mw.constants.C_0 / (f_max * n_max)
dl = lambda_min / 15 # about 6.7 mm here
grid = mw.GridSpec.uniform(dl)GridSpec.anisotropic(dx, dy, dz) uses different uniform steps per axis, for example a fine step across a thin layer only.
Let Maxwell build a nonuniform grid#
GridSpec.auto places cell boundaries on structure faces, resolves the local wavelength in every material, and grades the cells so neighbours never differ by more than max_ratio.
patch = mw.Box(position=(0.0, 0.0, 0.0008), size=(0.03, 0.04, 3.5e-5))
grid = mw.GridSpec.auto(
min_steps_per_wavelength=15,
wavelength=mw.constants.C_0 / 3e9, # free-space wavelength at f_max
max_ratio=1.4,
override_structures=(mw.MeshOverrideStructure(geometry=patch, dl=5e-4),),
layer_refinement=mw.LayerRefinementSpec(min_cells=3),
)MeshOverrideStructure(geometry, dl)caps the step inside the bounding box of a geometry (dlis a scalar or(dx, dy, dz)).LayerRefinementSpec(min_cells=3)puts at least three cells across every thin interval between structure faces, such as a substrate.
For full control, pass node coordinates per axis with GridSpec.custom(x_coords, y_coords, z_coords).
Note
FDFD needs a uniform grid. GridSpec.auto and GridSpec.custom work with FDTD only.
Resolve curved and slanted interfaces#
Staircased spheres and slanted faces converge slowly. Turn on subpixel averaging:
scene = mw.Scene(
domain=mw.Domain(bounds=((-0.5, 0.5),) * 3),
grid=mw.GridSpec.uniform(0.02),
boundary=mw.BoundarySpec.pml(num_layers=10),
subpixel_samples=mw.SubpixelSpec(samples=3, averaging="polarized", pec="staircase"),
)averaging="polarized" is the better choice at dielectric interfaces. Keep pec="staircase" for lossless metal scatterers; pec="conformal" fills cut metal edges partially and can add spurious absorption in closed metal cavities.
Open radiation and scattering problems#
Surround the domain with PML and leave room between objects and the absorber, roughly a quarter to half a wavelength:
boundary = mw.BoundarySpec.pml(num_layers=12)PML cells are added outside Domain.bounds. Conductors that should leave the domain, such as waveguide walls or transmission lines, must run through the PML to the edge of the padded grid: make them 2 * (half_span + num_layers * dl) long or longer.
Periodic structures at normal incidence#
Use periodic boundaries on the lattice axes and PML on the propagation axis. with_faces overrides faces of an existing spec:
boundary = mw.BoundarySpec.pml(num_layers=12).with_faces(
x_low="periodic", x_high="periodic",
y_low="periodic", y_high="periodic",
)The Domain extent along x and y is one unit cell. Excite with a PlaneWave travelling along z.
Periodic structures at oblique incidence#
A Bloch boundary adds the phase shift exp(i k L) across each period. Combine Bloch on the lattice axes with PML on z, and launch the wave with a TFSF slab:
period = 0.8e-6
k_x = 2 * 3.141592653589793 * 200e12 / mw.constants.C_0 * 0.25 # k0 * sin(theta), rad/m
boundary = mw.BoundarySpec.faces(
default="pml",
num_layers=12,
x="bloch",
y="bloch",
bloch_wavevector=(k_x, 0.0, 0.0),
)
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.4e-6, 0.4e-6)),
)bloch_wavevector="auto" lets Maxwell derive the wavevector from a fixed-angle CW TFSF slab source. Broadband pulses with "auto" are rejected, because the Bloch phase depends on frequency. Read diffraction efficiencies with DiffractionMonitor.
Closed cavities and waveguides#
BoundarySpec.pec() makes every face a perfect electric conductor, and BoundarySpec.pmc() a perfect magnetic conductor. Use faces to close some sides and absorb on others, for example a waveguide that is PEC on y and z and open along x:
boundary = mw.BoundarySpec.faces(default="pec", num_layers=10, x="pml")Use symmetry to cut the cost#
If the structure and the source are mirror-symmetric, compute half (or a quarter, or an eighth) of the domain. Each symmetry entry places a PEC or PMC mirror on one face of that axis:
scene = mw.Scene(
domain=mw.Domain(bounds=((0.0, 0.5), (-0.5, 0.5), (-0.5, 0.5))), # x >= 0 half
grid=mw.GridSpec.uniform(0.02),
boundary=mw.BoundarySpec.pml(num_layers=10),
symmetry=("PMC", None, None), # mirror on the x-low face
)Pick PEC when the tangential electric field vanishes on the mirror plane, and PMC when the tangential magnetic field does. For an Ez dipole on the plane x = 0, the electric field is even in x, so the mirror is PMC. No PML is placed on a symmetry face, and result.material(..., expand_symmetry=True) and result.at(expand_symmetry=True) unfold the results to the full domain. FDFD rejects symmetry.
Related#
- Scene API: every
GridSpec,BoundarySpecandSubpixelSpecoption - Choose sources and waveforms