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 = 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 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 run_plan(plan: optplan.OptimizationPlan, project_folder: str, save_folder: Optional[str] = None, resume: bool = False) -> None: """Executes an optimization plan. The GDS files referenced from the JSON file should be referenced from `project_folder`. Any logs will also be saved in `project_folder.` Args: plan: Plan to execute. project_folder: Folder containing additional project files. save_folder: Folder to save optimization data. Defaults to `project_folder` if None. resume: If `True`, attempt to resume optimization. This only works if `save_folder` exists. Optimization will resume from the last log file in `save_folder`. """ if not save_folder: save_folder = project_folder setup_logging(save_folder) console_logger = logging.getLogger(__name__) # Make workspace. console_logger.info("Setting up workspace.") work = workspace.Workspace(project_folder, save_folder) # Keep track of which transformation we are executing. This exists # in order to handle optimization resuming in which we start executing # a later transformation. transform_index = 0 # Most recent logging data. Used to resume transformations. event_data = None if resume: transform_index, event_data = restore_workspace( plan, work, save_folder, console_logger) # Run over the parametrization. for transformation_param in plan.transformations[transform_index:]: console_logger.info("Running transformation %s.", transformation_param.name) work.run(transformation_param, event_data) # Save the state after transformation executes. work.logger.write_checkpoint(transformation_param.name) event_data = None # Make a GDS if needed. final_parametrization = plan.transformations[-1].parametrization parametrization = work.get_object(final_parametrization) # TODO(logansu): Have a better way of generating GDS than this. if hasattr(parametrization, "generate_polygons"): simspace_name = final_parametrization.simulation_space poly_coords = parametrization.generate_polygons( work.get_object(simspace_name).dx) console_logger.info("Exporting GDS of final design.") spins.gds.gen_gds(poly_coords, os.path.join(save_folder, "spins_design.gds")) console_logger.info("Spins finished.")