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)
import dataclasses import numpy as np from spins import goos from spins.goos_sim.maxwell import render from spins.goos_sim.maxwell import simspace from spins import fdfd_solvers from spins import fdfd_tools from spins import gridlock from spins.fdfd_solvers import local_matrix_solvers # Have a single shared direct solver object because we need to use # multiprocessing to actually parallelize the solve. DIRECT_SOLVER = local_matrix_solvers.MultiprocessingSolver( local_matrix_solvers.DirectSolver()) class SimSource(goos.Model): pass @goos.polymorphic_model() class GaussianSource(SimSource): """Represents a gaussian source. Attributes: type: Must be "source.gaussian_beam". normalize_by_sim: If `True`, normalize the power by running a simulation. """
def __init__(self, solver: DirectSolver, dims: Tuple[int, int, int]) -> None: if solver.multiprocessing: self._solver = DIRECT_SOLVER else: self._solver = local_matrix_solvers.DirectSolver()
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)
from spins import fdfd_solvers from spins import fdfd_tools from spins import gridlock from spins.invdes import problem from spins.fdfd_solvers import local_matrix_solvers from spins.invdes.problem_graph import grid_utils from spins.invdes.problem_graph import optplan from spins.invdes.problem_graph import workspace # Make a style guide exception here because `simspace` is already used as a # variable. from spins.invdes.problem_graph.simspace import SimulationSpace # Have a single shared direct solver object because we need to use # multiprocessing to actually parallelize the solve. # DIRECT_SOLVER = local_matrix_solvers.MultiprocessingSolver(local_matrix_solvers.DirectSolver()) DIRECT_SOLVER = local_matrix_solvers.DirectSolver() @optplan.register_node(optplan.WaveguideModeSource) class WaveguideModeSource: def __init__(self, params: optplan.WaveguideModeSource, work: Optional[workspace.Workspace] = None) -> None: """Creates a new waveguide mode source. Args: params: Waveguide source parameters. work: Workspace for this source. Unused. """ self._params = params
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)