Guided Mode Expansion Method

After lot of trials of finding a way to optimize the Q factor in FDTD methods (specially in Tidy3D), we decided to switch to mode expansion methods. Thanks to Neelesh from UMD, we found a free python implementation of the GME method that’s called legume. It also comes with a differentiable implementation through the autograd backend, where we can compute the gradient of any objective function that depends on input parameters (Which is our main goal).

For theoretical background, you can refer to this paper.

Here, I shall write about setting up the simulation on legume for our case of calculating the Q factor of bullseye cavity.

Setting up the simulation

  • First, you need install the legume package
    • pip install legume-gme
  • Import all the necessary packages
    • import legume
    • import numpy as np
    • import matplotlib.pyplot as  plt
    • import time
    • from legume.minimize import Minimize
    • %load_ext autoreload
    • %autoreload 2
  • Declare some main parameters that’ll be used in your simulation
    • central_radius = 0.355
    • slab_height = 0.18
    • ring_width = 0.07
    • ring_number = 7
    • ring_period = 0.155
    • GaAs_permittivity = 12.25
    • quadrants = 4
    • Lx, Ly = 6, 6
  • Define the function rings so that given dcenter (deviation from the previous central radius value) and dperiod (deviation from the previous ring period value) the function generates the entire slab
    • def rings(dcenter, dperiod):
    •   lattice = legume.Lattice([Lx, 0], [0, Ly]) # We construct a superlattice which contains the entire structure, this two element array contains the elementary vectors of the lattice. See https://legume.readthedocs.io/en/latest/generated/legume.Lattice.html
    •   phc = legume.PhotCryst(lattice) # This creates a photonic crystal that can contain a number of layers. There are also other two parameters called eps_l and eps_u that contains the permittivities of the lower and upper claddings. By default, they are 1, which matches with our applications
    •   phc.add_layer(d = slab_height, eps_b = GaAs_permittivity) # By using the add_layer method we add the slab layer to our simulation with the correct parameters
    •   angles = np.linspace(0, 2*np.pi, 361) # to cover the entire range of the circle
    •   rings = [] # contains the 7 etched rings
    •   for j in range(ring_number):
    •     inner_radius = central_radius + dcenter + (ring_width + ring_period + dperiod) * j
    •     outer_radius = inner_radius + ring_width
    •     xs = [] # contains the x-coordinates of the vertices points
    •     ys = [] # # contains the y-coordinates of the vertices points
    •     for angle in angles:
    •       xs.append(outer_radius * np.cos(angle))
    •       ys.append(outer_radius * np.sin(angle))
    •     for angleR in angles[::-1]:
    •       xs.append(inner_radius * np.cos(angleR))
    •       ys.append(inner_radius * np.sin(angleR))
    •     ring = legume.Poly(
    •         eps = 1.0,
    •         x_edges = xs,
    •         y_edges = ys
    •     )
    •     rings.append(ring)
    •   phc.add_shape(rings) # We add the rings to the photonic crystal object we created
    •   return phc
  • Now, define the function to run the simulation using the guided mode expansion method
    • def gme_cavity(dx, dy, gmax, truncate_g, options): # given, dx, dy (deviation values of your input parameters), gmax (Maximum reciprocal lattice wave-vector length in units of 2pi/a) truncate_g (Truncation of the recriprocal vectors), options (options for the legume.GuidedModeExp.run() method), this function sets up the simulation. I shall elaborate more on these parameters below
    •   bullseye = rings(dx, dy) # The rings function returns your desired cavity
    •   options[‘compute_im’] = False # One of the many options. We don’t want to calculate the imaginary frequencies
    •   gme = legume.GuidedModeExp(bullseye, gmax = gmax, truncate_g = truncate_g)
    •   kpoints = np.array([
    •       [0, 0],
    •       [np.pi/Lx, 0],
    •       [np.pi/Lx, np.pi/Ly],
    •       [0, 0]
    •   ])
    •   gme.run(kpoints = kpoints, **options)
    •   (freq_im, _, _) = gme.compute_rad(0, [Lx*Ly])
    •   Q = gme.freqs[0, Lx*Ly]/2/freq_im[0]
    •   return(gme, Q)

Leave a Reply

Your email address will not be published. Required fields are marked *