示例#1
0
    def add_port(
        self,
        name: Optional[Union[str, int, object]] = None,
        midpoint: Tuple[float, float] = (
            0.0,
            0.0,
        ),
        width: float = 1.0,
        orientation: int = 45,
        port: Optional[Port] = None,
        layer: Tuple[int, int] = (1, 0),
        port_type: str = "optical",
        cross_section: Optional[CrossSection] = None,
    ) -> Port:
        """Can be called to copy an existing port like add_port(port = existing_port) or
        to create a new port add_port(myname, mymidpoint, mywidth, myorientation).
        Can also be called to copy an existing port
        with a new name add_port(port = existing_port, name = new_name)"""

        if port:
            if not isinstance(port, Port):
                raise ValueError(f"add_port() needs a Port, got {type(port)}")
            p = port.copy(new_uid=True)
            if name is not None:
                p.name = name
            p.parent = self

        elif isinstance(name, Port):
            p = name.copy(new_uid=True)
            p.parent = self
            name = p.name
        else:
            half_width = width / 2
            half_width_correct = snap_to_grid(half_width, nm=1)
            if not np.isclose(half_width, half_width_correct):
                warnings.warn(
                    f"port width = {width} will create off-grid points.\n"
                    f"You can fix it by changing width to {2*half_width_correct}\n"
                    f"port {name}, {midpoint}  {orientation} deg",
                    stacklevel=3,
                )
            p = Port(
                name=name,
                midpoint=(snap_to_grid(midpoint[0]),
                          snap_to_grid(midpoint[1])),
                width=snap_to_grid(width),
                orientation=orientation,
                parent=self,
                layer=layer,
                port_type=port_type,
                cross_section=cross_section,
            )
        if name is not None:
            p.name = name
        if p.name in self.ports:
            raise ValueError(
                f"add_port() Port name {p.name} exists in {self.name}")

        self.ports[p.name] = p
        return p
示例#2
0
def get_instance_name(
    component: Component,
    reference: ComponentReference,
    layer_label: Tuple[int, int] = LAYER.LABEL_INSTANCE,
) -> str:
    """Takes component names from instance XY location or label in layer_label
    Loop over references and find the reference under and associate reference with instance label
    map instance names to references
    Check if it has a instance name label and return the instance name from the label

    Args:
        component: with labels
        reference: reference that needs naming
        layer_label: ignores layer_label[1]
    """

    x = snap_to_grid(reference.x)
    y = snap_to_grid(reference.y)
    labels = component.labels

    # default instance name follows componetName_x_y
    text = clean_name(f"{reference.parent.name}_{x}_{y}")

    # text = f"{reference.parent.name}_X{int(x)}_Y{int(y)}"
    # text = f"{reference.parent.name}_{reference.uid}"

    # try to get the instance name from a label
    for label in labels:
        xl = snap_to_grid(label.position[0])
        yl = snap_to_grid(label.position[1])
        if x == xl and y == yl and label.layer == layer_label[0]:
            # print(label.text, xl, yl, x, y)
            return label.text

    return text
示例#3
0
    def assert_on_grid(self, nm: int = 1) -> None:
        """Ensures ports edges are on grid to avoid snap_to_grid errors."""
        half_width = self.width / 2
        half_width_correct = snap_to_grid(half_width, nm=nm)
        component_name = self.parent.name
        if not np.isclose(half_width, half_width_correct):
            raise PortNotOnGridError(
                f"{component_name}, port = {self.name}, midpoint = {self.midpoint} width = {self.width} will create off-grid points",
                f"you can fix it by changing width to {2*half_width_correct}",
            )

        if self.port_type.startswith("vertical"):
            return

        if self.orientation in [0, 180]:
            x = self.y + self.width / 2
            if not np.isclose(snap_to_grid(x, nm=nm), x):
                raise PortNotOnGridError(
                    f"{self.name} port in {component_name} has an off-grid point {x}",
                    f"you can fix it by moving the point to {snap_to_grid(x, nm=nm)}",
                )
        elif self.orientation in [90, 270]:
            x = self.x + self.width / 2
            if not np.isclose(snap_to_grid(x, nm=nm), x):
                raise PortNotOnGridError(
                    f"{self.name} port in {component_name} has an off-grid point {x}",
                    f"you can fix it by moving the point to {snap_to_grid(x, nm=nm)}",
                )
        else:
            raise PortOrientationError(
                f"{component_name} port {self.name} has invalid orientation"
                f" {self.orientation}")
示例#4
0
def get_instance_name(
    component: Component,
    reference: ComponentReference,
    layer_label: Tuple[int, int] = LAYER.LABEL_INSTANCE,
) -> str:
    """Returns the instance name from the label.
    If no label returns to instanceName_x_y

    Args:
        component: with labels
        reference: reference that needs naming
        layer_label: ignores layer_label[1]
    """

    x = snap_to_grid(reference.x)
    y = snap_to_grid(reference.y)
    labels = component.labels

    # default instance name follows componetName_x_y
    text = clean_name(f"{reference.parent.name}_{x}_{y}")

    # text = f"{reference.parent.name}_X{int(x)}_Y{int(y)}"
    # text = f"{reference.parent.name}_{reference.uid}"

    # try to get the instance name from a label
    for label in labels:
        xl = snap_to_grid(label.position[0])
        yl = snap_to_grid(label.position[1])
        if x == xl and y == yl and label.layer == layer_label[0]:
            # print(label.text, xl, yl, x, y)
            return label.text

    return text
示例#5
0
def add_ports_from_markers_square(
    component: Component,
    pin_layer: Layer = (69, 0),
    port_layer: Optional[Layer] = None,
    orientation: Optional[int] = 90,
    min_pin_area_um2: float = 0,
    max_pin_area_um2: float = 150 * 150,
    pin_extra_width: float = 0.0,
    port_names: Optional[Tuple[str, ...]] = None,
    port_name_prefix: str = "o",
) -> Component:
    """add ports from markers center in port_layer

    squared

    Args:
        component: to read polygons from and to write ports to
        pin_layer: for port markers
        port_layer: for the new created port
        orientation: in degrees 90: north, 0: east, 180: west, 270: south
        min_pin_area_um2: ignores pins with area smaller than min_pin_area_um2
        max_pin_area_um2: ignore pins for area above certain size
        pin_extra_width: 2*offset from pin to straight
        port_names: names of the ports (defaults to {i})

    """
    port_markers = read_port_markers(component, [pin_layer])
    port_names = port_names or [
        f"{port_name_prefix}{i+1}" for i in range(len(port_markers.polygons))
    ]
    layer = port_layer or pin_layer

    for port_name, p in zip(port_names, port_markers.polygons):
        dy = snap_to_grid(p.ymax - p.ymin)
        dx = snap_to_grid(p.xmax - p.xmin)
        x = p.x
        y = p.y
        if dx == dy and max_pin_area_um2 > dx * dy > min_pin_area_um2:
            component.add_port(
                port_name,
                midpoint=(x, y),
                width=dx - pin_extra_width,
                orientation=orientation,
                layer=layer,
            )
    return component
示例#6
0
    def get_ports_xsize(self, **kwargs) -> float:
        """Returns a the xdistance from east to west ports

        Args:
            kwargs: orientation, port_type, layer
        """
        ports_cw = self.get_ports_list(clockwise=True, **kwargs)
        ports_ccw = self.get_ports_list(clockwise=False, **kwargs)
        return snap_to_grid(ports_ccw[0].x - ports_cw[0].x)
