def create_sim_space(gds_fg: str, gds_bg: str) -> optplan.SimulationSpace: """Creates the simulation space. The simulation space contains information about the boundary conditions, gridding, and design region of the simulation. The material stack is 220 nm of silicon surrounded by oxide. The refractive index of the silicon changes based on whether the global variable `SIM_2D` is set. Args: gds_fg: Location of the foreground GDS file. gds_bg: Location of the background GDS file. Returns: A `SimulationSpace` description. """ mat_oxide = optplan.Material(mat_name="SiO2") if SIM_2D: device_index = SI_2D_INDEX else: device_index = SI_3D_INDEX mat_stack = optplan.GdsMaterialStack( background=mat_oxide, stack=[ optplan.GdsMaterialStackLayer( foreground=mat_oxide, background=mat_oxide, gds_layer=[100, 0], extents=[-10000, -110], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Ta2O5"), background=mat_oxide, gds_layer=[100, 0], extents=[-110, 110], ), ], ) if SIM_2D: # If the simulation is 2D, then we just take a slice through the # device layer at z = 0. We apply periodic boundary conditions along # the z-axis by setting PML thickness to zero. sim_region = optplan.Box3d( center=[0, 0, 0], extents=[9000, 3000, GRID_SPACING]) pml_thickness = [10, 10, 10, 10, 0, 0] else: sim_region = optplan.Box3d(center=[0, 0, 0], extents=[5000, 5000, 2000]) pml_thickness = [10, 10, 10, 10, 10, 10] return optplan.SimulationSpace( name="simspace_cont", mesh=optplan.UniformMesh(dx=GRID_SPACING), eps_fg=optplan.GdsEps(gds=gds_fg, mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds=gds_bg, mat_stack=mat_stack), sim_region=sim_region, selection_matrix_type="direct_lattice", boundary_conditions=[optplan.BlochBoundary()] * 6, pml_thickness=pml_thickness, )
def make_simspace(): mat_stack = optplan.GdsMaterialStack( background=optplan.Material(mat_name="Air"), stack=[ optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="SiO2"), background=optplan.Material(mat_name="SiO2"), gds_layer=[101, 0], extents=[-10000, -110], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="Air"), gds_layer=[100, 0], extents=[-110, 110], ), ], ) simspace_spec = optplan.SimulationSpace( mesh=optplan.UniformMesh(dx=110), eps_fg=optplan.GdsEps(gds="WDM_example_fg.gds", mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds="WDM_example_bg.gds", mat_stack=mat_stack), sim_region=optplan.Box3d(center=[0, 0, 0], extents=[5000, 5000, 300]), pml_thickness=[10, 10, 10, 10, 0, 0], ) return simspace.SimulationSpace(simspace_spec, TESTDATA)
def test_simspace_reduced(): mat_stack = optplan.GdsMaterialStack( background=optplan.Material(mat_name="Air"), stack=[ optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="SiO2"), background=optplan.Material(mat_name="SiO2"), gds_layer=[101, 0], extents=[-10000, -110], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="Air"), gds_layer=[100, 0], extents=[-110, 110], ), ], ) simspace_spec = optplan.SimulationSpace( mesh=optplan.UniformMesh(dx=40), eps_fg=optplan.GdsEps(gds="WDM_example_fg.gds", mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds="WDM_example_bg.gds", mat_stack=mat_stack), sim_region=optplan.Box3d(center=[0, 0, 0], extents=[5000, 5000, 500]), pml_thickness=[10, 10, 10, 10, 0, 0], selection_matrix_type=optplan.SelectionMatrixType.REDUCED.value, ) space = simspace.SimulationSpace(simspace_spec, TESTDATA) space_inst = space(1550) eps_bg = space_inst.eps_bg.grids eps_fg = fdfd_tools.unvec( fdfd_tools.vec(space_inst.eps_bg.grids) + space_inst.selection_matrix @ np.ones(np.prod(space._design_dims)), space_inst.eps_bg.shape) assert space_inst.selection_matrix.shape == (609375, 2601) np.testing.assert_array_equal(eps_bg[2][:, :, -3], 1) np.testing.assert_allclose(eps_bg[2][10, 10, 2], 2.0852) np.testing.assert_allclose(eps_fg[2][107, 47, 2], 2.0852) np.testing.assert_allclose(eps_fg[2][107, 47, 6], 12.086617) np.testing.assert_allclose(eps_fg[2][112, 57, 6], 1) np.testing.assert_allclose(eps_fg[2][107, 47, 10], 1)
def test_plane_power_grad(): space = Simspace( TESTDATA, optplan.SimulationSpace( pml_thickness=[0, 0, 0, 0, 0, 0], mesh=optplan.UniformMesh(dx=40), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[80, 80, 80], ), eps_bg=optplan.GdsEps( gds="straight_waveguide.gds", mat_stack=optplan.GdsMaterialStack( background=optplan.Material(mat_name="air"), stack=[ optplan.GdsMaterialStackLayer( gds_layer=[100, 0], extents=[-80, 80], foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="air"), ), ], ), ), )) wlen = 1550 power_fun = poynting.PowerTransmissionFunction( field=problem.Variable(1), simspace=space, wlen=wlen, plane_slice=grid_utils.create_region_slices(space.edge_coords, [0, 0, 0], [40, 80, 80]), axis=gridlock.axisvec2axis([1, 0, 0]), polarity=gridlock.axisvec2polarity([1, 0, 0])) field = np.arange(np.prod(space.dims) * 3).astype(np.complex128) * 1j grad_actual = power_fun.grad([field], 1) fun = lambda vec: power_fun.eval([vec]) grad_brute = eval_grad_brute_wirt(field, fun) np.testing.assert_array_almost_equal(grad_actual[0], grad_brute, decimal=4)
def test_stored_energy_grad(): space = Simspace( TESTDATA, optplan.SimulationSpace( pml_thickness=[0, 0, 0, 0, 0, 0], mesh=optplan.UniformMesh(dx=40), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[80, 80, 80], ), eps_bg=optplan.GdsEps( gds="straight_waveguide.gds", mat_stack=optplan.GdsMaterialStack( background=optplan.Material(mat_name="air"), stack=[ optplan.GdsMaterialStackLayer( gds_layer=[100, 0], extents=[-80, 80], foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="air"), ), ], ), ), )) wlen = 1550 energy_fun = stored_energy.StoredEnergyFunction( input_function=problem.Variable(1), simspace=space, center=[0,0,0], extents=[0,0,0], epsilon=space._eps_bg) plane_slice=grid_utils.create_region_slices(space.edge_coords, [0, 0, 0], [40, 80, 80]), axis=gridlock.axisvec2axis([1, 0, 0]), polarity=gridlock.axisvec2polarity([1, 0, 0]))
def create_sim_space( gds_fg_name: str, gds_bg_name: str, grating_len: float = 12000, etch_frac: float = 0.5, box_thickness: float = 2000, wg_width: float = 12000, wg_thickness: float = 220, buffer_len: float = 1500, dx: int = 40, num_pmls: int = 10, visualize: bool = False, ) -> optplan.SimulationSpace: """Creates the simulation space. The simulation space contains information about the boundary conditions, gridding, and design region of the simulation. Args: gds_fg_name: Location to save foreground GDS. gds_bg_name: Location to save background GDS. grating_len: Length of the grating coupler and design region. etch_frac: Etch fraction of the grating. 1.0 indicates a fully-etched grating. box_thickness: Thickness of BOX layer in nm. wg_thickness: Thickness of the waveguide. wg_width: Width of the waveguide. buffer_len: Buffer distance to put between grating and the end of the simulation region. This excludes PMLs. dx: Grid spacing to use. num_pmls: Number of PML layers to use on each side. visualize: If `True`, draws the polygons of the GDS file. Returns: A `SimulationSpace` description. """ # Calculate the simulation size, including PMLs sim_size = [ grating_len + 2 * buffer_len + dx * num_pmls, wg_width + 2 * buffer_len + dx * num_pmls ] # First, we use `gdspy` to draw the waveguides and shapes that we would # like to use. Instead of programmatically generating a GDS file using # `gdspy`, we could also simply provide a GDS file (e.g. drawn using # KLayout). # Declare some constants to represent the different layers. LAYER_SILICON_ETCHED = 100 LAYER_SILICON_NONETCHED = 101 # Create rectangles corresponding to the waveguide, the BOX layer, and the # design region. We extend the rectangles outside the simulation region # by multiplying locations by a factor of 1.1. # We distinguish between the top part of the waveguide (which is etched) # and the bottom part of the waveguide (which is not etched). waveguide_top = gdspy.Rectangle((-1.1 * sim_size[0] / 2, -wg_width / 2), (-grating_len / 2, wg_width / 2), LAYER_SILICON_ETCHED) waveguide_bottom = gdspy.Rectangle((-1.1 * sim_size[0] / 2, -wg_width / 2), (grating_len / 2, wg_width / 2), LAYER_SILICON_NONETCHED) design_region = gdspy.Rectangle((-grating_len / 2, -wg_width / 2), (grating_len / 2, wg_width / 2), LAYER_SILICON_ETCHED) # Generate the foreground and background GDS files. gds_fg = gdspy.Cell("FOREGROUND", exclude_from_current=True) gds_fg.add(waveguide_top) gds_fg.add(waveguide_bottom) gds_fg.add(design_region) gds_bg = gdspy.Cell("BACKGROUND", exclude_from_current=True) gds_bg.add(waveguide_top) gds_bg.add(waveguide_bottom) gdspy.write_gds(gds_fg_name, [gds_fg], unit=1e-9, precision=1e-9) gdspy.write_gds(gds_bg_name, [gds_bg], unit=1e-9, precision=1e-9) # The BOX layer/silicon device interface is set at `z = 0`. # # Describe materials in each layer. # We actually have four material layers: # 1) Silicon substrate # 2) Silicon oxide BOX layer # 3) Bottom part of grating that is not etched # 4) Top part of grating that can be etched. # # The last two layers put together properly describe a partial etch. # # Note that the layer numbering in the GDS file is arbitrary. In our case, # layer 100 and 101 correspond to actual structure. Layer 300 is a dummy # layer; it is used for layers that only have one material (i.e. the # background and foreground indices are identical) so the actual structure # used does not matter. stack = [ optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="Si"), # Note that layer number here does not actually matter because # the foreground and background are the same material. gds_layer=[300, 0], extents=[-10000, -box_thickness], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="SiO2"), background=optplan.Material(mat_name="SiO2"), gds_layer=[300, 0], extents=[-box_thickness, 0], ), ] # If `etch-frac` is 1, then we do not need two separate layers. if etch_frac != 1: stack.append( optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="SiO2"), gds_layer=[LAYER_SILICON_NONETCHED, 0], extents=[0, wg_thickness * (1 - etch_frac)], )) stack.append( optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="SiO2"), gds_layer=[LAYER_SILICON_ETCHED, 0], extents=[wg_thickness * (1 - etch_frac), wg_thickness], )) mat_stack = optplan.GdsMaterialStack( # Any region of the simulation that is not specified is filled with # oxide. background=optplan.Material(mat_name="SiO2"), stack=stack, ) sim_z_start = -box_thickness - 1000 sim_z_end = wg_thickness + 1500 # Create a simulation space for both continuous and discrete optimization. simspace = optplan.SimulationSpace( name="simspace", mesh=optplan.UniformMesh(dx=dx), eps_fg=optplan.GdsEps(gds=gds_fg_name, mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds=gds_bg_name, mat_stack=mat_stack), # Note that we explicitly set the simulation region. Anything # in the GDS file outside of the simulation extents will not be drawn. sim_region=optplan.Box3d( center=[0, 0, (sim_z_start + sim_z_end) / 2], extents=[sim_size[0], dx, sim_z_end - sim_z_start], ), selection_matrix_type="uniform", # PMLs are applied on x- and z-axes. No PMLs are applied along y-axis # because it is the axis of translational symmetry. pml_thickness=[num_pmls, num_pmls, 0, 0, num_pmls, num_pmls], ) if visualize: # To visualize permittivity distribution, we actually have to # construct the simulation space object. import matplotlib.pyplot as plt from spins.invdes.problem_graph.simspace import get_fg_and_bg context = workspace.Workspace() eps_fg, eps_bg = get_fg_and_bg(context.get_object(simspace), wlen=1550) def plot(x): plt.imshow(np.abs(x)[:, 0, :].T.squeeze(), origin="lower") plt.figure() plt.subplot(3, 1, 1) plot(eps_fg[2]) plt.title("eps_fg") plt.subplot(3, 1, 2) plot(eps_bg[2]) plt.title("eps_bg") plt.subplot(3, 1, 3) plot(eps_fg[2] - eps_bg[2]) plt.title("design region") plt.show() return simspace
def create_sim_space( gds_fg_name: str, gds_bg_name: str, sim_width: float = 10000, # size of sim_space box_width: float = 4000, # size of our editing structure wg_width: float = 500, buffer_len: float = 1500, # not sure we'll need dx: int = 40, num_pmls: int = 10, visualize: bool = False, ) -> optplan.SimulationSpace: """Creates the simulation space. The simulation space contains information about the boundary conditions, gridding, and design region of the simulation. Args: gds_fg_name: Location to save foreground GDS. gds_bg_name: Location to save background GDS. etch_frac: Etch fraction of the grating. 1.0 indicates a fully-etched grating. box_thickness: Thickness of BOX layer in nm. wg_width: Width of the waveguide. buffer_len: Buffer distance to put between grating and the end of the simulation region. This excludes PMLs. dx: Grid spacing to use. num_pmls: Number of PML layers to use on each side. visualize: If `True`, draws the polygons of the GDS file. Returns: A `SimulationSpace` description. """ # The BOX layer/silicon device interface is set at `z = 0`. # # Describe materials in each layer. # 1) Silicon Nitride # Note that the layer numbering in the GDS file is arbitrary. Layer 300 is a dummy # layer; it is used for layers that only have one material (i.e. the # background and foreground indices are identical) so the actual structure # used does not matter. # Will need to define out material, just silicon nitride # Remove the etching stuff # Can define Si3N4 - the material we want to use # Fix: try to make multiple layers, but all the same? #air = optplan.Material(index=optplan.ComplexNumber(real=1)) air = optplan.Material(mat_name="air") stack = [ optplan.GdsMaterialStackLayer( foreground=air, background=air, gds_layer=[100, 0], extents=[ -10000, -110 ], # will probably need to define a better thickness for our layer ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si3N4"), background=air, gds_layer=[100, 0], extents=[ -110, 110 ], # will probably need to define a better thickness for our layer ), ] mat_stack = optplan.GdsMaterialStack( # Any region of the simulation that is not specified is filled with # air. background=air, stack=stack, ) # these define the entire region you wish to scan in the z -direction, not sure for us # as we don't require etching or layers # will probably change this as thickness may be wrong # Create a simulation space for both continuous and discrete optimization. # TODO too large a space takes too long to process - may need blue bear simspace = optplan.SimulationSpace( name="simspace", mesh=optplan.UniformMesh(dx=dx), eps_fg=optplan.GdsEps(gds=gds_fg_name, mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds=gds_bg_name, mat_stack=mat_stack), # Note that we explicitly set the simulation region. Anything # in the GDS file outside of the simulation extents will not be drawn. sim_region=optplan.Box3d( center=[0, 0, 0], extents=[9000, 9000, 40], # this is what's messing things up, needs to be 2D ), # changing the size too much creates an error selection_matrix_type="direct_lattice", # or uniform # PMLs are applied on x- and z-axes. No PMLs are applied along y-axis # because it is the axis of translational symmetry. pml_thickness=[num_pmls, num_pmls, num_pmls, num_pmls, 0, 0], # may need to edit this, make z the 0 axis ) if visualize: # To visualize permittivity distribution, we actually have to # construct the simulation space object. import matplotlib.pyplot as plt from spins.invdes.problem_graph.simspace import get_fg_and_bg context = workspace.Workspace() eps_fg, eps_bg = get_fg_and_bg(context.get_object(simspace), label=1) #edit here def plot(x): plt.imshow(np.abs(x)[:, 0, :].T.squeeze(), origin="lower") plt.figure() plt.subplot(3, 1, 1) plot(eps_fg[2]) plt.title("eps_fg") plt.subplot(3, 1, 2) plot(eps_bg[2]) plt.title("eps_bg") plt.subplot(3, 1, 3) plot(eps_fg[2] - eps_bg[2]) plt.title("design region") plt.show() return simspace
def create_sim_space( gds_fg_name: str, gds_bg_name: str, sim_width: float = 8000, # size of sim_space box_width: float = 4000, # size of our editing structure wg_width: float = 600, buffer_len: float = 1500, # not sure we'll need dx: int = 40, num_pmls: int = 10, visualize: bool = False, ) -> optplan.SimulationSpace: """Creates the simulation space. The simulation space contains information about the boundary conditions, gridding, and design region of the simulation. Args: gds_fg_name: Location to save foreground GDS. gds_bg_name: Location to save background GDS. etch_frac: Etch fraction of the grating. 1.0 indicates a fully-etched grating. box_thickness: Thickness of BOX layer in nm. wg_width: Width of the waveguide. buffer_len: Buffer distance to put between grating and the end of the simulation region. This excludes PMLs. dx: Grid spacing to use. num_pmls: Number of PML layers to use on each side. visualize: If `True`, draws the polygons of the GDS file. Returns: A `SimulationSpace` description. """ # Calculate the simulation size, including PMLs # TODO change the first part of ech dimension to be equal sim_size = [ sim_width + 2 * buffer_len + dx * num_pmls, sim_width + 2 * buffer_len + dx * num_pmls ] # First, we use `gdspy` to draw the waveguides and shapes that we would # like to use. Instead of programmatically generating a GDS file using # `gdspy`, we could also simply provide a GDS file (e.g. drawn using # KLayout). # Declare some constants to represent the different layers. # Not sure if we need layers LAYER = 100 # Create rectangles corresponding to the waveguide, the BOX layer, and the # design region. We extend the rectangles outside the simulation region # by multiplying locations by a factor of 1.1. # We distinguish between the top part of the waveguide (which is etched) # and the bottom part of the waveguide (which is not etched). # TODO define our single waveguide and surface, I don't believe it will be etched. # Switch x and y temporarily to try and get better direction for field - change top to bottom # Add an exit waveguide_top = gdspy.Rectangle((-1.1 * sim_size[0] / 2, -wg_width / 2), (-box_width / 2, wg_width / 2), LAYER) waveguide_bottom = gdspy.Rectangle((box_width / 2, -wg_width / 2), (1.1 * sim_size[0] / 2, wg_width / 2), LAYER) design_region = gdspy.Rectangle( (-box_width / 2, -box_width / 2), (box_width / 2, box_width / 2), # extend region? LAYER) # Generate the foreground and background GDS files. gds_fg = gdspy.Cell("FOREGROUND", exclude_from_current=True) gds_fg.add(waveguide_top) gds_fg.add(waveguide_bottom) gds_fg.add(design_region) # I guess we keep this the same and not include the design_region gds_bg = gdspy.Cell("BACKGROUND", exclude_from_current=True) gds_bg.add(waveguide_top) gds_bg.add(waveguide_bottom) gdspy.write_gds(gds_fg_name, [gds_fg], unit=1e-9, precision=1e-9) gdspy.write_gds(gds_bg_name, [gds_bg], unit=1e-9, precision=1e-9) # The BOX layer/silicon device interface is set at `z = 0`. # # Describe materials in each layer. # 1) Silicon Nitride # Note that the layer numbering in the GDS file is arbitrary. Layer 300 is a dummy # layer; it is used for layers that only have one material (i.e. the # background and foreground indices are identical) so the actual structure # used does not matter. # Will need to define out material, just silicon nitride # Remove the etching stuff # Can define Si3N4 - the material we want to use # Fix: try to make multiple layers, but all the same? air = optplan.Material(index=optplan.ComplexNumber(real=1)) stack = [ optplan.GdsMaterialStackLayer( foreground=air, background=air, gds_layer=[100, 0], extents=[ -10000, -110 ], # will probably need to define a better thickness for our layer ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si3N4"), background=air, gds_layer=[100, 0], extents=[ -110, 110 ], # will probably need to define a better thickness for our layer ), ] mat_stack = optplan.GdsMaterialStack( # Any region of the simulation that is not specified is filled with # air. background=air, stack=stack, ) # these define the entire region you wish to scan in the z -direction, not sure for us # as we don't require etching or layers # will probably change this as thickness may be wrong # Create a simulation space for both continuous and discrete optimization. # TODO too large a space takes too long to process - may need blue bear simspace = optplan.SimulationSpace( name="simspace", mesh=optplan.UniformMesh(dx=dx), eps_fg=optplan.GdsEps(gds=gds_fg_name, mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds=gds_bg_name, mat_stack=mat_stack), # Note that we explicitly set the simulation region. Anything # in the GDS file outside of the simulation extents will not be drawn. sim_region=optplan.Box3d( center=[0, 0, 0], extents=[6000, 6000, 40], # this is what's messing things up, needs to be 2D ), # changing the size too much creates an error selection_matrix_type="direct_lattice", # or uniform # PMLs are applied on x- and z-axes. No PMLs are applied along y-axis # because it is the axis of translational symmetry. pml_thickness=[num_pmls, num_pmls, num_pmls, num_pmls, 0, 0], # may need to edit this, make z the 0 axis ) if visualize: # To visualize permittivity distribution, we actually have to # construct the simulation space object. import matplotlib.pyplot as plt from spins.invdes.problem_graph.simspace import get_fg_and_bg context = workspace.Workspace() eps_fg, eps_bg = get_fg_and_bg(context.get_object(simspace), label=1070) #edit here def plot(x): plt.imshow(np.abs(x)[:, 0, :].T.squeeze(), origin="lower") plt.figure() plt.subplot(3, 1, 1) plot(eps_fg[2]) plt.title("eps_fg") plt.subplot(3, 1, 2) plot(eps_bg[2]) plt.title("eps_bg") plt.subplot(3, 1, 3) plot(eps_fg[2] - eps_bg[2]) plt.title("design region") plt.show() return simspace
def test_simspace_mesh_list(): """Checks parity between `GdsMeshEps` and `GdsEps`.""" # First create using `GdsEps`. mat_stack = optplan.GdsMaterialStack( background=optplan.Material(mat_name="Air"), stack=[ optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="SiO2"), background=optplan.Material(mat_name="SiO2"), gds_layer=[101, 0], extents=[-10000, -110], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="Air"), gds_layer=[100, 0], extents=[-110, 110], ), ], ) simspace_spec = optplan.SimulationSpace( mesh=optplan.UniformMesh(dx=40), eps_fg=optplan.GdsEps(gds="WDM_example_fg.gds", mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds="WDM_example_bg.gds", mat_stack=mat_stack), sim_region=optplan.Box3d(center=[0, 0, 0], extents=[5000, 5000, 500]), pml_thickness=[10, 10, 10, 10, 0, 0], ) space = simspace.SimulationSpace(simspace_spec, TESTDATA) space_inst = space(1550) eps_bg = space_inst.eps_bg.grids eps_fg = fdfd_tools.unvec( fdfd_tools.vec(space_inst.eps_bg.grids) + space_inst.selection_matrix @ np.ones(np.prod(space.design_dims)), space_inst.eps_bg.shape) # Validate that `GdsEps` behaves as expected. assert space_inst.selection_matrix.shape == (609375, 10000) np.testing.assert_array_equal(eps_bg[2][:, :, -3], 1) np.testing.assert_allclose(eps_bg[2][10, 10, 2], 2.0852) np.testing.assert_allclose(eps_fg[2][107, 47, 2], 2.0852) np.testing.assert_allclose(eps_fg[2][107, 47, 6], 12.086617) np.testing.assert_allclose(eps_fg[2][112, 57, 6], 1) np.testing.assert_allclose(eps_fg[2][107, 47, 10], 1) # Now create space using `GdsMeshEps`. mesh_list = [ optplan.SlabMesh( material=optplan.Material(mat_name="SiO2"), extents=[-10000, -110]), optplan.GdsMesh( material=optplan.Material(mat_name="Si"), extents=[-110, 110], gds_layer=[100, 0], ), ] simspace_spec = optplan.SimulationSpace( mesh=optplan.UniformMesh(dx=40), eps_fg=optplan.GdsMeshEps( gds="WDM_example_fg.gds", background=optplan.Material(mat_name="Air"), mesh_list=mesh_list), eps_bg=optplan.GdsMeshEps( gds="WDM_example_bg.gds", background=optplan.Material(mat_name="Air"), mesh_list=mesh_list), sim_region=optplan.Box3d(center=[0, 0, 0], extents=[5000, 5000, 500]), pml_thickness=[10, 10, 10, 10, 0, 0], ) space_mesh = simspace.SimulationSpace(simspace_spec, TESTDATA) space_inst_mesh = space(1550) eps_bg_mesh = space_inst_mesh.eps_bg.grids eps_fg_mesh = fdfd_tools.unvec( fdfd_tools.vec(space_inst_mesh.eps_bg.grids) + space_inst_mesh.selection_matrix @ np.ones( np.prod(space_mesh.design_dims)), space_inst_mesh.eps_bg.shape) # Verify that the two methods yield the same permittivities. np.testing.assert_allclose(eps_bg_mesh, eps_bg) np.testing.assert_allclose(eps_fg_mesh, eps_fg)
def test_straight_waveguide_power(): """Tests that a straight waveguide with a single source and overlap.""" # TODO(logansu): Refactor. class Simspace: def __init__(self, filepath, params: optplan.SimulationSpace): # Setup the grid. self._dx = params.mesh.dx from spins.invdes.problem_graph.simspace import _create_edge_coords self._edge_coords = _create_edge_coords(params.sim_region, self._dx) self._ext_dir = gridlock.Direction.z # Currently always extrude in z. # TODO(logansu): Factor out grid functionality and drawing. # Create a grid object just so we can calculate dxes. self._grid = gridlock.Grid(self._edge_coords, ext_dir=self._ext_dir, num_grids=3) self._pml_layers = params.pml_thickness self._filepath = filepath self._eps_bg = params.eps_bg @property def dx(self) -> float: return self._dx @property def dxes(self) -> fdfd_tools.GridSpacing: return [self._grid.dxyz, self._grid.autoshifted_dxyz()] @property def pml_layers(self) -> fdfd_tools.PmlLayers: return self._pml_layers @property def dims(self) -> Tuple[int, int, int]: return [ len(self._edge_coords[0]) - 1, len(self._edge_coords[1]) - 1, len(self._edge_coords[2]) - 1 ] @property def edge_coords(self) -> fdfd_tools.GridSpacing: return self._edge_coords def __call__(self, wlen: float): from spins.invdes.problem_graph.simspace import _create_grid from spins.invdes.problem_graph.simspace import SimulationSpaceInstance eps_bg = _create_grid(self._eps_bg, self._edge_coords, wlen, self._ext_dir, self._filepath) return SimulationSpaceInstance(eps_bg=eps_bg, selection_matrix=None) space = Simspace( TESTDATA, optplan.SimulationSpace( pml_thickness=[10, 10, 10, 10, 0, 0], mesh=optplan.UniformMesh(dx=40), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[5000, 5000, 40], ), eps_bg=optplan.GdsEps( gds="straight_waveguide.gds", mat_stack=optplan.GdsMaterialStack( background=optplan.Material(mat_name="air"), stack=[ optplan.GdsMaterialStackLayer( gds_layer=[100, 0], extents=[-80, 80], foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="air"), ), ], ), ), )) source = creator_em.WaveguideModeSource( optplan.WaveguideModeSource( power=1.0, extents=[40, 1500, 600], normal=[1.0, 0.0, 0.0], center=[-1770, 0, 0], mode_num=0, )) overlap = creator_em.WaveguideModeOverlap( optplan.WaveguideModeOverlap( power=1.0, extents=[40, 1500, 600], normal=[1.0, 0.0, 0.0], center=[1770, 0, 0], mode_num=0, )) wlen = 1550 eps_grid = space(wlen).eps_bg.grids source_grid = source(space, wlen) overlap_grid = overlap(space, wlen) eps = problem.Constant(fdfd_tools.vec(eps_grid)) sim = creator_em.FdfdSimulation( eps=eps, solver=local_matrix_solvers.DirectSolver(), wlen=wlen, source=fdfd_tools.vec(source_grid), simspace=space, ) overlap_fun = creator_em.OverlapFunction(sim, fdfd_tools.vec(overlap_grid)) efield_grid = fdfd_tools.unvec(graph_executor.eval_fun(sim, None), eps_grid[0].shape) # Calculate emitted power. edotj = np.real( fdfd_tools.vec(efield_grid) * np.conj(fdfd_tools.vec(source_grid))) * 40**3 power = -0.5 * np.sum(edotj) # Allow for 4% error in emitted power. assert power > 0.96 and power < 1.04 # Check that overlap observes nearly unity power. np.testing.assert_almost_equal(np.abs( graph_executor.eval_fun(overlap_fun, None))**2, 1, decimal=2)
def create_sim_space( gds_fg: str, gds_bg: str, box_thickness: float = 2000, wg_thickness: float = 220, etch_frac: float = 0.5, ) -> optplan.SimulationSpace: """Creates the simulation space. The simulation space contains information about the boundary conditions, gridding, and design region of the simulation. Args: gds_fg: Location of the foreground GDS file. gds_bg: Location of the background GDS file. box_thickness: Thickness of BOX layer in nm. wg_thickness: Thickness of the waveguide. etch_frac: Etch fraction of the grating. 1.0 indicates a fully-etched grating. Returns: A `SimulationSpace` description. """ # The BOX layer/silicon device interface is set at `z = 0`. # # Describe materials in each layer. # We actually have four material layers: # 1) Silicon substrate # 2) Silicon oxide BOX layer # 3) Bottom part of grating that is not etched # 4) Top part of grating that can be etched. # # The last two layers put together properly describe a partial etch. # # Note that the layer numbering in the GDS file is arbitrary. In our case, # layer 100 and 101 correspond to actual structure. Layer 300 is a dummy # layer; it is used for layers that only have one material (i.e. the # background and foreground indices are identical) so the actual structure # used does not matter. stack = [ optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="Si"), # Note that layer number here does not actually matter because # the foreground and background are the same material. gds_layer=[300, 0], extents=[-10000, -box_thickness], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="SiO2"), background=optplan.Material(mat_name="SiO2"), gds_layer=[300, 0], extents=[-box_thickness, 0], ), ] # If `etch-frac` is 1, then we do not need two separate layers. if etch_frac != 1: stack.append( optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="SiO2"), gds_layer=[100, 0], extents=[0, wg_thickness * (1 - etch_frac)], )) stack.append( optplan.GdsMaterialStackLayer( foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="SiO2"), gds_layer=[101, 0], extents=[wg_thickness * (1 - etch_frac), wg_thickness], )) mat_stack = optplan.GdsMaterialStack( # Any region of the simulation that is not specified is filled with # oxide. background=optplan.Material(mat_name="SiO2"), stack=stack, ) sim_z_start = -box_thickness - 1000 sim_z_end = wg_thickness + 1500 # Create a simulation space for both continuous and discrete optimization. dx = 40 return optplan.SimulationSpace( name="simspace", mesh=optplan.UniformMesh(dx=dx), eps_fg=optplan.GdsEps(gds=gds_fg, mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds=gds_bg, mat_stack=mat_stack), # Note that we explicitly set the simulation region. Anything # in the GDS file outside of the simulation extents will not be drawn. sim_region=optplan.Box3d( center=[0, 0, (sim_z_start + sim_z_end) / 2], extents=[16000, dx, sim_z_end - sim_z_start], ), selection_matrix_type="uniform", # Here we are specifying periodic boundary conditions (Bloch boundary # conditions with zero k-vector). boundary_conditions=[optplan.BlochBoundary()] * 6, # PMLs are applied on x- and z-axes. No PMLs are applied along y-axis # because it is the axis of translational symmetry. pml_thickness=[10, 10, 0, 0, 10, 10], )
def create_sim_space(gds_fg_name: str, gds_bg_name: str, wg_thickness: float = 220, wg_length: float = 3000, wg_width: float = 200, buffer_len: float = 250, dx: int = 40, num_pmls: int = 10) -> optplan.SimulationSpace: """Creates the simulation space. The simulation space contains information about the boundary conditions, gridding, and design region of the simulation. Args: gds_fg_name: Location to save foreground GDS. gds_bg_name: Location to save background GDS. wg_thickness: Thickness of the waveguide. dx: Grid spacing to use. num_pmls: Number of PML layers to use on each side. Returns: A `SimulationSpace` description. """ sim_size = wg_length + buffer_len * 2 waveguide_input = gdspy.Rectangle((-sim_size / 2, -wg_width / 2), (-wg_length / 2, wg_width / 2), 100) waveguide_output = gdspy.Rectangle((-wg_width / 2, -sim_size / 2), (wg_width / 2, -wg_length / 2), 100) design_region = gdspy.Rectangle((-wg_length / 2, -wg_length / 2), (wg_length / 2, wg_length / 2), 100) gds_fg = gdspy.Cell("FOREGROUND", exclude_from_current=True) gds_fg.add(waveguide_input) gds_fg.add(waveguide_output) gds_fg.add(design_region) gds_bg = gdspy.Cell("BACKGROUND", exclude_from_current=True) gds_bg.add(waveguide_input) gds_bg.add(waveguide_output) gdspy.write_gds(gds_fg_name, [gds_fg], unit=1e-9, precision=1e-9) gdspy.write_gds(gds_bg_name, [gds_bg], unit=1e-9, precision=1e-9) mat_oxide = optplan.Material(index=optplan.ComplexNumber(real=1.45)) stack = [ optplan.GdsMaterialStackLayer( foreground=mat_oxide, background=mat_oxide, gds_layer=[100, 0], extents=[-wg_length * 3, -wg_thickness / 2], ), optplan.GdsMaterialStackLayer( foreground=optplan.Material(index=optplan.ComplexNumber(real=1.5)), background=mat_oxide, gds_layer=[100, 0], extents=[-wg_thickness / 2, wg_thickness / 2], ), ] mat_stack = optplan.GdsMaterialStack( # Any region of the simulation that is not specified is filled with # oxide. background=mat_oxide, stack=stack, ) # Create a simulation space for both continuous and discrete optimization. simspace = optplan.SimulationSpace( name="simspace", mesh=optplan.UniformMesh(dx=dx), eps_fg=optplan.GdsEps(gds=gds_fg_name, mat_stack=mat_stack), eps_bg=optplan.GdsEps(gds=gds_bg_name, mat_stack=mat_stack), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[wg_length * 2, wg_length * 2, dx], ), selection_matrix_type="direct_lattice", pml_thickness=[num_pmls, num_pmls, num_pmls, num_pmls, 0, 0], ) return simspace
def test_straight_waveguide_power_poynting(): """Tests that total through straight waveguide is unity.""" space = Simspace( TESTDATA, optplan.SimulationSpace( pml_thickness=[10, 10, 10, 10, 0, 0], mesh=optplan.UniformMesh(dx=40), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[5000, 5000, 40], ), eps_bg=optplan.GdsEps( gds="straight_waveguide.gds", mat_stack=optplan.GdsMaterialStack( background=optplan.Material(mat_name="air"), stack=[ optplan.GdsMaterialStackLayer( gds_layer=[100, 0], extents=[-80, 80], foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="air"), ), ], ), ), )) source = creator_em.WaveguideModeSource( optplan.WaveguideModeSource( power=1.0, extents=[40, 1500, 600], normal=[1.0, 0.0, 0.0], center=[-1770, 0, 0], mode_num=0, )) wlen = 1550 eps_grid = space(wlen).eps_bg.grids source_grid = source(space, wlen) eps = problem.Constant(fdfd_tools.vec(eps_grid)) sim = creator_em.FdfdSimulation( eps=eps, solver=local_matrix_solvers.DirectSolver(), wlen=wlen, source=fdfd_tools.vec(source_grid), simspace=space, ) power_fun = poynting.PowerTransmissionFunction( field=sim, simspace=space, wlen=wlen, plane_slice=grid_utils.create_region_slices( space.edge_coords, [1770, 0, 0], [40, 1500, 600]), axis=gridlock.axisvec2axis([1, 0, 0]), polarity=gridlock.axisvec2polarity([1, 0, 0])) # Same as `power_fun` but with opposite normal vector. Should give same # answer but with a negative sign. power_fun_back = poynting.PowerTransmissionFunction( field=sim, simspace=space, wlen=wlen, plane_slice=grid_utils.create_region_slices( space.edge_coords, [1770, 0, 0], [40, 1500, 600]), axis=gridlock.axisvec2axis([-1, 0, 0]), polarity=gridlock.axisvec2polarity([-1, 0, 0])) efield_grid = fdfd_tools.unvec( graph_executor.eval_fun(sim, None), eps_grid[0].shape) # Calculate emitted power. edotj = np.real( fdfd_tools.vec(efield_grid) * np.conj( fdfd_tools.vec(source_grid))) * 40**3 power = -0.5 * np.sum(edotj) np.testing.assert_almost_equal( graph_executor.eval_fun(power_fun, None), power, decimal=4) np.testing.assert_almost_equal( graph_executor.eval_fun(power_fun_back, None), -power, decimal=4)