示例#1
0
    def __call__(self, simspace: SimulationSpace, wlen: float,
                 **kwargs) -> fdfd_tools.VecField:
        """Creates the source vector.

        Args:
            simspace: Simulation space object to use.
            wlen: Wavelength to operate source.

        Returns:
            The vector field corresponding to the source.
        """
        space_inst = simspace(wlen)

        return fdfd_solvers.waveguide_mode.build_waveguide_source(
            omega=2 * np.pi / wlen,
            dxes=simspace.dxes,
            eps=space_inst.eps_bg.grids,
            mu=None,
            mode_num=self._params.mode_num,
            waveguide_slice=grid_utils.create_region_slices(
                simspace.edge_coords, self._params.center,
                self._params.extents),
            axis=gridlock.axisvec2axis(self._params.normal),
            polarity=gridlock.axisvec2polarity(self._params.normal),
            power=self._params.power)
示例#2
0
    def __init__(self,
                 function: problem.OptimizationFunction,
                 sim_space: simspace.SimulationSpace,
                 slice_point: Optional[List[float]] = None,
                 slice_normal: Optional[List[int]] = None) -> None:
        """Initializes the monitor.

        If `slice_point` and `slice_normal` are set, then a 2D slice is taken
        over the 3D vector field.

        Args:
            function: Field function to monitor.
            sim
            slice_point: Point in the field slice to monitor.
            slice_normal: Normal of the slice that is evaluated.
        """
        super().__init__(function)
        self._function = function
        self._slices = None
        self._sim_space = sim_space
        if slice_point and slice_normal:
            slice_axis = gridlock.axisvec2axis(slice_normal)
            grid = gridlock.Grid(sim_space.edge_coords, num_grids=3)
            slice_ind = grid.pos2ind(slice_point,
                                     which_shifts=None).astype(int)
            slice_ind = slice_ind[slice_axis]

            self._slices = 3 * [slice(0, None)]
            self._slices[slice_axis] = slice(slice_ind, slice_ind + 1)
