Skip to content

Taper

taper

Taper definitions [dbu].

A linear taper transitions between two cross sections (two core widths) over a given length::

       __
     _/  │ Slab/Exclude
   _/  __│
 _/  _/  │
│  _/    │
│_/      │
│_       │ Core
│ \_     │
│_  \_   │
  \_  \__│
    \_   │
      \__│ Slab/Exclude

The slabs and excludes are part of the cross sections the taper is built from, or can be given for the legacy (width1, width2, layer, enclosure) call.

TODO: Non-linear tapers

TaperFactory

Bases: Protocol[KC_co]

Source code in kfactory/factories/taper.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
class TaperFactory(Protocol[KC_co]):
    __name__: str

    def __call__(
        self,
        *,
        length: dbu,
        cross_section1: str
        | AnyCrossSectionInput
        | CrossSectionSpecDict
        | DCrossSectionSpecDict
        | None = None,
        cross_section2: str
        | AnyCrossSectionInput
        | CrossSectionSpecDict
        | DCrossSectionSpecDict
        | None = None,
        width1: dbu | None = None,
        width2: dbu | None = None,
        layer: kdb.LayerInfo | None = None,
        enclosure: LayerEnclosure | None = None,
    ) -> KC_co:
        r"""Linear Taper [dbu].

                   __
                 _/  │ Slab/Exclude
               _/  __│
             _/  _/  │
            │  _/    │
            │_/      │
            │_       │ Core
            │ \_     │
            │_  \_   │
              \_  \__│
                \_   │
                  \__│ Slab/Exclude

        Either pass two cross sections (``cross_section1``/``cross_section2``) or the
        legacy ``width1``/``width2``/``layer``/``enclosure`` (all dbu) which is
        normalized into a pair of symmetric cross sections.

        Args:
            length: Length of the taper. [dbu]
            cross_section1: Cross section of the left side.
            cross_section2: Cross section of the right side.
            width1: Width of the core on the left side. [dbu] (legacy; requires
                ``layer``)
            width2: Width of the core on the right side. [dbu] (legacy; requires
                ``layer``)
            layer: Main layer of the taper. (legacy)
            enclosure: Definition of the slab/exclude. (legacy)
        """
        ...

__call__

__call__(
    *,
    length: dbu,
    cross_section1: str
    | AnyCrossSectionInput
    | CrossSectionSpecDict
    | DCrossSectionSpecDict
    | None = None,
    cross_section2: str
    | AnyCrossSectionInput
    | CrossSectionSpecDict
    | DCrossSectionSpecDict
    | None = None,
    width1: dbu | None = None,
    width2: dbu | None = None,
    layer: LayerInfo | None = None,
    enclosure: LayerEnclosure | None = None,
) -> KC_co

Linear Taper [dbu].

       __
     _/  │ Slab/Exclude
   _/  __│
 _/  _/  │
│  _/    │
│_/      │
│_       │ Core
│ \_     │
│_  \_   │
  \_  \__│
    \_   │
      \__│ Slab/Exclude

Either pass two cross sections (cross_section1/cross_section2) or the legacy width1/width2/layer/enclosure (all dbu) which is normalized into a pair of symmetric cross sections.

Parameters:

Name Type Description Default
length dbu

Length of the taper. [dbu]

required
cross_section1 str | AnyCrossSectionInput | CrossSectionSpecDict | DCrossSectionSpecDict | None

Cross section of the left side.

None
cross_section2 str | AnyCrossSectionInput | CrossSectionSpecDict | DCrossSectionSpecDict | None

Cross section of the right side.

None
width1 dbu | None

Width of the core on the left side. [dbu] (legacy; requires layer)

None
width2 dbu | None

Width of the core on the right side. [dbu] (legacy; requires layer)

None
layer LayerInfo | None

Main layer of the taper. (legacy)

None
enclosure LayerEnclosure | None

Definition of the slab/exclude. (legacy)

None
Source code in kfactory/factories/taper.py
 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
def __call__(
    self,
    *,
    length: dbu,
    cross_section1: str
    | AnyCrossSectionInput
    | CrossSectionSpecDict
    | DCrossSectionSpecDict
    | None = None,
    cross_section2: str
    | AnyCrossSectionInput
    | CrossSectionSpecDict
    | DCrossSectionSpecDict
    | None = None,
    width1: dbu | None = None,
    width2: dbu | None = None,
    layer: kdb.LayerInfo | None = None,
    enclosure: LayerEnclosure | None = None,
) -> KC_co:
    r"""Linear Taper [dbu].

               __
             _/  │ Slab/Exclude
           _/  __│
         _/  _/  │
        │  _/    │
        │_/      │
        │_       │ Core
        │ \_     │
        │_  \_   │
          \_  \__│
            \_   │
              \__│ Slab/Exclude

    Either pass two cross sections (``cross_section1``/``cross_section2``) or the
    legacy ``width1``/``width2``/``layer``/``enclosure`` (all dbu) which is
    normalized into a pair of symmetric cross sections.

    Args:
        length: Length of the taper. [dbu]
        cross_section1: Cross section of the left side.
        cross_section2: Cross section of the right side.
        width1: Width of the core on the left side. [dbu] (legacy; requires
            ``layer``)
        width2: Width of the core on the right side. [dbu] (legacy; requires
            ``layer``)
        layer: Main layer of the taper. (legacy)
        enclosure: Definition of the slab/exclude. (legacy)
    """
    ...