示例#7
0
def clean_value(value: Any) -> str:
    """returns a readable string representation."""
    if isinstance(value, int):
        value = str(value)
    elif isinstance(value, (float, np.float64)):
        if 1 > value > 1e-3:
            value = f"{int(value*1e3)}n"
        elif int(value) == value:
            value = str(int(value))
        elif 1e-6 < value < 1e-3:
            value = f"{snap_to_grid(value*1e6)}u"
        elif 1e-9 < value < 1e-6:
            value = f"{snap_to_grid(value*1e9)}n"
        elif 1e-12 < value < 1e-9:
            value = f"{snap_to_grid(value*1e12)}p"
        else:  # Any unit < 1pm will disappear
            value = str(snap_to_grid(value)).replace(".", "p")
    elif isinstance(value, Device):
        value = clean_name(value.name)
    elif isinstance(value, str):
        value = value.strip()
    elif (isinstance(value, dict) and len(value) > 0
          and not isinstance(list(value.keys())[0], str)):
        value = [
            f"{clean_value(key)}={clean_value(value[key])}"
            for key in sorted(value.keys())
        ]
        value = "_".join(value)

    elif isinstance(value, dict):
        value = dict2name(**value)
        # value = [f"{k}={v!r}" for k, v in value.items()]
    elif isinstance(value, Port):
        value = f"{value.name}_{value.width}_{value.x}_{value.y}"
    elif isinstance(value, PathPhidl):
        value = f"path_{hash_points(value.points)}"
    elif (isinstance(value, object) and hasattr(value, "name")
          and isinstance(value.name, str)):
        value = clean_name(value.name)
    elif callable(value) and isinstance(value, functools.partial):
        sig = inspect.signature(value.func)
        args_as_kwargs = dict(zip(sig.parameters.keys(), value.args))
        args_as_kwargs.update(**value.keywords)
        value = value.func.__name__ + dict2name(**args_as_kwargs)
    elif callable(value) and isinstance(value, toolz.functoolz.Compose):
        value = "_".join([clean_value(v)
                          for v in value.funcs] + [clean_value(value.first)])
    elif callable(value) and hasattr(value, "__name__"):
        value = value.__name__
    elif hasattr(value, "get_name"):
        value = value.get_name()
    elif isinstance(value, Iterable):
        value = "_".join(clean_value(v) for v in value)

    return str(value)
示例#8
0
def bend_circular(angle: int = 90,
                  npoints: int = 720,
                  with_cladding_box: bool = True,
                  cross_section: CrossSectionOrFactory = strip,
                  **kwargs) -> Component:
    """Returns a radial arc.

    Args:
        angle: angle of arc (degrees)
        npoints: number of points
        with_cladding_box: square in layers_cladding to remove DRC
        cross_section:
        kwargs: cross_section settings


    .. code::

                  o2
                  |
                 /
                /
               /
       o1_____/

    """
    x = cross_section(**kwargs) if callable(cross_section) else cross_section
    radius = x.info["radius"]

    p = arc(radius=radius, angle=angle, npoints=npoints)
    c = Component()
    path = extrude(p, x)
    ref = c << path
    c.add_ports(ref.ports)

    c.info.length = snap_to_grid(p.length())
    c.info.dy = float(abs(p.points[0][0] - p.points[-1][0]))
    c.info.radius = float(radius)

    if with_cladding_box and x.info["layers_cladding"]:
        layers_cladding = x.info["layers_cladding"]
        cladding_offset = x.info["cladding_offset"]
        top = cladding_offset if angle == 180 else 0
        points = get_padding_points(
            component=c,
            default=0,
            bottom=cladding_offset,
            right=cladding_offset,
            top=top,
        )
        for layer in layers_cladding or []:
            c.add_polygon(points, layer=layer)

    c.absorb(ref)
    return c
def bend_circular_heater(
    radius: float = 10,
    angle: int = 90,
    npoints: int = 720,
    heater_to_wg_distance: float = 1.2,
    heater_width: float = 0.5,
    layer_heater=TECH.layer.HEATER,
    cross_section: CrossSectionFactory = strip,
) -> Component:
    """Creates an arc of arclength ``theta`` starting at angle ``start_angle``

    Args:
        radius
        angle: angle of arc (degrees)
        npoints: Number of points used per 360 degrees
        heater_to_wg_distance:
        heater_width
        layer_heater
        cross_section:
    """
    x = cross_section()
    width = x.info["width"]
    cladding_offset = x.info["cladding_offset"]
    layers_cladding = x.info["layers_cladding"] or []
    layer = x.info["layer"]

    x = gf.CrossSection()
    x.add(width=width, offset=0, layer=layer, ports=["in", "out"])

    for layer_cladding in layers_cladding:
        x.add(width=width + 2 * cladding_offset,
              offset=0,
              layer=layer_cladding)

    offset = heater_to_wg_distance + width / 2
    x.add(
        width=heater_width,
        offset=+offset,
        layer=layer_heater,
    )
    x.add(
        width=heater_width,
        offset=-offset,
        layer=layer_heater,
    )
    p = arc(radius=radius, angle=angle, npoints=npoints)
    c = extrude(p, x)
    c.length = snap_to_grid(p.length())
    c.dx = abs(p.points[0][0] - p.points[-1][0])
    c.dy = abs(p.points[0][0] - p.points[-1][0])
    return c
示例#10
0
def bend_circular(angle: int = 90,
                  npoints: int = 720,
                  with_cladding_box: bool = True,
                  cross_section: CrossSectionFactory = strip,
                  **kwargs) -> Component:
    """Returns a radial arc.

    Args:
        angle: angle of arc (degrees)
        npoints: number of points
        with_cladding_box: square in layers_cladding to remove DRC
        cross_section:
        **kwargs: cross_section settings

    .. plot::
        :include-source:

        import gdsfactory as gf

        c = gf.components.bend_circular(radius=10, angle=90, npoints=720)
        c.plot()

    """
    x = cross_section(**kwargs)
    radius = x.info["radius"]

    p = arc(radius=radius, angle=angle, npoints=npoints)
    c = extrude(p, x)

    c.info.length = snap_to_grid(p.length())
    c.info.dy = float(abs(p.points[0][0] - p.points[-1][0]))
    c.info.radius_min = float(radius)

    if with_cladding_box and x.info["layers_cladding"]:
        layers_cladding = x.info["layers_cladding"]
        cladding_offset = x.info["cladding_offset"]
        top = cladding_offset if angle == 180 else 0
        points = get_padding_points(
            component=c,
            default=0,
            bottom=cladding_offset,
            right=cladding_offset,
            top=top,
        )
        for layer in layers_cladding or []:
            c.add_polygon(points, layer=layer)
    return c
示例#11
0
文件: name.py 项目: tvt173/gdsfactory
def clean_value(value: Any) -> str:
    """returns more readable value (integer)
    if number is < 1:
        returns number units in nm (integer)

    units are in um by default. Therefore when we multiply by 1e3 we get nm.
    """

    if isinstance(value, int):
        value = str(value)
    elif isinstance(value, (float, np.float64)):
        if 1 > value > 1e-3:
            value = f"{int(value*1e3)}n"
        elif float(int(value)) == value:
            value = str(int(value))
        elif 1e-6 < value < 1e-3:
            value = f"{snap_to_grid(value*1e6)}u"
        elif 1e-9 < value < 1e-6:
            value = f"{snap_to_grid(value*1e9)}n"
        elif 1e-12 < value < 1e-9:
            value = f"{snap_to_grid(value*1e12)}p"
        else:
            value = str(snap_to_grid(value)).replace(".", "p")
    elif isinstance(value, list):
        value = "_".join(clean_value(v) for v in value)
    elif isinstance(value, tuple):
        value = "_".join(clean_value(v) for v in value)
    elif isinstance(value, dict):
        value = dict2name(**value)
        # value = [f"{k}={v!r}" for k, v in value.items()]
    elif isinstance(value, Device):
        value = clean_name(value.name)
    elif isinstance(value, Port):
        value = f"{value.name}_{value.width}_{value.x}_{value.y}"
    elif (isinstance(value, object) and hasattr(value, "name")
          and isinstance(value.name, str)):
        value = clean_name(value.name)
    elif callable(value) and hasattr(value, "__name__"):
        value = value.__name__
    elif callable(value) and isinstance(value, functools.partial):
        value = value.func.__name__ + dict2name(**value.keywords)
    elif isinstance(value, str):
        value = value.strip()
    return str(value)
示例#12
0
    def to_dict_polygons(self) -> DictConfig:
        """Returns a dict representation of the flattened compoment."""
        d = DictConfig({})
        polygons = {}
        layer_to_polygons = self.get_polygons(by_spec=True)

        for layer, polygons_layer in layer_to_polygons.items():
            for polygon in polygons_layer:
                layer_name = f"{layer[0]}_{layer[1]}"
                polygons[layer_name] = [
                    tuple(snap_to_grid(v)) for v in polygon
                ]

        ports = {port.name: port.settings for port in self.get_ports_list()}
        clean_dict(ports)
        clean_dict(polygons)
        d.info = self.info
        d.polygons = polygons
        d.ports = ports
        return OmegaConf.create(d)
