Esempio n. 1
0
def create_waveguide_multi_phase_function(params: optplan.ProblemGraphNode,
                                          work: workspace.Workspace):
    sim_space = work.get_object(params.simulation.simulation_space)
    wavelength = params.simulation.wavelength
    overlap_in = fdfd_tools.vec(
        work.get_object(params.overlap_in)(sim_space, wavelength))
    path = fdfd_tools.vec(work.get_object(params.path)(sim_space, wavelength))

    slice_in = grid_utils.create_region_slices(sim_space.edge_coords,
                                               params.overlap_in.center,
                                               params.overlap_in.extents)
    slice_out = grid_utils.create_region_slices(sim_space.edge_coords,
                                                params.overlap_out.center,
                                                params.overlap_out.extents)
    shape = sim_space.dims

    return WaveguideMultiPhaseFunction(input_function=work.get_object(
        params.simulation),
                                       overlap_model=overlap_in,
                                       slice_model=slice_in,
                                       slice_in=slice_in,
                                       slice_out=slice_out,
                                       path=path,
                                       wavelength=wavelength,
                                       shape=shape)
Esempio n. 2
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)]
def normalize_source_by_sim(
        omega: float,
        source: List[np.ndarray],
        eps: List[np.ndarray],
        dxes: List[np.ndarray],
        pml_layers: fdfd_tools.PmlLayers,
        solver: Callable,
        power: float,
        bloch_vector: List[float] = [0, 0, 0],
) -> List[np.ndarray]:
    """Normalizes a source by running a simulation.

    The simulation is run with uniform media (index is determined by averaging
    over all locations where the source is present), and the power emitted
    by the source is computed. Based on this, `source` is renormalized to
    emit `power` instead.

    WARNING: Only works for uniform meshes in all three directions.

    Args:
        omega: Angular frequency.
        source: Simulation source to normalize.
        eps: Permittivity distribution.
        dxes: List of grid spacings.
        pml_layers: Number of PML layers to apply on boundary.
        solver: Solver to use to run normalization simulation.
        power: Power that normalized source should emit.
        bloch_vector: Bloch vector to apply on simulation.

    Returns:
        Source that emits `power` in uniform media.
    """
    # Compute the average permittivity of the background by performing an
    # average over the permittivity distribution weighted by the magnitude of
    # the source (this is just convenience to avoid needing to set a threshold
    # for numerical errors in the source).
    source_abs = np.abs(fdfd_tools.vec(source))
    eps_avg = np.sum(source_abs * fdfd_tools.vec(eps)) / np.sum(source_abs)

    eps_uniform = [np.ones(eps[i].shape) * eps_avg for i in range(3)]
    electric_fields = solver.solve(
        omega=omega,
        dxes=dxes,
        epsilon=fdfd_tools.vec(eps_uniform),
        pml_layers=pml_layers,
        J=fdfd_tools.vec(source),
        bloch_vec=bloch_vector,
    )
    J_dot_E = -np.real(np.conj(fdfd_tools.vec(source)) * electric_fields)
    # TODO(logansu): Make this work for nonuniform meshes.
    dx = np.real(dxes[0][0][0])
    cur_power = 0.5 * np.sum(J_dot_E[:]) * dx**3

    J_normalized = copy.deepcopy(source)
    for i in range(3):
        J_normalized[i] *= np.sqrt(power / cur_power)
    return J_normalized
Esempio n. 4
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)]
Esempio n. 5
0
def create_phase_absolute_function(params: optplan.ProblemGraphNode,
                                   work: workspace.Workspace):
    simspace = work.get_object(params.simulation.simulation_space)
    wlen = params.simulation.wavelength
    region = fdfd_tools.vec(work.get_object(params.region)(simspace, wlen))
    path = fdfd_tools.vec(work.get_object(params.path)(simspace, wlen))
    return PhaseAverageFunction(input_function=work.get_object(
        params.simulation),
                                region=region,
                                path=path,
                                wavelength=wlen)
