Skip to content

Routing API

route_single

route_single

route_single places a Manhattan route between two ports.

route_single only works for an individual routes. For routing groups of ports you need to use route_bundle instead

To make a route, you need to supply:

  • input port
  • output port
  • bend
  • straight
  • taper to taper to wider straights and reduce straight loss (Optional)

To generate a route:

  1. Generate the backbone of the route. This is a list of manhattan coordinates that the route would pass through if it used only sharp bends (right angles)

  2. Replace the corners by bend references (with rotation and position computed from the manhattan backbone)

  3. Add tapers if needed and if space permits

  4. generate straight portions in between tapers or bends

route_single

route_single(
    component: Component,
    port1: Port,
    port2: Port,
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    start_straight_length: float = 0.0,
    end_straight_length: float = 0.0,
    waypoints: WayPoints | None = None,
    steps: Sequence[Step] | None = None,
    port_type: str | None = None,
    allow_width_mismatch: bool = False,
    radius: float | None = None,
    route_width: float | None = None,
    auto_taper: bool = True,
    on_error: Literal["error"] | None = None,
    layer_transitions: LayerTransitions | None = None,
) -> ManhattanRoute

Returns a Manhattan Route between 2 ports.

The references are straights, bends and tapers.

Parameters:

Name Type Description Default
component Component

to place the route into.

required
port1 Port

start port.

required
port2 Port

end port.

required
cross_section CrossSectionSpec | None

spec.

None
layer LayerSpec | None

layer spec.

None
bend ComponentSpec

bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
start_straight_length float

length of starting straight.

0.0
end_straight_length float

length of end straight.

0.0
waypoints WayPoints | None

optional list of points to pass through.

None
steps Sequence[Step] | None

optional list of steps to pass through. Each step is a dict with keys: x (absolute), y (absolute), dx (relative), dy (relative). Use x/y to set an absolute coordinate and dx/dy to shift relative to the current position.

None
port_type str | None

port type to route.

None
allow_width_mismatch bool

allow different port widths.

False
radius float | None

bend radius. If None, defaults to cross_section.radius.

None
route_width float | None

width of the route in um. If None, defaults to cross_section.width.

None
auto_taper bool

add auto tapers.

True
on_error Literal['error'] | None

what to do on error. If error, raises an error. If None ignores the error.

None
layer_transitions LayerTransitions | None

dictionary of layer transitions to use for the routing when auto_taper=True.

None
Example
import gdsfactory as gf

c = gf.Component()
mmi1 = c << gf.components.mmi1x2()
mmi2 = c << gf.components.mmi1x2()
mmi2.move((40, 20))
gf.routing.route_single(c, mmi1.ports["o2"], mmi2.ports["o1"], radius=5, cross_section="strip")
c.plot()
Source code in gdsfactory/routing/route_single.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def route_single(
    component: Component,
    port1: Port,
    port2: Port,
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    start_straight_length: float = 0.0,
    end_straight_length: float = 0.0,
    waypoints: WayPoints | None = None,
    steps: Sequence[Step] | None = None,
    port_type: str | None = None,
    allow_width_mismatch: bool = False,
    radius: float | None = None,
    route_width: float | None = None,
    auto_taper: bool = True,
    on_error: Literal["error"] | None = None,
    layer_transitions: LayerTransitions | None = None,
) -> ManhattanRoute:
    """Returns a Manhattan Route between 2 ports.

    The references are straights, bends and tapers.

    Args:
        component: to place the route into.
        port1: start port.
        port2: end port.
        cross_section: spec.
        layer: layer spec.
        bend: bend spec.
        straight: straight spec.
        start_straight_length: length of starting straight.
        end_straight_length: length of end straight.
        waypoints: optional list of points to pass through.
        steps: optional list of steps to pass through.
            Each step is a dict with keys: x (absolute), y (absolute), dx (relative), dy (relative).
            Use x/y to set an absolute coordinate and dx/dy to shift relative to the current position.
        port_type: port type to route.
        allow_width_mismatch: allow different port widths.
        radius: bend radius. If None, defaults to cross_section.radius.
        route_width: width of the route in um. If None, defaults to cross_section.width.
        auto_taper: add auto tapers.
        on_error: what to do on error. If error, raises an error. If None ignores the error.
        layer_transitions: dictionary of layer transitions to use for the routing when auto_taper=True.

    Example:
        ```python
        import gdsfactory as gf

        c = gf.Component()
        mmi1 = c << gf.components.mmi1x2()
        mmi2 = c << gf.components.mmi1x2()
        mmi2.move((40, 20))
        gf.routing.route_single(c, mmi1.ports["o2"], mmi2.ports["o1"], radius=5, cross_section="strip")
        c.plot()
        ```
    """
    if cross_section is None and (layer is None or route_width is None):
        raise ValueError(
            f"Either {cross_section=} or {layer=} and route_width must be provided"
        )

    c = component
    p1 = port1
    p2 = port2
    port_type = port_type or p1.port_type

    if cross_section is None:
        cross_section = gf.cross_section.cross_section(
            layer=cast("LayerSpec", layer),
            width=cast("float", route_width),
            port_names=("e1", "e2") if port_type == "electrical" else ("o1", "o2"),
            port_types=(port_type, port_type),
        )

    if route_width:
        xs = gf.get_cross_section(cross_section, width=route_width)
    else:
        xs = gf.get_cross_section(cross_section)
    width = route_width or xs.width

    radius = radius or xs.radius
    bend90 = gf.get_component(bend, cross_section=xs, radius=radius, width=width)
    if auto_taper:
        p1 = add_auto_tapers(component, [p1], xs, layer_transitions)[0]
        p2 = add_auto_tapers(component, [p2], xs, layer_transitions)[0]

    def straight_(width: float, length: float, **kwargs: Any) -> gf.Component:
        return gf.get_component(straight, length=length, **kwargs)

    def straight_dbu(width: int, length: int, **kwargs: Any) -> gf.Component:
        return straight_(c.kcl.to_um(width), c.kcl.to_um(length), **kwargs)

    if steps and waypoints:
        raise ValueError("Provide only one of steps or waypoints")

    waypoints_list = [] if waypoints is None else list(waypoints)

    if steps:
        x, y = p1.center
        for d in steps:
            if isinstance(d, dict):
                if not STEP_DIRECTIVES.issuperset(d):
                    raise ValueError(
                        f"Invalid step directives: {list(d.keys() - STEP_DIRECTIVES)}."
                        f"Valid directives are {list(STEP_DIRECTIVES)}"
                    )
                x = d.get("x", x) + d.get("dx", 0)
                y = d.get("y", y) + d.get("dy", 0)
            else:
                raise ValueError(
                    f"Invalid step {d!r}. Each step must be a dict with keys (x, y, dx, dy)."
                )
            waypoints_list.append((x, y))

    if waypoints_list and steps and len(waypoints_list) < 2:
        p = waypoints_list[-1]
        x, y = (p.x, p.y) if isinstance(p, kf.kdb.DPoint) else (p[0], p[1])
        x1, y1 = p1.center
        x2, y2 = p2.center
        orientation = p2.orientation
        if orientation is not None and int(orientation) in {0, 180}:
            yt = y1 + (y2 - y1) / 3
            ytt = y1 + 2 * (y2 - y1) / 3
            waypoints_list = [(x, yt), (x, ytt)]
        elif orientation is not None and int(orientation) in {90, 270}:
            xt = x1 + (x2 - x1) / 3
            xtt = x1 + 2 * (x2 - x1) / 3
            waypoints_list = [(xt, y), (xtt, y)]

    if waypoints_list:
        w: list[kf.kdb.Point] = []
        if not isinstance(waypoints_list[0], kf.kdb.DPoint):
            w.append(c.kcl.to_dbu(kf.kdb.DPoint(*p1.center)))
            for p in waypoints_list:
                if isinstance(p, tuple):
                    w.append(c.kcl.to_dbu(kf.kdb.DPoint(p[0], p[1])))
                else:
                    w.append(p.to_itype(c.kcl.dbu))
            w.append(c.kcl.to_dbu(kf.kdb.DPoint(*p2.center)))
        else:
            w = [
                p.to_itype(c.kcl.dbu)
                for p in cast("Sequence[gf.kdb.DPoint]", waypoints_list)
            ]

        try:
            return place_manhattan(
                component.to_itype(),
                p1=p1.to_itype(),
                p2=p2.to_itype(),
                straight_factory=straight_dbu,
                bend90_cell=bend90.to_itype(),
                pts=w,
                port_type=port_type,
                allow_width_mismatch=allow_width_mismatch,
                route_width=c.kcl.to_dbu(width),
            )
        except Exception as e:
            # error_route((ps, pe, router.start.pts, router.width))
            ps = p1
            pe = p2
            c = component
            pts = w
            db = kf.rdb.ReportDatabase("Route Placing Errors")
            cell = db.create_cell(
                (
                    c.kcl._future_cell_name or c.name
                    if c.name is not None and c.name.startswith("Unnamed_")
                    else c.name
                )
                or ""
            )
            cat = db.create_category(f"{ps.name} - {pe.name}")
            it = db.create_item(cell=cell, category=cat)
            it.add_value(
                f"Error while trying to place route from {ps.name} to {pe.name} at"
                f" points (dbu): {pts}"
            )
            it.add_value(f"Exception: {e}")
            if route_width is not None:
                path = kf.kdb.Path(pts, c.kcl.to_dbu(route_width))
            else:
                path = kf.kdb.Path(pts, c.kcl.to_dbu(ps.width))

            it.add_value(c.kcl.to_um(path.polygon()))
            if on_error == "error":
                c.name = (
                    c.kcl._future_cell_name or c.name
                    if c.name is not None and c.name.startswith("Unnamed_")
                    else c.name
                ) or ""
                c.show(lyrdb=db)
                raise kf.routing.generic.PlacerError(
                    f"Error while trying to place route from {ps.name} to {pe.name} at"
                    f" points (dbu): {pts}"
                ) from e
            layer_error = gf.CONF.layer_error_path
            layer_index = c.kcl.layer(*layer_error)
            c.shapes(layer_index).insert(path)
            return ManhattanRoute(
                backbone=pts,
                start_port=p1.to_itype(),
                end_port=p2.to_itype(),
            )

    else:
        return kf.routing.optical.route_bundle(
            c=component,
            start_ports=[p1],
            end_ports=[p2],
            straight_factory=straight_,
            bend90_cell=bend90,
            starts=start_straight_length,
            ends=end_straight_length,
            separation=0,
            place_port_type=port_type,
            allow_width_mismatch=allow_width_mismatch,
            route_width=route_width,
        )[0]

route_single_electrical

route_single_electrical(
    component: Component,
    port1: Port,
    port2: Port,
    start_straight_length: float | None = None,
    end_straight_length: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
) -> None

Places a route between two electrical ports.

Parameters:

Name Type Description Default
component Component

The cell to place the route in.

required
port1 Port

The first port.

required
port2 Port

The second port.

required
start_straight_length float | None

The length of the straight at the start of the route.

None
end_straight_length float | None

The length of the straight at the end of the route.

None
layer LayerSpec | None

The layer of the route.

None
width float | None

The width of the route.

None
cross_section CrossSectionSpec

The cross section of the route.

'metal_routing'
Source code in gdsfactory/routing/route_single.py
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def route_single_electrical(
    component: Component,
    port1: Port,
    port2: Port,
    start_straight_length: float | None = None,
    end_straight_length: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
) -> None:
    """Places a route between two electrical ports.

    Args:
        component: The cell to place the route in.
        port1: The first port.
        port2: The second port.
        start_straight_length: The length of the straight at the start of the route.
        end_straight_length: The length of the straight at the end of the route.
        layer: The layer of the route.
        width: The width of the route.
        cross_section: The cross section of the route.

    """
    xs = gf.get_cross_section(cross_section)
    layer = layer or xs.layer
    width = width or xs.width
    layer = gf.get_layer(layer)
    kf.routing.electrical.route_bundle(
        c=component,
        start_ports=[port1],
        end_ports=[port2],
        separation=0,
        route_width=width,
        place_layer=kf.kdb.LayerInfo(layer[0], layer[1])
        if isinstance(layer, tuple)
        else None,
        starts=start_straight_length,
        ends=end_straight_length,
    )

route_quad

Route for electrical based on phidl.routing.route_quad.

route_quad

route_quad(
    component: Component,
    port1: Port,
    port2: Port,
    width1: float | None = None,
    width2: float | None = None,
    layer: LayerSpec = "M1",
    manhattan_target_step: float | None = None,
) -> None

Routes a basic quadrilateral polygon directly between two ports.

Parameters:

Name Type Description Default
component Component

Component to add the route to.

required
port1 Port

Port to start route.

required
port2

Port objects to end route.

required
width1 float | None

Width of quadrilateral at ports. If None, uses port widths.

None
width2 float | None

Width of quadrilateral at ports. If None, uses port widths.

None
layer LayerSpec

Layer to put the route on.

'M1'
manhattan_target_step float | None

if not none, min step to manhattanize the polygon

None
Example
import gdsfactory as gf

c = gf.Component()
pad1 = c << gf.components.pad(size=(50, 50))
pad2 = c << gf.components.pad(size=(10, 10))
pad2.movex(100)
pad2.movey(50)
gf.routing.route_quad(
c,
pad1.ports["e2"],
pad2.ports["e4"],
width1=None,
width2=None,
)
c.plot()
Source code in gdsfactory/routing/route_quad.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def route_quad(
    component: gf.Component,
    port1: Port,
    port2: Port,
    width1: float | None = None,
    width2: float | None = None,
    layer: gf.typings.LayerSpec = "M1",
    manhattan_target_step: float | None = None,
) -> None:
    """Routes a basic quadrilateral polygon directly between two ports.

    Args:
        component: Component to add the route to.
        port1: Port to start route.
        port2 : Port objects to end route.
        width1: Width of quadrilateral at ports. If None, uses port widths.
        width2: Width of quadrilateral at ports. If None, uses port widths.
        layer: Layer to put the route on.
        manhattan_target_step: if not none, min step to manhattanize the polygon

    Example:
        ```python
        import gdsfactory as gf

        c = gf.Component()
        pad1 = c << gf.components.pad(size=(50, 50))
        pad2 = c << gf.components.pad(size=(10, 10))
        pad2.movex(100)
        pad2.movey(50)
        gf.routing.route_quad(
        c,
        pad1.ports["e2"],
        pad2.ports["e4"],
        width1=None,
        width2=None,
        )
        c.plot()
        ```
    """

    def get_port_edges(
        port: Port, width: float
    ) -> tuple[npt.NDArray[np.floating[Any]], npt.NDArray[np.floating[Any]]]:
        _, e1 = _get_rotated_basis(port.orientation)
        pt1 = port.center + e1 * width / 2
        pt2 = port.center - e1 * width / 2
        return pt1, pt2

    if width1 is None:
        width1 = port1.width
    if width2 is None:
        width2 = port2.width
    vertices = np.array(get_port_edges(port1, width1) + get_port_edges(port2, width2))
    center = np.mean(vertices, axis=0)
    displacements = vertices - center
    # sort vertices by angle from center of quadrilateral to make convex polygon
    angles = np.arctan2(displacements[:, 0], displacements[:, 1])
    sorted_vertices: npt.NDArray[np.floating[Any]] = np.array(
        [
            vert
            for _, vert in sorted(
                zip(angles, vertices, strict=False), key=lambda x: x[0]
            )
        ],
        dtype=np.float64,
    )

    if manhattan_target_step:
        component.add_polygon(
            sorted_vertices,
            layer=layer,
        )
    else:
        component.add_polygon(points=sorted_vertices, layer=layer)

route_sharp

based on phidl.routing.

path_C

path_C(
    port1: Port,
    port2: Port,
    length1: float = 100,
    left1: float = 100,
    length2: float = 100,
) -> Path

Return waypoint path between port1 and port2 in a C shape. Useful when ports are parallel and face away from each other.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
length1 float

Length of route segment coming out of port1. Should be larger than bend radius.

100
left1 float

Length of route segment that turns left (or right if negative) from port1. Should be larger than twice the bend radius.

100
length2 float

Length of route segment coming out of port2. Should be larger than bend radius.

100
Source code in gdsfactory/routing/route_sharp.py
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
def path_C(
    port1: typings.Port,
    port2: typings.Port,
    length1: float = 100,
    left1: float = 100,
    length2: float = 100,
) -> Path:
    """Return waypoint path between port1 and port2 in a C shape. Useful when ports are parallel and face away from each other.

    Args:
        port1: start port.
        port2: end port.
        length1: Length of route segment coming out of port1. Should be larger than bend radius.
        left1: Length of route segment that turns left (or right if negative) from port1. Should be larger than twice the bend radius.
        length2: Length of route segment coming out of port2. Should be larger than bend radius.

    """
    delta_orientation = np.round(
        np.abs(np.mod(port1.orientation - port2.orientation, 360)), 3
    )
    if delta_orientation not in (0, 180, 360):
        raise ValueError("path_C(): ports must be parallel.")
    e1, e_left = _get_rotated_basis(port1.orientation)
    e2, _ = _get_rotated_basis(port2.orientation)
    # assemble route points
    pt1 = port1.center
    pt2 = pt1 + length1 * e1  # outward from port1 by length1
    pt3 = pt2 + left1 * e_left  # leftward by left1
    pt6 = port2.center
    pt5 = pt6 + length2 * e2  # outward from port2 by length2
    delta_vec = pt5 - pt3
    pt4 = pt3 + np.dot(delta_vec, e1) * e1  # move orthogonally in e1 direction
    return Path(np.array([pt1, pt2, pt3, pt4, pt5, pt6]))

path_J

path_J(
    port1: Port,
    port2: Port,
    length1: float = 200,
    length2: float = 200,
) -> Path

Return waypoint path between port1 and port2 in a J shape.

Useful when orthogonal ports cannot be connected directly with an L shape.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
length1 float

Length of segment exiting port1. Should be larger than bend radius.

200
length2 float

Length of segment exiting port2. Should be larger than bend radius.