示例#13
0
def spiral_circular(
    length: float = 1e3,
    wg_width: float = 0.5,
    spacing: float = 3.0,
    min_bend_radius: float = 5.0,
    points: int = 1000,
    layer: Tuple[int, int] = gf.LAYER.WG,
) -> Component:
    """Returns a circular spiral.

    FIXME, has some issues

    Args:
        length: length in um
        wg_width:
        spacing: between straights
        min_bend_radius:
        points:
        layer:

    """
    wg_datatype = layer[1]
    wg_layer = layer[0]

    def pol_to_rect(radii, angles_deg):
        angles_rad = np.radians(angles_deg)
        z = radii * np.exp(1.0j * angles_rad)
        return z.real, z.imag

    ps = []

    # Estimate number of revolutions
    length_total = 0.0
    i = 0
    while length_total <= length:
        length_total += 3.0 * np.pi * (min_bend_radius * 2.0 +
                                       (i + 0.5) * spacing)
        i += 1
    revolutions = i + 1

    # Long spiral
    inner_revs = min_bend_radius * 4.0 / (spacing * 4.0)
    theta_1 = np.linspace(360.0 * inner_revs, 360.0 * (revolutions - 1) + 270,
                          points)
    theta_2 = np.linspace(360.0 * inner_revs, 360.0 * revolutions, points)
    a = np.sqrt(spacing / 180.0)
    radii_1 = a**2 * theta_1
    radii_2 = -(a**2) * theta_2
    x_1, y_1 = pol_to_rect(radii_1, theta_1)
    x_1 = np.append(x_1, x_1[-1] + 0.03)
    y_1 = np.append(y_1, y_1[-1])

    p = gds.PolyPath(np.c_[x_1, y_1],
                     wg_width,
                     layer=wg_layer,
                     datatype=wg_datatype)
    ps.append(p)
    x_2, y_2 = pol_to_rect(radii_2, theta_2)
    x_2 = np.append(x_2, x_2[-1])
    y_2 = np.append(y_2, y_2[-1] - 0.03)
    p = gds.PolyPath(np.c_[x_2, y_2],
                     wg_width,
                     layer=wg_layer,
                     datatype=wg_datatype)
    ps.append(p)

    start_1 = (x_1[-1], y_1[-1])
    start_2 = (x_2[-1], y_2[-1])
    end_1 = (x_1[0], y_1[0])
    end_2 = (x_2[0], y_2[0])

    length_1 = np.sum(np.hypot(np.diff(x_1), np.diff(y_1)))
    length_2 = np.sum(np.hypot(np.diff(x_2), np.diff(y_2)))

    # Inner bend
    theta_inner = np.linspace(360.0 * inner_revs, 360.0 * inner_revs + 180.0,
                              50)
    radii_inner_1 = min_bend_radius
    radii_inner_2 = -min_bend_radius
    x_1, y_1 = pol_to_rect(radii_inner_1, theta_inner)
    x_1 -= end_1[0] / 2.0
    y_1 -= end_1[1] / 2.0
    x_1 = np.append(x_1, x_1[-1])
    y_1 = np.append(y_1, y_1[-1] - 0.03)
    p = gds.PolyPath(np.c_[x_1, y_1],
                     wg_width,
                     layer=wg_layer,
                     datatype=wg_datatype)
    ps.append(p)
    x_2, y_2 = pol_to_rect(radii_inner_2, theta_inner)
    x_2 -= end_2[0] / 2.0
    y_2 -= end_2[1] / 2.0
    x_2 = np.append(x_2, x_2[-1])
    y_2 = np.append(y_2, y_2[-1] + 0.03)
    p = gds.PolyPath(np.c_[x_2, y_2],
                     wg_width,
                     layer=wg_layer,
                     datatype=wg_datatype)
    ps.append(p)
    length_3 = np.sum(np.hypot(np.diff(x_2), np.diff(y_2)))

    # Output straight
    p, _, e = straight(wg_width,
                       radii_1[-1],
                       start_1,
                       layer=wg_layer,
                       datatype=wg_datatype)
    ps.append(p)

    # Outer bend
    theta_input = np.linspace(0.0, -90.0, 50)
    r_input = min_bend_radius * 2.0
    x_input, y_input = pol_to_rect(r_input, theta_input)
    x_input += start_2[0] - r_input
    y_input += start_1[1] + r_input
    s = (x_input[-1], y_input[-1])
    end_input = (x_input[0], y_input[0])
    x_input = np.append(x_input[0], x_input)
    y_input = np.append(y_input[0] + 0.03, y_input)
    x_input = np.append(x_input, x_input[-1] - 0.03)
    y_input = np.append(y_input, y_input[-1])
    p = gds.PolyPath(np.c_[x_input, y_input],
                     wg_width,
                     layer=wg_layer,
                     datatype=wg_datatype)
    ps.append(p)
    length = start_2[1] - end_input[1]
    p, _, _ = straight(
        length,
        wg_width,
        (end_input[0] - wg_width / 2.0, end_input[1] + length / 2.0),
        layer=wg_layer,
        datatype=wg_datatype,
    )
    ps.append(p)

    length = length_1 + length_2 + length_3

    # Electrode ring
    # inner_radius = min_bend_radius * 2.
    # outer_radius = a**2 * theta_2[-1]
    # mid_radius = 0.5*(inner_radius + outer_radius)
    # thickness = outer_radius - mid_radius
    # r = gds.Round((0.,0.), outer_radius, inner_radius, layer=1, max_points=1000)

    ps = gds.fast_boolean(ps, None, "or")

    c = gf.Component()
    c.info.length = snap_to_grid(length)
    c.add_polygon(ps, layer=layer)

    c.add_port(
        name="o1",
        midpoint=(s[0], s[1]),
        orientation=180,
        layer=layer,
        width=wg_width,
    )
    c.add_port(
        name="o2",
        midpoint=(e[0], e[1]),
        orientation=180,
        layer=layer,
        width=wg_width,
    )
    return c
def straight_heater_doped_rib(
    length: float = 320.0,
    nsections: int = 3,
    cross_section: CrossSectionFactory = strip_rib_tip,
    cross_section_heater: CrossSectionFactory = rib_heater_doped,
    contact: Optional[ComponentFactory] = contact_slab_npp_m3,
    contact_metal: Optional[ComponentFactory] = contact_metal_function,
    contact_metal_size: Tuple[float, float] = (10.0, 10.0),
    contact_size: Tuple[float, float] = (10.0, 10.0),
    taper: Optional[ComponentOrFactory] = taper_cross_section,
    with_taper1: bool = True,
    with_taper2: bool = True,
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    contact_gap: float = 0.0,
    width: float = 0.5,
    with_top_contact: bool = True,
    with_bot_contact: bool = True,
    **kwargs
) -> Component:
    r"""Returns a doped thermal phase shifter.
    dimensions from https://doi.org/10.1364/OE.27.010456

    Args:
        length: of the waveguide
        nsections: between contacts
        cross_section: for the input/output ports
        cross_section_heater: for the heater
        contact: function to connect the heated strip
        contact_metal: function to connect the metal area
        contact_metal_size:
        contact_size:
        taper: optional taper function
        heater_width:
        heater_gap:
        contact_gap: from edge of contact to waveguide
        width: waveguide width on the ridge
        kwargs: cross_section settings

    .. code::

                              length
        |<--------------------------------------------->|
        |              length_section                   |
        |    <--------------------------->              |
        |  length_contact                               |
        |    <------->                             taper|
        |    _________                    _________     |
        |   |        |                    |        |    |
        |   | contact|____________________|        |    |
        |   |  size  |    heater width    |        |    |
        |  /|________|____________________|________|\   |
        | / |             heater_gap               | \  |
        |/  |______________________________________|  \ |
         \  |_______________width__________________|  /
          \ |                                      | /
           \|_____________heater_gap______________ |/
            |        |                    |        |
            |        |____heater_width____|        |
            |        |                    |        |
            |________|                    |________|

        taper         cross_section_heater



                                   |<------width------>|
                                    ____________________ heater_gap             slab_gap
             top_contact           |                   |<---------->| bot_contact   <-->
         ___ ______________________|                   |___________________________|___
        |   |            |               undoped Si                 |              |   |
        |   |layer_heater|               intrinsic region           |layer_heater  |   |
        |___|____________|__________________________________________|______________|___|
                                                                     <------------>
                                                                      heater_width
        <------------------------------------------------------------------------------>
                                       slab_width

    """
    c = Component()
    cross_section_heater = gf.partial(
        cross_section_heater,
        heater_width=heater_width,
        heater_gap=heater_gap,
        width=width,
        **kwargs
    )

    if taper:
        taper = (
            taper(cross_section1=cross_section, cross_section2=cross_section_heater)
            if callable(taper)
            else taper
        )
        length -= taper.get_ports_xsize() * 2

    wg = c << gf.c.straight(
        cross_section=cross_section_heater,
        length=snap_to_grid(length),
    )

    if taper:
        if with_taper1:
            taper1 = c << taper
            taper1.connect("o2", wg.ports["o1"])
            c.add_port("o1", port=taper1.ports["o1"])
        else:
            c.add_port("o1", port=wg.ports["o1"])

        if with_taper2:
            taper2 = c << taper
            taper2.mirror()
            taper2.connect("o2", wg.ports["o2"])
            c.add_port("o2", port=taper2.ports["o1"])

        else:
            c.add_port("o2", port=wg.ports["o2"])
    else:
        c.add_port("o2", port=wg.ports["o2"])
        c.add_port("o1", port=wg.ports["o1"])

    if contact_metal:
        contact_section = contact_metal(size=contact_metal_size)
    contacts = []
    length_contact = snap_to_grid(contact_size[1])
    length_section = snap_to_grid((length - length_contact) / nsections)
    x0 = contact_size[0] / 2
    for i in range(0, nsections + 1):
        xi = x0 + length_section * i

        if contact_metal and contact:
            contact_center = c.add_ref(contact_section)
            contact_center.x = xi
            contact_ref = c << contact_section
            contact_ref.x = xi
            contact_ref.y = (
                +contact_metal_size[1] if i % 2 == 0 else -contact_metal_size[1]
            )
            contacts.append(contact_ref)

        if contact:
            if with_top_contact:
                contact_top = c << contact(size=contact_size)
                contact_top.x = xi
                contact_top.ymin = +(heater_gap + width / 2 + contact_gap)

            if with_bot_contact:
                contact_bot = c << contact(size=contact_size)
                contact_bot.x = xi
                contact_bot.ymax = -(heater_gap + width / 2 + contact_gap)

    if contact_metal and contact:
        contact_length = length + contact_metal_size[0]
        contact_top = c << contact_metal(
            size=(contact_length, contact_metal_size[0]),
        )
        contact_bot = c << contact_metal(
            size=(contact_length, contact_metal_size[0]),
        )

        contact_bot.xmin = contacts[0].xmin
        contact_top.xmin = contacts[0].xmin

        contact_top.ymin = contacts[0].ymax
        contact_bot.ymax = contacts[1].ymin

        c.add_ports(contact_top.ports, prefix="top_")
        c.add_ports(contact_bot.ports, prefix="bot_")
    return c
