Pixel and spectral design bases#
FDTDX separates the optimization variables from the material density seen by Maxwell’s equations. One forward and one adjoint solve still produce the complete gradient, whether the variables are individual pixels or coefficients of correlated spatial waves.
Basis |
Parameters |
Useful bias |
|---|---|---|
|
one value per cell |
unrestricted antennas and final local polishing |
|
selected DCT-II cosine amplitudes |
mirror-reduced domains and photonic-crystal periods |
|
bias plus cosine/sine amplitudes |
general plane waves with learned phase |
|
every nonredundant discrete Fourier amplitude |
full pixel expressivity in spectral coordinates |
|
circular/elliptical cosine and sine amplitudes |
bullseyes and curved Bragg mirrors |
|
signed displacement of an existing interface |
binary, topology-preserving shape polishing |
Every basis implements the same small interface:
basis.parameter_shape # shape optimized by Adam/SGD/L-BFGS
basis.parameter_bounds # (0, 1) for pixels; unbounded for coefficients
parameters = basis.encode(existing_density)
density = basis.decode(parameters)
One fabrication-aware objective optimized with four interchangeable bases. The spectral bases reproduce correlated periodic patterns with far fewer variables; the radial basis deliberately searches a different family of curved fronts.#
Switching bases#
The only line that changes between pixel and spectral topology optimization is the basis constructor:
import fdtdx
shape = (180, 96)
spacing_um = 0.033333
pixel_basis = fdtdx.PixelBasis2D(shape)
cosine_basis = fdtdx.CosineBasis2D(
shape,
mode_shape=(72, 48),
spacing=(spacing_um, spacing_um),
minimum_period=0.20,
maximum_period=0.90,
)
fourier_basis = fdtdx.FourierBasis2D(
shape,
max_mode_indices=(36, 24),
spacing=(spacing_um, spacing_um),
minimum_period=0.20,
maximum_period=0.90,
)
dense_fourier_basis = fdtdx.FFTGridBasis2D(
shape,
spacing=(spacing_um, spacing_um),
)
CosineBasis2D is especially convenient when the stored design is an x/y
symmetry-reduced quadrant. Its waves have zero normal derivative at the design
box edges. FourierBasis2D supplies cosine and sine amplitudes for every unique
reciprocal vector, so phase and asymmetry remain free.
FFTGridBasis2D is the scalable choice when the desired Fourier basis is as
large as the material grid. It packs the Hermitian spectrum into exactly one
real variable per spatial cell and decodes it with ifft2. For an 183 x 75
design this means 13,725 independent real coefficients (6,863 reciprocal
waves), not a stored 13,725 x 13,725 basis matrix. The transform costs
O(N log N), preserves every representable spatial degree of freedom, and
rejects mode limits beyond the grid’s Nyquist range rather than silently
creating aliased duplicate waves.
Physical period limits select a spectral annulus. Omitting them retains every mode inside the requested rectangular mode range, including long-wavelength components that can create smooth antenna-like structures.
Fabrication transform and hard regions#
All bases use one implementation of filtering, projection, immutable geometry, and exact binarization:
parameters = cosine_basis.encode(seed_density)
def objective(parameters, beta, fraction):
del fraction
density_2d = fdtdx.fabrication_density_2d(
parameters,
cosine_basis,
beta=beta,
eta=0.50,
filter_radius=0.10,
filter_spacing=spacing_um,
trainable_mask=trainable,
fixed_density=fixed,
exact_binary=True,
)
z_fill = fdtdx.slab_fill_fractions(
simulation_z_edges_um,
thickness=0.20,
)
density_3d = fdtdx.extrude_density_2d(density_2d, z_fill)
arrays = put_density_in_material_array(arrays_template, density_3d)
output = run_differentiable_fdtd(arrays)
return measured_objective(output), {"density": density_2d}
result = fdtdx.optimize_topology(
objective,
parameters,
fdtdx.TopologyOptimizerConfig(
iterations=200,
learning_rate=0.02,
lower_bound=cosine_basis.parameter_bounds[0],
upper_bound=cosine_basis.parameter_bounds[1],
),
)
Set trainable_mask=0, fixed_density=0 for hard air and
trainable_mask=0, fixed_density=1 for immutable dielectric. Constraints are
reimposed after filtering, so material cannot bleed into an atom-clearance
region. With exact_binary=True, the forward solve sees only the two endpoint
materials while the backward pass uses the smooth projection derivative.
Running the same parameters at several projection thresholds supplies robust
erosion/dilation variants.
Curved and localized waves#
Circular or facing curved mirrors can use a radial/elliptical bank:
radial_basis = fdtdx.RadialCosineBasis2D(
shape,
spacing=(spacing_um, spacing_um),
periods=(0.24, 0.30, 0.36, 0.44, 0.54, 0.68),
axis_scale=(1.0, 1.5),
window_sigma=(2.0, 1.2),
)
axis_scale turns circular fronts into ellipses. window_sigma provides a
Gaussian envelope, allowing a periodic mirror field to taper into a defect
rather than forcing one infinite uniform crystal. Sine amplitudes are enabled
by default, so each radial period can learn its phase.
What the basis does—and does not—guarantee#
A spectral basis correlates geometry updates and can restrict their length scales. It does not change the optimization objective. A pure beta objective can still prefer emission suppression over a resonant cavity; LDOS and pole diagnostics remain necessary when resonance formation matters. Conversely, using too few modes can exclude the best device. A practical workflow is spectral discovery followed by pixel or boundary polishing.
A complete FFT grid is not a low-dimensional regularizer: it has exactly the
same number of independent variables as the pixel grid. Its benefit is the
coordinate system—each derivative perturbs a correlated periodic wave. Use a
truncated FourierBasis2D or CosineBasis2D when an actual band limit or
parameter-count reduction is desired.
The runnable
solver/examples/spectral_design_parameterizations.py example applies the
same fabrication-aware objective to all four bases.
Boundary-normal optimization#
The spectral and pixel bases are appropriate while the topology is still being
discovered. Once a grey design has useful connected components and holes,
LevelSetBoundary2D freezes that topology and changes the question asked by
the adjoint solve. A parameter no longer means “make this cell more SiN.” It
means “move this nearby material boundary outward or inward.”
boundary = fdtdx.LevelSetBoundary2D.from_density(
grey_density,
spacing=(dx_um, dy_um),
threshold=0.5,
narrow_band_width=4 * max(dx_um, dy_um),
maximum_displacement=2 * max(dx_um, dy_um),
supersample=6,
# min_x and min_y are mirror planes, not open exterior boundaries.
exterior_edges=("max_x", "max_y"),
)
displacement = boundary.initial_parameters()
def objective(candidate):
density = boundary.decode(candidate)
output = run_differentiable_fdtd(density)
pole = fit_tracked_pole(output)
peak = cavity_peak_ldos(pole, output)
fixed_ldos = fdtdx.fixed_frequency_cavity_ldos(
peak_resonant_ldos=peak,
quality_factor=pole.quality_factor,
pole_frequency=pole.frequency,
emitter_frequency=atomic_frequency, # immutable throughout the run
background_ldos=1.0,
)
return jnp.log(fixed_ldos)
value, normal_gradient = jax.value_and_grad(objective)(displacement)
proposal, topology_scale = fdtdx.topology_safe_boundary_step(
boundary,
displacement,
normal_gradient,
step_size=0.5 * min(dx_um, dy_um),
)
The reference is a positive-in-material signed-distance field. Adding a
positive scalar at an interface advances material along its outward normal;
adding a negative scalar retreats it. A compact cosine window makes the
derivative exactly zero away from existing interfaces, so a remote island or
hole cannot nucleate from a bulk pixel. topology_safe_boundary_step rejects
steps that merge, split, create, or erase components. It checks one-to-one
lineage as well as component counts, so deleting one hole while creating a
replacement elsewhere is also rejected. After several accepted small steps,
boundary.rebase(...) refits a signed-distance field and resets the
displacement to zero without changing the shape.
The density returned at an intersected Yee cell is a supersampled geometric area fraction. It is not a freely optimizable grey material: every subcell in the forward raster is one of the two endpoint materials. This retains subpixel boundary motion without the dishonest final act of projecting an arbitrary grey image to binary.
There is one important limitation. Fixing topology is a deliberate search-space restriction, not a proof that the chosen topology is globally optimal. The recommended sequence is therefore grey pixel/spectral discovery, one explicit topology-selection threshold, and then boundary optimization. If a better device genuinely needs a new hole, return to the discovery stage instead of letting a boundary step create it accidentally.