Skip to content

Optical

optical

Optical routing allows the creation of photonic (or any route using bends).

OpticalAllAngleRoute pydantic-model

Bases: BaseModel

Optical route containing a connection between two ports.

Fields:

  • backbone (list[DPoint])
  • start_port (Port)
  • end_port (Port)
  • instances (list[VInstance])
  • length (float)
  • length_straights (float)
Source code in kfactory/routing/aa/optical.py
19
20
21
22
23
24
25
26
27
class OpticalAllAngleRoute(BaseModel, arbitrary_types_allowed=True):
    """Optical route containing a connection between two ports."""

    backbone: list[kdb.DPoint]
    start_port: Port
    end_port: Port
    instances: list[VInstance]
    length: float = 0
    length_straights: float = 0

backbone2bundle

backbone2bundle(
    backbone: Sequence[DPoint],
    port_widths: list[float],
    spacings: list[float],
) -> list[list[kdb.DPoint]]

Used to extract a bundle from a backbone.

Source code in kfactory/routing/aa/optical.py
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
def backbone2bundle(
    backbone: Sequence[kdb.DPoint],
    port_widths: list[float],
    spacings: list[float],
) -> list[list[kdb.DPoint]]:
    """Used to extract a bundle from a backbone."""
    pts: list[list[kdb.DPoint]] = []

    edges: list[kdb.DEdge] = []
    p1 = backbone[0]

    for p2 in backbone[1:]:
        edges.append(kdb.DEdge(p1, p2))
        p1 = p2

    width = sum(port_widths) + sum(spacings)

    x = -width // 2

    for pw, spacing in zip(port_widths, spacings, strict=False):
        x += pw // 2 + spacing // 2

        _e1 = edges[0].shifted(-x)
        _pts = [_e1.p1]

        for e in edges[1:]:
            _e2 = e.shifted(-x)
            _pts.append(_e2.cut_point(_e1))
            _e1 = _e2
        _pts.append(_e1.p2)

        x += spacing - spacing // 2 + pw - pw // 2
        pts.append(_pts)

    return pts

route

route(
    c: VKCell | KCell | DKCell,
    width: float,
    backbone: Sequence[DPoint],
    straight_factory: VirtualStraightFactory,
    bend_factory: VirtualBendFactory,
    bend_ports: tuple[str, str] = ("o1", "o2"),
    straight_ports: tuple[str, str] = ("o1", "o2"),
    tolerance: float = 0.1,
    angle_tolerance: float = 0.0001,
) -> OpticalAllAngleRoute

Places an all-angle route.

Parameters:

Name Type Description Default
c VKCell | KCell | DKCell

The virtual or real KCell to place the route in.

required
width float

width of the rotue (passed to straight_factory and bend_factory). The layer the route ports will be extracted from bend port layers.

required
backbone Sequence[DPoint]

The points of the route.

required
straight_factory VirtualStraightFactory

Function to create straights from length and width. [um]

required
bend_factory VirtualBendFactory

Function to create bends from length and width. [um]

required
bend_ports tuple[str, str]

Names of the ports of the bend to use for connecting straights and bends.

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

Names of the ports of the straight to use for connecting straights and bends.

('o1', 'o2')
tolerance float

Allow for a small tolerance when placing bends and straights. If the distance is below this tolerance, the route will be placed.

0.1
angle_tolerance float

If a resulting bend from a point in the backbone would have an angle below this tolerance, the point will be skipped and a straight between the point before and the following will be created.

0.0001
Source code in kfactory/routing/aa/optical.py
 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
