Ejemplo n.º 1
0
def get_instance_name(
    component, reference, layer_label: Tuple[int, int] = LAYER.LABEL_INSTANCE
) -> str:
    """Takes a component names the instance based on its XY location or a 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: layer of the label (ignores layer_label[1]). Phidl ignores purpose of labels.
    """

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

    # default instance name follows componetName_x_y
    text = 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_1nm_grid(label.x)
        yl = snap_to_1nm_grid(label.y)
        if x == xl and y == yl and label.layer == layer_label[0]:
            # print(label.text, xl, yl, x, y)
            return label.text

    return text
Ejemplo n.º 2
0
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))
        else:
            value = str(snap_to_1nm_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)
    elif isinstance(value, Device):
        value = clean_name(value.name)
    elif callable(value):
        value = value.__name__
    else:
        value = clean_name(str(value))
    return value
Ejemplo n.º 3
0
def add_ports_from_markers_square(
    component: Component,
    layer: Layer = pp.LAYER.PORTE,
    port_type: "str" = "dc",
    orientation: int = 90,
    min_pin_area_um2: float = 0,
    pin_extra_width: float = 0.0,
    port_names: Optional[Iterable[str]] = None,
):
    """add ports from markers in port_layer

    adds ports at the marker center

    Args:
        component: to read polygons from and to write ports to
        layer: for port markers
        port_type: electrical, dc, optical
        orientation: orientation in degrees
            90: north, 0: east, 180: west, 270: south
        pin_extra_width: 2*offset from pin to waveguide
        min_pin_area_um2: ignores pins with area smaller than min_pin_area_um2
        port_names: names of the ports (defaults to f"{port_type}_{i}")

    """
    port_markers = read_port_markers(component, [layer])

    port_names = None or [f"{port_type}_{i}" for i in range(len(port_markers.polygons))]

    for port_name, p in zip(port_names, port_markers.polygons):
        dy = snap_to_1nm_grid(p.ymax - p.ymin)
        dx = snap_to_1nm_grid(p.xmax - p.xmin)
        x = p.x
        y = p.y
        if dx == dy and dx * dy > min_pin_area_um2:
            component.add_port(
                port_name,
                midpoint=(x, y),
                width=dx - pin_extra_width,
                orientation=orientation,
                port_type=port_type,
                layer=layer,
            )
Ejemplo n.º 4
0
def spiral_circular(
    length=1e3,
    wg_width=0.5,
    spacing=3,
    min_bend_radius=5,
    points=1000,
    layer=pp.LAYER.WG,
):
    """ Returns a circular spiral

    Args:
        length(um):

    .. plot::
        :include-source:

        import pp

        c = pp.c.spiral_circular(length=1e3)
        pp.plotgds(c)

    """
    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")
    """ component """
    c = pp.Component()
    c.length = snap_to_1nm_grid(length)
    c.add_polygon(ps, layer=layer)

    c.add_port(
        name="W0",
        midpoint=(s[0], s[1]),
        orientation=180,
        layer=layer,
        width=wg_width,
    )
    c.add_port(
        name="E0",
        midpoint=(e[0], e[1]),
        orientation=180,
        layer=layer,
        width=wg_width,
    )
    return c