示例#15
0
def import_gds(
    gdspath: Union[str, Path],
    cellname: Optional[str] = None,
    flatten: bool = False,
    snap_to_grid_nm: Optional[int] = None,
    name: Optional[str] = None,
    decorator: Optional[Callable] = None,
    gdsdir: Optional[Union[str, Path]] = None,
    **kwargs,
) -> Component:
    """Returns a Componenent from a GDS file.

    Adapted from phidl/geometry.py

    if any cell names are found on the component CACHE we append a $ with a
    number to the name

    Args:
        gdspath: path of GDS file.
        cellname: cell of the name to import (None) imports top cell.
        flatten: if True returns flattened (no hierarchy)
        snap_to_grid_nm: snap to different nm grid (does not snap if False)
        name: Optional name. Over-rides the default imported name.
        decorator: function to apply over the imported gds.
        gdsdir: optional GDS directory.
        kwargs: settings for the imported component (polarization, wavelength ...).
    """
    gdspath = Path(gdsdir) / Path(gdspath) if gdsdir else Path(gdspath)
    if not gdspath.exists():
        raise FileNotFoundError(f"No file {gdspath!r} found")

    metadata_filepath = gdspath.with_suffix(".yml")

    gdsii_lib = gdspy.GdsLibrary()
    gdsii_lib.read_gds(str(gdspath))
    top_level_cells = gdsii_lib.top_level()
    cellnames = [c.name for c in top_level_cells]

    if cellname is not None:
        if cellname not in gdsii_lib.cells:
            raise ValueError(
                f"cell {cellname} is not in file {gdspath} with cells {cellnames}"
            )
        topcell = gdsii_lib.cells[cellname]
    elif cellname is None and len(top_level_cells) == 1:
        topcell = top_level_cells[0]
    elif cellname is None and len(top_level_cells) > 1:
        raise ValueError(
            f"import_gds() There are multiple top-level cells in {gdspath!r}, "
            f"you must specify `cellname` to select of one of them among {cellnames}"
        )

    if name:
        if name in CACHE or name in CACHE_IMPORTED_CELLS:
            raise ValueError(
                f"name = {name!r} already on cache. "
                "Please, choose a different name or set name = None. ")
        else:
            topcell.name = name

    if flatten:
        component = Component(name=name or cellname or cellnames[0])
        polygons = topcell.get_polygons(by_spec=True)

        for layer_in_gds, polys in polygons.items():
            component.add_polygon(polys, layer=layer_in_gds)

        component = avoid_duplicated_cells(component)

    else:
        D_list = []
        cell_to_device = {}
        for c in gdsii_lib.cells.values():
            D = Component(name=c.name)
            D.polygons = c.polygons
            D.references = c.references
            D.name = c.name
            for label in c.labels:
                rotation = label.rotation
                if rotation is None:
                    rotation = 0
                label_ref = D.add_label(
                    text=label.text,
                    position=np.asfarray(label.position),
                    magnification=label.magnification,
                    rotation=rotation * 180 / np.pi,
                    layer=(label.layer, label.texttype),
                )
                label_ref.anchor = label.anchor

            D = avoid_duplicated_cells(D)
            D.unlock()

            cell_to_device.update({c: D})
            D_list += [D]

        for D in D_list:
            # First convert each reference so it points to the right Device
            converted_references = []
            for e in D.references:
                ref_device = cell_to_device[e.ref_cell]
                if isinstance(e, gdspy.CellReference):
                    dr = DeviceReference(
                        device=ref_device,
                        origin=e.origin,
                        rotation=e.rotation,
                        magnification=e.magnification,
                        x_reflection=e.x_reflection,
                    )
                    dr.owner = D
                    converted_references.append(dr)
                elif isinstance(e, gdspy.CellArray):
                    dr = CellArray(
                        device=ref_device,
                        columns=e.columns,
                        rows=e.rows,
                        spacing=e.spacing,
                        origin=e.origin,
                        rotation=e.rotation,
                        magnification=e.magnification,
                        x_reflection=e.x_reflection,
                    )
                    dr.owner = D
                    converted_references.append(dr)
            D.references = converted_references

            # Next convert each Polygon
            # temp_polygons = list(D.polygons)
            # D.polygons = []
            # for p in temp_polygons:
            #     D.add_polygon(p)

            # Next convert each Polygon
            temp_polygons = list(D.polygons)
            D.polygons = []
            for p in temp_polygons:
                if snap_to_grid_nm:
                    points_on_grid = snap_to_grid(p.polygons[0],
                                                  nm=snap_to_grid_nm)
                    p = gdspy.Polygon(points_on_grid,
                                      layer=p.layers[0],
                                      datatype=p.datatypes[0])
                D.add_polygon(p)
        component = cell_to_device[topcell]
        cast(Component, component)

    name = name or component.name
    component.name = name

    if metadata_filepath.exists():
        logger.info(f"Read YAML metadata from {metadata_filepath}")
        metadata = OmegaConf.load(metadata_filepath)

        for port_name, port in metadata.ports.items():
            if port_name not in component.ports:
                component.add_port(
                    name=port_name,
                    midpoint=port.midpoint,
                    width=port.width,
                    orientation=port.orientation,
                    layer=port.layer,
                    port_type=port.port_type,
                )

        component.info = metadata.info

    component.info.update(**kwargs)
    component.name = name
    component.info.name = name

    if decorator:
        component_new = decorator(component)
        component = component_new or component
    if flatten:
        component.flatten()
    component.lock()
    return component
