Esempio n. 1
0
def read_sparameters_lumerical(
    component: Optional[Component] = None,
    layer_stack: LayerStack = LAYER_STACK,
    filepath: Optional[str] = None,
    numports: Optional[int] = None,
    dirpath: Path = gf.CONFIG["sparameters"],
) -> Tuple[List[str], np.array, np.ndarray]:
    r"""Returns Sparameters from Lumerical interconnect .DAT file.

    Args:
        component: Component
        layer_stack:
        filepath:
        numports: number of ports
        dirpath: path where to look for the Sparameters

    Returns:
        port_names: list of port labels
        F: frequency 1d np.array
        S: Sparameters np.ndarray matrix


    the Sparameters file have Lumerical format
    https://support.lumerical.com/hc/en-us/articles/360036107914-Optical-N-Port-S-Parameter-SPAR-INTERCONNECT-Element#toc_5
    """

    if component is None and filepath is None:
        raise ValueError("You need to define the filepath or the component")

    if filepath and numports is None:
        raise ValueError("You need to define numports")

    filepath = filepath or get_sparameters_path(
        component=component,
        dirpath=dirpath,
        layer_to_material=layer_stack.get_layer_to_material(),
        layer_to_thickness=layer_stack.get_layer_to_thickness(),
    )
    numports = numports or len(component.ports)
    assert (filepath.exists()
            ), f"Sparameters for {component.name} not found in {filepath}"
    assert numports > 1, f"number of ports = {numports} and needs to be > 1"
    return _read_sparameters_file(filepath=filepath, numports=numports)
Esempio n. 2
0
def read_sparameters_pandas(
    component: Component,
    layer_stack: LayerStack = LAYER_STACK,
    dirpath: Path = gf.CONFIG["sparameters"],
) -> pd.DataFrame:
    """Returns Sparameters in a pandas DataFrame.

    `S11m` `m` stands for module `S11a` `a` stands for angle (in radians)

    Args:
        component: Component
        layer_stack:
        dirpath: path where to look for the Sparameters

    """
    filepath = get_sparameters_path(
        component=component,
        dirpath=dirpath,
        layer_to_material=layer_stack.get_layer_to_material(),
        layer_to_thickness=layer_stack.get_layer_to_thickness(),
    )
    df = pd.read_csv(filepath.with_suffix(".csv"))
    df.index = df["wavelength_nm"]
    return df