Ejemplo n.º 5
0
def get_netlist(
    component, full_settings=False, layer_label: Tuple[int, int] = LAYER.LABEL_INSTANCE
) -> Dict[str, Dict]:
    """From a component returns instances and placements dicts.
    it assumes that ports with same x,y are connected.

    Args:
        full_settings: True returns all settings, false only the ones that have changed
        layer_label: label to read instanceNames from (if any)

    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 = snap_to_1nm_grid(reference.origin)
        x = snap_to_1nm_grid(origin[0])
        y = snap_to_1nm_grid(origin[1])
        reference_name = get_instance_name(
            component, reference, layer_label=layer_label
        )
        settings = c.get_settings(full_settings=full_settings)
        instances[reference_name] = dict(
            component=c.function_name, settings=settings["settings"],
        )
        placements[reference_name] = dict(x=x, y=y, rotation=int(reference.rotation))

    # 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), set of portNames]
    for name, port in name2port.items():
        xy = snap_to_1nm_grid((port.x, port.y))
        if xy not in port_locations:
            port_locations[xy] = set()
        port_locations[xy].add(name)

    for xy, names_set in port_locations.items():
        if len(names_set) > 2:
            raise ValueError(f"more than 2 connections at {xy} {list(names_set)}")
        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 dict(
        connections=connections_sorted,
        instances=instances_sorted,
        placements=placements_sorted,
        ports=top_ports,
        name=component.name,
    )
Ejemplo n.º 6
0
def recurse_references(
    component,
    instances=None,
    placements=None,
    connections=None,
    port_locations=None,
    dx: float = 0.0,
    dy: float = 0.0,
    recursive=True,
    full_settings=False,
    level: int = 0,
):
    """From a component returns instances and placements dicts.
    it assumes that ports with same x,y are connected.
    Ensures that connections are at the same level of hierarchy.
    Deprecated! use pp.get_netlist instead.

    Args:
        component: to recurse
        instances: instance_name_x_y to settings dict
        placements: instance_name to x,y,rotation dict
        connections: instance_name_src,portName: instance_name_dst,portName
        port_locations: dict((x,y): set([referenceName, Port]))
        dx: port displacement in x (for recursice case)
        dy: port displacement in y (for recursive case)
        recursive: goes down the hierarchy
        level: current level of the hierarchy (0: Top level, 1: first level ...)

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

    """
    placements = placements or {}
    instances = instances or {}
    connections = connections or {}
    port_locations = port_locations or {
        snap_to_1nm_grid((port.x, port.y)): set()
        for port in component.get_ports()
    }

    level_name = component.name
    connections[level_name] = {}

    for r in component.references:
        c = r.parent
        x = snap_to_1nm_grid(r.x + dx)
        y = snap_to_1nm_grid(r.y + dy)
        reference_name = f"{c.name}_{int(x)}_{int(y)}"
        settings = c.get_settings(full_settings=full_settings)
        instances[reference_name] = dict(component=c.function_name,
                                         settings=settings)
        placements[reference_name] = dict(x=x, y=y, rotation=int(r.rotation))
        for port in r.get_ports_list():
            src = f"{reference_name},{port.name}"
            xy = snap_to_1nm_grid((port.x + dx, port.y + dy))
            assert (
                xy in port_locations
            ), f"{xy} for {port.name} {c.name} in level {level} not in {port_locations}"
            src_list = port_locations[xy]
            if len(src_list) > 0:
                for src2 in src_list:
                    connections[level_name][src2] = src
            else:
                src_list.add(src)

    if recursive:
        for r in component.references:
            c = r.parent
            dx = r.x - c.x
            dy = r.y - c.y
            # print(level, c.name, r.x, dx, c.x)
            if len(c.references) > 0:
                c2, i2, p2 = recurse_references(
                    component=c,
                    instances=instances,
                    placements=placements,
                    connections=connections,
                    dx=dx,
                    dy=dy,
                    port_locations=port_locations,
                    level=level + 1,
                    full_settings=full_settings,
                )
                placements.update(p2)
                instances.update(i2)
                connections.update(c2)

    # def get_level(key):
    #     int(key.split('_')[0])

    # levels = max([int(key.split('_')[0]) for key in x.keys()])
    # keys = connections.keys()

    flat = {}
    for connections_per_level in connections.values():
        for k, v in connections_per_level.items():
            flat[k] = v
    connections["flat"] = flat

    # for key in keys:
    #     level = get_level(key)
    #     if level<levels:
    #         for k, v in connections[key].items():
    #             flat[k] = v

    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 connections, instances_sorted, placements_sorted
Ejemplo n.º 7
0
def add_ports_from_markers_center(
    component: Component,
    port_layer2type: Dict[Layer, str] = port_layer2type_default,
    port_type2layer: Dict[str, Layer] = port_type2layer_default,
    inside: bool = False,
    tol: float = 0.1,
    pin_extra_width: float = 0.0,
    min_pin_area_um2: Optional[float] = None,
):
    """add ports from polygons in certain layers

    markers at port center, so half of the marker goes inside and half ouside the port. Works only for rectangular pins.

    Args:
        component: to read polygons from and to write ports to
        port_layer2type: dict of layer to port_type
        port_type2layer: dict of port_type to layer
        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 waveguide
        min_pin_area_um2: ignores pins with area smaller than min_pin_area_um2
        orientation_for_square_pins: electrical square points orientation in degrees
            90: north, 0: east, 180: west, 270: south

    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

    """
    i = 0
    xc = component.x
    yc = component.y
    xmax = component.xmax
    xmin = component.xmin
    ymax = component.ymax
    ymin = component.ymin

    for port_layer, port_type in port_layer2type.items():
        port_markers = read_port_markers(component, [port_layer])

        for p in port_markers.polygons:
            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

            # skip square ports as they have no clear orientation
            if snap_to_1nm_grid(dx) == snap_to_1nm_grid(dy):
                continue
            layer = port_type2layer[port_type]
            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

            component.add_port(
                i,
                midpoint=(x, y),
                width=width - pin_extra_width,
                orientation=orientation,
                port_type=port_type,
                layer=layer,
            )
            i += 1
Ejemplo n.º 8
0
def round_corners(
    points,
    bend90,
    straight_factory,
    taper=None,
    straight_factory_fall_back_no_taper=None,
    mirror_straight=False,
    straight_ports=None,
):
    """Return dict with reference list with rounded waveguide route from a list of manhattan points.
    Also returns a dict of ports
    As well as settings

    Args:
        points: manhattan route defined by waypoints
        bend90: the bend to use for 90Deg turns
        straight_factory: the straight factory to use to generate straight portions
        taper: taper for straight portions. If None, no tapering
        straight_factory_fall_back_no_taper: factory to use for straights in case there is no space to put a pair of tapers
        mirror_straight: mirror_straight waveguide
        straight_ports: port names for straights. If not specified, will use some heuristic to find them
    """
    references = []
    ports = dict()
    settings = dict()

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

    if straight_factory_fall_back_no_taper is None:
        straight_factory_fall_back_no_taper = straight_factory

    # 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 "length" in bend90.info:
        bend_length = bend90.info["length"]
    else:
        bend_length = 0

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

    assert a0 is not None, "Points should be manhattan, got {} {}".format(
        p0_straight, p1
    )

    pname_west, pname_north = [p.name for p in _get_bend_ports(bend90)]

    n_o_bends = points.shape[0] - 2
    total_length += n_o_bends * bend_length
    # 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
        )

        bend_ref = gen_sref(bend90, rotation, x_reflection, pname_west, bend_origin)
        references.append(bend_ref)

        straight_sections += [
            (p0_straight, a0, get_straight_distance(p0_straight, bend_origin))
        ]

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

    straight_sections += [
        (p0_straight, a0, get_straight_distance(p0_straight, points[-1]))
    ]

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

        total_length += length

        if taper is not None and length > 2 * taper.info["length"] + 1.0:
            length = length - 2 * taper.info["length"]
            with_taper = True

        if with_taper:
            # Taper starts where straight would have started
            taper_origin = straight_origin

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

            wg_width = taper.ports[pname_east].width

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

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

        # Straight waveguide
        if with_taper or taper is None:
            wg = straight_factory(length=length, width=wg_width)
        else:
            wg = straight_factory_fall_back_no_taper(length=length, width=wg_width)

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

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

        wg_ref.rotate(angle)
        wg_ref.move(straight_origin)
        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)]
            taper_ref = taper.ref(
                position=taper_origin, port_id=pname_east, rotation=angle + 180
            )

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

    ports["input"] = list(wg_refs[0].ports.values())[0]
    ports["output"] = list(wg_refs[-1].ports.values())[port_index_out]
    settings["length"] = snap_to_1nm_grid(float(total_length))
    return dict(references=references, ports=ports, settings=settings)