Skip to content

Instance group

instance_group

DInstanceGroup

Bases: ProtoTInstanceGroup[float, DInstance], UMGeometricObject, DCreatePort

Group of DInstances.

The instance group can be treated similar to a single instance with regards to transformation functions and bounding boxes.

Parameters:

Name Type Description Default
insts Sequence[TI] | None

List of the dinstances of the group.

None
Source code in kfactory/instance_group.py
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
class DInstanceGroup(
    ProtoTInstanceGroup[float, DInstance], UMGeometricObject, DCreatePort
):
    """Group of DInstances.

    The instance group can be treated similar to a single instance
    with regards to transformation functions and bounding boxes.

    Args:
        insts: List of the dinstances of the group.
    """

    @cached_property
    def ports(self) -> DPorts:
        return DPorts(kcl=self.kcl, bases=self._base_ports)

    def add_port(
        self,
        *,
        port: ProtoPort[Any],
        name: str | None = None,
        keep_mirror: bool = False,
    ) -> DPort:
        return self.ports.add_port(port=port, name=name, keep_mirror=keep_mirror)

    def add(self, inst: ProtoTInstance[Any]) -> None:
        self.insts.append(DInstance(kcl=inst.kcl, instance=inst.instance))

ports cached property

ports: DPorts

Ports of the instance.

InstanceGroup

Bases: ProtoTInstanceGroup[int, Instance], DBUGeometricObject, ICreatePort

Group of Instances.

The instance group can be treated similar to a single instance with regards to transformation functions and bounding boxes.

Parameters:

Name Type Description Default
insts Sequence[TI] | None

List of the instances of the group.

None
Source code in kfactory/instance_group.py
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
class InstanceGroup(
    ProtoTInstanceGroup[int, Instance], DBUGeometricObject, ICreatePort
):
    """Group of Instances.

    The instance group can be treated similar to a single instance
    with regards to transformation functions and bounding boxes.

    Args:
        insts: List of the instances of the group.
    """

    @cached_property
    def ports(self) -> Ports:
        return Ports(kcl=self.kcl, bases=self._base_ports)

    def add_port(
        self,
        *,
        port: ProtoPort[Any],
        name: str | None = None,
        keep_mirror: bool = False,
    ) -> Port:
        return self.ports.add_port(port=port, name=name, keep_mirror=keep_mirror)

    def add(self, inst: ProtoTInstance[Any]) -> None:
        self.insts.append(Instance(kcl=inst.kcl, instance=inst.instance))

ports cached property

ports: Ports

Ports of the instance.

ProtoInstanceGroup

Bases: GeometricObject[T], ABC