def route(
    c: VKCell | KCell | DKCell,
    width: float,
    backbone: Sequence[kdb.DPoint],
    straight_factory: VirtualStraightFactory,
    bend_factory: VirtualBendFactory,
    bend_ports: tuple[str, str] = ("o1", "o2"),
    straight_ports: tuple[str, str] = ("o1", "o2"),
    tolerance: float = 0.1,
    angle_tolerance: float = 0.0001,
) -> OpticalAllAngleRoute:
    """Places an all-angle route.

    Args:
        c: The virtual or real KCell to place the route in.
        width: width of the rotue (passed to straight_factory and bend_factory).
            The layer the route ports will be extracted from bend port layers.
        backbone: The points of the route.
        straight_factory: Function to create straights from length and width.
            [um]
        bend_factory: Function to  create bends from length and width. [um]
        bend_ports: Names of the ports of the bend to use for connecting
            straights and bends.
        straight_ports: Names of the ports of the straight to use for connecting
            straights and bends.
        tolerance: Allow for a small tolerance when placing bends and straights. If
            the distance is below this tolerance, the route will be placed.
        angle_tolerance: If a resulting bend from a point in the backbone would have an
            angle below this tolerance, the point will be skipped and a straight between
            the point before and the following will be created.
    """
    if len(backbone) < MIN_ALL_ANGLE_ROUTES_POINTS:
        raise ValueError("All angle routes with less than 3 points are not supported.")

    bends: dict[float, VKCell] = {90: bend_factory(width=width, angle=90)}
    layer = bends[90].ports[bend_ports[0]].layer

    _p0 = kdb.DPoint(0, 0)
    _p1 = kdb.DPoint(1, 0)

    start_v = backbone[1] - backbone[0]
    end_v = backbone[-1] - backbone[-2]
    start_angle = np.rad2deg(np.arctan2(start_v.y, start_v.x))
    end_angle = (np.rad2deg(np.arctan2(end_v.y, end_v.x)) + 180) % 360

    start_port = Port(
        name="o1",
        width=c.kcl.to_dbu(width),
        layer=layer,
        dcplx_trans=kdb.DCplxTrans(1, start_angle, False, backbone[0].to_v()),
        kcl=c.kcl,
    )
    end_port = Port(
        name="o1",
        width=c.kcl.to_dbu(width),
        layer=layer,
        dcplx_trans=kdb.DCplxTrans(1, end_angle, False, backbone[-1].to_v()),
        kcl=c.kcl,
    )

    old_pt = backbone[0]
    pt = backbone[1]
    start_offset = 0.0
    _port = start_port
    insts: list[VInstance] = []

    length = (pt - old_pt).abs()
    length_straights: float = 0

    for new_pt in backbone[2:]:
        # Calculate (4 quadrant) angle between the three points
        s_v = pt - old_pt
        e_v = new_pt - pt
        length += e_v.abs()
        s_a = _angle(s_v)
        e_a = _angle(e_v)
        _a = (e_a - s_a + 180) % 360 - 180

        if abs(_a) >= angle_tolerance:
            # create a virtual bend with the angle if non-existent
            if _a not in bends:
                bends[_a] = bend_factory(width=width, angle=abs(_a))
            bend = bends[_a]

            p1, p2 = (bend.ports[_p] for _p in bend_ports)

            # get the center of the bend
            # the center must be on the crossing point between the two

            # from this the effective radius can be calculated (the bend must be
            # symmetric so each lengths needs 1*eff_radius)
            effective_radius = _get_effective_radius(p1, p2, _p1=_p0, _p2=_p1)
            # if the resulting straight is < old_eff_radius + new_eff_radius
            # the route is invalid
            if (pt - old_pt).length() - effective_radius - start_offset < -(
                c.kcl.dbu * tolerance
            ):
                raise ValueError(
                    f"Not enough space to place bends at points {[old_pt, pt]}."
                    f"Needed space={start_offset + effective_radius}, available "
                    f"space={(pt - old_pt).length()}"
                )
        else:
            effective_radius = 0
            _a = 0

        # calculate and place the resulting straight if != 0
        _l = (pt - old_pt).length() - effective_radius - start_offset
        if _l > 0:
            s = c.create_vinst(straight_factory(width=width, length=_l))
            length_straights += _l
            s.connect(straight_ports[0], _port)
            _port = Port(base=s.ports[straight_ports[1]].base)
            insts.append(s)
        if _a != 0:
            # after the straight place the bend
            b = c.create_vinst(bend)
            if _a < 0:
                b.connect(bend_ports[1], _port)
                _port = Port(base=b.ports[bend_ports[0]].base)
            else:
                b.connect(bend_ports[0], _port)
                _port = Port(base=b.ports[bend_ports[1]].base)
            insts.append(b)
        start_offset = effective_radius
        old_pt = pt
        pt = new_pt
    # place last straight
    _l = (pt - old_pt).length() - effective_radius
    # if the resulting straight is < old_eff_radius + new_eff_radius
    # the route is invalid
    if _l < -(c.kcl.dbu * tolerance):
        raise ValueError(
            f"Not enough space to place bends at points {[old_pt, pt]}."
            f"Needed space={effective_radius}, available "
            f"space={(pt - old_pt).length()}"
        )
    if _l > 0:
        s = c.create_vinst(straight_factory(width=width, length=_l))
        length_straights += _l
        s.connect(straight_ports[0], _port)
        _port = Port(base=s.ports[straight_ports[1]].base)
        insts.append(s)

    return OpticalAllAngleRoute(
        backbone=list(backbone),
        start_port=start_port,
        end_port=end_port,
        instances=insts,
        length=length,
        length_straights=length_straights,
    )

route_bundle

route_bundle(
    c: KCell | DKCell | VKCell,
    start_ports: Sequence[DPort | Port],
    end_ports: Sequence[DPort | Port],
    backbone: Sequence[DPoint],
    separation: float | list[float],
    straight_factory: VirtualStraightFactory,
    bend_factory: VirtualBendFactory,
    bend_ports: tuple[str, str] = ("o1", "o2"),
    straight_ports: tuple[str, str] = ("o1", "o2"),
) -> list[OpticalAllAngleRoute]

Places all-angle routes.

Parameters:

Name Type Description Default
c KCell | DKCell | VKCell

The virtual or real KCell to place the route in.

required
start_ports Sequence[DPort | Port]

Ports denoting the beginning of each route. Must be sorted in anti-clockwise orientation with regards to the desired bundle order.