Esempio n. 6
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)
Esempio n. 7
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)
Esempio n. 8
0
def create_waveguide_phase_function(params: optplan.ProblemGraphNode,
                                    work: workspace.Workspace):
    sim_space = work.get_object(params.simulation.simulation_space)
    wavelength = params.simulation.wavelength
    overlap_in = fdfd_tools.vec(
        work.get_object(params.overlap_in)(sim_space, wavelength))
    overlap_out = fdfd_tools.vec(
        work.get_object(params.overlap_out)(sim_space, wavelength))
    path = fdfd_tools.vec(work.get_object(params.path)(sim_space, wavelength))
    return WaveguidePhaseFunction(input_function=work.get_object(
        params.simulation),
                                  overlap_in=overlap_in,
                                  overlap_out=overlap_out,
                                  path=path,
                                  wavelength=wavelength)
Esempio n. 9
0
def test_fdfd_simulation_grad():
    # Create a 3x3 2D grid to brute force check adjoint gradients.
    shape = [3, 3, 1]
    # Setup epsilon (pure vacuum).
    epsilon = [np.ones(shape) for i in range(3)]
    # Setup dxes. Assume dx = 40.
    dxes = [[np.ones(shape[i]) * 40 for i in range(3)] for j in range(2)]
    # Setup a point source in the center.
    J = [np.zeros(shape).astype(complex) for i in range(3)]
    J[2][1, 0, 0] = 1.2j
    J[2][1, 1, 0] = 1

    # Setup target fields.
    target_fields = [np.zeros(shape).astype(np.complex128) for i in range(3)]
    target_fields[2][:, :, 0] = 20j + 1
    overlap_vec = fdfd_tools.vec(target_fields)

    # TODO(logansu): Deal with this.
    class SimspaceMock:
        @property
        def dxes(self):
            return dxes

        @property
        def pml_layers(self):
            return [0] * 6

    eps_param = parametrization.DirectParam(fdfd_tools.vec(epsilon),
                                            bounds=[0, 100])
    eps_fun = problem.Variable(len(fdfd_tools.vec(epsilon)))
    sim_fun = creator_em.FdfdSimulation(
        eps=eps_fun,
        solver=local_matrix_solvers.DirectSolver(),
        wlen=1500,
        source=J,
        simspace=SimspaceMock(),
    )
    obj_fun = problem.AbsoluteValue(
        objective=creator_em.OverlapFunction(sim_fun, overlap_vec))**2

    grad_actual = obj_fun.calculate_gradient(eps_param)

    def eval_fun(vec: np.ndarray):
        eps_param.from_vector(vec)
        return obj_fun.calculate_objective_function(eps_param)

    grad_brute = eval_grad_brute(fdfd_tools.vec(epsilon), eval_fun)
    np.testing.assert_array_almost_equal(grad_actual, grad_brute, decimal=0)
Esempio n. 10
0
    def __init__(self, input_function: problem.OptimizationFunction,
                 simulation_space: SimulationSpace, center: np.ndarray,
                 extents: np.ndarray, epsilon: problem.OptimizationFunction):
        """Constructs the Stored Energy Fn

        Args:
            input_function: Input objectives (typically a simulation from which
                            we get the e-field).
            simulation_space: sim space we are simulating
            centre:  Centre of integration region
            extents: extends of integration region
            epsilon: Permittivity
        """
        # Call superclass initialisor. Nb: this defines the fn inputs for eval etc
        super().__init__([input_function, epsilon])

        region_slice = grid_utils.create_region_slices(
            simulation_space.edge_coords, center, extents)

        # Create a selection filter for vectorised fields and permittivities
        filter_grid = [np.zeros(simulation_space.dims) for i in range(3)]
        for i in range(3):
            filter_grid[i][
                tuple(region_slice
                      )] = 1  # X,Y,Z components set to 1 for region slice
        self._filter_vec = fdfd_tools.vec(filter_grid)
Esempio n. 11
0
def create_overlap_function(params: optplan.ProblemGraphNode,
                            work: workspace.Workspace):
    simspace = work.get_object(params.simulation.simulation_space)
    wlen = params.simulation.wavelength
    overlap = fdfd_tools.vec(work.get_object(params.overlap)(simspace, wlen))
    return OverlapFunction(input_function=work.get_object(params.simulation),
                           overlap=overlap)