Source code in kfactory/instance_group.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
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
class ProtoInstanceGroup[T: (int, float), TI: ProtoTInstance[Any] | VInstance[Any]](
    GeometricObject[T], ABC
):
    insts: list[TI]
    _base_ports: list[BasePort]

    def __init__(
        self,
        insts: Sequence[TI] | None = None,
        ports: Sequence[ProtoPort[Any]] | None = None,
    ) -> None:
        """Initialize the InstanceGroup."""
        self.insts = list(insts) if insts is not None else []
        self._base_ports = [p.base for p in ports] if ports is not None else []

    @abstractmethod
    def add(self, inst: ProtoTInstance[Any]) -> None: ...
    @property
    def name(self) -> str:
        return str(self)

    def __str__(self) -> str:
        return (
            f"InstanceGroup(insts={[inst.name for inst in self.insts]}, "
            f"ports={[p.name for p in self.ports]})"
        )

    @cached_property
    @abstractmethod
    def ports(self) -> ProtoPorts[T]:
        """Ports of the instance."""
        ...

    @property
    def kcl(self) -> KCLayout:
        try:
            return self.insts[0].kcl
        except IndexError as e:
            raise ValueError(
                "Cannot transform or retrieve the KCLayout "
                "of an instance group if it's empty"
            ) from e

    @kcl.setter
    def kcl(self, val: KCLayout) -> NoReturn:
        raise ValueError("KCLayout cannot be set on an instance group.")

    def transform(
        self, trans: kdb.Trans | kdb.DTrans | kdb.ICplxTrans | kdb.DCplxTrans
    ) -> None:
        """Transform the instance group."""
        for inst in self.insts:
            inst.transform(trans)
        if isinstance(trans, kdb.DTrans):
            trans = trans.to_itype(self.kcl.dbu)
        elif isinstance(trans, kdb.ICplxTrans):
            trans = kdb.DCplxTrans(trans=trans, dbu=self.kcl.dbu)
        for p in self.ports:
            p.transform(trans)

    def ibbox(self, layer: int | None = None) -> kdb.Box:
        """Get the total bounding box or the bounding box of a layer in dbu."""
        bb = kdb.Box()
        for _bb in (inst.ibbox(layer) for inst in self.insts):
            bb += _bb
        return bb

    def dbbox(self, layer: int | None = None) -> kdb.DBox:
        """Get the total bounding box or the bounding box of a layer in um."""
        bb = kdb.DBox()
        for _bb in (inst.dbbox(layer) for inst in self.insts):
            bb += _bb
        return bb

    def __iter__(self) -> Iterator[TI]:
        return iter(self.insts)

    @overload
    def connect(
        self,
        port: str | ProtoPort[Any] | None,
        other: ProtoPort[Any],
        *,
        mirror: bool = False,
        allow_width_mismatch: bool | None = None,
        allow_layer_mismatch: bool | None = None,
        allow_type_mismatch: bool | None = None,
        use_mirror: bool | None = None,
        use_angle: bool | None = None,
    ) -> None: ...

    @overload
    def connect(
        self,
        port: str | ProtoPort[Any] | None,
        other: ProtoTInstance[Any],
        other_port_name: str | int | tuple[int | str, int, int] | None,
        *,
        mirror: bool = False,
        allow_width_mismatch: bool | None = None,
        allow_layer_mismatch: bool | None = None,
        allow_type_mismatch: bool | None = None,
        use_mirror: bool | None = None,
        use_angle: bool | None = None,
    ) -> None: ...

    def connect(
        self,
        port: str | ProtoPort[Any] | None,
        other: ProtoTInstance[Any] | ProtoPort[Any],
        other_port_name: str | int | tuple[int | str, int, int] | None = None,
        *,
        mirror: bool = False,
        allow_width_mismatch: bool | None = None,
        allow_layer_mismatch: bool | None = None,
        allow_type_mismatch: bool | None = None,
        use_mirror: bool | None = None,
        use_angle: bool | None = None,
    ) -> None:
        """Align port with name `portname` to a port.

        Function to allow to transform this instance so that a port of this instance is
        connected (same center with 180° turn) to another instance.

        Args:
            port: The name of the port of this instance to be connected, or directly an
                instance port. Can be `None` because port names can be `None`.
            other: The other instance or a port. Skip `other_port_name` if it's a port.
            other_port_name: The name of the other port. Ignored if
                `other` is a port.
            mirror: Instead of applying klayout.db.Trans.R180 as a connection
                transformation, use klayout.db.Trans.M90, which effectively means this
                instance will be mirrored and connected.
            allow_width_mismatch: Skip width check between the ports if set.
            allow_layer_mismatch: Skip layer check between the ports if set.
            allow_type_mismatch: Skip port_type check between the ports if set.
            use_mirror: If False mirror flag does not get applied from the connection.
            use_angle: If False the angle does not get applied from the connection.
        """
        if allow_width_mismatch is None:
            allow_width_mismatch = config.allow_width_mismatch
        if allow_layer_mismatch is None:
            allow_layer_mismatch = config.allow_layer_mismatch
        if allow_type_mismatch is None:
            allow_type_mismatch = config.allow_type_mismatch
        if use_mirror is None:
            use_mirror = config.connect_use_mirror
        if use_angle is None:
            use_angle = config.connect_use_angle

        if isinstance(other, ProtoPort):
            op = Port(base=other.base)
        else:
            if other_port_name is None:
                raise ValueError(
                    "portname cannot be None if an Instance Object is given. For"
                    "complex connections (non-90 degree and floating point ports) use"
                    "route_cplx instead"
                )
            op = Port(base=other.ports[other_port_name].base)
        if isinstance(port, ProtoPort):
            p = Port(base=port.base)
        else:
            p = Port(base=self.ports[port].base)

        assert isinstance(p, Port)
        assert isinstance(op, Port)

        if p.width != op.width and not allow_width_mismatch:
            raise PortWidthMismatchError(self, other, p, op)
        if p.layer != op.layer and not allow_layer_mismatch:
            raise PortLayerMismatchError(self.kcl, self, other, p, op)
        if p.port_type != op.port_type and not allow_type_mismatch:
            raise PortTypeMismatchError(self, other, p, op)
        if p.base.dcplx_trans or op.base.dcplx_trans:
            dconn_trans = kdb.DCplxTrans.M90 if mirror else kdb.DCplxTrans.R180
            match (use_mirror, use_angle):
                case True, True:
                    dcplx_trans = (
                        op.dcplx_trans * dconn_trans * p.dcplx_trans.inverted()
                    )
                    self.transform(dcplx_trans)
                case False, True:
                    dconn_trans = (
                        kdb.DCplxTrans.M90
                        if mirror ^ p.dcplx_trans.mirror
                        else kdb.DCplxTrans.R180
                    )
                    opt = op.dcplx_trans
                    opt.mirror = False
                    dcplx_trans = opt * dconn_trans * p.dcplx_trans.inverted()
                    self.transform(dcplx_trans)
                case False, False:
                    self.transform(
                        kdb.DCplxTrans(op.dcplx_trans.disp - p.dcplx_trans.disp)
                    )
                case True, False:
                    self.transform(
                        kdb.DCplxTrans(op.dcplx_trans.disp - p.dcplx_trans.disp)
                    )
                    self.dmirror_y(op.dcplx_trans.disp.y)
                case _:
                    raise NotImplementedError("This shouldn't happen")

        else:
            conn_trans = kdb.Trans.M90 if mirror else kdb.Trans.R180
            match (use_mirror, use_angle):
                case True, True:
                    trans = op.trans * conn_trans * p.trans.inverted()
                    self.transform(trans)
                case False, True:
                    conn_trans = (
                        kdb.Trans.M90 if mirror ^ p.trans.mirror else kdb.Trans.R180
                    )
                    op = op.copy()
                    op.trans.mirror = False
                    trans = op.trans * conn_trans * p.trans.inverted()
                    self.transform(trans)
                case False, False:
                    self.transform(kdb.Trans(op.trans.disp - p.trans.disp))
                case True, False:
                    self.transform(kdb.Trans(op.trans.disp - p.trans.disp))
                    self.dmirror_y(op.dcplx_trans.disp.y)
                case _:
                    raise NotImplementedError("This shouldn't happen")