200
Source code in gdsfactory/routing/route_sharp.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def path_J(
    port1: typings.Port, port2: typings.Port, length1: float = 200, length2: float = 200
) -> Path:
    """Return waypoint path between port1 and port2 in a J shape.

    Useful when  orthogonal ports cannot be connected directly with an L shape.

    Args:
        port1: start port.
        port2: end port.
        length1: Length of segment exiting port1. Should be larger than bend radius.
        length2: Length of segment exiting port2. Should be larger than bend radius.
    """
    delta_orientation = np.round(
        np.abs(np.mod(port1.orientation - port2.orientation, 360)), 3
    )
    if delta_orientation not in (90, 270):
        raise ValueError("path_J(): ports must be orthogonal.")
    e1, _ = _get_rotated_basis(port1.orientation)
    e2, _ = _get_rotated_basis(port2.orientation)
    # assemble waypoints
    pt1 = port1.center
    pt2 = pt1 + length1 * e1  # outward from port1 by length1
    pt5 = port2.center
    pt4 = pt5 + length2 * e2  # outward from port2 by length2
    delta_vec = pt4 - pt2
    pt3 = pt2 + np.dot(delta_vec, e2) * e2  # move orthogonally in e2 direction
    return Path(np.array([pt1, pt2, pt3, pt4, pt5]))

path_L

path_L(port1: Port, port2: Port) -> Path

Return waypoint path between port1 and port2 in an L shape.

Useful when orthogonal ports can be directly connected with one turn.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
Source code in gdsfactory/routing/route_sharp.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
def path_L(port1: typings.Port, port2: typings.Port) -> Path:
    """Return waypoint path between port1 and port2 in an L shape.

    Useful when orthogonal ports can be directly connected with one turn.

    Args:
        port1: start port.
        port2: end port.

    """
    delta_orientation = np.round(
        np.abs(np.mod(port1.orientation - port2.orientation, 360)), 3
    )
    if delta_orientation not in (90, 270):
        raise ValueError("path_L(): ports must be orthogonal.")
    e1, _e2 = _get_rotated_basis(port1.orientation)

    # assemble waypoints
    pt1 = np.asarray(port1.center)
    pt3 = port2.center
    delta_vec = pt3 - pt1
    pt2 = pt1 + np.dot(delta_vec, e1) * e1
    return Path(np.array([pt1, pt2, pt3]))

path_U

path_U(
    port1: Port, port2: Port, length1: float = 200
) -> Path

Return waypoint path between port1 and port2 in a U shape.

Useful when ports face the same direction or toward each other.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
length1 float

Length of segment exiting port1. Should be larger than bend radius.

200
Source code in gdsfactory/routing/route_sharp.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def path_U(port1: typings.Port, port2: typings.Port, length1: float = 200) -> Path:
    """Return waypoint path between port1 and port2 in a U shape.

    Useful when ports face the same direction or toward each other.

    Args:
        port1: start port.
        port2: end port.
        length1: Length of segment exiting port1. Should be larger than bend radius.

    """
    delta_orientation = np.round(
        np.abs(np.mod(port1.orientation - port2.orientation, 360)), 3
    )
    if delta_orientation not in (0, 180, 360):
        raise ValueError("path_U(): ports must be parallel.")
    theta = np.radians(port1.orientation)
    e1 = np.array([np.cos(theta), np.sin(theta)])
    e2 = np.array([-1 * np.sin(theta), np.cos(theta)])
    # assemble waypoints
    pt1 = port1.center
    pt4 = port2.center
    pt2 = pt1 + length1 * e1  # outward by length1 distance
    delta_vec = pt4 - pt2
    pt3 = pt2 + np.dot(delta_vec, e2) * e2
    return Path(np.array([pt1, pt2, pt3, pt4]))

path_V

path_V(port1: Port, port2: Port) -> Path

Return waypoint path between port1 and port2 in a V shape.

Useful when ports point to a single connecting point.

Parameters:

Name Type Description Default
port1 Port

Start port.

required
port2 Port

End port.

required
Source code in gdsfactory/routing/route_sharp.py
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
def path_V(port1: typings.Port, port2: typings.Port) -> Path:
    """Return waypoint path between port1 and port2 in a V shape.

    Useful when ports point to a single connecting point.

    Args:
        port1: Start port.
        port2: End port.
    """
    # Get basis vectors in port directions
    e1, _ = _get_rotated_basis(port1.orientation)
    e2, _ = _get_rotated_basis(port2.orientation)

    # Assemble route points
    pt1 = port1.center
    pt3 = port2.center

    # Solve for intersection
    e = np.column_stack((e1, -1 * e2))
    distance = np.linalg.solve(e, np.asarray(pt3) - pt1)[0]
    pt2 = distance * e1 + pt1
    return Path(np.array([pt1, pt2, pt3]))

path_Z

path_Z(
    port1: Port,
    port2: Port,
    length1: float = 100,
    length2: float = 100,
) -> Path

Return waypoint path between port1 and port2 in a Z shape.

Ports can have any relative orientation.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
length1 float

Length of route segment coming out of port1.

100
length2 float

Length of route segment coming out of port2.

100
Source code in gdsfactory/routing/route_sharp.py
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def path_Z(
    port1: typings.Port, port2: typings.Port, length1: float = 100, length2: float = 100
) -> Path:
    """Return waypoint path between port1 and port2 in a Z shape.

    Ports can have any relative orientation.

    Args:
        port1: start port.
        port2: end port.
        length1: Length of route segment coming out of port1.
        length2: Length of route segment coming out of port2.

    """
    # get basis vectors in port directions
    e1, _ = _get_rotated_basis(port1.orientation)
    e2, _ = _get_rotated_basis(port2.orientation)
    # assemble route  points
    pt1 = port1.center
    pt2 = pt1 + length1 * e1  # outward from port1 by length1
    pt4 = port2.center
    pt3 = pt4 + length2 * e2  # outward from port2 by length2
    return Path(np.array([pt1, pt2, pt3, pt4]))

path_manhattan

path_manhattan(
    port1: Port, port2: Port, radius: float
) -> Path

Return waypoint path between port1 and port2 using manhattan routing.

Routing uses straight, L, U, J, or C waypoint path as needed. Ports must face orthogonal or parallel directions.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
radius float

Bend radius for 90 degree bend.

required
Source code in gdsfactory/routing/route_sharp.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
def path_manhattan(port1: typings.Port, port2: typings.Port, radius: float) -> Path:
    """Return waypoint path between port1 and port2 using manhattan routing.

    Routing uses straight, L, U, J, or C waypoint path as needed.
    Ports must face orthogonal or parallel directions.

    Args:
        port1: start port.
        port2: end port.
        radius: Bend radius for 90 degree bend.
    """
    radius += 0.1
    e1, e2 = _get_rotated_basis(port1.orientation)
    displacement = np.asarray(port2.center) - np.asarray(port1.center)
    xrel = np.round(
        np.dot(displacement, e1), 3
    )  # port2 position, forward(+)/backward(-) from port 1
    yrel = np.round(
        np.dot(displacement, e2), 3
    )  # port2 position, left(+)/right(-) from port1
    orel = np.round(
        np.abs(np.mod(port2.orientation - port1.orientation, 360)), 3
    )  # relative orientation
    if orel not in (0, 90, 180, 270, 360):
        raise ValueError(
            "path_manhattan(): ports must face parallel or orthogonal directions."
        )
    if orel in (90, 270):
        # Orthogonal case
        if (
            (orel == 90 and yrel < -1 * radius) or (orel == 270 and yrel > radius)
        ) and xrel > radius:
            pts = path_L(port1, port2)
        else:
            # Adjust length1 and length2 to ensure intermediate segments fit bend radius
            direction = -1 if (orel == 270) else 1
            length2 = (
                2 * radius - direction * yrel
                if (np.abs(radius + direction * yrel) < 2 * radius)
                else radius
            )
            length1 = (
                2 * radius + xrel if (np.abs(radius - xrel) < 2 * radius) else radius
            )
            pts = path_J(port1, port2, length1=float(length1), length2=float(length2))
    elif orel == 180 and yrel == 0 and xrel > 0:
        pts = path_straight(port1, port2)
    elif (orel == 180 and xrel <= 2 * radius) or (np.abs(yrel) < 2 * radius):
        # Adjust length1 and left1 to ensure intermediate segments fit bend radius
        left1 = np.abs(yrel) + 2 * radius if (np.abs(yrel) < 4 * radius) else 2 * radius
        y_direction = -1 if (yrel < 0) else 1
        left1 = y_direction * left1
        length2 = radius
        x_direction = -1 if (orel == 180) else 1
        segmentx_length = np.abs(xrel + x_direction * length2 - radius)
        length1 = (
            xrel + x_direction * length2 + 2 * radius
            if segmentx_length < 2 * radius
            else radius
        )

        pts = path_C(
            port1,
            port2,
            length1=float(length1),
            length2=float(length2),
            left1=float(left1),
        )
    else:
        # Adjust length1 to ensure segment comes out of port2
        length1 = radius + xrel if (orel == 0 and xrel > 0) else radius
        pts = path_U(port1, port2, length1=float(length1))
    return pts

path_straight

path_straight(port1: Port, port2: Port) -> Path

Return waypoint path between port1 and port2 in a straight line.

Useful when ports point directly at each other.

Parameters:

Name Type Description Default
port1 Port

start port.

required
port2 Port

end port.

required
Source code in gdsfactory/routing/route_sharp.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def path_straight(port1: typings.Port, port2: typings.Port) -> Path:
    """Return waypoint path between port1 and port2 in a straight line.

    Useful when ports point directly at each other.

    Args:
        port1: start port.
        port2: end port.

    """
    delta_orientation = np.round(
        np.abs(np.mod(port1.orientation - port2.orientation, 360)), 3
    )
    e1, e2 = _get_rotated_basis(port1.orientation)
    displacement = np.asarray(port2.center) - np.asarray(port1.center)
    xrel = np.round(
        np.dot(displacement, e1), 3
    )  # relative position of port 2, forward/backward
    yrel = np.round(
        np.dot(displacement, e2), 3
    )  # relative position of port 2, left/right
    if (delta_orientation not in (0, 180, 360)) or (yrel != 0) or (xrel <= 0):
        raise ValueError("path_straight(): ports must point directly at each other.")
    return Path(np.array([port1.center, port2.center]))

route_sharp

route_sharp(
    component: Component,
    port1: Port,
    port2: Port,
    width: float | None = None,
    path_type: str = "manhattan",
    manual_path: Path | None = None,
    layer: LayerSpec | None = None,
    cross_section: CrossSectionSpec | None = None,
    port_names: tuple[str, str] = ("o1", "o2"),
    **kwargs: Any
) -> None

Returns Component route between ports.

Parameters:

Name Type Description Default
component Component

Component to add the route to.

required
port1 Port

start port.

required
port2 Port

end port.

required
width float | None

None, int, float, array-like[2], or CrossSection. If None, the route linearly tapers between the widths the ports If set to a single number (e.g. width=1.7): makes a fixed-width route If set to a 2-element array (e.g. width=[1.8,2.5]): makes a route whose width varies linearly from width[0] to width[1] If set to a CrossSection: uses the CrossSection parameters for the route.

None
path_type

{'manhattan', 'L', 'U', 'J', 'C', 'V', 'Z', 'straight', 'manual'}.

required
manual_path Path | None

array-like[N][2] or Path Waypoint for manual route.

None
layer LayerSpec | None

Layer to put route on.

None
cross_section CrossSectionSpec | None

CrossSection to use for the route.

None
port_names tuple[str, str]

Tuple of port names for the start and end of the route.

('o1', 'o2')
kwargs Any

Keyword arguments passed to the waypoint path function.

{}

Method of waypoint path creation. Should be one of:

  • manhattan: automatic manhattan routing (see path_manhattan() ).
  • L: L-shaped path for orthogonal ports that can be directly connected.
  • U: U-shaped path for parallel or facing ports.
  • J: J-shaped path for orthogonal ports that cannot be directly connected.
  • C: C-shaped path for ports that face away from each other.
  • Z: Z-shaped path with three segments for ports at any angles.
  • V: V-shaped path with two segments for ports at any angles.
  • straight: straight path for ports that face each other.
  • manual: use an explicit waypoint path provided in manual_path.
Example
import gdsfactory as gf

c = gf.Component()
c1 = c << gf.components.pad(port_orientation=None)
c2 = c << gf.components.pad(port_orientation=None)

c2.movex(400)
c2.movey(-200)

gf.routing.route_sharp(c, c1.ports["e4"], c2.ports["e1"], path_type="L")
c.plot()
Source code in gdsfactory/routing/route_sharp.py
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
def route_sharp(
    component: Component,
    port1: typings.Port,
    port2: typings.Port,
    width: float | None = None,
    path_type: str = "manhattan",
    manual_path: Path | None = None,
    layer: LayerSpec | None = None,
    cross_section: CrossSectionSpec | None = None,
    port_names: tuple[str, str] = ("o1", "o2"),
    **kwargs: Any,
) -> None:
    """Returns Component route between ports.

    Args:
        component: Component to add the route to.
        port1: start port.
        port2: end port.
        width: None, int, float, array-like[2], or CrossSection. \
                If None, the route linearly tapers between the widths the ports \
                If set to a single number (e.g. `width=1.7`): makes a fixed-width route \
                If set to a 2-element array (e.g. `width=[1.8,2.5]`): makes a route \
                whose width varies linearly from width[0] to width[1] \
                If set to a CrossSection: uses the CrossSection parameters for the route.
        path_type : {'manhattan', 'L', 'U', 'J', 'C', 'V', 'Z', 'straight', 'manual'}.
        manual_path: array-like[N][2] or Path Waypoint for  manual route.
        layer: Layer to put route on.
        cross_section: CrossSection to use for the route.
        port_names: Tuple of port names for the start and end of the route.
        kwargs: Keyword arguments passed to the waypoint path function.

    Method of waypoint path creation. Should be one of:

     - manhattan: automatic manhattan routing (see path_manhattan() ).
     - L: L-shaped path for orthogonal ports that can be directly connected.
     - U: U-shaped path for parallel or facing ports.
     - J: J-shaped path for orthogonal ports that cannot be directly connected.
     - C: C-shaped path for ports that face away from each other.
     - Z: Z-shaped path with three segments for ports at any angles.
     - V: V-shaped path with two segments for ports at any angles.
     - straight: straight path for ports that face each other.
     - manual: use an explicit waypoint path provided in manual_path.

    Example:
        ```python
        import gdsfactory as gf

        c = gf.Component()
        c1 = c << gf.components.pad(port_orientation=None)
        c2 = c << gf.components.pad(port_orientation=None)

        c2.movex(400)
        c2.movey(-200)

        gf.routing.route_sharp(c, c1.ports["e4"], c2.ports["e1"], path_type="L")
        c.plot()
        ```
    """
    if path_type == "C":
        p = path_C(port1, port2, **kwargs)
    elif path_type == "J":
        p = path_J(port1, port2, **kwargs)
    elif path_type == "L":
        p = path_L(port1, port2)
    elif path_type == "U":
        p = path_U(port1, port2, **kwargs)
    elif path_type == "V":
        p = path_V(port1, port2)
    elif path_type == "Z":
        p = path_Z(port1, port2, **kwargs)
    elif path_type == "manhattan":
        radius = max(port1.width, port2.width)
        p = path_manhattan(port1, port2, radius=radius)
    elif path_type == "manual":
        p = manual_path if isinstance(manual_path, Path) else Path(manual_path)
    elif path_type == "straight":
        p = path_straight(port1, port2)
    else:
        raise ValueError(
            f"route_sharp() received invalid path_type {path_type} not in "
            "{'manhattan', 'L', 'U', 'J', 'C', 'V', 'Z', 'straight', 'manual'}"
        )

    if cross_section:
        cross_section = gf.get_cross_section(cross_section)
        d = p.extrude(cross_section=cross_section)
    elif width is None:
        layer = layer or port1.layer
        s1 = Section(
            width=port1.width,
            port_names=port_names,
            layer=layer,
        )
        s2 = Section(
            width=port2.width,
            port_names=port_names,
            layer=layer,
        )
        x1 = CrossSection(sections=(s1,))
        x2 = CrossSection(sections=(s2,))
        trans = transition(cross_section1=x1, cross_section2=x2, width_type="linear")
        d = p.extrude_transition(transition=trans)
    else:
        if layer is None:
            raise ValueError("layer is required for width")
        d = p.extrude(width=width, layer=layer)

    component << d

route_bundle

When you need to route groups of ports together without them crossing each other you can use a bundle/river/bus router. route_bundle is the generic river bundle bus routing function that will call different functions depending on the port orientation. Get bundle acts as a high level entry point. Based on the angle configurations of the banks of ports, it decides which sub-routine to call:

route_bundle

Routes bundles of ports (river routing).

get bundle is the generic river routing function route_bundle calls different function depending on the port orientation.

  • route_bundle_same_axis: ports facing each other with arbitrary pitch on each side
  • route_bundle_corner: 90Deg / 270Deg between ports with arbitrary pitch
  • route_bundle_udirect: ports with direct U-turns
  • route_bundle_uindirect: ports with indirect U-turns

get_min_spacing

get_min_spacing(
    ports1: Ports,
    ports2: Ports,
    separation: float = 5.0,
    radius: float = 5.0,
    sort_ports: bool = True,
) -> float

Returns the minimum amount of spacing in um required to create a fanout.

Parameters:

Name Type Description Default
ports1 Ports

first list of ports.

required
ports2 Ports

second list of ports.

required
separation float

minimum separation between two straights in um.

5.0
radius float

bend radius in um.

5.0
sort_ports bool

sort the ports according to the axis.

True
Source code in gdsfactory/routing/route_bundle.py
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def get_min_spacing(
    ports1: Ports,
    ports2: Ports,
    separation: float = 5.0,
    radius: float = 5.0,
    sort_ports: bool = True,
) -> float:
    """Returns the minimum amount of spacing in um required to create a fanout.

    Args:
        ports1: first list of ports.
        ports2: second list of ports.
        separation: minimum separation between two straights in um.
        radius: bend radius in um.
        sort_ports: sort the ports according to the axis.

    """
    if not ports1 or not ports2:
        raise ValueError("ports1 and ports2 must be non-empty")
    if len(ports1) != len(ports2):
        raise ValueError(f"ports1={len(ports1)} and ports2={len(ports2)} must be equal")

    axis = "X" if ports1[0].orientation in [0, 180] else "Y"
    j = 0
    min_j = 0
    max_j = 0
    if sort_ports:
        if axis in {"X", "x"}:
            ports1 = sorted(ports1, key=get_port_y)
            ports2 = sorted(ports2, key=get_port_y)
        else:
            ports1 = sorted(ports1, key=get_port_x)
            ports2 = sorted(ports2, key=get_port_x)

    for port1, port2 in zip(ports1, ports2, strict=False):
        if axis in {"X", "x"}:
            x1 = get_port_y(port1)
            x2 = get_port_y(port2)
        else:
            x1 = get_port_x(port1)
            x2 = get_port_x(port2)
        if x2 >= x1:
            j += 1
        else:
            j -= 1
        if j < min_j:
            min_j = j
        if j > max_j:
            max_j = j
    return (max_j - min_j) * separation + 2 * radius + 1.0