Esempio n. 12
0
def create_diff_epsilon(params: optplan.DiffEpsilon,
                        work: workspace.Workspace) -> DiffEpsilon:

    if params.epsilon_ref.type == "gds":
        space = work.get_object(params.epsilon.simulation_space)
        from spins.invdes.problem_graph.simspace import _create_grid
        eps = _create_grid(params.epsilon_ref, space._edge_coords,
                           params.epsilon.wavelength, space._ext_dir,
                           space._filepath)
        eps_vec = fdfd_tools.vec(eps.grids)

    def epsilon_ref() -> np.ndarray:
        if params.epsilon_ref.type == "parametrization":
            space = work.get_object(params.epsilon_ref.simulation_space)(
                params.epsilon_ref.wavelength)
            structure = work.get_object(
                params.epsilon_ref.parametrization).get_structure()
            return (fdfd_tools.vec(space.eps_bg.grids) +
                    space.selection_matrix @ structure)
        elif params.epsilon_ref.type == "gds":
            return eps_vec
        else:
            raise NotImplementedError(
                "Epsilon spec with type {} not yet supported".format(
                    params.epsilon_ref.type))

    return DiffEpsilon(epsilon=work.get_object(params.epsilon),
                       epsilon_ref=epsilon_ref)
Esempio n. 13
0
def build_overlap_annulus(omega: complex, dxes: List[np.ndarray],
                          eps: List[np.ndarray], mu: List[np.ndarray] or None,
                          axis: Direction or int, mode_num: int,
                          power: float) -> List[np.ndarray]:
    """This should call the overlap and  increase/decrease it to emit the desired power"""
    if type(axis) is Direction:
        axis = axis.value

    len_dxes = np.concatenate(dxes, axis=0)
    E = annulus_field(np.abs(len_dxes[0].size), 3e9)

    arg_overlap = {
        'E': E,  # where we insert our desired electric field
        'axis': axis,
        'omega': omega,
        'dxes': dxes,
        'mu': vec(mu)
    }
    C = compute_overlap_annulus(**arg_overlap)

    # Increase/decrease C to emit desired power.
    for k in range(len(C)):
        C[k] *= np.sqrt(power)
    # Might want to return absolute value to remove phase - list or array
    # Remove temporarily, might be messing things up
    #[abs(x) for x in C]
    return C
Esempio n. 14
0
 def _compute_objective(self, out_modes):
     num_modes = len(out_modes)
     C_shape = (3 * np.prod(self.sim.dims), num_modes)
     C = scipy.sparse.csr_matrix(C_shape, dtype=np.complex128)
     alpha = np.zeros((num_modes, ))
     beta = np.zeros((num_modes, ))
     # Compute field design objective.
     for j, out in enumerate(out_modes):
         # TODO(logansu): Finish overlap mode stuff.
         if out['type'] == OverlapMode.ModeType.wgmode:
             sim_params = {
                 'omega': self.sim.omega,
                 'dxes': self.sim.dxes,
                 'axis': out['axis'].value,
                 'slices': [slice(i, f + 1) for i, f in zip(*out['pos'])],
                 'polarity': out['polarity'],
                 'mu': self.sim.mu
             }
             wgmode_result = fdfd_solvers.waveguide_mode.solve_waveguide_mode(
                 mode_number=out['mode_num'],
                 epsilon=self.sim.base_epsilon,
                 **sim_params)
             wgmode_result.update({
                 'omega':
                 self.sim.omega,
                 'dxes':
                 self.sim.dxes,
                 'axis':
                 out['axis'].value,
                 'slices': [slice(i, f + 1) for i, f in zip(*out['pos'])],
                 'polarity':
                 out['polarity'],
             })
             E_out = fdfd_solvers.waveguide_mode.compute_overlap_e(
                 **wgmode_result)
             E_out = fdfd_tools.vec(E_out)
         elif out['type'] == OverlapMode.ModeType.arbitrary:
             E_out = fdfd_tools.vec(out['overlap'])
         else:
             raise Exception('Unrecognized mode type: ', out.mode_type)
         # Linear algebra-ize.
         C[:, j] = scipy.sparse.csr_matrix(np.matrix(E_out).H)
         alpha[j] = np.sqrt(np.min(out['power']))
         beta[j] = np.sqrt(np.max(out['power']))
     self.objective_alpha = alpha
     self.objective_beta = beta
     self.objective_C = C