ports abstractmethod cached property

ports: ProtoPorts[T]

Ports of the instance.

__init__

__init__(
    insts: Sequence[TI] | None = None,
    ports: Sequence[ProtoPort[Any]] | None = None,
) -> None

Initialize the InstanceGroup.

Source code in kfactory/instance_group.py
39
40
41
42
43
44
45
46
def __init__(
    self,
    insts: Sequence[TI] | None = None,
    ports: Sequence[ProtoPort[Any]] | None = None,
) -> None:
    """Initialize the InstanceGroup."""
    self.insts = list(insts) if insts is not None else []
    self._base_ports = [p.base for p in ports] if ports is not None else []

connect

connect(
    port: str | ProtoPort[Any] | None,
    other: ProtoPort[Any],
    *,
    mirror: bool = False,
    allow_width_mismatch: bool | None = None,
    allow_layer_mismatch: bool | None = None,
    allow_type_mismatch: bool | None = None,
    use_mirror: bool | None = None,
    use_angle: bool | None = None,
) -> None
connect(
    port: str | ProtoPort[Any] | None,
    other: ProtoTInstance[Any],
    other_port_name: str
    | int
    | tuple[int | str, int, int]
    | None,
    *,
    mirror: bool = False,
    allow_width_mismatch: bool | None = None,
    allow_layer_mismatch: bool | None = None,
    allow_type_mismatch: bool | None = None,
    use_mirror: bool | None = None,
    use_angle: bool | None = None,
) -> None
connect(
    port: str | ProtoPort[Any] | None,
    other: ProtoTInstance[Any] | ProtoPort[Any],
    other_port_name: str
    | int
    | tuple[int | str, int, int]
    | None = None,
    *,
    mirror: bool = False,
    allow_width_mismatch: bool | None = None,
    allow_layer_mismatch: bool | None = None,
    allow_type_mismatch: bool | None = None,
    use_mirror: bool | None = None,
    use_angle: bool | None = None,
) -> None

Align port with name portname to a port.

