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)
def __call__(self, simspace: SimulationSpace, wlen: float, **kwargs) -> fdfd_tools.VecField: """Creates the source vector. Args: simspace: Simulation space object to use. wlen: Wavelength to operate source. Returns: The vector field corresponding to the source. """ space_inst = simspace(wlen) return fdfd_solvers.waveguide_mode.build_waveguide_source( omega=2 * np.pi / wlen, dxes=simspace.dxes, eps=space_inst.eps_bg.grids, mu=None, mode_num=self._params.mode_num, waveguide_slice=grid_utils.create_region_slices( simspace.edge_coords, self._params.center, self._params.extents), axis=gridlock.axisvec2axis(self._params.normal), polarity=gridlock.axisvec2polarity(self._params.normal), power=self._params.power)
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)
def __call__( self, simspace: SimulationSpace, wlen: float, solver, **kwargs ) -> Union[fdfd_tools.VecField, Tuple[fdfd_tools.VecField, fdfd_tools.Vec3d]]: """Creates the plane wave source. Args: simspace: Simulation space to use for the source. wlen: Wavelength of source. Returns: If `overwrite_bloch_vector` is `True`, a tuple containing the source field and the Bloch vector corresponding to the plane wave source. Otherwise, only the source field is returned. """ space_inst = simspace(wlen) # Calculate the border in gridpoints and igore the border if it's larger then the simulation. dx = simspace.dx border = [int(b // dx) for b in self._params.border] # The plane wave is assumed to be in the z direction so the border is 0 for z. border.append(0) source, kvector = fdfd_tools.free_space_sources.build_plane_wave_source( omega=2 * np.pi / wlen, eps_grid=space_inst.eps_bg, mu=None, axis=gridlock.axisvec2axis(self._params.normal), polarity=gridlock.axisvec2polarity(self._params.normal), slices=grid_utils.create_region_slices(simspace.edge_coords, self._params.center, self._params.extents), theta=self._params.theta, psi=self._params.psi, polarization_angle=self._params.polarization_angle, border=border, power=self._params.power) if self._params.normalize_by_sim: source = fdfd_tools.free_space_sources.normalize_source_by_sim( omega=2 * np.pi / wlen, source=source, eps=space_inst.eps_bg.grids, dxes=simspace.dxes, pml_layers=simspace.pml_layers, solver=solver, power=self._params.power) if self._params.overwrite_bloch_vector: # TODO(logansu): Figure out what's wrong with mixing PMLs and # Bloch vector. It seems to create problems. # For now, we manually set Bloch to zero is PML is nonzero. if simspace.pml_layers[0] != 0 or simspace.pml_layers[1] != 0: kvector[0] = 0 if simspace.pml_layers[2] != 0 or simspace.pml_layers[3] != 0: kvector[1] = 0 if simspace.pml_layers[4] != 0 or simspace.pml_layers[5] != 0: kvector[2] = 0 return source, kvector return source
def create_power_transmission_function( params: PowerTransmission, context: workspace.Workspace) -> PowerTransmissionFunction: simspace = context.get_object(params.field.simulation_space) return PowerTransmissionFunction( field=context.get_object(params.field), simspace=simspace, wlen=params.field.wavelength, plane_slice=grid_utils.create_region_slices(simspace.edge_coords, params.center, params.extents), axis=gridlock.axisvec2axis(params.normal), polarity=gridlock.axisvec2polarity(params.normal))
def __call__(self, shape: List[int]) -> np.ndarray: region_slice = grid_utils.create_region_slices( self._params.edge_coords, self._params.center, self._params.extents) region = np.zeros(shape) region[tuple(region_slice)] = 1 outside_region = np.ones_like(self._region) - self._region lower_random = np.random.uniform(self._params.lower_min, self._params.lower_max, shape) upper_random = np.random.uniform(self._params.upper_min, self._params.upper_max, shape) return self._region * upper_random + outside_region * lower_random
def __call__(self, simspace: SimulationSpace, wlen: float, **kwargs) -> fdfd_tools.VecField: space_inst = simspace(wlen) return fdfd_solvers.waveguide_mode.build_overlap( omega=2 * np.pi / wlen, dxes=simspace.dxes, eps=space_inst.eps_bg.grids, mu=None, mode_num=self._params.mode_num, waveguide_slice=grid_utils.create_region_slices( simspace.edge_coords, self._params.center, self._params.extents), axis=gridlock.axisvec2axis(self._params.normal), polarity=gridlock.axisvec2polarity(self._params.normal), power=self._params.power)
def __call__(self, simspace: SimulationSpace, wlen: float, **kwargs) -> fdfd_tools.VecField: space_inst = simspace(wlen) region_slice = grid_utils.create_region_slices(simspace.edge_coords, self._params.center, self._params.extents) eps = space_inst.eps_bg.grids region = [np.zeros_like(eps[0])] * 3 for i in range(3): region[i][tuple(region_slice)] = np.ones_like( eps[0])[tuple(region_slice)] return np.multiply(region, self._params.power)
def test_plane_power_grad(): space = Simspace( TESTDATA, optplan.SimulationSpace( pml_thickness=[0, 0, 0, 0, 0, 0], mesh=optplan.UniformMesh(dx=40), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[80, 80, 80], ), eps_bg=optplan.GdsEps( gds="straight_waveguide.gds", mat_stack=optplan.GdsMaterialStack( background=optplan.Material(mat_name="air"), stack=[ optplan.GdsMaterialStackLayer( gds_layer=[100, 0], extents=[-80, 80], foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="air"), ), ], ), ), )) wlen = 1550 power_fun = poynting.PowerTransmissionFunction( field=problem.Variable(1), simspace=space, wlen=wlen, plane_slice=grid_utils.create_region_slices(space.edge_coords, [0, 0, 0], [40, 80, 80]), axis=gridlock.axisvec2axis([1, 0, 0]), polarity=gridlock.axisvec2polarity([1, 0, 0])) field = np.arange(np.prod(space.dims) * 3).astype(np.complex128) * 1j grad_actual = power_fun.grad([field], 1) fun = lambda vec: power_fun.eval([vec]) grad_brute = eval_grad_brute_wirt(field, fun) np.testing.assert_array_almost_equal(grad_actual[0], grad_brute, decimal=4)
def __call__(self, simspace: SimulationSpace, wlen: float, solver: Callable, **kwargs) -> fdfd_tools.VecField: """Creates the source vector. Args: simspace: Simulation space. wlen: Wavelength of source. solver: If `normalize_by_source` is `True`, `solver` will be used to run an EM simulation to normalize the source power. Returns: The source. """ space_inst = simspace(wlen) source, _ = fdfd_tools.free_space_sources.build_gaussian_source( omega=2 * np.pi / wlen, eps_grid=space_inst.eps_bg, mu=None, axis=gridlock.axisvec2axis(self._params.normal), polarity=gridlock.axisvec2polarity(self._params.normal), slices=grid_utils.create_region_slices(simspace.edge_coords, self._params.center, self._params.extents), theta=self._params.theta, psi=self._params.psi, polarization_angle=self._params.polarization_angle, w0=self._params.w0, center=self._params.beam_center, power=self._params.power) if self._params.normalize_by_sim: source = fdfd_tools.free_space_sources.normalize_source_by_sim( omega=2 * np.pi / wlen, source=source, eps=space_inst.eps_bg.grids, dxes=simspace.dxes, pml_layers=simspace.pml_layers, solver=solver, power=self._params.power) return source
def __call__( self, simspace: SimulationSpace, wlen: float, **kwargs ) -> Union[fdfd_tools.VecField, Tuple[fdfd_tools.VecField, fdfd_tools.Vec3d]]: """Creates the plane wave source. Args: simspace: Simulation space to use for the source. wlen: Wavelength of source. Returns: If `overwrite_bloch_vector` is `True`, a tuple containing the source field and the Bloch vector corresponding to the plane wave source. Otherwise, only the source field is returned. """ space_inst = simspace(wlen) # Calculate the border in gridpoints and igore the border if it's larger then the simulation. dx = simspace.dx border = [int(b // dx) for b in self._params.border] # The plane wave is assumed to be in the z direction so the border is 0 for z. border.append(0) source, kvector = fdfd_tools.free_space_sources.build_plane_wave_source( omega=2 * np.pi / wlen, eps_grid=space_inst.eps_bg, mu=None, axis=gridlock.axisvec2axis(self._params.normal), polarity=gridlock.axisvec2polarity(self._params.normal), slices=grid_utils.create_region_slices(simspace.edge_coords, self._params.center, self._params.extents), theta=self._params.theta, psi=self._params.psi, polarization_angle=self._params.polarization_angle, border=border, power=self._params.power) if self._params.overwrite_bloch_vector: return source, kvector return source
def __call__(self, simspace: SimulationSpace, wlen: float, **kwargs) -> fdfd_tools.VecField: space_inst = simspace(wlen) region_slice = grid_utils.create_region_slices(simspace.edge_coords, self._params.center, self._params.extents) eps = space_inst.eps_bg.grids eps_min = eps.min() eps_max = eps.max() eps_norm = (eps - eps_min) / (eps_max - eps_min) + 1 overlap = [None] * 3 for i in range(3): overlap[i] = np.zeros_like(eps_norm[0], dtype=complex) overlap_i = overlap[i] eps_i = eps_norm[i] eps_i_slice = eps_i[tuple(region_slice)] overlap_i[tuple(region_slice)] = eps_i_slice overlap[i] = overlap_i return np.multiply(overlap, self._params.power)
def test_stored_energy_grad(): space = Simspace( TESTDATA, optplan.SimulationSpace( pml_thickness=[0, 0, 0, 0, 0, 0], mesh=optplan.UniformMesh(dx=40), sim_region=optplan.Box3d( center=[0, 0, 0], extents=[80, 80, 80], ), eps_bg=optplan.GdsEps( gds="straight_waveguide.gds", mat_stack=optplan.GdsMaterialStack( background=optplan.Material(mat_name="air"), stack=[ optplan.GdsMaterialStackLayer( gds_layer=[100, 0], extents=[-80, 80], foreground=optplan.Material(mat_name="Si"), background=optplan.Material(mat_name="air"), ), ], ), ), )) wlen = 1550 energy_fun = stored_energy.StoredEnergyFunction( input_function=problem.Variable(1), simspace=space, center=[0,0,0], extents=[0,0,0], epsilon=space._eps_bg) plane_slice=grid_utils.create_region_slices(space.edge_coords, [0, 0, 0], [40, 80, 80]), axis=gridlock.axisvec2axis([1, 0, 0]), polarity=gridlock.axisvec2polarity([1, 0, 0]))
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)