def straight_heater_doped_rib(
    length: float = 320.0,
    nsections: int = 3,
    cross_section: CrossSectionFactory = strip_rib_tip,
    cross_section_heater: CrossSectionFactory = rib_heater_doped,
    contact_contact: Optional[ComponentFactory] = contact_slab_npp,
    contact_metal: Optional[ComponentFactory] = contact_metal_function,
    contact_metal_size: Tuple[float, float] = (10.0, 10.0),
    contact_contact_size: Tuple[float, float] = (10.0, 10.0),
    contact_contact_yspacing: float = 2.0,
    port_orientation_top: int = 0,
    port_orientation_bot: int = 180,
    taper: Optional[ComponentFactory] = taper_cross_section,
    taper_length: float = 10.0,
    **kwargs,
) -> Component:
    r"""Returns a doped thermal phase shifter.
    dimensions from https://doi.org/10.1364/OE.27.010456

    Args:
        length: of the waveguide
        nsections: between contacts
        cross_section_heater: for the heater
        contact_contact: function to connect the heated strip
        contact_metal: function to connect the metal area
        contact_metal_size:
        contact_contact_yspacing: spacing from waveguide to contact
        port_orientation_top: for top contact
        port_orientation_bot: for bottom contact
        kwargs: cross_section settings
        taper: optional taper
        taper_length:

    .. code::

                              length
          <-------------------------------------------->
                       length_section
             <--------------------------->
           length_contact
             <------->                             taper
             ______________________________________
           /|        |____________________|        |\
        __/ |viastack|                    |        | \___
        __  |        |                    |        |  ___cross_section
          \ | size   |____________________|        | /
           \|________|____________________|________|/

        taper         cross_section_heater



                                   |<------width------>|
                                    ____________________ heater_gap             slab_gap
                                   |                   |<---------->|               <-->
         ___ ______________________|                   |___________________________|___
        |   |            |               undoped Si                 |              |   |
        |   |layer_heater|               intrinsic region           |layer_heater  |   |
        |___|____________|__________________________________________|______________|___|
                                                                     <------------>
                                                                      heater_width
        <------------------------------------------------------------------------------>
                                       slab_width

    """
    c = Component()

    if taper:
        taper = taper(
            cross_section1=cross_section,
            cross_section2=cross_section_heater,
            length=taper_length,
            **kwargs,
        )
        length -= taper_length * 2

    wg = c << gf.c.straight(
        cross_section=cross_section_heater,
        length=snap_to_grid(length),
        **kwargs,
    )

    if taper:
        taper1 = c << taper
        taper2 = c << taper
        taper1.connect("o2", wg.ports["o1"])
        taper2.connect("o2", wg.ports["o2"])
        c.add_port("o1", port=taper1.ports["o1"])
        c.add_port("o2", port=taper2.ports["o1"])

    else:
        c.add_port("o1", port=wg.ports["o1"])
        c.add_port("o2", port=wg.ports["o2"])

    if contact_metal:
        contact_section = contact_metal(size=contact_metal_size)
    contacts = []
    length_contact = snap_to_grid(contact_contact_size[1])
    length_section = snap_to_grid((length - length_contact) / nsections)
    x0 = contact_contact_size[0] / 2
    for i in range(0, nsections + 1):
        xi = x0 + length_section * i

        if contact_metal and contact_contact:
            contact_center = c.add_ref(contact_section)
            contact_center.x = xi
            contact = c << contact_section
            contact.x = xi
            contact.y = +contact_metal_size[
                1] if i % 2 == 0 else -contact_metal_size[1]
            contacts.append(contact)

        if contact_contact:
            contact_bot = c << contact_contact(size=contact_contact_size)
            contact_top = c << contact_contact(size=contact_contact_size)
            contact_top.x = xi
            contact_bot.x = xi
            contact_top.ymin = +contact_contact_yspacing
            contact_bot.ymax = -contact_contact_yspacing

    if contact_metal and contact_contact:
        contact_length = length + contact_metal_size[0]
        contact_top = c << contact_metal(
            size=(contact_length, contact_metal_size[0]), )
        contact_bot = c << contact_metal(
            size=(contact_length, contact_metal_size[0]), )

        contact_bot.xmin = contacts[0].xmin
        contact_top.xmin = contacts[0].xmin

        contact_top.ymin = contacts[0].ymax
        contact_bot.ymax = contacts[1].ymin

        c.add_port("e1",
                   port=contact_top.get_ports_list(
                       orientation=port_orientation_top)[0])
        c.add_port("e2",
                   port=contact_bot.get_ports_list(
                       orientation=port_orientation_bot)[0])
    return c
示例#17
0
def import_gds(
    gdspath: Union[str, Path],
    cellname: Optional[str] = None,
    flatten: bool = False,
    snap_to_grid_nm: Optional[int] = None,
    decorator: Optional[Callable] = None,
    **kwargs,
) -> Component:
    """Returns a Componenent from a GDS file.

    Adapted from phidl/geometry.py

    Args:
        gdspath: path of GDS file
        cellname: cell of the name to import (None) imports top cell
        flatten: if True returns flattened (no hierarchy)
        snap_to_grid_nm: snap to different nm grid (does not snap if False)
        **kwargs
    """
    gdspath = Path(gdspath)
    if not gdspath.exists():
        raise FileNotFoundError(f"No file {gdspath} found")
    gdsii_lib = gdspy.GdsLibrary()
    gdsii_lib.read_gds(str(gdspath))
    top_level_cells = gdsii_lib.top_level()
    cellnames = [c.name for c in top_level_cells]

    if cellname is not None:
        if cellname not in gdsii_lib.cells:
            raise ValueError(
                f"cell {cellname} is not in file {gdspath} with cells {cellnames}"
            )
        topcell = gdsii_lib.cells[cellname]
    elif cellname is None and len(top_level_cells) == 1:
        topcell = top_level_cells[0]
    elif cellname is None and len(top_level_cells) > 1:
        raise ValueError(
            f"import_gds() There are multiple top-level cells in {gdspath}, "
            f"you must specify `cellname` to select of one of them among {cellnames}"
        )
    if flatten:
        component = Component()
        polygons = topcell.get_polygons(by_spec=True)

        for layer_in_gds, polys in polygons.items():
            component.add_polygon(polys, layer=layer_in_gds)

    else:
        D_list = []
        c2dmap = {}
        for cell in gdsii_lib.cells.values():
            D = Component(name=cell.name)
            D.polygons = cell.polygons
            D.references = cell.references
            D.name = cell.name
            for label in cell.labels:
                rotation = label.rotation
                if rotation is None:
                    rotation = 0
                label_ref = D.add_label(
                    text=label.text,
                    position=np.asfarray(label.position),
                    magnification=label.magnification,
                    rotation=rotation * 180 / np.pi,
                    layer=(label.layer, label.texttype),
                )
                label_ref.anchor = label.anchor
            c2dmap.update({cell: D})
            D_list += [D]

        for D in D_list:
            # First convert each reference so it points to the right Device
            converted_references = []
            for e in D.references:
                ref_device = c2dmap[e.ref_cell]
                if isinstance(e, gdspy.CellReference):
                    dr = DeviceReference(
                        device=ref_device,
                        origin=e.origin,
                        rotation=e.rotation,
                        magnification=e.magnification,
                        x_reflection=e.x_reflection,
                    )
                    dr.owner = D
                    converted_references.append(dr)
                elif isinstance(e, gdspy.CellArray):
                    dr = CellArray(
                        device=ref_device,
                        columns=e.columns,
                        rows=e.rows,
                        spacing=e.spacing,
                        origin=e.origin,
                        rotation=e.rotation,
                        magnification=e.magnification,
                        x_reflection=e.x_reflection,
                    )
                    dr.owner = D
                    converted_references.append(dr)
            D.references = converted_references

            # Next convert each Polygon
            # temp_polygons = list(D.polygons)
            # D.polygons = []
            # for p in temp_polygons:
            #     D.add_polygon(p)

            # Next convert each Polygon
            temp_polygons = list(D.polygons)
            D.polygons = []
            for p in temp_polygons:
                if snap_to_grid_nm:
                    points_on_grid = snap_to_grid(p.polygons[0], nm=snap_to_grid_nm)
                    p = gdspy.Polygon(
                        points_on_grid, layer=p.layers[0], datatype=p.datatypes[0]
                    )
                D.add_polygon(p)
        component = c2dmap[topcell]
        cast(Component, component)
    for key, value in kwargs.items():
        setattr(component, key, value)
    if decorator:
        decorator(component)
    component._autoname = False
    return component
示例#18
0
 def get_ports_ysize(self, **kwargs) -> float:
     """Returns a the ydistance from east to west ports"""
     ports_cw = self.get_ports_list(clockwise=True, **kwargs)
     ports_ccw = self.get_ports_list(clockwise=False, **kwargs)
     return snap_to_grid(ports_ccw[0].y - ports_cw[0].y)
