예제 #1
0
def add_pins_container(
    component: Component,
    add_pins_function=add_pins_triangle,
) -> Component:
    c = Component()
    ref = c << component
    add_pins_function(component=c, reference=ref)
    c.ref = ref
    return c
예제 #2
0
    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
예제 #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
예제 #4
0
def get_simulation(
    component: Component,
    extend_ports_length: Optional[float] = 4.0,
    layer_stack: LayerStack = LAYER_STACK,
    res: int = 20,
    t_clad_top: float = 1.0,
    t_clad_bot: float = 1.0,
    tpml: float = 1.0,
    clad_material: str = "SiO2",
    is_3d: bool = False,
    wl_min: float = 1.5,
    wl_max: float = 1.6,
    wl_steps: int = 50,
    dfcen: float = 0.2,
    port_source_name: str = 1,
    port_field_monitor_name: str = 2,
    port_margin: float = 0.5,
    distance_source_to_monitors: float = 0.2,
) -> Dict[str, Any]:
    """Returns Simulation dict from gdsfactory.component

    based on meep directional coupler example
    https://meep.readthedocs.io/en/latest/Python_Tutorials/GDSII_Import/

    https://support.lumerical.com/hc/en-us/articles/360042095873-Metamaterial-S-parameter-extraction

    Args:
        component: gf.Component
        extend_ports_function: function to extend the ports for a component to ensure it goes beyond the PML
        layer_to_thickness: Dict of layer number (int, int) to thickness (um)
        res: resolution (pixels/um) For example: (10: 100nm step size)
        t_clad_top: thickness for cladding above core
        t_clad_bot: thickness for cladding below core
        tpml: PML thickness (um)
        clad_material: material for cladding
        is_3d: if True runs in 3D
        wavelengths: iterable of wavelengths to simulate
        dfcen: delta frequency
        sidewall_angle: in degrees
        port_source_name: input port name
        port_field_monitor_name:
        port_margin: margin on each side of the port
        distance_source_to_monitors: in (um) source goes before

    Returns:
        sim: simulation object

    Make sure you visualize the simulation region with gf.before you simulate a component

    .. code::

        import gdsfactory as gf
        import gmeep as gm

        c = gf.components.bend_circular()
        margin = 2
        cm = gm.add_monitors(c)
        gf.show(cm)

    """
    layer_to_thickness = layer_stack.get_layer_to_thickness()
    layer_to_material = layer_stack.get_layer_to_material()
    layer_to_zmin = layer_stack.get_layer_to_zmin()
    layer_to_sidewall_angle = layer_stack.get_layer_to_sidewall_angle()

    wavelengths = np.linspace(wl_min, wl_max, wl_steps)
    if port_source_name not in component.ports:
        warnings.warn(
            f"port_source_name={port_source_name} not in {component.ports.keys()}"
        )
        port_source = component.get_ports_list()[0]
        port_source_name = port_source.name
        warnings.warn(f"Selecting port_source_name={port_source_name} instead.")

    if port_field_monitor_name not in component.ports:
        warnings.warn(
            f"port_field_monitor_name={port_field_monitor_name} not in {component.ports.keys()}"
        )
        port_field_monitor = (
            component.get_ports_list()[0]
            if len(component.ports) < 2
            else component.get_ports_list()[1]
        )
        port_field_monitor_name = port_field_monitor.name
        warnings.warn(
            f"Selecting port_field_monitor_name={port_field_monitor_name} instead."
        )

    assert isinstance(
        component, Component
    ), f"component needs to be a gf.Component, got Type {type(component)}"

    component_extended = (
        gf.components.extension.extend_ports(
            component=component, length=extend_ports_length, centered=True
        )
        if extend_ports_length
        else component
    )

    component = component.ref()
    component.x = 0
    component.y = 0

    gf.show(component_extended)

    component_extended.flatten()
    component_extended = component_extended.ref()

    # geometry_center = [component_extended.x, component_extended.y]
    # geometry_center = [0, 0]
    # print(geometry_center)

    layers_thickness = [
        layer_to_thickness[layer]
        for layer in component.get_layers()
        if layer in layer_to_thickness
    ]

    t_core = max(layers_thickness)
    cell_thickness = tpml + t_clad_bot + t_core + t_clad_top + tpml if is_3d else 0

    cell_size = mp.Vector3(
        component.xsize + 2 * tpml,
        component.ysize + 2 * tpml,
        cell_thickness,
    )

    geometry = []
    layer_to_polygons = component_extended.get_polygons(by_spec=True)
    for layer, polygons in layer_to_polygons.items():
        if layer in layer_to_thickness and layer in layer_to_material:
            height = layer_to_thickness[layer] if is_3d else mp.inf
            zmin_um = layer_to_zmin[layer] if is_3d else 0
            # center = mp.Vector3(0, 0, (zmin_um + height) / 2)

            for polygon in polygons:
                vertices = [mp.Vector3(p[0], p[1], zmin_um) for p in polygon]
                material_name = layer_to_material[layer]
                material = get_material(name=material_name)
                geometry.append(
                    mp.Prism(
                        vertices=vertices,
                        height=height,
                        sidewall_angle=layer_to_sidewall_angle[layer],
                        material=material,
                        # center=center
                    )
                )

    freqs = 1 / wavelengths
    fcen = np.mean(freqs)
    frequency_width = dfcen * fcen

    # Add source
    port = component.ports[port_source_name]
    angle = port.orientation
    width = port.width + 2 * port_margin
    size_x = width * abs(np.sin(angle * np.pi / 180))
    size_y = width * abs(np.cos(angle * np.pi / 180))
    size_x = 0 if size_x < 0.001 else size_x
    size_y = 0 if size_y < 0.001 else size_y
    size_z = cell_thickness - 2 * tpml if is_3d else 20
    size = [size_x, size_y, size_z]
    center = port.center.tolist() + [0]  # (x, y, z=0)

    field_monitor_port = component.ports[port_field_monitor_name]
    field_monitor_point = field_monitor_port.center.tolist() + [0]  # (x, y, z=0)

    sources = [
        mp.EigenModeSource(
            src=mp.GaussianSource(fcen, fwidth=frequency_width),
            size=size,
            center=center,
            eig_band=1,
            eig_parity=mp.NO_PARITY if is_3d else mp.EVEN_Y + mp.ODD_Z,
            eig_match_freq=True,
        )
    ]

    sim = mp.Simulation(
        resolution=res,
        cell_size=cell_size,
        boundary_layers=[mp.PML(tpml)],
        sources=sources,
        geometry=geometry,
        default_material=get_material(name=clad_material),
        # geometry_center=geometry_center,
    )

    # Add port monitors dict
    monitors = {}
    for port_name in component.ports.keys():
        port = component.ports[port_name]
        angle = port.orientation
        width = port.width + 2 * port_margin
        size_x = width * abs(np.sin(angle * np.pi / 180))
        size_y = width * abs(np.cos(angle * np.pi / 180))
        size_x = 0 if size_x < 0.001 else size_x
        size_y = 0 if size_y < 0.001 else size_y
        size = mp.Vector3(size_x, size_y, size_z)
        size = [size_x, size_y, size_z]

        # if monitor has a source move monitor inwards
        length = -distance_source_to_monitors if port_name == port_source_name else 0
        xy_shifted = move_polar_rad_copy(
            np.array(port.center), angle=angle * np.pi / 180, length=length
        )
        center = xy_shifted.tolist() + [0]  # (x, y, z=0)
        m = sim.add_mode_monitor(freqs, mp.ModeRegion(center=center, size=size))
        m.z = 0
        monitors[port_name] = m
    return dict(
        sim=sim,
        cell_size=cell_size,
        freqs=freqs,
        monitors=monitors,
        sources=sources,
        field_monitor_point=field_monitor_point,
        port_source_name=port_source_name,
    )