Esempio n. 3
0
def write_sparameters_meep(
    component: Component,
    port_symmetries: Optional[PortSymmetries] = None,
    resolution: int = 20,
    wl_min: float = 1.5,
    wl_max: float = 1.6,
    wl_steps: int = 50,
    dirpath: Path = sparameters_path,
    layer_stack: LayerStack = LAYER_STACK,
    port_margin: float = 2,
    port_monitor_offset: float = -0.1,
    port_source_offset: float = -0.1,
    filepath: Optional[Path] = None,
    overwrite: bool = False,
    animate: bool = False,
    lazy_parallelism: bool = False,
    run: bool = True,
    dispersive: bool = False,
    xmargin: float = 0,
    ymargin: float = 0,
    xmargin_left: float = 0,
    xmargin_right: float = 0,
    ymargin_top: float = 0,
    ymargin_bot: float = 0,
    **settings,
) -> pd.DataFrame:
    r"""Compute Sparameters and writes them to a CSV filepath.
    Simulates each time using a different input port (by default, all of them)
    unless you specify port_symmetries:

    port_symmetries = {"o1":
            {
                "s11": ["s22","s33","s44"],
                "s21": ["s21","s34","s43"],
                "s31": ["s13","s24","s42"],
                "s41": ["s14","s23","s32"],
            }
        }
    - Only simulations using the outer key port names will be run
    - The associated value is another dict whose keys are the S-parameters computed
        when this source is active
    - The values of this inner Dict are lists of s-parameters whose values are copied

    This allows you doing less simulations

    TODO: automate this for common component types
    (geometrical symmetries, reciprocal materials, etc.)

    TODO: enable other port naming conventions, such as (in0, in1, out0, out1)


    .. code::

         top view
              ________________________________
             |                               |
             | xmargin_left                  | port_extension
             |<------>          port_margin ||<-->
          ___|___________          _________||___
             |           \        /          |
             |            \      /           |
             |             ======            |
             |            /      \           |
          ___|___________/        \__________|___
             |   |                 <-------->|
             |   |ymargin_bot   xmargin_right|
             |   |                           |
             |___|___________________________|

        side view
              ________________________________
             |                     |         |
             |                     |         |
             |                   zmargin_top |
             |ymargin              |         |
             |<---> _____         _|___      |
             |     |     |       |     |     |
             |     |     |       |     |     |
             |     |_____|       |_____|     |
             |       |                       |
             |       |                       |
             |       |zmargin_bot            |
             |       |                       |
             |_______|_______________________|



    Args:
        component: to simulate.
        resolution: in pixels/um (20: for coarse, 120: for fine)
        port_symmetries: Dict to specify port symmetries, to save number of simulations
        source_ports: list of port string names to use as sources
        dirpath: directory to store Sparameters
        layer_stack: LayerStack class
        port_margin: margin on each side of the port
        port_monitor_offset: offset between monitor Component port and monitor MEEP port
        port_source_offset: offset between source Component port and source MEEP port
        filepath: to store pandas Dataframe with Sparameters in CSV format.
            Defaults to dirpath/component_.csv
        overwrite: overwrites stored simulation results.
        animate: saves a MP4 images of the simulation for inspection, and also
            outputs during computation. The name of the file is the source index
        lazy_parallelism: toggles the flag "meep.divide_parallel_processes" to
            perform the simulations with different sources in parallel
        run: runs simulation, if False, only plots simulation
        dispersive: use dispersive models for materials (requires higher resolution)
        xmargin: left and right distance from component to PML.
        xmargin_left: west distance from component to PML.
        xmargin_right: east distance from component to PML.
        ymargin: top and bottom distance from component to PML.
        ymargin_top: north distance from component to PML.
        ymargin_bot: south distance from component to PML.

    keyword Args:
        extend_ports_length: to extend ports beyond the PML (um).
        zmargin_top: thickness for cladding above core (um).
        zmargin_bot: thickness for cladding below core (um)
        tpml: PML thickness (um).
        clad_material: material for cladding.
        is_3d: if True runs in 3D
        wl_min: wavelength min (um).
        wl_max: wavelength max (um).
        wl_steps: wavelength steps
        dfcen: delta frequency
        port_source_name: input port name
        port_field_monitor_name:
        port_margin: margin on each side of the port (um).
        distance_source_to_monitors: in (um).
        port_source_offset: offset between source Component port and source MEEP port
        port_monitor_offset: offset between monitor Component port and monitor MEEP port

    Returns:
        sparameters in a pandas Dataframe (wavelengths, s11a, s12m, ...)
            where `a` is the angle in radians and `m` the module

    """

    port_symmetries = port_symmetries or {}

    xmargin_left = xmargin_left or xmargin
    xmargin_right = xmargin_right or xmargin

    ymargin_top = ymargin_top or ymargin
    ymargin_bot = ymargin_bot or ymargin

    sim_settings = dict(
        resolution=resolution,
        port_symmetries=port_symmetries,
        wl_min=wl_min,
        wl_max=wl_max,
        wl_steps=wl_steps,
        port_margin=port_margin,
        port_monitor_offset=port_monitor_offset,
        port_source_offset=port_source_offset,
        dispersive=dispersive,
        ymargin_top=ymargin_top,
        ymargin_bot=ymargin_bot,
        xmargin_left=xmargin_left,
        xmargin_right=xmargin_right,
        **settings,
    )

    filepath = filepath or get_sparameters_path(
        component=component,
        dirpath=dirpath,
        layer_stack=layer_stack,
        **sim_settings,
    )

    sim_settings = sim_settings.copy()
    sim_settings["layer_stack"] = layer_stack.to_dict()
    sim_settings["component"] = component.to_dict()
    filepath = pathlib.Path(filepath)
    filepath_sim_settings = filepath.with_suffix(".yml")

    # filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings))
    # logger.info(f"Write simulation settings to {filepath_sim_settings!r}")
    # return filepath_sim_settings

    component = gf.add_padding_container(
        component,
        default=0,
        top=ymargin_top,
        bottom=ymargin_bot,
        left=xmargin_left,
        right=xmargin_right,
    )

    if not run:
        sim_dict = get_simulation(
            component=component,
            wl_min=wl_min,
            wl_max=wl_max,
            wl_steps=wl_steps,
            layer_stack=layer_stack,
            port_margin=port_margin,
            port_monitor_offset=port_monitor_offset,
            port_source_offset=port_source_offset,
            **settings,
        )
        sim_dict["sim"].plot2D(plot_eps_flag=True)
        plt.show()
        return

    if filepath.exists() and not overwrite:
        logger.info(f"Simulation loaded from {filepath!r}")
        return pd.read_csv(filepath)

    # Parse ports (default)
    monitor_indices = []
    source_indices = []
    component_ref = component.ref()
    for port_name in component_ref.ports.keys():
        if component_ref.ports[port_name].port_type == "optical":
            monitor_indices.append(re.findall("[0-9]+", port_name)[0])
    if bool(port_symmetries):  # user-specified
        for port_name in port_symmetries.keys():
            source_indices.append(re.findall("[0-9]+", port_name)[0])
    else:  # otherwise cycle through all
        source_indices = monitor_indices

    # Create S-parameter storage object
    sp = {}

    @pydantic.validate_arguments
    def sparameter_calculation(
        n,
        component: Component,
        port_symmetries: Optional[PortSymmetries] = port_symmetries,
        monitor_indices: Tuple = monitor_indices,
        wl_min: float = wl_min,
        wl_max: float = wl_max,
        wl_steps: int = wl_steps,
        dirpath: Path = dirpath,
        animate: bool = animate,
        dispersive: bool = dispersive,
        **settings,
    ) -> Dict:

        sim_dict = get_simulation(
            component=component,
            port_source_name=f"o{monitor_indices[n]}",
            resolution=resolution,
            wl_min=wl_min,
            wl_max=wl_max,
            wl_steps=wl_steps,
            port_margin=port_margin,
            port_monitor_offset=port_monitor_offset,
            port_source_offset=port_source_offset,
            dispersive=dispersive,
            **settings,
        )

        sim = sim_dict["sim"]
        monitors = sim_dict["monitors"]
        # freqs = sim_dict["freqs"]
        # wavelengths = 1 / freqs
        # print(sim.resolution)

        # Make termination when field decayed enough across ALL monitors
        termination = []
        for monitor_name in monitors:
            termination.append(
                mp.stop_when_fields_decayed(
                    dt=50,
                    c=mp.Ez,
                    pt=monitors[monitor_name].regions[0].center,
                    decay_by=1e-9,
                ))

        if animate:
            sim.use_output_directory()
            animate = mp.Animate2D(
                sim,
                fields=mp.Ez,
                realtime=True,
                field_parameters={
                    "alpha": 0.8,
                    "cmap": "RdBu",
                    "interpolation": "none",
                },
                eps_parameters={"contour": True},
                normalize=True,
            )
            sim.run(mp.at_every(1, animate), until_after_sources=termination)
            animate.to_mp4(30, monitor_indices[n] + ".mp4")
        else:
            sim.run(until_after_sources=termination)
        # call this function every 50 time spes
        # look at simulation and measure Ez component
        # when field_monitor_point decays below a certain 1e-9 field threshold

        # Calculate mode overlaps
        # Get source monitor results
        component_ref = component.ref()
        source_entering, source_exiting = parse_port_eigenmode_coeff(
            monitor_indices[n], component_ref.ports, sim_dict)
        # Get coefficients
        for monitor_index in monitor_indices:
            j = monitor_indices[n]
            i = monitor_index
            if monitor_index == monitor_indices[n]:
                sii = source_exiting / source_entering
                siia = np.unwrap(np.angle(sii))
                siim = np.abs(sii)
                sp[f"s{i}{i}a"] = siia
                sp[f"s{i}{i}m"] = siim
            else:
                monitor_entering, monitor_exiting = parse_port_eigenmode_coeff(
                    monitor_index, component_ref.ports, sim_dict)
                sij = monitor_exiting / source_entering
                sija = np.unwrap(np.angle(sij))
                sijm = np.abs(sij)
                sp[f"s{i}{j}a"] = sija
                sp[f"s{i}{j}m"] = sijm
                sij = monitor_entering / source_entering
                sija = np.unwrap(np.angle(sij))
                sijm = np.abs(sij)

        if bool(port_symmetries) is True:
            for key in port_symmetries[f"o{monitor_indices[n]}"].keys():
                values = port_symmetries[f"o{monitor_indices[n]}"][key]
                for value in values:
                    sp[f"{value}m"] = sp[f"{key}m"]
                    sp[f"{value}a"] = sp[f"{key}a"]

        return sp

    # Since source is defined upon sim object instanciation, loop here
    # for port_index in monitor_indices:

    num_sims = len(port_symmetries.keys()) or len(source_indices)
    if lazy_parallelism:
        from mpi4py import MPI

        cores = min([num_sims, multiprocessing.cpu_count()])
        n = mp.divide_parallel_processes(cores)
        comm = MPI.COMM_WORLD
        size = comm.Get_size()
        rank = comm.Get_rank()

        sp = sparameter_calculation(
            n,
            component=component,
            port_symmetries=port_symmetries,
            wl_min=wl_min,
            wl_max=wl_max,
            wl_steps=wl_steps,
            animate=animate,
            monitor_indices=monitor_indices,
            **settings,
        )
        # Synchronize dicts
        if rank == 0:
            for i in range(1, size, 1):
                data = comm.recv(source=i, tag=11)
                sp.update(data)

            df = pd.DataFrame(sp)
            df["wavelengths"] = np.linspace(wl_min, wl_max, wl_steps)
            df["freqs"] = 1 / df["wavelengths"]
            df.to_csv(filepath, index=False)
            logger.info(f"Write simulation results to {filepath!r}")
            filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings))
            logger.info(
                f"Write simulation settings to {filepath_sim_settings!r}")
            return df
        else:
            comm.send(sp, dest=0, tag=11)

    else:
        for n in tqdm(range(num_sims)):
            sp.update(
                sparameter_calculation(
                    n,
                    component=component,
                    port_symmetries=port_symmetries,
                    wl_min=wl_min,
                    wl_max=wl_max,
                    wl_steps=wl_steps,
                    animate=animate,
                    monitor_indices=monitor_indices,
                    **settings,
                ))
        df = pd.DataFrame(sp)
        df["wavelengths"] = np.linspace(wl_min, wl_max, wl_steps)
        df["freqs"] = 1 / df["wavelengths"]
        df.to_csv(filepath, index=False)

        logger.info(f"Write simulation results to {filepath!r}")
        filepath_sim_settings.write_text(OmegaConf.to_yaml(sim_settings))
        logger.info(f"Write simulation settings to {filepath_sim_settings!r}")
        return df
