Пример #1
0
    def grad(self, input_vals: List[goos.Flow],
             grad_val: goos.ArrayFlow.Grad) -> List[goos.Flow.Grad]:
        eps = input_vals[0].array
        sim = FdfdSimProp(eps=eps,
                          source=np.zeros_like(eps),
                          wlen=self._wlen,
                          dxes=self._dxes,
                          pml_layers=self._pml_layers,
                          bloch_vec=self._bloch_vector,
                          grid=self._grid)

        omega = 2 * np.pi / sim.wlen
        for out, g in zip(self._outputs, grad_val.flows_grad):
            out.before_adjoint_sim(sim, g)

        adjoint_fields = self._solver.solve(
            omega=2 * np.pi / sim.wlen,
            dxes=sim.dxes,
            epsilon=fdfd_tools.vec(sim.eps),
            mu=None,
            J=fdfd_tools.vec(sim.source),
            pml_layers=sim.pml_layers,
            bloch_vec=sim.bloch_vec,
            adjoint=True,
        )
        adjoint_fields = np.stack(fdfd_tools.unvec(adjoint_fields,
                                                   eps[0].shape),
                                  axis=0)
        grad = -1j * omega * np.conj(
            adjoint_fields) * self._last_results.fields

        if np.isrealobj(eps):
            grad = 2 * np.real(grad)

        return [goos.NumericFlow.Grad(array_grad=grad)]
Пример #2
0
    def grad(self, input_vals: List[goos.Flow],
             grad_val: goos.ArrayFlow.Grad) -> List[goos.Flow.Grad]:
        """Runs the simulation.

        Args:
            input_vals: List with single element corresponding to the
                permittivity distribution.

        Returns:
            Simulated fields.
        """
        if self._last_eps is None or self._last_eps != input_vals[0]:
            eps = input_vals[0].array
            sim = EigSimProp(eps=eps,
                             source=np.zeros_like(eps),
                             wlen=self._wlen,
                             dxes=self._dxes,
                             pml_layers=self._pml_layers,
                             bloch_vec=self._bloch_vector,
                             grid=self._grid)

            for out in self._outputs:
                out.before_adjoint_sim(sim)

            #TODO vcruysse: for now we limit to 1 eigenvalue
            fields, omega = self._solver.solve(omega=2 * np.pi / sim.wlen,
                                               dxes=sim.dxes,
                                               J=fdfd_tools.vec(sim.source),
                                               epsilon=fdfd_tools.vec(sim.eps),
                                               mu=None,
                                               bloch_vec=sim.bloch_vec,
                                               pml_layers=sim.pml_layers,
                                               symmetry=sim.symmetry,
                                               n_eig=1)
            fields = fdfd_tools.unvec(fields[0], eps[0].shape)
            sim.fields = np.stack(fields, axis=0)
            sim.omegas = np.real(omega[0])

            self._last_eps = input_vals[0]
            self._last_results = sim

        fields = self._last_results.fields
        omega = self._last_results.omegas
        eps = self._last_results.eps
        domega_deps = np.real(-omega / 2 * 1 /
                              (fields.flatten().conj() @ fields.flatten()) *
                              fields.conj() * eps**(-1) * fields)

        # find the grad for omega
        for out, grad in zip(self._outputs, grad_val):
            if isinstance(out, EigenValueImpl):
                df_domega = grad.array_grad[0]

        return [goos.NumericFlow.Grad(array_grad=df_domega * domega_deps)]
Пример #3
0
def test_gaussian_source_2d():
    grid = gridlock.Grid(simspace.create_edge_coords(
        goos.Box3d(center=[0, 0, 0], extents=[14000, 40, 3000]), 40),
                         ext_dir=gridlock.Direction.z,
                         initial=1,
                         num_grids=3)
    grid.render()
    eps = np.array(grid.grids)
    dxes = [grid.dxyz, grid.autoshifted_dxyz()]
    sim = maxwell.FdfdSimProp(eps=eps,
                              source=np.zeros_like(eps),
                              wlen=1550,
                              dxes=dxes,
                              pml_layers=[10, 10, 0, 0, 10, 10],
                              grid=grid,
                              solver=maxwell.DIRECT_SOLVER)
    src = maxwell.GaussianSourceImpl(
        maxwell.GaussianSource(w0=5200,
                               center=[0, 0, 0],
                               extents=[14000, 0, 0],
                               normal=[0, 0, -1],
                               power=1,
                               theta=0,
                               psi=0,
                               polarization_angle=np.pi / 2,
                               normalize_by_sim=True))

    src.before_sim(sim)

    fields = maxwell.DIRECT_SOLVER.solve(
        omega=2 * np.pi / sim.wlen,
        dxes=sim.dxes,
        epsilon=fdfd_tools.vec(sim.eps),
        mu=None,
        J=fdfd_tools.vec(sim.source),
        pml_layers=sim.pml_layers,
        bloch_vec=sim.bloch_vec,
    )
    fields = fdfd_tools.unvec(fields, grid.shape)
    field_y = fields[1].squeeze()

    np.testing.assert_allclose(field_y[:, 53],
                               np.zeros_like(field_y[:, 53]),
                               atol=1e-4)

    # Calculate what the amplitude of the Gaussian field should look like.
    # Amplitude determined empirically through simulation.
    coords = (np.arange(len(field_y[:, 20])) - len(field_y[:, 20]) / 2) * 40
    target_gaussian = np.exp(-coords**2 / 5200**2) * 0.00278
    np.testing.assert_allclose(np.abs(field_y[:, 20]),
                               target_gaussian,
                               atol=1e-4)
Пример #4
0
def get_fg_and_bg(simspace: SimulationSpace, wlen: float
                 ) -> Tuple[fdfd_tools.VecField, fdfd_tools.VecField]:
    """Quick utility function to construct the fg and bg permittivities.

    Args:
        simspace: SimulationSpace object.
        wlen: Wavelength to plot simulation space.

    Returns:
        A tuple `(eps_fg, eps_bg)` where `eps_fg` is the permittivity if the
        structure vector is all ones and `eps_bg` is the permittivity if the
        structure vector is all zeros.
    """
    simspace_inst = simspace(wlen)
    # Number of elements in structure vector.
    num_el = simspace_inst.selection_matrix.shape[1]

    eps_bg = fdfd_tools.vec(simspace_inst.eps_bg.grids)
    eps_fg = eps_bg + simspace_inst.selection_matrix @ np.ones(num_el)

    # Reshape into the appropriate size.
    return fdfd_tools.unvec(eps_fg, simspace.dims), fdfd_tools.unvec(
        eps_bg, simspace.dims)
Пример #5
0
def compute_overlap_e_angled(
    E: field_t,
    H: field_t,
    axis: int,
    omega: complex,
    dxes: dx_lists_t,
    slices: List[slice],
    mu: field_t = None,
) -> field_t:
    """
    Given an eigenmode obtained by solve_waveguide_mode, calculates overlap_e for the
    mode orthogonality relation Integrate(((E x H_mode) + (E_mode x H)) dot dn)
    [assumes reflection symmetry].

    overlap_e makes use of the e2h operator to collapse the above expression into
     (vec(E) @ vec(overlap_e)), allowing for simple calculation of the mode overlap.

    :param E: E-field of the mode
    :param H: H-field of the mode (advanced by half of a Yee cell from E)
    :axis: propagation axis
    :param omega: Angular frequency of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param slices: epsilon[tuple(slices)] is used to select the portion of the grid to use
        as the waveguide cross-section. slices[axis] should select only one
    :param mu: Magnetic permeability (default 1 everywhere)
    :return: overlap_e for calculating the mode overlap
    """
    if (slices[axis].stop - slices[axis].start) != 1:
        raise ValueError('The slice in the axis direction is not 1 wide.')
    # Write out the operator product for the mode orthogonality integral
    domain = np.zeros_like(E)
    domain[[slice(0, 3)] + slices] = 1

    dn = np.zeros_like(E)
    dn[axis] = np.ones_like(E[axis])
    dn_vec = vec(dn)

    e2h = operators.e2h(omega, dxes, mu)
    ds = sparse.diags(vec(domain))
    h_cross_ = operators.poynting_h_cross(np.conj(vec(H)), dxes)
    e_cross_ = operators.poynting_e_cross(np.conj(vec(E)), dxes)

    overlap_e = dn_vec @ ds @ (-h_cross_ + e_cross_ @ e2h)

    # Normalize
    norm_factor = np.abs(overlap_e @ vec(E))
    overlap_e /= norm_factor

    return unvec(overlap_e, E[0].shape)