route_bundle

route_bundle(
    component: Component,
    ports1: Port | Ports | list[Pin] | None = None,
    ports2: Port | Ports | list[Pin] | None = None,
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    separation: float = 3.0,
    bend: ComponentSpec = "bend_euler",
    sort_ports: bool = False,
    start_straight_length: float = 0,
    end_straight_length: float = 0,
    min_straight_taper: float = 100,
    taper: ComponentSpec | None = None,
    port_type: str | None = None,
    collision_check_layers: LayerSpecs | None = None,
    on_collision: (
        Literal["error", "show_error", "warning"] | None
    ) = None,
    on_placer_error: (
        Literal["error", "show_error", "warning"] | None
    ) = None,
    bboxes: Sequence[DBox] | None = None,
    allow_width_mismatch: bool | None = None,
    allow_layer_mismatch: bool | None = None,
    allow_type_mismatch: bool | None = None,
    radius: float | None = None,
    route_width: float | None = None,
    straight: ComponentSpec = "straight",
    sbend: ComponentSpec | None = None,
    auto_taper: bool = True,
    auto_taper_taper: ComponentSpec | None = None,
    waypoints: Coordinates | Sequence[DPoint] | None = None,
    steps: Sequence[Step] | None = None,
    start_angles: float | list[float] | None = None,
    end_angles: float | list[float] | None = None,
    router: Literal["optical", "electrical"] | None = None,
    layer_transitions: LayerTransitions | None = None,
    show_waypoints: bool = False,
    layer_marker: LayerSpec | None = None,
    raise_on_error: bool | None = None,
    path_length_matching_config: (
        PathLengthConfig | None
    ) = None,
    constraints: Sequence[Constraint] | None = None,
    layer_label: LayerSpec | None = None,
    port1: Port | None = None,
    port2: Port | None = None,
    name: str | None = None,
) -> list[ManhattanRoute]

Places a bundle of routes to connect two groups of ports.

Routes connect a bundle of ports with a river router. Chooses the correct routing function depending on port angles.

Can also be used with single ports instead of lists, replacing route_bundle.

Parameters:

Name Type Description Default
component Component

component to add the routes to.

required
ports1 Port | Ports | list[Pin] | None

starting port or list of starting ports.

None
ports2 Port | Ports | list[Pin] | None

end port or list of end ports.

None
cross_section CrossSectionSpec | None

CrossSection or function that returns a cross_section.

None
layer LayerSpec | None

layer to use for the route.

None
separation float

bundle separation (center to center). Defaults to cross_section.width + cross_section.gap

3.0
bend ComponentSpec

function for the bend. Defaults to euler.

'bend_euler'
sort_ports bool

sort port coordinates.

False
start_straight_length float

straight length at the beginning of the route. If None, uses default value for the routing CrossSection.

0
end_straight_length float

end length at the beginning of the route. If None, uses default value for the routing CrossSection.

0
min_straight_taper float

minimum length for tapering the straight sections.

100
taper ComponentSpec | None

function for tapering long straight waveguides beyond min_straight_taper. Defaults to None.

None
port_type str | None

type of port to place. Defaults to optical.

None
collision_check_layers LayerSpecs | None

list of layers to check for collisions.

None
on_collision Literal['error', 'show_error', 'warning'] | None

action to take on collision. Defaults to None (ignore).

None
on_placer_error Literal['error', 'show_error', 'warning'] | None

action to take on placer error. Defaults to None (ignore).

None
bboxes Sequence[DBox] | None

list of bounding boxes to avoid collisions.

None
allow_width_mismatch bool | None

allow different port widths.

None
allow_layer_mismatch bool | None

allow different port layers to connect.

None
allow_type_mismatch bool | None

allow different port types to connect.

None
radius float | None

bend radius. If None, defaults to cross_section.radius.

None
route_width float | None

width of the route. If None, defaults to cross_section.width.

None
straight ComponentSpec

function for the straight. Defaults to straight.

'straight'
sbend ComponentSpec | None

function for the s-bend. If None, uses the same function as bend.

None
auto_taper bool

if True, auto-tapers ports to the cross-section of the route.

True
auto_taper_taper ComponentSpec | None

taper to use for auto-tapering. If None, uses the default taper for the cross-section.

None
waypoints Coordinates | Sequence[DPoint] | None

list of waypoints to add to the route.

None
steps Sequence[Step] | None

list of steps to add to the route. Each step is a dict with keys: x (absolute), y (absolute), dx (relative), dy (relative). Use x/y to set an absolute coordinate and dx/dy to shift relative to the current position.

None
start_angles float | list[float] | None

list of start angles for the routes. Only used for electrical ports.

None
end_angles float | list[float] | None

list of end angles for the routes. Only used for electrical ports.

None
router Literal['optical', 'electrical'] | None

Set the type of router to use, either the optical one or the electrical one. If None, the router is optical unless the port_type is "electrical".

None
layer_transitions LayerTransitions | None

dictionary of layer transitions to use for the routing when auto_taper=True.

None
show_waypoints bool

if True, places markers at each waypoint using CONF.layer_marker.

False
layer_marker LayerSpec | None

layer to place markers on the route. Overrides CONF.layer_marker when show_waypoints=True.

None
raise_on_error bool | None

if True, raises an exception on routing error instead of adding error markers.

None
path_length_matching_config PathLengthConfig | None

path length matching configuration. Convenience shortcut that is converted into a single kf.schematic.PathLengthMatch constraint. Mutually exclusive with constraints.

None
constraints Sequence[Constraint] | None

list of kfactory routing constraints (kf.schematic.Constraint instances, e.g. kf.schematic.PathLengthMatch) passed through to kfactory. Mutually exclusive with path_length_matching_config.

None
layer_label LayerSpec | None

layer to place length labels on the route.

None
port1 Port | None

single start port (alternative to ports1 for single-port routing).

None
port2 Port | None

single end port (alternative to ports2 for single-port routing).

None
name str | None

Name for the route. This is not important yet, but once constraints are implemented, the constraint, depending on the constraint class, might check against names to make enforcement or checking decisions.

None
Example
import gdsfactory as gf

dy = 200.0
xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