示例#3
0
    def __call__(
        self, simspace: SimulationSpace, wlen: float, solver, **kwargs
    ) -> Union[fdfd_tools.VecField, Tuple[fdfd_tools.VecField,
                                          fdfd_tools.Vec3d]]:
        """Creates the plane wave source.

        Args:
            simspace: Simulation space to use for the source.
            wlen: Wavelength of source.

        Returns:
            If `overwrite_bloch_vector` is `True`, a tuple containing the source
            field and the Bloch vector corresponding to the plane wave source.
            Otherwise, only the source field is returned.
        """
        space_inst = simspace(wlen)

        # Calculate the border in gridpoints and igore the border if it's larger then the simulation.
        dx = simspace.dx
        border = [int(b // dx) for b in self._params.border]
        # The plane wave is assumed to be in the z direction so the border is 0 for z.
        border.append(0)

        source, kvector = fdfd_tools.free_space_sources.build_plane_wave_source(
            omega=2 * np.pi / wlen,
            eps_grid=space_inst.eps_bg,
            mu=None,
            axis=gridlock.axisvec2axis(self._params.normal),
            polarity=gridlock.axisvec2polarity(self._params.normal),
            slices=grid_utils.create_region_slices(simspace.edge_coords,
                                                   self._params.center,
                                                   self._params.extents),
            theta=self._params.theta,
            psi=self._params.psi,
            polarization_angle=self._params.polarization_angle,
            border=border,
            power=self._params.power)

        if self._params.normalize_by_sim:
            source = fdfd_tools.free_space_sources.normalize_source_by_sim(
                omega=2 * np.pi / wlen,
                source=source,
                eps=space_inst.eps_bg.grids,
                dxes=simspace.dxes,
                pml_layers=simspace.pml_layers,
                solver=solver,
                power=self._params.power)

        if self._params.overwrite_bloch_vector:
            # TODO(logansu): Figure out what's wrong with mixing PMLs and
            # Bloch vector. It seems to create problems.
            # For now, we manually set Bloch to zero is PML is nonzero.
            if simspace.pml_layers[0] != 0 or simspace.pml_layers[1] != 0:
                kvector[0] = 0
            if simspace.pml_layers[2] != 0 or simspace.pml_layers[3] != 0:
                kvector[1] = 0
            if simspace.pml_layers[4] != 0 or simspace.pml_layers[5] != 0:
                kvector[2] = 0
            return source, kvector
        return source
示例#4
0
    def before_sim(self, sim: FdfdSimProp) -> None:
        beam_center = self._params.beam_center
        if beam_center is None:
            beam_center = self._params.center

        eps_grid = copy.deepcopy(sim.grid)
        eps_grid.grids = sim.eps

        source, _ = fdfd_tools.free_space_sources.build_gaussian_source(
            omega=2 * np.pi / sim.wlen,
            eps_grid=eps_grid,
            mu=None,
            axis=gridlock.axisvec2axis(self._params.normal),
            polarity=gridlock.axisvec2polarity(self._params.normal),
            slices=simspace.create_region_slices(sim.grid.exyz,
                                                 self._params.center,
                                                 self._params.extents),
            theta=self._params.theta,
            psi=self._params.psi,
            polarization_angle=self._params.polarization_angle,
            w0=self._params.w0,
            center=beam_center,
            power=self._params.power)

        if self._params.normalize_by_sim:
            source = fdfd_tools.free_space_sources.normalize_source_by_sim(
                omega=2 * np.pi / sim.wlen,
                source=source,
                eps=sim.eps,
                dxes=sim.dxes,
                pml_layers=sim.pml_layers,
                solver=sim.solver,
                power=self._params.power)

        sim.source += source
示例#5
0
 def __call__(self, simspace: SimulationSpace, wlen: float,
              **kwargs) -> fdfd_tools.VecField:
     # wlen should hopefully be defined when we define the problem
     space_inst = simspace(wlen)
     # Need to update the path here too
     return fdfd_solvers.annulus_mode.build_overlap_annulus(
         omega=2 * np.pi / wlen,
         dxes=simspace.dxes,
         eps=space_inst.eps_bg.grids,
         mu=None,
         mode_num=self._params.mode_num,
         axis=gridlock.axisvec2axis(self._params.normal),
         power=self._params.power)
示例#6
0
def create_power_transmission_function(
        params: PowerTransmission,
        context: workspace.Workspace) -> PowerTransmissionFunction:
    simspace = context.get_object(params.field.simulation_space)
    return PowerTransmissionFunction(
        field=context.get_object(params.field),
        simspace=simspace,
        wlen=params.field.wavelength,
        plane_slice=grid_utils.create_region_slices(simspace.edge_coords,
                                                    params.center,
                                                    params.extents),
        axis=gridlock.axisvec2axis(params.normal),
        polarity=gridlock.axisvec2polarity(params.normal))
示例#7
0
 def __call__(self, simspace: SimulationSpace, wlen: float,
              **kwargs) -> fdfd_tools.VecField:
     space_inst = simspace(wlen)
     return fdfd_solvers.waveguide_mode.build_overlap(
         omega=2 * np.pi / wlen,
         dxes=simspace.dxes,
         eps=space_inst.eps_bg.grids,
         mu=None,
         mode_num=self._params.mode_num,
         waveguide_slice=grid_utils.create_region_slices(
             simspace.edge_coords, self._params.center,
             self._params.extents),
         axis=gridlock.axisvec2axis(self._params.normal),
         polarity=gridlock.axisvec2polarity(self._params.normal),
         power=self._params.power)
示例#8
0
    def before_sim(self, sim: FdfdSimProp) -> None:
        if self._wg_mode is None:
            self._wg_mode = fdfd_solvers.waveguide_mode.build_waveguide_source(
                omega=2 * np.pi / sim.wlen,
                dxes=sim.dxes,
                eps=sim.eps,
                mu=None,
                mode_num=self._src.mode_num,
                waveguide_slice=simspace.create_region_slices(
                    sim.grid.exyz, self._src.center, self._src.extents),
                axis=gridlock.axisvec2axis(self._src.normal),
                polarity=gridlock.axisvec2polarity(self._src.normal),
                power=self._src.power)

        sim.source += self._wg_mode
示例#9
0
    def before_sim(self, sim: FdfdSimProp) -> None:
        # Calculate the eigenmode if we have not already.
        if self._wg_overlap is None:
            self._wg_overlap = fdfd_solvers.waveguide_mode.build_overlap(
                omega=2 * np.pi / sim.wlen,
                dxes=sim.dxes,
                eps=sim.eps,
                mu=None,
                mode_num=self._overlap.mode_num,
                waveguide_slice=simspace.create_region_slices(
                    sim.grid.exyz, self._overlap.center, self._overlap.extents),
                axis=gridlock.axisvec2axis(self._overlap.normal),
                polarity=gridlock.axisvec2polarity(self._overlap.normal),
                power=self._overlap.power)

            self._wg_overlap = np.stack(self._wg_overlap, axis=0)
示例#10
0
    def test_axisvec2axis_and_axisvec2polarity(self):

        # x-dir
        vec = np.array([1, 0, 0])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 0)
        self.assertTrue(pol == 1)

        vec = np.array([2, 1e-8, 1e-8])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 0)
        self.assertTrue(pol == 1)

        vec = np.array([-1, 0, 0])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 0)
        self.assertTrue(pol == -1)

        # y-dir
        vec = np.array([0, 1, 0])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 1)
        self.assertTrue(pol == 1)

        vec = np.array([0, -1, 0])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 1)
        self.assertTrue(pol == -1)

        # z-dir
        vec = np.array([0, 0, 1])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 2)
        self.assertTrue(pol == 1)

        vec = np.array([0, 0, -1])
        ax = axisvec2axis(vec)
        pol = axisvec2polarity(vec)
        self.assertTrue(ax == 2)
        self.assertTrue(pol == -1)
