Archived seminar material. These saved outputs were produced with Tidy3D 2.9.1
In the previous notebook, we used Bayesian Optimization to find a good starting design. The strength of that global optimization approach was its ability to efficiently search a low-dimensional parameter space. However, it was limited: we assumed the grating was uniform, with every tooth and gap being identical.
To push the performance further, we need to apodize the grating, which means varying the dimensions of each tooth individually to better match the profile of the incoming Gaussian beam. This drastically increases the number of design parameters. For our 15-element dual-layer grating, the design space just expanded from 5 global parameters to over 60 individual feature dimensions!
For such a high-dimensional problem, a global search is no longer efficient. In this notebook, we switch to a powerful local, gradient-based optimization technique, enabled by the adjoint method, to refine our design.
The Power of the Adjoint Method
The key challenge in gradient-based optimization is computing the gradient itself. A naive approach like finite differences would require N+1 simulations to find the gradient with respect to N parameters. For our ~60 parameters, this is far too slow.
This is where the adjoint method comes in. Tidy3D’s automatic differentiation capability uses this method under the hood. It allows us to compute the gradient of our objective function (the coupling efficiency) with respect to all design parameters simultaneously in just two simulations per iteration, regardless of how many parameters there are. This efficiency is what makes it possible to locally optimize structures with thousands of free parameters. We start from the global design found earlier and use these gradients to walk toward a nearby, higher-performance solution.
def objective(params):"""Objective function for adjoint optimization. Takes a dictionary of geometry parameters and returns a scalar loss. The function is differentiable via autograd so the adjoint method can supply gradients for every parameter in one shot. Parameters ---------- params: Dictionary holding the current grating geometry arrays. Returns ------- float Negative of the coupling efficiency so gradient descent maximizes power. """# Build the tidy3d simulation with the current parameters. Autograd traces# everything through the power extraction so the adjoint gradient can be# computed efficiently. sim = make_simulation( params["widths_si"], params["gaps_si"], params["widths_sin"], params["gaps_sin"], first_gap_si=params["first_gap_si"], first_gap_sin=params["first_gap_sin"], ) sim_data = web.run(sim, task_name="gc_adjoint", verbose=False)# Convert the mode monitor result into a scalar objective (negative power)# so minimization increases the coupled power at the target wavelength. power_da = get_mode_monitor_power(sim_data) freq0 = td.C_0 / center_wavelength target_power = power_da.sel(f=freq0, method="nearest")return-target_power.item()
High-Dimensional Parameterization
We load the best uniform design from the Bayesian search and expand those scalars into per-tooth arrays. Each layer now has individual widths and gaps, and first_gap_si remains a crucial phase-matching variable.
Each iteration proceeds as follows: 1. Evaluate both the loss and gradient with value_and_grad. 2. Use the Adam optimizer to compute a parameter update with momentum. 3. Apply the update to the parameters. 4. Clip the result to obey fabrication bounds.
for n inrange(num_iters): value, grad = vg_fun(params) target_power =-value target_powers.append(target_power)print(f"iter {n}: target_power={target_power:.4f}") updates, opt_state = adam_update(grad, opt_state) params = apply_updates(params, updates) params = clip_params(params, bounds)
iter 0: target_power=0.3426
iter 1: target_power=0.3342
iter 2: target_power=0.3857
iter 3: target_power=0.4068
iter 4: target_power=0.4014
iter 5: target_power=0.4060
iter 6: target_power=0.4225
iter 7: target_power=0.4361
iter 8: target_power=0.4367
iter 9: target_power=0.4350
iter 10: target_power=0.4383
iter 11: target_power=0.4460
iter 12: target_power=0.4502
iter 13: target_power=0.4528
iter 14: target_power=0.4527
iter 15: target_power=0.4522
iter 16: target_power=0.4579
iter 17: target_power=0.4630
iter 18: target_power=0.4638
iter 19: target_power=0.4651
iter 20: target_power=0.4674
iter 21: target_power=0.4704
iter 22: target_power=0.4720
iter 23: target_power=0.4718
iter 24: target_power=0.4754
iter 25: target_power=0.4794
iter 26: target_power=0.4783
iter 27: target_power=0.4805
iter 28: target_power=0.4838
iter 29: target_power=0.4860
iter 30: target_power=0.4885
iter 31: target_power=0.4889
iter 32: target_power=0.4911
iter 33: target_power=0.4933
iter 34: target_power=0.4945
iter 35: target_power=0.4982
iter 36: target_power=0.4977
iter 37: target_power=0.4998
iter 38: target_power=0.5021
iter 39: target_power=0.5043
iter 40: target_power=0.5069
iter 41: target_power=0.5093
iter 42: target_power=0.5109
iter 43: target_power=0.5109
iter 44: target_power=0.5135
iter 45: target_power=0.5169
iter 46: target_power=0.5192
iter 47: target_power=0.5213
iter 48: target_power=0.5230
iter 49: target_power=0.5252
iter 50: target_power=0.5280
iter 51: target_power=0.5309
iter 52: target_power=0.5341
iter 53: target_power=0.5362
iter 54: target_power=0.5384
iter 55: target_power=0.5406
iter 56: target_power=0.5440
iter 57: target_power=0.5459
iter 58: target_power=0.5498
iter 59: target_power=0.5514
iter 60: target_power=0.5528
iter 61: target_power=0.5541
iter 62: target_power=0.5543
iter 63: target_power=0.5561
iter 64: target_power=0.5567
iter 65: target_power=0.5575
iter 66: target_power=0.5591
iter 67: target_power=0.5590
iter 68: target_power=0.5606
iter 69: target_power=0.5613
iter 70: target_power=0.5619
iter 71: target_power=0.5626
iter 72: target_power=0.5636
iter 73: target_power=0.5647
iter 74: target_power=0.5638
iter 75: target_power=0.5657
iter 76: target_power=0.5663
iter 77: target_power=0.5669
iter 78: target_power=0.5676
iter 79: target_power=0.5676
Comparing the spectra shows the apodized design significantly boosts coupling near 1.55 µm relative to the uniform baseline from Bayesian optimization.
Lastly, we need to export the optimized grating geometry for further analysis.
def serialize_params(param_dict):"""Detach autograd containers into JSON-serializable Python objects."""return {"widths_si": [float(value) for value in param_dict["widths_si"]],"gaps_si": [float(value) for value in param_dict["gaps_si"]],"widths_sin": [float(value) for value in param_dict["widths_sin"]],"gaps_sin": [float(value) for value in param_dict["gaps_sin"]],"first_gap_si": float(param_dict["first_gap_si"]),"first_gap_sin": float(param_dict["first_gap_sin"]), }export_path = Path("./results/gc_adjoint_best.json")export_path.parent.mkdir(parents=True, exist_ok=True)payload = serialize_params(params)payload["target_power"] =float(target_powers[-1]) if target_powers elseNonewith export_path.open("w", encoding="utf-8") as f: json.dump(payload, f, indent=2)print(f"Saved adjoint design to {export_path.resolve()}")
Saved adjoint design to /home/yannick/flexcompute/worktrees/seminar_notebooks/docs/notebooks/2025-10-09-invdes-seminar/results/gc_adjoint_best.json
Conclusion and Next Steps
Switching to a gradient-based approach unlocked high-dimensional refinements and reduced the coupling loss by more than a decibel. The resulting design is finely tuned for nominal fabrication, so the next notebook introduces robust optimization to preserve performance under realistic manufacturing variations.
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.