-
Notifications
You must be signed in to change notification settings - Fork 14
FXC-3769 inverse design seminar notebooks #394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Inverse Design Seminar Demos | ||
|
|
||
| These notebooks track the inverse-designed dual-layer grating coupler workflow that we presented during the October 9, 2025 seminar. Start with the simulation setup, follow the optimization and robustness studies, and finish with a calibration example that ties measurements back into the digital twin. | ||
|
|
||
| Seminar recording: https://www.youtube.com/watch?v=OpVBJmomzoo | ||
|
|
||
| ## Repository Layout | ||
| - `00_setup_guide.ipynb`: builds the baseline Tidy3D simulation for a dual-layer grating coupler and visualizes the initial, uniform geometry. | ||
| - `01_bayes.ipynb`: performs a five-parameter Bayesian optimization to locate a high-performing uniform grating without gradient information. | ||
| - `02_adjoint.ipynb`: expands to per-tooth parameters and applies adjoint gradients with Adam to apodize the grating and boost peak efficiency. | ||
| - `03_sensitivity.ipynb`: quantifies fabrication variability through ±20 nm bias sweeps, Monte Carlo sampling, and adjoint-based sensitivity analysis. | ||
| - `04_adjoint_robust.ipynb`: optimizes the adjoint design against nominal/over/under etch corners by penalizing performance variance, yielding a fabrication-aware geometry. | ||
| - `05_robust_comparison.ipynb`: reruns the Monte Carlo experiment with the robust and nominal designs side by side to measure yield improvements. | ||
| - `06_measurement_calibration.ipynb`: demonstrates how adjoint gradients can back-fit SiN widths so simulated spectra line up with measured (synthetic) data. | ||
|
|
||
| Supporting assets: | ||
| - `nbconvert/`: Python exports of every notebook (`jupyter nbconvert --to python`) for quick diffing and review. | ||
| - `setup.py`: shared simulation utilities, geometry constraints, and helper routines used across the series. | ||
| - `optim.py`: lightweight, autograd-friendly Adam implementation plus parameter clipping helpers. | ||
| - `results/`: JSON snapshots of intermediate designs (Bayesian best guess, adjoint refinements, robust solution) consumed by later notebooks. | ||
|
|
||
| ## Getting Started | ||
| 1. Install dependencies (Python 3.10+ recommended): | ||
| ```bash | ||
| pip install tidy3d bayes_opt autograd pandas matplotlib scipy | ||
| ``` | ||
| You will also need an active Tidy3D account and API access since every notebook submits jobs with `tidy3d.web.run`. | ||
| 2. Launch Jupyter and open the notebooks in numerical order; each one assumes the prior results exist in `results/`. | ||
| 3. If you prefer scripts, run the equivalents under `nbconvert/`, but keep in mind they expect the same working directory layout. | ||
|
|
||
| ## Suggested Workflow | ||
| - Use `00_setup_guide.ipynb` to verify your environment and understand the baseline geometry. | ||
| - Iterate through optimization (`01`–`04`) to see how global and local methods complement each other. | ||
| - Leverage the sensitivity and comparison notebooks (`03`, `05`) when you need wafer-level statistics. | ||
| - Apply `06_measurement_calibration.ipynb` after you gather measured spectra to keep your model synced with hardware. | ||
|
|
||
| Enjoy the seminar content, and reach out if you adapt these workflows to your own devices - we’d love to hear what you build. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| """Utility routines for functional-style optimization in the tutorial notebooks. | ||
|
|
||
| The helpers here avoid mutating inputs so they play nicely with autograd. | ||
| """ | ||
|
|
||
| import autograd.numpy as np | ||
| from autograd.misc import flatten | ||
|
|
||
|
|
||
| def clip_params(params, bounds): | ||
| """Clip a parameter dictionary according to per-key bounds. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| params : dict[str, np.ndarray] | ||
| Dictionary mapping parameter names to array values. | ||
| bounds : dict[str, tuple[float | None, float | None]] | ||
| Lower and upper limits for each parameter. Missing keys default to no | ||
| clipping. ``None`` disables a bound on that side. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, np.ndarray] | ||
| New dictionary with values clipped to the requested interval. | ||
| """ | ||
| clipped = {} | ||
| for key, value in params.items(): | ||
| lo, hi = bounds.get(key, (None, None)) | ||
| lo_val = -np.inf if lo is None else lo | ||
| hi_val = np.inf if hi is None else hi | ||
| clipped[key] = np.clip(value, lo_val, hi_val) | ||
| return clipped | ||
|
|
||
|
|
||
| def _flatten(tree): | ||
| """Return a flat representation of a pytree and its inverse transform.""" | ||
| flat, unflatten = flatten(tree) | ||
| return np.array(flat, dtype=float), unflatten | ||
|
|
||
|
|
||
| def init_adam(params, lr=1e-2, beta1=0.9, beta2=0.999, eps=1e-8): | ||
| """Initialize Adam optimizer state for a parameter pytree. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| params : dict[str, np.ndarray] | ||
| Current parameter values used to size the optimizer state. | ||
| lr : float = 1e-2 | ||
| Learning rate applied to each step. | ||
| beta1 : float = 0.9 | ||
| Exponential decay applied to the first moment estimate. | ||
| beta2 : float = 0.999 | ||
| Exponential decay applied to the second moment estimate. | ||
| eps : float = 1e-8 | ||
| Numerical stabilizer added inside the square-root denominator. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, object] | ||
| Dictionary holding the Adam accumulator vectors and hyperparameters. | ||
| """ | ||
| flat_params, unflatten = _flatten(params) | ||
| state = { | ||
| "t": 0, | ||
| "m": np.zeros_like(flat_params), | ||
| "v": np.zeros_like(flat_params), | ||
| "unflatten": unflatten, | ||
| "lr": lr, | ||
| "beta1": beta1, | ||
| "beta2": beta2, | ||
| "eps": eps, | ||
| } | ||
| return state | ||
|
|
||
|
|
||
| def adam_update(grads, state): | ||
| """Compute Adam parameter updates from gradients and state. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| grads : dict[str, np.ndarray] | ||
| Gradient pytree with the same structure as the parameters. | ||
| state : dict[str, object] | ||
| Optimizer state returned by :func:`init_adam`. | ||
|
|
||
| Returns | ||
| ------- | ||
| updates : dict[str, np.ndarray] | ||
| Parameter deltas that should be subtracted from the current values. | ||
| new_state : dict[str, object] | ||
| Updated optimiser state after incorporating the gradients. | ||
| """ | ||
| g_flat, _ = _flatten(grads) | ||
| t = state["t"] + 1 | ||
|
|
||
| beta1 = state["beta1"] | ||
| beta2 = state["beta2"] | ||
| m = (1 - beta1) * g_flat + beta1 * state["m"] | ||
| v = (1 - beta2) * (g_flat * g_flat) + beta2 * state["v"] | ||
|
|
||
| m_hat = m / (1 - beta1**t) | ||
| v_hat = v / (1 - beta2**t) | ||
| updates_flat = state["lr"] * (m_hat / (np.sqrt(v_hat) + state["eps"])) | ||
|
|
||
| new_state = { | ||
| **state, | ||
| "t": t, | ||
| "m": m, | ||
| "v": v, | ||
| } | ||
| updates = state["unflatten"](updates_flat) | ||
| return updates, new_state | ||
|
|
||
|
|
||
| def apply_updates(params, updates): | ||
| """Apply additive updates to a parameter pytree. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| params : dict[str, np.ndarray] | ||
| Original parameter dictionary. | ||
| updates : dict[str, np.ndarray] | ||
| Update dictionary produced by :func:`adam_update`. | ||
|
|
||
| Returns | ||
| ------- | ||
| dict[str, np.ndarray] | ||
| New dictionary with ``updates`` subtracted element-wise. | ||
| """ | ||
| p_flat, unflatten = _flatten(params) | ||
| u_flat, _ = _flatten(updates) | ||
| return unflatten(p_flat - u_flat) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| { | ||
| "widths_si": [ | ||
| 0.4596210205783229, | ||
| 0.619021827243859, | ||
| 0.5091612364667776, | ||
| 0.44862540195053635, | ||
| 0.4519060476837924, | ||
| 0.45990865650576407, | ||
| 0.4707837996827998, | ||
| 0.5045578907962605, | ||
| 0.5736868046251105, | ||
| 0.38818932960888375, | ||
| 0.5103031259894519, | ||
| 0.5644712722428283, | ||
| 0.43499552429872157, | ||
| 0.7128307820061767, | ||
| 0.5880320841841612 | ||
| ], | ||
| "gaps_si": [ | ||
| 0.5471012483789967, | ||
| 0.5751743079764461, | ||
| 0.7609864084716235, | ||
| 0.6784088880064844, | ||
| 0.7110294438923443, | ||
| 0.7162660313191388, | ||
| 0.6572002123756033, | ||
| 0.6002624533497121, | ||
| 0.5423082463648713, | ||
| 0.6014877337345622, | ||
| 0.47967659439980775, | ||
| 0.4947734252066087, | ||
| 0.8223426836734665, | ||
| 0.3018370285344259, | ||
| 0.674088964498069 | ||
| ], | ||
| "widths_sin": [ | ||
| 0.8370101006141384, | ||
| 0.7358141802861392, | ||
| 0.8591427172226472, | ||
| 0.7188178275200784, | ||
| 0.7251240965135923, | ||
| 0.6477434589338728, | ||
| 0.630917116356729, | ||
| 0.7967252173677236, | ||
| 0.6712968595454194, | ||
| 0.664276799817368, | ||
| 1.0, | ||
| 0.6604544789433066, | ||
| 1.0, | ||
| 0.9505322926528258, | ||
| 0.6800512052506013 | ||
| ], | ||
| "gaps_sin": [ | ||
| 0.4946890226723554, | ||
| 0.5365379587726309, | ||
| 0.46065692058517027, | ||
| 0.42203895741407915, | ||
| 0.4834780230256085, | ||
| 0.5352412107737056, | ||
| 0.6262985005842511, | ||
| 0.46195800688016325, | ||
| 0.30113583498689617, | ||
| 0.44560626938492015, | ||
| 0.3061735955224083, | ||
| 0.39071041714258803, | ||
| 0.4939209477233372, | ||
| 0.312008460456016, | ||
| 0.3677091398546308 | ||
| ], | ||
| "first_gap_si": -0.6795213307403507, | ||
| "first_gap_sin": 0.36214218348124017, | ||
| "target_power": 0.5920533670750903 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| { | ||
| "widths_si": [ | ||
| 0.46178686916966183, | ||
| 0.6208605639642829, | ||
| 0.5023204446163415, | ||
| 0.45004614671073134, | ||
| 0.45596025644051, | ||
| 0.4585759420364934, | ||
| 0.4712105530469806, | ||
| 0.5100867903239975, | ||
| 0.5706512594868336, | ||
| 0.38871925269320423, | ||
| 0.5113155720985404, | ||
| 0.5659665523699627, | ||
| 0.4365234841396745, | ||
| 0.714475132758153, | ||
| 0.5937534510699842 | ||
| ], | ||
| "gaps_si": [ | ||
| 0.5466794735975892, | ||
| 0.5771193435069213, | ||
| 0.7533781486901343, | ||
| 0.685123665411786, | ||
| 0.7095082956179035, | ||
| 0.706653647605633, | ||
| 0.6514719255254264, | ||
| 0.5905499349639569, | ||
| 0.5425124682940347, | ||
| 0.6029380560625501, | ||
| 0.48106002793948394, | ||
| 0.49752227029601187, | ||
| 0.827596920790486, | ||
| 0.3073181132794698, | ||
| 0.674088964498069 | ||
| ], | ||
| "widths_sin": [ | ||
| 0.834943058398537, | ||
| 0.7353263082825178, | ||
| 0.8580322415966256, | ||
| 0.7188867567232994, | ||
| 0.726264496324061, | ||
| 0.6481023720890084, | ||
| 0.6319137099717905, | ||
| 0.7981058278133871, | ||
| 0.6755233787983985, | ||
| 0.6694648977631461, | ||
| 1.0, | ||
| 0.66622242713095, | ||
| 0.9909790766628245, | ||
| 0.9415172852346699, | ||
| 0.6734413979723209 | ||
| ], | ||
| "gaps_sin": [ | ||
| 0.49254257832079273, | ||
| 0.5375042420490991, | ||
| 0.4607741395165546, | ||
| 0.42353529383379157, | ||
| 0.4850861138159179, | ||
| 0.5366293370824164, | ||
| 0.6290946074785405, | ||
| 0.4656365963898396, | ||
| 0.30596460535700865, | ||
| 0.44925187170566083, | ||
| 0.3109077179555021, | ||
| 0.3812328426951072, | ||
| 0.48474019558900283, | ||
| 0.30278224480267296, | ||
| 0.3677091398546308 | ||
| ], | ||
| "first_gap_si": -0.679228403609027, | ||
| "first_gap_sin": 0.3604720266258309, | ||
| "etch_bias_modeled": 0.02 | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| { | ||
| "width_si": 0.4488560097489734, | ||
| "gap_si": 0.674088964498069, | ||
| "width_sin": 0.8434351040463873, | ||
| "gap_sin": 0.3677091398546308, | ||
| "first_gap_si": -0.6329943025756396, | ||
| "target_power": 0.4515458912419618, | ||
| "coupling_loss_db": 3.4529810515037673 | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you convert this to a rst file, add the toctree in here for each notebook and then it's relatively self contained to its own section in the docs. Then you just need to add this file to a notebook section toctree at whichever section hierarchy you want?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically you can move this file to docs/ but change the toctree to the notebook folder path in this directory
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, the bigger problem will be the way that this will get represented in the commercial website notebooks. That's why they're all at the same top level of this repo.