Inverse design¶
inverse
¶
Inverse design: density-based topology optimisation on any differentiable backend.
Quick start::
import lightsmith as ls
from lightsmith import inverse
spec = inverse.OptimizationProblem(device=device_problem, design_regions=[...],
objective=[inverse.SParameterTerm(out_port="o2", in_port="o1")])
res = inverse.optimize(spec, device="cpu", checkpoint="run.npz")
res.final_device # DeviceProblem with the exported polygons
inverse.write_gds("design.gds", inverse.marching_squares(...)) # via lightsmith.layout
The differentiable-backend protocol¶
Inverse design works with any registered backend that sets differentiable = True
and provides these classmethods (the FDFD backend, lightsmith-fdfd, is the
reference implementation)::
design_grid(problem) -> grid
The grid on which permittivities are given; must expose node coordinates
``grid.x``, ``grid.y`` (nm, 1D arrays), the cell size ``grid.dl`` (nm) and
``grid.shape``. (3D backends add ``grid.z``.)
base_permittivity(problem) -> np.ndarray, shape (n_wl, *grid.shape), complex
The rasterised permittivity of the problem's own structures.
differentiable_solve(problem, eps_grid_override, device, sources=None,
with_fields=True, **context) -> DifferentiableSolution
eps_grid_override: torch tensor (n_wl, *grid.shape) or (*grid.shape), which
may require grad. Returns torch tensors connected to the autograd graph:
``.s`` (n_wl, n_out, n_src) complex S[out, src], ``.outputs`` / ``.inputs``
(port-mode labels, 'name' or 'name@mode'), ``.fields`` (n_wl, n_src, *grid.shape)
or None, ``.grid``. Gradients must be exact (adjoint), and ports must not
depend on the override inside design regions.
prepare(problem) -> dict (optional)
Cached context (e.g. a factorisation-ready engine) passed back to
``differentiable_solve`` as ``**context`` on every iteration.
Conventions: lengths in nm, vacuum wavelengths, exp(-i omega t) (Im eps > 0 = loss), power-normalised S-parameters.
ObjectiveTerm
module-attribute
¶
ObjectiveTerm = Annotated[Union[SParameterTerm, FieldIntensityTerm], Field(discriminator='kind')]
DesignRegion
¶
Bases: _Model
A box whose permittivity is free: density 0 -> material_low, 1 -> material_high.
center_nm
class-attribute
instance-attribute
¶
size_nm
class-attribute
instance-attribute
¶
material_low
class-attribute
instance-attribute
¶
material_low: Material = Field(description='Material at density 0 (usually the cladding).')
material_high
class-attribute
instance-attribute
¶
material_high: Material = Field(description='Material at density 1 (usually the core).')
init
class-attribute
instance-attribute
¶
filter_radius_nm
class-attribute
instance-attribute
¶
eta
class-attribute
instance-attribute
¶
min_feature_nm
class-attribute
instance-attribute
¶
min_feature_nm: float | None = Field(None, gt=0, description='Minimum solid and void feature size, imposed with lengthscale penalties (needs filter_radius_nm >= min_feature_nm / 2).')
symmetry
class-attribute
instance-attribute
¶
symmetry: list[Literal['x', 'y']] = Field(default_factory=list, description="Mirror symmetry: 'x' = symmetric under x -> -x about the region centre, 'y' likewise.")
FieldIntensityTerm
¶
Bases: _Term
Mean |field|^2 (Hz for TE, Ez for TM) in a box, with one port mode as the source.
size_nm
class-attribute
instance-attribute
¶
size_nm: tuple[float, float, float] = Field(description='Box size; zero sizes select a line or point.')
OptimizationProblem
¶
Bases: _Model
Topology optimisation of a device: maximise sum(weight * term) (minimise terms with goal='minimize').
design_regions
class-attribute
instance-attribute
¶
design_regions: list[DesignRegion] = Field(min_length=1)
backend
class-attribute
instance-attribute
¶
backend: str = Field('lightsmith-fdfd', description='Any backend with differentiable=True and differentiable_solve (see lightsmith.inverse).')
options
class-attribute
instance-attribute
¶
OptimizerSettings
¶
Bases: _Model
method
class-attribute
instance-attribute
¶
method: Literal['mma', 'adam', 'lbfgsb'] = Field('mma', description='mma = globally convergent MMA (monotone; Svanberg 2002); adam; lbfgsb = SciPy L-BFGS-B.')
max_iter
class-attribute
instance-attribute
¶
max_iter: int = Field(100, ge=1, description='Total objective/gradient evaluations budget (outer iterations).')
learning_rate
class-attribute
instance-attribute
¶
move_limit
class-attribute
instance-attribute
¶
beta_start
class-attribute
instance-attribute
¶
beta_growth
class-attribute
instance-attribute
¶
beta_growth: float = Field(2.0, ge=1, description='beta *= beta_growth every beta_every iterations (continuation).')
lengthscale_weight
class-attribute
instance-attribute
¶
lengthscale_weight: float = Field(100.0, ge=0, description='Penalty weight of the min-feature constraints.')
lengthscale_from_beta
class-attribute
instance-attribute
¶
lengthscale_from_beta: float = Field(16.0, ge=0, description='Apply min-feature penalties once beta >= this.')
ftol
class-attribute
instance-attribute
¶
ftol: float = Field(0.0, ge=0, description='Stop when the relative improvement over 10 iterations is below this.')
SParameterTerm
¶
Bases: _Term
|S[out <- in]|^2 (power) of port modes, e.g. transmission o1 -> o2 or reflection o1 -> o1.