Note: the cost of running the entire notebook is larger than 10 FlexCredits.
Compact on-chip mirrors are a key building block for integrated Fabry-Perot cavities, laser feedback sections, and sensing. A broadband, high-reflectivity mirror is difficult to realize with conventional Bragg gratings in a small footprint, which makes it an excellent target for adjoint-based topology optimization.
In this notebook, we demonstrate the complete design flow of a terminating waveguide reflector on the 220 nm silicon-on-insulator platform with oxide cladding: a \(1 \times 3\)\(\mu m\) design region attached to a 500 nm single-mode waveguide is optimized to reflect the fundamental TE mode back into the waveguide, reaching about 99% reflectivity at 1550 nm while respecting a 100 nm minimum feature size in a fully binarized layout. We then assemble two copies of the mirror into a side-coupled Fabry-Perot cavity and compute its circuit-level spectrum, which shows a free spectral range of about 4.7 nm, deep on-resonance extinction, and a loaded quality factor of several thousand. Finally, we attach grating couplers following the SiEPIC layout conventions to produce a fabrication-ready chip layout.
The workflow has six parts: 1 robust inverse design of the reflector, 2 binarization and minimum-feature-size enforcement, 3 high-accuracy verification and GDS export, 4 cavity circuit assembly and simulation with PhotonForge on the SiEPIC PDK, 5 quality factor extraction, and 6 the fabrication-ready layout with grating couplers.
For more inverse design examples, please visit our examples page. If you are new to the finite-difference time-domain (FDTD) method, we highly recommend going through our FDTD101 tutorials.
import pickleimport autograd.numpy as anpimport matplotlib.pyplot as pltimport numpy as npfrom autograd.tracer import getvalimport tidy3d as tdimport tidy3d.web as webfrom tidy3d.plugins.autograd import adam, apply_updates, rescale, value_and_gradfrom tidy3d.plugins.autograd.invdes import ( make_conic_filter, make_erosion_dilation_penalty, symmetrize_mirror, tanh_projection,)
Inverse Design Setup
We define the wavelength, the constant-index silicon and oxide used during optimization, and the design-region geometry. The design region is discretized into 20 nm pixels. The conic filter radius of 150 nm deliberately exceeds the 100 nm fabrication limit: the margin keeps the thresholded design legal.
The reflector is a terminating mirror: the input waveguide extends through the boundary on one side and plain oxide lies beyond the design region on the other. We record the reflected fundamental mode with a ModeMonitor placed behind the ModeSource, so it only sees the backward-propagating wave. A MeshOverrideStructure locks the FDTD grid to the design pixels, and symmetry=(0,-1,1) exploits the two mirror symmetries of the TE mode for a fourfold cost reduction.
The objective is robust to fabrication bias and to the final thresholding step: each gradient evaluation simulates three versions of the design, projected at \(\eta = 0.45\), \(0.5\), and \(0.55\) (dilated, nominal, eroded), and averages their logarithmic reflection objectives. The average, rather than the worst case, keeps all three adjoint sources well conditioned. The erosion-dilation fabrication penalty is evaluated on the design padded with its physical surroundings (waveguide on the left, oxide on the right); without the padding the penalty cannot see sub-100 nm gaps pinched against the waveguide junction.
aux = {} # per-step diagnostics recorded by the objectivedef objective(params, beta: float, task_name: str) ->float:"""Mean of log-reflection objectives over eroded/nominal/dilated projections, minus the fabrication penalty.""" p = symmetrize_mirror(params, axis=1) filtered = conic_filter(p) sims, dens = {}, {}for tag, eta inzip(("dil", "nom", "ero"), etas): d = tanh_projection(filtered, beta, eta) dens[tag] = d base = make_sim_base() sims[tag] = base.updated_copy( structures=[*base.structures, make_design_structure(rescale(d, eps_sio2, eps_si))] ) batch = web.run(sims, task_name=task_name, verbose=False) Rs = [ anp.sum(anp.abs(batch[t]["refl"].amps.sel(direction="-", f=freq0, mode_index=0).values) **2)for t in ("dil", "nom", "ero") ] logs = [-anp.log10(1.0- r +1e-6) for r in Rs] wg_col = (np.abs((np.arange(ny) - (ny -1) /2) * pix) <= wg_width /2).astype(float) padded = anp.concatenate([np.tile(wg_col, (8, 1)), dens["nom"], np.zeros((8, ny))], axis=0) pen = penalty_fn(padded) weight =min(1.0, beta /25.0) * penalty_weight J = (logs[0] + logs[1] + logs[2]) /3.0- weight * pen aux.update(R=float(getval(Rs[1])), R_worst=float(min(getval(r) for r in Rs)), pen=float(getval(pen)))return Jval_grad = value_and_grad(objective)
Now we run the optimization with the Adam optimizer and a linear \(\beta\) ramp from 1 to 50, which gradually binarizes the design. The history is saved to disk after every iteration so a long run is never lost.
We plot the optimization history: the nominal and worst-case reflectivities converge together, which is exactly the robustness the three-projection objective buys.
The optimized gray-scale density must become a fully binary silicon/oxide layout that respects the 100 nm minimum feature size everywhere, including against the waveguide junction. We implement the morphological checks with plain numpy: disk-based opening flags undersized silicon features, disk-based closing flags undersized oxide gaps, and a flood fill groups violations into clusters. Clusters smaller than 6 pixels are convex corner tips, which lithography rounds anyway; larger clusters are genuine violations that we repair directionally, filling undersized gaps and dilating thin walls.
pad =8d100 = [(dx, dy) for dx inrange(-3, 4) for dy inrange(-3, 4) if dx**2+ dy**2<= (min_feature /2/ pix) **2+1e-9]d3x3 = [(dx, dy) for dx inrange(-1, 2) for dy inrange(-1, 2)]def shift(a, dx: int, dy: int):"""Shift a 2D bool array, filling with False.""" out = np.zeros_like(a) out[max(dx, 0) : a.shape[0] +min(dx, 0), max(dy, 0) : a.shape[1] +min(dy, 0)] = a[max(-dx, 0) : a.shape[0] +min(-dx, 0), max(-dy, 0) : a.shape[1] +min(-dy, 0) ]return outdef dilate(a, offsets):"""Morphological dilation by a structuring element given as offsets.""" out = np.zeros_like(a)for dx, dy in offsets: out |= shift(a, dx, dy)return outdef erode(a, offsets):"""Morphological erosion by a structuring element given as offsets.""" out = np.ones_like(a)for dx, dy in offsets: out &= shift(a, dx, dy)return outdef label_components(a):"""4-connected component labeling by flood fill.""" lab = np.zeros(a.shape, int) n =0for x0, y0 inzip(*np.where(a)):if lab[x0, y0]:continue n +=1 stack = [(x0, y0)] lab[x0, y0] = nwhile stack: x, y = stack.pop()for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)): u, v = x + dx, y + dyif0<= u < a.shape[0] and0<= v < a.shape[1] and a[u, v] andnot lab[u, v]: lab[u, v] = n stack.append((u, v))return lab, ndef embed(a):"""Pad the design with its physical surroundings: waveguide on the left, oxide elsewhere.""" wg_row = np.abs((np.arange(ny) - (ny -1) /2) * pix) <= wg_width /2 big = np.zeros((a.shape[0] +2* pad, a.shape[1] +2* pad), bool) big[pad:-pad, pad:-pad] = a big[:pad, pad:-pad] = wg_rowreturn bigdef violation_clusters(a, min_px: int=6):"""Return sub-100 nm features as (kind, mask) pairs, ignoring small corner tips.""" big = embed(a) out = []for kind, op in [("solid", lambda x: dilate(erode(x, d100), d100)), ("void", lambda x: erode(dilate(x, d100), d100))]: v = np.logical_xor(op(big), big)[pad:-pad, pad:-pad] lab, n = label_components(v) out += [(kind, lab == i) for i inrange(1, n +1) if (lab == i).sum() >= min_px]return outdef repair(b):"""Repair violations directionally: fill undersized gaps, dilate thin silicon walls.""" fixed = b.copy()for _ inrange(6): clusters = violation_clusters(fixed)ifnot clusters:return fixed, True lab_si, _ = label_components(fixed)for kind, m in clusters:if kind =="void": fixed |= melse: wall_ids = np.unique(lab_si[m & fixed]) wall = np.isin(lab_si, wall_ids[wall_ids >0])for _ inrange(2): wall = dilate(wall, d3x3) fixed |= wall fixed &= fixed[:, ::-1]return fixed, not violation_clusters(fixed)
We threshold the best-performing snapshots at \(\eta = 0.5\) and keep the first one that is legal (or becomes legal after repair). The comparison below highlights the repair. In the top panel, red marks the features of the thresholded design that violate the 100 nm rule: two oxide slots pinched against the input waveguide corners (left edge) and a roughly 70 nm thin silicon wall at the terminated end (right edge). In the bottom panel, orange marks the silicon the repair added: the slots are simply filled, while the thin wall triggers the dilate-the-whole-feature rule, so the entire trailing tooth is thickened by 40 nm and the orange traces its full outline.
We verify the final binary design with a broadband, high-accuracy simulation using a finer grid (min_steps_per_wvl=30 and a 10 nm design-region mesh) and a FieldMonitor to inspect the mirror in action.
11:21:40 UTC Estimated FlexCredit cost: 0.065. This assumes the FDTD solver runs
for the full simulation time; if early shutoff is reached, the
billed cost can be lower. Use 'web.real_cost(task_id)' to get the
billed FlexCredit cost after a simulation run.
status = queued
To cancel the simulation, use 'web.abort(task_id)' or
'web.delete(task_id)' or abort/delete the task in the web UI.
Terminating the Python script will not stop the job running on the
cloud.
11:21:55 UTC starting up solver
running solver
11:22:03 UTC early shutoff detected at 29%, exiting.
11:22:07 UTC Loading results from data/reflector_verify.hdf5
The reflection spectrum stays high across the full band, and the field map shows the incident mode turned around within the first few teeth of the mirror.
We now assemble the cavity circuit with PhotonForge using the SiEPIC OpenEBL technology. The reflector GDS becomes a component with one optical port and a Tidy3D S-matrix model. Two mirrors terminate a cavity waveguide that is side-coupled to a bus through an adiabatic S-bend coupler. The circuit is referenced at its two bus ports, so the simulation captures the pure device physics; the fiber interface is added later as layout only.
import photonforge as pfimport siepic_forge as siepicpf.config.default_technology = siepic.ebeam()gap =0.20# coupling gap (um)coupling_length =6.0arm_length =15.0reflector = pf.find_top_level(*pf.load_layout("reflector.gds").values())[0]reflector.name ="REFLECTOR"reflector.add_port(pf.Port((reflector.bounds()[0][0], 0.0), 0, "TE_1550_500"), port_name="P0")reflector.add_model(pf.Tidy3DModel(symmetry=(0, -1, 0), verbose=False), "Tidy3D")coupler = pf.parametric.s_bend_straight_coupler( port_spec="TE_1550_500", coupling_distance=gap + wg_width, coupling_length=coupling_length, s_bend_length=10.0, s_bend_offset=2.0, euler_fraction=0.5,)arm = pf.parametric.straight(port_spec="TE_1550_500", length=arm_length)ports =dict(coupler.ports)cav_l, cav_r =sorted((n for n, p in ports.items() ifabs(p.center[1]) <0.2), key=lambda n: ports[n].center[0])bus_l, bus_r =sorted((n for n, p in ports.items() ifabs(p.center[1]) >=0.2), key=lambda n: ports[n].center[0])fp = pf.Component("FP_CAVITY")coup = fp.add_reference(coupler)arm_l, arm_r = fp.add_reference(arm), fp.add_reference(arm)refl_l, refl_r = fp.add_reference(reflector), fp.add_reference(reflector)arm_l.connect("P1", coup[cav_l])refl_l.connect("P0", arm_l["P0"])arm_r.connect("P0", coup[cav_r])refl_r.connect("P0", arm_r["P1"])fp.add_port(coup[bus_l], port_name="P0")fp.add_port(coup[bus_r], port_name="P1")fp.add_model(pf.CircuitModel(verbose=False), "Circuit")
'Circuit'
The circuit S-matrix composes Tidy3D-computed S-matrices of the mirror and coupler with analytic waveguide models for the straight arms. On resonance, the side-coupled standing-wave cavity reflects the light back along the bus, so transmission dips and reflection peaks together.
11:22:11 UTC WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Structure at 'structures[14]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
11:23:59 UTC WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
To determine the loaded quality factor we rescan the central resonance with 5 pm resolution and read the full width at half maximum from the interpolated half-max crossings, using the window edge as the baseline (the median would sit on the Lorentzian wings and bias the width).
11:24:49 UTC WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
11:24:50 UTC WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Structure at 'structures[14]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
11:26:14 UTC WARNING: Structure at 'structures[13]' has bounds that extend exactly to simulation edges. This can cause unexpected behavior. Ifintending to extend the structure to infinity along one dimension, use td.inf as a size variable instead to make this explicit.
WARNING: Suppressed 1 WARNING message.
WARNING: Warning messages were found in the solver log. For more information, check 'SimulationData.log' or use 'web.download_log(task_id)'.
For tapeout we attach two ebeam_gc_te1550 grating couplers from the SiEPIC library, placed by the SiEPIC conventions: a vertical fiber-array column at 127 \(\mu m\) pitch with an opt_in label on the Text (10, 0) layer for automated probing. The grating couplers serve as layout here and are not simulated. Rotating the cavity parallel to the coupler column keeps the routing to a single bend on each side.
Finally, we display the assembled chip for a last visual inspection before tapeout. PhotonForge components render natively in notebooks.
chip
To zoom into the cavity itself, we display the FP_CAVITY component, which shows the two inverse designed mirrors, the straight cavity arms, and the S-bend bus coupler in detail.
fp
The loaded quality factor of the cavity is set by the coupling gap rather than the mirrors: widening the gap trades extinction depth for a higher Q, while the inverse designed mirrors would support a mirror-limited finesse an order of magnitude beyond the loaded value measured here.
We use necessary cookies to run this website. With your permission, we
also use analytics cookies to understand site usage and marketing
cookies for advertising, retargeting, and HubSpot tracking.
Learn more about our cookie policy.
Privacy choices
Choose which optional cookies Flexcompute may use. Necessary cookies
are always on because they support core website behavior, security, and
saving your consent record.
Your browser is sending a Global Privacy Control signal, so marketing
cookies are disabled.
Subscribe
Thanks for subscribing
Publish Your Notebook
Thank you for publishingA confirmation email has been sent to your inbox. Your notebook will be available within the next 48 hours.
Community Library
TERMS & CONDITIONS
EFFECTIVE DATE: January 1, 2025
Terms and Conditions for User-Submitted Content
By submitting content to Flexcompute, you agree to the following terms:
1. Ownership and Copyright
All content, including but not limited to text, data, images, and other materials submitted by users,
remains the sole property of the original creator. Flexcompute does not claim ownership of the submitted
content or any intellectual property rights associated with it.
Users affirm that they own the copyright or have obtained all necessary permissions for the submitted
content and are fully responsible for ensuring that their submissions do not infringe on any third-party
rights.
2. Responsibility for Content
Users are solely responsible for the content they submit. Flexcompute does not endorse, guarantee,
or verify the accuracy, legality, or appropriateness of any submitted content. Users are responsible
for ensuring that their content complies with all applicable laws and regulations.
Users declare that they have obtained authorization from all co-authors and collaborators associated
with the submitted content, granting them the right to submit and sign on behalf of all contributors.
Users agree not to submit content that is illegal, defamatory, obscene, or violates the rights
of others, including privacy and intellectual property rights.
3. Modification and Presentation
Flexcompute reserves the right to review, edit, or modify submitted content to improve clarity,
formatting, and presentation while maintaining the original intent and message.
These modifications are made to enhance the overall quality and readability of the published material.
Users acknowledge that Flexcompute may format or present the content in a way
that aligns with our editorial and visual standards.
4. Liability Disclaimer
Flexcompute shall not be held liable for any disputes arising from user-submitted content, including
but not limited to copyright claims, inaccuracies, or damages resulting from the publication of user content.
Users agree to indemnify Flexcompute against any claims or legal actions resulting from their submitted content.
5. Content Use Rights
By submitting content, users grant Flexcompute a non-exclusive, royalty-free, worldwide license to publish, distribute,
and promote the content for the purposes of showcasing user contributions and marketing our services.
This license does not transfer ownership of the content or any copyright to Flexcompute.
Users retain the right to withdraw their submissions at any time.
Upon request, Flexcompute will remove the content from its platform.
6. Privacy and Confidentiality
Users acknowledge that submitted content may be publicly accessible and therefore waive any
rights to confidentiality or privacy regarding the published material.
Flexcompute will not share personal information of users without
their consent, in accordance with our privacy policy.
Community Library
How the process works:
You submit your notebook.
You will get a permanent URL to promote your work.
Cite the URL as a reference for others in your upcoming papers to enhance the impact.
When someone clicks your URL, your notebook will be displayed.
Enter your email address below to receive the presentation slides. In the future, we’ll share you very few emails when we have new tutorial release, development updates, valuable toolkits and technical guidance . You can unsubscribe at any time by clicking the link at the bottom of every email. We’ll never share your information.