Пример #6
0
    def eval(self, input_vals: List[goos.Flow]) -> goos.ArrayFlow:
        """Runs the simulation.

        Args:
            input_vals: List with single element corresponding to the
                permittivity distribution.

        Returns:
            Simulated fields.
        """
        if self._last_eps is None or self._last_eps != input_vals[0]:
            eps = input_vals[0].array
            sim = EigSimProp(eps=eps,
                             source=np.zeros_like(eps),
                             wlen=self._wlen,
                             dxes=self._dxes,
                             pml_layers=self._pml_layers,
                             bloch_vec=self._bloch_vector,
                             symmetry=self._symmetry,
                             grid=self._grid)

            for src in self._sources:
                src.before_sim(sim)

            for out in self._outputs:
                out.before_sim(sim)

            #TODO vcruysse: for now we limit to 1 eigenvalue
            fields, omega = self._solver.solve(omega=2 * np.pi / sim.wlen,
                                               dxes=sim.dxes,
                                               J=fdfd_tools.vec(sim.source),
                                               epsilon=fdfd_tools.vec(sim.eps),
                                               mu=None,
                                               bloch_vec=sim.bloch_vec,
                                               pml_layers=sim.pml_layers,
                                               symmetry=sim.symmetry,
                                               n_eig=1)
            fields = fdfd_tools.unvec(fields[0], eps[0].shape)
            sim.fields = np.stack(fields, axis=0)
            sim.omegas = np.real(omega[0])

            self._last_eps = input_vals[0]
            self._last_results = sim

        return goos.ArrayFlow(
            [out.eval(self._last_results) for out in self._outputs])
Пример #7
0
    def eval(self, input_vals: List[np.ndarray]) -> np.ndarray:
        """Returns the  3D vector field.

        Args:
            input_vals: Single-element list containing the field.

        Returns:
            List of all three field components.
        """
        # Get fields.
        fields = fdfd_tools.unvec(input_vals[0], self._sim_space.dims)

        # Make slices.
        if self._slices:
            fields = [field[tuple(self._slices)] for field in fields]

        return fields
Пример #8
0
def compute_overlap_annulus(
    E: field_t,
    omega: complex,
    dxes: dx_lists_t,
    axis: int,
    mu: field_t = None,
) -> field_t:
    """This is hopefully going to calculate the overlap """

    # need to extract the size of dxes to adjust the size of mask and the E field
    len_dxes = np.concatenate(dxes, axis=0)

    # want to extract the absolute value, x=0, y=1, z=2

    # Domain is all zero
    domain = np.zeros_like(E[0], dtype=int)

    # Then set the slices part equal to 1 - may need to use our mask
    t = np.abs(len_dxes[0].size)

    # TODO adjust these values, may call from problem
    mask = annulus(t, t, [0, 0], 0.3 * t, 0.2 * t)
    mask = mask[..., newaxis]  # adds the z to mask
    domain[mask] = 1

    npts = E[0].size
    dn = np.zeros(npts * 3, dtype=int)
    dn[0:npts] = 1
    dn = np.roll(dn, npts * axis)

    e2h = operators.e2h(omega, dxes, mu)
    # Hopefully this works with the mask
    ds = sparse.diags(vec([domain] * 3))

    e_cross_ = operators.poynting_e_cross(vec(E), dxes)

    # Difference is, we have no H field
    overlap_e = dn @ ds @ (e_cross_ @ e2h)

    # Normalize
    norm_factor = np.abs(overlap_e @ vec(E))
    overlap_e /= norm_factor

    return unvec(overlap_e, E[0].shape)
Пример #9
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)
Пример #10
0
    def __init__(self,
                 input_function: problem.OptimizationFunction,
                 overlap_model: np.array,
                 slice_model,
                 slice_in,
                 slice_out,
                 path: np.array,
                 wavelength,
                 shape,
                 num_phase_checks=10):
        """Constructs the objective.

        Args:
            input_function: Input objectives (typically a simulation).
            overlap: WaveguideModeOverlap of the mode of interest
            path: path from the source along which to take the phase difference
        """
        super().__init__(input_function)

        self._input = input_function
        self.overlap_model = overlap_model
        self.path = path
        self.wavelength = wavelength
        self.num_phase_checks = num_phase_checks

        self.model_unvec = fdfd_tools.unvec(overlap_model, shape)
        self.model_region = [
            self.model_unvec[i][tuple(slice_model)] for i in range(3)
        ]
        self.overlap_in_unvec = np.zeros_like(self.model_unvec)
        self.overlap_out_unvec = np.zeros_like(self.model_unvec)
        for i in range(3):
            self.overlap_in_unvec[i][tuple(slice_in)] = self.model_region[i]
            self.overlap_out_unvec[i][tuple(slice_out)] = self.model_region[i]

        self.overlap_in = fdfd_tools.vec(self.overlap_in_unvec)
        self.overlap_out = fdfd_tools.vec(self.overlap_out_unvec)

        self.overlap_in_function = OverlapFunction(input_function,
                                                   self.overlap_in)
        self.overlap_out_function = OverlapFunction(input_function,
                                                    self.overlap_out)
Пример #11
0
def compute_source_angle(
    E: field_t,
    H: field_t,
    wavevector: List[float],
    omega: complex,
    eps: List[np.array],
    dxes: dx_lists_t,
    axis: int,
    polarity: int,
    slices: List[slice],
    mu: field_t = None,
) -> field_t:
    """
    Given an eigenmode obtained by solve_waveguide_mode, returns the current source distribution
    necessary to position a unidirectional source at the slice location.

    :param E: E-field of the mode
    :param H: H-field of the mode (advanced by half of a Yee cell from E)
    :param wavenumber: Wavenumber of the mode
    :param omega: Angular frequency of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param axis: Propagation axis (0=x, 1=y, 2=z)
    :param polarity: Propagation direction (+1 for +ve, -1 for -ve)
    :param slices: epsilon[tuple(slices)] is used to select the portion of the grid to use
        as the waveguide cross-section. slices[axis] should select only one
    :param mu: Magnetic permeability (default 1 everywhere)
    :return: J distribution for the unidirectional source
    """
    if polarity == 1:
        Mslice = copy.deepcopy(slices)
        Jslice = copy.deepcopy(slices)
        Jslice[axis] = slice(Jslice[axis].start + 1, Jslice[axis].stop + 1)
    elif polarity == -1:
        Mslice = copy.deepcopy(slices)
        Mslice[axis] = slice(Mslice[axis].start - 1, Mslice[axis].stop - 1)
        Jslice = copy.deepcopy(slices)

    if mu is None:
        mu = np.ones_like(eps)

    curl_h = lambda x: unvec(
        operators.curl_h(dxes=dxes, bloch_vec=wavevector) @ vec(x), x[0].shape)
    curl_e = lambda x: unvec(
        operators.curl_e(dxes=dxes, bloch_vec=wavevector) @ vec(x), x[0].shape)

    M_temp = -np.array(curl_e(E)) - 1j * omega * np.array(mu) * np.array(H)
    J_temp = np.array(curl_h(H)) - 1j * omega * np.array(eps) * np.array(E)
    M = np.zeros_like(M_temp)
    J = np.zeros_like(J_temp)
    J[[slice(0, 3)] + Jslice] = J_temp[[slice(0, 3)] + Jslice]
    M[[slice(0, 3)] + Mslice] = M_temp[[slice(0, 3)] + Mslice]
    J[axis] = np.zeros_like(J[axis])
    M[axis] = np.zeros_like(M[axis])

    Js = np.array(n_cross(H))
    Ms = -np.array(n_cross(E))

    Jm = np.array(curl_h(M / mu))

    J = J - Jm / (-1j * omega)

    return J
