def draw( session=None, fsp=str(CONFIG["dbr"]), material_wg="si", wg_height=220e-9, w1=550e-9, w2=450e-9, n_sweep=8, ): """ draw DBR unit cell in FDTD """ if material_wg not in materials: raise ValueError(f"{material_wg} not in {list(materials.keys())}") material_wg = materials[material_wg] import lumapi s = session if session is not None else lumapi.FDTD(hide=False) s.newproject() s.selectall() s.deleteall() s.load(fsp) s.setnamed("w1", "z span", wg_height) s.setnamed("w2", "z span", wg_height) s.setnamed("w1", "y span", w1) s.setnamed("w2", "y span", w2) s.setnamed("w1", "material", material_wg) s.setnamed("w2", "material", material_wg) s.setsweep("sweep", "number of points", n_sweep) return s
def write_sparameters_components_lumerical( factory: ComponentFactoryDict, run: bool = False, session: Optional[object] = None, **kwargs, ) -> None: """writes component Sparameters using Lumerical FDTD. Args: factory: dict of component functions run: if False, does not run and prompts you to review each simulation session: lumapi.FDTD() Lumerical FDTD session """ import lumapi session = session or lumapi.FDTD() need_review = [] for component_name in factory.keys(): component = factory[component_name]() write_sparameters_lumerical(component, run=run, session=session, **kwargs) if not run: response = input( f"does the simulation for {component_name} look good? (y/n)" ) if response.upper()[0] == "N": need_review.append(component_name)
def __init__(self, workingDir, use_var_fdtd, hide_fdtd_cad): """ Launches FDTD CAD and stores a handle. """ self.fdtd = lumapi.MODE( hide=hide_fdtd_cad) if use_var_fdtd else lumapi.FDTD( hide=hide_fdtd_cad) self.workingDir = workingDir self.fdtd.cd(self.workingDir)
def initialize(self, build_simulation): """ Launches FDTD CAD and stores the handle """ # lumopt objects expect self.fdtd to be a lumapi.FDTD handle self.fdtd = lumapi.FDTD(hide=self.hide_gui) self.fdtd.cd(self._working_dir) # build the simulation self.execute(build_simulation)
def __init__(self, workingDir, script=''): ''' :param workingDir: Working directory to save the simulation before running it :param script: (String) Base Lumerical script to execute when making the simulation ''' self.script = script self.workingDir = workingDir self.fdtd = lumapi.FDTD() self.fdtd.cd(workingDir) self.fdtd.eval(script)
def gc_sweep( session=None, draw_function=gc2d, dirpath=CONFIG["workspace"], overwrite=False, run=True, base_fsp_path=str(CONFIG["grating_coupler_2D_base"]), **kwargs ): """ grating coupler sweep grating_coupler_2D_base optimizes Transmission and does not calculate Sparameters """ import lumapi function_name = draw_function.__name__ + "_sweep" filename = kwargs.pop("name", get_function_name(function_name, **kwargs)) dirpath = pathlib.Path(dirpath) / function_name dirpath.mkdir(exist_ok=True) filepath = dirpath / filename filepath_sim_settings = filepath.with_suffix(".settings.json") filepath_json = filepath.with_suffix(".json") filepath_fsp = str(filepath.with_suffix(".fsp")) if filepath_json.exists() and not overwrite and run: return json.loads(open(filepath_json).read()) s = session or lumapi.FDTD(hide=False) simdict = draw_function(session=s, base_fsp_path=base_fsp_path, **kwargs) s.save(filepath_fsp) if not run: return s.run() T = s.getresult("fom", "T") results = dict(wavelength_nm=list(T["lambda"].ravel() * 1e9), T=list(T["T"])) with open(filepath_json, "w") as f: json.dump(results, f) settings = simdict.get("settings") if settings: with open(filepath_sim_settings, "w") as f: json.dump(settings, f) return results
def run_fdtd(scripts_dict, session=None, return_session=False): """ runs a dict of scripts in a FDTD session there should be a main.lsf defined .. code-block:: python import pylum from pylum.grating_coupler import sweep scripts_dict = sweep() run_fdtd(scripts_dict) """ import lumapi dirpath = scripts_dict.get("dirpath", write_scripts(scripts_dict)) s = session or lumapi.FDTD() s.cd(str(dirpath)) s.eval(scripts_dict["main.lsf"]) if return_session: return s
import queue import subprocess import platform import re import reinterpolate # # Create FDTD hook # fdtd_hook = lumapi.FDTD( hide=False ) # # Create project folder and save out the parameter file for documentation for this optimization # python_src_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), '.')) base_directory = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) projects_directory_location = base_directory + "/projects/cluster/nir/" if not os.path.isdir(projects_directory_location): os.mkdir(projects_directory_location) log_file = open( projects_directory_location + "/log.txt", 'w' ) log_file.write( "Log\n" ) log_file.close()
filepath_sim_settings = filepath.with_suffix(".settings.json") filepath_json = filepath.with_suffix(".json") filepath_fsp = str(filepath.with_suffix(".fsp")) if filepath_json.exists() and not overwrite and run: return json.loads(open(filepath_json).read()) s = session or lumapi.FDTD(hide=False) simdict = draw_function(session=s, base_fsp_path=base_fsp_path, **kwargs) s.save(filepath_fsp) if not run: return s.run() T = s.getresult("fom", "T") results = dict(wavelength_nm=list(T["lambda"].ravel() * 1e9), T=list(T["T"])) with open(filepath_json, "w") as f: json.dump(results, f) settings = simdict.get("settings") if settings: with open(filepath_sim_settings, "w") as f: json.dump(settings, f) return results if __name__ == "__main__": import lumapi s = lumapi.FDTD() s = gc_sweep(session=s)
def gc( session=None, period=0.66e-6, ff=0.5, wl=1550e-9, n_gratings=50, wg_height=220e-9, etch_depth=70e-9, box_height=2e-6, clad_height=2e-6, substrate_height=2e-6, material="Si (Silicon) - Palik", material_clad="SiO2 (Glass) - Palik", wg_width=500e-9, polarization="TE", wavelength=1550e-9, gc_xmin=0, fiber_position=4.5e-6, fiber_angle_deg=20, wl_span=0.3e-6, # wavelength span mesh_accuracy=3, # FDTD simulation mesh accuracy frequency_points=100, # global frequency points simulation_time=1000e-15, # maximum simulation time [s] core_index=1.4682, cladding_index=1.4629, core_diameter=8.2e-6, cladding_diameter=100e-6, d=0.2e-6, ): import lumapi s = session or lumapi.FDTD(hide=False) s.newproject() s.selectall() s.deleteall() gap = period * (1 - ff) # etched region of the grating s.addrect() s.set("name", "GC_base") s.set("material", material) s.set("x max", (n_gratings + 1) * period) s.set("x min", gc_xmin) s.set("y", 0.5 * (wg_height - etch_depth)) s.set("y span", wg_height - etch_depth) # add GC teeth; for i in range(n_gratings): s.addrect() s.set("name", "GC_tooth") s.set("material", material) s.set("y", 0.5 * wg_height) s.set("y span", wg_height) s.set("x min", gc_xmin + gap + i * period) s.set("x max", gc_xmin + period + i * period) s.selectpartial("GC") s.addtogroup("GC") # draw silicon substrate; s.addrect() s.set("name", "substrate") s.set("material", material) s.set("x max", 30e-6) s.set("x min", -20e-6) s.set("y", -1 * (box_height + 0.5 * substrate_height)) s.set("y span", substrate_height) s.set("alpha", 0.2) s.addrect() # draw burried oxide; s.set("name", "BOX") s.set("material", material_clad) s.set("x max", 30e-6) s.set("x min", -20e-6) s.set("y min", -box_height) s.set("y max", clad_height) s.set("override mesh order from material database", True) s.set("mesh order", 3) s.set("alpha", 0.3) s.addrect() # draw waveguide; s.set("name", "WG") s.set("material", material) s.set("x min", -20e-6) s.set("x max", gc_xmin) s.set("y", 0.11e-6) s.set("y span", wg_height) # add simulation region; s.addfdtd( dimension="2D", x_max=15e-6, x_min=-3.5e-6, y_min=-(box_height + 0.2e-6), y_max=clad_height + 2e-6, mesh_accuracy=mesh_accuracy, simulation_time=simulation_time, ) # add waveguide mode source; s.addmode() s.set("name", "waveguide_source") s.set("x", -3e-6) s.set("y", 0.5 * wg_height) s.set("y span", 2e-6) s.set("direction", "Forward") s.set("use global source settings", True) s.set("enabled", False) # add fibre; theta = np.arcsin( np.sin(fiber_angle_deg * np.pi / 180) / core_index) * 180 / np.pi r1 = core_diameter / 2 r2 = cladding_diameter / 2 span = 15 * r1 if theta > 89: theta = 89 if theta < -89: theta = -89 thetarad = theta * np.pi / 180 L = 20e-6 / np.cos(thetarad) V1 = [ (-r1 / np.cos(thetarad), 0), (r1 / np.cos(thetarad), 0), (r1 / np.cos(thetarad) + L * np.sin(thetarad), L * np.cos(thetarad)), (-r1 / np.cos(thetarad) + L * np.sin(thetarad), L * np.cos(thetarad)), ] V2 = [ (-r2 / np.cos(thetarad), 0), (r2 / np.cos(thetarad), 0), (r2 / np.cos(thetarad) + L * np.sin(thetarad), L * np.cos(thetarad)), (-r2 / np.cos(thetarad) + L * np.sin(thetarad), L * np.cos(thetarad)), ] v1 = s.matrix(4, 2) v2 = s.matrix(4, 2) for i in range(4): for j in range(2): v1[i][j] = V1[i][j] v2[i][j] = V2[i][j] s.addpoly() s.set("name", "fibre_core") s.set("x", 0) s.set("y", 0) s.set("vertices", v1) s.set("index", core_index) s.addpoly() s.set("name", "fibre_cladding") s.set("override mesh order from material database", 1) s.set("mesh order", 3) s.set("x", 0) s.set("y", 0) s.set("vertices", v2) s.set("index", cladding_index) s.addmode() s.set("name", "fibre_mode") s.set("injection axis", "y-axis") s.set("direction", "Backward") s.set("use global source settings", 1) s.set("theta", -theta) s.set("x span", span) s.d = 0.4e-6 s.set("x", d * np.sin(thetarad)) s.set("y", d * np.cos(thetarad)) s.set("rotation offset", abs(span / 2 * np.tan(thetarad))) s.addpower() s.set("name", "fibre_top") s.set("x span", span) s.set("x", d * np.sin(thetarad)) s.set("y", d * np.cos(thetarad)) s.addmodeexpansion() s.set("name", "fibre_modeExpansion") s.set("monitor type", "2D Y-normal") s.setexpansion("fibre_top", "fibre_top") s.set("x span", span) s.set("x", d * np.sin(thetarad)) s.set("y", d * np.cos(thetarad)) s.set("theta", -theta) s.set("rotation offset", abs(span / 2 * np.tan(thetarad))) s.set("override global monitor settings", False) s.selectpartial("fibre") s.addtogroup("fibre") s.select("fibre::fibre_modeExpansion") s.setexpansion("fibre_top", "::model::fibre::fibre_top") s.unselectall() s.select("fibre") s.set("x", fiber_position) s.set("y", clad_height + 1e-6) s.addpower() # add monitor; s.set("name", "T") s.set("monitor type", "2D X-normal") s.set("x", -2.8e-6) s.set("y", 0.5 * wg_height) s.set("y span", 1e-6) s.addmodeexpansion() # add waveguide mode expansion monitor s.set("name", "waveguide") s.set("monitor type", "2D X-normal") s.setexpansion("T", "T") s.set("x", -2.9e-6) s.set("y", 0.5 * wg_height) s.set("y span", 1e-6) if polarization == "TE": s.select("fibre::fibre_mode") s.set("mode selection", "fundamental TM") s.select("fibre::fibre_modeExpansion") s.set("mode selection", "fundamental TM") s.select("waveguide_source") s.set("mode selection", "fundamental TM") s.select("waveguide") s.set("mode selection", "fundamental TM") else: s.select("fibre::fibre_mode") s.set("mode selection", "fundamental TE") s.select("fibre::fibre_modeExpansion") s.set("mode selection", "fundamental TE") s.select("waveguide_source") s.set("mode selection", "fundamental TE") s.select("waveguide") s.set("mode selection", "fundamental TE") # global properties s.setglobalmonitor("frequency points", frequency_points) s.setglobalmonitor("use wavelength spacing", 1) s.setglobalmonitor("use source limits", 1) s.setglobalsource("center wavelength", wl) s.setglobalsource("wavelength span", wl_span) s.save("GC_fibre") ######################### # Compute Sparameters ######################### s.addport() # fibre port # p = "FDTD::ports::port 1" s.set("injection axis", "y-axis") s.set("x", d * np.sin(thetarad)) s.set("y", d * np.cos(thetarad) + 3) s.set("x span", span) s.set("theta", -theta) s.set("rotation offset", abs(span / 2 * np.tan(thetarad))) s.addport() # waveguide # p = "FDTD::ports::port 2" s.set("injection axis", "x-axis") s.set("x", -2.9e-6) s.set("y", 0.5 * wg_height) s.set("y span", 1e-6) return dict(session=s)
load_file = h5py.File(data_transfer_filename + ".mat", 'r') monitor_data = np.array(load_file[extracted_data_name]) return monitor_data def get_complex_monitor_data(monitor_name, monitor_field): data = get_monitor_data(monitor_name, monitor_field) return (data['real'] + np.complex(0, 1) * data['imag']) # # Create FDTD hook # fdtd_hook = lumapi.FDTD() python_src_directory = os.path.abspath( os.path.join(os.path.dirname(__file__), '.')) projects_directory_location = os.path.abspath( os.path.join(os.path.dirname(__file__), '../projects/')) if not os.path.isdir(projects_directory_location): os.mkdir(projects_directory_location) projects_directory_location += "/" + project_name if not os.path.isdir(projects_directory_location): os.mkdir(projects_directory_location) log_file = open(projects_directory_location + "/log.txt", 'w')
def write_sparameters( session=None, draw_function=gc2d, dirpath=CONFIG["workspace"], overwrite=False, run=True, **kwargs, ): """Write grating coupler Sparameters returns early if filepath_sp exists and overwrite flag is False Returns: Sparameters filepath in interconnect format Args: session draw_function: dirpath: where to store all the simulation files overwrite: run even if simulation exists period (m): 0.66e-6 ff: 0.5 fill factor n_gratings: 50 gap_width_list: [(gap1, width1), (gap2, width2) ...] overrides (period, ff and n_gratings) wg_height: 220e-9 etch_depth: 70e-9 box_height: 2e-6 clad_height: 2e-6 substrate_height: 2e-6 material_wg: "si" material_wafer: "si" material_clad: "sio2" material_box: "sio2" wavelength: 1550e-9 wavelength_span: 0.3e-6 gc_xmin: 3e-6 fiber_angle_deg: 20 """ import lumapi function_name = draw_function.__name__ filename = get_function_name(function_name, **kwargs) dirpath = pathlib.Path(dirpath) / function_name dirpath.mkdir(exist_ok=True) filepath = dirpath / filename filepath_sim_settings = filepath.with_suffix(".settings.json") filepath_json = filepath.with_suffix(".json") filepath_fsp = filepath.with_suffix(".fsp") filepath_sp = filepath.with_suffix(".dat") if filepath_sp.exists() and not overwrite and run: return filepath_sp s = session or lumapi.FDTD(hide=False) simdict = draw_function(session=s, **kwargs) s.save(str(filepath_fsp)) if not run: return filepath_sp s.runsweep("S-parameters") sp = s.getsweepresult("S-parameters", "S parameters") s.exportsweep("S-parameters", str(filepath_sp)) print(f"wrote sparameters to {filepath_sp}") 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} sp_dict = dict(wavelength_nm=list(sp["lambda"].flatten() * 1e9)) sp_dict.update(ra) sp_dict.update(rm) with open(filepath_json, "w") as f: json.dump(sp_dict, f) settings = simdict.get("settings") if settings: with open(filepath_sim_settings, "w") as f: json.dump(settings, f) return filepath_sp
def __init__(self, workingDir, hide_fdtd_cad): """ Launches FDTD CAD and stores a handle. """ self.fdtd = lumapi.FDTD(hide = hide_fdtd_cad) self.fdtd.cd(workingDir)
import sys, os, shutil, time, lumapi pattern = open("./DBS_lsf/DBS20by20_ini.txt", 'r').read().replace('\n', '') # Build fundamental structure # Set initial dot distribution & clone the pattern with fsp shutil.copy(f"{workPath}/DBS_lsf/DBS20by20_ini.txt", "./optPath/initial.txt") shutil.copy(f"{workPath}/DBS_lsf/DBS20by20_ini.txt", "./optPath/Gen1/p0/inherit.txt") while True: try: fdtd = lumapi.FDTD(hide=True) fdtd.eval(open(f"{workPath}/DBS_lsf/setBase.lsf", 'r').read()) for i in range(Nx*Ny): if pattern[i] == '1': dot_y = 60 + (19-i//20) * 120 dot_x = 60 + i % 20 * 120 fdtd.eval(add_dot.replace("{Number}", f"{i}").replace("dot_x", f"{dot_x}").replace("dot_y", f"{dot_y}")) # Set Simulation region & run fdtd.eval(open(f"{workPath}/DBS_lsf/setSimu.lsf", 'r').read()) fdtd.save(f"{workPath}/optPath/initial.fsp") break except: print(f"eval build ini fsp failed") print("initial fsp is settled.")
def __init__(self, core_thickness, hideGUI=True): self.fdtd = lumapi.FDTD(hide=hideGUI) self.h_total = core_thickness materials.make_Si_nasa(self.fdtd)
def lumerical_fdtd(args): # open file if os.path.isfile("api.lms"): os.remove("api.lms") fdtd = lm.FDTD(hide=True) fdtd.save("api.lms") fdtd.deleteall() # define parameters vlength = args.length * 1e-6 vwidth1 = args.width1 * 1e-6 vwidth2 = args.width2 * 1e-6 vthickness = args.thickness * 1e-6 vnum_periods = args.num_periods vnum_wavelengths = args.num_wavelengths # define cladding fdtd.addrect() fdtd.set("name", "cladding") fdtd.set("x", vlength * vnum_periods) fdtd.set("x span", 4 * vlength * 20 + vnum_periods * vlength * 2) fdtd.set("y", 0) fdtd.set("y span", 10 * np.max([vwidth1, vwidth2])) fdtd.set("z", 0) fdtd.set("z span", 10 * vthickness) fdtd.set("material", "SiO2 (Glass) - Palik") # define input waveguide fdtd.addrect() fdtd.set("name", "core1_input") fdtd.set("x", -vlength - vlength * 10) fdtd.set("x span", vlength * 20) fdtd.set("y", 0) fdtd.set("y span", vwidth1) fdtd.set("z", 0) fdtd.set("z span", vthickness) fdtd.set("material", "Si (Silicon) - Palik") # Define grating for i in range(vnum_periods): # define core block 1 fdtd.addrect() fdtd.set("name", "core1") fdtd.set("x", -0.5 * vlength + 2 * vlength * i) fdtd.set("x span", vlength) fdtd.set("y", 0) fdtd.set("y span", vwidth1) fdtd.set("z", 0) fdtd.set("z span", vthickness) fdtd.set("material", "Si (Silicon) - Palik") # define core block 2 fdtd.addrect() fdtd.set("name", "core2") fdtd.set("x", 0.5 * vlength + 2 * vlength * i) fdtd.set("x span", vlength) fdtd.set("y", 0) fdtd.set("y span", vwidth2) fdtd.set("z", 0) fdtd.set("z span", vthickness) fdtd.set("material", "Si (Silicon) - Palik") # define output waveguide fdtd.addrect() fdtd.set("name", "core1_input") fdtd.set("x", -vlength + 2 * vlength * vnum_periods + vlength * 10) fdtd.set("x span", vlength * 20) fdtd.set("y", 0) fdtd.set("y span", vwidth2) fdtd.set("z", 0) fdtd.set("z span", vthickness) fdtd.set("material", "Si (Silicon) - Palik") # setup source fdtd.addmode() fdtd.set("wavelength start", 1.45e-6) fdtd.set("wavelength stop", 1.65e-6) fdtd.set("frequency dependent profile", 1) fdtd.set("number of field profile samples", vnum_wavelengths) fdtd.set("x", -vlength - vlength * 10) fdtd.set("y", 0) fdtd.set("y span", np.max([vwidth1, vwidth2]) * 3) fdtd.set("z", 0) fdtd.set("z span", vthickness * 3) # setup fdtd fdtd.addfdtd() fdtd.set("x", vlength * vnum_periods - 1 * vlength) fdtd.set("x span", 2 * vlength * 15 + vnum_periods * vlength * 2) fdtd.set("y", 0) fdtd.set("y span", 5 * np.max([vwidth1, vwidth2])) fdtd.set("z", 0) fdtd.set("z span", 5 * vthickness) fdtd.set("mesh accuracy", 4) # setup monitor fdtd.addpower() fdtd.setglobalmonitor("frequency points", vnum_wavelengths) fdtd.set("monitor type", "2D X-normal") fdtd.set("x", -vlength + 2 * vlength * vnum_periods + vlength * 10) fdtd.set("y", 0) fdtd.set("y span", np.max([vwidth1, vwidth2]) * 3) fdtd.set("z", 0) fdtd.set("z span", vthickness * 3) # run fdtd.run() # postprocess f = fdtd.getdata("monitor", "f") l = 3e8 / f spect = fdtd.getresult("source", "spectrum") spectrum = np.abs(spect["spectrum"])**2 lambd = spect["lambda"] spectrum = fdtd.interp(spectrum, lambd, l) power = np.abs(fdtd.getdata("monitor", "power"))**2 * spectrum power = power / np.max(power) new_lambd = np.linspace(1.5, 1.6, args.num_wavelengths) * 1e-6 power = fdtd.interp(power, l, new_lambd) return power
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
def gc2d( session=None, period=0.66e-6, ff=0.5, gap_width_list=None, n_gratings=50, wg_height=220e-9, etch_depth=70e-9, box_height=2e-6, clad_height=2e-6, substrate_height=2e-6, material_wg="si", material_wafer="si", material_clad="sio2", material_box="sio2", gc_xmin=-3e-6, fiber_angle_deg=20, wavelength=1550e-9, wavelength_span=300e-9, # wavelength span base_fsp_path=str(CONFIG["grating_coupler_2D"]), ): """ draw 2D grating coupler gap_width_list overrides (period, ff and n_gratings) """ for material in [material_wg, material_box, material_clad, material_wafer]: if material not in materials: raise ValueError(f"{material} not in {list(materials.keys())}") material_wg = materials[material_wg] material_wafer = materials[material_wafer] material_clad = materials[material_clad] material_box = materials[material_box] import lumapi assert ff < 1, f"fill factor {ff:.3f} is the ratio of period/maxHeigh" s = session or lumapi.FDTD(hide=False) s.newproject() s.selectall() s.deleteall() s.load(base_fsp_path) s.select("fiber") s.set("theta", fiber_angle_deg) s.select("") s.set("lambda0", wavelength) s.setglobalsource("center wavelength", wavelength) s.setglobalsource("wavelength span", wavelength_span) # s.select("FDTD") # s.set("set simulation bandwidth", True) # s.set("simulation wavelength min", wavelength - wavelength_span / 2) # s.set("simulation wavelength max", wavelength + wavelength_span / 2) gap = period * (1 - ff) # etched region of the grating s.addrect() s.set("name", "GC_base") s.set("material", material_wg) s.set("x min", gc_xmin) s.set("x max", (n_gratings + 1) * period + gc_xmin) s.set("y", 0.5 * (wg_height - etch_depth)) s.set("y span", wg_height - etch_depth) # add GC teeth; gap_width_list = gap_width_list or [(gap, ff * period)] * n_gratings xmin = gc_xmin for gap, width in gap_width_list: s.addrect() s.set("name", "GC_tooth") s.set("material", material_wg) s.set("y min", 0) s.set("y max", wg_height) s.set("x min", xmin + gap) s.set("x max", xmin + gap + width) xmin += gap + width s.selectpartial("GC") s.addtogroup("GC") # draw silicon substrate; s.addrect() s.set("name", "substrate") s.set("material", material_wafer) s.set("x max", 30e-6) s.set("x min", -20e-6) s.set("y", -1 * (box_height + 0.5 * substrate_height)) s.set("y span", substrate_height) s.set("alpha", 0.2) s.addrect() # draw burried oxide; s.set("name", "BOX") s.set("material", material_box) s.set("x max", 30e-6) s.set("x min", -20e-6) s.set("y min", -box_height) s.set("y max", clad_height) s.set("override mesh order from material database", True) s.set("mesh order", 3) s.set("alpha", 0.3) s.addrect() # draw waveguide; s.set("name", "WG") s.set("material", material_wg) s.set("x min", -20e-6) s.set("x max", gc_xmin) s.set("y min", 0) s.set("y max", wg_height) return dict(session=s)
def write( component, session=None, run=True, overwrite=False, dirpath=pp.CONFIG["sp"], height_nm=220, **settings, ): """ writes Sparameters from a gdsfactory component using Lumerical FDTD 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 height_nm: height layer2nm: dict of {(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: results: dict(wavelength_nm, S11, S12 ...) after simulation, or if simulation exists and returns the Sparameters directly """ if hasattr(component, "simulation_settings"): settings.update(component.simulation_settings) sim_settings = s = dict( layer2nm=layer2nm, layer2material=layer2material, remove_layers=[pp.LAYER.WGCLAD], background_material="sio2", port_width=3e-6, port_height=1.5e-6, port_extension_um=1, mesh_accuracy=2, zmargin=1e-6, ymargin=2e-6, wavelength_start=1.2e-6, wavelength_stop=1.6e-6, wavelength_points=500, ) for setting in settings.keys(): assert ( setting in s ), f"`{setting}` is not a valid setting ({list(settings.keys())})" s.update(**settings) ss = namedtuple("sim_settings", s.keys())(*s.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) filepath = component.get_sparameters_path(dirpath=dirpath, height_nm=height_nm) filepath_json = filepath.with_suffix(".json") filepath_sim_settings = filepath.with_suffix(".settings.json") filepath_fsp = filepath.with_suffix(".fsp") if run and filepath_json.exists() and not overwrite: return json.loads(open(filepath_json).read()) if not run and session is None: print(""" you need to pass `run=True` flag to run the simulation To debug, you can create a lumerical FDTD session and pass it to the simulator ``` import lumapi s = lumapi.FDTD() import pp c = pp.c.waveguide() # or whatever you want to simulate pp.sp.write(component=c, run=False, session=s) ``` """) 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 import lumapi 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, ) layers = component.get_layers() 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} results = {"wavelength_nm": list(sp["lambda"].flatten() * 1e9)} results.update(ra) results.update(rm) with open(filepath_json, "w") as f: json.dump(results, f) with open(filepath_sim_settings, "w") as f: s = sim_settings s["layer2nm"] = [ f"{k[0]}_{k[1]}_{v}" for k, v in s["layer2nm"].items() ] s["layer2material"] = [ f"{k[0]}_{k[1]}_{v}" for k, v in s["layer2material"].items() ] json.dump(s, f) return results
ending_number = starting_number for i in range(starting_number, ending_number + 1): # Can use print("{:02d}".format(1)) to turn a 1 into a '01' # string. 111 -> 111 still, in case nodes hit triple-digits. output_arr.append(prefix + "{:02d}".format(i)) return output_arr # # Code from Conner // END # # # Create FDTD hook # fdtd_hook = lumapi.FDTD(hide=True) num_nodes_available = int(sys.argv[1]) num_cpus_per_node = 8 cluster_hostnames = get_slurm_node_list() # configure_resources_for_cluster( fdtd_hook, slurm_list, N_resources=num_nodes_to_use, N_threads_per_resource=num_cpus_per_node ) # # Create project folder and save out the parameter file for documentation for this optimization # python_src_directory = os.path.abspath( os.path.join(os.path.dirname(__file__), '.')) projects_directory_location = "/central/groups/Faraon_Computing/projects" projects_directory_location += "/" + project_name
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