Function to allow to transform this instance so that a port of this instance is connected (same center with 180° turn) to another instance.

Parameters:

Name Type Description Default
port str | ProtoPort[Any] | None

The name of the port of this instance to be connected, or directly an instance port. Can be None because port names can be None.

required
other ProtoTInstance[Any] | ProtoPort[Any]

The other instance or a port. Skip other_port_name if it's a port.

required
other_port_name str | int | tuple[int | str, int, int] | None

The name of the other port. Ignored if other is a port.

None
mirror bool

Instead of applying klayout.db.Trans.R180 as a connection transformation, use klayout.db.Trans.M90, which effectively means this instance will be mirrored and connected.

False
allow_width_mismatch bool | None

Skip width check between the ports if set.

None
allow_layer_mismatch bool | None

Skip layer check between the ports if set.

None
allow_type_mismatch bool | None

Skip port_type check between the ports if set.

None
use_mirror bool | None

If False mirror flag does not get applied from the connection.

None
use_angle bool | None

If False the angle does not get applied from the connection.

None
Source code in kfactory/instance_group.py
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
def connect(
    self,
    port: str | ProtoPort[Any] | None,
    other: ProtoTInstance[Any] | ProtoPort[Any],
    other_port_name: str | int | tuple[int | str, int, int] | None = None,
    *,
    mirror: bool = False,
    allow_width_mismatch: bool | None = None,
    allow_layer_mismatch: bool | None = None,
    allow_type_mismatch: bool | None = None,
    use_mirror: bool | None = None,
    use_angle: bool | None = None,
) -> None:
    """Align port with name `portname` to a port.

    Function to allow to transform this instance so that a port of this instance is
    connected (same center with 180° turn) to another instance.

    Args:
        port: The name of the port of this instance to be connected, or directly an
            instance port. Can be `None` because port names can be `None`.
        other: The other instance or a port. Skip `other_port_name` if it's a port.
        other_port_name: The name of the other port. Ignored if
            `other` is a port.
        mirror: Instead of applying klayout.db.Trans.R180 as a connection
            transformation, use klayout.db.Trans.M90, which effectively means this
            instance will be mirrored and connected.
        allow_width_mismatch: Skip width check between the ports if set.
        allow_layer_mismatch: Skip layer check between the ports if set.
        allow_type_mismatch: Skip port_type check between the ports if set.
        use_mirror: If False mirror flag does not get applied from the connection.
        use_angle: If False the angle does not get applied from the connection.
    """
    if allow_width_mismatch is None:
        allow_width_mismatch = config.allow_width_mismatch
    if allow_layer_mismatch is None:
        allow_layer_mismatch = config.allow_layer_mismatch
    if allow_type_mismatch is None:
        allow_type_mismatch = config.allow_type_mismatch
    if use_mirror is None:
        use_mirror = config.connect_use_mirror
    if use_angle is None:
        use_angle = config.connect_use_angle

    if isinstance(other, ProtoPort):
        op = Port(base=other.base)
    else:
        if other_port_name is None:
            raise ValueError(
                "portname cannot be None if an Instance Object is given. For"
                "complex connections (non-90 degree and floating point ports) use"
                "route_cplx instead"
            )
        op = Port(base=other.ports[other_port_name].base)
    if isinstance(port, ProtoPort):
        p = Port(base=port.base)
    else:
        p = Port(base=self.ports[port].base)

    assert isinstance(p, Port)
    assert isinstance(op, Port)

    if p.width != op.width and not allow_width_mismatch:
        raise PortWidthMismatchError(self, other, p, op)
    if p.layer != op.layer and not allow_layer_mismatch:
        raise PortLayerMismatchError(self.kcl, self, other, p, op)
    if p.port_type != op.port_type and not allow_type_mismatch:
        raise PortTypeMismatchError(self, other, p, op)
    if p.base.dcplx_trans or op.base.dcplx_trans:
        dconn_trans = kdb.DCplxTrans.M90 if mirror else kdb.DCplxTrans.R180
        match (use_mirror, use_angle):
            case True, True:
                dcplx_trans = (
                    op.dcplx_trans * dconn_trans * p.dcplx_trans.inverted()
                )
                self.transform(dcplx_trans)
            case False, True:
                dconn_trans = (
                    kdb.DCplxTrans.M90
                    if mirror ^ p.dcplx_trans.mirror
                    else kdb.DCplxTrans.R180
                )
                opt = op.dcplx_trans
                opt.mirror = False
                dcplx_trans = opt * dconn_trans * p.dcplx_trans.inverted()
                self.transform(dcplx_trans)
            case False, False:
                self.transform(
                    kdb.DCplxTrans(op.dcplx_trans.disp - p.dcplx_trans.disp)
                )
            case True, False:
                self.transform(
                    kdb.DCplxTrans(op.dcplx_trans.disp - p.dcplx_trans.disp)
                )
                self.dmirror_y(op.dcplx_trans.disp.y)
            case _:
                raise NotImplementedError("This shouldn't happen")

    else:
        conn_trans = kdb.Trans.M90 if mirror else kdb.Trans.R180
        match (use_mirror, use_angle):
            case True, True:
                trans = op.trans * conn_trans * p.trans.inverted()
                self.transform(trans)
            case False, True:
                conn_trans = (
                    kdb.Trans.M90 if mirror ^ p.trans.mirror else kdb.Trans.R180
                )
                op = op.copy()
                op.trans.mirror = False
                trans = op.trans * conn_trans * p.trans.inverted()
                self.transform(trans)
            case False, False:
                self.transform(kdb.Trans(op.trans.disp - p.trans.disp))
            case True, False:
                self.transform(kdb.Trans(op.trans.disp - p.trans.disp))
                self.dmirror_y(op.dcplx_trans.disp.y)
            case _:
                raise NotImplementedError("This shouldn't happen")

