def forward_simulation(design_params, mon_type, frequencies=None, mat2=silicon): matgrid = mp.MaterialGrid(mp.Vector3(Nx, Ny), mp.air, mat2, weights=design_params.reshape(Nx, Ny)) matgrid_geometry = [ mp.Block(center=mp.Vector3(), size=mp.Vector3(design_region_size.x, design_region_size.y, 0), material=matgrid) ] geometry = waveguide_geometry + matgrid_geometry sim = mp.Simulation(resolution=resolution, cell_size=cell_size, boundary_layers=pml_xy, sources=wvg_source, geometry=geometry) if not frequencies: frequencies = [fcen] if mon_type.name == 'EIGENMODE': mode = sim.add_mode_monitor( frequencies, mp.ModeRegion(center=mp.Vector3(0.5 * sxy - dpml - 0.1), size=mp.Vector3(0, sxy - 2 * dpml, 0)), yee_grid=True, eig_parity=eig_parity) elif mon_type.name == 'DFT': mode = sim.add_dft_fields([mp.Ez], frequencies, center=mp.Vector3(1.25), size=mp.Vector3(0.25, 1, 0), yee_grid=False) sim.run(until_after_sources=mp.stop_when_dft_decayed()) if mon_type.name == 'EIGENMODE': coeff = sim.get_eigenmode_coefficients(mode, [1], eig_parity).alpha[0, :, 0] S12 = np.power(np.abs(coeff), 2) elif mon_type.name == 'DFT': Ez2 = [] for f in range(len(frequencies)): Ez_dft = sim.get_dft_array(mode, mp.Ez, f) Ez2.append(np.power(np.abs(Ez_dft[4, 10]), 2)) Ez2 = np.array(Ez2) sim.reset_meep() if mon_type.name == 'EIGENMODE': return S12 elif mon_type.name == 'DFT': return Ez2
def _run_adjoint_simulation(self, monitor_values_grad): """Runs adjoint simulation, returning design region fields.""" if not self.design_regions: raise RuntimeError( 'An adjoint simulation was attempted when no design ' 'regions are present.') adjoint_sources = utils.create_adjoint_sources(self.monitors, monitor_values_grad) # TODO refactor with optimization_problem.py # self.simulation.restart_fields() self.simulation.clear_dft_monitors() self.simulation.change_sources(adjoint_sources) # # adj_design_region_monitors = utils.install_design_region_monitors( self.simulation, self.design_regions, self.frequencies, ) self.simulation.init_sim() sim_run_args = { 'until_after_sources' if self.until_after_sources else 'until': mp.stop_when_dft_decayed(self.dft_threshold, self.minimum_run_time, self.maximum_run_time) } self.simulation.run(**sim_run_args) return adj_design_region_monitors
def free_space_radiation(self): sxy = 4 dpml = 1 cell_size = mp.Vector3(sxy + 2 * dpml, sxy + 2 * dpml) pml_layers = [mp.PML(dpml)] fcen = 1 / self.wvl sources = [ mp.Source(src=mp.GaussianSource(fcen, fwidth=0.2 * fcen), center=mp.Vector3(), component=self.src_cmpt) ] if self.src_cmpt == mp.Hz: symmetries = [mp.Mirror(mp.X, phase=-1), mp.Mirror(mp.Y, phase=-1)] elif self.src_cmpt == mp.Ez: symmetries = [mp.Mirror(mp.X, phase=+1), mp.Mirror(mp.Y, phase=+1)] else: symmetries = [] sim = mp.Simulation(cell_size=cell_size, resolution=self.resolution, sources=sources, symmetries=symmetries, boundary_layers=pml_layers, default_material=mp.Medium(index=self.n)) nearfield_box = sim.add_near2far( fcen, 0, 1, mp.Near2FarRegion(center=mp.Vector3(0, +0.5 * sxy), size=mp.Vector3(sxy, 0)), mp.Near2FarRegion(center=mp.Vector3(0, -0.5 * sxy), size=mp.Vector3(sxy, 0), weight=-1), mp.Near2FarRegion(center=mp.Vector3(+0.5 * sxy, 0), size=mp.Vector3(0, sxy)), mp.Near2FarRegion(center=mp.Vector3(-0.5 * sxy, 0), size=mp.Vector3(0, sxy), weight=-1)) sim.run(until_after_sources=mp.stop_when_dft_decayed()) Pr = self.radial_flux(sim, nearfield_box, self.r) return Pr
def adjoint_run(self): # set up adjoint sources and monitors self.prepare_adjoint_run() # flip the m number if utils._check_if_cylindrical(self.sim): self.sim.m = -self.sim.m # flip the k point if self.sim.k_point: self.sim.change_k_point(-1 * self.sim.k_point) self.adjoint_design_region_monitors = [] for ar in range(len(self.objective_functions)): # Reset the fields self.sim.restart_fields() self.sim.clear_dft_monitors() # Update the sources self.sim.change_sources(self.adjoint_sources[ar]) # register design dft fields self.adjoint_design_region_monitors.append( utils.install_design_region_monitors(self.sim, self.design_regions, self.frequencies, self.decimation_factor)) self.sim._evaluate_dft_objects() # Adjoint run self.sim.run(until_after_sources=mp.stop_when_dft_decayed( self.decay_by, self.minimum_run_time, self.maximum_run_time)) # reset the m number if utils._check_if_cylindrical(self.sim): self.sim.m = -self.sim.m # reset the k point if self.sim.k_point: self.sim.k_point *= -1 # update optimizer's state self.current_state = "ADJ"
def _run_fwd_simulation(self, design_variables): """Runs forward simulation, returning monitor values and design region fields.""" utils.validate_and_update_design(self.design_regions, design_variables) self.simulation.reset_meep() self.simulation.change_sources(self.sources) utils.register_monitors(self.monitors, self.frequencies) fwd_design_region_monitors = utils.install_design_region_monitors( self.simulation, self.design_regions, self.frequencies, ) self.simulation.init_sim() sim_run_args = { 'until_after_sources' if self.until_after_sources else 'until': mp.stop_when_dft_decayed(self.dft_threshold, self.minimum_run_time, self.maximum_run_time) } self.simulation.run(**sim_run_args) monitor_values = utils.gather_monitor_values(self.monitors) return (jnp.asarray(monitor_values), fwd_design_region_monitors)
def forward_simulation_complex_fields(design_params, frequencies=None): matgrid = mp.MaterialGrid(mp.Vector3(Nx, Ny), mp.air, silicon, weights=design_params.reshape(Nx, Ny)) geometry = [ mp.Block(center=mp.Vector3(), size=mp.Vector3(design_region_size.x, design_region_size.y, 0), material=matgrid) ] sim = mp.Simulation(resolution=resolution, cell_size=cell_size, k_point=k_point, boundary_layers=pml_x, sources=pt_source, geometry=geometry) if not frequencies: frequencies = [fcen] mode = sim.add_dft_fields([mp.Ez], frequencies, center=mp.Vector3(0.9), size=mp.Vector3(0.2, 0.5), yee_grid=False) sim.run(until_after_sources=mp.stop_when_dft_decayed()) Ez2 = [] for f in range(len(frequencies)): Ez_dft = sim.get_dft_array(mode, mp.Ez, f) Ez2.append(np.power(np.abs(Ez_dft[3, 9]), 2)) Ez2 = np.array(Ez2) sim.reset_meep() return Ez2
def forward_simulation_damping(design_params, frequencies=None, mat2=silicon): matgrid = mp.MaterialGrid(mp.Vector3(Nx, Ny), mp.air, mat2, weights=design_params.reshape(Nx, Ny), damping=3.14 * fcen) matgrid_geometry = [ mp.Block(center=mp.Vector3(), size=mp.Vector3(design_region_size.x, design_region_size.y, 0), material=matgrid) ] geometry = waveguide_geometry + matgrid_geometry sim = mp.Simulation(resolution=resolution, cell_size=cell_size, boundary_layers=pml_xy, sources=wvg_source, geometry=geometry) if not frequencies: frequencies = [fcen] mode = sim.add_mode_monitor(frequencies, mp.ModeRegion( center=mp.Vector3(0.5 * sxy - dpml - 0.1), size=mp.Vector3(0, sxy - 2 * dpml, 0)), yee_grid=True, eig_parity=eig_parity) sim.run(until_after_sources=mp.stop_when_dft_decayed()) coeff = sim.get_eigenmode_coefficients(mode, [1], eig_parity).alpha[0, :, 0] S12 = np.power(np.abs(coeff), 2) sim.reset_meep() return S12
def forward_run(self): # set up monitors self.prepare_forward_run() # Forward run self.sim.run(until_after_sources=mp.stop_when_dft_decayed( self.decay_by, self.minimum_run_time, self.maximum_run_time)) # record objective quantities from user specified monitors self.results_list = [] for m in self.objective_arguments: self.results_list.append(m()) # evaluate objectives self.f0 = [fi(*self.results_list) for fi in self.objective_functions] if len(self.f0) == 1: self.f0 = self.f0[0] # store objective function evaluation in memory self.f_bank.append(self.f0) # update solver's current state self.current_state = "FWD"
def pec_ground_plane_radiation(src_cmpt=mp.Hz): L = 8.0 # length of non-PML region dpml = 1.0 # thickness of PML sxy = dpml + L + dpml cell_size = mp.Vector3(sxy, sxy, 0) boundary_layers = [mp.PML(dpml)] fcen = 1 / wvl # The near-to-far field transformation feature only supports # homogeneous media which means it cannot explicitly take into # account the ground plane. As a workaround, we use two antennas # of opposite sign surrounded by a single near2far box which # encloses both antennas. We then use an odd mirror symmetry to # divide the computational cell in half which is effectively # equivalent to a PEC boundary condition on one side. # Note: This setup means that the radiation pattern can only # be measured in the top half above the dipole. sources = [ mp.Source(src=mp.GaussianSource(fcen, fwidth=0.2 * fcen), component=src_cmpt, center=mp.Vector3(0, +h)), mp.Source(src=mp.GaussianSource(fcen, fwidth=0.2 * fcen), component=src_cmpt, center=mp.Vector3(0, -h), amplitude=-1 if src_cmpt == mp.Ez else +1) ] symmetries = [ mp.Mirror(direction=mp.X, phase=+1 if src_cmpt == mp.Ez else -1), mp.Mirror(direction=mp.Y, phase=-1 if src_cmpt == mp.Ez else +1) ] sim = mp.Simulation(resolution=resolution, cell_size=cell_size, boundary_layers=boundary_layers, sources=sources, symmetries=symmetries, default_material=mp.Medium(index=n)) nearfield_box = sim.add_near2far( fcen, 0, 1, mp.Near2FarRegion(center=mp.Vector3(0, 2 * h), size=mp.Vector3(2 * h, 0), weight=+1), mp.Near2FarRegion(center=mp.Vector3(0, -2 * h), size=mp.Vector3(2 * h, 0), weight=-1), mp.Near2FarRegion(center=mp.Vector3(h, 0), size=mp.Vector3(0, 4 * h), weight=+1), mp.Near2FarRegion(center=mp.Vector3(-h, 0), size=mp.Vector3(0, 4 * h), weight=-1)) sim.plot2D() plt.savefig('antenna_pec_ground_plane.png', bbox_inches='tight') sim.run(until_after_sources=mp.stop_when_dft_decayed()) Pr = radial_flux(sim, nearfield_box, r) return Pr
size=mp.Vector3(sx - 2 * dpml)), mp.Near2FarRegion(mp.Vector3(-0.5 * sx + dpml, 0.5 * w + 0.5 * d1), size=mp.Vector3(0, d1), weight=-1.0), mp.Near2FarRegion(mp.Vector3(0.5 * sx - dpml, 0.5 * w + 0.5 * d1), size=mp.Vector3(0, d1)), ) mon = sim.add_dft_fields([mp.Hz], fcen, 0, 1, center=mp.Vector3(0, 0.5 * w + d1 + d2), size=mp.Vector3(sx - 2 * (dpad + dpml), 0)) sim.run(until_after_sources=mp.stop_when_dft_decayed()) sim.plot2D() if mp.am_master(): plt.savefig(f'cavity_farfield_plot2D_dpad{dpad}_{d1}_{d2}.png', bbox_inches='tight', dpi=150) Hz_mon = sim.get_dft_array(mon, mp.Hz, 0) (x, y, z, w) = sim.get_array_metadata(dft_cell=mon) ff = [] for xc in x: ff_pt = sim.get_farfield(nearfield, mp.Vector3(xc, y[0])) ff.append(ff_pt[5])
def test_binary_grating_oblique(self, theta): # rotation angle of incident planewave # counterclockwise (CCW) about Z axis, 0 degrees along +X axis theta_in = math.radians(theta) # k (in source medium) with correct length (plane of incidence: XY) k = mp.Vector3(self.fcen * self.ng).rotate(mp.Vector3(0, 0, 1), theta_in) symmetries = [] eig_parity = mp.ODD_Z if theta_in == 0: symmetries = [mp.Mirror(mp.Y)] eig_parity += mp.EVEN_Y def pw_amp(k, x0): def _pw_amp(x): return cmath.exp(1j * 2 * math.pi * k.dot(x + x0)) return _pw_amp src_pt = mp.Vector3(-0.5 * self.sx + self.dpml, 0, 0) sources = [ mp.Source( mp.GaussianSource(self.fcen, fwidth=self.df), component=mp.Ez, # S polarization center=src_pt, size=mp.Vector3(0, self.sy, 0), amp_func=pw_amp(k, src_pt)) ] sim = mp.Simulation(resolution=self.resolution, cell_size=self.cell_size, boundary_layers=self.abs_layers, k_point=k, default_material=self.glass, sources=sources, symmetries=symmetries) refl_pt = mp.Vector3(-0.5 * self.sx + self.dpml + 0.5 * self.dsub, 0, 0) refl_flux = sim.add_flux( self.fcen, 0, 1, mp.FluxRegion(center=refl_pt, size=mp.Vector3(0, self.sy, 0))) sim.run(until_after_sources=mp.stop_when_dft_decayed()) input_flux = mp.get_fluxes(refl_flux) input_flux_data = sim.get_flux_data(refl_flux) sim.reset_meep() sim = mp.Simulation(resolution=self.resolution, cell_size=self.cell_size, boundary_layers=self.abs_layers, geometry=self.geometry, k_point=k, sources=sources, symmetries=symmetries) refl_flux = sim.add_flux( self.fcen, 0, 1, mp.FluxRegion(center=refl_pt, size=mp.Vector3(0, self.sy, 0))) sim.load_minus_flux_data(refl_flux, input_flux_data) tran_pt = mp.Vector3(0.5 * self.sx - self.dpml - 0.5 * self.dpad, 0, 0) tran_flux = sim.add_flux( self.fcen, 0, 1, mp.FluxRegion(center=tran_pt, size=mp.Vector3(0, self.sy, 0))) sim.run(until_after_sources=mp.stop_when_dft_decayed()) # number of reflected orders nm_r = np.floor((self.fcen * self.ng - k.y) * self.gp) - np.ceil( (-self.fcen * self.ng - k.y) * self.gp) if theta_in == 0: nm_r = nm_r / 2 # since eig_parity removes degeneracy in y-direction nm_r = int(nm_r) res = sim.get_eigenmode_coefficients(refl_flux, range(1, nm_r + 1), eig_parity=eig_parity) r_coeffs = res.alpha Rsum = 0 for nm in range(nm_r): Rsum += abs(r_coeffs[nm, 0, 1])**2 / input_flux[0] # number of transmitted orders nm_t = np.floor((self.fcen - k.y) * self.gp) - np.ceil( (-self.fcen - k.y) * self.gp) if theta_in == 0: nm_t = nm_t / 2 # since eig_parity removes degeneracy in y-direction nm_t = int(nm_t) res = sim.get_eigenmode_coefficients(tran_flux, range(1, nm_t + 1), eig_parity=eig_parity) t_coeffs = res.alpha Tsum = 0 for nm in range(nm_t): Tsum += abs(t_coeffs[nm, 0, 0])**2 / input_flux[0] r_flux = mp.get_fluxes(refl_flux) t_flux = mp.get_fluxes(tran_flux) Rflux = -r_flux[0] / input_flux[0] Tflux = t_flux[0] / input_flux[0] print("refl:, {}, {}".format(Rsum, Rflux)) print("tran:, {}, {}".format(Tsum, Tflux)) print("sum:, {}, {}".format(Rsum + Tsum, Rflux + Tflux)) self.assertAlmostEqual(Rsum, Rflux, places=2) self.assertAlmostEqual(Tsum, Tflux, places=2) self.assertAlmostEqual(Rsum + Tsum, 1.00, places=2)
def test_binary_grating_special_kz(self, theta, kz_2d): # rotation angle of incident planewave # counterclockwise (CCW) about Y axis, 0 degrees along +X axis theta_in = math.radians(theta) # k (in source medium) with correct length (plane of incidence: XZ) k = mp.Vector3(self.fcen * self.ng).rotate(mp.Vector3(0, 1, 0), theta_in) symmetries = [mp.Mirror(mp.Y)] def pw_amp(k, x0): def _pw_amp(x): return cmath.exp(1j * 2 * math.pi * k.dot(x + x0)) return _pw_amp src_pt = mp.Vector3(-0.5 * self.sx + self.dpml, 0, 0) sources = [ mp.Source(mp.GaussianSource(self.fcen, fwidth=self.df), component=mp.Ez, center=src_pt, size=mp.Vector3(0, self.sy, 0), amp_func=pw_amp(k, src_pt)) ] sim = mp.Simulation(resolution=self.resolution, cell_size=self.cell_size, boundary_layers=self.abs_layers, k_point=k, default_material=self.glass, sources=sources, symmetries=symmetries, kz_2d=kz_2d) refl_pt = mp.Vector3(-0.5 * self.sx + self.dpml + 0.5 * self.dsub, 0, 0) refl_flux = sim.add_mode_monitor( self.fcen, 0, 1, mp.FluxRegion(center=refl_pt, size=mp.Vector3(0, self.sy, 0))) sim.run(until_after_sources=mp.stop_when_dft_decayed()) input_flux = mp.get_fluxes(refl_flux) input_flux_data = sim.get_flux_data(refl_flux) sim.reset_meep() sim = mp.Simulation(resolution=self.resolution, cell_size=self.cell_size, boundary_layers=self.abs_layers, geometry=self.geometry, k_point=k, sources=sources, symmetries=symmetries, kz_2d=kz_2d) refl_flux = sim.add_mode_monitor( self.fcen, 0, 1, mp.FluxRegion(center=refl_pt, size=mp.Vector3(0, self.sy, 0))) sim.load_minus_flux_data(refl_flux, input_flux_data) tran_pt = mp.Vector3(0.5 * self.sx - self.dpml - 0.5 * self.dpad, 0, 0) tran_flux = sim.add_mode_monitor( self.fcen, 0, 1, mp.FluxRegion(center=tran_pt, size=mp.Vector3(0, self.sy, 0))) sim.run(until_after_sources=mp.stop_when_dft_decayed()) # number of reflected orders nm_r = ( np.ceil( (np.sqrt((self.fcen * self.ng)**2 - k.z**2) - k.y) * self.gp) - np.floor( (-np.sqrt((self.fcen * self.ng)**2 - k.z**2) - k.y) * self.gp)) nm_r = int(nm_r / 2) Rsum = 0 for nm in range(nm_r): for S_pol in [False, True]: res = sim.get_eigenmode_coefficients( refl_flux, mp.DiffractedPlanewave([0, nm, 0], mp.Vector3(1, 0, 0), 1 if S_pol else 0, 0 if S_pol else 1)) r_coeffs = res.alpha Rmode = abs(r_coeffs[0, 0, 1])**2 / input_flux[0] print("refl-order:, {}, {}, {}".format("s" if S_pol else "p", nm, Rmode)) Rsum += Rmode if nm == 0 else 2 * Rmode # number of transmitted orders nm_t = (np.ceil( (np.sqrt(self.fcen**2 - k.z**2) - k.y) * self.gp) - np.floor( (-np.sqrt(self.fcen**2 - k.z**2) - k.y) * self.gp)) nm_t = int(nm_t / 2) Tsum = 0 for nm in range(nm_t): for S_pol in [False, True]: res = sim.get_eigenmode_coefficients( tran_flux, mp.DiffractedPlanewave([0, nm, 0], mp.Vector3(1, 0, 0), 1 if S_pol else 0, 0 if S_pol else 1)) t_coeffs = res.alpha Tmode = abs(t_coeffs[0, 0, 0])**2 / input_flux[0] print("tran-order:, {}, {}, {}".format("s" if S_pol else "p", nm, Tmode)) Tsum += Tmode if nm == 0 else 2 * Tmode r_flux = mp.get_fluxes(refl_flux) t_flux = mp.get_fluxes(tran_flux) Rflux = -r_flux[0] / input_flux[0] Tflux = t_flux[0] / input_flux[0] print("refl:, {}, {}".format(Rsum, Rflux)) print("tran:, {}, {}".format(Tsum, Tflux)) print("sum:, {}, {}".format(Rsum + Tsum, Rflux + Tflux)) self.assertAlmostEqual(Rsum, Rflux, places=2) self.assertAlmostEqual(Tsum, Tflux, places=2) self.assertAlmostEqual(Rsum + Tsum, 1.00, places=2)
def calculate_fd_gradient( self, num_gradients=1, db=1e-4, design_variables_idx=0, filter=None, ): ''' Estimate central difference gradients. Parameters ---------- num_gradients ... : scalar number of gradients to estimate. Randomly sampled from parameters. db ... : scalar finite difference step size design_variables_idx ... : scalar which design region to pull design variables from Returns ----------- fd_gradient ... : lists [number of objective functions][number of gradients] ''' if filter is None: filter = lambda x: x if num_gradients < self.num_design_params[design_variables_idx]: # randomly choose indices to loop estimate fd_gradient_idx = np.random.choice( self.num_design_params[design_variables_idx], num_gradients, replace=False, ) elif num_gradients == self.num_design_params[design_variables_idx]: fd_gradient_idx = range( self.num_design_params[design_variables_idx]) else: raise ValueError( "The requested number of gradients must be less than or equal to the total number of design parameters." ) assert db < 0.2, "The step size of finite difference is too large." # cleanup simulation object self.sim.reset_meep() self.sim.change_sources(self.forward_sources) # preallocate result vector fd_gradient = [] for k in fd_gradient_idx: b0 = np.ones((self.num_design_params[design_variables_idx], )) b0[:] = (self.design_regions[design_variables_idx]. design_parameters.weights) # -------------------------------------------- # # left function evaluation # -------------------------------------------- # self.sim.reset_meep() # assign new design vector in_interior = True # b0[k] is not too close to the boundaries 0 and 1 if b0[k] < db or b0[k] + db > 1: in_interior = False # b0[k] is too close to 0 or 1 if b0[k] >= db: b0[k] -= db self.design_regions[design_variables_idx].update_design_parameters( b0) # initialize design monitors self.forward_monitors = [] for m in self.objective_arguments: self.forward_monitors.append( m.register_monitors(self.frequencies)) self.sim.run(until_after_sources=mp.stop_when_dft_decayed( self.decay_by, self.minimum_run_time, self.maximum_run_time)) # record final objective function value results_list = [] for m in self.objective_arguments: results_list.append(m()) fm = [fi(*results_list) for fi in self.objective_functions] # -------------------------------------------- # # right function evaluation # -------------------------------------------- # self.sim.reset_meep() # assign new design vector if in_interior: b0[k] += 2 * db # central difference rule... else: b0[k] += db # forward or backward difference... self.design_regions[design_variables_idx].update_design_parameters( b0) # initialize design monitors self.forward_monitors = [] for m in self.objective_arguments: self.forward_monitors.append( m.register_monitors(self.frequencies)) # add monitor used to track dft convergence self.sim.run(until_after_sources=mp.stop_when_dft_decayed( self.decay_by, self.minimum_run_time, self.maximum_run_time)) # record final objective function value results_list = [] for m in self.objective_arguments: results_list.append(m()) fp = [fi(*results_list) for fi in self.objective_functions] # -------------------------------------------- # # estimate derivative # -------------------------------------------- # fd_gradient.append([ np.squeeze((fp[fi] - fm[fi]) / db / (2 if in_interior else 1)) for fi in range(len(self.objective_functions)) ]) # Cleanup singleton dimensions if len(fd_gradient) == 1: fd_gradient = fd_gradient[0] return fd_gradient, fd_gradient_idx
def compute_transmittance(matgrid_symmetry=False): resolution = 25 cell_size = mp.Vector3(6, 6, 0) boundary_layers = [mp.PML(thickness=1.0)] matgrid_size = mp.Vector3(2, 2, 0) matgrid_resolution = 2 * resolution Nx, Ny = int(matgrid_size.x * matgrid_resolution), int(matgrid_size.y * matgrid_resolution) # ensure reproducible results rng = np.random.RandomState(2069588) w = rng.rand(Nx, Ny) weights = 0.5 * (w + np.fliplr(w)) if not matgrid_symmetry else w matgrid = mp.MaterialGrid(mp.Vector3(Nx, Ny), mp.air, mp.Medium(index=3.5), weights=weights, do_averaging=False, grid_type='U_MEAN') geometry = [ mp.Block(center=mp.Vector3(), size=mp.Vector3(mp.inf, 1.0, mp.inf), material=mp.Medium(index=3.5)), mp.Block(center=mp.Vector3(), size=mp.Vector3(matgrid_size.x, matgrid_size.y, 0), material=matgrid) ] if matgrid_symmetry: geometry.append( mp.Block(center=mp.Vector3(), size=mp.Vector3(matgrid_size.x, matgrid_size.y, 0), material=matgrid, e2=mp.Vector3(y=-1))) eig_parity = mp.ODD_Y + mp.EVEN_Z fcen = 0.65 df = 0.2 * fcen sources = [ mp.EigenModeSource(src=mp.GaussianSource(fcen, fwidth=df), center=mp.Vector3(-2.0, 0), size=mp.Vector3(0, 4.0), eig_parity=eig_parity) ] sim = mp.Simulation(resolution=resolution, cell_size=cell_size, boundary_layers=boundary_layers, sources=sources, geometry=geometry) mode_mon = sim.add_flux( fcen, 0, 1, mp.FluxRegion(center=mp.Vector3(2.0, 0), size=mp.Vector3(0, 4.0))) sim.run(until_after_sources=mp.stop_when_dft_decayed()) mode_coeff = sim.get_eigenmode_coefficients(mode_mon, [1], eig_parity).alpha[0, :, 0][0] tran = np.power(np.abs(mode_coeff), 2) print('tran:, {}, {}'.format("sym" if matgrid_symmetry else "nosym", tran)) return tran