Exemplo n.º 1
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
Exemplo n.º 2
0
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
Exemplo n.º 3
0
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
Exemplo n.º 4
0
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.")