Esempio n. 15
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])
Esempio n. 16
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)
Esempio n. 17
0
 def _compute_objective(self, out_modes):
     num_modes = len(out_modes)
     C_shape = (3 * np.prod(self.sim.dims), num_modes)
     C = scipy.sparse.csr_matrix(C_shape, dtype=np.complex128)
     T_complex = np.zeros((num_modes, ), dtype=complex)
     # Compute field design objective.
     for j, out in enumerate(out_modes):
         # TODO(logansu): Finish overlap mode stuff.
         if out['type'] == OverlapMode.ModeType.wgmode:
             args = {
                 'mode_num':
                 out['mode_num'],
                 'omega':
                 self.sim.omega,
                 'dxes':
                 self.sim.dxes,
                 'axis':
                 out['axis'].value,
                 'waveguide_slice':
                 [slice(i, f + 1) for i, f in zip(*out['pos'])],
                 'polarity':
                 out['polarity'],
                 'power':
                 1,
                 'eps':
                 self.sim.base_epsilon,
                 'mu':
                 self.sim.mu
             }
             E_out = fdfd_solvers.waveguide_mode.build_overlap(**args)
             E_out = fdfd_tools.vec(E_out)
         elif out['type'] == OverlapMode.ModeType.arbitrary:
             E_out = np.conj(fdfd_tools.vec(
                 out['overlap']))  # conj to compensate for .H later
         else:
             raise Exception('Unrecognized mode type: ', out.mode_type)
         # Linear algebra-ize.
         C[:, j] = scipy.sparse.csr_matrix(np.matrix(E_out).H)
         # get T
         T_complex[j] = out['transmission']
     self.objective_T_complex = T_complex
     self.objective_C = C
Esempio n. 18
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)
Esempio n. 19
0
    def __call__(self, wlen: float) -> SimulationSpaceInstance:
        """Creates the background permittivity and selection matrix at `wlen`.

        Since creating grids can be expensive, this function caches all the
        simulation space instances generated.

        Args:
            wlen: Wavelength to instantiate simulation space.

        Returns:
            Instantiated simulation space.
        """
        # Round the wavelength to avoid floating point discrepancies.
        wlen = _round(wlen, digits=6)

        if wlen not in self._cache:
            eps_bg = _create_grid(self._eps_bg, self._edge_coords, wlen,
                                  self._ext_dir, self._filepath)
            eps_fg = _create_grid(self._eps_fg, self._edge_coords, wlen,
                                  self._ext_dir, self._filepath)

            # Create the selection matrix.
            if self._selmat_type == optplan.SelectionMatrixType.DIRECT.value:
                # TODO(logansu): Create selection matrix from design dims.
                selection_mat, self._design_dims = (
                    gridlock.create_selection_matrix(
                        eps_bg.grids,
                        eps_fg.grids,
                        return_param_dims=True,
                    ))
            elif self._selmat_type == optplan.SelectionMatrixType.FULL_DIRECT.value:
                self._design_dims = np.array(eps_fg.grids).shape
                import scipy.sparse
                selection_mat = scipy.sparse.diags(
                    fdfd_tools.vec(
                        np.array(eps_fg.grids) - np.array(eps_bg.grids)))
            elif self._selmat_type == optplan.SelectionMatrixType.REDUCED.value:
                # TODO(logansu): Create selection matrix from design dims.
                selection_mat, self._design_dims = (
                    gridlock.create_selection_matrix(
                        eps_bg.grids,
                        eps_fg.grids,
                        reduced=True,
                        return_param_dims=True,
                    ))
            else:
                raise NotImplementedError(
                    "Selection matrix type {} not yet implemented".format(
                        self._selmat_type))

            self._cache[wlen] = SimulationSpaceInstance(
                eps_bg=eps_bg, selection_matrix=selection_mat)

        return self._cache[wlen]
Esempio n. 20
0
    def eval(self, input_val: List[np.ndarray]) -> np.ndarray:
        """Returns simulated fields.

        Args:
            input_vals: List of the input values.

        Returns:
            Simulated fields.
        """
        return (fdfd_tools.vec(self._space.eps_bg.grids) +
                self._space.selection_matrix @ input_val[0])
Esempio n. 21
0
 def epsilon_ref() -> np.ndarray:
     if params.epsilon_ref.type == "parametrization":
         space = work.get_object(params.epsilon_ref.simulation_space)(
             params.epsilon_ref.wavelength)
         structure = work.get_object(
             params.epsilon_ref.parametrization).get_structure()
         return (fdfd_tools.vec(space.eps_bg.grids) +
                 space.selection_matrix @ structure)
     else:
         raise NotImplementedError(
             "Epsilon spec with type {} not yet supported".format(
                 work.epsilon_ref.type))