taper_factory

taper_factory(
    kcl: KCLayout,
    *,
    additional_info: Callable[..., dict[str, MetaData]]
    | dict[str, MetaData]
    | None = None,
    port_type: str = "optical",
    **cell_kwargs: Unpack[CellKWargs],
) -> TaperFactory[KCell]
taper_factory(
    kcl: KCLayout,
    *,
    additional_info: Callable[..., dict[str, MetaData]]
    | dict[str, MetaData]
    | None = None,
    output_type: type[KC],
    port_type: str = "optical",
    **cell_kwargs: Unpack[CellKWargs],
) -> TaperFactory[KC]
taper_factory(
    kcl: KCLayout,
    additional_info: Callable[..., dict[str, MetaData]]
    | dict[str, MetaData]
    | None = None,
    output_type: type[KC] | None = None,
    port_type: str = "optical",
    **cell_kwargs: Unpack[CellKWargs],
) -> TaperFactory[KC]

Returns a function generating linear tapers [dbu].

       __
     _/  │ Slab/Exclude
   _/  __│
 _/  _/  │
│  _/    │
│_/      │
│_       │ Core
│ \_     │
│_  \_   │
  \_  \__│
    \_   │
      \__│ Slab/Exclude

The returned function is the generic interface: it accepts either two cross sections (cross_section1/cross_section2) or the legacy width1/width2/layer/enclosure (all dbu), normalized into a pair of symmetric cross sections.

Parameters:

Name Type Description Default
kcl KCLayout

The KCLayout which will be owned

required
additional_info Callable[..., dict[str, MetaData]] | dict[str, MetaData] | None

Add additional key/values to the KCell.info. Can be a static dict mapping info name to info value. Or can a callable which takes the taper functions' parameters as kwargs and returns a dict with the mapping.

None
output_type type[KC] | None

The type of the returned cell.

None
port_type str

Type of the ports the taper gets.

'optical'
cell_kwargs Unpack[CellKWargs]

Additional arguments passed as @kcl.cell(**cell_kwargs).

