The cost of running the entire optimization is about 8 FlexCredit (check this)
In this tutorial, we will show how to perform the adjoint-based inverse design of a quantum emitter (QE) light extraction structure. We will use a PointDipole to model the QE embedded within an integrated dielectric waveguide. Then, we will build an optimization problem to maximize the extraction efficiency of the dipole radiation into a collection waveguide. In addition, we will show how to use FluxMonitor objects in adjoint simulations to calculate the flux radiated from the dipole. You can also find helpful information in this related notebook.
Let’s start by importing the Python libraries used throughout this notebook.
# Standard python imports.import picklefrom typing import Listimport autograd.numpy as anpimport matplotlib.pylab as pltimport numpy as np# Import regular tidy3d.import tidy3d as tdimport tidy3d.web as webfrom tidy3d.plugins.autograd import ( adam, apply_updates, make_erosion_dilation_penalty, make_filter_and_project, value_and_grad,)
Simulation Set Up
The coupling region (design region) extends a single-mode dielectric waveguide placed over a lower refractive index substrate. The QE is modeled as a PointDipole oriented in the y-direction. The QE is placed within the design region so we surround it with a constant refractive index region to protect it from etching.
# Geometric parameters.cr_w =1.0# Coupling region width (um).cr_l =3.0# Coupling region length (um).wg_thick =0.19# Collection waveguide thickness (um).wg_width =0.35# Collection waveguide width (um).wg_length =1.0# Collection waveguide length (um).# Material.n_wg =3.50# Structure refractive index.n_sub =1.44# Substrate refractive index.# Fabrication constraints.min_feature =0.06# Minimum feature size.non_etch_r =0.06# Non-etched circular region radius (um).# Inverse design set up parameters.grid_size =0.015# Simulation grid size on design region (um).max_iter =100# Maximum number of iterations.iter_steps =5# Beta is increased at each iter_steps.beta_min =1.0# Minimum value for the tanh projection parameter.learning_rate =0.02# Simulation wavelength.wl =0.94# Central simulation wavelength (um).bw =0.04# Simulation bandwidth (um).n_wl =41# Number of wavelength points within the bandwidth.
Let’s calculate some variables used throughout the notebook. Here, we will also define the QE position and monitor planes.
We will start defining the density-based optimization functions to transform the design parameters into permittivity values. Here we include the ConicFilter, where we impose a minimum feature size fabrication constraint, and the tangent hyperbolic projection function, eliminating intermediary permittivity values as we increase the projection parameter beta. You can find more information in the Inverse design optimization of a compact grating coupler.
This function includes a circular region of constant permittivity value surrounding the QE. The objective here is to protect the QE from etching. In applications such as single photon sources, a larger unperturbed region surrounding the QE can be helpful to reduce linewidth broadening, as stated in J. Liu, K. Konthasinghe, M. Davanco, J. Lawall, V. Anant, V. Verma, R. Mirin, S. Nam, S. Woo, D. Jin, B. Ma, Z. Chen, H. Ni, Z. Niu, K. Srinivasan, "Single Self-Assembled InAs/GaAs Quantum Dots in Photonic Nanostructures: The Role of Nanofabrication," Phys. Rev. Appl. 9(6), 064019 (2018)DOI: 10.1103/PhysRevApplied.9.064019.
Now, we define a function to update the td.CustomMedium using the permittivity distribution. The simulation will include mirror symmetry concerning the y-direction, so only the upper half of the design region is returned by this function during the optimization process. To get the whole structure, you need to set unfold=True.
def update_design(eps, unfold=False) -> List[td.Structure]:# Definition of the coordinates x,y along the design region. coords_x = [(cr_center_x - cr_l /2) + ix * grid_size for ix inrange(nx_grid)] eps_val = anp.array(eps).reshape((nx_grid, ny_grid, 1))ifnot unfold: coords_yp = [0+ iy * grid_size for iy inrange(ny_grid)] coords =dict(x=coords_x, y=coords_yp, z=[0]) eps1 = td.SpatialDataArray(eps_val, coords) eps_medium = td.CustomMedium(permittivity=eps1) box = td.Box(center=(cr_center_x, cr_w /4, 0), size=(cr_l, cr_w /2, wg_thick)) structure = [td.Structure(geometry=box, medium=eps_medium)]# VJP for one of anp.copy(), anp.concatenate(), or anp.fliplr() not defined,# so the optimization should only be run with `unfold=False` for nowelse: coords_y = [-cr_w /2+ iy * grid_size for iy inrange(2* ny_grid)] coords =dict(x=coords_x, y=coords_y, z=[0]) eps1 = td.SpatialDataArray( anp.concatenate((anp.fliplr(anp.copy(eps_val)), eps_val), axis=1), coords ) eps_medium = td.CustomMedium(permittivity=eps1) box = td.Box(center=(cr_center_x, 0, 0), size=(cr_l, cr_w, wg_thick)) structure = [td.Structure(geometry=box, medium=eps_medium)]return structure
In the next cell, we define the output waveguide and the substrate, as well as the simulation monitors. It is worth mentioning the inclusion of a ModeMonitor in the output waveguide and a FluxMonitor box surrounding the dipole source to calculate the total radiated power.
# Input/output waveguide.waveguide = td.Structure( geometry=td.Box.from_bounds( rmin=(-eff_inf, -wg_width /2, -wg_thick /2), rmax=(wg_length, wg_width /2, wg_thick /2), ), medium=mat_wg,)# Substrate layer.substrate = td.Structure( geometry=td.Box.from_bounds( rmin=(-eff_inf, -eff_inf, -eff_inf), rmax=(eff_inf, eff_inf, -wg_thick /2) ), medium=mat_sub,)# Point dipole source located at the center of TiO2 thin film.dp_source = td.PointDipole( center=qe_pos.center, source_time=td.GaussianPulse(freq0=freq, fwidth=freqw), polarization="Ey",)# Mode monitor to compute the FOM.mode_spec = td.ModeSpec(num_modes=1, target_neff=n_wg)mode_monitor_fom = td.ModeMonitor( center=wg_mode_plan.center, size=wg_mode_plan.size, freqs=[freq], mode_spec=mode_spec, name="mode_monitor_fom",)# Flux monitor to compute the FOM.flux_monitor_fom = td.FluxMonitor( center=qe_flux_box.center, size=qe_flux_box.size, freqs=[freq], name="flux_monitor_fom", enable_adjoint=True,)# Mode monitor to compute spectral response.mode_spec = td.ModeSpec(num_modes=1, target_neff=n_wg)mode_monitor = td.ModeMonitor( center=wg_mode_plan.center, size=wg_mode_plan.size, freqs=freqs, mode_spec=mode_spec, name="mode_monitor",)# Flux monitor to compute spectral response.flux_monitor = td.FluxMonitor( center=qe_flux_box.center, size=qe_flux_box.size, freqs=freqs, name="flux_monitor",)# Field monitor to visualize the fields.field_monitor_xy = td.FieldMonitor( center=(size_x /2, 0, 0), size=(size_x, size_y, 0), freqs=freqs, name="field_xy",)
Lastly, we have a function that receives the design parameters from the optimization algorithm and then gathers the simulation objects altogether to create a td.Simulation.
We will also look at the collection waveguide mode to ensure we have considered the correct one in the ModeMonitor setup. We use the ModeSolver plugin to calculate the first two waveguide modes, as below.
from tidy3d.plugins.mode import ModeSolverfrom tidy3d.plugins.mode.web import run as run_mode_solversim_init = init_design.updated_copy(monitors=[field_monitor_xy, mode_monitor, flux_monitor])mode_solver = ModeSolver( simulation=sim_init, plane=wg_mode_plan, mode_spec=td.ModeSpec(num_modes=2), freqs=[freq],)modes = run_mode_solver(mode_solver, reduce_simulation=True)
19:36:25 UTC Mode solver created with
task_id='fdve-8f65754b-ca95-4db5-8815-3f8fe76239e9',
solver_id='mo-a991b77b-25ae-4af0-920d-f77a3643893b'.
19:36:47 UTC Mode solver status: queued
19:37:09 UTC Mode solver status: running
19:37:15 UTC Mode solver status: success
After inspecting the mode field distribution, we can confirm that the fundamental waveguide mode is mainly oriented in the y-direction, thus matching the dipole orientation.
19:37:23 UTC Estimated FlexCredit cost: 0.060. 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.
19:37:24 UTC 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.
19:37:34 UTC status = preprocess
19:37:38 UTC starting up solver
running solver
19:37:42 UTC early shutoff detected at 10%, exiting.
19:37:50 UTC Loading results from simulation_data.hdf5
The modal coupling efficiency is normalized by the dipole power. That is necessary because the dipole power will likely change significantly when the optimization algorithm modifies the design region.
The objective function defined next is the device figure-of-merit (FOM) minus a fabrication penalty.
# Figure of Merit (FOM) calculation.def fom(sim_data: td.SimulationData) ->float:"""Return the coupling efficiency."""# best to use autograd-wrapped numpy functions for differentiation mode_amps = sim_data["mode_monitor_fom"].amps.sel(direction="-", f=freq, mode_index=0).data mode_power = anp.sum(anp.abs(mode_amps) **2) dip_power = anp.sum(anp.abs(sim_data["flux_monitor_fom"].flux.data))return mode_power, dip_powerdef penalty(params, beta) ->float:"""Penalize changes in structure after erosion and dilation to enforce larger feature sizes.""" params_processed = pre_process(params, beta=beta) erode_dilate_penalty = make_erosion_dilation_penalty(radius=min_feature, dl=grid_size) ed_penalty = erode_dilate_penalty(params_processed)return ed_penalty# Objective function to be passed to the optimization algorithm.def obj(param, beta: float=1.0, step_num: int=None, verbose: bool=False) ->float: sim = make_adjoint_sim(param, beta, unfold=False) # non-differentiable if `unfold=True` task_name ="inv_des"if step_num: task_name +=f"_step_{step_num}" sim_data = web.run(sim, task_name=task_name, verbose=verbose) mode_power, dip_power = fom(sim_data) fom_val = mode_power / dip_power penalty_weight =0.1 penalty_val = penalty(param, beta) J = fom_val - penalty_weight * penalty_valreturn J, [sim_data, mode_power, dip_power, penalty_val]# Function to calculate the objective function value and its gradient with respect to the design parameters.# Use tidy3d's wrapped ag.value_and_grad() for it's auxiliary data functionalityobj_grad = value_and_grad(obj, has_aux=True)
In the following cell, we define some functions to save the optimization progress and load a previous optimization from the file.
# where to store historyhistory_fname ="misc/qe_light_coupler.pkl"def save_history(history_dict: dict) ->None:"""Convenience function to save the history to file."""withopen(history_fname, "wb") asfile: pickle.dump(history_dict, file)def load_history() ->dict:"""Convenience method to load the history from file."""withopen(history_fname, "rb") asfile: history_dict = pickle.load(file)return history_dict
Then, we will start a new optimization or load the parameters of a previous one.
In the optimization loop, we will gradually increase the projection parameter beta to eliminate intermediary permittivity values. At each iteration, we record the design parameters and the optimization history to restore them as needed.
iter_done =len(history_dict["values"])if iter_done < max_iter:# small # of iters for quick testingfor i inrange(iter_done, max_iter):print(f"Iteration = ({i +1} / {max_iter})") plt.subplots(1, 1, figsize=(3, 2)) plt.imshow(np.flipud(1- params.T), cmap="gray", vmin=0, vmax=1) plt.axis("off") plt.show()# Compute gradient and current objective function value. beta_i = i // iter_steps + beta_min (value, gradient), data = obj_grad(params, beta=beta_i, step_num=(i +1)) sim_data_i, mode_power_i, dip_power_i, penalty_val_i = [data[0]] + [ dat._value for dat in data[1:] ]# Outputs.print(f"\tbeta = {beta_i}")print(f"\tJ = {value:.4e}")print(f"\tgrad_norm = {np.linalg.norm(gradient):.4e}")print(f"\tpenalty = {penalty_val_i:.3f}")print(f"\tmode power = {mode_power_i:.3f}")print(f"\tdip power = {dip_power_i:.3f}")print(f"\tcoupling efficiency = {mode_power_i / dip_power_i:.3f}")# Compute and apply updates to the optimizer based on gradient (-1 sign to maximize obj_fn). updates, opt_state = optimizer.update(-gradient, opt_state, params) params = apply_updates(params, updates)# Cap parameters between 0 and 1. params = anp.minimum(params, 1.0) params = anp.maximum(params, 0.0)# Save history. history_dict["values"].append(value) history_dict["coupl_eff"].append(mode_power_i / dip_power_i) history_dict["penalty"].append(penalty_val_i) history_dict["params"].append(params) history_dict["beta"].append(beta_i) history_dict["gradients"].append(gradient) history_dict["opt_states"].append(opt_state)# history_dict["data"].append(sim_data_i) # Uncomment to store data, can create large files. save_history(history_dict)
The following figure shows how coupling efficiency and the fabrication penalty have evolved along the optimization process. The coupling efficiency quickly rises above 0.8, and along the binarization process, we can observe two large drops before a more stable final optimization stage. The formation of resonant modes sensitive to the small structural changes can potentially explain this behavior. The discontinuities in the fabrication penalty curve are caused by the increments in the projection parameter beta at each 5 iterations.
Interestingly, the final quantum emitter light extractor resembles a nanocavity, even though we have considered only the coupling efficiency into the output waveguide in the optimization. We have DBR mirrors on both sides of the dipole. However, on the left side, the mirror has only a few periods and partially reflects the radiation, which couples to the output waveguide.
23:03:19 UTC Estimated FlexCredit cost: 0.057. 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.
23:03:20 UTC 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.
23:03:37 UTC starting up solver
running solver
23:03:54 UTC early shutoff detected at 33%, exiting.
23:04:06 UTC Loading results from simulation_data.hdf5
In this cavity-like system, the extraction efficiency of photons from the QE into the collection waveguide mode is proportional to \(\beta\times C_{wg}\), where the \(\beta\)-factor quantifies the fraction of the QE spontaneous emission emitted in the cavity mode, and \(C_{wg}\) is the fraction of the cavity photons coupled to the guided mode A. Enderlin, Y. Ota, R. Ohta, N. Kumagai, S. Ishida, S. Iwamoto, and Y. Arakawa, "High guided mode–cavity mode coupling for an efficient extraction of spontaneous emission of a single quantum dot embedded in a photonic crystal nanobeam cavity," Phys. Rev. B 86, 075314 (2012)DOI: 10.1103/PhysRevB.86.075314. By the field distribution image below, we can see a cavity mode resonance, which should increase the Purcell factor at the QE position, thus contributing to a higher \(\beta\)-factor. At the same time, the partial reflection mirror at the left side was potentially optimized to adjust \(C_{wg}\).
To conclude, we will calculate the final coupling efficiency and the cavity Purcell value. The coupling efficiency is above 80% along an extensive wavelength range, and we have confirmed the Purcell enhancement.
The Simulation object has the .to_gds_file convenience function to export the final design to a GDS file. In addition to a file name, it is necessary to set a cross-sectional plane (z = 0 in this case) on which to evaluate the geometry, a frequency to evaluate the permittivity, and a permittivity_threshold to define the shape boundaries in custom mediums. See the GDS export notebook for a detailed example on using .to_gds_file and other GDS related functions.
# make the misc/ directory to store the GDS file if it doesn't exist alreadyimport osifnot os.path.exists("./misc/"): os.mkdir("./misc/")sim_final.to_gds_file( fname="./misc/inv_des_light_extractor_autograd.gds", z=0, permittivity_threshold=(eps_max + eps_min) /2, frequency=freq,)
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.