def write_gds( component: Component, gdspath: Optional[PosixPath] = None, unit: float = 1e-6, precision: float = 1e-9, remove_previous_markers: bool = False, auto_rename: bool = False, with_settings_label: bool = conf.tech.with_settings_label, ) -> str: """write component to GDS and returs gdspath Args: component (required) gdspath: by default saves it into CONFIG['gds_directory'] auto_rename: False by default (otherwise it calls it top_cell) unit precission Returns: gdspath """ gdspath = gdspath or CONFIG["gds_directory"] / (component.name + ".gds") gdspath = pathlib.Path(gdspath) gdsdir = gdspath.parent gdspath = str(gdspath) gdsdir.mkdir(parents=True, exist_ok=True) if remove_previous_markers: # If the component HAS ports AND markers and we want to # avoid duplicate port markers, then we clear the previous ones port_layer = (LAYER.PORT, ) label_layer = (LAYER.TEXT, ) component.remove_layers([port_layer]) component.remove_layers([label_layer]) # write component settings into text layer if with_settings_label: settings = component.get_settings() for i, k in enumerate(sorted(list(settings.keys()))): v = clean_value(settings.get(k)) text = f"{k} = {clean_value(v)}" # print(text) component.add_label( text=text, position=component.center - [0, i * 1], layer=CONFIG["layers"]["TEXT"], ) component.write_gds( gdspath, precision=precision, auto_rename=auto_rename, ) component.path = gdspath return gdspath
def write( component: Component, session: Optional[object] = None, run: bool = True, overwrite: bool = False, dirpath: PosixPath = pp.CONFIG["sp"], **settings, ) -> pd.DataFrame: """Return and write component Sparameters from Lumerical FDTD. if simulation exists and returns the Sparameters directly unless overwrite=False Args: component: gdsfactory Component session: you can pass a session=lumapi.FDTD() for debugging run: True-> runs Lumerical , False -> only draws simulation overwrite: run even if simulation results already exists dirpath: where to store the simulations layer2nm: dict of GDSlayer to thickness (nm) {(1, 0): 220} layer2material: dict of {(1, 0): "si"} remove_layers: list of tuples (layers to remove) background_material: for the background port_width: port width (m) port_height: port height (m) port_extension_um: port extension (um) mesh_accuracy: 2 (1: coarse, 2: fine, 3: superfine) zmargin: for the FDTD region 1e-6 (m) ymargin: for the FDTD region 2e-6 (m) wavelength_start: 1.2e-6 (m) wavelength_stop: 1.6e-6 (m) wavelength_points: 500 Return: Sparameters pandas DataFrame (wavelength_nm, S11m, S11a, S12a ...) suffix `a` for angle and `m` for module """ sim_settings = default_simulation_settings if hasattr(component, "simulation_settings"): sim_settings.update(component.simulation_settings) for setting in sim_settings.keys(): assert ( setting in sim_settings ), f"`{setting}` is not a valid setting ({list(sim_settings.keys())})" for setting in settings.keys(): assert ( setting in sim_settings ), f"`{setting}` is not a valid setting ({list(sim_settings.keys())})" sim_settings.update(**settings) # easier to access dict in a namedtuple `ss.port_width` ss = namedtuple("sim_settings", sim_settings.keys())(*sim_settings.values()) assert ss.port_width < 5e-6 assert ss.port_height < 5e-6 assert ss.zmargin < 5e-6 assert ss.ymargin < 5e-6 ports = component.ports component.remove_layers(ss.remove_layers) component._bb_valid = False c = pp.extend_ports(component=component, length=ss.port_extension_um) gdspath = pp.write_gds(c) layer2material = settings.pop("layer2material", ss.layer2material) layer2nm = settings.pop("layer2nm", ss.layer2nm) filepath = get_sparameters_path( component=component, dirpath=dirpath, layer2material=layer2material, layer2nm=layer2nm, **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: return pd.read_csv(filepath_csv) if not run and session is None: print(run_false_warning) pe = ss.port_extension_um * 1e-6 / 2 x_min = c.xmin * 1e-6 + pe x_max = c.xmax * 1e-6 - pe y_min = c.ymin * 1e-6 - ss.ymargin y_max = c.ymax * 1e-6 + ss.ymargin port_orientations = [p.orientation for p in ports.values()] if 90 in port_orientations and len(ports) > 2: y_max = c.ymax * 1e-6 - pe x_max = c.xmax * 1e-6 elif 90 in port_orientations: y_max = c.ymax * 1e-6 - pe x_max = c.xmax * 1e-6 + ss.ymargin z = 0 z_span = 2 * ss.zmargin + max(ss.layer2nm.values()) * 1e-9 layers = component.get_layers() sim_settings = dict( simulation_settings=clean_dict(sim_settings, layers), component=component.get_settings(), version=__version__, ) # filepath_sim_settings.write_text(yaml.dump(sim_settings)) # print(filepath_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 materials: raise ValueError(f"{material} not in {list(materials.keys())}") material = materials[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, ) for layer, nm in ss.layer2nm.items(): if layer not in layers: continue assert layer in ss.layer2material, f"{layer} not in {ss.layer2material.keys()}" material = ss.layer2material[layer] if material not in materials: raise ValueError(f"{material} not in {list(materials.keys())}") material = materials[material] s.gdsimport(str(gdspath), c.name, f"{layer[0]}:{layer[1]}") silicon = f"GDS_LAYER_{layer[0]}:{layer[1]}" s.setnamed(silicon, "z span", nm * 1e-9) s.setnamed(silicon, "material", material) for i, port in enumerate(ports.values()): 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 span", ss.port_height) deg = int(port.orientation) # assert port.orientation in [0, 90, 180, 270], f"{port.orientation} needs to be [0, 90, 180, 270]" if -45 <= deg <= 45: direction = "Backward" injection_axis = "x-axis" dxp = 0 dyp = ss.port_width elif 45 < deg < 90 + 45: direction = "Backward" injection_axis = "y-axis" dxp = ss.port_width dyp = 0 elif 90 + 45 < deg < 180 + 45: direction = "Forward" injection_axis = "x-axis" dxp = 0 dyp = ss.port_width elif 180 + 45 < deg < -45: direction = "Forward" injection_axis = "y-axis" dxp = ss.port_width dyp = 0 else: raise ValueError( f"port {port.name} with orientation {port.orientation} is not a valid" " number ") s.setnamed(p, "direction", direction) s.setnamed(p, "injection axis", injection_axis) s.setnamed(p, "y span", dyp) s.setnamed(p, "x span", dxp) # s.setnamed(p, "theta", deg) s.setnamed(p, "name", port.name) s.setglobalsource("wavelength start", ss.wavelength_start) s.setglobalsource("wavelength stop", ss.wavelength_stop) 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") # collect results # S_matrix = s.getsweepresult("s-parameter sweep", "S matrix") sp = s.getsweepresult("s-parameter sweep", "S parameters") # export S-parameter data to file named s_params.dat to be loaded in # INTERCONNECT s.exportsweep("s-parameter sweep", str(filepath)) print(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() sim_settings.update(compute_time_seconds=end - start) df.to_csv(filepath_csv, index=False) filepath_sim_settings.write_text(yaml.dump(sim_settings)) return df