示例#1
0
def test_prod_mul_bad_node_raise_type_error():
    var1 = optplan.Parameter()
    var2 = optplan.Parameter()
    prod1 = optplan.Product(functions=[var1, var2])

    with pytest.raises(TypeError, match="multiply a node"):
        prod1 * optplan.SimulationSpace()
示例#2
0
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)
示例#3
0
def test_sum_add_bad_node_raise_type_error():
    var1 = optplan.Parameter()
    var2 = optplan.Parameter()
    sum1 = optplan.Sum(functions=[var1, var2])

    with pytest.raises(TypeError, match="add a node"):
        sum1 + optplan.SimulationSpace()
示例#4
0
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,
    )
示例#5
0
def test_validate_references_nested_raises_value_error():
    with pytest.raises(ValueError, match="Expected type"):
        optplan.validate_references(
            optplan.Sum(functions=[
                optplan.Power(
                    function=optplan.Sum(functions=[
                        optplan.make_constant(2),
                        optplan.SimulationSpace()
                    ])),
                optplan.make_constant(2)
            ]))
示例#6
0
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)
示例#7
0
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)
示例#8
0
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]))
示例#9
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
示例#10
0
def test_validate_references_lists_raises_value_error():
    with pytest.raises(ValueError, match="Expected type"):
        optplan.validate_references(
            optplan.Sum(functions=[optplan.SimulationSpace()]))
示例#11
0
文件: bend.py 项目: hund030/spins-b
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
示例#12
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
示例#13
0
def test_sum_fun_and_bad_obj_raises_type_error():
    with pytest.raises(TypeError, match="add node"):
        optplan.Parameter() + optplan.SimulationSpace()
示例#14
0
def test_prod_fun_and_bad_obj_raises_type_error():
    with pytest.raises(TypeError, match="multiply node"):
        optplan.Parameter() * optplan.SimulationSpace()
示例#15
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
示例#16
0
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],
    )
示例#17
0
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)
示例#18
0
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)
示例#19
0
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)
示例#20
0
def test_validate_references_values_raises_value_error():
    with pytest.raises(ValueError, match="Expected type"):
        optplan.validate_references(
            optplan.Power(function=optplan.SimulationSpace()))
示例#21
0
def test_problem_node_with_bad_reference_raises_value_error():
    with pytest.raises(ValueError, match="Expected type"):
        optplan.Power(function=optplan.SimulationSpace())