dbbox

dbbox(layer: int | None = None) -> kdb.DBox

Get the total bounding box or the bounding box of a layer in um.

Source code in kfactory/instance_group.py
100
101
102
103
104
105
def dbbox(self, layer: int | None = None) -> kdb.DBox:
    """Get the total bounding box or the bounding box of a layer in um."""
    bb = kdb.DBox()
    for _bb in (inst.dbbox(layer) for inst in self.insts):
        bb += _bb
    return bb

ibbox

ibbox(layer: int | None = None) -> kdb.Box

Get the total bounding box or the bounding box of a layer in dbu.

Source code in kfactory/instance_group.py
93
94
95
96
97
98
def ibbox(self, layer: int | None = None) -> kdb.Box:
    """Get the total bounding box or the bounding box of a layer in dbu."""
    bb = kdb.Box()
    for _bb in (inst.ibbox(layer) for inst in self.insts):
        bb += _bb
    return bb

transform

transform(
    trans: Trans | DTrans | ICplxTrans | DCplxTrans,
) -> None

Transform the instance group.

Source code in kfactory/instance_group.py
80
81
82
83
84
85
86
87
88
89
90
91
def transform(
    self, trans: kdb.Trans | kdb.DTrans | kdb.ICplxTrans | kdb.DCplxTrans
) -> None:
    """Transform the instance group."""
    for inst in self.insts:
        inst.transform(trans)
    if isinstance(trans, kdb.DTrans):
        trans = trans.to_itype(self.kcl.dbu)
    elif isinstance(trans, kdb.ICplxTrans):
        trans = kdb.DCplxTrans(trans=trans, dbu=self.kcl.dbu)
    for p in self.ports:
        p.transform(trans)

VInstanceGroup

Bases: ProtoInstanceGroup[float, VInstance], UMGeometricObject, DCreatePort

Group of DInstances.

The instance group can be treated similar to a single instance with regards to transformation functions and bounding boxes.

Parameters:

Name Type Description Default
insts Sequence[TI] | None

List of the vinstances of the group.

None
Source code in kfactory/instance_group.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
class VInstanceGroup(
    ProtoInstanceGroup[float, VInstance], UMGeometricObject, DCreatePort
):
    """Group of DInstances.

    The instance group can be treated similar to a single instance
    with regards to transformation functions and bounding boxes.

    Args:
        insts: List of the vinstances of the group.
    """

    @cached_property
    def ports(self) -> DPorts:
        return DPorts(kcl=self.kcl, bases=self._base_ports)

    def add_port(
        self,
        *,
        port: ProtoPort[Any],
        name: str | None = None,
        keep_mirror: bool = False,
    ) -> DPort:
        return self.ports.add_port(port=port, name=name, keep_mirror=keep_mirror)

ports cached property

ports: DPorts

Ports of the instance.