def to_3d( component: Component, layer_set: LayerSet, layer_stack: LayerStack = LAYER_STACK, exclude_layers: Optional[Tuple[Layer, ...]] = None, ) -> Scene: """Return the Component 3D trimesh Scene. Args: component: layer_set: layer colors from Klayout Layer Properties file layer_stack: contains thickness and zmin for each layer exclude_layers: layers to exclude """ scene = Scene() layer_to_thickness = layer_stack.get_layer_to_thickness() layer_to_zmin = layer_stack.get_layer_to_zmin() exclude_layers = exclude_layers or [] for layer, polygons in component.get_polygons(by_spec=True).items(): if (layer not in exclude_layers and layer in layer_to_thickness and layer in layer_to_zmin): height = layer_to_thickness[layer] zmin = layer_to_zmin[layer] color_hex = layer_set.get_from_tuple(layer).color color_rgb = matplotlib.colors.to_rgb(color_hex) for polygon in polygons: p = shapely.geometry.Polygon(polygon) mesh = extrude_polygon(p, height=height) mesh.apply_translation((0, 0, zmin)) mesh.visual.face_colors = (*color_rgb, 0.5) scene.add_geometry(mesh) return scene
def to_stl( component: Component, filepath: str, layer_set: LayerSet, layer_stack: LayerStack = LAYER_STACK, exclude_layers: Optional[Tuple[Layer, ...]] = None, ) -> None: """Exports a Component into STL. Args: component: filepath: layer_set: layer colors from Klayout Layer Properties file layer_stack: contains thickness and zmin for each layer exclude_layers: layers to exclude """ from trimesh.creation import extrude_polygon layer_to_thickness = layer_stack.get_layer_to_thickness() layer_to_zmin = layer_stack.get_layer_to_zmin() filepath = pathlib.Path(filepath) exclude_layers = exclude_layers or [] for layer, polygons in component.get_polygons(by_spec=True).items(): if ( layer not in exclude_layers and layer in layer_to_thickness and layer in layer_to_zmin ): height = layer_to_thickness[layer] zmin = layer_to_zmin[layer] color_hex = layer_set.get_from_tuple(layer).color color_rgb = matplotlib.colors.to_rgb(color_hex) filepath_layer = ( filepath.parent / f"{filepath.stem}_{layer[0]}_{layer[1]}{filepath.suffix}" ) print(filepath_layer) for polygon in polygons: p = shapely.geometry.Polygon(polygon) mesh = extrude_polygon(p, height=height) mesh.apply_translation((0, 0, zmin)) mesh.visual.face_colors = (*color_rgb, 0.5) mesh.export(filepath_layer)
def read_sparameters_lumerical( component: Optional[Component] = None, layer_stack: LayerStack = LAYER_STACK, filepath: Optional[str] = None, numports: Optional[int] = None, dirpath: Path = gf.CONFIG["sparameters"], ) -> Tuple[List[str], np.array, np.ndarray]: r"""Returns Sparameters from Lumerical interconnect .DAT file. Args: component: Component layer_stack: filepath: numports: number of ports dirpath: path where to look for the Sparameters Returns: port_names: list of port labels F: frequency 1d np.array S: Sparameters np.ndarray matrix the Sparameters file have Lumerical format https://support.lumerical.com/hc/en-us/articles/360036107914-Optical-N-Port-S-Parameter-SPAR-INTERCONNECT-Element#toc_5 """ if component is None and filepath is None: raise ValueError("You need to define the filepath or the component") if filepath and numports is None: raise ValueError("You need to define numports") filepath = filepath or get_sparameters_path( component=component, dirpath=dirpath, layer_to_material=layer_stack.get_layer_to_material(), layer_to_thickness=layer_stack.get_layer_to_thickness(), ) numports = numports or len(component.ports) assert (filepath.exists() ), f"Sparameters for {component.name} not found in {filepath}" assert numports > 1, f"number of ports = {numports} and needs to be > 1" return _read_sparameters_file(filepath=filepath, numports=numports)
def read_sparameters_pandas( component: Component, layer_stack: LayerStack = LAYER_STACK, dirpath: Path = gf.CONFIG["sparameters"], ) -> pd.DataFrame: """Returns Sparameters in a pandas DataFrame. `S11m` `m` stands for module `S11a` `a` stands for angle (in radians) Args: component: Component layer_stack: dirpath: path where to look for the Sparameters """ filepath = get_sparameters_path( component=component, dirpath=dirpath, layer_to_material=layer_stack.get_layer_to_material(), layer_to_thickness=layer_stack.get_layer_to_thickness(), ) df = pd.read_csv(filepath.with_suffix(".csv")) df.index = df["wavelength_nm"] return df
def get_layer_stack_fab_c(thickness: float = 350.0) -> LayerStack: """Returns generic LayerStack""" return LayerStack( layers=[ LayerLevel( layer=LAYER.WG, zmin=0.0, thickness=0.22, ), LayerLevel( layer=LAYER.WGN, zmin=0.22 + 0.1, thickness=0.4, ), ] )
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 write_sparameters_lumerical( component: ComponentOrFactory, session: Optional[object] = None, run: bool = True, overwrite: bool = False, dirpath: Path = gf.CONFIG["sparameters"], layer_stack: LayerStack = LAYER_STACK, simulation_settings: SimulationSettings = SIMULATION_SETTINGS, **settings, ) -> pd.DataFrame: """Returns and writes component Sparameters using Lumerical FDTD. If simulation exists it returns the Sparameters directly unless overwrite=True which forces a re-run of the simulation Lumerical units are in meters while gdsfactory units are in um Writes Sparameters both in .CSV and .DAT (interconnect format) as well as simulation settings in YAML In the CSV format you can see `S12m` where `m` stands for magnitude and `S12a` where `a` stands for angle in radians Your components need to have ports, that will extend over the PML. .. image:: https://i.imgur.com/dHAzZRw.png For your Fab technology you can overwrite - Simulation Settings - dirpath - layerStack Args: component: Component to simulate session: you can pass a session=lumapi.FDTD() or it will create one run: True runs Lumerical, False only draws simulation overwrite: run even if simulation results already exists dirpath: where to store the Sparameters layer_stack: layer_stack simulation_settings: dataclass with all simulation_settings **settings: overwrite any simulation settings background_material: for the background port_margin: on both sides of the port width (um) port_height: port height (um) port_extension: port extension (um) mesh_accuracy: 2 (1: coarse, 2: fine, 3: superfine) zmargin: for the FDTD region 1 (um) ymargin: for the FDTD region 2 (um) xmargin: for the FDTD region pml_margin: for all the FDTD region wavelength_start: 1.2 (um) wavelength_stop: 1.6 (um) wavelength_points: 500 simulation_time: determines the max structure size (3e8/2.4*10e-12*1e6) = 1.25mm simulation_temperature: in kelvin 300 Return: Sparameters pandas DataFrame (wavelength_nm, S11m, S11a, S12a ...) suffix `a` for angle in radians and `m` for module """ component = component() if callable(component) else component sim_settings = dataclasses.asdict(simulation_settings) layer_to_thickness = layer_stack.get_layer_to_thickness() layer_to_zmin = layer_stack.get_layer_to_zmin() layer_to_material = layer_stack.get_layer_to_material() if hasattr(component.info, "simulation_settings"): sim_settings.update(component.info.simulation_settings) logger.info( "Updating {component.name} sim settings {component.simulation_settings}" ) for setting in settings.keys(): if setting not in sim_settings: raise ValueError( f"`{setting}` is not a valid setting ({list(sim_settings.keys()) + simulation_settings})" ) sim_settings.update(**settings) ss = SimulationSettings(**sim_settings) component_extended = gf.c.extend_ports( component, length=ss.distance_source_to_monitors) ports = component_extended.get_ports_list(port_type="optical") if not ports: raise ValueError(f"`{component.name}` does not have any optical ports") c = gf.components.extension.extend_ports(component=component, length=ss.port_extension) c.remove_layers(component.layers - set(layer_to_thickness.keys())) c._bb_valid = False c.flatten() c.name = "top" gdspath = c.write_gds() filepath = get_sparameters_path( component=component, dirpath=dirpath, layer_to_material=layer_to_material, layer_to_thickness=layer_to_thickness, **settings, ) filepath_csv = filepath.with_suffix(".csv") filepath_sim_settings = filepath.with_suffix(".yml") filepath_fsp = filepath.with_suffix(".fsp") if run and filepath_csv.exists() and not overwrite: logger.info(f"Reading Sparameters from {filepath_csv}") return pd.read_csv(filepath_csv) if not run and session is None: print(run_false_warning) logger.info(f"Writing Sparameters to {filepath_csv}") x_min = (component.xmin - ss.xmargin - ss.pml_margin) * 1e-6 x_max = (component.xmax + ss.xmargin + ss.pml_margin) * 1e-6 y_min = (component.ymin - ss.ymargin - ss.pml_margin) * 1e-6 y_max = (component.ymax + ss.ymargin + ss.pml_margin) * 1e-6 port_orientations = [p.orientation for p in ports] # bend if 90 in port_orientations: y_max -= ss.ymargin * 1e-6 if 270 in port_orientations: y_min += ss.ymargin * 1e-6 layers_thickness = [ layer_to_thickness[layer] for layer in component.get_layers() if layer in layer_to_thickness ] if not layers_thickness: raise ValueError(f"no layers for component {component.get_layers()}" f"in layer stack {layers_thickness.keys()}") layers_zmin = [ layer_to_zmin[layer] for layer in component.get_layers() if layer in layer_to_zmin ] component_thickness = max(layers_thickness) component_zmin = min(layers_zmin) z = (component_zmin + component_thickness) / 2 * 1e-6 z_span = (2 * ss.zmargin + component_thickness) * 1e-6 x_span = x_max - x_min y_span = y_max - y_min layers = c.get_layers() sim_settings.update(dict(layer_stack=layer_stack.to_dict())) sim_settings = dict( simulation_settings=sim_settings, component=component.settings, version=__version__, ) logger.info( f"Simulation size = {(x_span)*1e6:.3f}, {(y_span)*1e6:.3f}, {z_span*1e6:.3f} um" ) # from pprint import pprint # filepath_sim_settings.write_text(omegaconf.OmegaConf.to_yaml(sim_settings)) # print(filepath_sim_settings) # pprint(sim_settings) # return try: import lumapi except ModuleNotFoundError as e: print( "Cannot import lumapi (Python Lumerical API). " "You can add set the PYTHONPATH variable or add it with `sys.path.append()`" ) raise e except OSError as e: raise e start = time.time() s = session or lumapi.FDTD(hide=False) s.newproject() s.selectall() s.deleteall() s.addrect( x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max, z=z, z_span=z_span, index=1.5, name="clad", ) material = ss.background_material if material not in MATERIAL_NAME_TO_LUMERICAL: raise ValueError( f"{material} not in {list(MATERIAL_NAME_TO_LUMERICAL.keys())}") material = MATERIAL_NAME_TO_LUMERICAL[material] s.setnamed("clad", "material", material) s.addfdtd( dimension="3D", x_min=x_min, x_max=x_max, y_min=y_min, y_max=y_max, z=z, z_span=z_span, mesh_accuracy=ss.mesh_accuracy, use_early_shutoff=True, simulation_time=ss.simulation_time, simulation_temperature=ss.simulation_temperature, ) for layer, thickness in layer_to_thickness.items(): if layer not in layers: continue if layer not in layer_to_material: raise ValueError(f"{layer} not in {layer_to_material.keys()}") material_name = layer_to_material[layer] if material_name not in MATERIAL_NAME_TO_LUMERICAL: raise ValueError( f"{material_name} not in {list(MATERIAL_NAME_TO_LUMERICAL.keys())}" ) material_name_lumerical = MATERIAL_NAME_TO_LUMERICAL[material_name] if layer not in layer_to_zmin: raise ValueError(f"{layer} not in {list(layer_to_zmin.keys())}") zmin = layer_to_zmin[layer] zmax = zmin + thickness z = (zmax + zmin) / 2 s.gdsimport(str(gdspath), "top", f"{layer[0]}:{layer[1]}") layername = f"GDS_LAYER_{layer[0]}:{layer[1]}" s.setnamed(layername, "z", z * 1e-6) s.setnamed(layername, "z span", thickness * 1e-6) s.setnamed(layername, "material", material_name_lumerical) logger.info( f"adding {layer}, thickness = {thickness} um, zmin = {zmin} um ") for i, port in enumerate(ports): zmin = layer_to_zmin[port.layer] thickness = layer_to_thickness[port.layer] z = (zmin + thickness) / 2 zspan = 2 * ss.port_margin + thickness s.addport() p = f"FDTD::ports::port {i+1}" s.setnamed(p, "x", port.x * 1e-6) s.setnamed(p, "y", port.y * 1e-6) s.setnamed(p, "z", z * 1e-6) s.setnamed(p, "z span", zspan * 1e-6) deg = int(port.orientation) # if port.orientation not in [0, 90, 180, 270]: # raise ValueError(f"{port.orientation} needs to be [0, 90, 180, 270]") if -45 <= deg <= 45: direction = "Backward" injection_axis = "x-axis" dxp = 0 dyp = 2 * ss.port_margin + port.width elif 45 < deg < 90 + 45: direction = "Backward" injection_axis = "y-axis" dxp = 2 * ss.port_margin + port.width dyp = 0 elif 90 + 45 < deg < 180 + 45: direction = "Forward" injection_axis = "x-axis" dxp = 0 dyp = 2 * ss.port_margin + port.width elif 180 + 45 < deg < 180 + 45 + 90: direction = "Forward" injection_axis = "y-axis" dxp = 2 * ss.port_margin + port.width dyp = 0 else: raise ValueError( f"port {port.name} orientation {port.orientation} is not valid" ) s.setnamed(p, "direction", direction) s.setnamed(p, "injection axis", injection_axis) s.setnamed(p, "y span", dyp * 1e-6) s.setnamed(p, "x span", dxp * 1e-6) # s.setnamed(p, "theta", deg) s.setnamed(p, "name", port.name) # s.setnamed(p, "name", f"o{i+1}") logger.info(f"port {p} {port.name}: at ({port.x}, {port.y}, 0)" f"size = ({dxp}, {dyp}, {zspan})") s.setglobalsource("wavelength start", ss.wavelength_start * 1e-6) s.setglobalsource("wavelength stop", ss.wavelength_stop * 1e-6) s.setnamed("FDTD::ports", "monitor frequency points", ss.wavelength_points) if run: s.save(str(filepath_fsp)) s.deletesweep("s-parameter sweep") s.addsweep(3) s.setsweep("s-parameter sweep", "Excite all ports", 0) s.setsweep("S sweep", "auto symmetry", True) s.runsweep("s-parameter sweep") sp = s.getsweepresult("s-parameter sweep", "S parameters") s.exportsweep("s-parameter sweep", str(filepath)) logger.info(f"wrote sparameters to {filepath}") keys = [key for key in sp.keys() if key.startswith("S")] ra = { f"{key}a": list(np.unwrap(np.angle(sp[key].flatten()))) for key in keys } rm = {f"{key}m": list(np.abs(sp[key].flatten())) for key in keys} wavelength_nm = sp["lambda"].flatten() * 1e9 results = {"wavelength_nm": wavelength_nm} results.update(ra) results.update(rm) df = pd.DataFrame(results, index=wavelength_nm) end = time.time() df.to_csv(filepath_csv, index=False) sim_settings.update(compute_time_seconds=end - start) filepath_sim_settings.write_text( omegaconf.OmegaConf.to_yaml(sim_settings)) return df filepath_sim_settings.write_text(omegaconf.OmegaConf.to_yaml(sim_settings)) return s
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, )