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)
Exemple #3
0
 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)
Exemple #5
0
 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
Exemple #8
0
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)
Exemple #10
0
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')
Exemple #12
0
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
Exemple #13
0
 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)
Exemple #14
0
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.")
Exemple #15
0
 def __init__(self, core_thickness, hideGUI=True):
     self.fdtd = lumapi.FDTD(hide=hideGUI)
     self.h_total = core_thickness
     materials.make_Si_nasa(self.fdtd)
Exemple #16
0
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
Exemple #18
0
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)
Exemple #19
0
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
Exemple #20
0
                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
Exemple #21
0
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