示例#11
0
    def before_sim(self, sim: mp.Simulation) -> None:
        # Set extents to zero in the direction of the normal as MEEP
        # can automatically deduce the normal this way.
        extents = np.array(self._overlap.extents) * 1.0
        extents[gridlock.axisvec2axis(self._overlap.normal)] = 0

        # Add a flux region to monitor the power.
        self._mon = sim.add_mode_monitor(
            1 / self._overlap.wavelength, 0, 1,
            mp.FluxRegion(center=self._overlap.center, size=extents))

        # Calculate the eigenmode if we have not already.
        if not self._wg_mode:
            self._wg_mode = _get_waveguide_mode(sim, self._overlap)

            norms = _get_overlap_integral(self._wg_mode.mode_fields,
                                          self._wg_mode.mode_fields,
                                          self._wg_mode.xyzw)
            self._mode_norm = np.sqrt(0.5 * np.abs(norms[0] + norms[1]))
示例#12
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)
示例#13
0
    def __call__(
        self, simspace: SimulationSpace, wlen: float, **kwargs
    ) -> Union[fdfd_tools.VecField, Tuple[fdfd_tools.VecField,
                                          fdfd_tools.Vec3d]]:
        """Creates the plane wave source.

        Args:
            simspace: Simulation space to use for the source.
            wlen: Wavelength of source.

        Returns:
            If `overwrite_bloch_vector` is `True`, a tuple containing the source
            field and the Bloch vector corresponding to the plane wave source.
            Otherwise, only the source field is returned.
        """
        space_inst = simspace(wlen)

        # Calculate the border in gridpoints and igore the border if it's larger then the simulation.
        dx = simspace.dx
        border = [int(b // dx) for b in self._params.border]
        # The plane wave is assumed to be in the z direction so the border is 0 for z.
        border.append(0)

        source, kvector = fdfd_tools.free_space_sources.build_plane_wave_source(
            omega=2 * np.pi / wlen,
            eps_grid=space_inst.eps_bg,
            mu=None,
            axis=gridlock.axisvec2axis(self._params.normal),
            polarity=gridlock.axisvec2polarity(self._params.normal),
            slices=grid_utils.create_region_slices(simspace.edge_coords,
                                                   self._params.center,
                                                   self._params.extents),
            theta=self._params.theta,
            psi=self._params.psi,
            polarization_angle=self._params.polarization_angle,
            border=border,
            power=self._params.power)

        if self._params.overwrite_bloch_vector:
            return source, kvector
        return source
示例#14
0
    def __call__(self, simspace: SimulationSpace, wlen: float,
                 solver: Callable, **kwargs) -> fdfd_tools.VecField:
        """Creates the source vector.

        Args:
            simspace: Simulation space.
            wlen: Wavelength of source.
            solver: If `normalize_by_source` is `True`, `solver` will be used
                to run an EM simulation to normalize the source power.

        Returns:
            The source.
        """
        space_inst = simspace(wlen)
        source, _ = fdfd_tools.free_space_sources.build_gaussian_source(
            omega=2 * np.pi / wlen,
            eps_grid=space_inst.eps_bg,
            mu=None,
            axis=gridlock.axisvec2axis(self._params.normal),
            polarity=gridlock.axisvec2polarity(self._params.normal),
            slices=grid_utils.create_region_slices(simspace.edge_coords,
                                                   self._params.center,
                                                   self._params.extents),
            theta=self._params.theta,
            psi=self._params.psi,
            polarization_angle=self._params.polarization_angle,
            w0=self._params.w0,
            center=self._params.beam_center,
            power=self._params.power)

        if self._params.normalize_by_sim:
            source = fdfd_tools.free_space_sources.normalize_source_by_sim(
                omega=2 * np.pi / wlen,
                source=source,
                eps=space_inst.eps_bg.grids,
                dxes=simspace.dxes,
                pml_layers=simspace.pml_layers,
                solver=solver,
                power=self._params.power)

        return source
示例#15
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]))
示例#16
0
def test_axisvec2axis_no_primary_coordinate_raises_value_error(vec):
    with pytest.raises(ValueError, match="no valid primary coordinate axis"):
        axisvec2axis(vec)