示例#19
0
def round_corners(
    points: Coordinates,
    straight: ComponentFactory = straight_function,
    bend: ComponentFactory = bend_euler,
    bend_s_factory: Optional[ComponentFactory] = bend_s,
    taper: Optional[ComponentFactory] = None,
    straight_fall_back_no_taper: Optional[ComponentFactory] = None,
    mirror_straight: bool = False,
    straight_ports: Optional[List[str]] = None,
    cross_section: CrossSectionFactory = strip,
    on_route_error: Callable = get_route_error,
    with_point_markers: bool = False,
    snap_to_grid_nm: Optional[int] = 1,
    **kwargs,
) -> Route:
    """Returns Route:

    - references list with rounded straight route from a list of manhattan points.
    - ports: Tuple of ports
    - length: route length (float)

    Args:
        points: manhattan route defined by waypoints
        bend90: the bend to use for 90Deg turns
        straight: the straight library to use to generate straight portions
        taper: taper for straight portions. If None, no tapering
        straight_fall_back_no_taper: in case there is no space for two tapers
        mirror_straight: mirror_straight waveguide
        straight_ports: port names for straights. If None finds them automatically.
        cross_section:
        on_route_error: function to run when route fails
        with_point_markers: add route points markers (easy for debugging)
        snap_to_grid_nm: nm to snap to grid
        kwargs: cross_section settings
    """
    x = cross_section(**kwargs)
    points = (gf.snap.snap_to_grid(points, nm=snap_to_grid_nm)
              if snap_to_grid_nm else points)

    auto_widen = x.info.get("auto_widen", False)
    auto_widen_minimum_length = x.info.get("auto_widen_minimum_length", 200.0)
    taper_length = x.info.get("taper_length", 10.0)
    width = x.info.get("width", 2.0)
    width_wide = x.info.get("width_wide", None)
    references = []
    bend90 = bend(cross_section=cross_section, **
                  kwargs) if callable(bend) else bend
    # bsx = bsy = _get_bend_size(bend90)
    taper = taper or taper_function(
        cross_section=cross_section,
        width1=width,
        width2=width_wide,
        length=taper_length,
    )
    taper = taper(cross_section=cross_section, **
                  kwargs) if callable(taper) else taper

    # If there is a taper, make sure its length is known
    if taper and isinstance(taper, Component):
        if "length" not in taper.info:
            _taper_ports = list(taper.ports.values())
            taper.info["length"] = _taper_ports[-1].x - _taper_ports[0].x

    straight_fall_back_no_taper = straight_fall_back_no_taper or straight

    # Remove any flat angle, otherwise the algorithm won't work
    points = remove_flat_angles(points)
    points = np.array(points)

    straight_sections = []  # (p0, angle, length)
    p0_straight = points[0]
    p1 = points[1]

    total_length = 0  # Keep track of the total path length

    if not hasattr(bend90.info, "length"):
        raise ValueError(
            f"bend {bend90} needs to have bend.info.length defined")

    bend_length = bend90.info.length

    dp = p1 - p0_straight
    bend_orientation = None
    if _is_vertical(p0_straight, p1):
        if dp[1] > 0:
            bend_orientation = 90
        elif dp[1] < 0:
            bend_orientation = 270
    elif _is_horizontal(p0_straight, p1):
        if dp[0] > 0:
            bend_orientation = 0
        elif dp[0] < 0:
            bend_orientation = 180

    if bend_orientation is None:
        return on_route_error(points=points, cross_section=x)

    layer = x.info["layer"]
    try:
        pname_west, pname_north = [
            p.name for p in _get_bend_ports(bend=bend90, layer=layer)
        ]
    except ValueError as exc:
        raise ValueError(
            f"Did not find 2 ports on layer {layer}. Got {list(bend90.ports.values())}"
        ) from exc
    n_o_bends = points.shape[0] - 2
    total_length += n_o_bends * bend_length

    previous_port_point = points[0]
    bend_points = [previous_port_point]

    # Add bend sections and record straight-section information
    for i in range(1, points.shape[0] - 1):
        bend_origin, rotation, x_reflection = _get_bend_reference_parameters(
            points[i - 1], points[i], points[i + 1], bend90, x.info["layer"])
        bend_ref = gen_sref(bend90, rotation, x_reflection, pname_west,
                            bend_origin)
        references.append(bend_ref)

        dx_points = points[i][0] - points[i - 1][0]
        dy_points = points[i][1] - points[i - 1][1]

        if abs(dx_points) < TOLERANCE:
            matching_ports = [
                port for port in bend_ref.ports.values()
                if np.isclose(port.x, points[i][0])
            ]

        if abs(dy_points) < TOLERANCE:
            matching_ports = [
                port for port in bend_ref.ports.values()
                if np.isclose(port.y, points[i][1])
            ]

        if matching_ports:
            next_port = matching_ports[0]
            other_port_name = set(bend_ref.ports.keys()) - {next_port.name}
            other_port = bend_ref.ports[list(other_port_name)[0]]
            bend_points.append(next_port.midpoint)
            bend_points.append(other_port.midpoint)
            previous_port_point = other_port.midpoint

        try:
            straight_sections += [(
                p0_straight,
                bend_orientation,
                get_straight_distance(p0_straight, bend_origin),
            )]
        except RouteError:
            on_route_error(
                points=(p0_straight, bend_origin),
                cross_section=x,
                references=references,
            )

        p0_straight = bend_ref.ports[pname_north].midpoint
        bend_orientation = bend_ref.ports[pname_north].orientation

    bend_points.append(points[-1])

    try:
        straight_sections += [(
            p0_straight,
            bend_orientation,
            get_straight_distance(p0_straight, points[-1]),
        )]
    except RouteError:
        on_route_error(
            points=(p0_straight, points[-1]),
            cross_section=x,
            references=references,
        )

    # with_point_markers=True
    # print()
    # for i, point in enumerate(points):
    #     print(i, point)
    # print()
    # for i, point in enumerate(bend_points):
    #     print(i, point)

    # ensure bend connectivity
    for i, point in enumerate(points[:-1]):
        sx = np.sign(points[i + 1][0] - point[0])
        sy = np.sign(points[i + 1][1] - point[1])
        bsx = np.sign(bend_points[2 * i + 1][0] - bend_points[2 * i][0])
        bsy = np.sign(bend_points[2 * i + 1][1] - bend_points[2 * i][1])
        if bsx * sx == -1 or bsy * sy == -1:
            return on_route_error(points=points,
                                  cross_section=x,
                                  references=references)

    wg_refs = []
    for straight_origin, angle, length in straight_sections:
        with_taper = False
        # wg_width = list(bend90.ports.values())[0].width
        length = snap_to_grid(length)
        total_length += length

        if auto_widen and length > auto_widen_minimum_length and width_wide:
            # Taper starts where straight would have started
            with_taper = True
            length = length - 2 * taper_length
            taper_origin = straight_origin

            pname_west, pname_east = [
                p.name
                for p in _get_straight_ports(taper, layer=x.info["layer"])
            ]
            taper_ref = taper.ref(position=taper_origin,
                                  port_id=pname_west,
                                  rotation=angle)

            references.append(taper_ref)
            wg_refs += [taper_ref]

            # Update start straight position
            straight_origin = taper_ref.ports[pname_east].midpoint

            # Straight waveguide
            kwargs_wide = kwargs.copy()
            kwargs_wide.update(width=width_wide)
            cross_section_wide = gf.partial(cross_section, **kwargs_wide)
            wg = straight(length=length, cross_section=cross_section_wide)
        else:
            wg = straight_fall_back_no_taper(length=length,
                                             cross_section=cross_section,
                                             **kwargs)

        if straight_ports is None:
            straight_ports = [
                p.name for p in _get_straight_ports(wg, layer=x.info["layer"])
            ]
        pname_west, pname_east = straight_ports

        wg_ref = wg.ref()
        wg_ref.move(wg.ports[pname_west], (0, 0))
        if mirror_straight:
            wg_ref.reflect_v(list(wg_ref.ports.values())[0].name)

        wg_ref.rotate(angle)
        wg_ref.move(straight_origin)

        if length > 0:
            references.append(wg_ref)
            wg_refs += [wg_ref]

        port_index_out = 1
        if with_taper:
            # Second taper:
            # Origin at end of straight waveguide, starting from east side of taper

            taper_origin = wg_ref.ports[pname_east]
            pname_west, pname_east = [
                p.name
                for p in _get_straight_ports(taper, layer=x.info["layer"])
            ]

            taper_ref = taper.ref(
                position=taper_origin,
                port_id=pname_east,
                rotation=angle + 180,
                v_mirror=True,
            )
            # references += [
            #     gf.Label(
            #         text=f"a{angle}",
            #         position=taper_ref.center,
            #         layer=2,
            #         texttype=0,
            #     )
            # ]
            references.append(taper_ref)
            wg_refs += [taper_ref]
            port_index_out = 0

    if with_point_markers:
        route = get_route_error(points, cross_section=x)
        references += route.references

    port_input = list(wg_refs[0].ports.values())[0]
    port_output = list(wg_refs[-1].ports.values())[port_index_out]
    length = snap_to_grid(float(total_length))
    return Route(references=references,
                 ports=(port_input, port_output),
                 length=length)
