Source code for gplugins.lumerical.interconnect

from __future__ import annotations

import contextlib
import pathlib
from collections import OrderedDict

import numpy as np
from gdsfactory import Component
from gdsfactory.config import PATH

c = 2.9979e8
pi = np.pi
um = 1e-6


[docs] def install_design_kit( session: object, cml_path: pathlib.Path, install_dir: pathlib.Path = PATH.interconnect, overwrite: bool = False, ) -> None: session.installdesignkit(str(cml_path), str(install_dir), overwrite)
def set_named_settings( session: object, simulation_settings: dict, element: str ) -> None: for param, val in zip(simulation_settings.keys(), simulation_settings.values()): session.setnamed(element, param, val)
[docs] def add_interconnect_element( session: object, label: str, model: str, loc: tuple[float, float] = (200.0, 200.0), flip_vert: bool = False, flip_horiz: bool = False, rotation: float = 0.0, simulation_props: OrderedDict | None = None, ): """Add an element to the Interconnect session. TODO: Need to connect this to generated s-parameters and add them to the model as well Args: session: Interconnect session. label: label for Interconnect component. model: loc: flip_vert: flip_horiz: rotation: extra_props: """ props = OrderedDict( [ ("name", label), ("x position", loc[0]), ("y position", loc[1]), ("horizontal flipped", float(flip_horiz)), ("vertical flipped", float(flip_vert)), ("rotated", rotation), ] ) if simulation_props: if "library" in simulation_props.keys(): _ = simulation_props.pop("library") if "properties" in simulation_props.keys(): props.update(simulation_props["properties"]) else: props.update(simulation_props) return session.addelement(model, properties=props)
[docs] def get_interconnect_settings(instance): info = instance.info.copy() if "interconnect" not in info.keys(): return {} settings = info["interconnect"] if "properties" not in settings: settings["properties"] = [] if "layout_model_property_pairs" in settings.keys(): pairs = settings.pop("layout_model_property_pairs") for inc_name, (layout_name, scale) in pairs.items(): settings["properties"][inc_name] = info[layout_name] * scale return settings
[docs] def send_to_interconnect( component: Component, session: object, ports_in: dict | None = None, ports_out: dict | None = None, placements: dict | None = None, simulation_settings: OrderedDict | None = None, drop_port_prefix: str | None = None, component_distance_scaling_x: float = 1, component_distance_scaling_y: float = 1, setup_mc: bool = False, exclude_electrical: bool = True, **settings, ) -> object: """Send netlist components to Interconnect and connect them according to netlist. Args: component: component from which to extract netlist. session: Interconnect session. placements: x,y pairs for where to place the components in the Interconnect GUI. simulation_settings: global settings for Interconnect simulation. drop_port_prefix: if components are written with some prefix, drop up to and including the prefix character. (i.e. "c1_input" -> "input"). component_distance_scaling: scaling factor for component distances when laying out Interconnect schematic. """ if not session: import lumapi session = lumapi.INTERCONNECT() install_design_kit(session=session) session.switchtolayout() session.deleteall() # Make compound element for circuit compound_element = session.addelement( "Compound Element", properties=OrderedDict([("name", str(component.name))]) ) if setup_mc: MC_param_element = f"::Root Element::{component.name}" # Add Monte-Carlo params session.addproperty( MC_param_element, "MC_uniformity_thickness", "wafer", "Matrix" ) session.addproperty(MC_param_element, "MC_uniformity_width", "wafer", "Matrix") session.addproperty(MC_param_element, "MC_non_uniform", "wafer", "Number") session.addproperty(MC_param_element, "MC_grid", "wafer", "Number") session.addproperty(MC_param_element, "MC_resolution_x", "wafer", "Number") session.addproperty(MC_param_element, "MC_resolution_y", "wafer", "Number") # Switch groupscope to compound element so that anything added will go into it session.groupscope(compound_element.name) c = component netlist = c.get_netlist() instances = netlist["instances"] connections = netlist["connections"] placements = placements or netlist["placements"] ports = netlist["ports"] relay_count = 1 excluded = [] for instance in instances: if exclude_electrical: # Exclude if purely electrical with contextlib.suppress(Exception): port_types = instances[instance].full["cross_section"].port_types if ("electrical" in port_types) and ("optical" not in port_types): excluded.append(instance) continue loc = ( component_distance_scaling_x * placements[instance].x, component_distance_scaling_y * placements[instance].y, ) sim_props = get_interconnect_settings(instances[instance]) if "model" not in sim_props.keys(): raise KeyError(f"Please specify an interconnect model for {instance!r}") model = sim_props.pop("model") if "layout_model_port_pairs" in sim_props.keys(): _ = sim_props.pop("layout_model_port_pairs") add_interconnect_element( session=session, label=instance, loc=loc, rotation=float(placements[instance].rotation), model=model, simulation_props=sim_props, ) if instance in ports_in: # Add input port and connect to the compound element input port session.addport( f"::Root Element::{compound_element.name}", f"{instance}.{ports_in[instance]}", "input", "Optical Signal", "left", ) session.connect( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "output", instance, ports_in[instance], ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "y position", loc[1] - 50, ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "x position", loc[0] - 50, ) relay_count += 1 elif instance in ports_out: session.addport( f"::Root Element::{compound_element.name}", f"{instance}.{ports_out[instance]}", "output", "Optical Signal", "right", ) session.connect( instance, ports_out[instance], f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "input", ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "y position", loc[1] + 50, ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "x position", loc[0] + 50, ) relay_count += 1 if ports: for port in ports_in: input_instance, instance_port = ports[port].split(",") info = get_interconnect_settings(instances[input_instance]) if "layout_model_port_pairs" in info.keys(): instance_port = info["layout_model_port_pairs"][instance_port] session.addport( f"::Root Element::{compound_element.name}", str(port), "input", "Optical Signal", "left", ) session.connect( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "output", input_instance, instance_port, ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "y position", session.getnamed(input_instance, "y position") - 50, ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "x position", session.getnamed(input_instance, "x position") - 50, ) relay_count += 1 for port in ports_out: output_instance, instance_port = ports[port].split(",") info = get_interconnect_settings(instances[output_instance]) if "layout_model_port_pairs" in info.keys(): instance_port = info["layout_model_port_pairs"][instance_port] session.addport( f"::Root Element::{compound_element.name}", str(port), "output", "Optical Signal", "right", ) session.connect( output_instance, instance_port, f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "input", ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "y position", session.getnamed(output_instance, "y position") + 50, ) session.setnamed( f"::Root Element::{compound_element.name}::RELAY_{relay_count}", "x position", session.getnamed(output_instance, "x position") + 50, ) relay_count += 1 for connection in connections: element2, port2 = connection.split(",") element1, port1 = connections[connection].split(",") if (element1 in excluded) or (element2 in excluded): continue if drop_port_prefix: # a bad way to autodetect which ports need to have prefixes dropped. with contextlib.suppress(Exception): port1 = port1[port1.index(drop_port_prefix) + 1 :] with contextlib.suppress(Exception): port2 = port2[port2.index(drop_port_prefix) + 1 :] # EBeam ports are not named consistently between Klayout and Interconnect.. element1_info = get_interconnect_settings(instances[element1]) if "layout_model_port_pairs" in element1_info.keys(): port1 = element1_info["layout_model_port_pairs"][port1] element2_info = get_interconnect_settings(instances[element2]) if "layout_model_port_pairs" in element2_info.keys(): port2 = element2_info["layout_model_port_pairs"][port2] session.connect(element1, port1, element2, port2) if simulation_settings: set_named_settings( session, simulation_settings, element=f"::Root Element::{component.name}" ) # Return to highest-level element session.groupscope("::Root Element") # Auto-distribute ports on the compound element session.autoarrange(component.name) return session
[docs] def run_wavelength_sweep( component: Component, session: object | None = None, setup_simulation: bool = True, is_top_level: bool = False, ports_in: dict | None = None, ports_out: dict | None = None, mode: int = 1, wavelength_range: tuple[float, float] = (1.500, 1.600), n_points: int = 1000, results: tuple[str, ...] = ("transmission",), extra_ona_props: dict | None = None, **kwargs, ) -> dict: """Args are the following. component: session: setup_simulation: whether to send the component to interconnect before running the sweep. ports_in: specify the port in the Interconnect model to attach the ONA output to. ports_out: specify the ports in the Interconnect models to attach the ONA input to. wavelength_range: n_points: results: extra_ona_props: kwargs: """ if len(ports_in) > 1: raise ValueError("Only 1 input port is supported at this time") import lumapi if not session: session = lumapi.INTERCONNECT() install_design_kit(session=session) if setup_simulation: session = send_to_interconnect( component=component, session=session, ports_in=ports_in, ports_out=ports_out, **kwargs, ) ona_props = OrderedDict( [ ("number of input ports", len(ports_out)), ("number of points", n_points), ("input parameter", "start and stop"), ("start frequency", (c / (wavelength_range[1] * um))), ("stop frequency", (c / (wavelength_range[0] * um))), ("plot kind", "wavelength"), ("relative to center", float(False)), ] ) if extra_ona_props: ona_props.update(extra_ona_props) ona = add_interconnect_element( session=session, model="Optical Network Analyzer", label="ONA_1", loc=(0, -50), simulation_props=ona_props, ) for port in ports_in.keys(): name = port if is_top_level else f"{port}.{ports_in[port]}" session.connect(ona.name, "output", component.name, name) for i, port in enumerate(ports_out.keys()): name = port if is_top_level else f"{port}.{ports_out[port]}" session.connect(ona.name, f"input {i + 1}", component.name, name) session.run() # inc.close() return { result: { port: session.getresult(ona.name, f"input {i + 1}/mode {mode}/{result}") for i, port in enumerate(ports_out) } for result in results }
[docs] def plot_wavelength_sweep( ports_out, results, result_name: str = "TE Transmission", show: bool = True ) -> None: import matplotlib.pyplot as plt for port in ports_out: wl = results["transmission"][port]["wavelength"] / um T = 10 * np.log10(np.abs(results["transmission"][port][result_name])) plt.plot(wl, T, label=str(port)) plt.legend() plt.xlabel(r"Wavelength ($\mu$m)") plt.ylabel(f"{result_name} (dB)") if show: plt.show()
if __name__ == "__main__": import ubcpdk.components as pdk mzi = pdk.mzi() netlist = mzi.get_netlist() ports_in = {"o1": "o1"} ports_out = {"o2": "o2"} simulation_settings = OrderedDict( [ ("MC_uniformity_thickness", np.array([200, 200])), ("MC_uniformity_width", np.array([200, 200])), ("MC_non_uniform", 0), ("MC_grid", 1e-5), ("MC_resolution_x", 200), ("MC_resolution_y", 0), ] ) results = run_wavelength_sweep( component=mzi, ports_in=ports_in, ports_out=ports_out, results=("transmission",), component_distance_scaling=50, simulation_settings=simulation_settings, setup_mc=True, is_top_level=True, ) plot_wavelength_sweep(ports_out=ports_out, results=results)