def add_pins_container( component: Component, add_pins_function=add_pins_triangle, ) -> Component: c = Component() ref = c << component add_pins_function(component=c, reference=ref) c.ref = ref return c
def sparameter_calculation( n, component: Component, port_symmetries: Optional[PortSymmetries] = port_symmetries, monitor_indices: Tuple = monitor_indices, wl_min: float = wl_min, wl_max: float = wl_max, wl_steps: int = wl_steps, dirpath: Path = dirpath, animate: bool = animate, dispersive: bool = dispersive, **settings, ) -> Dict: sim_dict = get_simulation( component=component, port_source_name=f"o{monitor_indices[n]}", resolution=resolution, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, dispersive=dispersive, **settings, ) sim = sim_dict["sim"] monitors = sim_dict["monitors"] # freqs = sim_dict["freqs"] # wavelengths = 1 / freqs # print(sim.resolution) # Make termination when field decayed enough across ALL monitors termination = [] for monitor_name in monitors: termination.append( mp.stop_when_fields_decayed( dt=50, c=mp.Ez, pt=monitors[monitor_name].regions[0].center, decay_by=1e-9, )) if animate: sim.use_output_directory() animate = mp.Animate2D( sim, fields=mp.Ez, realtime=True, field_parameters={ "alpha": 0.8, "cmap": "RdBu", "interpolation": "none", }, eps_parameters={"contour": True}, normalize=True, ) sim.run(mp.at_every(1, animate), until_after_sources=termination) animate.to_mp4(30, monitor_indices[n] + ".mp4") else: sim.run(until_after_sources=termination) # call this function every 50 time spes # look at simulation and measure Ez component # when field_monitor_point decays below a certain 1e-9 field threshold # Calculate mode overlaps # Get source monitor results component_ref = component.ref() source_entering, source_exiting = parse_port_eigenmode_coeff( monitor_indices[n], component_ref.ports, sim_dict) # Get coefficients for monitor_index in monitor_indices: j = monitor_indices[n] i = monitor_index if monitor_index == monitor_indices[n]: sii = source_exiting / source_entering siia = np.unwrap(np.angle(sii)) siim = np.abs(sii) sp[f"s{i}{i}a"] = siia sp[f"s{i}{i}m"] = siim else: monitor_entering, monitor_exiting = parse_port_eigenmode_coeff( monitor_index, component_ref.ports, sim_dict) sij = monitor_exiting / source_entering sija = np.unwrap(np.angle(sij)) sijm = np.abs(sij) sp[f"s{i}{j}a"] = sija sp[f"s{i}{j}m"] = sijm sij = monitor_entering / source_entering sija = np.unwrap(np.angle(sij)) sijm = np.abs(sij) if bool(port_symmetries) is True: for key in port_symmetries[f"o{monitor_indices[n]}"].keys(): values = port_symmetries[f"o{monitor_indices[n]}"][key] for value in values: sp[f"{value}m"] = sp[f"{key}m"] sp[f"{value}a"] = sp[f"{key}a"] return sp
def write_sparameters_meep( component: Component, port_symmetries: Optional[PortSymmetries] = None, resolution: int = 20, wl_min: float = 1.5, wl_max: float = 1.6, wl_steps: int = 50, dirpath: Path = sparameters_path, layer_stack: LayerStack = LAYER_STACK, port_margin: float = 2, port_monitor_offset: float = -0.1, port_source_offset: float = -0.1, filepath: Optional[Path] = None, overwrite: bool = False, animate: bool = False, lazy_parallelism: bool = False, run: bool = True, dispersive: bool = False, xmargin: float = 0, ymargin: float = 0, xmargin_left: float = 0, xmargin_right: float = 0, ymargin_top: float = 0, ymargin_bot: float = 0, **settings, ) -> pd.DataFrame: r"""Compute Sparameters and writes them to a CSV filepath. Simulates each time using a different input port (by default, all of them) unless you specify port_symmetries: port_symmetries = {"o1": { "s11": ["s22","s33","s44"], "s21": ["s21","s34","s43"], "s31": ["s13","s24","s42"], "s41": ["s14","s23","s32"], } } - Only simulations using the outer key port names will be run - The associated value is another dict whose keys are the S-parameters computed when this source is active - The values of this inner Dict are lists of s-parameters whose values are copied This allows you doing less simulations TODO: automate this for common component types (geometrical symmetries, reciprocal materials, etc.) TODO: enable other port naming conventions, such as (in0, in1, out0, out1) .. code:: top view ________________________________ | | | xmargin_left | port_extension |<------> port_margin ||<--> ___|___________ _________||___ | \ / | | \ / | | ====== | | / \ | ___|___________/ \__________|___ | | <-------->| | |ymargin_bot xmargin_right| | | | |___|___________________________| side view ________________________________ | | | | | | | zmargin_top | |ymargin | | |<---> _____ _|___ | | | | | | | | | | | | | | |_____| |_____| | | | | | | | | |zmargin_bot | | | | |_______|_______________________| Args: component: to simulate. resolution: in pixels/um (20: for coarse, 120: for fine) port_symmetries: Dict to specify port symmetries, to save number of simulations source_ports: list of port string names to use as sources dirpath: directory to store Sparameters layer_stack: LayerStack class port_margin: margin on each side of the port port_monitor_offset: offset between monitor Component port and monitor MEEP port port_source_offset: offset between source Component port and source MEEP port filepath: to store pandas Dataframe with Sparameters in CSV format. Defaults to dirpath/component_.csv overwrite: overwrites stored simulation results. animate: saves a MP4 images of the simulation for inspection, and also outputs during computation. The name of the file is the source index lazy_parallelism: toggles the flag "meep.divide_parallel_processes" to perform the simulations with different sources in parallel run: runs simulation, if False, only plots simulation dispersive: use dispersive models for materials (requires higher resolution) xmargin: left and right distance from component to PML. xmargin_left: west distance from component to PML. xmargin_right: east distance from component to PML. ymargin: top and bottom distance from component to PML. ymargin_top: north distance from component to PML. ymargin_bot: south distance from component to PML. keyword Args: extend_ports_length: to extend ports beyond the PML (um). zmargin_top: thickness for cladding above core (um). zmargin_bot: thickness for cladding below core (um) tpml: PML thickness (um). clad_material: material for cladding. is_3d: if True runs in 3D wl_min: wavelength min (um). wl_max: wavelength max (um). wl_steps: wavelength steps dfcen: delta frequency port_source_name: input port name port_field_monitor_name: port_margin: margin on each side of the port (um). distance_source_to_monitors: in (um). port_source_offset: offset between source Component port and source MEEP port port_monitor_offset: offset between monitor Component port and monitor MEEP port Returns: sparameters in a pandas Dataframe (wavelengths, s11a, s12m, ...) where `a` is the angle in radians and `m` the module """ port_symmetries = port_symmetries or {} xmargin_left = xmargin_left or xmargin xmargin_right = xmargin_right or xmargin ymargin_top = ymargin_top or ymargin ymargin_bot = ymargin_bot or ymargin sim_settings = dict( resolution=resolution, port_symmetries=port_symmetries, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, dispersive=dispersive, ymargin_top=ymargin_top, ymargin_bot=ymargin_bot, xmargin_left=xmargin_left, xmargin_right=xmargin_right, **settings, ) filepath = filepath or get_sparameters_path( component=component, dirpath=dirpath, layer_stack=layer_stack, **sim_settings, ) sim_settings = sim_settings.copy() sim_settings["layer_stack"] = layer_stack.to_dict() sim_settings["component"] = component.to_dict() filepath = pathlib.Path(filepath) filepath_sim_settings = filepath.with_suffix(".yml") # filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings)) # logger.info(f"Write simulation settings to {filepath_sim_settings!r}") # return filepath_sim_settings component = gf.add_padding_container( component, default=0, top=ymargin_top, bottom=ymargin_bot, left=xmargin_left, right=xmargin_right, ) if not run: sim_dict = get_simulation( component=component, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, layer_stack=layer_stack, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, **settings, ) sim_dict["sim"].plot2D(plot_eps_flag=True) plt.show() return if filepath.exists() and not overwrite: logger.info(f"Simulation loaded from {filepath!r}") return pd.read_csv(filepath) # Parse ports (default) monitor_indices = [] source_indices = [] component_ref = component.ref() for port_name in component_ref.ports.keys(): if component_ref.ports[port_name].port_type == "optical": monitor_indices.append(re.findall("[0-9]+", port_name)[0]) if bool(port_symmetries): # user-specified for port_name in port_symmetries.keys(): source_indices.append(re.findall("[0-9]+", port_name)[0]) else: # otherwise cycle through all source_indices = monitor_indices # Create S-parameter storage object sp = {} @pydantic.validate_arguments def sparameter_calculation( n, component: Component, port_symmetries: Optional[PortSymmetries] = port_symmetries, monitor_indices: Tuple = monitor_indices, wl_min: float = wl_min, wl_max: float = wl_max, wl_steps: int = wl_steps, dirpath: Path = dirpath, animate: bool = animate, dispersive: bool = dispersive, **settings, ) -> Dict: sim_dict = get_simulation( component=component, port_source_name=f"o{monitor_indices[n]}", resolution=resolution, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, port_margin=port_margin, port_monitor_offset=port_monitor_offset, port_source_offset=port_source_offset, dispersive=dispersive, **settings, ) sim = sim_dict["sim"] monitors = sim_dict["monitors"] # freqs = sim_dict["freqs"] # wavelengths = 1 / freqs # print(sim.resolution) # Make termination when field decayed enough across ALL monitors termination = [] for monitor_name in monitors: termination.append( mp.stop_when_fields_decayed( dt=50, c=mp.Ez, pt=monitors[monitor_name].regions[0].center, decay_by=1e-9, )) if animate: sim.use_output_directory() animate = mp.Animate2D( sim, fields=mp.Ez, realtime=True, field_parameters={ "alpha": 0.8, "cmap": "RdBu", "interpolation": "none", }, eps_parameters={"contour": True}, normalize=True, ) sim.run(mp.at_every(1, animate), until_after_sources=termination) animate.to_mp4(30, monitor_indices[n] + ".mp4") else: sim.run(until_after_sources=termination) # call this function every 50 time spes # look at simulation and measure Ez component # when field_monitor_point decays below a certain 1e-9 field threshold # Calculate mode overlaps # Get source monitor results component_ref = component.ref() source_entering, source_exiting = parse_port_eigenmode_coeff( monitor_indices[n], component_ref.ports, sim_dict) # Get coefficients for monitor_index in monitor_indices: j = monitor_indices[n] i = monitor_index if monitor_index == monitor_indices[n]: sii = source_exiting / source_entering siia = np.unwrap(np.angle(sii)) siim = np.abs(sii) sp[f"s{i}{i}a"] = siia sp[f"s{i}{i}m"] = siim else: monitor_entering, monitor_exiting = parse_port_eigenmode_coeff( monitor_index, component_ref.ports, sim_dict) sij = monitor_exiting / source_entering sija = np.unwrap(np.angle(sij)) sijm = np.abs(sij) sp[f"s{i}{j}a"] = sija sp[f"s{i}{j}m"] = sijm sij = monitor_entering / source_entering sija = np.unwrap(np.angle(sij)) sijm = np.abs(sij) if bool(port_symmetries) is True: for key in port_symmetries[f"o{monitor_indices[n]}"].keys(): values = port_symmetries[f"o{monitor_indices[n]}"][key] for value in values: sp[f"{value}m"] = sp[f"{key}m"] sp[f"{value}a"] = sp[f"{key}a"] return sp # Since source is defined upon sim object instanciation, loop here # for port_index in monitor_indices: num_sims = len(port_symmetries.keys()) or len(source_indices) if lazy_parallelism: from mpi4py import MPI cores = min([num_sims, multiprocessing.cpu_count()]) n = mp.divide_parallel_processes(cores) comm = MPI.COMM_WORLD size = comm.Get_size() rank = comm.Get_rank() sp = sparameter_calculation( n, component=component, port_symmetries=port_symmetries, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, animate=animate, monitor_indices=monitor_indices, **settings, ) # Synchronize dicts if rank == 0: for i in range(1, size, 1): data = comm.recv(source=i, tag=11) sp.update(data) df = pd.DataFrame(sp) df["wavelengths"] = np.linspace(wl_min, wl_max, wl_steps) df["freqs"] = 1 / df["wavelengths"] df.to_csv(filepath, index=False) logger.info(f"Write simulation results to {filepath!r}") filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings)) logger.info( f"Write simulation settings to {filepath_sim_settings!r}") return df else: comm.send(sp, dest=0, tag=11) else: for n in tqdm(range(num_sims)): sp.update( sparameter_calculation( n, component=component, port_symmetries=port_symmetries, wl_min=wl_min, wl_max=wl_max, wl_steps=wl_steps, animate=animate, monitor_indices=monitor_indices, **settings, )) df = pd.DataFrame(sp) df["wavelengths"] = np.linspace(wl_min, wl_max, wl_steps) df["freqs"] = 1 / df["wavelengths"] df.to_csv(filepath, index=False) logger.info(f"Write simulation results to {filepath!r}") filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings)) logger.info(f"Write simulation settings to {filepath_sim_settings!r}") return df
def get_simulation( component: Component, extend_ports_length: Optional[float] = 4.0, layer_stack: LayerStack = LAYER_STACK, res: int = 20, t_clad_top: float = 1.0, t_clad_bot: float = 1.0, tpml: float = 1.0, clad_material: str = "SiO2", is_3d: bool = False, wl_min: float = 1.5, wl_max: float = 1.6, wl_steps: int = 50, dfcen: float = 0.2, port_source_name: str = 1, port_field_monitor_name: str = 2, port_margin: float = 0.5, distance_source_to_monitors: float = 0.2, ) -> Dict[str, Any]: """Returns Simulation dict from gdsfactory.component based on meep directional coupler example https://meep.readthedocs.io/en/latest/Python_Tutorials/GDSII_Import/ https://support.lumerical.com/hc/en-us/articles/360042095873-Metamaterial-S-parameter-extraction Args: component: gf.Component extend_ports_function: function to extend the ports for a component to ensure it goes beyond the PML layer_to_thickness: Dict of layer number (int, int) to thickness (um) res: resolution (pixels/um) For example: (10: 100nm step size) t_clad_top: thickness for cladding above core t_clad_bot: thickness for cladding below core tpml: PML thickness (um) clad_material: material for cladding is_3d: if True runs in 3D wavelengths: iterable of wavelengths to simulate dfcen: delta frequency sidewall_angle: in degrees port_source_name: input port name port_field_monitor_name: port_margin: margin on each side of the port distance_source_to_monitors: in (um) source goes before Returns: sim: simulation object Make sure you visualize the simulation region with gf.before you simulate a component .. code:: import gdsfactory as gf import gmeep as gm c = gf.components.bend_circular() margin = 2 cm = gm.add_monitors(c) gf.show(cm) """ layer_to_thickness = layer_stack.get_layer_to_thickness() layer_to_material = layer_stack.get_layer_to_material() layer_to_zmin = layer_stack.get_layer_to_zmin() layer_to_sidewall_angle = layer_stack.get_layer_to_sidewall_angle() wavelengths = np.linspace(wl_min, wl_max, wl_steps) if port_source_name not in component.ports: warnings.warn( f"port_source_name={port_source_name} not in {component.ports.keys()}" ) port_source = component.get_ports_list()[0] port_source_name = port_source.name warnings.warn(f"Selecting port_source_name={port_source_name} instead.") if port_field_monitor_name not in component.ports: warnings.warn( f"port_field_monitor_name={port_field_monitor_name} not in {component.ports.keys()}" ) port_field_monitor = ( component.get_ports_list()[0] if len(component.ports) < 2 else component.get_ports_list()[1] ) port_field_monitor_name = port_field_monitor.name warnings.warn( f"Selecting port_field_monitor_name={port_field_monitor_name} instead." ) assert isinstance( component, Component ), f"component needs to be a gf.Component, got Type {type(component)}" component_extended = ( gf.components.extension.extend_ports( component=component, length=extend_ports_length, centered=True ) if extend_ports_length else component ) component = component.ref() component.x = 0 component.y = 0 gf.show(component_extended) component_extended.flatten() component_extended = component_extended.ref() # geometry_center = [component_extended.x, component_extended.y] # geometry_center = [0, 0] # print(geometry_center) layers_thickness = [ layer_to_thickness[layer] for layer in component.get_layers() if layer in layer_to_thickness ] t_core = max(layers_thickness) cell_thickness = tpml + t_clad_bot + t_core + t_clad_top + tpml if is_3d else 0 cell_size = mp.Vector3( component.xsize + 2 * tpml, component.ysize + 2 * tpml, cell_thickness, ) geometry = [] layer_to_polygons = component_extended.get_polygons(by_spec=True) for layer, polygons in layer_to_polygons.items(): if layer in layer_to_thickness and layer in layer_to_material: height = layer_to_thickness[layer] if is_3d else mp.inf zmin_um = layer_to_zmin[layer] if is_3d else 0 # center = mp.Vector3(0, 0, (zmin_um + height) / 2) for polygon in polygons: vertices = [mp.Vector3(p[0], p[1], zmin_um) for p in polygon] material_name = layer_to_material[layer] material = get_material(name=material_name) geometry.append( mp.Prism( vertices=vertices, height=height, sidewall_angle=layer_to_sidewall_angle[layer], material=material, # center=center ) ) freqs = 1 / wavelengths fcen = np.mean(freqs) frequency_width = dfcen * fcen # Add source port = component.ports[port_source_name] angle = port.orientation width = port.width + 2 * port_margin size_x = width * abs(np.sin(angle * np.pi / 180)) size_y = width * abs(np.cos(angle * np.pi / 180)) size_x = 0 if size_x < 0.001 else size_x size_y = 0 if size_y < 0.001 else size_y size_z = cell_thickness - 2 * tpml if is_3d else 20 size = [size_x, size_y, size_z] center = port.center.tolist() + [0] # (x, y, z=0) field_monitor_port = component.ports[port_field_monitor_name] field_monitor_point = field_monitor_port.center.tolist() + [0] # (x, y, z=0) sources = [ mp.EigenModeSource( src=mp.GaussianSource(fcen, fwidth=frequency_width), size=size, center=center, eig_band=1, eig_parity=mp.NO_PARITY if is_3d else mp.EVEN_Y + mp.ODD_Z, eig_match_freq=True, ) ] sim = mp.Simulation( resolution=res, cell_size=cell_size, boundary_layers=[mp.PML(tpml)], sources=sources, geometry=geometry, default_material=get_material(name=clad_material), # geometry_center=geometry_center, ) # Add port monitors dict monitors = {} for port_name in component.ports.keys(): port = component.ports[port_name] angle = port.orientation width = port.width + 2 * port_margin size_x = width * abs(np.sin(angle * np.pi / 180)) size_y = width * abs(np.cos(angle * np.pi / 180)) size_x = 0 if size_x < 0.001 else size_x size_y = 0 if size_y < 0.001 else size_y size = mp.Vector3(size_x, size_y, size_z) size = [size_x, size_y, size_z] # if monitor has a source move monitor inwards length = -distance_source_to_monitors if port_name == port_source_name else 0 xy_shifted = move_polar_rad_copy( np.array(port.center), angle=angle * np.pi / 180, length=length ) center = xy_shifted.tolist() + [0] # (x, y, z=0) m = sim.add_mode_monitor(freqs, mp.ModeRegion(center=center, size=size)) m.z = 0 monitors[port_name] = m return dict( sim=sim, cell_size=cell_size, freqs=freqs, monitors=monitors, sources=sources, field_monitor_point=field_monitor_point, port_source_name=port_source_name, )
def get_simulation( component: Component, mode_index: int = 0, n_modes: int = 2, port_extension: Optional[float] = 4.0, layer_stack: LayerStack = LAYER_STACK, zmargin: float = 1.0, thickness_pml: float = 1.0, clad_material: str = "SiO2", port_source_name: str = "o1", port_margin: float = 0.5, distance_source_to_monitors: float = 0.2, mesh_step: float = 40e-3, wavelength: float = 1.55, ) -> td.Simulation: """Returns Simulation object from gdsfactory.component based on GDS example https://simulation.cloud/docs/html/examples/ParameterScan.html Args: component: gf.Component mode_index: mode index n_modes: number of modes port_extension: extend ports beyond the PML layer_stack: contains layer numbers (int, int) to thickness, zmin zmargin: thickness for cladding above and below core thickness_pml: PML thickness (um) clad_material: material for cladding port_source_name: input port name port_margin: margin on each side of the port distance_source_to_monitors: in (um) source goes before monitors mesh_step: in all directions wavelength: in (um) You can visualize the simulation with gdsfactory .. code:: import matplotlib.pyplot as plt import gdsfactory as gf import gdsfactory.simulation.tidy3d as gm c = gf.components.bend_circular() sim = gm.get_simulation(c) gm.plot_simulation(sim) """ layer_to_thickness = layer_stack.get_layer_to_thickness() layer_to_material = layer_stack.get_layer_to_material() layer_to_zmin = layer_stack.get_layer_to_zmin() # layer_to_sidewall_angle = layer_stack.get_layer_to_sidewall_angle() assert isinstance( component, Component ), f"component needs to be a gf.Component, got Type {type(component)}" if port_source_name not in component.ports: warnings.warn( f"port_source_name={port_source_name} not in {component.ports.keys()}" ) port_source = component.get_ports_list()[0] port_source_name = port_source.name warnings.warn( f"Selecting port_source_name={port_source_name} instead.") component_extended = (gf.components.extension.extend_ports( component=component, length=port_extension, centered=True) if port_extension else component) gf.show(component_extended) component_extended.flatten() component_extended_ref = component_extended.ref() component_ref = component.ref() component_ref.x = 0 component_ref.y = 0 structures = [ td.Box( material=get_material(name=clad_material), size=(td.inf, td.inf, td.inf), center=(0, 0, 0), ) ] layers_thickness = [ layer_to_thickness[layer] for layer in component.get_layers() if layer in layer_to_thickness ] t_core = max(layers_thickness) cell_thickness = thickness_pml + t_core + thickness_pml + 2 * zmargin sim_size = [ component_ref.xsize + 2 * thickness_pml, component_ref.ysize + 2 * thickness_pml, cell_thickness, ] for layer in component.layers: if layer in layer_to_thickness and layer in layer_to_material: height = layer_to_thickness[layer] zmin = layer_to_zmin[layer] z_cent = zmin + height / 2 material_name = MATERIAL_NAME_TO_TIDY3D[layer_to_material[layer]] material = get_material(name=material_name) geometry = td.GdsSlab( material=material, gds_cell=component_extended_ref, gds_layer=layer[0], gds_dtype=layer[1], z_cent=z_cent, z_size=height, ) structures.append(geometry) # Add source port = component_ref.ports[port_source_name] angle = port.orientation width = port.width + 2 * port_margin size_x = width * abs(np.sin(angle * np.pi / 180)) size_y = width * abs(np.cos(angle * np.pi / 180)) size_x = 0 if size_x < 0.001 else size_x size_y = 0 if size_y < 0.001 else size_y size_z = cell_thickness - 2 * thickness_pml size = [size_x, size_y, size_z] center = port.center.tolist() + [0] # (x, y, z=0) freq0 = td.constants.C_0 / wavelength fwidth = freq0 / 10 msource = td.ModeSource( size=size, center=center, source_time=td.GaussianPulse(frequency=freq0, fwidth=fwidth), direction="forward", ) # Add port monitors monitors = {} ports = sort_ports_x(sort_ports_y(component_ref.get_ports_list())) for port in ports: port_name = port.name angle = port.orientation width = port.width + 2 * port_margin size_x = width * abs(np.sin(angle * np.pi / 180)) size_y = width * abs(np.cos(angle * np.pi / 180)) size_x = 0 if size_x < 0.001 else size_x size_y = 0 if size_y < 0.001 else size_y size = (size_x, size_y, size_z) # if monitor has a source move monitor inwards length = -distance_source_to_monitors if port_name == port_source_name else 0 xy_shifted = move_polar_rad_copy(np.array(port.center), angle=angle * np.pi / 180, length=length) center = xy_shifted.tolist() + [0] # (x, y, z=0) monitors[port_name] = td.ModeMonitor( center=[port.x, port.y, t_core / 2], size=size, freqs=[freq0], Nmodes=1, name=port.name, ) domain_monitor = td.FreqMonitor(center=[0, 0, z_cent], size=[sim_size[0], sim_size[1], 0], freqs=[freq0]) sim = td.Simulation( size=sim_size, mesh_step=mesh_step, structures=structures, sources=[msource], monitors=[domain_monitor] + list(monitors.values()), run_time=20 / fwidth, pml_layers=[12, 12, 12], ) # set the modes sim.compute_modes(msource, Nmodes=n_modes) sim.set_mode(msource, mode_ind=mode_index) return sim
def get_simulation( component: Component, resolution: int = 20, extend_ports_length: Optional[float] = 10.0, layer_stack: LayerStack = LAYER_STACK, zmargin_top: float = 3.0, zmargin_bot: float = 3.0, tpml: float = 1.5, clad_material: str = "SiO2", is_3d: bool = False, wl_min: float = 1.5, wl_max: float = 1.6, wl_steps: int = 50, dfcen: float = 0.2, port_source_name: str = "o1", port_field_monitor_name: str = "o2", port_margin: float = 3, distance_source_to_monitors: float = 0.2, port_source_offset: float = 0, port_monitor_offset: float = 0, dispersive: bool = False, **settings, ) -> Dict[str, Any]: r"""Returns Simulation dict from gdsfactory Component based on meep directional coupler example https://meep.readthedocs.io/en/latest/Python_Tutorials/GDSII_Import/ https://support.lumerical.com/hc/en-us/articles/360042095873-Metamaterial-S-parameter-extraction .. code:: top view ________________________________ | | | xmargin_left | port_extension |<------> port_margin ||<--> ___|___________ _________||___ | \ / | | \ / | | ====== | | / \ | ___|___________/ \__________|___ | | <-------->| | |ymargin_bot xmargin_right| | | | |___|___________________________| side view ________________________________ | | | | | | | zmargin_top | |ymargin | | |<---> _____ _|___ | | | | | | | | | | | | | | |_____| |_____| | | | | | | | | |zmargin_bot | | | | |_______|_______________________| Args: component: gf.Component resolution: in pixels/um (20: for coarse, 120: for fine) extend_ports_length: to extend ports beyond the PML layer_stack: Dict of layer number (int, int) to thickness (um) zmargin_top: thickness for cladding above core zmargin_bot: thickness for cladding below core tpml: PML thickness (um) clad_material: material for cladding is_3d: if True runs in 3D wl_min: wavelength min (um) wl_max: wavelength max (um) wl_steps: wavelength steps dfcen: delta frequency port_source_name: input port name port_field_monitor_name: port_margin: margin on each side of the port distance_source_to_monitors: in (um) source goes before port_source_offset: offset between source GDS port and source MEEP port port_monitor_offset: offset between monitor GDS port and monitor MEEP port dispersive: use dispersive material models (requires higher resolution) Keyword Args: settings: other parameters for sim object (resolution, symmetries, etc.) Returns: simulation dict: sim, monitors, sources Make sure you review the simulation before you simulate a component .. code:: import gdsfactory as gf import gdsfactory.simulation.meep as gm c = gf.components.bend_circular() gm.write_sparameters_meep(c, run=False) """ layer_to_thickness = layer_stack.get_layer_to_thickness() layer_to_material = layer_stack.get_layer_to_material() layer_to_zmin = layer_stack.get_layer_to_zmin() layer_to_sidewall_angle = layer_stack.get_layer_to_sidewall_angle() component_ref = component.ref() component_ref.x = 0 component_ref.y = 0 wavelengths = np.linspace(wl_min, wl_max, wl_steps) port_names = list(component_ref.ports.keys()) if port_source_name not in port_names: warnings.warn(f"port_source_name={port_source_name!r} not in {port_names}") port_source = component_ref.get_ports_list()[0] port_source_name = port_source.name warnings.warn(f"Selecting port_source_name={port_source_name!r} instead.") if port_field_monitor_name not in component_ref.ports: warnings.warn( f"port_field_monitor_name={port_field_monitor_name!r} not in {port_names}" ) port_field_monitor = ( component_ref.get_ports_list()[0] if len(component.ports) < 2 else component.get_ports_list()[1] ) port_field_monitor_name = port_field_monitor.name warnings.warn( f"Selecting port_field_monitor_name={port_field_monitor_name!r} instead." ) assert isinstance( component, Component ), f"component needs to be a gf.Component, got Type {type(component)}" component_extended = ( gf.components.extension.extend_ports( component=component, length=extend_ports_length, centered=True ) if extend_ports_length else component ) gf.show(component_extended) component_extended.flatten() component_extended = component_extended.ref() # geometry_center = [component_extended.x, component_extended.y] # geometry_center = [0, 0] # print(geometry_center) layers_thickness = [ layer_to_thickness[layer] for layer in component.layers if layer in layer_to_thickness ] t_core = max(layers_thickness) cell_thickness = tpml + zmargin_bot + t_core + zmargin_top + tpml if is_3d else 0 cell_size = mp.Vector3( component.xsize + 2 * tpml, component.ysize + 2 * tpml, cell_thickness, ) geometry = [] layer_to_polygons = component_extended.get_polygons(by_spec=True) for layer, polygons in layer_to_polygons.items(): if layer in layer_to_thickness and layer in layer_to_material: height = layer_to_thickness[layer] if is_3d else mp.inf zmin_um = layer_to_zmin[layer] if is_3d else 0 # center = mp.Vector3(0, 0, (zmin_um + height) / 2) for polygon in polygons: vertices = [mp.Vector3(p[0], p[1], zmin_um) for p in polygon] material_name = layer_to_material[layer] material = get_material(name=material_name, dispersive=dispersive) geometry.append( mp.Prism( vertices=vertices, height=height, sidewall_angle=layer_to_sidewall_angle[layer], material=material, # center=center ) ) freqs = 1 / wavelengths fcen = np.mean(freqs) frequency_width = dfcen * fcen # Add source port = component_ref.ports[port_source_name] angle_rad = np.radians(port.orientation) width = port.width + 2 * port_margin size_x = width * abs(np.sin(angle_rad)) size_y = width * abs(np.cos(angle_rad)) size_x = 0 if size_x < 0.001 else size_x size_y = 0 if size_y < 0.001 else size_y size_z = cell_thickness - 2 * tpml if is_3d else 20 size = [size_x, size_y, size_z] xy_shifted = move_polar_rad_copy( np.array(port.center), angle=angle_rad, length=port_source_offset ) center = xy_shifted.tolist() + [0] # (x, y, z=0) field_monitor_port = component_ref.ports[port_field_monitor_name] field_monitor_point = field_monitor_port.center.tolist() + [0] # (x, y, z=0) if np.isclose(port.orientation, 0): direction = mp.X elif np.isclose(port.orientation, 90): direction = mp.Y elif np.isclose(port.orientation, 180): direction = mp.X elif np.isclose(port.orientation, 270): direction = mp.Y else: ValueError(f"Port angle {port.orientation} not 0, 90, 180, or 270 degrees!") sources = [ mp.EigenModeSource( src=mp.GaussianSource(fcen, fwidth=frequency_width), size=size, center=center, eig_band=1, eig_parity=mp.NO_PARITY if is_3d else mp.EVEN_Y + mp.ODD_Z, eig_match_freq=True, eig_kpoint=-1 * mp.Vector3(x=1).rotate(mp.Vector3(z=1), angle_rad), direction=direction, ) ] sim = mp.Simulation( cell_size=cell_size, boundary_layers=[mp.PML(tpml)], sources=sources, geometry=geometry, default_material=get_material(name=clad_material), resolution=resolution, **settings, ) # Add port monitors dict monitors = {} for port_name in component_ref.ports.keys(): port = component_ref.ports[port_name] angle_rad = np.radians(port.orientation) width = port.width + 2 * port_margin size_x = width * abs(np.sin(angle_rad)) size_y = width * abs(np.cos(angle_rad)) size_x = 0 if size_x < 0.001 else size_x size_y = 0 if size_y < 0.001 else size_y size = mp.Vector3(size_x, size_y, size_z) size = [size_x, size_y, size_z] # if monitor has a source move monitor inwards length = ( -distance_source_to_monitors + port_source_offset if port_name == port_source_name else port_monitor_offset ) xy_shifted = move_polar_rad_copy( np.array(port.center), angle=angle_rad, length=length ) center = xy_shifted.tolist() + [0] # (x, y, z=0) m = sim.add_mode_monitor(freqs, mp.ModeRegion(center=center, size=size)) m.z = 0 monitors[port_name] = m return dict( sim=sim, cell_size=cell_size, freqs=freqs, monitors=monitors, sources=sources, field_monitor_point=field_monitor_point, port_source_name=port_source_name, initialized=False, )