Пример #12
0
def mode_solver(bloch_vec: np.ndarray,
                omega_appx: float,
                num_modes: int,
                dxes: dx_lists_t,
                epsilon: np.ndarray,
                op_type: str,
                set_init_cond: bool,
                init_vec: np.ndarray = None,
                mu: np.ndarray = None,
                shift_orthogonal=np.zeros((3, 3))):

    if mu is None:
        mu = np.ones_like(epsilon)

    # Setting up the operator
    if op_type == 'efield':
        op, eps_norm, eps_un_norm = efield_operator(bloch_vec, dxes,
                                                    vec(epsilon), vec(mu),
                                                    shift_orthogonal)

    elif op_type == 'hfield':
        op, eps_norm, eps_norm = hfield_operator(bloch_vec, dxes, vec(epsilon),
                                                 vec(mu), shift_orthogonal)
    else:
        raise ValueError('Undefined operator type')

    if set_init_cond and init_vec is None:

        # Starting with initial condition with a FDFD simulation
        J = np.zeros_like(epsilon)
        J[0][J.shape[1] // 2, J.shape[2] // 2, J.shape[3] // 2] = 1.0
        J[1][J.shape[1] // 2, J.shape[2] // 2, J.shape[3] // 2] = 1.0
        #J[2][J.shape[1]//2, J.shape[2]//2,J.shape[3]//2] = 1.0
        solver = DirectSolver()
        sim_args = {
            'omega': omega_appx,
            'dxes': dxes,
            'epsilon': vec(epsilon),
            'mu': vec(mu),
            'J': vec(J),
            'bloch_vec': bloch_vec
        }

        E = solver.solve(**sim_args)
        elec_field = unvec(E, J[0].shape)

        if op_type == 'efield':
            mode_estimate = eps_un_norm @ E
        elif op_type == 'hfield':
            op_e2h = operators.e2h(omega=1.0,
                                   dxes=dxes,
                                   mu=vec(mu),
                                   bloch_vec=bloch_vec)
            mode_estimate = op_e2h @ E
        else:
            raise ValueError('Undefined operator type')

        # Solving for eigen values
        eig_value, mode_field = scipy.sparse.linalg.eigs(op,
                                                         num_modes,
                                                         sigma=omega_appx**2,
                                                         v0=mode_estimate)

    elif init_vec is not None:
        eig_value, mode_field = scipy.sparse.linalg.eigs(op,
                                                         1,
                                                         sigma=omega_appx**2,
                                                         v0=init_vec)

    else:
        eig_value, mode_field = scipy.sparse.linalg.eigs(op,
                                                         num_modes,
                                                         sigma=omega_appx**2)
    omega = np.sqrt(eig_value)

    if op_type == 'efield':
        if len(eig_value) == 1:
            mode_field = eps_norm @ mode_field
        else:
            mode_field = [eps_norm @ mode for mode in mode_field]

    return omega, mode_field.transpose()
Пример #13
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)
Пример #14
0
def build_laguerre_gaussian_source(eps_grid: Grid,
                          omega: float,
                          w0: float,
                          center: np.ndarray,
                          axis: int,
                          slices=List[slice],
                          mu: List[np.ndarray] = None,
                          theta: float = 0,
                          psi: float = 0,
                          polarization_angle: float = 0,
                          m: int = 0,
                          p : int = 0,
                          polarity: int = 1,
                          power: float = 1):
    """Builds a laguerre gaussian beam source.

    By default, the laguerre gaussian beam propagates along polarity of the given axis and
    is linearly polarized along the x-direction if axis is z, y-direction if x and
    z direction if y. `theta` rotates the propagation direction around the E-field,
    then 'psi' rotates source plane normal, and the polarization_angle rotates around
    the propagation direction.

    Args:
        eps_grid: gridlock.grid with the permittivity distribution.
        omega: The frequency of the mode.
        axis: Direction of propagation.
        slices: Source slice which define the position of the source in the grid.
        mu: Permeability distribution.
        theta: Rotation around the default E-component.
        psi: Rotation around the source plane normal.
        polarization_angle: Rotation around the propagation direction.
        m : parameters of the  laguerre gaussian beam
        p : parameters of the  laguerre gaussian beam
        polarity: 1 if forward propagating. -1 if backward propagating.
        power: Power is the gaussian beam.

    Returns:
        Current source J.
    """
    # Make scalar fields.
    eps_val = np.real(np.average(eps_grid.grids[0][tuple(slices)]))
    scalar_field_function = lambda x, y, z, R: laguerre_gaussian_beam_z_axis_x_pol(
        x, y, z, w0, center, R, omega, m, p, polarity, eps_val)

    # Get vector fields.
    fields = scalar2rotated_vector_fields(
        eps_grid=eps_grid,
        scalar_field_function=scalar_field_function,
        mu=mu,
        omega=omega,
        axis=axis,
        slices=slices,
        theta=theta,
        psi=psi,
        polarization_angle=polarization_angle,
        polarity=polarity,
        power=power,
        full_fields=True)

    # Calculate the source.
    dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()]

    # make current
    field_slices = [slice(None, None)] * 3
    J_slices = slices
    if polarity == -1:
        ind = slices[axis].start
        field_slices[axis] = slice(None, ind)
        J_slices[axis] = slice(ind - 1, ind + 1)
    else:
        ind = slices[axis].stop - 1
        field_slices[axis] = slice(ind, None)
        J_slices[axis] = slice(ind - 1, ind + 1)
    E = np.zeros_like(fields['E'])
    for i in range(3):
        E[i][tuple(field_slices)] = fields['E'][i][tuple(field_slices)]

    full = operators.e_full(omega,
                            dxes,
                            vec(eps_grid.grids),
                            bloch_vec=fields['wavevector'])
    J_vec = 1 / (-1j * omega) * full @ vec(E)
    J_temp = unvec(J_vec, E[0].shape)
    J = np.zeros_like(J_temp)
    for i in range(3):
        J[i][tuple(J_slices)] = J_temp[i][tuple(J_slices)]

    return J, fields['wavevector']