Esempio n. 4
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
Esempio n. 5
0
def write_sparameters_meep_mpi_pool(
    jobs: List[Dict],
    cores_per_run: int = 2,
    total_cores: int = 4,
    temp_dir: Path = temp_dir_default,
    delete_temp_files: bool = True,
    dirpath: Path = sparameters_path,
    layer_stack: LayerStack = LAYER_STACK,
    **kwargs,
) -> List[Path]:
    """Write Sparameters and returns the filepaths
    Given a list of write_sparameters_meep keyword arguments (the "jobs"),
        launches them in different cores
    Each simulation is assigned "cores_per_run" cores
    A total of "total_cores" is assumed, if cores_per_run * len(jobs) > total_cores
    then the overflow will run sequentially (not in parallel)

    Args
        jobs: list of Dicts containing the simulation settings for each job.
            for write_sparameters_meep
        cores_per_run: number of processors to assign to each component simulation
        total_cores: total number of cores to use
        temp_dir: temporary directory to hold simulation files
        delete_temp_files: deletes temp_dir when done
        dirpath: directory to store Sparameters
        layer_stack:

    keyword Args:
        overwrite: overwrites stored simulation results.
        dispersive: use dispersive models for materials (requires higher resolution)
        extend_ports_length: to extend ports beyond the PML
        zmargin_top: thickness for cladding above core
        zmargin_bot: thickness for cladding below core
        tpml: PML thickness (um)
        clad_material: material for cladding
        is_3d: if True runs in 3D
        wl_min: wavelength min (um)
        wl_max: wavelength max (um)
        wl_steps: wavelength steps
        dfcen: delta frequency
        port_source_name: input port name
        port_field_monitor_name:
        port_margin: margin on each side of the port
        distance_source_to_monitors: in (um) source goes before
        port_source_offset: offset between source GDS port and source MEEP port
        port_monitor_offset: offset between monitor GDS port and monitor MEEP port

    Returns:
        filepath list for sparameters CSV (wavelengths, s11a, s12m, ...)
            where `a` is the angle in radians and `m` the module

    """
    # Parse jobs
    jobs_to_run = []
    for job in jobs:
        settings = remove_simulation_kwargs(kwargs)
        filepath = job.get(
            "filepath",
            get_sparameters_path(
                component=job["component"],
                dirpath=dirpath,
                layer_stack=layer_stack,
                **settings,
            ),
        )
        if filepath.exists():
            job.update(**kwargs)
            if job.get("overwrite", False):
                pathlib.Path.unlink(filepath)
                logger.info(
                    f"Simulation {filepath!r} found and overwrite is True. "
                    "Deleting file and adding it to the queue."
                )
                jobs_to_run.append(job)
            else:
                logger.info(
                    f"Simulation {filepath!r} found exists and "
                    "overwrite is False. Removing it from the queue."
                )
        else:
            logger.info(f"Simulation {filepath!r} not found. Adding it to the queue")
            jobs_to_run.append(job)

    # Update jobs
    jobs = jobs_to_run

    # Setup pools
    num_pools = int(np.ceil(cores_per_run * len(jobs) / total_cores))
    jobs_per_pool = int(np.floor(total_cores / cores_per_run))
    njobs = len(jobs)

    logger.info(f"Running parallel simulations over {njobs} jobs")
    logger.info(
        f"Using a total of {total_cores} cores with {cores_per_run} cores per job"
    )
    logger.info(
        f"Tasks split amongst {num_pools} pools with up to {jobs_per_pool} jobs each."
    )

    i = 0
    # For each pool
    for j in range(num_pools):
        filepaths = []

        # For each job in the pool
        for k in range(jobs_per_pool):
            # Flag to catch non full pools
            if i >= njobs:
                continue
            logger.info(f"Task {k} of pool {j} is job {i}")

            # Obtain current job
            simulations_settings = jobs[i]

            filepath = write_sparameters_meep_mpi(
                cores=cores_per_run,
                temp_dir=temp_dir,
                temp_file_str=f"write_sparameters_meep_mpi_{i}",
                wait_to_finish=False,
                **simulations_settings,
            )
            filepaths.append(filepath)

            # Increment task number
            i += 1

        # Wait for pool to end
        done = False
        num_pool_jobs = len(filepaths)
        while not done:
            # Check if all jobs finished
            jobs_done = 0
            for filepath in filepaths:
                if filepath.exists():
                    jobs_done += 1
            if jobs_done == num_pool_jobs:
                done = True
            else:
                time.sleep(1)

    if delete_temp_files:
        shutil.rmtree(temp_dir)
    return filepaths