{}
Source code in kfactory/factories/taper.py
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
327
328
329
330
331
332
333
334
def taper_factory(
    kcl: KCLayout,
    additional_info: Callable[
        ...,
        dict[str, MetaData],
    ]
    | dict[str, MetaData]
    | None = None,
    output_type: type[KC] | None = None,
    port_type: str = "optical",
    **cell_kwargs: Unpack[CellKWargs],
) -> TaperFactory[KC]:
    r"""Returns a function generating linear tapers [dbu].

               __
             _/  │ Slab/Exclude
           _/  __│
         _/  _/  │
        │  _/    │
        │_/      │
        │_       │ Core
        │ \_     │
        │_  \_   │
          \_  \__│
            \_   │
              \__│ Slab/Exclude

    The returned function is the generic interface: it accepts either two cross
    sections (``cross_section1``/``cross_section2``) or the legacy
    ``width1``/``width2``/``layer``/``enclosure`` (all dbu), normalized into a pair
    of symmetric cross sections.

    Args:
        kcl: The KCLayout which will be owned
        additional_info: Add additional key/values to the
            [`KCell.info`][kfactory.settings.Info]. Can be a static dict
            mapping info name to info value. Or can a callable which takes the taper
            functions' parameters as kwargs and returns a dict with the mapping.
        output_type: The type of the returned cell.
        port_type: Type of the ports the taper gets.
        cell_kwargs: Additional arguments passed as `@kcl.cell(**cell_kwargs)`.
    """
    _additional_info: dict[str, MetaData] = {}
    if _is_additional_info_func(additional_info):
        _additional_info_func: Callable[
            ...,
            dict[str, MetaData],
        ] = additional_info
    else:

        def additional_info_func(
            **kwargs: Any,
        ) -> dict[str, MetaData]:
            return {}

        _additional_info_func = additional_info_func
        _additional_info = additional_info or {}  # ty:ignore[invalid-assignment]

    ports = cell_kwargs.get("ports")
    if ports is None:
        if kcl.rename_function == rename_clockwise:
            cell_kwargs["ports"] = {"left": ["o1"], "right": ["o2"]}
        elif kcl.rename_function == rename_by_direction:
            cell_kwargs["ports"] = {"left": ["W0"], "right": ["E0"]}
    cell_kwargs.setdefault("basename", "taper")
    basename = cell_kwargs["basename"]

    if output_type is not None:
        cell = kcl.cell(output_type=output_type, **cell_kwargs)
    else:
        cell = kcl.cell(output_type=cast("type[KC]", KCell), **cell_kwargs)

    @cell
    def _taper(
        cross_section1: str | AnyCrossSectionInput,
        cross_section2: str | AnyCrossSectionInput,
        length: dbu,
    ) -> KCell:
        """Linear taper defined by two cross sections."""
        c = kcl.kcell()
        if length < 0:
            logger.critical(
                f"Negative lengths are not allowed {length} as ports"
                " will be inverted. Please use a positive number. Forcing positive"
                " lengths."
            )
            length = -length

        xs1 = kcl.get_base_cross_section(cross_section1)
        xs2 = kcl.get_base_cross_section(cross_section2)
        if isinstance(xs1, AsymmetricalCrossSection) or isinstance(
            xs2, AsymmetricalCrossSection
        ):
            raise NotImplementedError(
                "Tapers do not support asymmetric cross sections yet (the straight"
                " edge of the taper is ambiguous for an off-center profile). Got"
                f" {xs1.name!r} -> {xs2.name!r}."
            )
        # The taper applies a single, constant enclosure (and core layer) along its
        # length via minkowski; a differing enclosure between the two cross sections
        # (slab/exclude sections, bbox sections, or main layer) cannot be represented
        # and would be silently taken from cross_section1 only. ``LayerEnclosure``
        # equality is structural (its name normalizes to a geometry hash) and includes
        # the main layer, so this also catches a core-layer mismatch.
        if xs1.enclosure != xs2.enclosure:
            raise ValueError(
                "Taper requires both cross sections to share the same enclosure "
                "(slab/exclude sections and main layer); got "
                f"{xs1.name!r} ({xs1.enclosure.name!r}) and "
                f"{xs2.name!r} ({xs2.enclosure.name!r}). Only the core width may "
                "differ between the two cross sections."
            )
        width1 = xs1.width
        width2 = xs2.width
        layer = xs1.main_layer
        enclosure = xs1.enclosure

        li = c.kcl.layer(layer)
        taper = c.shapes(li).insert(
            kdb.Polygon(
                [
                    kdb.Point(0, int(-width1 / 2)),
                    kdb.Point(0, width1 // 2),
                    kdb.Point(length, width2 // 2),
                    kdb.Point(length, int(-width2 / 2)),
                ]
            )
        )

        c.create_port(
            name="o1",
            trans=kdb.Trans(2, False, 0, 0),
            cross_section=xs1,
            port_type=port_type,
        )
        c.create_port(
            name="o2",
            trans=kdb.Trans(0, False, length, 0),
            cross_section=xs2,
            port_type=port_type,
        )

        enclosure.apply_minkowski_y(c, layer)

        _info: dict[str, MetaData] = {
            "width1_um": c.kcl.to_um(width1),
            "width2_um": c.kcl.to_um(width2),
            "length_um": c.kcl.to_um(length),
            "width1_dbu": width1,
            "width2_dbu": width2,
            "length_dbu": length,
        }
        _info.update(
            _additional_info_func(
                cross_section1=xs1,
                cross_section2=xs2,
                length=length,
            )
        )
        _info.update(_additional_info)
        c.info = Info(**_info)
        c.auto_rename_ports()
        c.boundary = taper.dpolygon

        return c

    @kcl.generic_factory(name=basename)
    def taper(
        *,
        length: dbu,
        cross_section1: str
        | AnyCrossSectionInput
        | CrossSectionSpecDict
        | DCrossSectionSpecDict
        | None = None,
        cross_section2: str
        | AnyCrossSectionInput
        | CrossSectionSpecDict
        | DCrossSectionSpecDict
        | None = None,
        width1: dbu | None = None,
        width2: dbu | None = None,
        layer: kdb.LayerInfo | None = None,
        enclosure: LayerEnclosure | None = None,
    ) -> KC:
        if cross_section1 is None or cross_section2 is None:
            if width1 is None or width2 is None or layer is None:
                raise ValueError(
                    "Provide cross_section1 and cross_section2, or width1, width2 and"
                    " layer (legacy call)."
                )
            xs1 = cross_section_from_width(kcl, width1, layer, enclosure)
            xs2 = cross_section_from_width(kcl, width2, layer, enclosure)
        else:
            xs1 = kcl.get_icross_section(cross_section1)
            xs2 = kcl.get_icross_section(cross_section2)
        return _taper(cross_section1=xs1, cross_section2=xs2, length=length)

    return taper