示例#20
0
def spiral_inner_io(N: int = 6,
                    x_straight_inner_right: float = 150.0,
                    x_straight_inner_left: float = 50.0,
                    y_straight_inner_top: float = 50.0,
                    y_straight_inner_bottom: float = 10.0,
                    grating_spacing: float = 127.0,
                    waveguide_spacing: float = 3.0,
                    bend90_function: ComponentFactory = bend_euler,
                    bend180_function: ComponentFactory = bend_euler180,
                    straight: ComponentFactory = straight_function,
                    length: Optional[float] = None,
                    cross_section: CrossSectionFactory = strip,
                    cross_section_bend: Optional[CrossSectionFactory] = None,
                    **kwargs) -> Component:
    """Returns Spiral with ports inside the spiral loop.
    You can add grating couplers inside .

    Args:
        N: number of loops
        x_straight_inner_right:
        x_straight_inner_left:
        y_straight_inner_top:
        y_straight_inner_bottom:
        grating_spacing: defaults to 127 for fiber array
        waveguide_spacing: center to center spacing
        bend90_function
        bend180_function
        straight: straight function
        length: spiral target length (um), overrides x_straight_inner_left
            to match the length by a simple 1D interpolation
        cross_section:
        cross_section_bend: for the bends
        kwargs: cross_section settings

    """
    dx = dy = waveguide_spacing
    x = cross_section(**kwargs)
    width = x.info.get("width")
    layer = x.info.get("layer")
    cross_section_bend = cross_section_bend or cross_section

    if length:
        x_straight_inner_left = get_straight_length(
            length=length,
            spiral_function=spiral_inner_io,
            N=N,
            x_straight_inner_right=x_straight_inner_right,
            x_straight_inner_left=x_straight_inner_left,
            y_straight_inner_top=y_straight_inner_top,
            y_straight_inner_bottom=y_straight_inner_bottom,
            grating_spacing=grating_spacing,
            waveguide_spacing=waveguide_spacing,
        )

    _bend180 = gf.call_if_func(bend180_function,
                               cross_section=cross_section_bend,
                               **kwargs)
    _bend90 = gf.call_if_func(bend90_function,
                              cross_section=cross_section_bend,
                              **kwargs)

    rx, ry = get_bend_port_distances(_bend90)
    _, rx180 = get_bend_port_distances(
        _bend180)  # rx180, second arg since we rotate

    component = Component()

    p1 = gf.Port(
        name="o1",
        midpoint=(0, y_straight_inner_top),
        orientation=270,
        width=width,
        layer=layer,
        cross_section=cross_section_bend,
    )
    p2 = gf.Port(
        name="o2",
        midpoint=(grating_spacing, y_straight_inner_top),
        orientation=270,
        width=width,
        layer=layer,
        cross_section=cross_section_bend,
    )

    component.add_port(name="o1", port=p1)
    component.add_port(name="o2", port=p2)

    # Create manhattan path going from west grating to westest port of bend 180
    _pt = np.array(p1.position)
    pts_w = [_pt]

    for i in range(N):
        y1 = y_straight_inner_top + ry + (2 * i + 1) * dy
        x2 = grating_spacing + 2 * rx + x_straight_inner_right + (2 * i +
                                                                  1) * dx
        y3 = -y_straight_inner_bottom - ry - (2 * i + 3) * dy
        x4 = -x_straight_inner_left - (2 * i + 1) * dx
        if i == N - 1:
            x4 = x4 - rx180 + dx

        _pt1 = np.array([_pt[0], y1])
        _pt2 = np.array([x2, _pt1[1]])
        _pt3 = np.array([_pt2[0], y3])
        _pt4 = np.array([x4, _pt3[1]])
        _pt5 = np.array([_pt4[0], 0])
        _pt = _pt5

        pts_w += [_pt1, _pt2, _pt3, _pt4, _pt5]

    route_west = round_corners(pts_w,
                               bend=_bend90,
                               straight=straight,
                               cross_section=cross_section,
                               **kwargs)
    component.add(route_west.references)

    # Add loop back
    bend180_ref = _bend180.ref(port_id="o2",
                               position=route_west.ports[1],
                               rotation=90)
    component.add(bend180_ref)

    # Create manhattan path going from east grating to eastest port of bend 180
    _pt = np.array(p2.position)
    pts_e = [_pt]

    for i in range(N):
        y1 = y_straight_inner_top + ry + (2 * i) * dy
        x2 = grating_spacing + 2 * rx + x_straight_inner_right + 2 * i * dx
        y3 = -y_straight_inner_bottom - ry - (2 * i + 2) * dy
        x4 = -x_straight_inner_left - (2 * i) * dx

        _pt1 = np.array([_pt[0], y1])
        _pt2 = np.array([x2, _pt1[1]])
        _pt3 = np.array([_pt2[0], y3])
        _pt4 = np.array([x4, _pt3[1]])
        _pt5 = np.array([_pt4[0], 0])
        _pt = _pt5

        pts_e += [_pt1, _pt2, _pt3, _pt4, _pt5]

    route_east = round_corners(pts_e,
                               bend=_bend90,
                               straight=straight,
                               cross_section=cross_section,
                               **kwargs)
    component.add(route_east.references)

    length = route_east.length + route_west.length + _bend180.info.length
    component.info.length = snap_to_grid(length + 2 * y_straight_inner_top)
    return component
示例#21
0
def get_netlist(
    component: Component,
    full_settings: bool = False,
    layer_label: Tuple[int, int] = LAYER.LABEL_INSTANCE,
    tolerance: int = 1,
) -> omegaconf.DictConfig:
    """From a component returns instances, connections and placements dict. It
    assumes that ports with same width, x, y are connected.

     Args:
         component: to Extract netlist
         full_settings: True returns all settings, false only the ones that have changed
         layer_label: label to read instanceNames from (if any)
         tolerance: tolerance in nm to consider two ports the same

     Returns:
         connections: Dict of Instance1Name,portName: Instace2Name,portName
         instances: Dict of instances and settings
         placements: Dict of instances and placements (x, y, rotation)
         port: Dict portName: CompoentName,port
         name: name of component

    """
    placements = {}
    instances = {}
    connections = {}
    top_ports = {}

    for reference in component.references:
        c = reference.parent
        origin = reference.origin
        x = float(snap_to_grid(origin[0]))
        y = float(snap_to_grid(origin[1]))
        reference_name = get_instance_name(component,
                                           reference,
                                           layer_label=layer_label)

        settings = (c.info.get("full", {}) if full_settings else c.info.get(
            "changed", {}))
        instances[reference_name] = dict(
            component=getattr(c.info, "function_name", c.name),
            settings=settings,
        )
        placements[reference_name] = dict(
            x=x,
            y=y,
            rotation=int(reference.rotation),
            mirror=reference.x_reflection,
        )

    # store where ports are located
    name2port = {}

    # Initialize a dict of port locations to Instance1Name,PortNames
    port_locations = {}

    # TOP level ports
    ports = component.get_ports(depth=0)
    top_ports_list = set()
    for port in ports:
        src = port.name
        name2port[src] = port
        top_ports_list.add(src)

    # lower level ports
    for reference in component.references:
        for port in reference.ports.values():
            reference_name = get_instance_name(component,
                                               reference,
                                               layer_label=layer_label)
            src = f"{reference_name},{port.name}"
            name2port[src] = port

    # build connectivity port_locations = Dict[Tuple(x,y,width), set of portNames]
    for name, port in name2port.items():
        xyw = tuple(
            round(1000 * v)
            for v in snap_to_grid((port.x, port.y, port.width), nm=tolerance))
        if xyw not in port_locations:
            port_locations[xyw] = set()
        port_locations[xyw].add(name)

    for xyw, names_set in port_locations.items():
        if len(names_set) > 2:
            x, y, w = (v / 1000 for v in xyw)
            raise ValueError(
                f"more than 2 connections at {x, y} {list(names_set)}, width  = {w} "
            )
        if len(names_set) == 2:
            names_list = list(names_set)
            src = names_list[0]
            dst = names_list[1]
            if src in top_ports_list:
                top_ports[src] = dst
            elif dst in top_ports_list:
                top_ports[dst] = src
            else:
                src_dest = sorted([src, dst])
                connections[src_dest[0]] = src_dest[1]

    connections_sorted = {
        k: connections[k]
        for k in sorted(list(connections.keys()))
    }
    placements_sorted = {
        k: placements[k]
        for k in sorted(list(placements.keys()))
    }
    instances_sorted = {
        k: instances[k]
        for k in sorted(list(instances.keys()))
    }
    return omegaconf.DictConfig(
        dict(
            connections=connections_sorted,
            instances=instances_sorted,
            placements=placements_sorted,
            ports=top_ports,
            name=component.name,
        ))