Пример #15
0
def build_plane_wave_source(eps_grid: Grid,
                            omega: float,
                            axis: int,
                            slices=List[slice],
                            mu: List[np.ndarray] = None,
                            theta: float = 0,
                            psi: float = 0,
                            polarization_angle: float = 0,
                            polarity: int = 1,
                            border: int or List[int] = 0,
                            power: float = 1):
    """Builds a plane wave source.

    By default, the plane wave propagates along polarity of the given axis and
    is linearly polarized along the x-direction if axis is z, y-direction if x and
    z direction if y. `theta` rotates the propagation direction around the E-field,
    then 'psi' rotates source plane normal, and the polarization_angle rotates around
    the propagation direction.

    Args:
        eps_grid: gridlock.grid with the permittivity distribution.
        omega: The frequency of the mode.
        axis: Direction of propagation.
        slices: Source slice which define the position of the source in the grid.
        mu: Permeability distribution.
        theta: Rotation around the default E-component.
        psi: Rotation around the source plane normal.
        polarization_angle: Rotation around the propagation direction.
        polarity: 1 if forward propagating. -1 if backward propagating.
        border: border in grid points where the intensity of the planewave decreased to 0.
            For example: [10, 10, 10] assuming radiation in the z direction will put 10
                grid border on both sides in the x and y direction and ignore the z.
                (If the border is larger then the simulation it will be ignored aswell.)
        power: power transmitted through the slice.

    Returns:
        Current source J.
    """
    # Perpare arguments.
    shape = eps_grid.shape

    # Make scalar fields.
    eps_val = np.real(np.average(eps_grid.grids[0][tuple(slices)]))
    scalar_field_function = lambda x, y, z, R: plane_wave_z_axis_x_pol(
        x, y, z, R, omega, polarity, eps_val)

    # Get vector fields.
    fields = scalar2rotated_vector_fields(
        eps_grid=eps_grid,
        scalar_field_function=scalar_field_function,
        mu=mu,
        omega=omega,
        axis=axis,
        slices=slices,
        theta=theta,
        psi=psi,
        polarization_angle=polarization_angle,
        polarity=polarity,
        power=power,
        full_fields=True)

    def make_mask(shape: List[int], slices: List, axis: int,
                  border: List[int]) -> np.ndarray:
        """Builds a mask with a border. On the border the intensity goes from 1 to 0.

        Args:
            shape: Shape of the simulation.
            slices: List of slices where the plane wave is.
            axis: Direction of the plane wave.
            border: number of gridpoint that make up the border.

        Returns:
            mask
        """
        size = [s.stop - s.start for s in slices]
        coords = []
        for i in range(3):
            if (size[i] - 1) > 2 * border[i] and border[i] > 0:
                d = 1 / np.array(border[i])
                x = np.hstack([
                    np.arange(1, 0, -d),
                    np.zeros(size[i] - 2 * border[i]),
                    np.arange(d, 1 + d, d)
                ])
            else:
                x = np.zeros(size[i])
            coords.append(x)
        coords[axis] = np.zeros(shape[axis])
        ind = np.meshgrid(coords[0], coords[1], coords[2], indexing='ij')
        co = (ind[0]**2 + ind[1]**2 + ind[2]**2)**0.5
        co[co > 1] = 1
        mask_slice = 1 + 2 * co**3 - 3 * co**2
        mask = np.zeros(shape)
        slices_mask = copy.deepcopy(slices)
        slices_mask[axis] = slice(None, None)
        mask[tuple(slices_mask)] = mask_slice
        return mask

    # Prepare border and the mask for be fields.
    if isinstance(border, int):
        border = 3 * [border]
    border[axis] = 0  # You can not have a border in the axis direction
    size = [s.stop - s.start for s in slices]
    for i, (b, s) in enumerate(zip(border, size)):
        if 2 * b < s:
            border[i] = b
        elif s == 1:
            border[i] = 0
        else:
            raise ValueError("Two times the border is larger then the size" +
                             " of the plane wave in axis {}.".format(i))

    border = [b if 2 * b < s else 0 for b, s in zip(border, size)]
    mask = make_mask(shape, slices, axis, border)

    # Normalize fields
    dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()]
    slices_P = copy.deepcopy(slices)
    slices_P = [
        slice(sl.start + b, sl.stop - b) for sl, b in zip(slices_P, border)
    ]
    P = waveguide_mode.compute_transmission_chew(E=fields['E'],
                                                 H=fields['H'],
                                                 axis=axis,
                                                 omega=omega,
                                                 dxes=dxes,
                                                 slices=slices_P)
    fields['E'] /= np.sqrt(abs(P))
    fields['H'] /= np.sqrt(abs(P))
    fields['E'] = [mask * e for e in fields['E']]
    fields['H'] = [mask * h for h in fields['H']]

    # Calculate the source.
    dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()]

    # make current
    field_slices = [slice(None, None)] * 3
    J_slices = slices
    if polarity == -1:
        ind = slices[axis].start
        field_slices[axis] = slice(None, ind)
        J_slices[axis] = slice(ind - 1, ind + 1)
    else:
        ind = slices[axis].stop - 1
        field_slices[axis] = slice(ind, None)
        J_slices[axis] = slice(ind - 1, ind + 1)
    E = np.zeros_like(fields['E'])
    for i in range(3):
        E[i][tuple(field_slices)] = fields['E'][i][tuple(field_slices)]

    full = operators.e_full(omega,
                            dxes,
                            vec(eps_grid.grids),
                            bloch_vec=fields['wavevector'])
    J_vec = 1 / (-1j * omega) * full @ vec(E)
    J_temp = unvec(J_vec, E[0].shape)
    J = np.zeros_like(J_temp)
    for i in range(3):
        J[i][tuple(J_slices)] = J_temp[i][tuple(J_slices)]

    return J, fields['wavevector']
Пример #16
0
def scalar2rotated_vector_fields(eps_grid: Grid,
                                 scalar_field_function: Callable,
                                 mu: List[np.ndarray],
                                 omega: float,
                                 axis: int,
                                 slices: List[slice],
                                 theta: float = 0,
                                 psi: float = 0,
                                 polarization_angle: float = 0,
                                 polarity: int = 1,
                                 power: float = 1,
                                 full_fields: bool = False):
    """
    Given a function that evaluates the scalar field that propagates in the z-direction,
    this function will make the vector field taking into account three rotations.

    Args:
        eps_grid: gridlock.grid with the permittivity distribution.
        mu: Permeability distribution.
        omega: The frequency of the mode.
        axis: Direction of propagation.
        slices: Source slice which define the position of the source in the grid.
        theta: Rotation around the default E-component.
        psi: Rotation around the source plane normal.
        polarization_angle: Rotation around the propagation direction.
        polarity: 1 if forward propagating. -1 if backward propagating.
        power: power
        full_fields: True gives you the field in the entire simulation space.
            False gives only the fields in the slices, the rest of the simulation space
            will be zero.

    Returns:
        Results: dict with the wavevector, the e-field and the h-field.

    """
    if mu is None:
        mu = [np.ones(eps_grid.shape)] * 3
    dxes = [eps_grid.dxyz, eps_grid.autoshifted_dxyz()]
    epsilon = eps_grid.grids
    xyz_shift = [eps_grid.shifted_xyz(which_shifts=i) for i in range(3)]

    # Define rotation to set z as propagation direction.
    order = np.roll(range(3), 2 - axis)
    reverse_order = np.roll(range(3), axis - 2)

    #Make grid points
    xyz = xyz_shift[order[0]]
    x_Ex, y_Ex, z_Ex = np.meshgrid(xyz[order[0]],
                                   xyz[order[1]],
                                   xyz[order[2]],
                                   indexing='ij')
    xyz = xyz_shift[order[1]]
    x_Ey, y_Ey, z_Ey = np.meshgrid(xyz[order[0]],
                                   xyz[order[1]],
                                   xyz[order[2]],
                                   indexing='ij')
    xyz = xyz_shift[order[2]]
    x_Ez, y_Ez, z_Ez = np.meshgrid(xyz[order[0]],
                                   xyz[order[1]],
                                   xyz[order[2]],
                                   indexing='ij')

    #Make total rotation matrix
    k = np.array([0, 0, polarity])
    e0 = np.array([1, 0, 0])
    R_theta = rotation_matrix(e0, theta)
    R_psi = rotation_matrix(k, psi)
    k = R_psi @ R_theta @ k
    R_pol = rotation_matrix(k, polarization_angle)
    R = R_pol @ R_psi @ R_theta
    e0_rot = R @ e0

    #Make wavevector
    ref_index = np.sqrt(np.real(np.average(epsilon[0][tuple(slices)])))
    bloch_vector = (k * omega * ref_index)

    #Evaluate fields.
    ex_mag = scalar_field_function(x_Ex, y_Ex, z_Ex,
                                   np.linalg.inv(R)) * e0_rot[0]
    ey_mag = scalar_field_function(x_Ey, y_Ey, z_Ey,
                                   np.linalg.inv(R)) * e0_rot[1]
    ez_mag = scalar_field_function(x_Ez, y_Ez, z_Ez,
                                   np.linalg.inv(R)) * e0_rot[2]
    E_fields = [ex_mag, ey_mag, ez_mag]

    #Make H fields.
    dxes = [[dx[i] for i in order] for dx in dxes]
    e_vec = vec(E_fields)
    h_vec = operators.e2h(omega=omega,
                          dxes=dxes,
                          mu=vec([mu[i].transpose(order) for i in order]),
                          bloch_vec=bloch_vector) @ e_vec
    H_fields = unvec(h_vec, E_fields[0].shape)

    #Normalize fields.
    #Roll back
    E = [None] * 3
    H = [None] * 3
    wavevector = np.zeros(3)
    for a, o in enumerate(reverse_order):
        E[a] = np.zeros_like(epsilon[0], dtype=complex)
        H[a] = np.zeros_like(epsilon[0], dtype=complex)

        if full_fields:
            E[a] = np.sqrt(power) * E_fields[o].transpose(reverse_order)
            H[a] = np.sqrt(power) * H_fields[o].transpose(reverse_order)
        else:
            E[a][tuple(slices)] = np.sqrt(power) * E_fields[o][tuple(
                [slices[i] for i in order])].transpose(reverse_order)
            H[a][tuple(slices)] = np.sqrt(power) * H_fields[o][tuple(
                [slices[i] for i in order])].transpose(reverse_order)

        wavevector[a] = bloch_vector[o]

    results = {
        'wavevector': wavevector,
        'H': H,
        'E': E,
    }

    return results
