FDTD Luminescent Simulation and Inverse Design#

v0.2.12-beta 2024/12/06
Paul Shen pxshen@alumni.stanford.edu

Synopsis#

Luminescent AI empowers photonic and RF engineers to simulate or inverse design complex electromagnetic components in just a few lines of code! We created an automatic differentiation (AD) and GPU compatible FDTD engine and geometry generator for photonic integrated circuits (PIC) and metasurfaces, as well (in the future) RF microstrip circuits and patch antennas. Experimental release đŸ„Œ. Expect bugs 🐛🐞

Follow us for updates! Star us GitHub if you like our work. We respond to issues within a day

%%HTML
<iframe width="560" height="315" src="https://www.youtube.com/embed/n_O1r6wggLU?si=x2tKzw4hzrIz6h7C" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

Features and conventions#

We do simulation and generative inverse design in 3D or 2.5D, with multiple wavelengths and modes, and on CPU or GPU. However, GPU acceleration is guaranteed only for simulation and not inverse design because the later requires significant VRAM.

Technical features of FDTD backend#

  • Automatic differentiation (AD) compatible for adjoint optimization

  • Optional GPU acceleration

  • Length scale controlled geometry optimizer

  • Nonlinear and anisotropic materials

  • Tensor subpixel smoothing for accuracy

  • Adaptive graded grid and Float16 support for speed

  • PML, periodic, PEC, PMC boundaries

  • Modal sources, plane waves, Gaussian beams

  • Modal monitors, DFT fields

Geometry layout#

We layout using Python gdsfactory which is integrated with KLayout. Can also import .gds into gdsfactory . Internally, gdsfactory component and layer stack let generate a 3D mesh which is clipped vertically some depths above and below core_layer. By default, we apply a SOI 220nm node (Si, SiO, SiN and Ge layers) implemented by gdsfactory.generic_tech layer stack. luminescent.MATERIALS maps the material tag of a layer in the layer stack to its property eg MATERIALS["si"]["epsilon"]. Can create your own LAYER_STACK and MATERIALS for your process node.

Margins and ports#

We automatically extend waveguides and adds margins during simulation but port locations remain fixed thus not affecting sparams. Port numbers map to gdsfactory component ports. Specify port pairs as eg 2,1 which is equivalent to o2@0,o1@0 meaning optical_port_number@mode_number.

Reciprocity and symmetry#

Usually only a subset of sparams is needed because of symmetry or reciprocity, specified as keys in sparams study or inferred automatically from inverse design study. We do a run for each port excitation. If keys is omitted in sparams study, all sparams will be calculated requiring more runs.

File workflow#

Each simulation or design optimization run is saved to a folder named after name arg or (if unnamed) timestamp string. These run folders are inside working directory wd="~/luminescent_runs" which can be modified by passing to write_sparams or gcell_problem. Can access saved runs via load_solution(), load_solution(), finetune() which act on latest modified run folder inside wd if name not specified.

Installation#

Backend CPU binaries at releases . GPU binaries can be requested

Windows#

  1. Frontend. Install Anaconda distribution of Python . Inside Anaconda Prompt do pip install -U luminescent

  2. Backend. Unzip LuminescentAI.zip binaries. Add where_you_unzipped\LuminescentAI\bin to Path (using “edit environmental variables”)

Linux#

  1. Frontend. pip install -U luminescent

  2. Backend. TBD

Tutorials: PIC S-parameters simulation#

waveguide bend#

5um inner radius#

Single wavelength 3D#

We characterize insertion loss (IL) of waveguide bends wrt radius. We set R=5.0um, the often cited minimum radius in silicon photonics. First, we use gdsfactory to make the bend (note gdsfactory uses inner radius). Next, write_sparams simulates and saves results, which are retrieved and visualized by load_solution.

nres is the maximum resolution - the number of points per wavelength in free space. The grid is adaptive so border regions actually use a lower resolution for speed. Remember FDTD scales as O(n^4)! nres has a huge effect on simulation time! Here, nres=40 is accurate but can take half hour on CPU. nres=20 is 16x faster and offers a reasonable estimate. Finally, Because of reciprocity we only need S or T param keys of “2,1”.

import gdsfactory as gf
import luminescent as lumi
import numpy as np

radius = 5
c = gf.components.bend_circular(radius=radius)
# c.plot()