示例#22
0
def coupler(gap: float = 0.236,
            length: float = 20.0,
            coupler_symmetric: ComponentFactory = coupler_symmetric_function,
            coupler_straight: ComponentFactory = coupler_straight_function,
            dy: float = 5.0,
            dx: float = 10.0,
            cross_section: CrossSectionFactory = strip,
            **kwargs) -> Component:
    r"""Symmetric coupler.

    Args:
        gap: between straights
        length: of coupling region
        coupler_symmetric
        coupler_straight
        dy: port to port vertical spacing
        dx: length of bend in x direction
        cross_section: factory
        kwargs: cross_section settings

    .. code::

               dx                                 dx
            |------|                           |------|
         o2 ________                           ______o3
                    \                         /           |
                     \        length         /            |
                      ======================= gap         | dy
                     /                       \            |
            ________/                         \_______    |
         o1                                          o4

                        coupler_straight  coupler_symmetric


    """
    length = snap_to_grid(length)
    assert_on_2nm_grid(gap)
    c = Component()

    sbend = coupler_symmetric(gap=gap,
                              dy=dy,
                              dx=dx,
                              cross_section=cross_section,
                              **kwargs)

    sr = c << sbend
    sl = c << sbend
    cs = c << coupler_straight(
        length=length, gap=gap, cross_section=cross_section, **kwargs)
    sl.connect("o2", destination=cs.ports["o1"])
    sr.connect("o1", destination=cs.ports["o4"])

    c.add_port("o1", port=sl.ports["o3"])
    c.add_port("o2", port=sl.ports["o4"])
    c.add_port("o3", port=sr.ports["o3"])
    c.add_port("o4", port=sr.ports["o4"])

    c.absorb(sl)
    c.absorb(sr)
    c.absorb(cs)
    c.info.length = sbend.info.length
    c.info.min_bend_radius = sbend.info.min_bend_radius
    c.auto_rename_ports()
    return c
示例#23
0
def add_ports_from_markers_center(
    component: Component,
    pin_layer: Layer = (1, 10),
    port_layer: Optional[Layer] = None,
    inside: bool = False,
    tol: float = 0.1,
    pin_extra_width: float = 0.0,
    min_pin_area_um2: Optional[float] = None,
    max_pin_area_um2: float = 150.0 * 150.0,
    skip_square_ports: bool = True,
    xcenter: Optional[float] = None,
    ycenter: Optional[float] = None,
    port_name_prefix: str = "",
    port_type: str = "optical",
) -> Component:
    """Add ports from rectangular pin markers.

    markers at port center, so half of the marker goes inside and half ouside the port.

    guess port orientation from the component center

    Args:
        component: to read polygons from and to write ports to
        pin_layer: GDS layer for maker [int, int]
        port_layer: for the new created port
        inside: True-> markers  inside. False-> markers at center
        tol: tolerance for comparing how rectangular is the pin
        pin_extra_width: 2*offset from pin to straight
        min_pin_area_um2: ignores pins with area smaller than min_pin_area_um2
        max_pin_area_um2: ignore pins for area above certain size
        skip_square_ports: skips square ports (hard to guess orientation)
        xcenter: for guessing orientation of rectangular ports
        ycenter: for guessing orientation of rectangular ports
        port_name_prefix: o for optical ports (o1, o2, o3)

    For the default center case (inside=False)

    .. code::
           _______________
          |               |
          |               |
         |||             |||____  | pin_extra_width/2 > 0
         |||             |||
         |||             |||____
         |||             |||
          |      __       |
          |_______________|
                 __


    For the inside case (inside=True)

    .. code::
           _______________
          |               |
          |               |
          |               |
          | |             |
          | |             |
          |      __       |
          |               |
          |_______________|



    dx < dy: port is east or west
        x > xc: east
        x < xc: west

    dx > dy: port is north or south
        y > yc: north
        y < yc: south

    dx = dy
        x > xc: east
        x < xc: west

    """
    xc = xcenter or component.x
    yc = ycenter or component.y
    xmax = component.xmax
    xmin = component.xmin
    ymax = component.ymax
    ymin = component.ymin

    port_markers = read_port_markers(component, layers=(pin_layer,))
    layer = port_layer or pin_layer
    port_locations = []

    ports = {}

    for i, p in enumerate(port_markers.polygons):
        port_name = f"{port_name_prefix}{i+1}" if port_name_prefix else i
        dy = p.ymax - p.ymin
        dx = p.xmax - p.xmin
        x = p.x
        y = p.y
        if min_pin_area_um2 and dx * dy <= min_pin_area_um2:
            continue

        if max_pin_area_um2 and dx * dy > max_pin_area_um2:
            continue

        # skip square ports as they have no clear orientation
        if skip_square_ports and snap_to_grid(dx) == snap_to_grid(dy):
            continue
        pxmax = p.xmax
        pxmin = p.xmin
        pymax = p.ymax
        pymin = p.ymin

        if dx < dy and x > xc:  # east
            orientation = 0
            width = dy
            if inside:
                x = p.xmax
        elif dx < dy and x < xc:  # west
            orientation = 180
            width = dy
            if inside:
                x = p.xmin
        elif dx > dy and y > yc:  # north
            orientation = 90
            width = dx
            if inside:
                y = p.ymax
        elif dx > dy and y < yc:  # south
            orientation = 270
            width = dx
            if inside:
                y = p.ymin

        # port markers have same width and height
        # check which edge (E, W, N, S) they are closer to
        elif pxmax > xmax - tol:  # east
            orientation = 0
            width = dy
            x = p.xmax
        elif pxmin < xmin + tol:  # west
            orientation = 180
            width = dy
            x = p.xmin
        elif pymax > ymax - tol:  # north
            orientation = 90
            width = dx
            y = p.ymax
        elif pymin < ymin + tol:  # south
            orientation = 270
            width = dx
            y = p.ymin

        x = snap_to_grid(x)
        y = snap_to_grid(y)
        width = np.round(width - pin_extra_width, 3)

        if (x, y) not in port_locations:
            port_locations.append((x, y))
            ports[port_name] = Port(
                name=port_name,
                midpoint=(x, y),
                width=width,
                orientation=orientation,
                layer=layer,
                port_type=port_type,
            )

    ports = sort_ports_clockwise(ports)

    for port_name, port in ports.items():
        component.add_port(name=port_name, port=port)
    return component
示例#24
0
def bend_euler(angle: int = 90,
               p: float = 0.5,
               with_arc_floorplan: bool = True,
               npoints: int = 720,
               direction: str = "ccw",
               with_cladding_box: bool = True,
               cross_section: CrossSectionFactory = strip,
               **kwargs) -> Component:
    """Returns an euler bend that adiabatically transitions from straight to curved.
    By default, `radius` corresponds to the minimum radius of curvature of the bend.
    However, if `with_arc_floorplan` is True, `radius` corresponds to the effective
    radius of curvature (making the curve a drop-in replacement for an arc). If
    p < 1.0, will create a "partial euler" curve as described in Vogelbacher et.
    al. https://dx.doi.org/10.1364/oe.27.031394

    default p = 0.5 based on this paper
    https://www.osapublishing.org/oe/fulltext.cfm?uri=oe-25-8-9150&id=362937

    Args:
        angle: total angle of the curve
        p: Proportion of the curve that is an Euler curve
        with_arc_floorplan: If False: `radius` is the minimum radius of curvature
          If True: The curve scales such that the endpoints match a bend_circular
          with parameters `radius` and `angle`
        npoints: Number of points used per 360 degrees
        direction: cw (clock-wise) or ccw (counter clock-wise)
        with_cladding_box: to avoid DRC acute angle errors in cladding
        cross_section:


    """
    x = cross_section(**kwargs)
    radius = x.info["radius"]

    p = euler(radius=radius,
              angle=angle,
              p=p,
              use_eff=with_arc_floorplan,
              npoints=npoints)
    c = extrude(p, x)
    c.info.length = snap_to_grid(p.length())
    c.info.dy = abs(float(p.points[0][0] - p.points[-1][0]))
    c.info.radius_min = float(p.info["Rmin"])
    c.info.radius = radius

    if with_cladding_box and x.info["layers_cladding"]:
        layers_cladding = x.info["layers_cladding"]
        cladding_offset = x.info["cladding_offset"]
        top = cladding_offset if angle == 180 else 0
        points = get_padding_points(
            component=c,
            default=0,
            bottom=cladding_offset,
            right=cladding_offset,
            top=top,
        )
        for layer in layers_cladding or []:
            c.add_polygon(points, layer=layer)

    if direction == "cw":
        c.mirror(p1=[0, 0], p2=[1, 0])
    return c