示例#17
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)
示例#18
0
def _get_waveguide_mode(sim: mp.Simulation,
                        src: WaveguideModeSource) -> MeepWaveguideMode:
    """Computes the waveguide mode in Meep (via MPB).

    Args:
        sim: Meep simulation object.
        src: Waveguide mode source properties.

    Returns:
        A tuple `(mode_fields, mode_comps, xyzw)`. `mode_fields`
        is the sampled transverse fields of the mode. `mode_fields` is an array
        with four elements, one for each transverse element as determined by
        `mode_comps`. `xyzw` is the Meep array metadata for the region: It is
        a 4-element tuple `(x, y, z, w)` where the `x`, `y`, and `z` are arrays
        indicating the cell coordinates where the fields are sampled and `w`
        is the volume weight factor used when calculating integrals involving
        the fields (see Meep documentation for details).
    """

    wlen = src.wavelength
    fcen = 1 / wlen
    normal_axis = gridlock.axisvec2axis(src.normal)

    # Extract the eigenmode from Meep.
    # `xyzw` is a tuple `(x_coords, y_coords, z_coords, weights)` where
    # `x_coords`, `y_coords`, and `z_coords` is a list of the coordinates of
    # the Yee cells within the source region.

    # TODO(logansu): Remove this hack when Meep fixes its bugs with
    # `get_array_metadata`. For now, we ensure that the slices are 3D, otherwise
    # the values returned for `w` can be wrong and undeterministic.
    dx = 1 / sim.resolution
    # Count number of dimensions are effectively "zero". This is used to
    # determine the dimensionality of the source (1D or 2D).
    overlap_dims = 3 - sum(val <= dx for val in src.extents)
    extents = [val if val >= dx else dx for val in src.extents]
    xyzw = sim.get_array_metadata(center=src.center, size=extents)

    # TODO(logansu): Understand how to guess a k-vector. Is it necessary?
    k_guess = [0, 0, 0]
    k_guess[normal_axis] = fcen
    k_guess = mp.Vector3(*k_guess)
    mode_dirs = [mp.X, mp.Y, mp.Z]
    mode_data = sim.get_eigenmode(
        fcen, mode_dirs[normal_axis],
        mp.Volume(center=src.center, size=src.extents), src.mode_num + 1,
        k_guess)

    # Determine which field components are relevant for TFSF source (i.e. the
    # field components tangential to the propagation direction.
    # For simplicity, we order them in circular permutation order (x->y->z) with
    # E fields followed by H fields.
    if normal_axis == 0:
        field_comps = [mp.Ey, mp.Ez, mp.Hy, mp.Hz]
    elif normal_axis == 1:
        field_comps = [mp.Ez, mp.Ex, mp.Hz, mp.Hx]
    else:
        field_comps = [mp.Ex, mp.Ey, mp.Hx, mp.Hy]

    # Extract the actual field values for the relevant field components.
    # Note that passing in `mode_data.amplitude` into `amp_func` parameter of
    # `mp.Source` seems to give a bad source.
    mode_fields = []
    for c in field_comps:
        field_slice = []
        for x, y, z in itertools.product(xyzw[0], xyzw[1], xyzw[2]):
            field_slice.append(mode_data.amplitude(mp.Vector3(x, y, z), c))
        field_slice = np.reshape(field_slice,
                                 (len(xyzw[0]), len(xyzw[1]), len(xyzw[2])))
        mode_fields.append(field_slice)

    # Sometimes Meep returns an extra layer of fields when one of the dimensions
    # of the overlap region is zero. In that case, only use the first slice.
    field_slicer = [slice(None), slice(None), slice(None)]
    field_slicer[normal_axis] = slice(0, 1)
    field_slicer = tuple(field_slicer)
    for i in range(4):
        mode_fields[i] = mode_fields[i][field_slicer]

    xyzw[3] = np.reshape(xyzw[3], (len(xyzw[0]), len(xyzw[1]), len(xyzw[2])))
    xyzw[3] = xyzw[3][field_slicer]
    # TODO(logansu): See above TODO about hacking `get_array_metadata`.
    # For now, just guess the correct value. The error introduced by this
    # occurs at the edges of the source/overlap where the fields are small
    # anyway.
    xyzw[3][:] = dx**overlap_dims

    # Fix the phase of the mode by normalizing the phase by the phase where
    # the electric field as largest magnitude.
    arr = np.hstack([mode_fields[0].flatten(), mode_fields[1].flatten()])
    phase_norm = np.angle(arr[np.argmax(np.abs(arr))])
    phase_norm = np.exp(1j * phase_norm)
    mode_fields = [f / phase_norm for f in mode_fields]

    return MeepWaveguideMode(wlen, field_comps, mode_fields, xyzw)