예제 #5
0
def get_simulation(
    component: Component,
    mode_index: int = 0,
    n_modes: int = 2,
    port_extension: Optional[float] = 4.0,
    layer_stack: LayerStack = LAYER_STACK,
    zmargin: float = 1.0,
    thickness_pml: float = 1.0,
    clad_material: str = "SiO2",
    port_source_name: str = "o1",
    port_margin: float = 0.5,
    distance_source_to_monitors: float = 0.2,
    mesh_step: float = 40e-3,
    wavelength: float = 1.55,
) -> td.Simulation:
    """Returns Simulation object from gdsfactory.component

    based on GDS example
    https://simulation.cloud/docs/html/examples/ParameterScan.html

    Args:
        component: gf.Component
        mode_index: mode index
        n_modes: number of modes
        port_extension: extend ports beyond the PML
        layer_stack: contains layer numbers (int, int) to thickness, zmin
        zmargin: thickness for cladding above and below core
        thickness_pml: PML thickness (um)
        clad_material: material for cladding
        port_source_name: input port name
        port_margin: margin on each side of the port
        distance_source_to_monitors: in (um) source goes before monitors
        mesh_step: in all directions
        wavelength: in (um)

    You can visualize the simulation with gdsfactory


    .. code::

        import matplotlib.pyplot as plt
        import gdsfactory as gf
        import gdsfactory.simulation.tidy3d as gm

        c = gf.components.bend_circular()
        sim = gm.get_simulation(c)
        gm.plot_simulation(sim)

    """
    layer_to_thickness = layer_stack.get_layer_to_thickness()
    layer_to_material = layer_stack.get_layer_to_material()
    layer_to_zmin = layer_stack.get_layer_to_zmin()
    # layer_to_sidewall_angle = layer_stack.get_layer_to_sidewall_angle()

    assert isinstance(
        component, Component
    ), f"component needs to be a gf.Component, got Type {type(component)}"
    if port_source_name not in component.ports:
        warnings.warn(
            f"port_source_name={port_source_name} not in {component.ports.keys()}"
        )
        port_source = component.get_ports_list()[0]
        port_source_name = port_source.name
        warnings.warn(
            f"Selecting port_source_name={port_source_name} instead.")

    component_extended = (gf.components.extension.extend_ports(
        component=component, length=port_extension, centered=True)
                          if port_extension else component)

    gf.show(component_extended)
    component_extended.flatten()
    component_extended_ref = component_extended.ref()

    component_ref = component.ref()
    component_ref.x = 0
    component_ref.y = 0

    structures = [
        td.Box(
            material=get_material(name=clad_material),
            size=(td.inf, td.inf, td.inf),
            center=(0, 0, 0),
        )
    ]
    layers_thickness = [
        layer_to_thickness[layer] for layer in component.get_layers()
        if layer in layer_to_thickness
    ]

    t_core = max(layers_thickness)
    cell_thickness = thickness_pml + t_core + thickness_pml + 2 * zmargin
    sim_size = [
        component_ref.xsize + 2 * thickness_pml,
        component_ref.ysize + 2 * thickness_pml,
        cell_thickness,
    ]

    for layer in component.layers:
        if layer in layer_to_thickness and layer in layer_to_material:
            height = layer_to_thickness[layer]
            zmin = layer_to_zmin[layer]
            z_cent = zmin + height / 2
            material_name = MATERIAL_NAME_TO_TIDY3D[layer_to_material[layer]]
            material = get_material(name=material_name)

            geometry = td.GdsSlab(
                material=material,
                gds_cell=component_extended_ref,
                gds_layer=layer[0],
                gds_dtype=layer[1],
                z_cent=z_cent,
                z_size=height,
            )
            structures.append(geometry)

    # Add source
    port = component_ref.ports[port_source_name]
    angle = port.orientation
    width = port.width + 2 * port_margin
    size_x = width * abs(np.sin(angle * np.pi / 180))
    size_y = width * abs(np.cos(angle * np.pi / 180))
    size_x = 0 if size_x < 0.001 else size_x
    size_y = 0 if size_y < 0.001 else size_y
    size_z = cell_thickness - 2 * thickness_pml
    size = [size_x, size_y, size_z]
    center = port.center.tolist() + [0]  # (x, y, z=0)
    freq0 = td.constants.C_0 / wavelength
    fwidth = freq0 / 10

    msource = td.ModeSource(
        size=size,
        center=center,
        source_time=td.GaussianPulse(frequency=freq0, fwidth=fwidth),
        direction="forward",
    )

    # Add port monitors
    monitors = {}
    ports = sort_ports_x(sort_ports_y(component_ref.get_ports_list()))
    for port in ports:
        port_name = port.name
        angle = port.orientation
        width = port.width + 2 * port_margin
        size_x = width * abs(np.sin(angle * np.pi / 180))
        size_y = width * abs(np.cos(angle * np.pi / 180))
        size_x = 0 if size_x < 0.001 else size_x
        size_y = 0 if size_y < 0.001 else size_y
        size = (size_x, size_y, size_z)

        # if monitor has a source move monitor inwards
        length = -distance_source_to_monitors if port_name == port_source_name else 0
        xy_shifted = move_polar_rad_copy(np.array(port.center),
                                         angle=angle * np.pi / 180,
                                         length=length)
        center = xy_shifted.tolist() + [0]  # (x, y, z=0)

        monitors[port_name] = td.ModeMonitor(
            center=[port.x, port.y, t_core / 2],
            size=size,
            freqs=[freq0],
            Nmodes=1,
            name=port.name,
        )

    domain_monitor = td.FreqMonitor(center=[0, 0, z_cent],
                                    size=[sim_size[0], sim_size[1], 0],
                                    freqs=[freq0])

    sim = td.Simulation(
        size=sim_size,
        mesh_step=mesh_step,
        structures=structures,
        sources=[msource],
        monitors=[domain_monitor] + list(monitors.values()),
        run_time=20 / fwidth,
        pml_layers=[12, 12, 12],
    )
    # set the modes
    sim.compute_modes(msource, Nmodes=n_modes)
    sim.set_mode(msource, mode_ind=mode_index)
    return sim