name = f"bend_R{radius}"
N = 3  # 3D or 2D
wavelengths = 1.55
keys = ["2,1"]  # same as keys=["o2@0,o1@0"]
nres = 40

lumi.write_sparams(c, name=name, wavelengths=wavelengths, nres=nres, keys=keys, N=N)
sol = lumi.load_solution(name=name)

Loss is <-0.01dB, which is lower than reported by Tidy3D example

Wavelength range 3D#

We can repeat the simulation with multiple wavelengths in a range. Closely spaced wavelengths may require longer integration periods to resolve.

name = f"bend_R{radius}_multi"
wavelengths = np.linspace(1.5, 1.6, 5)  # number or list or array

lumi.write_sparams(c, name=name, wavelengths=wavelengths, nres=nres, keys=keys, N=N)
sol = lumi.load_solution(name=name)

Quickie 2.5D approximation#

Set N=2 to do a 2.5D simulation using (Ex, Ey, Hz) on a 2D plane with an “effective” refractive index integrated from a vertical modal profile. This is significantly faster but not accurate. Nevertheless it’s a good way of exploring various geometries.

1.5um inner radius#

Let’s try a tighter bend

radius = 1.5
name = f"bend_R{radius}"
c = gf.components.bend_circular(radius=radius, allow_min_radius_violation=True)

lumi.write_sparams(c, name=name, wavelengths=wavelengths, nres=nres, keys=keys, N=N)
sol = lumi.load_solution(name=name)

Loss increased to 0.11dB

customizing materials, layers, and modes#

Defaults#

import gdsfactory as gf
import luminescent as lumi
from gdsfactory.generic_tech import LAYER, LAYER_STACK
from luminescent import MATERIALS

core_layer = LAYER.WG
bbox_layer = LAYER.WAFER
layer_stack = LAYER_STACK
materials = MATERIALS

N = 3
dtype = "float32"
gpu = None

TE1#

c = gf.components.straight(length=1.0, width=0.5, layer=core_layer)
wavelengths = 1.55
nres = 30

name = "TE1"
keys = ["o2@1,o1@1"]
lumi.write_sparams(
    c,
    name=name,
    wavelengths=wavelengths,
    keys=keys,
    nres=nres,
    N=N,
    dtype=dtype,
    gpu=gpu,
)
lumi.load_solution(name=name)

Si rib waveguide#

only need to modify bbox_layer to include 90nm bottom silicon slab

name = "rib"
keys = ["2,1"]
bbox_layer = [LAYER.WAFER, LAYER.SLAB90]
lumi.write_sparams(
    c,
    name=name,
    wavelengths=wavelengths,
    keys=keys,
    nres=nres,
    N=N,
    dtype=dtype,
    gpu=gpu,
    bbox_layer=bbox_layer,
)
sol = lumi.load_solution(name=name)

SiN strip waveguide#

name = "SiN"
core_layer = LAYER.WGN
c = gf.components.straight(length=2.0, width=1.0, layer=core_layer)

lumi.write_sparams(
    c,
    name=name,
    wavelengths=wavelengths,
    keys=keys,
    nres=nres,
    N=N,
    dtype=dtype,
    gpu=gpu,
    core_layer=core_layer,
)
sol = lumi.load_solution(name=name)

Tutorials: PIC generative inverse design#

We introduce GCells (generative cells), a natural evolution of PCells (parametric cells) in semiconductor design . Given a set of inverse design objectives, a GCell will generate optimal geometry using adjoint optimization while ensuring manufacturability by enforcing minimum feature lengths.

In examples below, gcells.mimo (multi in multi out) is just a gdsfactory component with configurable waveguide ports, simple slab as pre-optimization geometry, and overlying rectangular design regions. Dimensions l along x and w along y. Ports are numbered incrementally: west (SW->NW) -> east (SE->NE) -> south (SW->SE) -> north (NW->NE). By default, they’re spaced equally on a side. Example: west=1, east=2 places port 1 on west, ports 2 & 3 on east. But can also individually specify their locations and widths. Example : west=[1.0, 2.5], wwg_west=[0.5, 0.4].

Optimization targets is a dictionary organized wrt target type & wavelength. Types include T-params (tparams, most common), phase difference (phasediff), S-params (sparams). Multiple types & wavelengths are possible & often necessary . Loss for each type is scaled automatically to vary from 0 to 1 usually . For example , tparams loss of 0.5 roughly means 50% of power going to wrong places.