Пример #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 get_electric_fields(self, param: Parametrization):
     return fdfd_tools.unvec(self.sim.simulate(param.get_structure()),
                             self.sim.get_dims())
Пример #19
0
    def _run_solver(self, z: np.ndarray, J: np.ndarray) -> np.ndarray:
        """ Runs the solver to compute electric fields.

        Here I use the notation M* = conj(M), M.' = transpose(M),
        M' = ctranspose(M), M N = dot(M, N)

        Maxwell uses a symmetrized wave operator M = (L A R) = (L A R).',
         where L = inv(R) = diag(sqrt(s)) [s as in the code below] when
          solving the wave equation; it thus solves the problem
          M y = d
          => (L A R) (inv(R) x) = (L b)
          => A x = b
          with x = R y

         From the fact that M is symmetric, we can write
          M* = (L A R)* = (L A R)' = R' A' L' = R* A' L*
         We obtain M* by conjugating the contents of sim (except sim.J).
         We then multiply sim.J by (R* R*) = diag(1./s)*, obtaining
          (R* A' L*) v = (L* (R* R* b))
                       = (R* b)
         and then fix the returned value v by multiplying by 
          (L* L*) = diag(s)
         leading to the result (L* L* v) = (L* L* (R* x)) = L* x

         Putting it all together, with adjoint=true, we have
          (R* A' L*) (inv(L* L* R*) x) = (L* (R* R* b))
          => (R* A' L*) (inv(L*) x) = (R* b)
          => A' x = b

        Args:
            z: The structure.
        Returns:
            Vectorized form of the electric fields.
        """
        # TODO(logansu): Support PEC/PMC.
        dxes = [[np.conj(dx) for dx in grid] for grid in self.sim.dxes]

        spx, spy, spz = np.meshgrid(dxes[1][0],
                                    dxes[1][1],
                                    dxes[1][2],
                                    indexing='ij')
        sdx, sdy, sdz = np.meshgrid(dxes[0][0],
                                    dxes[0][1],
                                    dxes[0][2],
                                    indexing='ij')
        mult = np.multiply
        s = [
            mult(mult(sdx, spy), spz),
            mult(mult(spx, sdy), spz),
            mult(mult(spx, spy), sdz)
        ]
        new_J = np.copy(fdfd_tools.unvec(J, self.sim.dims))

        for k in range(3):
            new_J[k] /= np.conj(s[k])
        new_J = fdfd_tools.vec(new_J)
        mu = None
        if self.sim.mu is not None:
            mu = np.conj(fdfd_tools.vec(self.sim.mu))
        sim_args = {
            'omega': np.conj(self.sim.omega),
            'dxes': dxes,
            'epsilon': np.conj(self.sim.get_epsilon(z)),
            'mu': mu,
            'J': new_J,
            'pec': fdfd_tools.vec(self.sim.pec),
            'pmc': fdfd_tools.vec(self.sim.pmc),
            'bloch_vec': self.sim.bloch_vec,
        }
        efields = self.sim.solver.solve(**sim_args)
        # Unvec to undo right pre-conditioner.
        efields = fdfd_tools.unvec(efields, self.sim.dims)
        for i in range(3):
            efields[i] = np.multiply(efields[i], np.conj(s[i]))
        efields = fdfd_tools.vec(efields)
        return efields