Esempio n. 6
0
def write_sparameters_meep_mpi(
    component: Component,
    cores: int = ncores,
    filepath: Optional[Path] = None,
    dirpath: Path = sparameters_path,
    layer_stack: LayerStack = LAYER_STACK,
    temp_dir: Path = temp_dir_default,
    temp_file_str: str = "write_sparameters_meep_mpi",
    overwrite: bool = False,
    wait_to_finish: bool = True,
    **kwargs,
) -> Path:
    """Write Sparameters using multiple cores and MPI
    and returns Sparameters CSV filepath.

    Simulates each time using a different input port (by default, all of them)
    unless you specify port_symmetries:

    port_symmetries = {"o1":
            {
                "s11": ["s22","s33","s44"],
                "s21": ["s21","s34","s43"],
                "s31": ["s13","s24","s42"],
                "s41": ["s14","s23","s32"],
            }
        }

    Args:
        component: gdsfactory Component.
        cores: number of processors.
        filepath: to store pandas Dataframe with Sparameters in CSV format.
            Defaults to dirpath/component_.csv
        dirpath: directory to store Sparameters
        layer_stack:
        temp_dir: temporary directory to hold simulation files.
        temp_file_str: names of temporary files in temp_dir.
        overwrite: overwrites stored simulation results.
        wait_to_finish:

    Keyword Args:
        resolution: in pixels/um (20: for coarse, 120: for fine)
        port_symmetries: Dict to specify port symmetries, to save number of simulations
        source_ports: list of port string names to use as sources
        dirpath: directory to store Sparameters
        layer_stack: LayerStack class
        port_margin: margin on each side of the port
        port_monitor_offset: offset between monitor GDS port and monitor MEEP port
        port_source_offset: offset between source GDS port and source MEEP port
        filepath: to store pandas Dataframe with Sparameters in CSV format.
        animate: saves a MP4 images of the simulation for inspection, and also
            outputs during computation. The name of the file is the source index
        lazy_parallelism: toggles the flag "meep.divide_parallel_processes" to
            perform the simulations with different sources in parallel
        dispersive: use dispersive models for materials (requires higher resolution)
        extend_ports_length: to extend ports beyond the PML
        layer_stack: Dict of layer number (int, int) to thickness (um)
        zmargin_top: thickness for cladding above core
        zmargin_bot: thickness for cladding below core
        tpml: PML thickness (um)
        clad_material: material for cladding
        is_3d: if True runs in 3D
        wl_min: wavelength min (um)
        wl_max: wavelength max (um)
        wl_steps: wavelength steps
        dfcen: delta frequency
        port_source_name: input port name
        port_field_monitor_name:
        port_margin: margin on each side of the port
        distance_source_to_monitors: in (um) source goes before
        port_source_offset: offset between source GDS port and source MEEP port
        port_monitor_offset: offset between monitor GDS port and monitor MEEP port

    Returns:
        filepath for sparameters CSV (wavelengths, s11a, s12m, ...)
            where `a` is the angle in radians and `m` the module
    """
    settings = remove_simulation_kwargs(kwargs)
    filepath = filepath or get_sparameters_path(
        component=component,
        dirpath=dirpath,
        layer_stack=layer_stack,
        **settings,
    )
    filepath = pathlib.Path(filepath)
    if filepath.exists() and not overwrite:
        logger.info(f"Simulation {filepath!r} already exists")
        return filepath

    # Save the component object to simulation for later retrieval
    temp_dir.mkdir(exist_ok=True, parents=True)
    tempfile = temp_dir / temp_file_str
    component_file = tempfile.with_suffix(".pkl")
    kwargs.update(filepath=str(filepath))

    with open(component_file, "wb") as outp:
        pickle.dump(component, outp, pickle.HIGHEST_PROTOCOL)

    # Write execution file
    script_lines = [
        "import pickle\n",
        "from gdsfactory.simulation.gmeep import write_sparameters_meep\n\n",
        'if __name__ == "__main__":\n\n',
        f"\twith open(\"{component_file}\", 'rb') as inp:\n",
        "\t\tcomponent = pickle.load(inp)\n\n"
        "\twrite_sparameters_meep(component = component,\n",
    ]
    for key in kwargs.keys():
        script_lines.append(f"\t\t{key} = {kwargs[key]!r},\n")

    script_lines.append("\t)")
    script_file = tempfile.with_suffix(".py")
    script_file_obj = open(script_file, "w")
    script_file_obj.writelines(script_lines)
    script_file_obj.close()

    command = f"mpirun -np {cores} python {script_file}"
    logger.info(command)
    logger.info(str(filepath))

    subprocess.Popen(
        shlex.split(command),
        shell=False,
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
    )
    if wait_to_finish:
        while not filepath.exists():
            time.sleep(1)

    return filepath