Esempio n. 22
0
    def _run_solver(self, z: np.ndarray) -> np.ndarray:
        """ Runs the solver to compute electric fields.

        Args:
            z: The structure.
        Returns:
            Vectorized form of the electric fields.
        """
        sim_args = {
            'omega': self.omega,
            'dxes': self.dxes,
            'epsilon': self.get_epsilon(z),
            'mu': fdfd_tools.vec(self.mu),
            'J': fdfd_tools.vec(self.J),
            'pec': fdfd_tools.vec(self.pec),
            'pmc': fdfd_tools.vec(self.pmc),
            'pemc': self.pemc,
            'symmetry': self.symmetry,
            'bloch_vec': self.bloch_vec
        }
        return self.solver.solve(**sim_args)
Esempio n. 23
0
def compute_transmission_chew(
    E: field_t,
    H: field_t,
    axis: int,
    omega: complex,
    dxes: dx_lists_t,
    slices: List[slice],
    mu: field_t = None,
    bloch_vec: np.ndarray = np.zeros(3)) -> field_t:
    """
    :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
    :omega: Angular frequency of the simulation
    :dxes: Grid parameters [dx_e, dx_h] as described in fdfd_tools.operators header
    :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
    :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[tuple([slice(0, 3)] + slices)] = 1

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

    ds = sparse.diags(vec(domain))
    e_cross_ = operators.poynting_chew_e_cross(vec(E), dxes)

    overlap_e = dn_vec @ ds @ e_cross_

    return np.abs(overlap_e @ np.conj(vec(H)))
Esempio n. 24
0
def test_epsilon_eval():
    space = make_simspace()
    space_inst = space(1550)
    param_shape = np.prod(space.design_dims)
    vec = np.ones(param_shape)
    vec[10:20] = 0.6
    vec[30:40] = 0.2

    eps = creator_em.Epsilon(problem.Variable(1), 1550, space)

    eps_actual = eps.eval([vec])
    eps_expected = (fdfd_tools.vec(space_inst.eps_bg.grids) +
                    space_inst.selection_matrix @ vec)

    np.testing.assert_array_equal(eps_actual, eps_expected)
Esempio n. 25
0
    def _simulate(self, eps: np.ndarray) -> np.ndarray:
        """Computes the electric field distribution.

        Because simulations are very expensive, we cache the simulations.

        Args:
            eps: The structure.

        Returns:
            Vectorized form of the electric fields.
        """
        # Only solve for the fields if the structure has changed.
        electric_fields = None
        # The cache is implemented as a list where the most recent
        # access is at the back of the list.

        # Search through cache for fields.
        for cache_index in range(len(self._cache)):
            cache_item = self._cache[cache_index]
            if cache_item is None:
                continue
            cache_struc, cache_fields = cache_item
            if np.array_equal(eps, cache_struc):
                electric_fields = cache_fields
                # Remove the hit entry (it will be reinserted later).
                del self._cache[cache_index]
                break

        if electric_fields is None:
            # Perfrom the solve.
            electric_fields = self._solver.solve(
                omega=2 * np.pi / self._wlen,
                dxes=self._simspace.dxes,
                epsilon=eps,
                mu=None,
                J=fdfd_tools.vec(self._source),
                pml_layers=self._simspace.pml_layers,
                bloch_vec=self._bloch_vector,
            )
            # Remove the last used element.
            del self._cache[0]

        # Insert data into cache.
        self._cache.append((eps, electric_fields))
        return electric_fields
Esempio n. 26
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)
Esempio n. 27
0
    def test_gradient(self):
        # Create a 3x3 2D grid to brute force check adjoint gradients.
        shape = [3, 3, 1]
        # Setup epsilon (pure vacuum).
        epsilon = [np.ones(shape) for i in range(3)]
        # Setup dxes. Assume dx = 40.
        dxes = [[np.ones(shape[i]) * 40 for i in range(3)] for j in range(2)]
        # Setup a point source in the center.
        J = [np.zeros(shape) for i in range(3)]
        J[2][1, 1, 0] = 1
        # Setup frequency.
        omega = 2 * np.pi / 1500
        # Avoid complexities of selection matrix by setting to the identity.
        # Number of elements: 3 field components * grid size
        S = np.identity(3 * np.prod(shape))
        # Use 2D solver.
        sim = FdfdSimulation(DirectSolver(), shape, omega, dxes, J, S, epsilon)

        # Setup target fields.
        target_fields = [
            np.zeros(shape).astype(np.complex128) for i in range(3)
        ]
        target_fields[2][:, :, 0] = 20j

        objective = SimpleEmObjective(sim, fdfd_tools.vec(target_fields))

        # Check gradient for initial parametrization of 0.5 everywhere.
        param = DirectParam(0.5 * np.ones(np.prod(shape) * 3))

        f = objective.calculate_objective_function(param)
        gradient = objective.calculate_gradient(param)

        # Now brute-force the gradient.
        eps = 1e-7  # Empirically 1e-6 to 1e-7 is the best step size.
        vec = param.encode()
        brute_gradient = np.zeros_like(vec)
        for i in range(len(vec)):
            temp_vec = np.array(vec)
            temp_vec[i] += eps
            new_f = objective.calculate_objective_function(
                DirectParam(temp_vec))
            brute_gradient[i] = (new_f - f) / eps

        np.testing.assert_almost_equal(gradient, brute_gradient, decimal=4)
Esempio n. 28
0
    def _compute_objective(self, out):
        C_shape = (3 * np.prod(self.sim.dims), 1)
        C = scipy.sparse.csr_matrix(C_shape, dtype=np.complex128)
        # Compute field design objective.
        if out['type'] == OverlapMode.ModeType.wgmode:
            sim_params = {
                'omega': self.sim.omega,
                'dxes': self.sim.dxes,
                'axis': out['axis'].value,
                'slices': [slice(i, f + 1) for i, f in zip(*out['pos'])],
                'polarity': out['polarity'],
                'mu': self.sim.mu
            }
            wgmode_result = fdfd_solvers.waveguide_mode.solve_waveguide_mode(
                mode_number=out['mode_num'],
                epsilon=self.sim.base_epsilon,
                **sim_params)
            wgmode_result.update({
                'omega':
                self.sim.omega,
                'dxes':
                self.sim.dxes,
                'axis':
                out['axis'].value,
                'slices': [slice(i, f + 1) for i, f in zip(*out['pos'])],
                'polarity':
                out['polarity'],
            })
            E_out = fdfd_solvers.waveguide_mode.compute_overlap_e(
                **wgmode_result)
        elif out['type'] == OverlapMode.ModeType.arbitrary:
            E_out = out['overlap']
        else:
            raise Exception('Unrecognized mode type: ', out['type'])

        self.objective_T = out.get('transmission', 0)
        self.objective_C = scipy.sparse.csr_matrix(
            np.matrix(fdfd_tools.vec(E_out)).H)
Esempio n. 29
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)
Esempio n. 30
0
    def __init__(
        self,
        field: creator_em.FdfdSimulation,
        simspace: SimulationSpace,
        wlen: float,
        plane_slice: Tuple[slice, slice, slice],
        axis: int,
        polarity: int,
    ) -> None:
        """Creates a new power plane function.

        Args:
            field: The `FdfdSimulation` to use to calculate power.
            simspace: Simulation space corresponding to the field.
            wlen: Wavelength of the field.
            plane_slice: Represents the locations of the field that are part
                of the plane.
            axis: Which Poynting vector field component to use.
            polarity: Indicates directionality of the plane.
        """
        super().__init__(field)
        self._omega = 2 * np.pi / wlen
        self._dxes = simspace.dxes
        self._plane_slice = plane_slice
        self._axis = axis
        self._polarity = polarity

        # Precompute any operations that can be computed without knowledge of
        # the field.
        self._op_e2h = fdfd_tools.operators.e2h(self._omega, self._dxes)
        self._op_curl_e = fdfd_tools.operators.curl_e(self._dxes)

        # Create a filter that sets a 1 in every position that is included in
        # the computation of the Poynting vector.
        filter_grid = [np.zeros(simspace.dims) for i in range(3)]
        filter_grid[self._axis][tuple(self._plane_slice)] = 1
        self._filter_vec = fdfd_tools.vec(filter_grid)