required
end_ports Sequence[DPort | Port]

Ports denoting the end of each route. Must be sorted in clockwise orientation with regards to the desired bundle order.

required
backbone Sequence[DPoint]

The points of the route. The router will route to the first point and then create a bundle which follows this points as a backbone. Bends leading the first or following the last backbone point are guaranteed to be outside the backbone.

required
separation float | list[float]

Minimal separation between each piece of the bundle. This is only guaranteed from the backbone start to backbone end.

required
straight_factory VirtualStraightFactory

Function to create straights from length and width. [um]

required
bend_factory VirtualBendFactory

Function to create bends from length and width. [um]

required
bend_ports tuple[str, str]

Names of the ports of the bend to use for connecting straights and bends.

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

Names of the ports of the straight to use for connecting straights and bends.

('o1', 'o2')
Source code in kfactory/routing/aa/optical.py
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
def route_bundle(
    c: KCell | DKCell | VKCell,
    start_ports: Sequence[DPort | Port],
    end_ports: Sequence[DPort | Port],
    backbone: Sequence[kdb.DPoint],
    separation: float | list[float],
    straight_factory: VirtualStraightFactory,
    bend_factory: VirtualBendFactory,
    bend_ports: tuple[str, str] = ("o1", "o2"),
    straight_ports: tuple[str, str] = ("o1", "o2"),
) -> list[OpticalAllAngleRoute]:
    """Places all-angle routes.

    Args:
        c: The virtual or real KCell to place the route in.
        start_ports: Ports denoting the beginning of each route. Must be
            sorted in anti-clockwise orientation with regards to the desired
            bundle order.
        end_ports: Ports denoting the end of each route. Must be
            sorted in clockwise orientation with regards to the desired
            bundle order.
        backbone: The points of the route. The router will route to the first point
            and then create a bundle which follows this points as a backbone. Bends
            leading the first or following the last backbone point are guaranteed to be
            outside the backbone.
        separation: Minimal separation between each piece of the bundle.
            This is only guaranteed from the backbone start to backbone end.
        straight_factory: Function to create straights from length and width.
            [um]
        bend_factory: Function to  create bends from length and width. [um]
        bend_ports: Names of the ports of the bend to use for connecting
            straights and bends.
        straight_ports: Names of the ports of the straight to use for connecting
            straights and bends.
    """
    routes: list[OpticalAllAngleRoute] = []

    if backbone:
        if len(backbone) < MIN_WAYPOINTS_FOR_ROUTING:
            raise NotImplementedError(
                "A bundle with less than two points has no orientation. "
                "Cannot automatically determine orientation."
            )

        if isinstance(separation, int | float):
            separation = [separation] * len(start_ports)
        pts_list = backbone2bundle(
            backbone=backbone,
            port_widths=[p.dwidth for p in start_ports],
            spacings=separation,
        )

        for ps, pe, pts in zip(start_ports, end_ports, pts_list, strict=False):
            # use edges and transformation to get distances to calculate crossings
            # and types of crossings
            pts_ = pts
            vector_bundle_start = pts_[0] - pts_[1]
            vector_bundle_end = pts_[-1] - pts_[-2]
            trans_bundle_start = kdb.DCplxTrans(
                1,
                np.rad2deg(np.arctan2(vector_bundle_start.y, vector_bundle_start.x)),
                False,
                pts_[0].to_v(),
            )
            trans_bundle_end = kdb.DCplxTrans(
                1,
                np.rad2deg(np.arctan2(vector_bundle_end.y, vector_bundle_end.x)),
                False,
                pts_[-1].to_v(),
            )
            psb = ps.copy()
            psb.dcplx_trans = trans_bundle_start
            peb = pe.copy()
            peb.dcplx_trans = trans_bundle_end

            pts_ = _get_connection_between_ports(
                port_start=ps,
                port_end=psb,
                bend_factory=bend_factory,
                bend_ports=bend_ports,
                backbone=pts_,
            )

            pts_.reverse()
            pts_ = _get_connection_between_ports(
                port_start=pe,
                port_end=peb,
                bend_factory=bend_factory,
                backbone=pts_,
                bend_ports=bend_ports,
            )
            pts_.reverse()
            routes.append(
                route(
                    c,
                    ps.dwidth,
                    pts_,
                    straight_factory=straight_factory,
                    bend_factory=bend_factory,
                    bend_ports=bend_ports,
                    straight_ports=straight_ports,
                )
            )
    else:
        for ps, pe in zip(start_ports, end_ports, strict=False):
            pts_ = _get_connection_between_ports(
                port_start=ps,
                port_end=pe,
                bend_factory=bend_factory,
                bend_ports=bend_ports,
                backbone=[],
            )
            # the connection will not write the end point
            pts_.append(pe.dcplx_trans.disp.to_p())
            routes.append(
                route(
                    c,
                    ps.dwidth,
                    pts_,
                    straight_factory=straight_factory,
                    bend_factory=bend_factory,
                    bend_ports=bend_ports,
                    straight_ports=straight_ports,
                )
            )

    return routes