"""Waveguide primitives."""
from functools import partial
from typing import TypedDict, Unpack
import gdsfactory as gf
from gdsfactory.typings import CrossSectionSpec, Ints, LayerSpec, Size
from kfactory import VInstance
from klayout.db import DCplxTrans
from qpdk import tech
from qpdk.helper import show_components
_DEFAULT_CROSS_SECTION = tech.cpw
_DEFAULT_KWARGS = {"cross_section": _DEFAULT_CROSS_SECTION}
_DEFAULT_BEND_KWARGS = _DEFAULT_KWARGS | {"allow_min_radius_violation": True}
[docs]
@gf.cell
def rectangle(
size: Size = (4.0, 2.0),
layer: LayerSpec = "M1_DRAW",
centered: bool = False,
port_type: str | None = "electrical",
port_orientations: Ints | None = (180, 90, 0, -90),
) -> gf.Component:
"""Returns a rectangle.
Args:
size: (tuple) Width and height of rectangle.
layer: Specific layer to put polygon geometry on.
centered: True sets center to (0, 0), False sets south-west to (0, 0).
port_type: optical, electrical.
port_orientations: list of port_orientations to add. None adds no ports.
"""
c = gf.Component()
ref = c << gf.c.compass(
size=size, layer=layer, port_type=port_type, port_orientations=port_orientations
)
if not centered:
ref.move((size[0] / 2, size[1] / 2))
if port_type:
c.add_ports(ref.ports)
c.flatten()
return c
ring = gf.c.ring
taper_cross_section = partial(
gf.c.taper_cross_section, cross_section1="cpw", cross_section2="cpw"
)
class StraightKwargs(TypedDict, total=False):
"""Type definition for straight keyword arguments."""
length: float
cross_section: CrossSectionSpec
width: float | None
npoints: int
[docs]
@gf.cell
def straight(**kwargs: Unpack[StraightKwargs]) -> gf.Component:
"""Returns a straight waveguide.
Args:
**kwargs: Arguments passed to :func:`gf.c.straight`.
"""
return gf.c.straight(**(_DEFAULT_KWARGS | kwargs))
straight_shorted = straight
[docs]
@gf.cell
def straight_open(**kwargs: Unpack[StraightKwargs]) -> gf.Component:
"""Returns a straight waveguide with etched gap at one end.
Args:
**kwargs: Arguments passed to :func:`gf.c.straight`.
"""
params = _DEFAULT_KWARGS | kwargs
c = gf.Component()
straight_ref = c << gf.c.straight(**params)
c.add_ports(straight_ref.ports)
add_etch_gap(c, c.ports["o2"], cross_section=params["cross_section"])
return c
[docs]
@gf.cell
def straight_double_open(**kwargs: Unpack[StraightKwargs]) -> gf.Component:
r"""Returns a straight waveguide with etched gaps at both ends.
Note:
This may be treated as a :math:`\lambda/2` as a straight resonator in some contexts.
Args:
**kwargs: Arguments passed to :func:`gf.c.straight`.
"""
params = _DEFAULT_KWARGS | kwargs
c = gf.Component()
straight_ref = c << straight_open(**params)
c.add_ports(straight_ref.ports)
add_etch_gap(c, c.ports["o1"], cross_section=params["cross_section"])
return c
class NxnKwargs(TypedDict, total=False):
"""Type definition for tee keyword arguments."""
xsize: float
ysize: float
wg_width: float
layer: LayerSpec
wg_margin: float
north: int
east: int
south: int
west: int
_NXN_DEFAULTS = {
"xsize": 10.0,
"ysize": 10.0,
"wg_width": 10,
"layer": tech.LAYER.M1_DRAW,
"wg_margin": 0,
"north": 1,
"east": 1,
"south": 1,
"west": 1,
}
[docs]
@gf.cell
def nxn(**kwargs: Unpack[NxnKwargs]) -> gf.Component:
"""Returns a tee waveguide.
Args:
**kwargs: Arguments passed to gf.c.nxn.
"""
return gf.c.nxn(**(_DEFAULT_KWARGS | _NXN_DEFAULTS | kwargs))
[docs]
@gf.cell
def tee(cross_section: CrossSectionSpec = "cpw") -> gf.Component:
"""Returns a three-way tee waveguide.
Args:
cross_section: specification (CrossSection, string or dict).
"""
c = gf.Component()
cross_section = gf.get_cross_section(cross_section)
etch_section = next(
s
for s in cross_section.sections
if s.name is not None and s.name.startswith("etch")
)
nxn_ref = c << nxn(
**{
"north": 1,
"east": 1,
"south": 1,
"west": 1,
}
)
for port in list(nxn_ref.ports)[:-1]:
straight_ref = c << straight(
cross_section=cross_section, length=etch_section.width
)
straight_ref.connect("o1", port)
c.add_port(f"{port.name}", port=straight_ref.ports["o2"])
etch_ref = c << rectangle(
size=(etch_section.width, cross_section.width),
layer=etch_section.layer,
centered=True,
)
etch_ref.transform(
list(nxn_ref.ports)[-1].dcplx_trans * DCplxTrans(etch_section.width / 2, 0)
)
# center
c.center = (0, 0)
return c
class BendEulerKwargs(TypedDict, total=False):
"""Type definition for bend_euler keyword arguments."""
angle: float
p: float
with_arc_floorplan: bool
npoints: int
direction: str
with_cladding_box: bool
cross_section: gf.CrossSection
allow_min_radius_violation: bool
[docs]
@gf.cell
def bend_euler(**kwargs: Unpack[BendEulerKwargs]) -> gf.Component:
"""Regular degree euler bend.
Args:
**kwargs: Arguments passed to gf.c.bend_euler.
"""
return gf.c.bend_euler(**(_DEFAULT_BEND_KWARGS | kwargs))
class BendCircularKwargs(TypedDict, total=False):
"""Type definition for bend_circular keyword arguments."""
angle: float
npoints: int
with_arc_floorplan: bool
cross_section: gf.CrossSection
radius: float
direction: str
allow_min_radius_violation: bool
_BEND_CIRCULAR_DEFAULTS = {
"radius": 100,
}
[docs]
@gf.cell
def bend_circular(**kwargs: Unpack[BendCircularKwargs]) -> gf.Component:
"""Returns circular bend.
Args:
**kwargs: Arguments passed to gf.c.bend_circular.
"""
return gf.c.bend_circular(
**(_DEFAULT_BEND_KWARGS | _BEND_CIRCULAR_DEFAULTS | kwargs)
)
class BendSKwargs(TypedDict, total=False):
"""Type definition for bend_s keyword arguments."""
size: Size
cross_section: CrossSectionSpec
width: float | None
allow_min_radius_violation: bool
_BEND_S_DEFAULTS = {
"size": (20.0, 3.0),
}
[docs]
@gf.cell
def bend_s(**kwargs: Unpack[BendSKwargs]) -> gf.Component:
"""Return S bend with bezier curve.
stores min_bend_radius property in self.info['min_bend_radius']
min_bend_radius depends on height and length
Args:
**kwargs: Arguments passed to :func:`~gf.c.bend_s`.
"""
return gf.c.bend_s(**(_DEFAULT_BEND_KWARGS | _BEND_S_DEFAULTS | kwargs))
coupler_straight = partial(gf.c.coupler_straight, cross_section="cpw", gap=16)
coupler_ring = partial(
gf.c.coupler_ring,
cross_section="cpw",
length_x=20,
bend=bend_circular,
straight=straight,
gap=16,
)
class StraightAllAngleKwargs(TypedDict, total=False):
"""Type definition for straight_all_angle keyword arguments."""
length: float
npoints: int
cross_section: CrossSectionSpec
width: float | None
[docs]
@gf.vcell
def straight_all_angle(
**kwargs: Unpack[StraightAllAngleKwargs],
) -> gf.ComponentAllAngle:
"""Returns a Straight waveguide with offgrid ports.
Args:
**kwargs: Arguments passed to :func:`~gf.c.straight_all_angle`.
.. code::
o1 ──────────────── o2
length
"""
return gf.c.straight_all_angle(**(_DEFAULT_KWARGS | kwargs))
class BendEulerAllAngleKwargs(TypedDict, total=False):
"""Type definition for bend_euler_all_angle keyword arguments."""
radius: float | None
angle: float
p: float
with_arc_floorplan: bool
npoints: int | None
layer: gf.typings.LayerSpec | None
width: float | None
cross_section: CrossSectionSpec
allow_min_radius_violation: bool
[docs]
@gf.vcell
def bend_euler_all_angle(
**kwargs: Unpack[BendEulerAllAngleKwargs],
) -> gf.ComponentAllAngle:
"""Returns regular degree euler bend with arbitrary angle.
Args:
**kwargs: Arguments passed to gf.c.bend_euler_all_angle.
"""
return gf.c.bend_euler_all_angle(**(_DEFAULT_BEND_KWARGS | kwargs))
class BendCircularAllAngleKwargs(TypedDict, total=False):
"""Type definition for bend_circular_all_angle keyword arguments."""
radius: float | None
angle: float
npoints: int | None
layer: gf.typings.LayerSpec | None
width: float | None
cross_section: CrossSectionSpec
allow_min_radius_violation: bool
[docs]
@gf.vcell
def bend_circular_all_angle(
**kwargs: Unpack[BendCircularAllAngleKwargs],
) -> gf.ComponentAllAngle:
"""Returns circular bend with arbitrary angle.
Args:
**kwargs: Arguments passed to gf.c.bend_circular_all_angle.
"""
return gf.c.bend_circular_all_angle(
**(_DEFAULT_BEND_KWARGS | _BEND_CIRCULAR_DEFAULTS | kwargs)
)
def add_etch_gap(
c: gf.Component | gf.ComponentAllAngle,
port: gf.Port,
cross_section: CrossSectionSpec,
) -> gf.ComponentReference | VInstance:
"""Adds an etch gap rectangle at the given port of the component.
Args:
c: Component to which the etch gap will be added.
port: Port where the etch gap will be added.
cross_section: Cross-section specification to determine etch dimensions.
The etch width is taken from a :class:`~Section` that includes "etch" in its name.
"""
cross_section = gf.get_cross_section(cross_section)
etch_section = next(
s
for s in cross_section.sections
if s.name is not None and s.name.startswith("etch")
)
etch_ref = c << rectangle(
size=(etch_section.width, cross_section.width + 2 * etch_section.width),
layer=etch_section.layer,
centered=True,
)
etch_ref.transform(port.dcplx_trans * DCplxTrans(etch_section.width / 2, 0))
return etch_ref
if __name__ == "__main__":
show_components(
taper_cross_section,
bend_euler,
bend_circular,
tee,
bend_s,
straight,
coupler_ring,
coupler_straight,
partial(straight_open, length=20),
partial(straight_double_open, length=20),
straight_all_angle,
partial(bend_euler_all_angle, angle=33),
rectangle,
spacing=50,
)