lvoid is minimum length scale for voids. No void features smaller than it (currently this isn’t exact - contact us for a more precise algorithm). Similarly for lsolid.

N=2 optimizes in 2.5D which serves as a fast initialization for 3D optimization. Examples done at low resolution and lax convergence in 2.5D on CPU. For accuracy, the result must be finetuned in 3D at finer resolution, a feature that can be requested from Luminescent AI .

Generative cells PDK for passive devices#

mode converter#

We target “o2@1,o1@0”, converting optical port 1 mode TE0 input to optical port 2 mode TE1 output at 1.55um. Mode converters are notoriously hard to design, but generative AI does it in a pinch!

# recommended RAM: >16G
import luminescent as lumi

name = "mode_converter"  # can be any string
c = lumi.gcells.mimo(west=1, east=1, l=5.0, w=2.4, wwg=0.5, taper=0.05)
targets = {"tparams": {1.55: {"o2@1,o1@0": 1.0}}}

prob = lumi.gcell_problem(
    c,
    targets,
    name=name,
    N=2,
    nres=15,
    lvoid=0.15,
    lsolid=0.15,
    iters=100,
    stoploss=0.05,
)
lumi.solve(prob)

Can optionally optimize more using finetune

# import luminescent as lumi
# lumi.finetune(iters=40,name="mode_converter")

Lets see simulation fields and optimized geometry. Note the lax convergence criteria (stoploss=.05) means the solution isn’t perfect.

name = "mode_converter"
sol = lumi.load_solution(name=name)

1x2 splitter MMI#

1.55um wavelength 1x2 splitter. We set symmetry about y so only need to specify T21=.5 as optimization target. Data saved to name folder inside working directory. We start iteratiions of adjoint optimization.

import luminescent as lumi

name = "splitter"
c = lumi.gcells.mimo(
    west=1,
    east=2,
    l=4.0,
    w=2.0,
    wwg=0.5,
    taper=0.05,
)
targets = {
    "tparams": {1.55: {"2,1": 0.5}},
}

prob = lumi.gcell_problem(
    c,
    targets,
    name=name,
    N=2,
    nres=15,
    symmetries=[1],
    lvoid=0.15,
    lsolid=0.15,
    iters=50,
    stoploss=0.05,
)
lumi.solve(prob, run=False)
name = "splitter"
sol = lumi.load_solution(name=name)

1x4 splitter MMI#

1x4 splitter at 1.55um with same phase on outputs. Because of symmetry, only need 0.0 phase difference between first 2 outputs.

# finetune(iters=10,name=name)
sol = lumi.load_solution(name)

wavelength domain demultiplexer#

# RAM: 32G
import luminescent as lumi

name = "demux"
c = lumi.gcells.mimo(west=1, east=2, l=4.0, w=4.0, wwg=0.5)
targets = {
    "tparams": {
        1.55: {"2,1": 1.0},
        1.20: {"3,1": 1.0},
    }
}

prob = lumi.gcell_problem(
    c, targets, name=name, lvoid=0.15, lsolid=0.15, nres=15, N=2, iters=50
)
lumi.solve(prob)
name = "demux"
sol = lumi.load_solution(name)

symmetric crossing#

import luminescent as lumi

name = "crossing"
c = lumi.gcells.mimo(west=1, east=1, south=1, north=1, l=4.0, w=4.0, wwg=0.5)
targets = {"tparams": {1.55: {"2,1": 1.0}}}

prob = lumi.gcell_problem(
    c,
    targets,
    name=name,
    lvoid=0.15,
    lsolid=0.15,
    nres=15,
    symmetries=[0, 1],
    N=2,
    iters=40,
)
lumi.solve(prob)
sol = lumi.load_solution(name)

GCells PDK for active devices#

Thermo-optic phase shifters#

Please request

GCells PDK for nonlinear devices#

Please request

Collaboration and research opportunities#

We can publish or keep it proprietary, depending on your needs.

Algorithms

  • Reduced basis geometry generators for length scale controlled topology optimization

  • Automatic differentiation and GPU compatible FDTD for inverse design in photonics and RF

  • Algorithms for reduced memory usage in time domain adjoint optimization

  • Neural surrogates for FDTD

Photonic applications

  • Energy efficient and compact photonic phase shifters and resonators

  • Energy efficient and compact photonic modulators and MZIs

  • Inverse design of nonlinear photonic devices

  • Inverse design of