예제 #6
0
def get_simulation(
    component: Component,
    resolution: int = 20,
    extend_ports_length: Optional[float] = 10.0,
    layer_stack: LayerStack = LAYER_STACK,
    zmargin_top: float = 3.0,
    zmargin_bot: float = 3.0,
    tpml: float = 1.5,
    clad_material: str = "SiO2",
    is_3d: bool = False,
    wl_min: float = 1.5,
    wl_max: float = 1.6,
    wl_steps: int = 50,
    dfcen: float = 0.2,
    port_source_name: str = "o1",
    port_field_monitor_name: str = "o2",
    port_margin: float = 3,
    distance_source_to_monitors: float = 0.2,
    port_source_offset: float = 0,
    port_monitor_offset: float = 0,
    dispersive: bool = False,
    **settings,
) -> Dict[str, Any]:
    r"""Returns Simulation dict from gdsfactory Component

    based on meep directional coupler example
    https://meep.readthedocs.io/en/latest/Python_Tutorials/GDSII_Import/

    https://support.lumerical.com/hc/en-us/articles/360042095873-Metamaterial-S-parameter-extraction

    .. code::

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

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


    Args:
        component: gf.Component
        resolution: in pixels/um (20: for coarse, 120: for fine)
        extend_ports_length: to extend ports beyond the PML
        layer_stack: Dict of layer number (int, int) to thickness (um)
        zmargin_top: thickness for cladding above core
        zmargin_bot: thickness for cladding below core
        tpml: PML thickness (um)
        clad_material: material for cladding
        is_3d: if True runs in 3D
        wl_min: wavelength min (um)
        wl_max: wavelength max (um)
        wl_steps: wavelength steps
        dfcen: delta frequency
        port_source_name: input port name
        port_field_monitor_name:
        port_margin: margin on each side of the port
        distance_source_to_monitors: in (um) source goes before
        port_source_offset: offset between source GDS port and source MEEP port
        port_monitor_offset: offset between monitor GDS port and monitor MEEP port
        dispersive: use dispersive material models (requires higher resolution)

    Keyword Args:
        settings: other parameters for sim object (resolution, symmetries, etc.)

    Returns:
        simulation dict: sim, monitors, sources

    Make sure you review the simulation before you simulate a component

    .. code::

        import gdsfactory as gf
        import gdsfactory.simulation.meep as gm

        c = gf.components.bend_circular()
        gm.write_sparameters_meep(c, run=False)

    """

    layer_to_thickness = layer_stack.get_layer_to_thickness()
    layer_to_material = layer_stack.get_layer_to_material()
    layer_to_zmin = layer_stack.get_layer_to_zmin()
    layer_to_sidewall_angle = layer_stack.get_layer_to_sidewall_angle()

    component_ref = component.ref()
    component_ref.x = 0
    component_ref.y = 0

    wavelengths = np.linspace(wl_min, wl_max, wl_steps)
    port_names = list(component_ref.ports.keys())

    if port_source_name not in port_names:
        warnings.warn(f"port_source_name={port_source_name!r} not in {port_names}")
        port_source = component_ref.get_ports_list()[0]
        port_source_name = port_source.name
        warnings.warn(f"Selecting port_source_name={port_source_name!r} instead.")

    if port_field_monitor_name not in component_ref.ports:
        warnings.warn(
            f"port_field_monitor_name={port_field_monitor_name!r} not in {port_names}"
        )
        port_field_monitor = (
            component_ref.get_ports_list()[0]
            if len(component.ports) < 2
            else component.get_ports_list()[1]
        )
        port_field_monitor_name = port_field_monitor.name
        warnings.warn(
            f"Selecting port_field_monitor_name={port_field_monitor_name!r} instead."
        )

    assert isinstance(
        component, Component
    ), f"component needs to be a gf.Component, got Type {type(component)}"

    component_extended = (
        gf.components.extension.extend_ports(
            component=component, length=extend_ports_length, centered=True
        )
        if extend_ports_length
        else component
    )
    gf.show(component_extended)

    component_extended.flatten()
    component_extended = component_extended.ref()

    # geometry_center = [component_extended.x, component_extended.y]
    # geometry_center = [0, 0]
    # print(geometry_center)

    layers_thickness = [
        layer_to_thickness[layer]
        for layer in component.layers
        if layer in layer_to_thickness
    ]

    t_core = max(layers_thickness)
    cell_thickness = tpml + zmargin_bot + t_core + zmargin_top + tpml if is_3d else 0

    cell_size = mp.Vector3(
        component.xsize + 2 * tpml,
        component.ysize + 2 * tpml,
        cell_thickness,
    )

    geometry = []
    layer_to_polygons = component_extended.get_polygons(by_spec=True)
    for layer, polygons in layer_to_polygons.items():
        if layer in layer_to_thickness and layer in layer_to_material:
            height = layer_to_thickness[layer] if is_3d else mp.inf
            zmin_um = layer_to_zmin[layer] if is_3d else 0
            # center = mp.Vector3(0, 0, (zmin_um + height) / 2)

            for polygon in polygons:
                vertices = [mp.Vector3(p[0], p[1], zmin_um) for p in polygon]
                material_name = layer_to_material[layer]
                material = get_material(name=material_name, dispersive=dispersive)
                geometry.append(
                    mp.Prism(
                        vertices=vertices,
                        height=height,
                        sidewall_angle=layer_to_sidewall_angle[layer],
                        material=material,
                        # center=center
                    )
                )

    freqs = 1 / wavelengths
    fcen = np.mean(freqs)
    frequency_width = dfcen * fcen

    # Add source
    port = component_ref.ports[port_source_name]
    angle_rad = np.radians(port.orientation)
    width = port.width + 2 * port_margin
    size_x = width * abs(np.sin(angle_rad))
    size_y = width * abs(np.cos(angle_rad))
    size_x = 0 if size_x < 0.001 else size_x
    size_y = 0 if size_y < 0.001 else size_y
    size_z = cell_thickness - 2 * tpml if is_3d else 20
    size = [size_x, size_y, size_z]
    xy_shifted = move_polar_rad_copy(
        np.array(port.center), angle=angle_rad, length=port_source_offset
    )
    center = xy_shifted.tolist() + [0]  # (x, y, z=0)

    field_monitor_port = component_ref.ports[port_field_monitor_name]
    field_monitor_point = field_monitor_port.center.tolist() + [0]  # (x, y, z=0)

    if np.isclose(port.orientation, 0):
        direction = mp.X
    elif np.isclose(port.orientation, 90):
        direction = mp.Y
    elif np.isclose(port.orientation, 180):
        direction = mp.X
    elif np.isclose(port.orientation, 270):
        direction = mp.Y
    else:
        ValueError(f"Port angle {port.orientation} not 0, 90, 180, or 270 degrees!")

    sources = [
        mp.EigenModeSource(
            src=mp.GaussianSource(fcen, fwidth=frequency_width),
            size=size,
            center=center,
            eig_band=1,
            eig_parity=mp.NO_PARITY if is_3d else mp.EVEN_Y + mp.ODD_Z,
            eig_match_freq=True,
            eig_kpoint=-1 * mp.Vector3(x=1).rotate(mp.Vector3(z=1), angle_rad),
            direction=direction,
        )
    ]

    sim = mp.Simulation(
        cell_size=cell_size,
        boundary_layers=[mp.PML(tpml)],
        sources=sources,
        geometry=geometry,
        default_material=get_material(name=clad_material),
        resolution=resolution,
        **settings,
    )

    # Add port monitors dict
    monitors = {}
    for port_name in component_ref.ports.keys():
        port = component_ref.ports[port_name]
        angle_rad = np.radians(port.orientation)
        width = port.width + 2 * port_margin
        size_x = width * abs(np.sin(angle_rad))
        size_y = width * abs(np.cos(angle_rad))
        size_x = 0 if size_x < 0.001 else size_x
        size_y = 0 if size_y < 0.001 else size_y
        size = mp.Vector3(size_x, size_y, size_z)
        size = [size_x, size_y, size_z]

        # if monitor has a source move monitor inwards
        length = (
            -distance_source_to_monitors + port_source_offset
            if port_name == port_source_name
            else port_monitor_offset
        )
        xy_shifted = move_polar_rad_copy(
            np.array(port.center), angle=angle_rad, length=length
        )
        center = xy_shifted.tolist() + [0]  # (x, y, z=0)
        m = sim.add_mode_monitor(freqs, mp.ModeRegion(center=center, size=size))
        m.z = 0
        monitors[port_name] = m
    return dict(
        sim=sim,
        cell_size=cell_size,
        freqs=freqs,
        monitors=monitors,
        sources=sources,
        field_monitor_point=field_monitor_point,
        port_source_name=port_source_name,
        initialized=False,
    )