Пример #20
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)
Пример #21
0
    def solve(self,
              omega: complex,
              dxes: List[List[np.ndarray]],
              J: np.ndarray,
              epsilon: np.ndarray,
              pml_layers: Optional[fdfd_tools.PmlLayers] = None,
              mu: np.ndarray = None,
              pec: np.ndarray = None,
              pmc: np.ndarray = None,
              pemc: np.ndarray = None,
              bloch_vec: np.ndarray = None,
              symmetry: np.ndarray = None,
              adjoint: bool = False,
              E0: np.ndarray = None,
              solver_info: bool = False,
              n_eig: int = 1):
        if symmetry is None:
            symmetry = np.zeros(3)
        if pemc is None:
            pemc = np.zeros(6)

        server_url = 'http://' + self.server + '/'

        dxes = fdfd_tools.grid.apply_scpml(dxes, pml_layers, omega)

        # Set initial condition to all zeros if not specified.
        if E0 is None:
            E0 = np.zeros_like(epsilon)
        # Set mu to 1 if not specified.
        if mu is None:
            mu = np.ones_like(epsilon)
        if bloch_vec is None:
            bloch_phase = np.ones([3, 3])
        else:
            # TODO(Dries): check this phase calculation for non-uniform grid
            sim_length = np.array([np.real(np.sum(a)) for a in dxes[0]])
            bloch_phase_uniform = np.exp(1j * (sim_length * bloch_vec))
            bloch_phase = np.transpose([bloch_phase_uniform]) @ np.ones([1, 3])
        # Set up using symmetry
        J_unvec = fdfd_tools.unvec(J, self.shape)
        eps_unvec = fdfd_tools.unvec(epsilon, self.shape)
        mu_unvec = fdfd_tools.unvec(mu, self.shape)
        E0_unvec = fdfd_tools.unvec(E0, self.shape)
        slices = [slice(0, sh) for sh in self.shape]
        if symmetry[0] == 1:
            slices[0] = slice(self.shape[0] // 2, self.shape[0])
            pemc[:2] = 1
        elif symmetry[0] == 2:
            slices[0] = slice(self.shape[0] // 2, self.shape[0])
            pemc[:2] = 2
        if symmetry[1] == 1:
            slices[1] = slice(self.shape[1] // 2, self.shape[1])
            pemc[2:4] = 1
        elif symmetry[1] == 2:
            slices[1] = slice(self.shape[1] // 2, self.shape[1])
            pemc[2:4] = 2
        if symmetry[2] == 1:
            slices[2] = slice(self.shape[2] // 2, self.shape[2])
            pemc[4:6] = 1
        elif symmetry[2] == 2:
            slices[2] = slice(self.shape[2] // 2, self.shape[2])
            pemc[4:6] = 2
        shape = [sl.stop - sl.start for sl in slices]
        J = fdfd_tools.vec([j[tuple(slices)] for j in J_unvec])
        E0 = fdfd_tools.vec([e[tuple(slices)] for e in E0_unvec])
        epsilon = fdfd_tools.vec([e[tuple(slices)] for e in eps_unvec])
        mu = fdfd_tools.vec([m[tuple(slices)] for m in mu_unvec])
        dxes = [[dxes[j][i][slices[i]] for i in range(3)] for j in range(2)]

        if adjoint:
            omega = np.conj(omega)
            mu = np.conj(mu)
            epsilon = np.conj(epsilon)
            new_dxes = []
            for i in range(2):
                dx = []
                for j in range(3):
                    dx.append(np.conj(dxes[i][j]))
                new_dxes.append(dx)
            dxes = new_dxes

            spx, spy, spz = np.meshgrid(dxes[1][0],
                                        dxes[1][1],
                                        dxes[1][2],
                                        indexing='ij')
            sdx, sdy, sdz = np.meshgrid(dxes[0][0],
                                        dxes[0][1],
                                        dxes[0][2],
                                        indexing='ij')
            mult = np.multiply
            s = [
                mult(mult(sdx, spy), spz),
                mult(mult(spx, sdy), spz),
                mult(mult(spx, spy), sdz)
            ]
            new_J = fdfd_tools.unvec(J, shape)
            for k in range(3):
                new_J[k] /= np.conj(s[k])
            J = fdfd_tools.vec(new_J)

        # Generate ID based on date, time, and UUID.
        sim_id = time.strftime('%Y%m%d-%H%M%S-') + str(uuid.uuid1())
        sim_name_prefix = 'maxwell-' + sim_id + '.'

        # Create a temporary directory.
        upload_dir = tempfile.mkdtemp()
        local_prefix = os.path.join(upload_dir, sim_name_prefix)

        make_array = lambda a: np.array([a])

        # set solver
        solver = 0
        # If there is a Bloch phase, we must use BiCGSTAB.
        if np.any(bloch_phase != np.ones([3, 3])):
            solver = 1
        if self.solver == 'biCGSTAB':
            solver = 1
        elif self.solver == 'lgmres':
            solver = 2
        elif self.solver == 'Jacobi-Davidson':
            solver = 3

        # Write the grid file.
        gridfile = local_prefix + 'grid'
        with h5py.File(gridfile, 'w') as f:
            f.create_dataset('omega_r', data=make_array(np.real(omega)))
            f.create_dataset('omega_i', data=make_array(np.imag(omega)))
            f.create_dataset('shape', data=shape)
            f.create_dataset('n_eig', data=make_array(n_eig))
            f.create_dataset('max_iters', data=make_array(self.max_iters))
            f.create_dataset('err_thresh', data=make_array(self.err_thresh))
            f.create_dataset('bloch_phase', data=bloch_phase)
            f.create_dataset('pemc', data=pemc)
            f.create_dataset('solver', data=solver)

            xyz = ['x', 'y', 'z']
            for direc in range(3):
                f.create_dataset('sd_' + xyz[direc] + 'r',
                                 data=np.real(dxes[0][direc]).astype(
                                     np.float64))
                f.create_dataset('sd_' + xyz[direc] + 'i',
                                 data=np.imag(dxes[0][direc]).astype(
                                     np.float64))
                f.create_dataset('sp_' + xyz[direc] + 'r',
                                 data=np.real(dxes[1][direc]).astype(
                                     np.float64))
                f.create_dataset('sp_' + xyz[direc] + 'i',
                                 data=np.imag(dxes[1][direc]).astype(
                                     np.float64))
        # Write the rest of the files.
        write_field(local_prefix + 'e', fdfd_tools.unvec(epsilon, shape))
        write_field(local_prefix + 'J', fdfd_tools.unvec(J, shape))
        write_field(local_prefix + 'm', fdfd_tools.unvec(mu, shape))
        write_field(local_prefix + 'A', fdfd_tools.unvec(E0, shape))

        # Upload files.
        upload_files(server_url, upload_dir, os.listdir(upload_dir))
        # Upload empty request file.
        request_filename = os.path.join(upload_dir, sim_name_prefix + 'request')
        with open(request_filename, 'w') as f:
            f.write('All files uploaded at {0}.'.format(
                time.strftime('%Y-%m-%d-%H:%M:%S')))
        upload_files(server_url, upload_dir, [sim_name_prefix + 'request'])

        # Delete temporary upload directory.
        shutil.rmtree(upload_dir)

        # Create temporary download directory.
        download_dir = tempfile.mkdtemp()

        # Wait for solution.
        def check_existence(filename):
            r = requests.get(server_url + sim_name_prefix + filename)
            return r.status_code == 200

        while True:
            time.sleep(1)  # Wait one second before retry.
            try:
                if check_existence('request'):
                    # Request not yet processed. Wait longer.
                    time.sleep(5)
                elif check_existence('finished'):
                    break
            except requests.exceptions.ConnectionError:
                logger.exception(
                    'ConnectionError while waiting for results. Retrying...')
                # Random retry time to prevent DoS on any machine.
                # Choose somewhere between 5 and 15 seconds.
                time.sleep(5 + random.uniform(0, 10))

        # Download the files.
        if self.solver == 'Jacobi-Davidson':
            filenames = [
                sim_name_prefix + 'Q' + str(i) + '_' + comp + quad
                for comp in 'xyz' for quad in 'ri' for i in range(n_eig)
            ]
            filenames += [sim_name_prefix + 'q' + quad for quad in 'ri']
        else:
            filenames = [
                sim_name_prefix + 'E_' + comp + quad
                for comp in 'xyz'
                for quad in 'ri'
            ]

        download_files(server_url, download_dir, filenames)

        # define apply_symmetry
        def apply_symmetry(E, symmetry):
            dummy = np.expand_dims(np.zeros_like(E[1][0, :, :]), 0)
            if symmetry[0] == 1:
                E[0] = np.concatenate((np.flip(E[0], 0), E[0]), axis=0)
                E[1] = np.concatenate(
                    (dummy, -np.flip(E[1][1:, :, :], 0), E[1]), axis=0)
                E[2] = np.concatenate(
                    (dummy, -np.flip(E[2][1:, :, :], 0), E[2]), axis=0)
            elif symmetry[0] == 2:
                E[0] = np.concatenate((-np.flip(E[0], 0), E[0]), axis=0)
                E[1] = np.concatenate((dummy, np.flip(E[1][1:, :, :], 0), E[1]),
                                      axis=0)
                E[2] = np.concatenate((dummy, np.flip(E[2][1:, :, :], 0), E[2]),
                                      axis=0)

            dummy = np.expand_dims(np.zeros_like(E[1][:, 0, :]), 1)
            if symmetry[1] == 1:
                E[0] = np.concatenate(
                    (dummy, -np.flip(E[0][:, 1:, :], 1), E[0]), axis=1)
                E[1] = np.concatenate((np.flip(E[1], 1), E[1]), axis=1)
                E[2] = np.concatenate(
                    (dummy, -np.flip(E[2][:, 1:, :], 1), E[2]), axis=1)
            elif symmetry[1] == 2:
                E[0] = np.concatenate((dummy, np.flip(E[0][:, 1:, :], 1), E[0]),
                                      axis=1)
                E[1] = np.concatenate((-np.flip(E[1], 1), E[1]), axis=1)
                E[2] = np.concatenate((dummy, np.flip(E[2][:, 1:, :], 1), E[2]),
                                      axis=1)

            dummy = np.expand_dims(np.zeros_like(E[1][:, :, 0]), 2)
            if symmetry[2] == 1:
                E[0] = np.concatenate(
                    (dummy, -np.flip(E[0][:, :, 1:], 2), E[0]), axis=2)
                E[1] = np.concatenate(
                    (dummy, -np.flip(E[1][:, :, 1:], 2), E[1]), axis=2)
                E[2] = np.concatenate((np.flip(E[2], 2), E[2]), axis=2)
            elif symmetry[2] == 2:
                E[0] = np.concatenate((dummy, np.flip(E[0][:, :, 1:], 2), E[0]),
                                      axis=2)
                E[1] = np.concatenate((dummy, np.flip(E[1][:, :, 1:], 2), E[1]),
                                      axis=2)
                E[2] = np.concatenate((-np.flip(E[2], 2), E[2]), axis=2)

            return E

        # Load in electric field.
        if self.solver == 'Jacobi-Davidson':
            E = []
            for i in range(n_eig):
                Q = []
                for comp in 'xyz':
                    file_prefix = os.path.join(
                        download_dir,
                        sim_name_prefix + 'Q' + str(i) + '_' + comp)
                    field_comp = None
                    with h5py.File(file_prefix + 'r') as f:
                        field_comp = f['data'][:].astype(np.complex128)
                    with h5py.File(file_prefix + 'i') as f:
                        field_comp += 1j * f['data'][:].astype(np.complex128)
                    Q.append(field_comp)
                E.append(apply_symmetry(Q, symmetry))

        else:
            E = []
            for comp in 'xyz':
                file_prefix = os.path.join(download_dir,
                                           sim_name_prefix + 'E_' + comp)
                field_comp = None
                with h5py.File(file_prefix + 'r') as f:
                    field_comp = f['data'][:].astype(np.complex128)
                with h5py.File(file_prefix + 'i') as f:
                    field_comp += 1j * f['data'][:].astype(np.complex128)
                E.append(field_comp)
            E = apply_symmetry(E, symmetry)

        # Undo right preconditioner for adjoint calculations.
        if adjoint:
            for i in range(3):
                E[i] = np.multiply(E[i], np.conj(s[i]))

        # Remove downloaded files.
        if solver_info:
            file_prefix = os.path.join(download_dir, sim_name_prefix)
            field_comp = None
            with h5py.File(file_prefix + 'time_info') as f:
                solve_time = f['data'][0]
            text_file = open(file_prefix + 'status', "r")
            lines = text_file.read().split('\n')
            error = [float(l) for l in lines[:-1]]

            shutil.rmtree(download_dir)
            return fdfd_tools.vec(E), solve_time, error
        elif self.solver == 'Jacobi-Davidson':
            file_prefix = os.path.join(download_dir, sim_name_prefix)
            q = None
            with h5py.File(file_prefix + 'qr') as f:
                q = f['data'][()].astype(np.complex128)
            with h5py.File(file_prefix + 'qi') as f:
                q += 1j * f['data'][()].astype(np.complex128)

            shutil.rmtree(download_dir)

            return [fdfd_tools.vec(Q) for Q in E], q**(0.5)
        else:
            shutil.rmtree(download_dir)
            return fdfd_tools.vec(E)
Пример #22
0
def solve_waveguide_mode_2d(
        mode_number: int,
        omega: complex,
        dxes: dx_lists_t,
        epsilon: vfield_t,
        mu: vfield_t = None,
        wavenumber_correction: bool = False) -> Dict[str, complex or field_t]:
    """
    Given a 2d region, attempts to solve for the eigenmode with the specified mode number.

    :param mode_number: Number of the mode, 0-indexed
    :param omega: Angular frequency of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param epsilon: Dielectric constant
    :param mu: Magnetic permeability (default 1 everywhere)
    :param wavenumber_correction: Whether to correct the wavenumber to
        account for numerical dispersion (default True)
    :return: {'E': List[np.ndarray], 'H': List[np.ndarray], 'wavenumber': complex}
    """
    '''
    Solve for the largest-magnitude eigenvalue of the real operator
     by using power iteration.
    '''
    # check if eps has a imaginary part
    if (np.imag(epsilon) != 0).any():
        warnings.warn('Epsilon in 2D mode solver has an imaginary part')

    dxes_real = [[np.real(dx) for dx in dxi] for dxi in dxes]

    A_r = waveguide.operator(np.real(omega), dxes_real, np.real(epsilon),
                             np.real(mu))

    # Use power iteration for 20 steps to estimate the dominant eigenvector
    v = np.random.rand(A_r.shape[0])
    for _ in range(20):
        v = A_r @ v
        v /= np.linalg.norm(v)

    lm_eigval = v @ A_r @ v
    '''
    Shift by the absolute value of the largest eigenvalue, then find a few of the
     largest (shifted) eigenvalues. The shift ensures that we find the largest
     _positive_ eigenvalues, since any negative eigenvalues will be shifted to the range
     0 >= neg_eigval + abs(lm_eigval) > abs(lm_eigval)
    '''
    shifted_A_r = A_r + abs(lm_eigval) * sparse.eye(A_r.shape[0])
    eigvals, eigvecs = spalg.eigs(shifted_A_r,
                                  which='LM',
                                  k=mode_number + 3,
                                  ncv=50)

    # Pick the eigenvalue we want from the few we found
    k = eigvals.argsort()[-(mode_number + 1)]
    v = eigvecs[:, k]
    '''
    Now solve for the eigenvector of the full operator, using the real operator's
     eigenvector as an initial guess for Rayleigh quotient iteration.
    '''
    A = waveguide.operator(omega, dxes, epsilon, mu)

    eigval = None
    for _ in range(40):
        eigval = v @ A @ v
        if np.linalg.norm(A @ v - eigval * v) < 1e-13:
            break
        w = spalg.spsolve(A - eigval * sparse.eye(A.shape[0]), v)
        v = w / np.linalg.norm(w)

    # Calculate the wave-vector (force the real part to be positive)
    wavenumber = np.sqrt(eigval)
    wavenumber *= np.sign(np.real(wavenumber))

    e, h = waveguide.normalized_fields(v, wavenumber, omega, dxes, epsilon, mu)
    '''
    Perform correction on wavenumber to account for numerical dispersion.

     See Numerical Dispersion in Taflove's FDTD book.
     This correction term reduces the error in emitted power, but additional
      error is introduced into the E_err and H_err terms. This effect becomes
      more pronounced as beta increases.
    '''
    if wavenumber_correction:
        # TODO(logansu): This was the original code but written out more
        # clearly. Clearly, `dx` is not always unity but this function
        # currently does not have access to the `dx` in the propagating
        # direction.
        dx = 1
        wavenumber = np.sin(np.real(
            wavenumber * dx / 2)) / (dx / 2) + np.imag(wavenumber)

    shape = [d.size for d in dxes[0]]
    fields = {
        'wavenumber': wavenumber,
        'E': unvec(e, shape),
        'H': unvec(h, shape),
    }

    return fields
Пример #23
0
def solve_waveguide_mode_3d(
        mode_number: int,
        omega_appx: float,
        wavenumber: float,
        dxes: dx_lists_t,
        axis: int,
        axis_rot: int,
        angle: float,
        polarity: int,
        slices: List[slice],
        eps: field_t,
        mu: field_t = None,
        expand_fields: bool = False,
        additional_mode_cal: int = 1) -> Dict[str, complex or np.ndarray]:
    """
    Given a 3d region, attempts to solve for the eigenmode with the specified mode number.

    :param mode_number: Number of the mode, 0-indexed
    :param omega_appx: approximated angular frequency of the simulation
    :param wavenumber: wavenumber of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param axis: propagation axis, i.e. x, y or z
    :param angle: angle with the propagation axis
    :param polarity: propagation direction (1 or -1)
    :param slices: slices that describe the region for the mode solve
    :param eps: permittivity
    :param mu: magnetic permeability (default 1 everywhere)
    :param expand_field: whether the calculated fields are duplicated over the entire simulation
        according to the periodicity and angle (default=false)
    :additional_mode_cal: how many mode are calculated
    :return: {'wavenumber': List[np.ndarray], 'H': List[np.ndarray], 'E':List[np.array]}, omega
    """
    # set propagation direction to the z-axis
    order = np.roll(range(3), 2 - axis)
    reverse_order = np.roll(range(3), axis - 2)

    source_slices = [slices[i] for i in order]
    if polarity == 1:
        source_slices[2] = slice(source_slices[2].stop - 1,
                                 source_slices[2].stop)
    else:
        source_slices[2] = slice(source_slices[2].start,
                                 source_slices[2].start + 1)
    eig_slices = [slices[i] for i in order]

    eps_sim = [eps[i].transpose(order) for i in order]
    dxes_sim = [[dx[i] for i in order] for dx in dxes]

    eps_eig = [eps[i][tuple(slices)].transpose(order) for i in order]
    if mu:
        mu_eig = [mu[i][tuple(slices)].transpose(order) for i in order]
    else:
        mu_eig = np.ones_like(eps_eig)
    dxes_eig = [[dx[i][slices[i]] for i in order] for dx in dxes]
    shp_eig = eps_eig[0].shape

    # calculate eigenmode
    shift_orthogonal = np.zeros((3, 3))
    shift_orthogonal[2, int(not reverse_order[axis_rot])] = -np.round(
        shp_eig[2] * np.tan(angle))
    bloch_vec = polarity * np.array(
        [np.sin(angle), np.sin(angle),
         np.cos(angle)]) * wavenumber
    bloch_vec[reverse_order[axis_rot]] = 0
    mode_arg = {
        'omega_appx': omega_appx,
        'bloch_vec': bloch_vec,
        'dxes': dxes_eig,
        'epsilon': eps_eig,
        'op_type': 'hfield',
        'set_init_cond': False,
        'num_modes': mode_number + 1 + additional_mode_cal,
        'shift_orthogonal': shift_orthogonal
    }
    eig_list, mode_list = phc.mode_solver(**mode_arg)
    index_sort = np.argsort(np.real(eig_list))

    h_vec = mode_list[index_sort[mode_number]]
    e_vec = operators.h2e(omega=eig_list[index_sort[mode_number]],
                          dxes=dxes_eig,
                          eps=vec(eps_eig),
                          bloch_vec=bloch_vec,
                          shift_orthogonal=shift_orthogonal) @ h_vec
    h_unvec = unvec(h_vec, shp_eig)
    e_unvec = unvec(e_vec, shp_eig)

    # expand fields
    axis_angle = int(not reverse_order[axis_rot])
    h_fields = np.zeros_like(eps_sim)
    e_fields = np.zeros_like(eps_sim)
    i_start = slices[axis].start
    i = i_start
    while i < e_fields[0].shape[2]:
        sl = [source_slices[0], source_slices[1], slice(i, i + 1)]
        for a in range(3):
            e_fields[a][sl] = operators.append_bloch_shift(
                A=e_unvec[a],
                axis=2,
                ind_axis=i - i_start,
                dx=dxes_eig[0],
                bloch_vector=bloch_vec,
                shift_orthogonal=shift_orthogonal[2])
            h_fields[a][sl] = operators.append_bloch_shift(
                A=h_unvec[a],
                axis=2,
                ind_axis=i - i_start,
                dx=dxes_eig[1],
                bloch_vector=bloch_vec,
                shift_orthogonal=shift_orthogonal[2])
        i += 1
    i = slices[axis].start - 1
    while i >= 0:
        sl = [source_slices[0], source_slices[1], slice(i, i + 1)]
        for a in range(3):
            e_fields[a][sl] = operators.append_bloch_shift(
                A=e_unvec[a],
                axis=2,
                ind_axis=i - i_start,
                dx=dxes_eig[0],
                bloch_vector=bloch_vec,
                shift_orthogonal=shift_orthogonal[2])
            h_fields[a][sl] = operators.append_bloch_shift(
                A=h_unvec[a],
                axis=2,
                ind_axis=i - i_start,
                dx=dxes_eig[1],
                bloch_vector=bloch_vec,
                shift_orthogonal=shift_orthogonal[2])
        i -= 1

    omega = eig_list[index_sort[mode_number]]

    # normalize field by the transmission through the slice
    transmission = compute_transmission_chew(
        E=e_fields,
        H=h_fields,
        axis=2,
        omega=omega,
        dxes=dxes_sim,
        slices=source_slices,
    )
    e = e_fields / np.sqrt(transmission)
    h = h_fields / np.sqrt(transmission)

    # select slice
    if not expand_fields:
        h_fields = np.zeros_like(eps_sim)
        h_fields[[slice(0, 3)] + source_slices] = h[[slice(0, 3)] +
                                                    source_slices]
        e_fields = np.zeros_like(eps_sim)
        e_fields[[slice(0, 3)] + source_slices] = e[[slice(0, 3)] +
                                                    source_slices]
    else:
        e_fields = e
        h_fields = h

    # reverse the order
    e_fields = [e_fields[i].transpose(reverse_order) for i in reverse_order]
    h_fields = [h_fields[i].transpose(reverse_order) for i in reverse_order]

    return {'wavenumber': bloch_vec[2], 'H': h_fields, 'E': e_fields}, omega
Пример #24
0
def compute_overlap_e(
    E: field_t,
    H: field_t,
    wavenumber: complex,
    omega: complex,
    dxes: dx_lists_t,
    axis: int,
    polarity: int,
    slices: List[slice],
    mu: field_t = None,
) -> field_t:
    """
    Given an eigenmode obtained by solve_waveguide_mode, calculates overlap_e for the
    mode orthogonality relation Integrate(((E x H_mode) + (E_mode x H)) dot dn)
    [assumes reflection symmetry].

    overlap_e makes use of the e2h operator to collapse the above expression into
     (vec(E) @ vec(overlap_e)), allowing for simple calculation of the mode overlap.

    :param E: E-field of the mode
    :param H: H-field of the mode (advanced by half of a Yee cell from E)
    :param wavenumber: Wavenumber of the mode
    :param omega: Angular frequency of the simulation
    :param dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :param axis: Propagation axis (0=x, 1=y, 2=z)
    :param polarity: Propagation direction (+1 for +ve, -1 for -ve)
    :param slices: epsilon[tuple(slices)] is used to select the portion of the grid to use
        as the waveguide cross-section. slices[axis] should select only one
    :param mu: Magnetic permeability (default 1 everywhere)
    :return: overlap_e for calculating the mode overlap
    """
    cross_plane = [slice(None)] * 3
    cross_plane[axis] = slices[axis]

    # Determine phase factors for parallel slices
    a_shape = np.roll([-1, 1, 1], axis)
    a_E = np.real(dxes[0][axis]).cumsum()
    a_H = np.real(dxes[1][axis]).cumsum()
    iphi = -polarity * 1j * wavenumber
    phase_E = np.exp(iphi * (a_E - a_E[slices[axis]])).reshape(a_shape)
    phase_H = np.exp(iphi * (a_H - a_H[slices[axis]])).reshape(a_shape)

    # Expand our slice to the entire grid using the calculated phase factors
    Ee = [None] * 3
    He = [None] * 3
    for k in range(3):
        Ee[k] = phase_E * E[k][tuple(cross_plane)]
        He[k] = phase_H * H[k][tuple(cross_plane)]

    # Write out the operator product for the mode orthogonality integral
    domain = np.zeros_like(E[0], dtype=int)
    domain[tuple(slices)] = 1

    npts = E[0].size
    dn = np.zeros(npts * 3, dtype=int)
    dn[0:npts] = 1
    dn = np.roll(dn, npts * axis)

    e2h = operators.e2h(omega, dxes, mu)
    ds = sparse.diags(vec([domain] * 3))
    h_cross_ = operators.poynting_h_cross(vec(He), dxes)
    e_cross_ = operators.poynting_e_cross(vec(Ee), dxes)

    overlap_e = dn @ ds @ (-h_cross_ + e_cross_ @ e2h)

    # Normalize
    norm_factor = np.abs(overlap_e @ vec(Ee))
    overlap_e /= norm_factor

    return unvec(overlap_e, E[0].shape)
Пример #25
0
 def get_electric_fields(self, param: Parametrization):
     # TODO(logansu): Refactor please
     return fdfd_tools.unvec(self.sim.simulate(param.get_structure()),
                             self.sim.get_dims())