pitch = 10.0
N = len(xs1)
xs2 = [-20 + i * pitch for i in range(N // 2)]
xs2 += [400 + i * pitch for i in range(N // 2)]

a1 = 90
a2 = a1 + 180

ports1 = [gf.Port(name=f"top_{i}", center=(xs1[i], +0), width=0.5, orientation=a1, layer=(1, 0)) for i in range(N)]
ports2 = [gf.Port(name=f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=(1, 0)) for i in range(N)]

c = gf.Component()
gf.routing.route_bundle(component=c, ports1=ports1, ports2=ports2, cross_section='strip', separation=5)
c.plot()
Source code in gdsfactory/routing/route_bundle.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
def route_bundle(
    component: gf.Component,
    ports1: Port | Ports | list[Pin] | None = None,
    ports2: Port | Ports | list[Pin] | None = None,
    cross_section: CrossSectionSpec | None = None,
    layer: LayerSpec | None = None,
    separation: float = 3.0,
    bend: ComponentSpec = "bend_euler",
    sort_ports: bool = False,
    start_straight_length: float = 0,
    end_straight_length: float = 0,
    min_straight_taper: float = 100,
    taper: ComponentSpec | None = None,
    port_type: str | None = None,
    collision_check_layers: LayerSpecs | None = None,
    on_collision: Literal["error", "show_error", "warning"] | None = None,
    on_placer_error: Literal["error", "show_error", "warning"] | None = None,
    bboxes: Sequence[kf.kdb.DBox] | None = None,
    allow_width_mismatch: bool | None = None,
    allow_layer_mismatch: bool | None = None,
    allow_type_mismatch: bool | None = None,
    radius: float | None = None,
    route_width: float | None = None,
    straight: ComponentSpec = "straight",
    sbend: ComponentSpec | None = None,
    auto_taper: bool = True,
    auto_taper_taper: ComponentSpec | None = None,
    waypoints: Coordinates | Sequence[gf.kdb.DPoint] | None = None,
    steps: Sequence[Step] | None = None,
    start_angles: float | list[float] | None = None,
    end_angles: float | list[float] | None = None,
    router: Literal["optical", "electrical"] | None = None,
    layer_transitions: LayerTransitions | None = None,
    show_waypoints: bool = False,
    layer_marker: LayerSpec | None = None,
    raise_on_error: bool | None = None,
    path_length_matching_config: PathLengthConfig | None = None,
    constraints: Sequence[Constraint] | None = None,
    layer_label: LayerSpec | None = None,
    port1: Port | None = None,
    port2: Port | None = None,
    name: str | None = None,
) -> list[ManhattanRoute]:
    """Places a bundle of routes to connect two groups of ports.

    Routes connect a bundle of ports with a river router.
    Chooses the correct routing function depending on port angles.

    Can also be used with single ports instead of lists, replacing route_bundle.

    Args:
        component: component to add the routes to.
        ports1: starting port or list of starting ports.
        ports2: end port or list of end ports.
        cross_section: CrossSection or function that returns a cross_section.
        layer: layer to use for the route.
        separation: bundle separation (center to center). Defaults to cross_section.width + cross_section.gap
        bend: function for the bend. Defaults to euler.
        sort_ports: sort port coordinates.
        start_straight_length: straight length at the beginning of the route. If None, uses default value for the routing CrossSection.
        end_straight_length: end length at the beginning of the route. If None, uses default value for the routing CrossSection.
        min_straight_taper: minimum length for tapering the straight sections.
        taper: function for tapering long straight waveguides beyond min_straight_taper. Defaults to None.
        port_type: type of port to place. Defaults to optical.
        collision_check_layers: list of layers to check for collisions.
        on_collision: action to take on collision. Defaults to None (ignore).
        on_placer_error: action to take on placer error. Defaults to None (ignore).
        bboxes: list of bounding boxes to avoid collisions.
        allow_width_mismatch: allow different port widths.
        allow_layer_mismatch: allow different port layers to connect.
        allow_type_mismatch: allow different port types to connect.
        radius: bend radius. If None, defaults to cross_section.radius.
        route_width: width of the route. If None, defaults to cross_section.width.
        straight: function for the straight. Defaults to straight.
        sbend: function for the s-bend. If None, uses the same function as bend.
        auto_taper: if True, auto-tapers ports to the cross-section of the route.
        auto_taper_taper: taper to use for auto-tapering. If None, uses the default taper for the cross-section.
        waypoints: list of waypoints to add to the route.
        steps: list of steps to add to the route.
            Each step is a dict with keys: x (absolute), y (absolute), dx (relative), dy (relative).
            Use x/y to set an absolute coordinate and dx/dy to shift relative to the current position.
        start_angles: list of start angles for the routes. Only used for electrical ports.
        end_angles: list of end angles for the routes. Only used for electrical ports.
        router: Set the type of router to use, either the optical one or the electrical one.
            If None, the router is optical unless the port_type is "electrical".
        layer_transitions: dictionary of layer transitions to use for the routing when auto_taper=True.
        show_waypoints: if True, places markers at each waypoint using CONF.layer_marker.
        layer_marker: layer to place markers on the route. Overrides CONF.layer_marker when show_waypoints=True.
        raise_on_error: if True, raises an exception on routing error instead of adding error markers.
        path_length_matching_config: path length matching configuration. Convenience \
            shortcut that is converted into a single ``kf.schematic.PathLengthMatch`` \
            constraint. Mutually exclusive with ``constraints``.
        constraints: list of kfactory routing constraints (``kf.schematic.Constraint`` \
            instances, e.g. ``kf.schematic.PathLengthMatch``) passed through to \
            kfactory. Mutually exclusive with ``path_length_matching_config``.
        layer_label: layer to place length labels on the route.
        port1: single start port (alternative to ports1 for single-port routing).
        port2: single end port (alternative to ports2 for single-port routing).
        name: Name for the route. This is not important yet, but once constraints are implemented, the constraint, depending
            on the constraint class, might check against names to make enforcement or checking decisions.

    Example:
        ```python
        import gdsfactory as gf

        dy = 200.0
        xs1 = [-500, -300, -100, -90, -80, -55, -35, 200, 210, 240, 500, 650]

        pitch = 10.0
        N = len(xs1)
        xs2 = [-20 + i * pitch for i in range(N // 2)]
        xs2 += [400 + i * pitch for i in range(N // 2)]

        a1 = 90
        a2 = a1 + 180

        ports1 = [gf.Port(name=f"top_{i}", center=(xs1[i], +0), width=0.5, orientation=a1, layer=(1, 0)) for i in range(N)]
        ports2 = [gf.Port(name=f"bot_{i}", center=(xs2[i], dy), width=0.5, orientation=a2, layer=(1, 0)) for i in range(N)]

        c = gf.Component()
        gf.routing.route_bundle(component=c, ports1=ports1, ports2=ports2, cross_section='strip', separation=5)
        c.plot()
        ```
    """
    name = name or "unnamed_route_bundle"
    on_collision = on_collision or CONF.on_collision
    on_placer_error = on_placer_error or CONF.on_placer_error

    if raise_on_error is None:
        raise_on_error = CONF.raise_on_error

    # Support deprecated port1/port2 keyword arguments
    if port1 is not None:
        if ports1 is not None:
            raise ValueError("Cannot specify both ports1 and port1")
        ports1 = port1
    if port2 is not None:
        if ports2 is not None:
            raise ValueError("Cannot specify both ports2 and port2")
        ports2 = port2

    if ports1 is None or ports2 is None:
        raise ValueError("ports1 and ports2 are required")

    # Wrap single ports in lists
    if isinstance(ports1, kf.DPort):
        ports1 = [ports1]
    if isinstance(ports2, kf.DPort):
        ports2 = [ports2]

    # Ensure ports are lists (they may be reversed, generators, etc.)
    port_list1 = list(ports1)
    port_list2 = list(ports2)

    # Resolve Pin inputs to Ports
    if port_list1 and isinstance(port_list1[0], kf.DPin):
        if not (port_list2 and isinstance(port_list2[0], kf.DPin)):
            raise TypeError(
                "Cannot mix Pins and Ports. "
                "If ports1 contains Pins, ports2 must also contain Pins."
            )
        port_list1, port_list2 = resolve_pins(  # type: ignore[assignment]
            cast(list[Pin], port_list1), cast(list[Pin], port_list2)
        )
    elif port_list2 and isinstance(port_list2[0], kf.DPin):
        raise TypeError(
            "Cannot mix Pins and Ports. "
            "If ports2 contains Pins, ports1 must also contain Pins."
        )

    if show_waypoints and layer_marker is None:
        layer_marker = gf.CONF.layer_marker

    component = gf.Component(base=component.base)  # type: ignore[call-overload]
    ports1_resolved = [gf.Port(base=p1.base) for p1 in cast(list[kf.DPort], port_list1)]
    ports2_resolved = [gf.Port(base=p2.base) for p2 in cast(list[kf.DPort], port_list2)]

    if router:
        warnings.warn(
            f"The argument {router=} is ignored and will be removed in a future release.",
            stacklevel=2,
        )

    if cross_section is None:
        if layer is None or route_width is None:
            raise ValueError(
                f"Either {cross_section=} or {layer=} and {route_width=} must be provided"
            )
    elif layer is not None:
        raise ValueError(
            f"Cannot have both {layer=} and {cross_section=} provided. Choose one."
        )

    c = component
    ports1_ = ports1_resolved
    ports2_ = ports2_resolved
    port_type = port_type or ports1_[0].port_type

    if cross_section is None:
        cross_section = partial(
            gf.cross_section.cross_section,
            layer=cast("LayerSpec", layer),
            width=cast("float", route_width),
            port_names=("e1", "e2") if port_type == "electrical" else ("o1", "o2"),
            port_types=(port_type, port_type),
        )

    if len(ports1_) != len(ports2_):
        raise ValueError(
            f"ports1={len(ports1_)} and ports2={len(ports2_)} must be equal"
        )
    if route_width:
        xs = gf.get_cross_section(cross_section, width=route_width)
    else:
        xs = gf.get_cross_section(cross_section)
    width = route_width or xs.width

    radius = radius or xs.radius
    taper_cell = gf.get_component(taper) if taper else None

    if collision_check_layers:
        collision_check_layer_enums = [
            gf.get_layer(layer) for layer in collision_check_layers
        ]
    else:
        collision_check_layer_enums = None

    bboxes = list(bboxes or [])

    if auto_taper and auto_taper_taper:
        warn(
            "Use of `auto_taper_taper` is deprecated. Please use `layer_transitions` instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        taper_ = gf.get_component(auto_taper_taper)
        taper_o1 = taper_.ports[0].name
        taper_o2 = taper_.ports[1].name
        ports1_new: list[gf.Port] = []
        ports2_new: list[gf.Port] = []

        for p1, p2 in zip(ports1_, ports2_, strict=False):
            t1 = c << taper_
            t2 = c << taper_
            t1.connect(taper_o1, p1)
            t2.connect(taper_o1, p2)

            ports1_new.append(t1.ports[taper_o2])
            ports2_new.append(t2.ports[taper_o2])

        ports1_ = ports1_new
        ports2_ = ports2_new

        bbox1 = gf.kdb.DBox()
        bbox2 = gf.kdb.DBox()

        for port in ports1_:
            bbox1 += port.dcplx_trans.disp.to_p()

        for port in ports2_:
            bbox2 += port.dcplx_trans.disp.to_p()

        bboxes.append(bbox1)
        bboxes.append(bbox2)

    elif auto_taper:
        bbox1 = gf.kdb.DBox()
        bbox2 = gf.kdb.DBox()
        for port in ports1_:
            bbox1 += port.dcplx_trans.disp.to_p()

        for port in ports2_:
            bbox2 += port.dcplx_trans.disp.to_p()

        ports1_ = add_auto_tapers(
            component, ports1_, cross_section=xs, layer_transitions=layer_transitions
        )
        ports2_ = add_auto_tapers(
            component, ports2_, cross_section=xs, layer_transitions=layer_transitions
        )

        for port in ports1_:
            bbox1 += port.dcplx_trans.disp.to_p()

        for port in ports2_:
            bbox2 += port.dcplx_trans.disp.to_p()

        bboxes.append(bbox1)
        bboxes.append(bbox2)
        # component.shapes(component.kcl.layer(1,0)).insert(bbox)

    if steps and waypoints:
        raise ValueError("Provide only one of steps or waypoints")

    if steps:
        waypoints = []
        x, y = ports1_[0].center
        for d in steps:
            if isinstance(d, dict):
                if not STEP_DIRECTIVES.issuperset(d):
                    raise ValueError(
                        f"Invalid step directives: {list(d.keys() - STEP_DIRECTIVES)}."
                        f"Valid directives are {list(STEP_DIRECTIVES)}"
                    )
                x = d.get("x", x) + d.get("dx", 0)
                y = d.get("y", y) + d.get("dy", 0)
            else:
                raise ValueError(
                    f"Invalid step {d!r}. Each step must be a dict with keys (x, y, dx, dy)."
                )
            waypoints += [(x, y)]  # type: ignore[arg-type]
            if layer_marker:
                marker = component << gf.components.rectangle(
                    size=(10, 10), layer=layer_marker, centered=True
                )
                marker.center = (x, y)

    if waypoints is not None and steps and len(waypoints) < 2:
        x, y = waypoints[-1][0], waypoints[-1][1]  # type: ignore[index]
        x1, y1 = ports1_[0].center
        port2 = ports2_[0]
        x2, y2 = port2.center
        orientation = port2.orientation
        if orientation is not None and int(orientation) in {0, 180}:
            yt = y1 + (y2 - y1) / 3
            ytt = y1 + 2 * (y2 - y1) / 3
            waypoints = [(x, yt), (x, ytt)]
        elif orientation is not None and int(orientation) in {90, 270}:
            xt = x1 + (x2 - x1) / 3
            xtt = x1 + 2 * (x2 - x1) / 3
            waypoints = [(xt, y), (xtt, y)]

    waypoints_: list[kf.kdb.DPoint] | None
    if waypoints is None:
        waypoints_ = None
    elif len(waypoints) == 0:
        waypoints_ = []
    elif not isinstance(waypoints[0], kf.kdb.DPoint):
        waypoints_ = [
            kf.kdb.DPoint(p[0], p[1])  # type: ignore[index]
            for p in waypoints
        ]
    else:
        waypoints_ = [cast("kf.kdb.DPoint", p) for p in waypoints]

    if layer_marker and waypoints_ is not None:
        for p in waypoints_:
            marker = component << gf.components.rectangle(
                size=(10, 10), layer=layer_marker, centered=True
            )
            marker.center = (p.x, p.y)

    if waypoints_ is not None and len(waypoints_) >= 2:
        waypoints_ = _ensure_manhattan_waypoints(waypoints_, start_port=ports1_[0])

    bend90 = (
        bend
        if isinstance(bend, gf.Component)
        else gf.get_component(
            bend, cross_section=cross_section, radius=radius, width=width
        )
    )

    def straight_um(width: float, length: float) -> gf.Component:
        return gf.get_component(
            straight, length=length, cross_section=cross_section, width=width
        )

    if sbend:

        def _sbend(
            c: gf.kf.ProtoTKCell[Any], offset: float, length: float, width: float
        ) -> gf.kf.DInstanceGroup:
            sb = gf.get_component(
                sbend,
                cross_section=cross_section,
                width=width,
                size=(length, offset),
            )
            sb_ref = component << sb
            return gf.kf.DInstanceGroup(insts=[sb_ref], ports=list(sb_ref.ports))

    if path_length_matching_config is not None and constraints is not None:
        raise ValueError(
            "path_length_matching_config and constraints are mutually exclusive. "
            "Pass a kf.schematic.PathLengthMatch constraint via constraints instead."
        )

    if path_length_matching_config is not None:
        route_constraints: list[Constraint] = [
            kf.schematic.PathLengthMatch(
                route_names=[name],
                instance_names=[],
                on_failure=None,
                loops=path_length_matching_config.get("loops", 1),
                loop_side=path_length_matching_config.get("loop_side", -1),
                element=path_length_matching_config.get("element", -1),
                loop_position=path_length_matching_config.get("loop_position", -1),
                length=path_length_matching_config.get("total_length"),
                all=True,
            )
        ]
    else:
        route_constraints = list(constraints or [])

    try:
        kf_on_collision = "error" if on_collision == "warning" else on_collision
        kf_on_placer_error = (
            "error" if on_placer_error == "warning" else on_placer_error
        )

        route = kf.routing.optical.route_bundle(
            component,
            ports1_,
            ports2_,
            separation=separation,
            straight_factory=straight_um,
            bend90_cell=bend90,
            taper_cell=taper_cell,
            starts=start_straight_length,
            ends=end_straight_length,
            min_straight_taper=min_straight_taper,
            place_port_type=port_type,
            collision_check_layers=[
                c.kcl.layout.get_info(layer) for layer in collision_check_layer_enums
            ]
            if collision_check_layer_enums
            else None,
            on_collision=kf_on_collision,
            on_placer_error=kf_on_placer_error,
            allow_width_mismatch=allow_width_mismatch,
            allow_layer_mismatch=allow_layer_mismatch,
            allow_type_mismatch=allow_type_mismatch,
            bboxes=list(bboxes or []),
            route_width=width,
            sort_ports=sort_ports,
            waypoints=waypoints_,
            end_angles=end_angles,
            start_angles=start_angles,
            constraints=route_constraints,
            sbend_factory=_sbend if sbend else None,
        )
    except Exception as e:
        if raise_on_error:
            if "kdb.Trans" in str(e):
                raise ValueError("You need at least 2 waypoints or steps.") from e
            if "non-manhattan" in str(e):
                raise ValueError(
                    "Waypoints need to be Manhattan (axis-aligned) coordinates."
                ) from e
            raise

        if "kdb.Trans" in str(e):
            e = ValueError("You need at least 2 waypoints or steps.")
        elif "non-manhattan" in str(e):
            e = ValueError("Waypoints need to be Manhattan (axis-aligned) coordinates.")
        gf.logger.error(f"Error in route_bundle: {e}")
        warn(f"Routing failed: {e}", stacklevel=2)
        layer_error_path = gf.get_layer_info(gf.CONF.layer_error_path)
        route = kf.routing.electrical.route_bundle(
            component,
            ports1_,
            ports2_,
            separation=separation,
            starts=start_straight_length,
            ends=end_straight_length,
            on_collision=None,
            on_placer_error=None,
            bboxes=bboxes,
            route_width=width,
            sort_ports=sort_ports,
            end_angles=end_angles,
            start_angles=start_angles,
            place_layer=layer_error_path,
        )

        if waypoints and waypoints_ is not None:
            layer_marker = gf.CONF.layer_error_path
            for p in waypoints_:
                marker = component << gf.components.rectangle(
                    size=(10, 10), layer=layer_marker, centered=True
                )
                marker.center = (p.x, p.y)

    if layer_label:
        for route_i in route:
            c.add_label(
                text=f"{route_i.length:.3f}",
                layer=layer_label,
                position=route_i.instances[0].dcenter,
            )

    return route

route_bundle_electrical module-attribute

route_bundle_electrical = partial(
    route_bundle,
    bend="wire_corner",
    allow_width_mismatch=True,
)

route_bundle_all_angle

route_bundle_all_angle

route_bundle_all_angle

route_bundle_all_angle(
    component: ComponentSpec,
    ports1: list[Port],
    ports2: list[Port],
    backbone: Coordinates | None = None,
    separation: list[float] | float = 3.0,
    straight: CellAllAngleSpec = "straight_all_angle",
    bend: CellAllAngleSpec = "bend_euler_all_angle",
    bend_ports: tuple[str, str] = ("o1", "o2"),
    straight_ports: tuple[str, str] = ("o1", "o2"),
    cross_section: CrossSectionSpec | None = None,
) -> list[OpticalAllAngleRoute]

Route a bundle of ports to another bundle of ports with non manhattan ports.

Parameters:

Name Type Description Default
component ComponentSpec

to add the routing.

required
ports1 list[Port]

list of start ports to connect.

required
ports2 list[Port]

list of end ports to connect.

required
backbone Coordinates | None

list of points to connect the ports.

None
separation list[float] | float

list of spacings.

3.0
straight CellAllAngleSpec

function to create straights.

'straight_all_angle'
bend CellAllAngleSpec

function to create bends.

'bend_euler_all_angle'
bend_ports tuple[str, str]

tuple of ports to connect the bends.

('o1', 'o2')
straight_ports tuple[str, str]

tuple of ports to connect the straights.

('o1', 'o2')
cross_section CrossSectionSpec | None

cross_section to use. Overrides the cross_section.

None
Source code in gdsfactory/routing/route_bundle_all_angle.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
def route_bundle_all_angle(
    component: ComponentSpec,
    ports1: list[Port],
    ports2: list[Port],
    backbone: Coordinates | None = None,
    separation: list[float] | float = 3.0,
    straight: CellAllAngleSpec = "straight_all_angle",
    bend: CellAllAngleSpec = "bend_euler_all_angle",
    bend_ports: tuple[str, str] = ("o1", "o2"),
    straight_ports: tuple[str, str] = ("o1", "o2"),
    cross_section: CrossSectionSpec | None = None,
) -> list[OpticalAllAngleRoute]:
    """Route a bundle of ports to another bundle of ports with non manhattan ports.

    Args:
        component: to add the routing.
        ports1: list of start ports to connect.
        ports2: list of end ports to connect.
        backbone: list of points to connect the ports.
        separation: list of spacings.
        straight: function to create straights.
        bend: function to create bends.
        bend_ports: tuple of ports to connect the bends.
        straight_ports: tuple of ports to connect the straights.
        cross_section: cross_section to use. Overrides the  cross_section.
    """
    if cross_section:
        straight_func = gf.get_cell(straight, cross_section=cross_section)
        bend_func = gf.get_cell(bend, cross_section=cross_section)

    else:
        straight_func = gf.get_cell(straight)
        bend_func = gf.get_cell(bend)

    backbone = backbone or []

    c = gf.get_component(component)

    return route_bundle(
        c=c,
        start_ports=ports1,
        end_ports=ports2,
        backbone=to_kdb_dpoints(backbone),
        separation=separation,
        straight_factory=straight_func,
        bend_factory=bend_func,
        bend_ports=bend_ports,
        straight_ports=straight_ports,
    )

route_ports_to_side

For now route_bundle is not smart enough to decide whether it should call route_ports_to_side. So you either need to connect your ports to face in one direction first, or to use route_ports_to_side before calling route_bundle.

route_ports_to_side

route_ports_to_side

route_ports_to_side(
    component: Component,
    cross_section: CrossSectionSpec,
    ports: Ports | None = None,
    side: Literal[
        "north", "east", "south", "west"
    ] = "north",
    x: float | None | Literal["east", "west"] = None,
    y: float | None | Literal["north", "south"] = None,
    **kwargs: Any
) -> tuple[list[ManhattanRoute], list[kf.DPort]]

Routes ports to a given side.

Parameters:

Name Type Description Default
component Component

component to route.

required
cross_section CrossSectionSpec

cross_section to use for routing.

required
ports Ports | None

ports to route to a side.

None
side Literal['north', 'east', 'south', 'west']

'north', 'south', 'east' or 'west'.

'north'
x float | None | Literal['east', 'west']

position to route ports for east/west. None, uses most east/west value.

None
y float | None | Literal['north', 'south']

position to route ports for south/north. None, uses most north/south value.

None
kwargs Any

additional arguments to pass to the routing function.

{}

Other Parameters:

Name Type Description
radius

in um.

separation

in um.

Returns:

Type Description
list[ManhattanRoute]

List of routes: with routing elements.

list[DPort]

List of ports: of the new ports.

Example
import gdsfactory as gf

c = gf.Component()
dummy = gf.components.nxn(north=2, south=2, west=2, east=2)
sides = ["north", "south", "east", "west"]
d = 100
positions = [(0, 0), (d, 0), (d, d), (0, d)]

for pos, side in zip(positions, sides):
dummy_ref = c << dummy
dummy_ref.move(pos)
routes, ports = gf.routing.route_ports_to_side(
component=c, side=side, ports=dummy_ref.ports, cross_section="strip"
)

for i, p in enumerate(ports):
c.add_port(name=f"{side[0]}{i}", port=p)

c.plot()
Source code in gdsfactory/routing/route_ports_to_side.py
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
def route_ports_to_side(
    component: Component,
    cross_section: CrossSectionSpec,
    ports: Ports | None = None,
    side: Literal["north", "east", "south", "west"] = "north",
    x: float | None | Literal["east", "west"] = None,
    y: float | None | Literal["north", "south"] = None,
    **kwargs: Any,
) -> tuple[list[ManhattanRoute], list[kf.DPort]]:
    """Routes ports to a given side.

    Args:
        component: component to route.
        cross_section: cross_section to use for routing.
        ports: ports to route to a side.
        side: 'north', 'south', 'east' or 'west'.
        x: position to route ports for east/west. None, uses most east/west value.
        y: position to route ports for south/north. None, uses most north/south value.
        kwargs: additional arguments to pass to the routing function.

    Keyword Args:
      radius: in um.
      separation: in um.
      extend_bottom/extend_top for east/west routing.
      extend_left, extend_right for south/north routing.

    Returns:
        List of routes: with routing elements.
        List of ports: of the new ports.

    Example:
        ```python
        import gdsfactory as gf

        c = gf.Component()
        dummy = gf.components.nxn(north=2, south=2, west=2, east=2)
        sides = ["north", "south", "east", "west"]
        d = 100
        positions = [(0, 0), (d, 0), (d, d), (0, d)]

        for pos, side in zip(positions, sides):
        dummy_ref = c << dummy
        dummy_ref.move(pos)
        routes, ports = gf.routing.route_ports_to_side(
        component=c, side=side, ports=dummy_ref.ports, cross_section="strip"
        )

        for i, p in enumerate(ports):
        c.add_port(name=f"{side[0]}{i}", port=p)

        c.plot()
        ```
    """
    if not ports:
        return [], []

    if side in {"north", "south"}:
        y_value = y if y is not None else side
        if isinstance(y_value, str):
            y_value = cast("Literal['north', 'south']", y_value)
        side = cast("Literal['north', 'south']", side)
        return route_ports_to_y(
            component=component,
            ports=ports,
            y=y_value,
            side=side,
            cross_section=cross_section,
            **kwargs,
        )
    if side in {"east", "west"}:
        x_value = x if x is not None else cast("Literal['east', 'west']", side)
        side = cast("Literal['west', 'east']", side)
        return route_ports_to_x(
            component=component,
            ports=ports,
            x=x_value,
            side=side,
            cross_section=cross_section,
            **kwargs,
        )
    raise ValueError(f"side={side} must be 'north', 'south', 'east' or 'west'")

route_ports_to_x

route_ports_to_x(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    x: float | Literal["east", "west"] = "east",
    separation: float = 10.0,
    radius: float = 10.0,
    extend_bottom: float = 0.0,
    extend_top: float = 0.0,
    extension_length: float = 0.0,
    y0_bottom: float | None = None,
    y0_top: float | None = None,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["east", "west"] = "east",
    **routing_func_args: Any
) -> tuple[list[ManhattanRoute], list[typings.Port]]

Returns route to x.

Parameters:

Name Type Description Default
component Component

component to route.

required
ports Ports

reasonably well behaved list of ports. ports facing north ports are norther than any other ports ports facing south ports are souther ... ports facing west ports are the wester ... ports facing east ports are the easter ...

required
cross_section CrossSectionSpec

cross_section to use for routing.

required
x float | Literal['east', 'west']

float or string. if float: x coordinate to which the ports will be routed if string: "east" -> route to east if string: "west" -> route to west

'east'
separation float

in um.

10.0
radius float

in um.

10.0
extend_bottom float

in um.

0.0
extend_top float

in um.

0.0
extension_length float

in um.

0.0
y0_bottom float | None

in um.

None
y0_top float | None

in um.

None
backward_port_side_split_index int

integer represents and index in the list of backwards ports (bottom to top) all ports with an index strictly lower or equal are routed bottom all ports with an index larger or equal are routed top.

0
start_straight_length float

in um.

0.01
dx_start float | None

override minimum starting x distance.

None
dy_start float | None

override minimum starting y distance.

None
side Literal['east', 'west']

"east" or "west".

'east'
routing_func_args Any

additional arguments to pass to the routing function.

{}

Returns:

Name Type Description
routes list[ManhattanRoute]

list of routes

ports list[Port]

list of the new optical ports

  1. routes the bottom-half of the ports facing opposite side of x
  2. routes the south ports
  3. front ports
  4. north ports
Source code in gdsfactory/routing/route_ports_to_side.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def route_ports_to_x(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    x: float | Literal["east", "west"] = "east",
    separation: float = 10.0,
    radius: float = 10.0,
    extend_bottom: float = 0.0,
    extend_top: float = 0.0,
    extension_length: float = 0.0,
    y0_bottom: float | None = None,
    y0_top: float | None = None,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["east", "west"] = "east",
    **routing_func_args: Any,
) -> tuple[list[ManhattanRoute], list[typings.Port]]:
    """Returns route to x.

    Args:
        component: component to route.
        ports: reasonably well behaved list of ports.
           ports facing north ports are norther than any other ports
           ports facing south ports are souther ...
           ports facing west ports are the wester ...
           ports facing east ports are the easter ...
        cross_section: cross_section to use for routing.
        x: float or string.
           if float: x coordinate to which the ports will be routed
           if string: "east" -> route to east
           if string: "west" -> route to west
        separation: in um.
        radius: in um.
        extend_bottom: in um.
        extend_top: in um.
        extension_length: in um.
        y0_bottom: in um.
        y0_top: in um.
        backward_port_side_split_index: integer represents and index in the list of backwards ports (bottom to top)
            all ports with an index strictly lower or equal are routed bottom
            all ports with an index larger or equal are routed top.
        start_straight_length: in um.
        dx_start: override minimum starting x distance.
        dy_start: override minimum starting y distance.
        side: "east" or "west".
        routing_func_args: additional arguments to pass to the routing function.

    Returns:
        routes: list of routes
        ports: list of the new optical ports

    1. routes the bottom-half of the ports facing opposite side of x
    2. routes the south ports
    3. front ports
    4. north ports

    """
    north_ports = [p for p in ports if p.orientation == 90]
    south_ports = [p for p in ports if p.orientation == 270]
    east_ports = [p for p in ports if p.orientation == 0]
    west_ports = [p for p in ports if p.orientation == 180]

    epsilon = 1.0
    a = epsilon + max(radius, separation)
    bx = epsilon + max(radius, dx_start) if dx_start else a
    by = epsilon + max(radius, dy_start) if dy_start else a

    xs = [p.x for p in ports]
    ys = [p.y for p in ports]

    if y0_bottom is None:
        y0_bottom = min(ys) - by

    y0_bottom -= extend_bottom

    if y0_top is None:
        y0_top = max(ys) + (max(radius, dy_start) if dy_start else a)
    y0_top += extend_top

    if x == "west" and extension_length > 0:
        extension_length = -extension_length

    if x == "east":
        x = max(p.x for p in ports) + bx
    elif x == "west":
        x = min(p.x for p in ports) - bx

    if x < min(xs):
        sort_key_north = sort_key_west_to_east
        sort_key_south = sort_key_west_to_east
        forward_ports = west_ports
        backward_ports = east_ports
        angle = 0

    elif x > max(xs):
        sort_key_south = sort_key_east_to_west
        sort_key_north = sort_key_east_to_west
        forward_ports = east_ports
        backward_ports = west_ports
        angle = 180
    else:
        raise ValueError("x should be either to the east or to the west of all ports")

    # forward_ports.sort()
    north_ports.sort(key=sort_key_north)
    south_ports.sort(key=sort_key_south)
    forward_ports.sort(key=sort_key_south_to_north)

    backward_ports.sort(key=sort_key_south_to_north)
    backward_ports_thru_south = backward_ports[:backward_port_side_split_index]
    backward_ports_thru_north = backward_ports[backward_port_side_split_index:]
    backward_ports_thru_south.sort(key=sort_key_south_to_north)
    backward_ports_thru_north.sort(key=sort_key_north_to_south)

    routes: list[ManhattanRoute] = []
    new_ports: list[typings.Port] = []

    def add_port(
        port: typings.Port,
        y: float,
        l_elements: list[ManhattanRoute],
        l_ports: list[typings.Port],
        start_straight_length: float = start_straight_length,
    ) -> None:
        if side == "west":
            angle = 0

        elif side == "east":
            angle = 180

        else:
            raise ValueError(f"{side=} should be either 'west' or 'east'")

        new_port = port.copy()
        new_port.orientation = angle
        new_port.x = x + extension_length
        new_port.y = y

        new_port2 = new_port.copy()
        new_port2.trans *= gf.kdb.Trans.R180

        l_elements += route_bundle(
            component,
            port,
            new_port,
            start_straight_length=start_straight_length,
            radius=radius,
            cross_section=cross_section,
            **routing_func_args,
        )
        l_ports.append(new_port2)

    y_optical_bot = y0_bottom
    for p in south_ports:
        add_port(p, y_optical_bot, routes, new_ports)
        y_optical_bot -= separation

    for p in forward_ports:
        add_port(p, p.y, routes, new_ports)

    y_optical_top = y0_top
    for p in north_ports:
        add_port(p, y_optical_top, routes, new_ports)
        y_optical_top += separation

    start_straight_length_section = start_straight_length
    max_x = max(xs)
    min_x = min(xs)

    for p in backward_ports_thru_north:
        # Extend new_ports if necessary
        if angle == 0 and p.x < max_x:
            start_straight_length_section = max_x - p.x
        elif angle == 180 and p.x > min_x:
            start_straight_length_section = p.x - min_x
        else:
            start_straight_length_section = 0

        add_port(
            p,
            y_optical_top,
            routes,
            new_ports,
            start_straight_length=start_straight_length + start_straight_length_section,
        )
        y_optical_top += separation
        start_straight_length += separation

    start_straight_length_section = start_straight_length
    for p in backward_ports_thru_south:
        # Extend new_ports if necessary
        if angle == 0 and p.x < max_x:
            start_straight_length_section = max_x - p.x
        elif angle == 180 and p.x > min_x:
            start_straight_length_section = p.x - min_x
        else:
            start_straight_length_section = 0

        add_port(
            p,
            y_optical_bot,
            routes,
            new_ports,
            start_straight_length=start_straight_length + start_straight_length_section,
        )
        y_optical_bot -= separation
        start_straight_length += separation

    return routes, new_ports

route_ports_to_y

route_ports_to_y(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    y: float | Literal["north", "south"] = "north",
    separation: float = 10.0,
    radius: float = 10.0,
    x0_left: float | None = None,
    x0_right: float | None = None,
    extension_length: float = 0.0,
    extend_left: float = 0.0,
    extend_right: float = 0.0,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["north", "south"] = "north",
    **routing_func_args: Any
) -> tuple[list[ManhattanRoute], list[typings.Port]]

Route ports to y.

Parameters:

Name Type Description Default
component Component

component to route.

required
ports Ports

reasonably well behaved list of ports. ports facing north ports are norther than any other ports ports facing south ports are souther ... ports facing west ports are the wester ... ports facing east ports are the easter ...

required
cross_section CrossSectionSpec

cross_section to use for routing.

required
y float | Literal['north', 'south']

float or string. if float: y coordinate to which the ports will be routed if string: "north" -> route to north if string: "south" -> route to south

'north'
separation float

in um.

10.0
radius float

in um.

10.0
x0_left float | None

in um.

None
x0_right float | None

in um.

None
extension_length float

in um.

0.0
extend_left float

in um.

0.0
extend_right float

in um.

0.0
backward_port_side_split_index int

integer this integer represents and index in the list of backwards ports (sorted from left to right) all ports with an index strictly larger are routed right all ports with an index lower or equal are routed left

0
start_straight_length float

in um.

0.01
dx_start float | None

override minimum starting x distance.

None
dy_start float | None

override minimum starting y distance.

None
side Literal['north', 'south']

"north" or "south".

'north'
routing_func_args Any

additional arguments to pass to the routing function.

{}

Returns:

Type Description
list[ManhattanRoute]
  • a list of Routes
list[Port]
  • a list of the new optical ports

First route the bottom-half of the back ports (back ports are the one facing opposite side of x) Then route the south ports then the front ports then the north ports

Source code in gdsfactory/routing/route_ports_to_side.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def route_ports_to_y(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    y: float | Literal["north", "south"] = "north",
    separation: float = 10.0,
    radius: float = 10.0,
    x0_left: float | None = None,
    x0_right: float | None = None,
    extension_length: float = 0.0,
    extend_left: float = 0.0,
    extend_right: float = 0.0,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["north", "south"] = "north",
    **routing_func_args: Any,
) -> tuple[list[ManhattanRoute], list[typings.Port]]:
    """Route ports to y.

    Args:
        component: component to route.
        ports: reasonably well behaved list of ports.
           ports facing north ports are norther than any other ports
           ports facing south ports are souther ...
           ports facing west ports are the wester ...
           ports facing east ports are the easter ...
        cross_section: cross_section to use for routing.
        y: float or string.
               if float: y coordinate to which the ports will be routed
               if string: "north" -> route to north
               if string: "south" -> route to south
        separation: in um.
        radius: in um.
        x0_left: in um.
        x0_right: in um.
        extension_length: in um.
        extend_left: in um.
        extend_right: in um.
        backward_port_side_split_index: integer
               this integer represents and index in the list of backwards ports
                   (sorted from left to right)
               all ports with an index strictly larger are routed right
               all ports with an index lower or equal are routed left
        start_straight_length: in um.
        dx_start: override minimum starting x distance.
        dy_start: override minimum starting y distance.
        side: "north" or "south".
        routing_func_args: additional arguments to pass to the routing function.


    Returns:
        - a list of Routes
        - a list of the new optical ports

    First route the bottom-half of the back ports (back ports are the one facing opposite side of x)
    Then route the south ports
    then the front ports
    then the north ports
    """
    if y == "south" and extension_length > 0:
        extension_length = -extension_length

    da = 45
    north_ports = [
        p for p in ports if p.orientation > 90 - da and p.orientation < 90 + da
    ]
    south_ports = [
        p for p in ports if p.orientation > 270 - da and p.orientation < 270 + da
    ]
    east_ports = [p for p in ports if p.orientation < da or p.orientation > 360 - da]
    west_ports = [
        p for p in ports if p.orientation < 180 + da and p.orientation > 180 - da
    ]

    epsilon = 1.0
    a = radius + max(radius, separation)
    bx = epsilon + max(radius, dx_start) if dx_start else a
    by = epsilon + max(radius, dy_start) if dy_start else a

    xs = [p.x for p in ports]
    ys = [p.y for p in ports]

    if x0_left is None:
        x0_left = min(xs) - bx
    x0_left -= extend_left

    if x0_right is None:
        x0_right = max(xs) + (max(radius, dx_start) if dx_start else a)
    x0_right += extend_right

    if y == "north":
        y_float = (
            max(p.y + a * np.abs(np.cos(p.orientation * np.pi / 180)) for p in ports)
            + by
        )
    elif y == "south":
        y_float = (
            min(p.y - a * np.abs(np.cos(p.orientation * np.pi / 180)) for p in ports)
            - by
        )
    elif isinstance(y, float | int):
        y_float = y
    if y_float <= min(ys):
        sort_key_east = sort_key_south_to_north
        sort_key_west = sort_key_south_to_north
        forward_ports = south_ports
        backward_ports = north_ports

    elif y_float >= max(ys):
        sort_key_west = sort_key_north_to_south
        sort_key_east = sort_key_north_to_south
        forward_ports = north_ports
        backward_ports = south_ports
    else:
        raise ValueError("y should be either to the north or to the south of all ports")

    west_ports.sort(key=sort_key_west)
    east_ports.sort(key=sort_key_east)
    forward_ports.sort(key=sort_key_west_to_east)
    backward_ports.sort(key=sort_key_east_to_west)

    backward_ports.sort(key=sort_key_west_to_east)
    backward_ports_thru_west = backward_ports[:backward_port_side_split_index]
    backward_ports_thru_east = backward_ports[backward_port_side_split_index:]

    backward_ports_thru_west.sort(key=sort_key_west_to_east)
    backward_ports_thru_east.sort(key=sort_key_east_to_west)

    routes: list[ManhattanRoute] = []
    new_ports: list[typings.Port] = []

    def add_port(
        port: typings.Port,
        x: float,
        l_elements: list[ManhattanRoute],
        l_ports: list[typings.Port],
        start_straight_length: float = start_straight_length,
    ) -> None:
        if side == "south":
            angle = 90

        elif side == "north":
            angle = 270

        new_port = port.copy()
        new_port.orientation = angle
        new_port.center = (x, y_float + extension_length)

        if np.sum(np.abs((np.array(new_port.center) - port.center) ** 2)) < 1:
            l_ports += [flipped(new_port)]
            return

        try:
            l_elements += route_bundle(
                component,
                port,
                new_port,
                start_straight_length=start_straight_length,
                radius=radius,
                cross_section=cross_section,
                **routing_func_args,
            )
            l_ports += [flipped(new_port)]

        except Exception as error:
            raise ValueError(
                f"Could not connect {port.name!r} to {new_port.name!r} {error}"
            ) from error

    x_optical_left = x0_left
    for p in west_ports:
        add_port(p, x_optical_left, routes, new_ports)
        x_optical_left -= separation

    for p in forward_ports:
        add_port(p, p.x, routes, new_ports)

    x_optical_right = x0_right
    for p in east_ports:
        add_port(p, x_optical_right, routes, new_ports)
        x_optical_right += separation

    start_straight_length_section = start_straight_length
    for p in backward_ports_thru_east:
        add_port(
            p,
            x_optical_right,
            routes,
            new_ports,
            start_straight_length=start_straight_length_section,
        )
        x_optical_right += separation
        start_straight_length_section += separation

    start_straight_length_section = start_straight_length
    for p in backward_ports_thru_west:
        add_port(
            p,
            x_optical_left,
            routes,
            new_ports,
            start_straight_length=start_straight_length_section,
        )
        x_optical_left -= separation
        start_straight_length_section += separation

    return routes, new_ports

route_ports_to_x

route_ports_to_x(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    x: float | Literal["east", "west"] = "east",
    separation: float = 10.0,
    radius: float = 10.0,
    extend_bottom: float = 0.0,
    extend_top: float = 0.0,
    extension_length: float = 0.0,
    y0_bottom: float | None = None,
    y0_top: float | None = None,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["east", "west"] = "east",
    **routing_func_args: Any
) -> tuple[list[ManhattanRoute], list[typings.Port]]

Returns route to x.

Parameters:

Name Type Description Default
component Component

component to route.

required
ports Ports

reasonably well behaved list of ports. ports facing north ports are norther than any other ports ports facing south ports are souther ... ports facing west ports are the wester ... ports facing east ports are the easter ...

required
cross_section CrossSectionSpec

cross_section to use for routing.

required
x float | Literal['east', 'west']

float or string. if float: x coordinate to which the ports will be routed if string: "east" -> route to east if string: "west" -> route to west

'east'
separation float

in um.

10.0
radius float

in um.

10.0
extend_bottom float

in um.

0.0
extend_top float

in um.

0.0
extension_length float

in um.

0.0
y0_bottom float | None

in um.

None
y0_top float | None

in um.

None
backward_port_side_split_index int

integer represents and index in the list of backwards ports (bottom to top) all ports with an index strictly lower or equal are routed bottom all ports with an index larger or equal are routed top.

0
start_straight_length float

in um.

0.01
dx_start float | None

override minimum starting x distance.

None
dy_start float | None

override minimum starting y distance.

None
side Literal['east', 'west']

"east" or "west".

'east'
routing_func_args Any

additional arguments to pass to the routing function.

{}

Returns:

Name Type Description
routes list[ManhattanRoute]

list of routes

ports list[Port]

list of the new optical ports

  1. routes the bottom-half of the ports facing opposite side of x
  2. routes the south ports
  3. front ports
  4. north ports
Source code in gdsfactory/routing/route_ports_to_side.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def route_ports_to_x(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    x: float | Literal["east", "west"] = "east",
    separation: float = 10.0,
    radius: float = 10.0,
    extend_bottom: float = 0.0,
    extend_top: float = 0.0,
    extension_length: float = 0.0,
    y0_bottom: float | None = None,
    y0_top: float | None = None,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["east", "west"] = "east",
    **routing_func_args: Any,
) -> tuple[list[ManhattanRoute], list[typings.Port]]:
    """Returns route to x.

    Args:
        component: component to route.
        ports: reasonably well behaved list of ports.
           ports facing north ports are norther than any other ports
           ports facing south ports are souther ...
           ports facing west ports are the wester ...
           ports facing east ports are the easter ...
        cross_section: cross_section to use for routing.
        x: float or string.
           if float: x coordinate to which the ports will be routed
           if string: "east" -> route to east
           if string: "west" -> route to west
        separation: in um.
        radius: in um.
        extend_bottom: in um.
        extend_top: in um.
        extension_length: in um.
        y0_bottom: in um.
        y0_top: in um.
        backward_port_side_split_index: integer represents and index in the list of backwards ports (bottom to top)
            all ports with an index strictly lower or equal are routed bottom
            all ports with an index larger or equal are routed top.
        start_straight_length: in um.
        dx_start: override minimum starting x distance.
        dy_start: override minimum starting y distance.
        side: "east" or "west".
        routing_func_args: additional arguments to pass to the routing function.

    Returns:
        routes: list of routes
        ports: list of the new optical ports

    1. routes the bottom-half of the ports facing opposite side of x
    2. routes the south ports
    3. front ports
    4. north ports

    """
    north_ports = [p for p in ports if p.orientation == 90]
    south_ports = [p for p in ports if p.orientation == 270]
    east_ports = [p for p in ports if p.orientation == 0]
    west_ports = [p for p in ports if p.orientation == 180]

    epsilon = 1.0
    a = epsilon + max(radius, separation)
    bx = epsilon + max(radius, dx_start) if dx_start else a
    by = epsilon + max(radius, dy_start) if dy_start else a

    xs = [p.x for p in ports]
    ys = [p.y for p in ports]

    if y0_bottom is None:
        y0_bottom = min(ys) - by

    y0_bottom -= extend_bottom

    if y0_top is None:
        y0_top = max(ys) + (max(radius, dy_start) if dy_start else a)
    y0_top += extend_top

    if x == "west" and extension_length > 0:
        extension_length = -extension_length

    if x == "east":
        x = max(p.x for p in ports) + bx
    elif x == "west":
        x = min(p.x for p in ports) - bx

    if x < min(xs):
        sort_key_north = sort_key_west_to_east
        sort_key_south = sort_key_west_to_east
        forward_ports = west_ports
        backward_ports = east_ports
        angle = 0

    elif x > max(xs):
        sort_key_south = sort_key_east_to_west
        sort_key_north = sort_key_east_to_west
        forward_ports = east_ports
        backward_ports = west_ports
        angle = 180
    else:
        raise ValueError("x should be either to the east or to the west of all ports")

    # forward_ports.sort()
    north_ports.sort(key=sort_key_north)
    south_ports.sort(key=sort_key_south)
    forward_ports.sort(key=sort_key_south_to_north)

    backward_ports.sort(key=sort_key_south_to_north)
    backward_ports_thru_south = backward_ports[:backward_port_side_split_index]
    backward_ports_thru_north = backward_ports[backward_port_side_split_index:]
    backward_ports_thru_south.sort(key=sort_key_south_to_north)
    backward_ports_thru_north.sort(key=sort_key_north_to_south)

    routes: list[ManhattanRoute] = []
    new_ports: list[typings.Port] = []

    def add_port(
        port: typings.Port,
        y: float,
        l_elements: list[ManhattanRoute],
        l_ports: list[typings.Port],
        start_straight_length: float = start_straight_length,
    ) -> None:
        if side == "west":
            angle = 0

        elif side == "east":
            angle = 180

        else:
            raise ValueError(f"{side=} should be either 'west' or 'east'")

        new_port = port.copy()
        new_port.orientation = angle
        new_port.x = x + extension_length
        new_port.y = y

        new_port2 = new_port.copy()
        new_port2.trans *= gf.kdb.Trans.R180

        l_elements += route_bundle(
            component,
            port,
            new_port,
            start_straight_length=start_straight_length,
            radius=radius,
            cross_section=cross_section,
            **routing_func_args,
        )
        l_ports.append(new_port2)

    y_optical_bot = y0_bottom
    for p in south_ports:
        add_port(p, y_optical_bot, routes, new_ports)
        y_optical_bot -= separation

    for p in forward_ports:
        add_port(p, p.y, routes, new_ports)

    y_optical_top = y0_top
    for p in north_ports:
        add_port(p, y_optical_top, routes, new_ports)
        y_optical_top += separation

    start_straight_length_section = start_straight_length
    max_x = max(xs)
    min_x = min(xs)

    for p in backward_ports_thru_north:
        # Extend new_ports if necessary
        if angle == 0 and p.x < max_x:
            start_straight_length_section = max_x - p.x
        elif angle == 180 and p.x > min_x:
            start_straight_length_section = p.x - min_x
        else:
            start_straight_length_section = 0

        add_port(
            p,
            y_optical_top,
            routes,
            new_ports,
            start_straight_length=start_straight_length + start_straight_length_section,
        )
        y_optical_top += separation
        start_straight_length += separation

    start_straight_length_section = start_straight_length
    for p in backward_ports_thru_south:
        # Extend new_ports if necessary
        if angle == 0 and p.x < max_x:
            start_straight_length_section = max_x - p.x
        elif angle == 180 and p.x > min_x:
            start_straight_length_section = p.x - min_x
        else:
            start_straight_length_section = 0

        add_port(
            p,
            y_optical_bot,
            routes,
            new_ports,
            start_straight_length=start_straight_length + start_straight_length_section,
        )
        y_optical_bot -= separation
        start_straight_length += separation

    return routes, new_ports

route_ports_to_y

route_ports_to_y(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    y: float | Literal["north", "south"] = "north",
    separation: float = 10.0,
    radius: float = 10.0,
    x0_left: float | None = None,
    x0_right: float | None = None,
    extension_length: float = 0.0,
    extend_left: float = 0.0,
    extend_right: float = 0.0,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["north", "south"] = "north",
    **routing_func_args: Any
) -> tuple[list[ManhattanRoute], list[typings.Port]]

Route ports to y.

Parameters:

Name Type Description Default
component Component

component to route.

required
ports Ports

reasonably well behaved list of ports. ports facing north ports are norther than any other ports ports facing south ports are souther ... ports facing west ports are the wester ... ports facing east ports are the easter ...

required
cross_section CrossSectionSpec

cross_section to use for routing.

required
y float | Literal['north', 'south']

float or string. if float: y coordinate to which the ports will be routed if string: "north" -> route to north if string: "south" -> route to south

'north'
separation float

in um.

10.0
radius float

in um.

10.0
x0_left float | None

in um.

None
x0_right float | None

in um.

None
extension_length float

in um.

0.0
extend_left float

in um.

0.0
extend_right float

in um.

0.0
backward_port_side_split_index int

integer this integer represents and index in the list of backwards ports (sorted from left to right) all ports with an index strictly larger are routed right all ports with an index lower or equal are routed left

0
start_straight_length float

in um.

0.01
dx_start float | None

override minimum starting x distance.

None
dy_start float | None

override minimum starting y distance.

None
side Literal['north', 'south']

"north" or "south".

'north'
routing_func_args Any

additional arguments to pass to the routing function.

{}

Returns:

Type Description
list[ManhattanRoute]
  • a list of Routes
list[Port]
  • a list of the new optical ports

First route the bottom-half of the back ports (back ports are the one facing opposite side of x) Then route the south ports then the front ports then the north ports

Source code in gdsfactory/routing/route_ports_to_side.py
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
def route_ports_to_y(
    component: Component,
    ports: Ports,
    cross_section: CrossSectionSpec,
    y: float | Literal["north", "south"] = "north",
    separation: float = 10.0,
    radius: float = 10.0,
    x0_left: float | None = None,
    x0_right: float | None = None,
    extension_length: float = 0.0,
    extend_left: float = 0.0,
    extend_right: float = 0.0,
    backward_port_side_split_index: int = 0,
    start_straight_length: float = 0.01,
    dx_start: float | None = None,
    dy_start: float | None = None,
    side: Literal["north", "south"] = "north",
    **routing_func_args: Any,
) -> tuple[list[ManhattanRoute], list[typings.Port]]:
    """Route ports to y.

    Args:
        component: component to route.
        ports: reasonably well behaved list of ports.
           ports facing north ports are norther than any other ports
           ports facing south ports are souther ...
           ports facing west ports are the wester ...
           ports facing east ports are the easter ...
        cross_section: cross_section to use for routing.
        y: float or string.
               if float: y coordinate to which the ports will be routed
               if string: "north" -> route to north
               if string: "south" -> route to south
        separation: in um.
        radius: in um.
        x0_left: in um.
        x0_right: in um.
        extension_length: in um.
        extend_left: in um.
        extend_right: in um.
        backward_port_side_split_index: integer
               this integer represents and index in the list of backwards ports
                   (sorted from left to right)
               all ports with an index strictly larger are routed right
               all ports with an index lower or equal are routed left
        start_straight_length: in um.
        dx_start: override minimum starting x distance.
        dy_start: override minimum starting y distance.
        side: "north" or "south".
        routing_func_args: additional arguments to pass to the routing function.


    Returns:
        - a list of Routes
        - a list of the new optical ports

    First route the bottom-half of the back ports (back ports are the one facing opposite side of x)
    Then route the south ports
    then the front ports
    then the north ports
    """
    if y == "south" and extension_length > 0:
        extension_length = -extension_length

    da = 45
    north_ports = [
        p for p in ports if p.orientation > 90 - da and p.orientation < 90 + da
    ]
    south_ports = [
        p for p in ports if p.orientation > 270 - da and p.orientation < 270 + da
    ]
    east_ports = [p for p in ports if p.orientation < da or p.orientation > 360 - da]
    west_ports = [
        p for p in ports if p.orientation < 180 + da and p.orientation > 180 - da
    ]

    epsilon = 1.0
    a = radius + max(radius, separation)
    bx = epsilon + max(radius, dx_start) if dx_start else a
    by = epsilon + max(radius, dy_start) if dy_start else a

    xs = [p.x for p in ports]
    ys = [p.y for p in ports]

    if x0_left is None:
        x0_left = min(xs) - bx
    x0_left -= extend_left

    if x0_right is None:
        x0_right = max(xs) + (max(radius, dx_start) if dx_start else a)
    x0_right += extend_right

    if y == "north":
        y_float = (
            max(p.y + a * np.abs(np.cos(p.orientation * np.pi / 180)) for p in ports)
            + by
        )
    elif y == "south":
        y_float = (
            min(p.y - a * np.abs(np.cos(p.orientation * np.pi / 180)) for p in ports)
            - by
        )
    elif isinstance(y, float | int):
        y_float = y
    if y_float <= min(ys):
        sort_key_east = sort_key_south_to_north
        sort_key_west = sort_key_south_to_north
        forward_ports = south_ports
        backward_ports = north_ports

    elif y_float >= max(ys):
        sort_key_west = sort_key_north_to_south
        sort_key_east = sort_key_north_to_south
        forward_ports = north_ports
        backward_ports = south_ports
    else:
        raise ValueError("y should be either to the north or to the south of all ports")

    west_ports.sort(key=sort_key_west)
    east_ports.sort(key=sort_key_east)
    forward_ports.sort(key=sort_key_west_to_east)
    backward_ports.sort(key=sort_key_east_to_west)

    backward_ports.sort(key=sort_key_west_to_east)
    backward_ports_thru_west = backward_ports[:backward_port_side_split_index]
    backward_ports_thru_east = backward_ports[backward_port_side_split_index:]

    backward_ports_thru_west.sort(key=sort_key_west_to_east)
    backward_ports_thru_east.sort(key=sort_key_east_to_west)

    routes: list[ManhattanRoute] = []
    new_ports: list[typings.Port] = []

    def add_port(
        port: typings.Port,
        x: float,
        l_elements: list[ManhattanRoute],
        l_ports: list[typings.Port],
        start_straight_length: float = start_straight_length,
    ) -> None:
        if side == "south":
            angle = 90

        elif side == "north":
            angle = 270

        new_port = port.copy()
        new_port.orientation = angle
        new_port.center = (x, y_float + extension_length)

        if np.sum(np.abs((np.array(new_port.center) - port.center) ** 2)) < 1:
            l_ports += [flipped(new_port)]
            return

        try:
            l_elements += route_bundle(
                component,
                port,
                new_port,
                start_straight_length=start_straight_length,
                radius=radius,
                cross_section=cross_section,
                **routing_func_args,
            )
            l_ports += [flipped(new_port)]

        except Exception as error:
            raise ValueError(
                f"Could not connect {port.name!r} to {new_port.name!r} {error}"
            ) from error

    x_optical_left = x0_left
    for p in west_ports:
        add_port(p, x_optical_left, routes, new_ports)
        x_optical_left -= separation

    for p in forward_ports:
        add_port(p, p.x, routes, new_ports)

    x_optical_right = x0_right
    for p in east_ports:
        add_port(p, x_optical_right, routes, new_ports)
        x_optical_right += separation

    start_straight_length_section = start_straight_length
    for p in backward_ports_thru_east:
        add_port(
            p,
            x_optical_right,
            routes,
            new_ports,
            start_straight_length=start_straight_length_section,
        )
        x_optical_right += separation
        start_straight_length_section += separation

    start_straight_length_section = start_straight_length
    for p in backward_ports_thru_west:
        add_port(
            p,
            x_optical_left,
            routes,
            new_ports,
            start_straight_length=start_straight_length_section,
        )
        x_optical_left -= separation
        start_straight_length_section += separation

    return routes, new_ports

route_south

route_south

route_south(
    component: Component,
    component_to_route: Component | ComponentReference,
    optical_routing_type: int = 1,
    excluded_ports: Sequence[str] | None = None,
    straight_separation: float = 4.0,
    io_gratings_lines: (
        list[list[ComponentReference]] | None
    ) = None,
    gc_port_name: str = "o1",
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    select_ports: SelectPorts = select_ports_optical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "strip",
    start_straight_length: float = 0.5,
    port_type: str | None = None,
    allow_width_mismatch: bool = False,
    auto_taper: bool = True,
) -> list[ManhattanRoute]

Places routes to route a component ports to the south.

Parameters:

Name Type Description Default
component Component

top level component to add the routes.

required
component_to_route Component | ComponentReference

component or reference to route ports to south.

required
optical_routing_type int

routing heuristic 1 or 2 1: uses the component size info to estimate the box size. 2: only looks at the optical port positions to estimate the size.

1
excluded_ports Sequence[str] | None

list of port names to NOT route.

None
straight_separation float

in um.

4.0
io_gratings_lines list[list[ComponentReference]] | None

list of ports to which the ports produced by this function will be connected. Supplying this information helps avoiding straight collisions.

None
gc_port_name str

grating coupler port name. Used only if io_gratings_lines is supplied.

'o1'
bend ComponentSpec

spec.

'bend_euler'
straight ComponentSpec

spec.

'straight'
select_ports SelectPorts

function to select_ports.

select_ports_optical
port_names Strs | None

optional port names. Overrides select_ports.

None
cross_section CrossSectionSpec

cross_section spec.

'strip'
start_straight_length float

in um.

0.5
port_type str | None

optical or electrical.

None
allow_width_mismatch bool

allow width mismatch.

False
auto_taper bool

auto taper.

True
Works well if the component looks roughly like a rectangular box with

north ports on the north of the box. south ports on the south of the box. east ports on the east of the box. west ports on the west of the box.

Example
import gdsfactory as gf

c = gf.Component()
ref = c << gf.components.ring_double()
r = gf.routing.route_south(c, ref)
c.plot()
Source code in gdsfactory/routing/route_south.py
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def route_south(
    component: Component,
    component_to_route: Component | ComponentReference,
    optical_routing_type: int = 1,
    excluded_ports: Sequence[str] | None = None,
    straight_separation: float = 4.0,
    io_gratings_lines: list[list[ComponentReference]] | None = None,
    gc_port_name: str = "o1",
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    select_ports: SelectPorts = select_ports_optical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "strip",
    start_straight_length: float = 0.5,
    port_type: str | None = None,
    allow_width_mismatch: bool = False,
    auto_taper: bool = True,
) -> list[ManhattanRoute]:
    """Places routes to route a component ports to the south.

    Args:
        component: top level component to add the routes.
        component_to_route: component or reference to route ports to south.
        optical_routing_type: routing heuristic `1` or `2` \
            1: uses the component size info to estimate the box size.\
            2: only looks at the optical port positions to estimate the size.
        excluded_ports: list of port names to NOT route.
        straight_separation: in um.
        io_gratings_lines: list of ports to which the ports produced by this function will be connected. \
                Supplying this information helps avoiding straight collisions.
        gc_port_name: grating coupler port name. Used only if io_gratings_lines is supplied.
        bend: spec.
        straight: spec.
        select_ports: function to select_ports.
        port_names: optional port names. Overrides select_ports.
        cross_section: cross_section spec.
        start_straight_length: in um.
        port_type: optical or electrical.
        allow_width_mismatch: allow width mismatch.
        auto_taper: auto taper.

    Works well if the component looks roughly like a rectangular box with:
        north ports on the north of the box.
        south ports on the south of the box.
        east ports on the east of the box.
        west ports on the west of the box.

    Example:
        ```python
        import gdsfactory as gf

        c = gf.Component()
        ref = c << gf.components.ring_double()
        r = gf.routing.route_south(c, ref)
        c.plot()
        ```
    """
    xs = gf.get_cross_section(cross_section)
    excluded_ports = excluded_ports or ()
    start_straight_length0 = start_straight_length
    routes: list[ManhattanRoute] = []

    if optical_routing_type not in {1, 2}:
        raise ValueError(
            f"optical_routing_type = {optical_routing_type} not in supported [1, 2]"
        )

    if port_names:
        optical_ports = [component_to_route[port_name] for port_name in port_names]
    else:
        optical_ports = [
            p
            for p in select_ports(component_to_route.ports)
            if p.name not in excluded_ports
        ]

    if auto_taper:
        optical_ports = add_auto_tapers(component, optical_ports, cross_section)

    if not optical_ports:
        return []

    port_type = port_type or optical_ports[0].port_type
    bend90 = bend(cross_section=cross_section) if callable(bend) else bend
    bend90 = gf.get_component(bend90)
    dy = abs(bend90.info["dy"])

    # Handle empty list gracefully

    route_with_conn_params = partial(
        route_bundle,
        bend=bend,
        straight=straight,
        cross_section=cross_section,
        port_type=port_type,
        allow_width_mismatch=allow_width_mismatch,
        auto_taper=False,
    )

    # Used to avoid crossing between straights in special cases
    # This could happen when abs(x_port - x_grating) <= 2 * dy
    delta_gr_min = 2 * dy + 1
    sep = straight_separation

    # Get lists of optical ports by orientation
    direction_ports = direction_ports_from_list_ports(optical_ports)

    north_ports = direction_ports["N"]
    north_start = north_ports[: len(north_ports) // 2]
    north_finish = north_ports[len(north_ports) // 2 :]

    west_ports = direction_ports["W"]
    west_ports.reverse()
    east_ports = direction_ports["E"]
    south_ports = direction_ports["S"]
    north_finish.reverse()  # Sort right to left
    north_start.reverse()  # Sort right to left
    ordered_ports = north_start + west_ports + south_ports + east_ports + north_finish

    def get_index_port_closest_to_x(
        x: float, component_references: list[ComponentReference]
    ) -> np.intp:
        return np.array(
            [abs(x - p.ports[gc_port_name].x) for p in component_references]
        ).argmin()

    def gen_port_from_port(
        x: float, y: float, p: typings.Port, cross_section: CrossSection
    ) -> Port:
        return Port(
            name=p.name,
            center=(x, y),
            orientation=90.0,
            width=p.width,
            layer=gf.get_layer(cross_section.layer),
            port_type=p.port_type,
        )

    west_ports.reverse()
    y0 = min(p.y for p in ordered_ports) - dy - 0.5
    ports_to_route: list[Port] = []

    optical_xs_tmp = [p.x for p in ordered_ports]
    x_optical_min = min(optical_xs_tmp)
    x_optical_max = max(optical_xs_tmp)

    # Set starting ``x`` on the west side
    # ``x`` is the x-coord of the waypoint where the current component port is connected.
    # x starts as close as possible to the component.
    # For each new port, the distance is increased by the separation.
    # The starting x depends on the heuristic chosen : ``1`` or ``2``
    if optical_routing_type == 1:
        # use component size to know how far to route
        x = component_to_route.xmin - dy - 1
    else:
        # use optical port to know how far to route
        x = x_optical_min - dy - 1

    # First route the ports facing west
    # In case we have to connect these ports to a line of gratings,
    # Ensure that the port is aligned with the grating port or
    # has enough space for manhattan routing (at least two bend radius)
    for p in west_ports:
        if io_gratings_lines:
            i_grating = get_index_port_closest_to_x(x, io_gratings_lines[-1])
            x_gr = io_gratings_lines[-1][i_grating].ports[gc_port_name].x
            if abs(x - x_gr) < delta_gr_min:
                if x > x_gr:
                    x = x_gr
                elif x < x_gr:
                    x = x_gr - delta_gr_min

        tmp_port = gen_port_from_port(x, y0, p, cross_section=xs)
        ports_to_route.append(tmp_port)
        route = route_with_conn_params(component, tmp_port, p)[0]
        x -= sep

    # route first halft of north ports above the top west one
    north_start.reverse()  # We need them from left to right

    start_straight_length = start_straight_length0
    if len(north_start) > 0:
        y_max = max(p.y for p in west_ports + north_start)
        for p in north_start:
            tmp_port = gen_port_from_port(x, y0, p, cross_section=xs)
            route = route_with_conn_params(
                component,
                tmp_port,
                p,
                end_straight_length=start_straight_length + y_max - p.y,
            )[0]

            ports_to_route.append(tmp_port)
            x -= sep
            start_straight_length += sep
            routes.append(route)

    # Set starting ``x`` on the east side
    if optical_routing_type == 1:
        #  use component size to know how far to route
        x = component_to_route.xmax + dy + 1
    elif optical_routing_type == 2:
        # use optical port to know how far to route
        x = x_optical_max + dy + 1

    # Route the east ports
    # In case we have to connect these ports to a line of gratings,
    # Ensure that the port is aligned with the grating port or
    # has enough space for manhattan routing (at least two bend radius)
    start_straight_length = start_straight_length0
    for p in east_ports:
        if io_gratings_lines:
            i_grating = get_index_port_closest_to_x(x, io_gratings_lines[-1])
            x_gr = io_gratings_lines[-1][i_grating].ports[gc_port_name].x
            if abs(x - x_gr) < delta_gr_min:
                if x < x_gr:
                    x = x_gr
                elif x > x_gr:
                    x = x_gr + delta_gr_min

        tmp_port = gen_port_from_port(x, y0, p, cross_section=xs)
        route = route_with_conn_params(
            component,
            tmp_port,
            p,
            end_straight_length=start_straight_length,
        )[0]
        routes.append(route)
        ports_to_route.append(tmp_port)
        x += sep

    # Route the remaining north ports
    start_straight_length = start_straight_length0
    if len(north_finish) > 0:
        y_max = max(p.y for p in east_ports + north_finish)
        for p in north_finish:
            tmp_port = gen_port_from_port(x, y0, p, cross_section=xs)
            ports_to_route.append(tmp_port)
            route = route_with_conn_params(
                component,
                tmp_port,
                p,
                end_straight_length=start_straight_length + y_max - p.y,
            )[0]
            x += sep
            start_straight_length += sep
            routes.append(route)

    flipped_ports = [p.copy() for p in ports_to_route]
    for p in flipped_ports:
        p.trans *= gf.kdb.Trans.R180
    component.add_ports(flipped_ports)
    component.add_ports(south_ports)
    component.auto_rename_ports()
    return routes

fanout

fanout2x2

fanout2x2

fanout2x2(
    component: ComponentSpec = "mmi2x2",
    port_spacing: float = 20.0,
    bend_length: float | None = None,
    npoints: int = 101,
    select_ports: PortsFactory = select_ports_optical,
    cross_section: CrossSectionSpec = "strip",
    port1: str = "o1",
    port2: str = "o2",
    port3: str = "o3",
    port4: str = "o4",
    **kwargs: Any
) -> Component

Returns component with Sbend fanout routes.

Parameters:

Name Type Description Default
component ComponentSpec

to fanout.

'mmi2x2'
port_spacing float

for the returned component.

20.0
bend_length float | None

length of the bend (defaults to port_spacing).

None
npoints int

for sbend.

101
select_ports PortsFactory

function to select optical_ports ports.

select_ports_optical
cross_section CrossSectionSpec

cross_section spec.

'strip'
port1 str

bottom west port.

'o1'
port2 str

top west port.

'o2'
port3 str

top east port.

'o3'
port4 str

bottom east port.

'o4'
kwargs Any

cross_section settings.

{}
Example
import gdsfactory as gf
c = gf.components.nxn(west=2, east=2)

cc = gf.routing.fanout2x2(component=c, port_spacing=20)
cc.plot()
Source code in gdsfactory/routing/fanout2x2.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def fanout2x2(
    component: ComponentSpec = "mmi2x2",
    port_spacing: float = 20.0,
    bend_length: float | None = None,
    npoints: int = 101,
    select_ports: PortsFactory = select_ports_optical,
    cross_section: CrossSectionSpec = "strip",
    port1: str = "o1",
    port2: str = "o2",
    port3: str = "o3",
    port4: str = "o4",
    **kwargs: Any,
) -> Component:
    """Returns component with Sbend fanout routes.

    Args:
        component: to fanout.
        port_spacing: for the returned component.
        bend_length: length of the bend (defaults to port_spacing).
        npoints: for sbend.
        select_ports: function to select  optical_ports ports.
        cross_section: cross_section spec.
        port1: bottom west port.
        port2: top west port.
        port3: top east port.
        port4: bottom east port.
        kwargs: cross_section settings.

    Example:
        ```python
        import gdsfactory as gf
        c = gf.components.nxn(west=2, east=2)

        cc = gf.routing.fanout2x2(component=c, port_spacing=20)
        cc.plot()
        ```
    """
    c = gf.Component()

    component = gf.get_component(component)
    ref = c << component
    ref.movey(-ref.y)

    if bend_length is None:
        bend_length = port_spacing
    dx = bend_length

    y = port_spacing / 2.0

    p_w0 = ref.ports[port1]
    p_w1 = ref.ports[port2]
    p_e1 = ref.ports[port3]
    p_e0 = ref.ports[port4]

    y0 = p_e1.center[1]
    dy = y - y0

    x = gf.get_cross_section(cross_section, **kwargs)
    bend = gf.c.bend_s(size=(dx, dy), npoints=npoints, cross_section=x)

    b_tr = c << bend
    b_br = c << bend
    b_tl = c << bend
    b_bl = c << bend

    b_tr.connect(port=port1, other=p_e1)
    b_br.connect(port=port1, other=p_e0, mirror=True)
    b_tl.connect(port=port1, other=p_w1, mirror=True)
    b_bl.connect(port=port1, other=p_w0)

    c.add_port(port1, port=b_bl.ports[port2])
    c.add_port(port2, port=b_tl.ports[port2])
    c.add_port(port3, port=b_tr.ports[port2])
    c.add_port(port4, port=b_br.ports[port2])

    c.info["min_bend_radius"] = bend.info["min_bend_radius"]

    optical_ports = select_ports(ref.ports)
    optical_port_names = [port.name for port in optical_ports]
    for port in ref.ports:
        port_name = port.name
        if port_name not in optical_port_names:
            c.add_port(port_name, port=ref.ports[port_name])
    c.copy_child_info(component)
    return c

add_fiber_array

In cases where individual components have to be tested, you can generate the array of optical I/O and connect them to the component.

You can connect the waveguides to a 127um pitch fiber array or to individual fibers for input and output.

add_fiber_array

add_fiber_array(
    component: ComponentSpec = "straight",
    grating_coupler: ComponentSpecOrList = "grating_coupler_te",
    gc_port_name: str = "o1",
    select_ports: PortsFactory = select_ports_optical,
    cross_section: CrossSectionSpec = "strip",
    start_straight_length: float = 0,
    end_straight_length: float = 0,
    mirror_grating_coupler: bool = False,
    **kwargs: Any
) -> Component

Returns component with south routes and grating_couplers.

You can also use pads or other terminations instead of grating couplers.

Parameters:

Name Type Description Default
component ComponentSpec

component spec to connect to grating couplers.

'straight'
grating_coupler ComponentSpecOrList

spec for route terminations.

'grating_coupler_te'
gc_port_name str

grating coupler input port name.

'o1'
select_ports PortsFactory

function to select ports.

select_ports_optical
cross_section CrossSectionSpec

cross_section function.

'strip'
mirror_grating_coupler bool

if True, mirrors the grating coupler.

False
kwargs Any

additional arguments.

{}

Other Parameters:

Name Type Description
bend

bend spec.

straight

straight spec.

fanout_length

if None, automatic calculation of fanout length.

max_y0_optical

in um.

with_loopback

True, adds loopback structures.

with_loopback_inside

True, adds loopback structures inside the component.

straight_separation

from edge to edge.

list_port_labels

None, adds TM labels to port indices in this list.

nb_optical_ports_lines

number of grating coupler lines.

force_manhattan

False

excluded_ports

list of port names to exclude when adding gratings.

grating_indices

list of grating coupler indices.

routing_straight

function to route.

routing_method

route_bundle.

gc_rotation

fiber coupler rotation in degrees. Defaults to -90.

input_port_indexes

to connect.

pitch

in um.

radius

optional radius of the bend. Defaults to the cross_section.

radius_loopback

optional radius of the loopback bend. Defaults to the cross_section.

start_straight_length float

length of the start straight.

end_straight_length float

length of the end straight.

Example
import gdsfactory as gf

c = gf.components.crossing()
cc = gf.routing.add_fiber_array(
component=c,
grating_coupler=gf.components.grating_coupler_elliptical_te,
with_loopback=False
)
cc.plot()
Source code in gdsfactory/routing/add_fiber_array.py
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def add_fiber_array(
    component: ComponentSpec = "straight",
    grating_coupler: ComponentSpecOrList = "grating_coupler_te",
    gc_port_name: str = "o1",
    select_ports: PortsFactory = select_ports_optical,
    cross_section: CrossSectionSpec = "strip",
    start_straight_length: float = 0,
    end_straight_length: float = 0,
    mirror_grating_coupler: bool = False,
    **kwargs: Any,
) -> Component:
    """Returns component with south routes and grating_couplers.

    You can also use pads or other terminations instead of grating couplers.

    Args:
        component: component spec to connect to grating couplers.
        grating_coupler: spec for route terminations.
        gc_port_name: grating coupler input port name.
        select_ports: function to select ports.
        cross_section: cross_section function.
        mirror_grating_coupler: if True, mirrors the grating coupler.
        kwargs: additional arguments.

    Keyword Args:
        bend: bend spec.
        straight: straight spec.
        fanout_length: if None, automatic calculation of fanout length.
        max_y0_optical: in um.
        with_loopback: True, adds loopback structures.
        with_loopback_inside: True, adds loopback structures inside the component.
        straight_separation: from edge to edge.
        list_port_labels: None, adds TM labels to port indices in this list.
        nb_optical_ports_lines: number of grating coupler lines.
        force_manhattan: False
        excluded_ports: list of port names to exclude when adding gratings.
        grating_indices: list of grating coupler indices.
        routing_straight: function to route.
        routing_method: route_bundle.
        gc_rotation: fiber coupler rotation in degrees. Defaults to -90.
        input_port_indexes: to connect.
        pitch: in um.
        radius: optional radius of the bend. Defaults to the cross_section.
        radius_loopback: optional radius of the loopback bend. Defaults to the cross_section.
        start_straight_length: length of the start straight.
        end_straight_length: length of the end straight.

    Example:
        ```python
        import gdsfactory as gf

        c = gf.components.crossing()
        cc = gf.routing.add_fiber_array(
        component=c,
        grating_coupler=gf.components.grating_coupler_elliptical_te,
        with_loopback=False
        )
        cc.plot()
        ```
    """
    component = gf.get_component(component)

    if isinstance(grating_coupler, list):
        gc = grating_coupler[0]
    else:
        gc = grating_coupler
    gc = gf.get_component(gc)
    if mirror_grating_coupler:
        gc = gf.functions.mirror(gc)

    gc_port_names = [port.name for port in gc.ports]
    if gc_port_name not in gc_port_names:
        raise ValueError(f"gc_port_name = {gc_port_name!r} not in {gc_port_names}")

    orientation = gc.ports[gc_port_name].orientation

    grating_coupler = (
        [gf.get_component(i) for i in cast(list[Any], grating_coupler)]
        if isinstance(grating_coupler, list)
        else gf.get_component(grating_coupler)
    )

    if int(orientation) != 180:
        raise ValueError(
            "add_fiber_array requires a grating coupler port facing west "
            f"(orientation = 180). "
            f"Got orientation = {orientation} degrees for port {gc_port_name!r}"
        )

    if gc_port_name not in gc.ports:
        raise ValueError(f"gc_port_name={gc_port_name!r} not in {list(gc.ports)}")

    component_new = Component()

    optical_ports = select_ports(component.ports)
    if not optical_ports:
        raise ValueError(f"No optical ports found in {component.name!r}")

    ref = component_new.add_ref(component)
    route_fiber_array(
        component_new,
        ref,
        grating_coupler=grating_coupler,
        gc_port_name=gc_port_name,
        cross_section=cross_section,
        select_ports=select_ports,
        start_straight_length=start_straight_length,
        end_straight_length=end_straight_length,
        **kwargs,
    )

    component_new.copy_child_info(component)
    return component_new

add_pads

add_pads_top

add_pads_top(
    component: ComponentSpec = "straight_heater_metal",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
    pad_port_name: str = "e1",
    pad: ComponentSpec = "pad_rectangular",
    bend: ComponentSpec = "wire_corner",
    straight_separation: float = 15.0,
    pad_pitch: float = 100.0,
    port_type: str = "electrical",
    allow_width_mismatch: bool = True,
    fanout_length: float | None = None,
    route_width: float | None = 0,
    bboxes: BoundingBoxes | None = None,
    avoid_component_bbox: bool = False,
    auto_taper: bool = True,
    **kwargs: Any
) -> Component

Returns new component with ports connected top pads.

Parameters:

Name Type Description Default
component ComponentSpec

component spec to connect to.

'straight_heater_metal'
select_ports SelectPorts

function to select_ports.

select_ports_electrical
port_names Strs | None

optional port names. Overrides select_ports.

None
cross_section CrossSectionSpec

cross_section spec.

'metal_routing'
get_input_labels_function

function to get input labels. None skips labels.

required
layer_label

optional layer for grating coupler label.

required
pad_port_name str

pad input port name.

'e1'
pad_port_labels

pad list of labels.

required
pad ComponentSpec

spec for route terminations.

'pad_rectangular'
bend ComponentSpec

bend spec.

'wire_corner'
straight_separation float

from wire edge to edge. Defaults to xs.width+xs.gap

15.0
pad_pitch float

in um. Defaults to pad_pitch constant from the PDK.

100.0
port_type str

port type.

'electrical'
allow_width_mismatch bool

True

True
fanout_length float | None

if None, automatic calculation of fanout length.

None
route_width float | None

width of the route. If None, defaults to cross_section.width.

0
bboxes BoundingBoxes | None

list of bounding boxes to avoid.

None
avoid_component_bbox bool

True

False
auto_taper bool

adds tapers to the routing.

True
kwargs Any

additional arguments.

{}

Other Parameters:

Name Type Description
straight

straight spec.

get_input_label_text_loopback_function

function to get input label test.

get_input_label_text_function

for labels.

max_y0_optical

in um.

with_loopback

True, adds loopback structures.

list_port_labels

None, adds TM labels to port indices in this list.

connected_port_list_ids

names of ports only for type 0 optical routing.

nb_optical_ports_lines

number of grating coupler lines.

force_manhattan

False

excluded_ports

list of port names to exclude when adding gratings.

grating_indices

list of grating coupler indices.

routing_straight

function to route.

routing_method

route_bundle.

gc_rotation

fiber coupler rotation in degrees. Defaults to -90.

input_port_indexes

to connect.

allow_width_mismatch bool

True

Example
import gdsfactory as gf
c = gf.c.nxn(
xsize=600,
ysize=200,
north=2,
south=3,
wg_width=10,
layer="M3",
port_type="electrical",
)
cc = gf.routing.add_pads_top(component=c, port_names=("e1", "e4"), fanout_length=50)
cc.plot()
Source code in gdsfactory/routing/add_pads.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def add_pads_top(
    component: ComponentSpec = "straight_heater_metal",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
    pad_port_name: str = "e1",
    pad: ComponentSpec = "pad_rectangular",
    bend: ComponentSpec = "wire_corner",
    straight_separation: float = 15.0,
    pad_pitch: float = 100.0,
    port_type: str = "electrical",
    allow_width_mismatch: bool = True,
    fanout_length: float | None = None,
    route_width: float | None = 0,
    bboxes: BoundingBoxes | None = None,
    avoid_component_bbox: bool = False,
    auto_taper: bool = True,
    **kwargs: Any,
) -> Component:
    """Returns new component with ports connected top pads.

    Args:
        component: component spec to connect to.
        select_ports: function to select_ports.
        port_names: optional port names. Overrides select_ports.
        cross_section: cross_section spec.
        get_input_labels_function: function to get input labels. None skips labels.
        layer_label: optional layer for grating coupler label.
        pad_port_name: pad input port name.
        pad_port_labels: pad list of labels.
        pad: spec for route terminations.
        bend: bend spec.
        straight_separation: from wire edge to edge. Defaults to xs.width+xs.gap
        pad_pitch: in um. Defaults to pad_pitch constant from the PDK.
        port_type: port type.
        allow_width_mismatch: True
        fanout_length: if None, automatic calculation of fanout length.
        route_width: width of the route. If None, defaults to cross_section.width.
        bboxes: list of bounding boxes to avoid.
        avoid_component_bbox: True
        auto_taper: adds tapers to the routing.
        kwargs: additional arguments.

    Keyword Args:
        straight: straight spec.
        get_input_label_text_loopback_function: function to get input label test.
        get_input_label_text_function: for labels.
        max_y0_optical: in um.
        with_loopback: True, adds loopback structures.
        list_port_labels: None, adds TM labels to port indices in this list.
        connected_port_list_ids: names of ports only for type 0 optical routing.
        nb_optical_ports_lines: number of grating coupler lines.
        force_manhattan: False
        excluded_ports: list of port names to exclude when adding gratings.
        grating_indices: list of grating coupler indices.
        routing_straight: function to route.
        routing_method: route_bundle.
        gc_rotation: fiber coupler rotation in degrees. Defaults to -90.
        input_port_indexes: to connect.
        allow_width_mismatch: True

    Example:
        ```python
        import gdsfactory as gf
        c = gf.c.nxn(
        xsize=600,
        ysize=200,
        north=2,
        south=3,
        wg_width=10,
        layer="M3",
        port_type="electrical",
        )
        cc = gf.routing.add_pads_top(component=c, port_names=("e1", "e4"), fanout_length=50)
        cc.plot()
        ```
    """
    c = Component()
    _c = add_pads_bot(
        component=component,
        select_ports=select_ports,
        port_names=port_names,
        cross_section=cross_section,
        pad_port_name=pad_port_name,
        pad=pad,
        bend=bend,
        straight_separation=straight_separation,
        pad_pitch=pad_pitch,
        port_type=port_type,
        allow_width_mismatch=allow_width_mismatch,
        fanout_length=fanout_length,
        route_width=route_width,
        bboxes=bboxes,
        avoid_component_bbox=avoid_component_bbox,
        auto_taper=auto_taper,
        **kwargs,
    )
    ref = c << _c
    ref.mirror_y()
    c.add_ports(ref.ports)
    c.copy_child_info(_c)
    return c

add_pads_bot

add_pads_bot(
    component: ComponentSpec = "straight_heater_metal",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
    pad_port_name: str = "e1",
    pad: ComponentSpec = "pad_rectangular",
    bend: ComponentSpec = "wire_corner",
    straight_separation: float = 15.0,
    pad_pitch: float = 100.0,
    port_type: str = "electrical",
    allow_width_mismatch: bool = True,
    fanout_length: float | None = None,
    route_width: float | None = None,
    bboxes: BoundingBoxes | None = None,
    avoid_component_bbox: bool = False,
    auto_taper: bool = True,
    **kwargs: Any
) -> Component

Returns new component with ports connected bottom pads.

Parameters:

Name Type Description Default
component ComponentSpec

component spec to connect to.

'straight_heater_metal'
select_ports SelectPorts

function to select_ports.

select_ports_electrical
port_names Strs | None

optional port names. Overrides select_ports.

None
cross_section CrossSectionSpec

cross_section spec.

'metal_routing'
get_input_labels_function

function to get input labels. None skips labels.

required
layer_label

optional layer for grating coupler label.

required
pad_port_name str

pad input port name.

'e1'
pad_port_labels

pad list of labels.

required
pad ComponentSpec

spec for route terminations.

'pad_rectangular'
bend ComponentSpec

bend spec.

'wire_corner'
straight_separation float

from wire edge to edge. Defaults to xs.width+xs.gap

15.0
pad_pitch float

in um. Defaults to pad_pitch constant from the PDK.

100.0
port_type str

port type.

'electrical'
allow_width_mismatch bool

True

True
fanout_length float | None

if None, automatic calculation of fanout length.

None
route_width float | None

width of the route. If None, defaults to cross_section.width.

None
bboxes BoundingBoxes | None

list bounding boxes to avoid for routing.

None
avoid_component_bbox bool

avoid component bbox for routing.

False
auto_taper bool

adds tapers to the routing.

True
kwargs Any

additional arguments.

{}

Other Parameters:

Name Type Description
straight

straight spec.

get_input_label_text_loopback_function

function to get input label test.

get_input_label_text_function

for labels.

max_y0_optical

in um.

with_loopback

True, adds loopback structures.

list_port_labels

None, adds TM labels to port indices in this list.

connected_port_list_ids

names of ports only for type 0 optical routing.

nb_optical_ports_lines

number of grating coupler lines.

force_manhattan

False

excluded_ports

list of port names to exclude when adding gratings.

grating_indices

list of grating coupler indices.

routing_straight

function to route.

routing_method

route_bundle.

gc_rotation

fiber coupler rotation in degrees. Defaults to -90.

input_port_indexes

to connect.

allow_width_mismatch bool

True

Example
import gdsfactory as gf
c = gf.c.nxn(
xsize=600,
ysize=200,
north=2,
south=3,
wg_width=10,
layer="M3",
port_type="electrical",
)
cc = gf.routing.add_pads_bot(component=c, port_names=("e1", "e4"), fanout_length=50)
cc.plot()
Source code in gdsfactory/routing/add_pads.py
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def add_pads_bot(
    component: ComponentSpec = "straight_heater_metal",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
    pad_port_name: str = "e1",
    pad: ComponentSpec = "pad_rectangular",
    bend: ComponentSpec = "wire_corner",
    straight_separation: float = 15.0,
    pad_pitch: float = 100.0,
    port_type: str = "electrical",
    allow_width_mismatch: bool = True,
    fanout_length: float | None = None,
    route_width: float | None = None,
    bboxes: BoundingBoxes | None = None,
    avoid_component_bbox: bool = False,
    auto_taper: bool = True,
    **kwargs: Any,
) -> Component:
    """Returns new component with ports connected bottom pads.

    Args:
        component: component spec to connect to.
        select_ports: function to select_ports.
        port_names: optional port names. Overrides select_ports.
        cross_section: cross_section spec.
        get_input_labels_function: function to get input labels. None skips labels.
        layer_label: optional layer for grating coupler label.
        pad_port_name: pad input port name.
        pad_port_labels: pad list of labels.
        pad: spec for route terminations.
        bend: bend spec.
        straight_separation: from wire edge to edge. Defaults to xs.width+xs.gap
        pad_pitch: in um. Defaults to pad_pitch constant from the PDK.
        port_type: port type.
        allow_width_mismatch: True
        fanout_length: if None, automatic calculation of fanout length.
        route_width: width of the route. If None, defaults to cross_section.width.
        bboxes: list bounding boxes to avoid for routing.
        avoid_component_bbox: avoid component bbox for routing.
        auto_taper: adds tapers to the routing.
        kwargs: additional arguments.

    Keyword Args:
        straight: straight spec.
        get_input_label_text_loopback_function: function to get input label test.
        get_input_label_text_function: for labels.
        max_y0_optical: in um.
        with_loopback: True, adds loopback structures.
        list_port_labels: None, adds TM labels to port indices in this list.
        connected_port_list_ids: names of ports only for type 0 optical routing.
        nb_optical_ports_lines: number of grating coupler lines.
        force_manhattan: False
        excluded_ports: list of port names to exclude when adding gratings.
        grating_indices: list of grating coupler indices.
        routing_straight: function to route.
        routing_method: route_bundle.
        gc_rotation: fiber coupler rotation in degrees. Defaults to -90.
        input_port_indexes: to connect.
        allow_width_mismatch: True

    Example:
        ```python
        import gdsfactory as gf
        c = gf.c.nxn(
        xsize=600,
        ysize=200,
        north=2,
        south=3,
        wg_width=10,
        layer="M3",
        port_type="electrical",
        )
        cc = gf.routing.add_pads_bot(component=c, port_names=("e1", "e4"), fanout_length=50)
        cc.plot()
        ```
    """
    component_new = Component()
    component = gf.get_component(component)

    cref = component_new << component
    ports = [cref[port_name] for port_name in port_names] if port_names else None
    ports_list: Sequence[Port] = ports or select_ports(cref.ports)

    pad_component = gf.get_component(pad)

    # Detect Pin-based pads
    pad_has_pins = len(pad_component.pins) > 0

    if pad_has_pins:
        # Pin-based pad: skip orientation assertion, routing will resolve directions
        pass
    else:
        if pad_port_name not in pad_component.ports:
            pad_ports = list(pad_component.ports)
            raise ValueError(
                f"pad_port_name = {pad_port_name!r} not in {pad_component.name!r} ports {pad_ports}"
            )

        pad_orientation = int(pad_component[pad_port_name].orientation)
        if pad_orientation != 180:
            raise ValueError(
                f"port.orientation={pad_orientation} for port {pad_port_name!r} needs to be 180 degrees."
            )

    if not ports_list:
        raise ValueError(
            f"select_ports or port_names did not match any ports in {list(component.ports)}"
        )

    route_fiber_array(
        component_new,
        component,
        grating_coupler=pad,
        gc_port_name=pad_port_name,
        cross_section=cross_section,
        select_ports=select_ports,
        with_loopback=False,
        bend=bend,
        straight_separation=straight_separation,
        port_names=port_names,
        pitch=pad_pitch,
        port_type=port_type,
        gc_port_name_fiber=pad_port_name,
        allow_width_mismatch=allow_width_mismatch,
        fanout_length=fanout_length,
        route_width=route_width,
        bboxes=bboxes,
        avoid_component_bbox=avoid_component_bbox,
        auto_taper=auto_taper,
        **kwargs,
    )
    component_new.add_ref(component)
    component_new.copy_child_info(component)
    return component_new

add_electrical_pads_shortest

add_electrical_pads_shortest

add_electrical_pads_shortest(
    component: ComponentSpec = "wire_straight",
    pad: ComponentSpec = "pad",
    pad_port_spacing: float = 50.0,
    pad_size: Size | None = None,
    select_ports: PortsFactory = select_ports_electrical,
    port_names: Strs | None = None,
    port_orientation: AngleInDegrees = 90,
    layer: LayerSpec = "M3",
) -> Component

Returns new Component with a pad by each electrical port.

Parameters:

Name Type Description Default
component ComponentSpec

to route.

'wire_straight'
pad ComponentSpec

pad element or function.

'pad'
pad_port_spacing float

spacing between pad and port.

50.0
pad_size Size | None

pad size.

None
select_ports PortsFactory

function to select ports.

select_ports_electrical
port_names Strs | None

optional port names. Overrides select_ports.

None
port_orientation AngleInDegrees

in degrees.

90
layer LayerSpec

for the routing.

'M3'
Example
import gdsfactory as gf
c = gf.components.cross(length=100, layer=(49, 0), port_type="electrical")
c = gf.routing.add_electrical_pads_shortest(c, pad_port_spacing=200)
c.plot()
Source code in gdsfactory/routing/add_electrical_pads_shortest.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def add_electrical_pads_shortest(
    component: ComponentSpec = "wire_straight",
    pad: ComponentSpec = "pad",
    pad_port_spacing: float = 50.0,
    pad_size: Size | None = None,
    select_ports: PortsFactory = select_ports_electrical,
    port_names: Strs | None = None,
    port_orientation: AngleInDegrees = 90,
    layer: LayerSpec = "M3",
) -> Component:
    """Returns new Component with a pad by each electrical port.

    Args:
        component: to route.
        pad: pad element or function.
        pad_port_spacing: spacing between pad and port.
        pad_size: pad size.
        select_ports: function to select ports.
        port_names: optional port names. Overrides select_ports.
        port_orientation: in degrees.
        layer: for the routing.

    Example:
        ```python
        import gdsfactory as gf
        c = gf.components.cross(length=100, layer=(49, 0), port_type="electrical")
        c = gf.routing.add_electrical_pads_shortest(c, pad_port_spacing=200)
        c.plot()
        ```
    """
    c = Component()
    component = gf.get_component(component)

    if pad_size is not None:
        pad = gf.get_component(pad, size=pad_size)
    else:
        pad = gf.get_component(pad)

    ref = c << component
    ports = (
        [ref[port_name] for port_name in port_names]
        if port_names
        else select_ports(ref.ports)
    )

    for i, port in enumerate(ports):
        p = c << pad
        port_orientation = port.orientation

        if port_orientation == 0:
            p.x = port.x + pad_port_spacing
            p.y = port.y
            route_quad(component=c, port1=port, port2=p.ports["e1"], layer=layer)
        elif port_orientation == 180:
            p.x = port.x - pad_port_spacing
            p.y = port.y
            route_quad(c, port, p.ports["e3"], layer=layer)
        elif port_orientation == 90:
            p.y = port.y + pad_port_spacing
            p.x = port.x
            route_quad(c, port, p.ports["e4"], layer=layer)
        elif port_orientation == 270:
            p.y = port.y - pad_port_spacing
            p.x = port.x
            route_quad(c, port, p.ports["e2"], layer=layer)

        c.add_port(port=p.ports["pad"], name=f"elec-{component.name}-{i + 1}")

    c.add_ports(ref.ports)

    c.copy_child_info(component)
    return c

add_electrical_pads_top

add_electrical_pads_top

add_electrical_pads_top(
    component: ComponentSpec,
    direction: Literal["top", "right"] = "top",
    spacing: Float2 = (0.0, 100.0),
    pad_array: ComponentSpec = "pad_array",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    layer: LayerSpec = "MTOP",
    **kwargs: Any
) -> Component

Returns new component with electrical ports connected to top pad array.

Parameters:

Name Type Description Default
component ComponentSpec

to route.

required
direction Literal['top', 'right']

sets direction of the array (top or right).

'top'
spacing Float2

component to pad spacing.

(0.0, 100.0)
pad_array ComponentSpec

function for pad_array.

'pad_array'
select_ports SelectPorts

function to select electrical ports.

select_ports_electrical
port_names Strs | None

optional port names. Overrides select_ports.

None
layer LayerSpec

for the routes.

'MTOP'
kwargs Any

additional arguments.

{}

Other Parameters:

Name Type Description
ports

Dict[str, Port] a port dict {port name: port}.

prefix

select ports with port name prefix.

suffix

select ports with port name suffix.

orientation

select ports with orientation in degrees.

width

select ports with port width.

layers_excluded

List of layers to exclude.

port_type

select ports with port type (optical, electrical, vertical_te).

clockwise

if True, sort ports clockwise, False: counter-clockwise.

Example
import gdsfactory as gf

c = gf.components.wire_straight(length=200.)
cc = gf.routing.add_electrical_pads_top(component=c, spacing=(-150, 30))
cc.plot()
Source code in gdsfactory/routing/add_electrical_pads_top.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def add_electrical_pads_top(
    component: ComponentSpec,
    direction: Literal["top", "right"] = "top",
    spacing: Float2 = (0.0, 100.0),
    pad_array: ComponentSpec = "pad_array",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    layer: LayerSpec = "MTOP",
    **kwargs: Any,
) -> Component:
    """Returns new component with electrical ports connected to top pad array.

    Args:
        component: to route.
        direction: sets direction of the array (top or right).
        spacing: component to pad spacing.
        pad_array: function for pad_array.
        select_ports: function to select electrical ports.
        port_names: optional port names. Overrides select_ports.
        layer: for the routes.
        kwargs: additional arguments.

    Keyword Args:
        ports: Dict[str, Port] a port dict {port name: port}.
        prefix: select ports with port name prefix.
        suffix: select ports with port name suffix.
        orientation: select ports with orientation in degrees.
        width: select ports with port width.
        layers_excluded: List of layers to exclude.
        port_type: select ports with port type (optical, electrical, vertical_te).
        clockwise: if True, sort ports clockwise, False: counter-clockwise.


    Example:
        ```python
        import gdsfactory as gf

        c = gf.components.wire_straight(length=200.)
        cc = gf.routing.add_electrical_pads_top(component=c, spacing=(-150, 30))
        cc.plot()
        ```
    """
    c = Component()
    component = gf.get_component(component)
    ref = c << component

    ports = [ref[port_name] for port_name in port_names] if port_names else None
    ports_electrical = ports or select_ports(ref.ports, **kwargs)

    if not ports_electrical:
        raise ValueError("No electrical ports found")

    if direction == "top":
        pads = c << gf.get_component(
            pad_array, columns=len(ports_electrical), rows=1, port_orientation=270
        )
    elif direction == "right":
        pads = c << gf.get_component(
            pad_array, columns=1, rows=len(ports_electrical), orientation=270
        )
    else:
        raise ValueError(f"Invalid direction {direction}")

    pads.x = ref.x + spacing[0]
    pads.ymin = ref.ymax + spacing[1]

    ports_pads = sort_ports_x(pads.ports)
    ports_component = sort_ports_x(ports_electrical)

    for p1, p2 in zip(ports_component, ports_pads, strict=False):
        route_quad(c, p1, p2, layer=layer)

    for port in ref.ports:
        if port.port_type != "electrical":
            c.add_port(name=port.name, port=port)

    c.add_ports(pads.ports)
    c.copy_child_info(component)
    c.auto_rename_ports()
    return c

add_electrical_pads_top_dc

add_electrical_pads_top_dc

add_electrical_pads_top_dc(
    component: ComponentSpec,
    spacing: Float2 = (0.0, 100.0),
    pad_array: ComponentSpec = "pad_array270",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
    **kwargs: Any
) -> Component

Returns new component with electrical ports connected to top pad array.

Parameters:

Name Type Description Default
component ComponentSpec

component spec to connect to.

required
spacing Float2

component to pad spacing.

(0.0, 100.0)
pad_array ComponentSpec

component factor for pad_array

'pad_array270'
select_ports SelectPorts

function to select_ports.

select_ports_electrical
route_bundle_function

function to route bundle of ports.

required
port_names Strs | None

optional port names. Overrides select_ports.

None
cross_section CrossSectionSpec

cross_section for the route.

'metal_routing'
kwargs Any

route settings.

{}
Example
import gdsfactory as gf
c = gf.components.wire_straight(length=200.)
c = gf.routing.add_electrical_pads_top_dc(c, width=10)
c.plot()
Source code in gdsfactory/routing/add_electrical_pads_top_dc.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def add_electrical_pads_top_dc(
    component: ComponentSpec,
    spacing: Float2 = (0.0, 100.0),
    pad_array: ComponentSpec = "pad_array270",
    select_ports: SelectPorts = select_ports_electrical,
    port_names: Strs | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
    **kwargs: Any,
) -> Component:
    """Returns new component with electrical ports connected to top pad array.

    Args:
        component: component spec to connect to.
        spacing: component to pad spacing.
        pad_array: component factor for pad_array
        select_ports: function to select_ports.
        route_bundle_function: function to route bundle of ports.
        port_names: optional port names. Overrides select_ports.
        cross_section: cross_section for the route.
        kwargs: route settings.

    Example:
        ```python
        import gdsfactory as gf
        c = gf.components.wire_straight(length=200.)
        c = gf.routing.add_electrical_pads_top_dc(c, width=10)
        c.plot()
        ```
    """
    c = Component()
    component = gf.get_component(component)

    cref = c << component
    ports = (
        [cref[port_name] for port_name in port_names]
        if port_names
        else select_ports(cref.ports)
    )

    if not ports:
        raise ValueError(
            f"select_ports or port_names did not match any ports in "
            f"{[port.name for port in component.ports]}"
        )

    ports_component = [port.copy() for port in ports]

    for port in ports_component:
        port.orientation = 90

    pad_array_component = gf.get_component(pad_array, columns=len(ports))
    pads = c << pad_array_component
    pads.x = cref.x + spacing[0]
    pads.ymin = cref.ymax + spacing[1]

    ports_pads = pads.ports.filter(orientation=270)
    ports_component = sort_ports_x(ports_component)
    ports_pads = sort_ports_x(ports_pads)

    route_bundle_electrical(
        c, ports_component, ports_pads, cross_section=cross_section, **kwargs
    )

    for port in cref.ports:
        if port not in ports_component:
            c.add_port(name=port.name, port=port)

    for i, port_pad in enumerate(ports_pads):
        c.add_port(port=port_pad, name=f"elec-{component.name}-{i}")
    c.copy_child_info(component)
    return c