Skip to content

PCells

Parametric Cells for the Generic PDK.

Consider them a foundation from which you can draw inspiration. Feel free to modify their cross-sections and layers to tailor a unique PDK suited for any foundry of your choice.

By doing so, you'll possess a versatile, retargetable PDK, empowering you to design your circuits with speed and flexibility.

components

analog

inductor

inductor(
    width: float = 2.0,
    space: float = 2.1,
    diameter: float = 25.35,
    resistance: float = 0.5777,
    inductance: float = 3.3303e-11,
    turns: int = 1,
    layer_metal: LayerSpec = "M3",
    layer_inductor: LayerSpec = "M1",
    layer_metal_pin: LayerSpec = "WG_PIN",
    layers_no_fill: LayerSpecs = ("DEVREC", "NO_TILE_SI"),
) -> Component

Create a 2-turn inductor.

Parameters:

Name Type Description Default
width float

Width of the inductor trace in micrometers.

2.0
space float

Space between turns in micrometers.

2.1
diameter float

Inner diameter in micrometers.

25.35
resistance float

Resistance in ohms.

0.5777
inductance float

Inductance in henries.

3.3303e-11
turns int

Number of turns (default 1 for inductor2).

1
layer_metal LayerSpec

Layer for the metal trace.

'M3'
layer_inductor LayerSpec

Layer for the inductor region.

'M1'
layer_metal_pin LayerSpec

Layer for the metal pins.

'WG_PIN'
layers_no_fill LayerSpecs

Layers to exclude from fill.

('DEVREC', 'NO_TILE_SI')

Returns:

Type Description
Component

Component with inductor layout.

Source code in gdsfactory/components/analog/inductors.py
 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
@gf.cell_with_module_name(schematic_function=inductor_schematic, tags=["analog"])
def inductor(
    width: float = 2.0,
    space: float = 2.1,
    diameter: float = 25.35,
    resistance: float = 0.5777,
    inductance: float = 33.303e-12,
    turns: int = 1,
    layer_metal: LayerSpec = "M3",
    layer_inductor: LayerSpec = "M1",
    layer_metal_pin: LayerSpec = "WG_PIN",
    layers_no_fill: LayerSpecs = ("DEVREC", "NO_TILE_SI"),
) -> Component:
    """Create a 2-turn inductor.

    Args:
        width: Width of the inductor trace in micrometers.
        space: Space between turns in micrometers.
        diameter: Inner diameter in micrometers.
        resistance: Resistance in ohms.
        inductance: Inductance in henries.
        turns: Number of turns (default 1 for inductor2).
        layer_metal: Layer for the metal trace.
        layer_inductor: Layer for the inductor region.
        layer_metal_pin: Layer for the metal pins.
        layers_no_fill: Layers to exclude from fill.

    Returns:
        Component with inductor layout.
    """
    c = Component()

    # Grid fixing for manufacturing constraints
    grid = 0.01
    w = round(width / (2 * grid)) * 2 * grid
    s = round(space / grid) * grid
    d = round(diameter / (2 * grid)) * 2 * grid

    # Calculate geometry parameters
    r = d / 2 + s
    octagon_center_y = 3 * r
    pi_over_4 = math.radians(45)

    path_points = []
    path_points.append((+space / 2, octagon_center_y - r * math.cos(pi_over_4 / 2)))

    for i in range(-2, 6):
        angle = i * pi_over_4 + pi_over_4 / 2
        r = d / 2 + s
        x = r * math.cos(angle)
        y = r * math.sin(angle) + octagon_center_y

        if -2 <= i < 2:
            path_points.append((x, y))
        else:
            path_points.append((x, y))

    path_points.append((-space / 2, octagon_center_y - r * math.cos(pi_over_4 / 2)))

    # Create the path
    path = gf.Path(path_points)
    c = gf.path.extrude(path, layer=layer_metal, width=w)

    # Adding ports
    length = 2 * r + s

    port1_trace = c << gf.components.rectangle(size=(s, length), layer=layer_metal)
    port1_trace.move((-s - s / 2, 0))
    c.add_port(name="P1", center=(-s, s), width=s, orientation=270, layer=layer_metal)

    port2_trace = c << gf.components.rectangle(size=(s, length), layer=layer_metal)
    port2_trace.move((s - s / 2, 0))
    c.add_port(name="P2", center=(+s, s), width=s, orientation=270, layer=layer_metal)

    # Add IND layer
    outer_polygon_pts = []
    for i in range(8):
        r_outer = (d / 2 + length) / (math.cos(pi_over_4 / 2))
        angle = i * pi_over_4 + pi_over_4 / 2
        x = r_outer * math.cos(angle)
        y = r_outer * math.sin(angle) + octagon_center_y
        outer_polygon_pts.append((x, y))

    c.add_polygon(points=outer_polygon_pts, layer=layer_inductor)

    # Add No fill layers
    for layer in layers_no_fill:
        c.add_polygon(points=outer_polygon_pts, layer=layer)

    # Adding pins
    pin_1_trace = c << gf.components.rectangle(size=(s, s), layer=layer_metal_pin)
    pin_1_trace.move((s / 2, 0))

    pin_2_trace = c << gf.components.rectangle(size=(s, s), layer=layer_metal_pin)
    pin_2_trace.move((-s - s / 2, 0))

    # Add metadata
    c.info["resistance"] = resistance
    c.info["inductance"] = inductance
    c.info["model"] = "inductor2"
    c.info["turns"] = turns
    c.info["width"] = width
    c.info["space"] = space
    c.info["diameter"] = diameter
    return c

inductor

interdigital_capacitor

interdigital_capacitor

interdigital_capacitor(
    fingers: int = 4,
    finger_length: float | int = 20.0,
    finger_gap: float | int = 2.0,
    thickness: float | int = 5.0,
    layer: LayerSpec = "WG",
) -> Component

Generate an interdigital capacitor component with ports on both ends.

An interdigital capacitor consists of interleaved metal fingers that create a distributed capacitance. This component creates a planar capacitor with two sets of interleaved fingers extending from opposite ends.

See for example Zhu et al., Accurate circuit model of interdigital capacitor and its application to design of new quasi-lumped miniaturized filters with suppression of harmonic resonance, doi: 10.1109/22.826833.

Note

finger_length=0 effectively provides a parallel plate capacitor. The capacitance scales approximately linearly with the number of fingers and finger length.

Parameters:

Name Type Description Default
fingers int

Total number of fingers of the capacitor (must be >= 1).

4
finger_length float | int

Length of each finger in μm.

20.0
finger_gap float | int

Gap between adjacent fingers in μm.

2.0
thickness float | int

Thickness of fingers and the base section in μm.

5.0
layer LayerSpec

Layer specification for the capacitor geometry.

'WG'

Returns:

Name Type Description
Component Component

A gdsfactory component with the interdigital capacitor geometry

Component

and two ports ('o1' and 'o2') on opposing sides.

Source code in gdsfactory/components/analog/interdigital_capacitor.py
 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
 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
@gf.cell_with_module_name(schematic_function=capacitor_schematic, tags=["analog"])
def interdigital_capacitor(
    fingers: int = 4,
    finger_length: float | int = 20.0,
    finger_gap: float | int = 2.0,
    thickness: float | int = 5.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Generate an interdigital capacitor component with ports on both ends.

    An interdigital capacitor consists of interleaved metal fingers that create
    a distributed capacitance. This component creates a planar capacitor with
    two sets of interleaved fingers extending from opposite ends.

    See for example Zhu et al., `Accurate circuit model of interdigital
    capacitor and its application to design of new quasi-lumped miniaturized
    filters with suppression of harmonic resonance`, doi: 10.1109/22.826833.

    Note:
        ``finger_length=0`` effectively provides a parallel plate capacitor.
        The capacitance scales approximately linearly with the number of fingers
        and finger length.

    Args:
        fingers: Total number of fingers of the capacitor (must be >= 1).
        finger_length: Length of each finger in μm.
        finger_gap: Gap between adjacent fingers in μm.
        thickness: Thickness of fingers and the base section in μm.
        layer: Layer specification for the capacitor geometry.

    Returns:
        Component: A gdsfactory component with the interdigital capacitor geometry
        and two ports ('o1' and 'o2') on opposing sides.
    """
    c = Component()

    assert fingers >= 1, "Must have at least 1 finger"

    width = 2 * thickness + finger_length + finger_gap  # total length
    height = fingers * thickness + (fingers - 1) * finger_gap  # total height
    points_1 = [
        (0, 0),
        (0, height),
        (thickness + finger_length, height),
        (thickness + finger_length, height - thickness),
        (thickness, height - thickness),
        *chain.from_iterable(
            (
                (thickness, height - (2 * i) * (thickness + finger_gap)),
                (
                    thickness + finger_length,
                    height - (2 * i) * (thickness + finger_gap),
                ),
                (
                    thickness + finger_length,
                    height - (2 * i) * (thickness + finger_gap) - thickness,
                ),
                (thickness, height - (2 * i) * (thickness + finger_gap) - thickness),
            )
            for i in range(ceil(fingers / 2))
        ),
        (thickness, 0),
        (0, 0),
    ]

    points_2 = [
        (width, 0),
        (width, height),
        (width - thickness, height),
        *chain.from_iterable(
            (
                (
                    width - thickness,
                    height - (1 + 2 * i) * thickness - (1 + 2 * i) * finger_gap,
                ),
                (
                    width - (thickness + finger_length),
                    height - (1 + 2 * i) * thickness - (1 + 2 * i) * finger_gap,
                ),
                (
                    width - (thickness + finger_length),
                    height - (2 + 2 * i) * thickness - (1 + 2 * i) * finger_gap,
                ),
                (
                    width - thickness,
                    height - (2 + 2 * i) * thickness - (1 + 2 * i) * finger_gap,
                ),
            )
            for i in range(floor(fingers / 2))
        ),
        (width - thickness, 0),
        (width, 0),
    ]

    c.add_polygon(points_1, layer=layer)
    c.add_polygon(points_2, layer=layer)
    c.add_port(
        name="o1",
        center=(0, height / 2),
        width=thickness,
        orientation=180,
        layer=layer,
    )
    c.add_port(
        name="o2",
        center=(width, height / 2),
        width=thickness,
        orientation=0,
        layer=layer,
    )
    return c

interdigital_capacitor

interdigitated_electrodes

interdigitated_electrodes

interdigitated_electrodes(
    n_fingers: int = 10,
    finger_width: float = 0.5,
    finger_length: float = 10.0,
    finger_gap: float = 0.5,
    bus_width: float = 2.0,
    bus_length: float | None = None,
    layer: LayerSpec = "MTOP",
    port_type: str = "electrical",
) -> Component

Interdigitated electrode pattern.

Two horizontal bus bars (top and bottom) with alternating fingers extending from each bus toward the opposite one. Fingers from the top bus extend downward and fingers from the bottom bus extend upward, interleaving with a gap between the finger tips and the opposite bus.

Parameters:

Name Type Description Default
n_fingers int

Total number of fingers (split between top and bottom buses).

10
finger_width float

Width of each finger in um.

0.5
finger_length float

Length of each finger in um.

10.0
finger_gap float

Gap between adjacent fingers (edge to edge) in um.

0.5
bus_width float

Width (height) of each bus bar in um.

2.0
bus_length float | None

Length of each bus bar in um. Defaults to the total width needed to accommodate all fingers.

None
layer LayerSpec

Layer specification for all geometry.

'MTOP'
port_type str

Port type for electrical ports at bus bar ends.

'electrical'
Source code in gdsfactory/components/analog/interdigitated_electrodes.py
 10
 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
 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
@gf.cell_with_module_name(tags=["analog"])
def interdigitated_electrodes(
    n_fingers: int = 10,
    finger_width: float = 0.5,
    finger_length: float = 10.0,
    finger_gap: float = 0.5,
    bus_width: float = 2.0,
    bus_length: float | None = None,
    layer: LayerSpec = "MTOP",
    port_type: str = "electrical",
) -> Component:
    """Interdigitated electrode pattern.

    Two horizontal bus bars (top and bottom) with alternating fingers extending
    from each bus toward the opposite one. Fingers from the top bus extend
    downward and fingers from the bottom bus extend upward, interleaving
    with a gap between the finger tips and the opposite bus.

    Args:
        n_fingers: Total number of fingers (split between top and bottom buses).
        finger_width: Width of each finger in um.
        finger_length: Length of each finger in um.
        finger_gap: Gap between adjacent fingers (edge to edge) in um.
        bus_width: Width (height) of each bus bar in um.
        bus_length: Length of each bus bar in um. Defaults to the total width
            needed to accommodate all fingers.
        layer: Layer specification for all geometry.
        port_type: Port type for electrical ports at bus bar ends.
    """
    c = Component()

    finger_pitch = finger_width + finger_gap
    total_finger_span = n_fingers * finger_width + (n_fingers - 1) * finger_gap

    if bus_length is None:
        bus_length = total_finger_span + 2 * finger_gap

    # Vertical extent:
    #   bottom bus: y in [-bus_width - finger_length - finger_gap/2, -finger_length - finger_gap/2]
    #   bottom fingers extend up from bottom bus
    #   top fingers extend down from top bus
    #   top bus: y in [finger_length + finger_gap/2, finger_length + finger_gap/2 + bus_width]

    gap_half = finger_gap / 2  # gap between finger tip and opposite bus
    top_bus_bottom = finger_length + gap_half
    top_bus_top = top_bus_bottom + bus_width
    bottom_bus_top = -(finger_length + gap_half)
    bottom_bus_bottom = bottom_bus_top - bus_width

    # Bottom bus bar
    c.add_polygon(
        [
            (-bus_length / 2, bottom_bus_bottom),
            (bus_length / 2, bottom_bus_bottom),
            (bus_length / 2, bottom_bus_top),
            (-bus_length / 2, bottom_bus_top),
        ],
        layer=layer,
    )

    # Top bus bar
    c.add_polygon(
        [
            (-bus_length / 2, top_bus_bottom),
            (bus_length / 2, top_bus_bottom),
            (bus_length / 2, top_bus_top),
            (-bus_length / 2, top_bus_top),
        ],
        layer=layer,
    )

    # Fingers: center the finger array horizontally
    x_start = -total_finger_span / 2 + finger_width / 2

    for i in range(n_fingers):
        x_center = x_start + i * finger_pitch
        x_left = x_center - finger_width / 2
        x_right = x_center + finger_width / 2

        if i % 2 == 0:
            # Bottom bus finger: extends upward from bottom bus top
            c.add_polygon(
                [
                    (x_left, bottom_bus_top),
                    (x_right, bottom_bus_top),
                    (x_right, bottom_bus_top + finger_length),
                    (x_left, bottom_bus_top + finger_length),
                ],
                layer=layer,
            )
        else:
            # Top bus finger: extends downward from top bus bottom
            c.add_polygon(
                [
                    (x_left, top_bus_bottom),
                    (x_right, top_bus_bottom),
                    (x_right, top_bus_bottom - finger_length),
                    (x_left, top_bus_bottom - finger_length),
                ],
                layer=layer,
            )

    # Electrical ports at bus bar ends
    c.add_port(
        name="bot_left",
        center=(-bus_length / 2, (bottom_bus_bottom + bottom_bus_top) / 2),
        width=bus_width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )
    c.add_port(
        name="bot_right",
        center=(bus_length / 2, (bottom_bus_bottom + bottom_bus_top) / 2),
        width=bus_width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )
    c.add_port(
        name="top_left",
        center=(-bus_length / 2, (top_bus_bottom + top_bus_top) / 2),
        width=bus_width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )
    c.add_port(
        name="top_right",
        center=(bus_length / 2, (top_bus_bottom + top_bus_top) / 2),
        width=bus_width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    if port_type == "electrical":
        bot_ports = [p for p in c.ports if p.name and p.name.startswith("bot_")]
        top_ports = [p for p in c.ports if p.name and p.name.startswith("top_")]
        if bot_ports:
            c.create_pin(ports=bot_ports, name="bot")
        if top_ports:
            c.create_pin(ports=top_ports, name="top")

    return c

interdigitated_electrodes

bends

bend_circular

bend_circular

bend_circular(
    radius: float | None = None,
    angle: float = 90.0,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component

Returns a radial arc.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
angle float

angle of arc (degrees).

90.0
npoints int | None

number of points.

None
angular_step float | None

If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

spec (CrossSection, string or dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_circular.py
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
@gf.cell_with_module_name(schematic_function=bend_schematic, tags=["bends"])
def bend_circular(
    radius: float | None = None,
    angle: float = 90.0,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: gf.typings.LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component:
    """Returns a radial arc.

    Args:
        radius: in um. Defaults to cross_section_radius.
        angle: angle of arc (degrees).
        npoints: number of points.
        angular_step: If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: spec (CrossSection, string or dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
    """
    if angle not in {90, 180}:
        warnings.warn(
            f"bend_euler angle should be 90 or 180. Got {angle}. Use bend_euler_all_angle instead.",
            UserWarning,
            stacklevel=3,
        )
    return _bend_circular(
        radius=radius,
        angle=angle,
        npoints=npoints,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        all_angle=False,
        angular_step=angular_step,
    )

bend_circular_all_angle

bend_circular_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle

Returns a radial arc.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
angle float

angle of arc (degrees).

90.0
npoints int | None

number of points.

None
angular_step float | None

If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

spec (CrossSection, string or dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_circular.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
@gf.vcell
def bend_circular_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: gf.typings.LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle:
    """Returns a radial arc.

    Args:
        radius: in um. Defaults to cross_section_radius.
        angle: angle of arc (degrees).
        npoints: number of points.
        angular_step: If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: spec (CrossSection, string or dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
    """
    return _bend_circular(
        radius=radius,
        angle=angle,
        npoints=npoints,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        all_angle=True,
        angular_step=angular_step,
    )

bend_circular

bend_circular_all_angle

bend_circular_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle

Returns a radial arc.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
angle float

angle of arc (degrees).

90.0
npoints int | None

number of points.

None
angular_step float | None

If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

spec (CrossSection, string or dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_circular.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
@gf.vcell
def bend_circular_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: gf.typings.LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle:
    """Returns a radial arc.

    Args:
        radius: in um. Defaults to cross_section_radius.
        angle: angle of arc (degrees).
        npoints: number of points.
        angular_step: If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: spec (CrossSection, string or dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
    """
    return _bend_circular(
        radius=radius,
        angle=angle,
        npoints=npoints,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        all_angle=True,
        angular_step=angular_step,
    )
import gdsfactory as gf

gf.gpdk.PDK.activate()

c = gf.components.bend_circular_all_angle(angle=90, cross_section='strip', allow_min_radius_violation=False).copy()
c.draw_ports()
c.plot()

bend_circular_heater

bend_circular_heater

bend_circular_heater(
    radius: float | None = None,
    angle: float = 90,
    npoints: int | None = None,
    heater_to_wg_distance: float = 1.2,
    heater_width: float = 0.5,
    layer_heater: LayerSpec = "HEATER",
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component

Creates an arc of arclength theta starting at angle start_angle.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section.radius.

None
angle float

angle of arc (degrees).

90
npoints int | None

Number of points used per 360 degrees.

None
heater_to_wg_distance float

in um.

1.2
heater_width float

in um.

0.5
layer_heater LayerSpec

for heater.

'HEATER'
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_circular_heater.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
@gf.cell_with_module_name(schematic_function=bend_schematic, tags=["bends"])
def bend_circular_heater(
    radius: float | None = None,
    angle: float = 90,
    npoints: int | None = None,
    heater_to_wg_distance: float = 1.2,
    heater_width: float = 0.5,
    layer_heater: LayerSpec = "HEATER",
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component:
    """Creates an arc of arclength `theta` starting at angle `start_angle`.

    Args:
        radius: in um. Defaults to cross_section.radius.
        angle: angle of arc (degrees).
        npoints: Number of points used per 360 degrees.
        heater_to_wg_distance: in um.
        heater_width: in um.
        layer_heater: for heater.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
    """
    x = gf.get_cross_section(cross_section)
    radius = radius or x.radius
    assert radius is not None
    width = x.width

    offset = heater_to_wg_distance + width / 2
    s1 = gf.Section(
        width=heater_width,
        offset=+offset,
        layer=layer_heater,
    )
    s2 = gf.Section(
        width=heater_width,
        offset=-offset,
        layer=layer_heater,
    )
    sections = list(x.sections) + [s1, s2]

    xs = x.copy(sections=tuple(sections))
    p = arc(radius=radius, angle=angle, npoints=npoints)

    c = Component()
    path = p.extrude(xs)
    ref = c << path
    c.add_ports(ref.ports)
    c.info["length"] = p.length()
    c.info["dx"] = float(abs(p.points[0][0] - p.points[-1][0]))
    c.info["dy"] = float(abs(p.points[0][0] - p.points[-1][0]))
    if not allow_min_radius_violation:
        x.validate_radius(radius)
    c.flatten()
    return c

bend_circular_heater

bend_euler

bend_euler

bend_euler(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component

Regular degree euler bend.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
angle float

total angle of the curve.

90.0
p float

Proportion of the curve that is an Euler curve.

0.5
with_arc_floorplan bool

if True the size of the bend will be adjusted to match an arc bend with the specified radius. If False: radius is the minimum radius of curvature.

True
npoints int | None

Number of points used per 360 degrees.

None
angular_step float | None

if not None, the angle step in degrees for the all_angle bend.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_euler.py
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
@gf.cell_with_module_name(schematic_function=bend_schematic, tags=["bends"])
def bend_euler(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component:
    """Regular degree euler bend.

    Args:
        radius: in um. Defaults to cross_section_radius.
        angle: total angle of the curve.
        p: Proportion of the curve that is an Euler curve.
        with_arc_floorplan: if True the size of the bend will be adjusted to match an arc bend with the specified radius. If False: `radius` is the minimum radius of curvature.
        npoints: Number of points used per 360 degrees.
        angular_step: if not None, the angle step in degrees for the all_angle bend.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
    """
    if abs(angle) not in {90, 180}:
        warnings.warn(
            f"bend_euler angle should be 90 or 180. Got {angle}. Use bend_euler_all_angle instead.",
            UserWarning,
            stacklevel=3,
        )

    return _bend_euler(
        radius=radius,
        angle=angle,
        p=p,
        with_arc_floorplan=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        all_angle=False,
    )

bend_euler_all_angle

bend_euler_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle

Regular degree euler bend.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
angle float

total angle of the curve.

90.0
p float

Proportion of the curve that is an Euler curve.

0.5
with_arc_floorplan bool

If False: radius is the minimum radius of curvature

True
npoints int | None

Number of points used per 360 degrees.

None
angular_step float | None

if not None, the angle step in degrees for the all_angle bend.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_euler.py
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
@gf.vcell
def bend_euler_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: gf.typings.LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle:
    """Regular degree euler bend.

    Args:
        radius: in um. Defaults to cross_section_radius.
        angle: total angle of the curve.
        p: Proportion of the curve that is an Euler curve.
        with_arc_floorplan: If False: `radius` is the minimum radius of curvature
        npoints: Number of points used per 360 degrees.
        angular_step: if not None, the angle step in degrees for the all_angle bend.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.

    """
    return _bend_euler(
        radius=radius,
        angle=angle,
        p=p,
        with_arc_floorplan=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        all_angle=True,
    )

bend_euler_s

bend_euler_s(
    radius: float | None = None,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component

Sbend made of 2 euler bends.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
p float

Proportion of the curve that is an Euler curve.

0.5
with_arc_floorplan bool

If False: radius is the minimum radius of curvature.

True
npoints int | None

Number of points used per 360 degrees.

None
angular_step float | None

if not None, the angle step in degrees for the all_angle bend.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
port1 str

input port name.

'o1'
port2 str

output port name.

        _____ o2
       /
      /
     /
    /
    |
   /
  /
 /

o1_____/

'o2'
Source code in gdsfactory/components/bends/bend_euler.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_euler_s(
    radius: float | None = None,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component:
    r"""Sbend made of 2 euler bends.

    Args:
        radius: in um. Defaults to cross_section_radius.
        p: Proportion of the curve that is an Euler curve.
        with_arc_floorplan: If False: `radius` is the minimum radius of curvature.
        npoints: Number of points used per 360 degrees.
        angular_step: if not None, the angle step in degrees for the all_angle bend.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        port1: input port name.
        port2: output port name.

                        _____ o2
                       /
                      /
                     /
                    /
                    |
                   /
                  /
                 /
         o1_____/

    """
    c = Component()
    b = bend_euler(
        radius=radius,
        p=p,
        with_arc_floorplan=with_arc_floorplan,
        npoints=npoints,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        angular_step=angular_step,
    )
    b1 = c.add_ref(b)
    b2 = c.add_ref(b)
    b2.connect(port1, b1[port2], mirror=True)
    c.add_port(port1, port=b1[port1])
    c.add_port(port2, port=b2[port2])
    c.info["length"] = 2 * b.info["length"]
    return c

bend_euler

bend_euler_all_angle

bend_euler_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle

Regular degree euler bend.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
angle float

total angle of the curve.

90.0
p float

Proportion of the curve that is an Euler curve.

0.5
with_arc_floorplan bool

If False: radius is the minimum radius of curvature

True
npoints int | None

Number of points used per 360 degrees.

None
angular_step float | None

if not None, the angle step in degrees for the all_angle bend.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
Source code in gdsfactory/components/bends/bend_euler.py
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
@gf.vcell
def bend_euler_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: gf.typings.LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> ComponentAllAngle:
    """Regular degree euler bend.

    Args:
        radius: in um. Defaults to cross_section_radius.
        angle: total angle of the curve.
        p: Proportion of the curve that is an Euler curve.
        with_arc_floorplan: If False: `radius` is the minimum radius of curvature
        npoints: Number of points used per 360 degrees.
        angular_step: if not None, the angle step in degrees for the all_angle bend.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.

    """
    return _bend_euler(
        radius=radius,
        angle=angle,
        p=p,
        with_arc_floorplan=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        all_angle=True,
    )
import gdsfactory as gf

gf.gpdk.PDK.activate()

c = gf.components.bend_euler_all_angle(angle=90, p=0.5, with_arc_floorplan=True, cross_section='strip', allow_min_radius_violation=False).copy()
c.draw_ports()
c.plot()

bend_euler_s

bend_euler_s(
    radius: float | None = None,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component

Sbend made of 2 euler bends.

Parameters:

Name Type Description Default
radius float | None

in um. Defaults to cross_section_radius.

None
p float

Proportion of the curve that is an Euler curve.

0.5
with_arc_floorplan bool

If False: radius is the minimum radius of curvature.

True
npoints int | None

Number of points used per 360 degrees.

None
angular_step float | None

if not None, the angle step in degrees for the all_angle bend.

None
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
port1 str

input port name.

'o1'
port2 str

output port name.

        _____ o2
       /
      /
     /
    /
    |
   /
  /
 /

o1_____/

'o2'
Source code in gdsfactory/components/bends/bend_euler.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_euler_s(
    radius: float | None = None,
    p: float = 0.5,
    with_arc_floorplan: bool = True,
    npoints: int | None = None,
    angular_step: float | None = None,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component:
    r"""Sbend made of 2 euler bends.

    Args:
        radius: in um. Defaults to cross_section_radius.
        p: Proportion of the curve that is an Euler curve.
        with_arc_floorplan: If False: `radius` is the minimum radius of curvature.
        npoints: Number of points used per 360 degrees.
        angular_step: if not None, the angle step in degrees for the all_angle bend.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        port1: input port name.
        port2: output port name.

                        _____ o2
                       /
                      /
                     /
                    /
                    |
                   /
                  /
                 /
         o1_____/

    """
    c = Component()
    b = bend_euler(
        radius=radius,
        p=p,
        with_arc_floorplan=with_arc_floorplan,
        npoints=npoints,
        layer=layer,
        width=width,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        angular_step=angular_step,
    )
    b1 = c.add_ref(b)
    b2 = c.add_ref(b)
    b2.connect(port1, b1[port2], mirror=True)
    c.add_port(port1, port=b1[port1])
    c.add_port(port2, port=b2[port2])
    c.info["length"] = 2 * b.info["length"]
    return c

bend_euler_s

bend_s

bend_s

bend_s(
    size: Size = (11.0, 1.8),
    npoints: int = 99,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    width: float | None = None,
) -> 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

Parameters:

Name Type Description Default
size Size

in x and y direction.

(11.0, 1.8)
npoints int

number of points.

99
cross_section CrossSectionSpec

spec.

'strip'
allow_min_radius_violation bool

bool.

False
width float | None

width to use. Defaults to cross_section.width.

None
Source code in gdsfactory/components/bends/bend_s.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_s(
    size: Size = (11.0, 1.8),
    npoints: int = 99,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    width: float | None = None,
) -> 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:
        size: in x and y direction.
        npoints: number of points.
        cross_section: spec.
        allow_min_radius_violation: bool.
        width: width to use. Defaults to cross_section.width.

    """
    dx, dy = size

    if dy == 0:
        return gf.components.straight(
            length=dx, cross_section=cross_section, width=width
        )

    return bezier(
        control_points=((0, 0), (dx / 2, 0), (dx / 2, dy), (dx, dy)),
        npoints=npoints,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        width=width,
    )

bend_s_offset

bend_s_offset(
    offset: float = 40.0,
    radius: float | None = 10.0,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
    with_euler: bool | None = None,
    p: float = 1,
    with_arc_floorplan: bool = False,
    npoints: int | None = None,
    angular_step: float | None = None,
) -> gf.Component

Return S bend made of two bends with a straight section.

Parameters:

Name Type Description Default
offset float

in um.

40.0
radius float | None

in um. if None, uses cross_section_radius.

10.0
cross_section CrossSectionSpec

spec.

'strip'
width float | None

width to use. Defaults to cross_section.width.

None
with_euler bool | None

deprecated, use p=0 for circular arc instead.

None
p float

1 means standard Euler bend. 0 means circular arc.

1
with_arc_floorplan bool

if True the size of the bend will be adjusted to match an arc bend with the specified radius. If False: radius is the minimum radius of curvature.

False
npoints int | None

number of points.

None
angular_step float | None

If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.

None
Source code in gdsfactory/components/bends/bend_s.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_s_offset(
    offset: float = 40.0,
    radius: float | None = 10.0,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
    with_euler: bool | None = None,
    p: float = 1,
    with_arc_floorplan: bool = False,
    npoints: int | None = None,
    angular_step: float | None = None,
) -> gf.Component:
    """Return S bend made of two bends with a straight section.

    Args:
        offset: in um.
        radius: in um. if None, uses cross_section_radius.
        cross_section: spec.
        width: width to use. Defaults to cross_section.width.
        with_euler: deprecated, use p=0 for circular arc instead.
        p: 1 means standard Euler bend. 0 means circular arc.
        with_arc_floorplan: if True the size of the bend will be adjusted to match an arc bend with the specified radius. If False: `radius` is the minimum radius of curvature.
        npoints: number of points.
        angular_step: If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.
    """
    if with_euler is not None:
        warnings.warn(
            "with_euler is deprecated. Use p=0 for circular arc instead. And p=1 for euler bend.",
            DeprecationWarning,
            stacklevel=2,
        )
        if not with_euler:
            p = 0

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

    radius = radius or xs.radius
    assert radius is not None, "radius cannot be None"

    xs.validate_radius(radius)
    angle, middle_length = _get_euler_sbend_angle_middle_length_from_jog(
        jog=abs(offset) / 2, radius=radius, p=p, use_eff=with_arc_floorplan
    )
    angle = math.copysign(angle, offset)
    path = gf.path.euler(
        radius=radius,
        angle=+angle,
        p=p,
        use_eff=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
    )
    if middle_length > 1e-6:
        path += gf.path.straight(length=middle_length)
    path += gf.path.euler(
        radius=radius,
        angle=-angle,
        p=p,
        use_eff=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
    )

    return gf.path.extrude(path, cross_section=xs)

bezier

bezier(
    control_points: Coordinates = (
        (0.0, 0.0),
        (5.0, 0.0),
        (5.0, 1.8),
        (10.0, 1.8),
    ),
    npoints: int = 201,
    with_manhattan_facing_angles: bool = True,
    start_angle: int | None = None,
    end_angle: int | None = None,
    cross_section: CrossSectionSpec = "strip",
    bend_radius_error_type: ErrorType | None = None,
    allow_min_radius_violation: bool = False,
    width: float | None = None,
) -> Component

Returns Bezier bend.

Parameters:

Name Type Description Default
control_points Coordinates

list of points.

((0.0, 0.0), (5.0, 0.0), (5.0, 1.8), (10.0, 1.8))
npoints int

number of points varying between 0 and 1.

201
with_manhattan_facing_angles bool

bool.

True
start_angle int | None

optional start angle in deg.

None
end_angle int | None

optional end angle in deg.

None
cross_section CrossSectionSpec

spec.

'strip'
bend_radius_error_type ErrorType | None

error type.

None
allow_min_radius_violation bool

bool.

False
width float | None

width to use. Defaults to cross_section.width.

None
Source code in gdsfactory/components/bends/bend_s.py
 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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bezier(
    control_points: Coordinates = ((0.0, 0.0), (5.0, 0.0), (5.0, 1.8), (10.0, 1.8)),
    npoints: int = 201,
    with_manhattan_facing_angles: bool = True,
    start_angle: int | None = None,
    end_angle: int | None = None,
    cross_section: CrossSectionSpec = "strip",
    bend_radius_error_type: ErrorType | None = None,
    allow_min_radius_violation: bool = False,
    width: float | None = None,
) -> Component:
    """Returns Bezier bend.

    Args:
        control_points: list of points.
        npoints: number of points varying between 0 and 1.
        with_manhattan_facing_angles: bool.
        start_angle: optional start angle in deg.
        end_angle: optional end angle in deg.
        cross_section: spec.
        bend_radius_error_type: error type.
        allow_min_radius_violation: bool.
        width: width to use. Defaults to cross_section.width.
    """
    if width:
        xs = gf.get_cross_section(cross_section, width=width)
    else:
        xs = gf.get_cross_section(cross_section)

    t = np.linspace(0, 1, npoints)
    path_points = bezier_curve(t, control_points)
    path = gf.Path(path_points)

    if with_manhattan_facing_angles:
        path.start_angle = start_angle or snap_angle(path.start_angle)
        path.end_angle = end_angle or snap_angle(path.end_angle)

    c = path.extrude(xs)
    curv = curvature(path_points, t)
    length = path.length()
    if max(np.abs(curv)) == 0:
        min_bend_radius = np.inf
    else:
        min_bend_radius = float(gf.snap.snap_to_grid(float(1 / np.max(np.abs(curv)))))

    c.info["length"] = length
    c.info["min_bend_radius"] = min_bend_radius
    c.info["start_angle"] = float(path.start_angle)
    c.info["end_angle"] = float(path.end_angle)
    c.add_route_info(
        cross_section=xs,
        length=c.info["length"],
        n_bend_s=1,
        min_bend_radius=min_bend_radius,
    )

    if not allow_min_radius_violation:
        xs.validate_radius(min_bend_radius, bend_radius_error_type)

    xs.add_bbox(c)
    return c

bezier_curve

bezier_curve(
    t: NDArray[floating[Any]], control_points: Coordinates
) -> npt.NDArray[np.floating[Any]]

Returns bezier coordinates.

Parameters:

Name Type Description Default
t NDArray[floating[Any]]

1D array of points varying between 0 and 1.

required
control_points Coordinates

for the bezier curve.

required
Source code in gdsfactory/components/bends/bend_s.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def bezier_curve(
    t: npt.NDArray[np.floating[Any]], control_points: Coordinates
) -> npt.NDArray[np.floating[Any]]:
    """Returns bezier coordinates.

    Args:
        t: 1D array of points varying between 0 and 1.
        control_points: for the bezier curve.
    """
    from scipy.special import binom

    xs = 0.0
    ys = 0.0
    n = len(control_points) - 1
    for k in range(n + 1):
        ank = binom(n, k) * (1 - t) ** (n - k) * t**k
        xs += ank * control_points[k][0]
        ys += ank * control_points[k][1]

    return np.column_stack([xs, ys])

find_min_curv_bezier_control_points

find_min_curv_bezier_control_points(
    start_point: Coordinate,
    end_point: Coordinate,
    start_angle: float,
    end_angle: float,
    npoints: int = 201,
    alpha: float = 0.05,
    nb_pts: int = 2,
) -> Coordinates

Returns bezier control points that minimize curvature.

Parameters:

Name Type Description Default
start_point Coordinate

start point.

required
end_point Coordinate

end point.

required
start_angle float

start angle in deg.

required
end_angle float

end angle in deg.

required
npoints int

number of points varying between 0 and 1.

201
alpha float

weight for angle mismatch.

0.05
nb_pts int

number of control points.

2
Source code in gdsfactory/components/bends/bend_s.py
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
def find_min_curv_bezier_control_points(
    start_point: Coordinate,
    end_point: Coordinate,
    start_angle: float,
    end_angle: float,
    npoints: int = 201,
    alpha: float = 0.05,
    nb_pts: int = 2,
) -> Coordinates:
    """Returns bezier control points that minimize curvature.

    Args:
        start_point: start point.
        end_point: end point.
        start_angle: start angle in deg.
        end_angle: end angle in deg.
        npoints: number of points varying between 0 and 1.
        alpha: weight for angle mismatch.
        nb_pts: number of control points.
    """
    from scipy.optimize import minimize

    t = np.linspace(0, 1, npoints)

    def array_1d_to_cpts(a: npt.NDArray[np.float64]) -> list[tuple[float, float]]:
        xs = a[::2]
        ys = a[1::2]
        return list(zip(xs, ys, strict=False))

    def objective_func(p: npt.NDArray[np.float64]) -> float:
        """Minimize  max curvaturea and negligible start angle and end angle mismatch."""
        ps = array_1d_to_cpts(p)
        control_points = [start_point] + ps + [end_point]
        path_points = bezier_curve(t, control_points)

        max_curv = max(np.abs(curvature(path_points, t)))

        angles = angles_deg(path_points)
        dstart_angle = abs(angles[0] - start_angle)
        dend_angle = abs(angles[-2] - end_angle)
        angle_mismatch = dstart_angle + dend_angle
        return float(angle_mismatch * alpha + max_curv)

    x0, y0 = start_point[0], start_point[1]
    xn, yn = end_point[0], end_point[1]

    initial_guess: list[float] = []
    for i in range(nb_pts):
        x = (i + 1) * (x0 + xn) / nb_pts
        y = (i + 1) * (y0 + yn) / nb_pts
        initial_guess += [x, y]

    # initial_guess = [(x0 + xn) / 2, y0, (x0 + xn) / 2, yn]
    res = minimize(objective_func, initial_guess, method="Nelder-Mead")
    p = res.x
    points = [start_point] + array_1d_to_cpts(p) + [end_point]
    return tuple(points)

get_min_sbend_size

get_min_sbend_size(
    size: tuple[float | None, float | None] = (None, 10.0),
    cross_section: CrossSectionSpec = "strip",
    num_points: int = 100,
) -> float

Returns the minimum sbend size to comply with bend radius requirements.

Parameters:

Name Type Description Default
size tuple[float | None, float | None]

in x and y direction. One of them is None, which is the size we need to figure out.

(None, 10.0)
cross_section CrossSectionSpec

spec.

'strip'
num_points int

number of points to iterate over between max_size and 0.1 * max_size.

100
Source code in gdsfactory/components/bends/bend_s.py
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
def get_min_sbend_size(
    size: tuple[float | None, float | None] = (None, 10.0),
    cross_section: CrossSectionSpec = "strip",
    num_points: int = 100,
) -> float:
    """Returns the minimum sbend size to comply with bend radius requirements.

    Args:
        size: in x and y direction. One of them is None, which is the size we need to figure out.
        cross_section: spec.
        num_points: number of points to iterate over between max_size and 0.1 * max_size.
    """
    size_list = list(size)
    cross_section_f = gf.get_cross_section(cross_section)

    if size_list[0] is None:
        ind = 0
        known_s = size_list[1]
    elif size_list[1] is None:
        ind = 1
        known_s = size_list[0]
    else:
        raise ValueError("One of the two elements in size has to be None")

    min_radius = cross_section_f.radius

    if min_radius is None:
        raise ValueError("The min radius for the specified layer is not known!")

    min_size = np.inf

    assert known_s is not None

    # Guess sizes, iterate over them until we cannot achieve the min radius
    # the max size corresponds to an ellipsoid
    max_size = float(np.sqrt(np.abs(min_radius * known_s)) * 2.5)
    sizes = np.linspace(max_size, 0.1 * max_size, num_points)

    for s in sizes:
        sz = size_list
        sz[ind] = s
        dx, dy = size_list
        assert dx is not None and dy is not None
        control_points = ((0, 0), (dx / 2, 0), (dx / 2, dy), (dx, dy))
        npoints = 201
        t = np.linspace(0, 1, npoints)
        path_points = bezier_curve(t, control_points)
        curv = curvature(path_points, t)
        min_bend_radius = 1 / max(np.abs(curv))
        if min_bend_radius < min_radius:
            min_size = s
            break

    return min_size

bend_s

bend_s_offset

bend_s_offset(
    offset: float = 40.0,
    radius: float | None = 10.0,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
    with_euler: bool | None = None,
    p: float = 1,
    with_arc_floorplan: bool = False,
    npoints: int | None = None,
    angular_step: float | None = None,
) -> gf.Component

Return S bend made of two bends with a straight section.

Parameters:

Name Type Description Default
offset float

in um.

40.0
radius float | None

in um. if None, uses cross_section_radius.

10.0
cross_section CrossSectionSpec

spec.

'strip'
width float | None

width to use. Defaults to cross_section.width.

None
with_euler bool | None

deprecated, use p=0 for circular arc instead.

None
p float

1 means standard Euler bend. 0 means circular arc.

1
with_arc_floorplan bool

if True the size of the bend will be adjusted to match an arc bend with the specified radius. If False: radius is the minimum radius of curvature.

False
npoints int | None

number of points.

None
angular_step float | None

If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.

None
Source code in gdsfactory/components/bends/bend_s.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_s_offset(
    offset: float = 40.0,
    radius: float | None = 10.0,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
    with_euler: bool | None = None,
    p: float = 1,
    with_arc_floorplan: bool = False,
    npoints: int | None = None,
    angular_step: float | None = None,
) -> gf.Component:
    """Return S bend made of two bends with a straight section.

    Args:
        offset: in um.
        radius: in um. if None, uses cross_section_radius.
        cross_section: spec.
        width: width to use. Defaults to cross_section.width.
        with_euler: deprecated, use p=0 for circular arc instead.
        p: 1 means standard Euler bend. 0 means circular arc.
        with_arc_floorplan: if True the size of the bend will be adjusted to match an arc bend with the specified radius. If False: `radius` is the minimum radius of curvature.
        npoints: number of points.
        angular_step: If provided, determines the angular step (in degrees) between points. Mutually exclusive with npoints.
    """
    if with_euler is not None:
        warnings.warn(
            "with_euler is deprecated. Use p=0 for circular arc instead. And p=1 for euler bend.",
            DeprecationWarning,
            stacklevel=2,
        )
        if not with_euler:
            p = 0

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

    radius = radius or xs.radius
    assert radius is not None, "radius cannot be None"

    xs.validate_radius(radius)
    angle, middle_length = _get_euler_sbend_angle_middle_length_from_jog(
        jog=abs(offset) / 2, radius=radius, p=p, use_eff=with_arc_floorplan
    )
    angle = math.copysign(angle, offset)
    path = gf.path.euler(
        radius=radius,
        angle=+angle,
        p=p,
        use_eff=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
    )
    if middle_length > 1e-6:
        path += gf.path.straight(length=middle_length)
    path += gf.path.euler(
        radius=radius,
        angle=-angle,
        p=p,
        use_eff=with_arc_floorplan,
        npoints=npoints,
        angular_step=angular_step,
    )

    return gf.path.extrude(path, cross_section=xs)

bend_s_offset

bend_topic

bend_topic

bend_topic(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.1,
    npoints: int = 100,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    layer: LayerSpec | None = None,
    width: float | None = None,
) -> Component

Returns a regular degree Third Order Polynomial Interconnected Circular (TOPIC) bend component.

The implementation follows the description in this publication https://arxiv.org/html/2411.15025v1.

The bend consists of three parts: a. Initial transition from straight to bend, known as TOP segment. b. Circular part whose center and radius are calculated analytically. c. Mirroring of TOP segment with respect to the bisection of the angle.

Parameters:

Name Type Description Default
radius float | None

radius at the start and end of bend.

None
angle float

total angle of the curve in degrees.

90.0
p float

used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).

0.1
npoints int

Number of points used per 360 degrees.

100
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
Source code in gdsfactory/components/bends/bend_topic.py
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
@gf.cell_with_module_name(schematic_function=bend_schematic, tags=["bends"])
def bend_topic(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.1,
    npoints: int = 100,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    layer: LayerSpec | None = None,
    width: float | None = None,
) -> Component:
    """Returns a regular degree Third Order Polynomial Interconnected Circular (TOPIC) bend component.

    The implementation follows the description in this publication https://arxiv.org/html/2411.15025v1.

    The bend consists of three parts:
    a. Initial transition from straight to bend, known as TOP segment.
    b. Circular part whose center and radius are calculated analytically.
    c. Mirroring of TOP segment with respect to the bisection of the angle.

    Args:
        radius: radius at the start and end of bend.
        angle: total angle of the curve in degrees.
        p: used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).
        npoints: Number of points used per 360 degrees.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
    """
    if abs(angle) not in {90, 180}:
        warnings.warn(
            f"bend_topic angle should be 90 or 180. Got {angle}. Use bend_topic_all_angle instead.",
            UserWarning,
            stacklevel=3,
        )
    return _bend_topic(
        radius=radius,
        angle=angle,
        p=p,
        npoints=npoints,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        layer=layer,
        width=width,
        all_angle=False,
    )

bend_topic_all_angle

bend_topic_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.1,
    npoints: int = 100,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    layer: LayerSpec | None = None,
    width: float | None = None,
) -> ComponentAllAngle

Returns a Third Order Polynomial Interconnected Circular (TOPIC) bend component of arbitrary angle.

The implementation follows the description in this publication https://arxiv.org/html/2411.15025v1.

The bend consists of three parts: a. Initial transition from straight to bend, known as TOP segment. b. Circular part whose center and radius are calculated analytically. c. Mirroring of TOP segment with respect to the bisection of the angle.

Parameters:

Name Type Description Default
radius float | None

radius at the start and end of bend.

None
angle float

total angle of the curve in degrees.

90.0
p float

used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).

0.1
npoints int

Number of points used per 360 degrees.

100
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
Source code in gdsfactory/components/bends/bend_topic.py
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
@gf.vcell
def bend_topic_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.1,
    npoints: int = 100,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    layer: LayerSpec | None = None,
    width: float | None = None,
) -> ComponentAllAngle:
    """Returns a Third Order Polynomial Interconnected Circular (TOPIC) bend component of arbitrary angle.

    The implementation follows the description in this publication https://arxiv.org/html/2411.15025v1.

    The bend consists of three parts:
    a. Initial transition from straight to bend, known as TOP segment.
    b. Circular part whose center and radius are calculated analytically.
    c. Mirroring of TOP segment with respect to the bisection of the angle.

    Args:
        radius: radius at the start and end of bend.
        angle: total angle of the curve in degrees.
        p: used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).
        npoints: Number of points used per 360 degrees.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
    """
    return _bend_topic(
        radius=radius,
        angle=angle,
        p=p,
        npoints=npoints,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        layer=layer,
        width=width,
        all_angle=True,
    )

bend_topic_s

bend_topic_s(
    radius: float | None = None,
    p: float = 0.1,
    npoints: int = 100,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component

Sbend made of 2 topic bends.

Parameters:

Name Type Description Default
radius float | None

radius at the start and end of bend.

None
p float

used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).

0.1
npoints int

Number of points used per 360 degrees.

100
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
port1 str

input port name.

'o1'
port2 str

output port name.

        _____ o2
       /
      /
     /
    /
    |
   /
  /
 /

o1_____/

'o2'
Source code in gdsfactory/components/bends/bend_topic.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_topic_s(
    radius: float | None = None,
    p: float = 0.1,
    npoints: int = 100,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component:
    r"""Sbend made of 2 topic bends.

    Args:
        radius: radius at the start and end of bend.
        p: used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).
        npoints: Number of points used per 360 degrees.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        port1: input port name.
        port2: output port name.

                        _____ o2
                       /
                      /
                     /
                    /
                    |
                   /
                  /
                 /
         o1_____/

    """
    c = Component()
    b = bend_topic(
        radius=radius,
        angle=90,
        p=p,
        npoints=npoints,
        layer=layer,
        width=width,
        allow_min_radius_violation=allow_min_radius_violation,
        cross_section=cross_section,
    )
    b1 = c.add_ref(b)
    b2 = c.add_ref(b)
    b2.connect(port1, b1[port2], mirror=True)
    c.add_port(port1, port=b1[port1])
    c.add_port(port2, port=b2[port2])
    c.info["length"] = 2 * b.info["length"]
    return c

bend_topic

bend_topic180 module-attribute

bend_topic180 = partial(bend_topic, angle=180)

bend_topic180

bend_topic_all_angle

bend_topic_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.1,
    npoints: int = 100,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    layer: LayerSpec | None = None,
    width: float | None = None,
) -> ComponentAllAngle

Returns a Third Order Polynomial Interconnected Circular (TOPIC) bend component of arbitrary angle.

The implementation follows the description in this publication https://arxiv.org/html/2411.15025v1.

The bend consists of three parts: a. Initial transition from straight to bend, known as TOP segment. b. Circular part whose center and radius are calculated analytically. c. Mirroring of TOP segment with respect to the bisection of the angle.

Parameters:

Name Type Description Default
radius float | None

radius at the start and end of bend.

None
angle float

total angle of the curve in degrees.

90.0
p float

used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).

0.1
npoints int

Number of points used per 360 degrees.

100
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
Source code in gdsfactory/components/bends/bend_topic.py
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
@gf.vcell
def bend_topic_all_angle(
    radius: float | None = None,
    angle: float = 90.0,
    p: float = 0.1,
    npoints: int = 100,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    layer: LayerSpec | None = None,
    width: float | None = None,
) -> ComponentAllAngle:
    """Returns a Third Order Polynomial Interconnected Circular (TOPIC) bend component of arbitrary angle.

    The implementation follows the description in this publication https://arxiv.org/html/2411.15025v1.

    The bend consists of three parts:
    a. Initial transition from straight to bend, known as TOP segment.
    b. Circular part whose center and radius are calculated analytically.
    c. Mirroring of TOP segment with respect to the bisection of the angle.

    Args:
        radius: radius at the start and end of bend.
        angle: total angle of the curve in degrees.
        p: used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).
        npoints: Number of points used per 360 degrees.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
    """
    return _bend_topic(
        radius=radius,
        angle=angle,
        p=p,
        npoints=npoints,
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
        layer=layer,
        width=width,
        all_angle=True,
    )
import gdsfactory as gf

gf.gpdk.PDK.activate()

c = gf.components.bend_topic_all_angle(angle=90, p=0.1, npoints=100, cross_section='strip', allow_min_radius_violation=False).copy()
c.draw_ports()
c.plot()

bend_topic_s

bend_topic_s(
    radius: float | None = None,
    p: float = 0.1,
    npoints: int = 100,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component

Sbend made of 2 topic bends.

Parameters:

Name Type Description Default
radius float | None

radius at the start and end of bend.

None
p float

used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).

0.1
npoints int

Number of points used per 360 degrees.

100
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
allow_min_radius_violation bool

if True allows radius to be smaller than cross_section radius.

False
layer LayerSpec | None

layer to use. Defaults to cross_section.layer.

None
width float | None

width to use. Defaults to cross_section.width.

None
port1 str

input port name.

'o1'
port2 str

output port name.

        _____ o2
       /
      /
     /
    /
    |
   /
  /
 /

o1_____/

'o2'
Source code in gdsfactory/components/bends/bend_topic.py
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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bend_topic_s(
    radius: float | None = None,
    p: float = 0.1,
    npoints: int = 100,
    layer: LayerSpec | None = None,
    width: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    port1: str = "o1",
    port2: str = "o2",
) -> Component:
    r"""Sbend made of 2 topic bends.

    Args:
        radius: radius at the start and end of bend.
        p: used to calculate the angle of the bend at the end of TOP / start of circular arc, as p*angle. It should be within [0, 0.5).
        npoints: Number of points used per 360 degrees.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        allow_min_radius_violation: if True allows radius to be smaller than cross_section radius.
        layer: layer to use. Defaults to cross_section.layer.
        width: width to use. Defaults to cross_section.width.
        port1: input port name.
        port2: output port name.

                        _____ o2
                       /
                      /
                     /
                    /
                    |
                   /
                  /
                 /
         o1_____/

    """
    c = Component()
    b = bend_topic(
        radius=radius,
        angle=90,
        p=p,
        npoints=npoints,
        layer=layer,
        width=width,
        allow_min_radius_violation=allow_min_radius_violation,
        cross_section=cross_section,
    )
    b1 = c.add_ref(b)
    b2 = c.add_ref(b)
    b2.connect(port1, b1[port2], mirror=True)
    c.add_port(port1, port=b1[port1])
    c.add_port(port2, port=b2[port2])
    c.info["length"] = 2 * b.info["length"]
    return c

bend_topic_s

bezier

bezier(
    control_points: Coordinates = (
        (0.0, 0.0),
        (5.0, 0.0),
        (5.0, 1.8),
        (10.0, 1.8),
    ),
    npoints: int = 201,
    with_manhattan_facing_angles: bool = True,
    start_angle: int | None = None,
    end_angle: int | None = None,
    cross_section: CrossSectionSpec = "strip",
    bend_radius_error_type: ErrorType | None = None,
    allow_min_radius_violation: bool = False,
    width: float | None = None,
) -> Component

Returns Bezier bend.

Parameters:

Name Type Description Default
control_points Coordinates

list of points.

((0.0, 0.0), (5.0, 0.0), (5.0, 1.8), (10.0, 1.8))
npoints int

number of points varying between 0 and 1.

201
with_manhattan_facing_angles bool

bool.

True
start_angle int | None

optional start angle in deg.

None
end_angle int | None

optional end angle in deg.

None
cross_section CrossSectionSpec

spec.

'strip'
bend_radius_error_type ErrorType | None

error type.

None
allow_min_radius_violation bool

bool.

False
width float | None

width to use. Defaults to cross_section.width.

None
Source code in gdsfactory/components/bends/bend_s.py
 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
@gf.cell_with_module_name(schematic_function=sbend_schematic, tags=["bends"])
def bezier(
    control_points: Coordinates = ((0.0, 0.0), (5.0, 0.0), (5.0, 1.8), (10.0, 1.8)),
    npoints: int = 201,
    with_manhattan_facing_angles: bool = True,
    start_angle: int | None = None,
    end_angle: int | None = None,
    cross_section: CrossSectionSpec = "strip",
    bend_radius_error_type: ErrorType | None = None,
    allow_min_radius_violation: bool = False,
    width: float | None = None,
) -> Component:
    """Returns Bezier bend.

    Args:
        control_points: list of points.
        npoints: number of points varying between 0 and 1.
        with_manhattan_facing_angles: bool.
        start_angle: optional start angle in deg.
        end_angle: optional end angle in deg.
        cross_section: spec.
        bend_radius_error_type: error type.
        allow_min_radius_violation: bool.
        width: width to use. Defaults to cross_section.width.
    """
    if width:
        xs = gf.get_cross_section(cross_section, width=width)
    else:
        xs = gf.get_cross_section(cross_section)

    t = np.linspace(0, 1, npoints)
    path_points = bezier_curve(t, control_points)
    path = gf.Path(path_points)

    if with_manhattan_facing_angles:
        path.start_angle = start_angle or snap_angle(path.start_angle)
        path.end_angle = end_angle or snap_angle(path.end_angle)

    c = path.extrude(xs)
    curv = curvature(path_points, t)
    length = path.length()
    if max(np.abs(curv)) == 0:
        min_bend_radius = np.inf
    else:
        min_bend_radius = float(gf.snap.snap_to_grid(float(1 / np.max(np.abs(curv)))))

    c.info["length"] = length
    c.info["min_bend_radius"] = min_bend_radius
    c.info["start_angle"] = float(path.start_angle)
    c.info["end_angle"] = float(path.end_angle)
    c.add_route_info(
        cross_section=xs,
        length=c.info["length"],
        n_bend_s=1,
        min_bend_radius=min_bend_radius,
    )

    if not allow_min_radius_violation:
        xs.validate_radius(min_bend_radius, bend_radius_error_type)

    xs.add_bbox(c)
    return c

bezier

containers

add_fiber_array_optical_south_electrical_north

add_fiber_array_optical_south_electrical_north

add_fiber_array_optical_south_electrical_north(
    component: ComponentSpec = "straight_heater_metal",
    pad: ComponentSpec = "pad",
    grating_coupler: ComponentSpec = "grating_coupler_te",
    cross_section_metal: CrossSectionSpec = "metal_routing",
    with_loopback: bool = True,
    pad_pitch: float = 100.0,
    pitch: float = 127.0,
    pad_gc_spacing: float = 250.0,
    electrical_port_names: list[str] | None = None,
    electrical_port_orientation: AngleInDegrees | None = 90,
    npads: int | None = None,
    port_types_grating_couplers: list[str] | None = None,
    auto_taper_pads: bool = True,
    **kwargs: Any
) -> Component

Returns a fiber array with Optical gratings on South and Electrical pads on North.

This a test configuration for DC pads.

Parameters:

Name Type Description Default
component ComponentSpec

component spec to add fiber and pads.

'straight_heater_metal'
pad ComponentSpec

pad spec.

'pad'
grating_coupler ComponentSpec

grating coupler function.

'grating_coupler_te'
cross_section_metal CrossSectionSpec

metal cross section.

'metal_routing'
with_loopback bool

whether to add a loopback port.

True
pad_pitch float

spacing between pads.

100.0
pitch float

spacing between grating couplers.

127.0
pad_gc_spacing float

spacing between pads and grating couplers.

250.0
electrical_port_names list[str] | None

list of electrical port names. Defaults to all.

None
electrical_port_orientation AngleInDegrees | None

orientation of electrical ports. Defaults to 90.

90
npads int | None

number of pads. Defaults to one per electrical_port_names.

None
port_types_grating_couplers list[str] | None

port types for grating couplers. Defaults to vertical TE, TM, and dual.

None
auto_taper_pads bool

whether to add a taper to the pads.

True
kwargs Any

additional arguments.

{}

Other Parameters:

Name Type Description
layer_label

layer for settings label.

measurement

measurement name.

measurement_settings

measurement settings.

analysis

analysis name.

doe

Design of Experiment.

anchor

anchor point for the label. Defaults to south-west "sw". Valid options are: "n", "s", "e", "w", "ne", "nw", "se", "sw", "c".

gc_port_name

grating coupler input port name.

gc_port_labels

grating coupler list of labels.

component_name

optional for the label.

select_ports

function to select ports.

cross_section

cross_section function.

get_input_labels_function

function to get input labels. None skips labels.

layer_label

optional layer for grating coupler label.

bend

bend spec.

straight

straight spec.

taper

taper spec.

get_input_label_text_loopback_function

function to get input label test.

get_input_label_text_function

for labels.

fanout_length

if None, automatic calculation of fanout length.

max_y0_optical

in um.

with_loopback bool

True, adds loopback structures.

straight_separation

from edge to edge.

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.

Source code in gdsfactory/components/containers/add_fiber_array_optical_south_electrical_north.py
 10
 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
 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
@gf.cell_with_module_name(tags=["containers"])
def add_fiber_array_optical_south_electrical_north(
    component: ComponentSpec = "straight_heater_metal",
    pad: ComponentSpec = "pad",
    grating_coupler: ComponentSpec = "grating_coupler_te",
    cross_section_metal: CrossSectionSpec = "metal_routing",
    with_loopback: bool = True,
    pad_pitch: float = 100.0,
    pitch: float = 127.0,
    pad_gc_spacing: float = 250.0,
    electrical_port_names: list[str] | None = None,
    electrical_port_orientation: AngleInDegrees | None = 90,
    npads: int | None = None,
    port_types_grating_couplers: list[str] | None = None,
    auto_taper_pads: bool = True,
    **kwargs: Any,
) -> Component:
    """Returns a fiber array with Optical gratings on South and Electrical pads on North.

    This a test configuration for DC pads.

    Args:
        component: component spec to add fiber and pads.
        pad: pad spec.
        grating_coupler: grating coupler function.
        cross_section_metal: metal cross section.
        with_loopback: whether to add a loopback port.
        pad_pitch: spacing between pads.
        pitch: spacing between grating couplers.
        pad_gc_spacing: spacing between pads and grating couplers.
        electrical_port_names: list of electrical port names. Defaults to all.
        electrical_port_orientation: orientation of electrical ports. Defaults to 90.
        npads: number of pads. Defaults to one per electrical_port_names.
        port_types_grating_couplers: port types for grating couplers. Defaults to vertical TE, TM, and dual.
        auto_taper_pads: whether to add a taper to the pads.
        kwargs: additional arguments.

    Keyword Args:
        layer_label: layer for settings label.
        measurement: measurement name.
        measurement_settings: measurement settings.
        analysis: analysis name.
        doe: Design of Experiment.
        anchor: anchor point for the label. Defaults to south-west "sw". \
            Valid options are: "n", "s", "e", "w", "ne", "nw", "se", "sw", "c".
        gc_port_name: grating coupler input port name.
        gc_port_labels: grating coupler list of labels.
        component_name: optional for the label.
        select_ports: function to select ports.
        cross_section: cross_section function.
        get_input_labels_function: function to get input labels. None skips labels.
        layer_label: optional layer for grating coupler label.
        bend: bend spec.
        straight: straight spec.
        taper: taper spec.
        get_input_label_text_loopback_function: function to get input label test.
        get_input_label_text_function: for labels.
        fanout_length: if None, automatic calculation of fanout length.
        max_y0_optical: in um.
        with_loopback: True, adds loopback structures.
        straight_separation: from edge to edge.
        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.

    """
    c = Component()
    component = gf.get_component(component)
    r = c << gf.routing.add_fiber_array(
        component=component,
        grating_coupler=grating_coupler,
        with_loopback=with_loopback,
        pitch=pitch,
        **kwargs,
    )
    port_types_grating_couplers = (
        port_types_grating_couplers or gf.CONF.port_types_grating_couplers
    )
    optical_ports = [
        port for port in r.ports if port.port_type in port_types_grating_couplers
    ]
    c.add_ports(optical_ports)

    electrical_ports = r.ports.filter(
        port_type="electrical", orientation=electrical_port_orientation
    )
    electrical_port_names_list = electrical_port_names or [
        p.name for p in electrical_ports if p.name is not None
    ]

    npads = npads or len(electrical_port_names_list)
    pads = c << gf.components.array(
        component=pad,
        columns=npads,
        column_pitch=pad_pitch,
    )
    pads.x = r.x
    pads.ymin = r.ymin + pad_gc_spacing

    electrical_ports = [r[por_name] for por_name in electrical_port_names_list]
    nroutes = min(len(electrical_ports), npads)

    ports1 = electrical_ports[:nroutes]
    ports2 = list(pads.ports.filter(orientation=270))[:nroutes]

    gf.routing.route_bundle_electrical(
        c,
        ports1=ports1,
        ports2=ports2,
        cross_section=cross_section_metal,
        sort_ports=True,
        auto_taper=auto_taper_pads,
    )

    c.add_ports(ports2)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

add_fiber_array_optical_south_electrical_north

add_termination

add_termination

add_termination(
    component: ComponentSpec = "straight",
    port_names: tuple[str, ...] | None = None,
    terminator: ComponentSpec = _terminator_function,
    terminator_port_name: str | None = None,
) -> Component

Returns component with terminator on some ports.

Parameters:

Name Type Description Default
component ComponentSpec

to add terminator.

'straight'
port_names tuple[str, ...] | None

ports to add terminator.

None
terminator ComponentSpec

factory for the terminator.

_terminator_function
terminator_port_name str | None

for the terminator to connect to the component ports.

None
Source code in gdsfactory/components/containers/add_termination.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
@gf.cell_with_module_name(tags=["containers"])
def add_termination(
    component: ComponentSpec = "straight",
    port_names: tuple[str, ...] | None = None,
    terminator: ComponentSpec = _terminator_function,
    terminator_port_name: str | None = None,
) -> Component:
    """Returns component with terminator on some ports.

    Args:
        component: to add terminator.
        port_names: ports to add terminator.
        terminator: factory for the terminator.
        terminator_port_name: for the terminator to connect to the component ports.
    """
    terminator = gf.get_component(terminator)
    terminator_port_name = terminator_port_name or terminator.ports[0].name

    assert terminator_port_name is not None

    c = Component()
    component = gf.get_component(component)
    ref = c.add_ref(component)

    ports_names_all = [p.name for p in component.ports]
    ports_names_to_terminate = port_names or ports_names_all

    for port_name in ports_names_all:
        if port_name in ports_names_to_terminate:
            t_ref = c.add_ref(terminator)
            t_ref.connect(port=terminator_port_name, other=ref[port_name])
        else:
            port = ref[port_name]
            c.add_port(name=port.name, port=port)

    c.copy_child_info(component)
    return c

add_termination

add_trenches

add_trenches

add_trenches(
    component: ComponentSpec = "coupler",
    layer_component: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    width_trench: float = 2.0,
    cross_section: CrossSectionSpec | None = None,
    top: float | None = None,
    bot: float | None = None,
    right: float | None = 0,
    left: float | None = 0,
) -> gf.Component

Return inverted component with trenches.

Parameters:

Name Type Description Default
component ComponentSpec

component to add to the trenches.

'coupler'
layer_component LayerSpec

layer of the component to invert.

'WG'
layer_trench LayerSpec

layer of the trenches.

'DEEP_ETCH'
width_trench float

width of the trenches.

2.0
cross_section CrossSectionSpec | None

spec (CrossSection, string or dict).

None
top float | None

width of the trench on the top. If None uses width_trench.

None
bot float | None

width of the trench on the bottom. If None uses width_trench.

None
right float | None

width of the trench on the right. If None uses width_trench.

0
left float | None

width of the trench on the left. If None uses width_trench.

0
Source code in gdsfactory/components/containers/add_trenches.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
@gf.cell_with_module_name(tags=["containers"])
def add_trenches(
    component: ComponentSpec = "coupler",
    layer_component: LayerSpec = "WG",
    layer_trench: LayerSpec = "DEEP_ETCH",
    width_trench: float = 2.0,
    cross_section: CrossSectionSpec | None = None,
    top: float | None = None,
    bot: float | None = None,
    right: float | None = 0,
    left: float | None = 0,
) -> gf.Component:
    """Return inverted component with trenches.

    Args:
        component: component to add to the trenches.
        layer_component: layer of the component to invert.
        layer_trench: layer of the trenches.
        width_trench: width of the trenches.
        cross_section: spec (CrossSection, string or dict).
        top: width of the trench on the top. If None uses width_trench.
        bot: width of the trench on the bottom. If None uses width_trench.
        right: width of the trench on the right. If None uses width_trench.
        left: width of the trench on the left. If None uses width_trench.
    """
    component = gf.get_component(component)
    top = top if top is not None else width_trench
    bot = bot if bot is not None else width_trench
    right = right if right is not None else width_trench
    left = left if left is not None else width_trench

    core = component
    clad = gf.c.bbox(
        core, layer=layer_trench, top=top, bottom=bot, left=left, right=right
    )
    c = gf.boolean(
        clad,
        core,
        operation="not",
        layer=layer_trench,
        layer1=layer_trench,
        layer2=layer_component,
    )

    c.add_ports(component.ports)
    c.copy_child_info(component)
    if cross_section is not None:
        xs = gf.get_cross_section(cross_section)
        xs.add_bbox(c)
    return c

add_trenches

add_trenches90 module-attribute

add_trenches90 = partial(
    add_trenches,
    component="bend_euler",
    top=0,
    left=0,
    right=None,
)

add_trenches90

array

array(
    component: ComponentSpec = "pad",
    columns: int = 6,
    rows: int = 1,
    column_pitch: float = 150,
    row_pitch: float = 150,
    add_ports: bool = True,
    size: Size | None = None,
    centered: bool = False,
    post_process: PostProcesses | None = None,
    auto_rename_ports: bool = False,
) -> Component

Returns an array of components.

Parameters:

Name Type Description Default
component ComponentSpec

to replicate.

'pad'
columns int

in x.

6
rows int

in y.

1
column_pitch float

pitch between columns.

150
row_pitch float

pitch between rows.

150
auto_rename_ports bool

True to auto rename ports.

False
add_ports bool

add ports from component into the array.

True
size Size | None

Optional x, y size. Overrides columns and rows.

None
centered bool

center the array around the origin.

False
post_process PostProcesses | None

function to apply to the array after creation.

None

Raises:

Type Description
ValueError

If columns > 1 and spacing[0] = 0.

ValueError

If rows > 1 and spacing[1] = 0.

Source code in gdsfactory/components/containers/array_component.py
10
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
@gf.cell_with_module_name(tags=["containers"])
def array(
    component: ComponentSpec = "pad",
    columns: int = 6,
    rows: int = 1,
    column_pitch: float = 150,
    row_pitch: float = 150,
    add_ports: bool = True,
    size: Size | None = None,
    centered: bool = False,
    post_process: PostProcesses | None = None,
    auto_rename_ports: bool = False,
) -> Component:
    """Returns an array of components.

    Args:
        component: to replicate.
        columns: in x.
        rows: in y.
        column_pitch: pitch between columns.
        row_pitch: pitch between rows.
        auto_rename_ports: True to auto rename ports.
        add_ports: add ports from component into the array.
        size: Optional x, y size. Overrides columns and rows.
        centered: center the array around the origin.
        post_process: function to apply to the array after creation.

    Raises:
        ValueError: If columns > 1 and spacing[0] = 0.
        ValueError: If rows > 1 and spacing[1] = 0.

        2 rows x 4 columns

          column_pitch
          <---------->
         ___        ___       ___        ___
        |   |      |   |     |   |      |   |
        |___|      |___|     |___|      |___|

         ___        ___       ___        ___
        |   |      |   |     |   |      |   |
        |___|      |___|     |___|      |___|
    """
    if size:
        columns = int(size[0] / column_pitch)
        rows = int(size[1] / row_pitch)

    if rows > 1 and row_pitch == 0:
        raise ValueError(f"rows = {rows} > 1 require {row_pitch=} > 0")

    if columns > 1 and column_pitch == 0:
        raise ValueError(f"columns = {columns} > 1 require {column_pitch} > 0")

    c = Component()
    component = gf.get_component(component)
    ref = c.add_ref(
        component,
        columns=columns,
        rows=rows,
        column_pitch=column_pitch,
        row_pitch=row_pitch,
    )
    if centered:
        ref.center = (0, 0)

    if add_ports and component.ports:
        for ix in range(ref.na or 1):
            for iy in range(ref.nb or 1):
                for port in component.ports:
                    port = port.copy(ref.trans * gf.kdb.Trans(ix * ref.a + iy * ref.b))
                    name = f"{port.name}_{iy + 1}_{ix + 1}"
                    c.add_port(name, port=port)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    if post_process:
        for f in post_process:
            f(c)
    if auto_rename_ports:
        c.auto_rename_ports()
    return c

array

array_hexagonal

array_hexagonal

array_hexagonal(
    component: ComponentSpec = "circle",
    columns: int = 10,
    rows: int = 10,
    pitch: float = 25.0,
    centered: bool = True,
    add_ports: bool = True,
) -> Component

Returns a hexagonal close-packed array of components.

Even rows are placed normally, odd rows are offset by pitch/2. Row spacing is pitch * sqrt(3)/2.

Parameters:

Name Type Description Default
component ComponentSpec

component to replicate.

'circle'
columns int

number of columns.

10
rows int

number of rows.

10
pitch float

spacing between adjacent elements.

25.0
centered bool

center the array around the origin.

True
add_ports bool

add ports from each element.

True
Source code in gdsfactory/components/containers/array_hexagonal.py
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
@gf.cell_with_module_name(tags=["containers"])
def array_hexagonal(
    component: ComponentSpec = "circle",
    columns: int = 10,
    rows: int = 10,
    pitch: float = 25.0,
    centered: bool = True,
    add_ports: bool = True,
) -> Component:
    """Returns a hexagonal close-packed array of components.

    Even rows are placed normally, odd rows are offset by pitch/2.
    Row spacing is pitch * sqrt(3)/2.

    Args:
        component: component to replicate.
        columns: number of columns.
        rows: number of rows.
        pitch: spacing between adjacent elements.
        centered: center the array around the origin.
        add_ports: add ports from each element.
    """
    c = Component()
    comp = gf.get_component(component)
    row_spacing = pitch * np.sqrt(3) / 2

    for row in range(rows):
        x_offset = pitch / 2 if row % 2 else 0.0
        for col in range(columns):
            ref = c.add_ref(comp)
            x = col * pitch + x_offset
            y = row * row_spacing
            ref.move((x, y))

            if add_ports and comp.ports:
                for port in comp.ports:
                    name = f"{port.name}_{row + 1}_{col + 1}"
                    c.add_port(name, port=port.copy(ref.trans))

    if centered:
        c.center = (0, 0)

    return c

array_hexagonal

array_polar

array_polar

array_polar(
    component: ComponentSpec = "C",
    n_items: int = 6,
    radius: float = 50.0,
    start_angle: float = 0.0,
    end_angle: float = 360.0,
    rotate_items: bool = True,
    add_ports: bool = True,
) -> Component

Returns a polar/circular array of components.

Places component refs at equal angular intervals around a circle.

Parameters:

Name Type Description Default
component ComponentSpec

component to replicate.

'C'
n_items int

number of items in the array.

6
radius float

radius of the circle.

50.0
start_angle float

starting angle in degrees.

0.0
end_angle float

ending angle in degrees.

360.0
rotate_items bool

if True, rotate each item to point radially outward.

True
add_ports bool

add ports from each element.

True
Source code in gdsfactory/components/containers/array_polar.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
@gf.cell(
    with_module_name=True,
    check_instances=CheckInstances.IGNORE,
    tags=["containers"],
)
def array_polar(
    component: ComponentSpec = "C",
    n_items: int = 6,
    radius: float = 50.0,
    start_angle: float = 0.0,
    end_angle: float = 360.0,
    rotate_items: bool = True,
    add_ports: bool = True,
) -> Component:
    """Returns a polar/circular array of components.

    Places component refs at equal angular intervals around a circle.

    Args:
        component: component to replicate.
        n_items: number of items in the array.
        radius: radius of the circle.
        start_angle: starting angle in degrees.
        end_angle: ending angle in degrees.
        rotate_items: if True, rotate each item to point radially outward.
        add_ports: add ports from each element.
    """
    c = Component()
    comp = gf.get_component(component)

    if abs(end_angle - start_angle) >= 360.0:
        angles = np.linspace(start_angle, end_angle, n_items, endpoint=False)
    else:
        angles = np.linspace(start_angle, end_angle, n_items, endpoint=True)

    for i, angle_deg in enumerate(angles):
        angle_rad = np.radians(angle_deg)
        x = radius * np.cos(angle_rad)
        y = radius * np.sin(angle_rad)

        ref = c.add_ref(comp)
        if rotate_items:
            ref.rotate(angle_deg)
        ref.move((x, y))

        if add_ports and comp.ports:
            for port in comp.ports:
                name = f"{port.name}_{i + 1}"
                c.add_port(name, port=port.copy(ref.trans))

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

array_polar

component_sequence

SequenceGenerator

Source code in gdsfactory/components/containers/component_sequence.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
class SequenceGenerator:
    def __init__(
        self,
        start_sequence: str = "IL",
        repeated_sequence: str = "ASASBSBS",
        end_sequence: str = "LO",
    ) -> None:
        """Sequence generator.

        Main use case: any type of cascade of components with repeating patterns
        such as serpentine, cutbacks etc...
        Component sequences have two ports by default.
        it adds aliases for the components forming the sequence.
        They use the component symbol with a suffix index starting from 1,
        so you may access the ports from any subcomponent.

        Usually we can break these components in 3 parts:
        - there is a starting pattern with input and possibly some special
        connections
        - then a repeating pattern
        - An ending pattern with an output

        Example of symbol meaning

        A: bend connected with input W0
        B: bend connected with input N0
        I: taper with input '1'
        O: taper with input '2'
        S: short straight waveguide
        L: long straight waveguide

        Args:
            start_sequence: starting sequence.
            end_sequence: ending sequence.
            repeated_sequence: repeating sequence.
        """
        self.start_sequence = start_sequence
        self.end_sequence = end_sequence
        self.repeated_sequence = repeated_sequence

    def get_sequence(self, n: int = 2) -> str:
        return self.start_sequence + n * self.repeated_sequence + self.end_sequence

__init__

__init__(
    start_sequence: str = "IL",
    repeated_sequence: str = "ASASBSBS",
    end_sequence: str = "LO",
) -> None

Sequence generator.

Main use case: any type of cascade of components with repeating patterns such as serpentine, cutbacks etc... Component sequences have two ports by default. it adds aliases for the components forming the sequence. They use the component symbol with a suffix index starting from 1, so you may access the ports from any subcomponent.

Usually we can break these components in 3 parts: - there is a starting pattern with input and possibly some special connections - then a repeating pattern - An ending pattern with an output

Example of symbol meaning

A: bend connected with input W0 B: bend connected with input N0 I: taper with input '1' O: taper with input '2' S: short straight waveguide L: long straight waveguide

Parameters:

Name Type Description Default
start_sequence str

starting sequence.

'IL'
end_sequence str

ending sequence.

'LO'
repeated_sequence str

repeating sequence.

'ASASBSBS'
Source code in gdsfactory/components/containers/component_sequence.py
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
def __init__(
    self,
    start_sequence: str = "IL",
    repeated_sequence: str = "ASASBSBS",
    end_sequence: str = "LO",
) -> None:
    """Sequence generator.

    Main use case: any type of cascade of components with repeating patterns
    such as serpentine, cutbacks etc...
    Component sequences have two ports by default.
    it adds aliases for the components forming the sequence.
    They use the component symbol with a suffix index starting from 1,
    so you may access the ports from any subcomponent.

    Usually we can break these components in 3 parts:
    - there is a starting pattern with input and possibly some special
    connections
    - then a repeating pattern
    - An ending pattern with an output

    Example of symbol meaning

    A: bend connected with input W0
    B: bend connected with input N0
    I: taper with input '1'
    O: taper with input '2'
    S: short straight waveguide
    L: long straight waveguide

    Args:
        start_sequence: starting sequence.
        end_sequence: ending sequence.
        repeated_sequence: repeating sequence.
    """
    self.start_sequence = start_sequence
    self.end_sequence = end_sequence
    self.repeated_sequence = repeated_sequence

component_sequence

component_sequence(
    sequence: str,
    symbol_to_component: dict[
        str, tuple[Component, str, str]
    ],
    ports_map: dict[str, tuple[str, str]] | None = None,
    port_name1: str = "o1",
    port_name2: str = "o2",
    start_orientation: AngleInDegrees = 0.0,
    **kwargs: Any
) -> Component

Returns component from ASCII sequence.

if you prefix a symbol with ! it mirrors the component

Parameters:

Name Type Description Default
sequence str

a string or a list of symbols.

required
symbol_to_component dict[str, tuple[Component, str, str]]

maps symbols to (component, input, output).

required
ports_map dict[str, tuple[str, str]] | None

(optional) extra port mapping using the convention.

None
port_name1 str

input port_name.

'o1'
port_name2 str

output port_name.

'o2'
start_orientation AngleInDegrees

in degrees.

0.0
**kwargs Any

additional keyword arguments passed to the connect method.

{}

Returns:

Name Type Description
component Component

containing the sequence of sub-components instantiated and connected together in the sequence order.

Example
import gdsfactory as gf

bend180 = gf.components.bend_circular180()
wg_pin = gf.components.straight_pin(length=40)
wg = gf.components.straight()

# Define a map between symbols and (component, input port, output port)
symbol_to_component = {
"A": (bend180, 'o1', 'o2'),
"B": (bend180, 'o2', 'o1'),
"H": (wg_pin, 'o1', 'o2'),
"-": (wg, 'o1', 'o2'),
}

# Each character in the sequence represents a component
s = "AB-H-H-H-H-BA"
c = gf.components.component_sequence(sequence=s, symbol_to_component=symbol_to_component)
c.plot()
Source code in gdsfactory/components/containers/component_sequence.py
 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
def component_sequence(
    sequence: str,
    symbol_to_component: dict[str, tuple[Component, str, str]],
    ports_map: dict[str, tuple[str, str]] | None = None,
    port_name1: str = "o1",
    port_name2: str = "o2",
    start_orientation: AngleInDegrees = 0.0,
    **kwargs: Any,
) -> Component:
    """Returns component from ASCII sequence.

    if you prefix a symbol with ! it mirrors the component

    Args:
        sequence: a string or a list of symbols.
        symbol_to_component: maps symbols to (component, input, output).
        ports_map: (optional) extra port mapping using the convention.
            {port_name: (alias_name, port_name)}
        port_name1: input port_name.
        port_name2: output port_name.
        start_orientation: in degrees.
        **kwargs: additional keyword arguments passed to the connect method.

    Returns:
        component: containing the sequence of sub-components
            instantiated and connected together in the sequence order.

    Example:
        ```python
        import gdsfactory as gf

        bend180 = gf.components.bend_circular180()
        wg_pin = gf.components.straight_pin(length=40)
        wg = gf.components.straight()

        # Define a map between symbols and (component, input port, output port)
        symbol_to_component = {
        "A": (bend180, 'o1', 'o2'),
        "B": (bend180, 'o2', 'o1'),
        "H": (wg_pin, 'o1', 'o2'),
        "-": (wg, 'o1', 'o2'),
        }

        # Each character in the sequence represents a component
        s = "AB-H-H-H-H-BA"
        c = gf.components.component_sequence(sequence=s, symbol_to_component=symbol_to_component)
        c.plot()
        ```
    """
    ports_map = ports_map or {}
    named_references_counter: Counter[str] = Counter()
    component = Component()

    # Add first component reference and input port
    symbol = sequence[0] if "!" not in sequence[0] else sequence[:2]
    index = 2 if "!" in sequence[0] else 1
    name_start_device, do_flip = parse_component_name(symbol)
    component_input, input_port, prev_port = symbol_to_component[name_start_device]
    prev_device = component.add_ref(component_input, name=f"{symbol}{index}")
    named_references_counter.update({name_start_device: 1})

    if do_flip:
        prev_device = _flip_ref(prev_device, input_port)

    prev_device.rotate(angle=start_orientation)

    try:
        component.add_port(name=port_name1, port=prev_device.ports[input_port])
    except KeyError as exc:
        port_names = [port.name for port in prev_device.ports]
        raise KeyError(
            f"{prev_device.parent_cell.name!r} input_port {input_port!r} not in {port_names}"
        ) from exc

    ref: ComponentReference | None = None
    next_port: str | None = None

    while index < len(sequence):
        s = sequence[index]

        if s == "!":
            # if it's the last character skip
            if index + 1 >= len(sequence):
                index += 1
                continue
            s = sequence[index + 1]
            do_flip = True
            index += 1
        else:
            do_flip = False

        index += 1
        component_i, input_port, next_port = symbol_to_component[s]
        component_i = gf.get_component(component_i)
        named_references_counter.update({s: 1})
        alias = f"{s}{named_references_counter[s]}"
        ref = component.add_ref(component_i, name=alias)

        if do_flip:
            ref = _flip_ref(ref, input_port)

        try:
            ref.connect(input_port, prev_device.ports[prev_port], **kwargs)
        except KeyError as exc:
            port_names = [port.name for port in prev_device.ports]
            raise KeyError(
                f"{prev_device.parent_cell.name!r} port {prev_port!r} not in {port_names}"
            ) from exc

        prev_device = ref
        prev_port = next_port

    # Deal with edge case where the sequence contains only one component
    if len(sequence) == 1:
        ref = prev_device
        next_port = prev_port

    assert ref is not None
    assert next_port is not None

    component.add_port(name=port_name2, port=ref.ports[next_port])

    # Add any extra port specified in ports_map
    for name, (ref_name, alias_port_name) in ports_map.items():
        component.add_port(
            name=name, port=component.insts[ref_name].ports[alias_port_name]
        )

    return component

parse_component_name

parse_component_name(name: str) -> tuple[str, bool]

If the component name has more than one character and starts with "!".

then we need to flip along the axis given by the input port angle.

Source code in gdsfactory/components/containers/component_sequence.py
57
58
59
60
61
62
def parse_component_name(name: str) -> tuple[str, bool]:
    """If the component name has more than one character and starts with "!".

    then we need to flip along the axis given by the input port angle.
    """
    return (name[1:], True) if len(name) != 1 and name[0] == "!" else (name, False)

copy_layers

copy_layers

copy_layers(
    factory: ComponentSpec = "cross",
    layers: LayerSpecs = ((1, 0), (2, 0)),
    flatten: bool = False,
    **kwargs: Any
) -> Component

Returns a component with the geometry copied in different layers.

Parameters:

Name Type Description Default
factory ComponentSpec

component spec.

'cross'
layers LayerSpecs

iterable of layers.

((1, 0), (2, 0))
flatten bool

flatten the result.

False
kwargs Any

keyword arguments passed to the component.

{}
Source code in gdsfactory/components/containers/copy_layers.py
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
@gf.cell_with_module_name(tags=["containers"])
def copy_layers(
    factory: ComponentSpec = "cross",
    layers: LayerSpecs = ((1, 0), (2, 0)),
    flatten: bool = False,
    **kwargs: Any,
) -> Component:
    """Returns a component with the geometry copied in different layers.

    Args:
        factory: component spec.
        layers: iterable of layers.
        flatten: flatten the result.
        kwargs: keyword arguments passed to the component.
    """
    c = Component()

    ci = None
    for layer in layers:
        c << (ci := gf.get_component(factory, layer=layer, **kwargs))
    if ci is not None:
        c.copy_child_info(ci)

    if flatten:
        c.flatten()
    return c

copy_layers

extend_ports

extend_ports(
    component: ComponentSpec = "mmi1x2",
    port_names: PortNames | None = None,
    length: float = 5.0,
    extension: ComponentSpec | None = None,
    port1: str | None = None,
    port2: str | None = None,
    port_type: str = "optical",
    centered: bool = False,
    cross_section: CrossSectionSpec | None = None,
    extension_port_names: list[str] | None = None,
    allow_width_mismatch: bool = False,
    auto_taper: bool = True,
    **kwargs: Any
) -> Component

Returns a new component with some ports extended.

You can define extension Spec defaults to port cross_section of each port to extend.

Parameters:

Name Type Description Default
component ComponentSpec

component to extend ports.

'mmi1x2'
port_names PortNames | None

list of ports names to extend, if None it extends all ports.

None
length float

extension length.

5.0
extension ComponentSpec | None

function to extend ports (defaults to a straight).

None
port1 str | None

extension input port name.

None
port2 str | None

extension output port name.

None
port_type str

type of the ports to extend.

'optical'
centered bool

if True centers rectangle at (0, 0).

False
cross_section CrossSectionSpec | None

extension cross_section, defaults to port cross_section if port has no cross_section it creates one using width and layer.

None
extension_port_names list[str] | None

extension port names add to the new component.

None
allow_width_mismatch bool

allow width mismatches.

False
auto_taper bool

if True adds automatic tapers.

True
kwargs Any

cross_section settings.

{}

Other Parameters:

Name Type Description
layer

port GDS layer.

prefix

port name prefix.

orientation

in degrees.

width

port width.

layers_excluded

List of layers to exclude.

port_type str

optical, electrical, ....

clockwise

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

Source code in gdsfactory/components/containers/extension.py
 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
@gf.cell_with_module_name(tags=["containers"])
def extend_ports(
    component: ComponentSpec = "mmi1x2",
    port_names: PortNames | None = None,
    length: float = 5.0,
    extension: ComponentSpec | None = None,
    port1: str | None = None,
    port2: str | None = None,
    port_type: str = "optical",
    centered: bool = False,
    cross_section: CrossSectionSpec | None = None,
    extension_port_names: list[str] | None = None,
    allow_width_mismatch: bool = False,
    auto_taper: bool = True,
    **kwargs: Any,
) -> Component:
    """Returns a new component with some ports extended.

    You can define extension Spec
    defaults to port cross_section of each port to extend.

    Args:
        component: component to extend ports.
        port_names: list of ports names to extend, if None it extends all ports.
        length: extension length.
        extension: function to extend ports (defaults to a straight).
        port1: extension input port name.
        port2: extension output port name.
        port_type: type of the ports to extend.
        centered: if True centers rectangle at (0, 0).
        cross_section: extension cross_section, defaults to port cross_section
            if port has no cross_section it creates one using width and layer.
        extension_port_names: extension port names add to the new component.
        allow_width_mismatch: allow width mismatches.
        auto_taper: if True adds automatic tapers.
        kwargs: cross_section settings.

    Keyword Args:
        layer: port GDS layer.
        prefix: port name prefix.
        orientation: in degrees.
        width: port width.
        layers_excluded: List of layers to exclude.
        port_type: optical, electrical, ....
        clockwise: if True, sort ports clockwise, False: counter-clockwise.
    """
    c = gf.Component()
    component = gf.get_component(component)

    cref = c << component
    if centered:
        cref.x = 0
        cref.y = 0

    ports_all = cref.ports
    port_names_all = [p.name for p in ports_all if p.name is not None]

    ports_to_extend = list(
        gf.port.get_ports_list(cref.ports, port_type=port_type, **kwargs)
    )
    ports_to_extend_names = [p.name for p in ports_to_extend if p.name is not None]
    ports_to_extend_names = cast("list[str]", port_names or ports_to_extend_names)

    if auto_taper and cross_section:
        from gdsfactory.routing.auto_taper import add_auto_tapers

        _ = add_auto_tapers(
            component=c, ports=ports_to_extend, cross_section=cross_section
        )

    for port_name_to_extend in ports_to_extend_names:
        if port_name_to_extend not in port_names_all:
            warnings.warn(
                f"Port Name {port_name_to_extend!r} not in {port_names_all}",
                stacklevel=3,
                category=UserWarning,
            )

    for port in ports_all:
        port_name = port.name
        port = cref.ports[port_name]

        if port_name in ports_to_extend_names:
            if extension:
                extension_component = gf.get_component(extension)
            else:
                if cross_section:
                    cross_section_extension = cross_section
                else:
                    pdk = gf.get_active_pdk()
                    cross_section_names = list(pdk.cross_sections)
                    port_xs_name = port.info.get("cross_section", None)

                    if port_xs_name and port_xs_name in cross_section_names:
                        cross_section_extension = gf.get_cross_section(
                            port.info["cross_section"]
                        )

                    else:
                        cross_section_extension = cross_section_function(
                            layer=gf.get_layer_tuple(port.layer),
                            width=port.width,
                            port_types=(port_type, port_type),
                        )

                extension_component = gf.components.straight(
                    length=length,
                    cross_section=cross_section_extension,
                )
            port_labels = [p.name for p in extension_component.ports]
            port1 = port1 or port_labels[0]
            port2 = port2 or port_labels[-1]

            assert port1 is not None

            extension_ref = c << extension_component
            extension_ref.connect(
                port1, port, allow_width_mismatch=allow_width_mismatch
            )
            c.add_port(port_name, port=extension_ref.ports[port2])
            extension_port_names = extension_port_names or []
            [
                c.add_port(name, port=extension_ref.ports[name])
                for name in extension_port_names
            ]
        else:
            c.add_port(port_name, port=component.ports[port_name])

    c.copy_child_info(component)
    return c

extend_ports

extend_ports_list

extend_ports_list

extend_ports_list(
    component_spec: ComponentSpec,
    extension: ComponentSpec,
    extension_port_name: str | None = None,
    ignore_ports: Strs | None = None,
) -> Component

Returns a component with an extension attached to a list of ports.

Parameters:

Name Type Description Default
component_spec ComponentSpec

component from which to get ports.

required
extension ComponentSpec

function for extension.

required
extension_port_name str | None

to connect extension.

None
ignore_ports Strs | None

list of port names to ignore.

None
Source code in gdsfactory/components/containers/extend_ports_list.py
10
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
@gf.cell(set_name=False, tags=["containers"])
def extend_ports_list(
    component_spec: ComponentSpec,
    extension: ComponentSpec,
    extension_port_name: str | None = None,
    ignore_ports: Strs | None = None,
) -> Component:
    """Returns a component with an extension attached to a list of ports.

    Args:
        component_spec: component from which to get ports.
        extension: function for extension.
        extension_port_name: to connect extension.
        ignore_ports: list of port names to ignore.
    """
    from gdsfactory.pdk import get_component

    ports = get_component(component_spec).ports

    c = Component()
    extension = get_component(extension)
    c.name = f"{extension.name}_extended_{c.cell_index()}"

    extension_port_name_or_port = extension_port_name or extension.ports[0]
    ignore_ports = ignore_ports or ()

    for i, port in enumerate(ports):
        extension_ref = c << extension
        extension_ref.connect(extension_port_name_or_port, port)

        for ext_port in extension_ref.ports:
            port_name = ext_port.name
            if port_name not in ignore_ports:
                c.add_port(f"{i}_{port_name}", port=ext_port)

    c.auto_rename_ports()
    return c

splitter_chain

splitter_chain

splitter_chain(
    splitter: ComponentSpec = "mmi1x2",
    columns: int = 3,
    bend: ComponentSpec = "bend_s",
) -> Component

Chain of splitters.

Parameters:

Name Type Description Default
splitter ComponentSpec

splitter to chain.

'mmi1x2'
columns int

number of splitters to chain.

3
bend ComponentSpec

bend to connect splitters.

'bend_s'
             __o5
          __|
       __|  |__o4
  o1 _|  |__o3
      |__o2
       __o2
  o1 _|
      |__o3
Source code in gdsfactory/components/containers/splitter_chain.py
10
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
@gf.cell_with_module_name(tags=["containers"])
def splitter_chain(
    splitter: ComponentSpec = "mmi1x2",
    columns: int = 3,
    bend: ComponentSpec = "bend_s",
) -> Component:
    """Chain of splitters.

    Args:
        splitter: splitter to chain.
        columns: number of splitters to chain.
        bend: bend to connect splitters.

    ```text
                 __o5
              __|
           __|  |__o4
      o1 _|  |__o3
          |__o2
    ```

    ```text
           __o2
      o1 _|
          |__o3
    ```
    """
    c = gf.Component()
    splitter_component = gf.get_component(splitter)
    cref = c.add_ref(splitter_component)

    splitter_ports_east = list(cref.ports.filter(port_type="optical", orientation=0))
    e1_port_name = splitter_ports_east[0].name
    e0_port_name = splitter_ports_east[1].name

    bend = gf.get_component(bend)
    c.add_port(name="o1", port=cref.ports["o1"])
    c.add_port(name="o2", port=cref.ports[e0_port_name])

    for i in range(1, columns):
        bref = c.add_ref(bend)
        bref.connect(port="o1", other=cref.ports[e1_port_name])
        cref = c.add_ref(splitter_component)
        cref.connect(port="o1", other=bref.ports["o2"])
        c.add_port(name=f"o{i + 2}", port=cref.ports[e0_port_name])

    c.add_port(name=f"o{i + 3}", port=cref.ports[e1_port_name])
    c.copy_child_info(splitter_component)
    return c

splitter_chain

splitter_tree

Returns a switch_tree.

          __
        _|  |_
  __   | |  |_   _
 |  |__| |__|    |
_|  |__          |dy
 |__|  |  __     |
       |_|  |_   |
         |  |_   -
         |__|

|<-dx->|

splitter_tree

splitter_tree(
    coupler: ComponentSpec = "mmi1x2",
    noutputs: int = 4,
    spacing: Spacing = (90.0, 50.0),
    bend_s: ComponentSpec | None = "bend_s",
    bend_s_xsize: float | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> gf.Component

Tree of power splitters.

Parameters:

Name Type Description Default
coupler ComponentSpec

coupler factory.

'mmi1x2'
noutputs int

number of outputs.

4
spacing Spacing

x, y spacing between couplers.

(90.0, 50.0)
bend_s ComponentSpec | None

Sbend function for termination.

'bend_s'
bend_s_xsize float | None

xsize for the sbend.

None
cross_section CrossSectionSpec

cross_section.

| | |__

'strip'
Source code in gdsfactory/components/containers/splitter_tree.py
 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
@gf.cell_with_module_name(tags=["containers"])
def splitter_tree(
    coupler: ComponentSpec = "mmi1x2",
    noutputs: int = 4,
    spacing: Spacing = (90.0, 50.0),
    bend_s: ComponentSpec | None = "bend_s",
    bend_s_xsize: float | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> gf.Component:
    """Tree of power splitters.

    Args:
        coupler: coupler factory.
        noutputs: number of outputs.
        spacing: x, y spacing between couplers.
        bend_s: Sbend function for termination.
        bend_s_xsize: xsize for the sbend.
        cross_section: cross_section.

             __|
          __|  |__
        _|  |__
         |__        dy

          dx
    """
    c = gf.Component()

    dx, dy = spacing

    coupler = gf.get_component(coupler)
    coupler_ports_west = coupler.get_ports_list(
        port_type="optical", orientation=180, sort_ports=True
    )
    coupler_ports_east = coupler.get_ports_list(
        port_type="optical", orientation=0, sort_ports=True
    )

    e1_port_name = coupler_ports_east[0].name
    e0_port_name = coupler_ports_east[1].name
    w0_port_name = coupler_ports_west[0].name

    if bend_s:
        dy_coupler_ports = abs(
            coupler.ports[e0_port_name].center[1]
            - coupler.ports[e1_port_name].center[1]
        )
        bend_s_ysize = dy / 4 - dy_coupler_ports / 2
        bend_s_xsize = bend_s_xsize or dx
        bend_s = gf.get_component(
            bend_s,
            cross_section=cross_section,
            size=(bend_s_xsize, bend_s_ysize),
        )
        # c.info["bend_s"] = bend_s.info
    cols = int(np.log2(noutputs))
    i = 0

    for col in range(cols):
        ncouplers = int(2**col)
        y0 = -0.5 * dy * 2 ** (cols - 1)
        for row in range(ncouplers):
            x = col * dx
            y = y0 + (row + 0.5) * dy * 2 ** (cols - col - 1)
            coupler_ref = c.add_ref(coupler, name=f"coupler_{col}_{row}")
            coupler_ref.move((x, y))
            if col == 0:
                for port in coupler_ref.ports:
                    if port.name not in [e0_port_name, e1_port_name]:
                        c.add_port(name=f"{port.name}_{col}_{i}", port=port)
                        i += 1
            if col > 0:
                port_name = e0_port_name if row % 2 == 0 else e1_port_name
                gf.routing.route_bundle(
                    c,
                    c.insts[f"coupler_{col - 1}_{row // 2}"].ports[port_name],
                    coupler_ref["o1"],
                    cross_section=cross_section,
                )
            if cols > col > 0:
                for port in coupler_ref.ports:
                    if port.name not in [
                        "o1",
                        e0_port_name,
                        e1_port_name,
                        w0_port_name,
                    ]:
                        c.add_port(name=f"{port.name}_{col}_{i}", port=port)
                        i += 1
            if col == cols - 1 and bend_s is None:
                for port in coupler_ref.ports:
                    if port.name in [e1_port_name, e0_port_name]:
                        c.add_port(name=f"{port.name}_{col}_{i}", port=port)
                        i += 1
            if col == cols - 1 and bend_s:
                assert isinstance(bend_s, Component)
                btop = c << bend_s
                bbot = c << bend_s
                bbot.dmirror()
                btop.connect("o1", coupler_ref[e1_port_name])
                bbot.connect("o1", coupler_ref[e0_port_name])
                port = btop.ports["o2"]
                c.add_port(name=f"{port.name}_{col}_{i}", port=port)
                i += 1
                port = bbot.ports["o2"]
                c.add_port(name=f"{port.name}_{col}_{i}", port=port)
                i += 1

    return c

splitter_tree

switch_tree module-attribute

switch_tree = partial(
    splitter_tree, coupler=_mzi1x2_2x2, spacing=(500, 100)
)

switch_tree

couplers

coupler

coupler

coupler(
    gap: float = 0.236,
    length: float = 20.0,
    dy: Delta = 4.0,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    bend: ComponentSpec = "bend_s",
) -> Component

Symmetric coupler.

Parameters:

Name Type Description Default
gap float

between straights in um.

0.236
length float

of coupling region in um.

20.0
dy Delta

port to port vertical spacing in um.

4.0
dx Delta

length of bend in x direction in um.

10.0
cross_section CrossSectionSpec

spec (CrossSection, string or dict).

'strip'
allow_min_radius_violation bool

if True does not check for min bend radius.

False
bend ComponentSpec

input and output sbend components.

dx dx |------| |------| o2 __ __o3 \ / | \ length / | ======================= gap | dy / \ | __/ _____ | o1 o4

        coupler_straight  coupler_symmetric
'bend_s'
Source code in gdsfactory/components/couplers/coupler.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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler(
    gap: float = 0.236,
    length: float = 20.0,
    dy: Delta = 4.0,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
    bend: ComponentSpec = "bend_s",
) -> Component:
    r"""Symmetric coupler.

    Args:
        gap: between straights in um.
        length: of coupling region in um.
        dy: port to port vertical spacing in um.
        dx: length of bend in x direction in um.
        cross_section: spec (CrossSection, string or dict).
        allow_min_radius_violation: if True does not check for min bend radius.
        bend: input and output sbend components.

               dx                                 dx
            |------|                           |------|
         o2 ________                           ______o3
                    \                         /           |
                     \        length         /            |
                      ======================= gap         | dy
                     /                       \            |
            ________/                         \_______    |
         o1                                          o4

                        coupler_straight  coupler_symmetric
    """
    c = Component()
    sbend = coupler_symmetric(
        gap=gap,
        dy=dy,
        dx=dx,
        cross_section=cross_section,
        bend=bend,
        allow_min_radius_violation=allow_min_radius_violation,
    )

    sr = c << sbend
    sl = c << sbend
    cs = c << coupler_straight(length=length, gap=gap, cross_section=cross_section)
    sl.connect("o2", other=cs.ports["o1"])
    sr.connect("o1", other=cs.ports["o4"])

    c.add_port("o1", port=sl.ports["o3"])
    c.add_port("o2", port=sl.ports["o4"])
    c.add_port("o3", port=sr.ports["o3"])
    c.add_port("o4", port=sr.ports["o4"])

    c.info["path_length"] = 2 * sbend.info["length"] + length
    c.info["min_bend_radius"] = sbend.info["min_bend_radius"]
    c.auto_rename_ports()

    x = gf.get_cross_section(cross_section)
    x.add_bbox(c)
    c.flatten()
    assert x.radius is not None
    if not allow_min_radius_violation:
        x.validate_radius(x.radius)
    return c

coupler_straight

coupler_straight(
    length: float = 10.0,
    gap: float = 0.27,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Coupler_straight with two parallel straights.

Parameters:

Name Type Description Default
length float

of straight.

10.0
gap float

between straights.

0.27
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
Source code in gdsfactory/components/couplers/coupler.py
 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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_straight(
    length: float = 10.0,
    gap: float = 0.27,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Coupler_straight with two parallel straights.

    Args:
        length: of straight.
        gap: between straights.
        cross_section: specification (CrossSection, string or dict).

        o2──────▲─────────o3
                │gap
        o1──────▼─────────o4
    """
    c = Component()
    x = gf.get_cross_section(cross_section)
    _straight = gf.c.straight(length=length, cross_section=cross_section)

    top = c << _straight
    bot = c << _straight

    w = x.width
    y = w + gap

    top.movey(+y)

    if bot.ports and top.ports:
        c.add_port("o1", port=bot.ports[0])
        c.add_port("o2", port=top.ports[0])
        c.add_port("o3", port=bot.ports[1])
        c.add_port("o4", port=top.ports[1])
        c.auto_rename_ports()
    return c

coupler_symmetric

coupler_symmetric(
    bend: ComponentSpec = "bend_s",
    gap: float = 0.234,
    dy: Delta = 4.0,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component

Two coupled straights with bends.

Parameters:

Name Type Description Default
bend ComponentSpec

bend spec.

'bend_s'
gap float

in um.

0.234
dy Delta

port to port vertical spacing.

4.0
dx Delta

bend length in x direction.

10.0
cross_section CrossSectionSpec

section.

'strip'
allow_min_radius_violation bool

if True does not check for min bend radius.

       dx
    |-----|
       ___ o3
      /       |

o2 _/ | | o1 ___ | dy \ | ___ | o4

False
Source code in gdsfactory/components/couplers/coupler.py
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
@gf.cell_with_module_name(tags=["couplers"])
def coupler_symmetric(
    bend: ComponentSpec = "bend_s",
    gap: float = 0.234,
    dy: Delta = 4.0,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component:
    r"""Two coupled straights with bends.

    Args:
        bend: bend spec.
        gap: in um.
        dy: port to port vertical spacing.
        dx: bend length in x direction.
        cross_section: section.
        allow_min_radius_violation: if True does not check for min bend radius.

                       dx
                    |-----|
                       ___ o3
                      /       |
             o2 _____/        |
                              |
             o1 _____         |  dy
                     \        |
                      \___    |
                           o4

    """
    c = Component()
    x = gf.get_cross_section(cross_section)
    width = x.width
    dy = (dy - gap - width) / 2

    bend_component = gf.get_component(
        bend,
        size=(dx, dy),
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
    )
    top_bend = c << bend_component
    bot_bend = c << bend_component
    bend_ports = top_bend.ports.filter(port_type="optical")
    bend_port1_name = bend_ports[0].name
    bend_port2_name = bend_ports[1].name

    w = bend_component[bend_port1_name].width
    y = w + gap
    y /= 2

    bot_bend.dmirror_y()
    top_bend.movey(+y)
    bot_bend.movey(-y)

    c.add_port("o1", port=bot_bend[bend_port1_name])
    c.add_port("o2", port=top_bend[bend_port1_name])
    c.add_port("o3", port=top_bend[bend_port2_name])
    c.add_port("o4", port=bot_bend[bend_port2_name])

    c.info["length"] = bend_component.info["length"]
    c.info["min_bend_radius"] = bend_component.info["min_bend_radius"]
    return c

coupler

coupler90

coupler90

coupler90(
    gap: float = 0.2,
    radius: float | None = None,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    cross_section_bend: CrossSectionSpec | None = None,
    length_straight: float | None = None,
) -> Component

Straight coupled to a bend.

Parameters:

Name Type Description Default
gap float

um.

0.2
radius float | None

um.

None
straight ComponentSpec

for straight.

'straight'
bend ComponentSpec

bend spec.

'bend_euler'
cross_section CrossSectionSpec

cross_section spec.

'strip'
cross_section_bend CrossSectionSpec | None

optional bend cross_section spec.

None
length_straight float | None

optional length of the straight waveguide.

None
        o3
         |
        /
       /
   o2_/
   o1___o4
Source code in gdsfactory/components/couplers/coupler90.py
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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler90(
    gap: float = 0.2,
    radius: float | None = None,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    cross_section_bend: CrossSectionSpec | None = None,
    length_straight: float | None = None,
) -> Component:
    r"""Straight coupled to a bend.

    Args:
        gap: um.
        radius: um.
        straight: for straight.
        bend: bend spec.
        cross_section: cross_section spec.
        cross_section_bend: optional bend cross_section spec.
        length_straight: optional length of the straight waveguide.

    ```text
            o3
             |
            /
           /
       o2_/
       o1___o4
    ```

    """
    c = Component()
    x = gf.get_cross_section(cross_section, radius=radius)
    xs_bend = cross_section_bend or cross_section

    bend90 = gf.get_component(
        bend,
        radius=radius,
        cross_section=xs_bend,
    )
    bend_ref = c << bend90
    bend90_ports = bend_ref.ports.filter(port_type="optical")

    if length_straight is None:
        length_straight = bend90_ports[1].center[0] - bend90_ports[0].center[0]

    straight_component = gf.get_component(
        straight,
        cross_section=cross_section,
        length=length_straight,
    )
    wg_ref = c << straight_component
    width = x.width

    pbw = bend90_ports[0]
    bend_ref.movey(pbw.y + gap + width)
    c.add_ports(wg_ref.ports, prefix="wg")
    c.add_ports(bend_ref.ports, prefix="bend")
    c.auto_rename_ports()
    return c

coupler90

coupler90bend

coupler90bend

coupler90bend(
    radius: float = 10.0,
    gap: float = 0.2,
    bend: ComponentSpec = "bend_euler",
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
) -> Component

Returns 2 coupled bends.

Parameters:

Name Type Description Default
radius float

um.

10.0
gap float

um.

0.2
bend ComponentSpec

for bend.

'bend_euler'
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

r 3 4 | | | | / / | / /

'strip'
Source code in gdsfactory/components/couplers/coupler90bend.py
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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler90bend(
    radius: float = 10.0,
    gap: float = 0.2,
    bend: ComponentSpec = "bend_euler",
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
) -> Component:
    r"""Returns 2 coupled bends.

    Args:
        radius: um.
        gap: um.
        bend: for bend.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.

            r   3 4
            |   | |
            |  / /
            | / /
        2____/ /
        1_____/

    """
    c = Component()

    xi = gf.get_cross_section(cross_section_inner)
    xo = gf.get_cross_section(cross_section_outer)

    width = xo.width / 2 + xi.width / 2
    spacing = gap + width

    bend90_inner = gf.get_component(
        bend, radius=radius, cross_section=cross_section_inner
    )
    bend90_outer = gf.get_component(
        bend, radius=radius + spacing, cross_section=cross_section_outer
    )
    bend_inner_ref = c << bend90_inner
    bend_outer_ref = c << bend90_outer

    pbw = bend_inner_ref["o1"]
    bend_inner_ref.movey(pbw.center[1] + spacing)

    c.add_port("o1", port=bend_outer_ref["o1"])
    c.add_port("o2", port=bend_inner_ref["o1"])
    c.add_port("o3", port=bend_inner_ref["o2"])
    c.add_port("o4", port=bend_outer_ref["o2"])
    c.flatten()
    return c

coupler90bend

coupler90circular module-attribute

coupler90circular = partial(coupler90, bend="bend_circular")

coupler90circular

coupler_adiabatic

coupler_adiabatic

coupler_adiabatic(
    length1: float = 20.0,
    length2: float = 50.0,
    length3: float = 30.0,
    wg_sep: float = 1.0,
    input_wg_sep: float = 3.0,
    output_wg_sep: float = 3.0,
    dw: float = 0.1,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns 50/50 adiabatic coupler.

Design based on asymmetric adiabatic 3dB coupler designs, such as those. - https://doi.org/10.1364/CLEO.2010.CThAA2, - https://doi.org/10.1364/CLEO_SI.2017.SF1I.5 - https://doi.org/10.1364/CLEO_SI.2018.STh4B.4

input Bezier curves, with poles set to half of the x-length of the S-bend. 1. is the first half of input S-bend where input widths taper by +dw and -dw 2. is the second half of the S-bend straight with constant, unbalanced widths 3. is the region where the two asymmetric straights gradually come together 4. straights taper back to the original width at a fixed distance from one another 5. is the output S-bend straight.

Parameters:

Name Type Description Default
length1 float

region that gradually brings the two asymmetric straights together. In this region the straight widths gradually change to be different by dw.

20.0
length2 float

coupling region, where asymmetric straights gradually become the same width.

50.0
length3 float

output region where the two straights separate.

30.0
wg_sep float

Distance between center-to-center in the coupling region (Region 2).

1.0
input_wg_sep float

Separation of the two straights at the input, center-to-center.

3.0
output_wg_sep float

Separation of the two straights at the output, center-to-center.

3.0
dw float

Change in straight width. In Region 1, top arm tapers to width+dw/2.0, bottom taper to width-dw/2.0.

0.1
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/couplers/coupler_adiabatic.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
 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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_adiabatic(
    length1: float = 20.0,
    length2: float = 50.0,
    length3: float = 30.0,
    wg_sep: float = 1.0,
    input_wg_sep: float = 3.0,
    output_wg_sep: float = 3.0,
    dw: float = 0.1,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns 50/50 adiabatic coupler.

    Design based on asymmetric adiabatic 3dB coupler designs, such as those.
    - https://doi.org/10.1364/CLEO.2010.CThAA2,
    - https://doi.org/10.1364/CLEO_SI.2017.SF1I.5
    - https://doi.org/10.1364/CLEO_SI.2018.STh4B.4

    input Bezier curves, with poles set to half of the x-length of the S-bend.
    1. is the first half of input S-bend where input widths taper by +dw and -dw
    2. is the second half of the S-bend straight with constant, unbalanced widths
    3. is the region where the two asymmetric straights gradually come together
    4. straights taper back to the original width at a fixed distance from one another
    5. is the output S-bend straight.

    Args:
        length1: region that gradually brings the two asymmetric straights together.
            In this region the straight widths gradually change to be different by `dw`.
        length2: coupling region, where asymmetric straights gradually
            become the same width.
        length3: output region where the two straights separate.
        wg_sep: Distance between center-to-center in the coupling region (Region 2).
        input_wg_sep: Separation of the two straights at the input, center-to-center.
        output_wg_sep: Separation of the two straights at the output, center-to-center.
        dw: Change in straight width.
            In Region 1, top arm tapers to width+dw/2.0, bottom taper to width-dw/2.0.
        cross_section: cross_section spec.

    """
    # Control points for input and output S-bends
    control_points_input_top = (
        (0, 0),
        (length1 / 2.0, 0),
        (length1 / 2.0, -input_wg_sep / 2.0 + wg_sep / 2.0),
        (length1, -input_wg_sep / 2.0 + wg_sep / 2.0),
    )

    control_points_input_bottom = (
        (0, -input_wg_sep),
        (length1 / 2.0, -input_wg_sep),
        (length1 / 2.0, -input_wg_sep / 2.0 - wg_sep / 2.0),
        (length1, -input_wg_sep / 2.0 - wg_sep / 2.0),
    )

    control_points_output_top = (
        (length1 + length2, -input_wg_sep / 2.0 + wg_sep / 2.0),
        (
            length1 + length2 + length3 / 2.0,
            -input_wg_sep / 2.0 + wg_sep / 2.0,
        ),
        (
            length1 + length2 + length3 / 2.0,
            -input_wg_sep / 2.0 + output_wg_sep / 2.0,
        ),
        (
            length1 + length2 + length3,
            -input_wg_sep / 2.0 + output_wg_sep / 2.0,
        ),
    )

    c = Component()

    x = gf.get_cross_section(cross_section)
    width = float(x.width)
    width_top = width + dw
    width_bot = width - dw
    x_top = x.copy(width=width_top)
    x_bot = x.copy(width=width_bot)

    coupler = c << gf.components.coupler_straight(length=length2, cross_section=x)

    taper_top = c << gf.components.taper(
        width1=width, width2=width_top, cross_section=cross_section
    )
    taper_bot = c << gf.components.taper(
        width1=width, width2=width_bot, cross_section=cross_section
    )

    taper_bot.connect("o1", coupler.ports["o1"])
    taper_top.connect("o1", coupler.ports["o2"])

    sbend_left_top = c << bezier(
        control_points=control_points_input_top, cross_section=x_top
    )
    sbend_left_bot = c << bezier(
        control_points=control_points_input_bottom, cross_section=x_bot
    )

    sbend_left_top.connect("o2", taper_top.ports["o2"])
    sbend_left_bot.connect("o2", taper_bot.ports["o2"])

    sbend_right = bezier(control_points=control_points_output_top, cross_section=x)
    sbend_right_top = c << sbend_right
    sbend_right_bot = c << sbend_right

    sbend_right_top.connect("o1", coupler.ports["o3"])
    sbend_right_bot.connect("o1", coupler.ports["o4"], mirror=True)

    c.add_port("o1", port=sbend_left_bot.ports["o1"])
    c.add_port("o2", port=sbend_left_top.ports["o1"])
    c.add_port("o3", port=sbend_right_top.ports["o2"])
    c.add_port("o4", port=sbend_right_bot.ports["o2"])
    c.flatten()
    return c

coupler_adiabatic

coupler_asymmetric

coupler_asymmetric

coupler_asymmetric(
    gap: float = 0.234,
    dy: Delta = 2.5,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Bend coupled to straight waveguide.

Parameters:

Name Type Description Default
gap float

um.

0.234
dy Delta

port to port vertical spacing.

2.5
dx Delta

bend length in x direction.

10.0
cross_section CrossSectionSpec

spec.

        dx
     |-----|
      _____ o2
     /         |

_/ | gap o1____ | dy o3

'strip'
Source code in gdsfactory/components/couplers/coupler_asymmetric.py
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
@gf.cell_with_module_name(schematic_function=mmi_1x2_schematic, tags=["couplers"])
def coupler_asymmetric(
    gap: float = 0.234,
    dy: Delta = 2.5,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Bend coupled to straight waveguide.

    Args:
        gap: um.
        dy: port to port vertical spacing.
        dx: bend length in x direction.
        cross_section: spec.

                        dx
                     |-----|
                      _____ o2
                     /         |
               _____/          |
         gap o1____________    |  dy
                            o3
    """
    c = Component()
    x = gf.get_cross_section(cross_section)
    width = x.width
    bend = gf.c.bend_s(size=(dx, dy - gap - width), cross_section=cross_section)
    wg = gf.c.straight(cross_section=cross_section)

    w = bend.ports[0].width
    y = (w + gap) / 2

    wg_ref = c << wg
    bend_ref = c << bend
    bend_ref.dmirror_y()
    bend_ref.xmin = 0
    wg_ref.xmin = 0

    bend_ref.movey(-y)
    wg_ref.movey(+y)

    port_width = 2 * w + gap
    c.add_port(
        name="o1",
        center=(0, 0),
        width=port_width,
        orientation=180,
        cross_section=x,
    )
    c.add_port(name="o3", port=bend_ref.ports[1])
    c.add_port(name="o2", port=wg_ref.ports[0])
    c.flatten()
    return c

coupler_asymmetric

coupler_bent

coupler_bent

coupler_bent(
    gap: float = 0.2,
    radius: float = 26,
    length: float = 8.6,
    width1: float = 0.4,
    width2: float = 0.4,
    length_straight: float = 10,
    cross_section: str = "strip",
) -> gf.Component

Returns Broadband SOI curved / straight directional coupler.

based on: https://doi.org/10.1038/s41598-017-07618-6.

Parameters:

Name Type Description Default
gap float

gap.

0.2
radius float

radius coupling.

26
length float

coupler_length.

8.6
width1 float

width1.

0.4
width2 float

width2.

0.4
length_straight float

input and output straight length.

10
cross_section str

cross_section.

'strip'
Source code in gdsfactory/components/couplers/coupler_bent.py
 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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_bent(
    gap: float = 0.200,
    radius: float = 26,
    length: float = 8.6,
    width1: float = 0.400,
    width2: float = 0.400,
    length_straight: float = 10,
    cross_section: str = "strip",
) -> gf.Component:
    """Returns Broadband SOI curved / straight directional coupler.

    based on: https://doi.org/10.1038/s41598-017-07618-6.

    Args:
        gap: gap.
        radius: radius coupling.
        length: coupler_length.
        width1: width1.
        width2: width2.
        length_straight: input and output straight length.
        cross_section: cross_section.
    """
    c = gf.Component()

    right_half = c << coupler_bent_half(
        gap=gap,
        radius=radius,
        length=length,
        width1=width1,
        width2=width2,
        length_straight=length_straight,
        cross_section=cross_section,
    )
    left_half = c << coupler_bent_half(
        gap=gap,
        radius=radius,
        length=length,
        width1=width1,
        width2=width2,
        length_straight=length_straight,
        cross_section=cross_section,
    )

    left_half.connect(port="o1", other=right_half.ports["o1"], mirror=True)

    c.add_port("o1", port=left_half.ports["o3"])
    c.add_port("o2", port=left_half.ports["o4"])
    c.add_port("o3", port=right_half.ports["o3"])
    c.add_port("o4", port=right_half.ports["o4"])

    c.flatten()
    return c

coupler_bent_half

coupler_bent_half(
    gap: float = 0.2,
    radius: float = 26,
    length: float = 8.6,
    width1: float = 0.4,
    width2: float = 0.4,
    length_straight: float = 10,
    length_straight_exit: float = 18,
    cross_section: str = "strip",
) -> gf.Component

Returns Broadband SOI curved / straight directional coupler.

Parameters:

Name Type Description Default
gap float

gap.

0.2
radius float

radius coupling.

26
length float

coupler_length.

8.6
width1 float

width1.

0.4
width2 float

width2.

0.4
length_straight float

input and output straight length.

10
length_straight_exit float

length straight exit.

18
cross_section str

cross_section.

'strip'
Source code in gdsfactory/components/couplers/coupler_bent.py
10
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
@gf.cell_with_module_name(tags=["couplers"])
def coupler_bent_half(
    gap: float = 0.200,
    radius: float = 26,
    length: float = 8.6,
    width1: float = 0.400,
    width2: float = 0.400,
    length_straight: float = 10,
    length_straight_exit: float = 18,
    cross_section: str = "strip",
) -> gf.Component:
    """Returns Broadband SOI curved / straight directional coupler.

    Args:
        gap: gap.
        radius: radius coupling.
        length: coupler_length.
        width1: width1.
        width2: width2.
        length_straight: input and output straight length.
        length_straight_exit: length straight exit.
        cross_section: cross_section.
    """
    radius_outer = radius + (width1 + gap) / 2
    radius_inner = radius - (width2 + gap) / 2
    alpha = round(np.rad2deg(length / (2 * radius)), 4)
    beta = alpha

    c = gf.Component()

    xs = gf.get_cross_section(cross_section)
    xs1 = xs.copy(radius=radius_outer, width=width1)
    xs2 = xs.copy(radius=radius_inner, width=width2)

    outer_bend = gf.path.arc(angle=-alpha, radius=radius_outer)
    inner_bend = gf.path.arc(angle=-alpha, radius=radius_inner)

    outer_straight = gf.path.straight(length=length, npoints=100)
    inner_straight = gf.path.straight(length=length, npoints=100)

    outer_exit_bend = gf.path.arc(angle=alpha, radius=radius_outer)
    inner_exit_bend_down = gf.path.arc(angle=-beta, radius=radius_inner)
    inner_exit_bend_up = gf.path.arc(angle=alpha + beta, radius=radius_inner)

    inner_exit_straight = gf.path.straight(
        length=length_straight,
        npoints=100,
    )
    outer_exit_straight = gf.path.straight(
        length=length_straight_exit,
        npoints=100,
    )

    outer = outer_bend + outer_straight + outer_exit_bend + outer_exit_straight
    inner = (
        inner_bend
        + inner_straight
        + inner_exit_bend_down
        + inner_exit_bend_up
        + inner_exit_straight
    )

    inner_component = c << inner.extrude(xs2)
    outer_component = c << outer.extrude(xs1)
    outer_component.movey(+(width1 + gap) / 2)
    inner_component.movey(-(width2 + gap) / 2)

    c.add_port("o1", port=outer_component.ports["o1"])
    c.add_port("o2", port=inner_component.ports["o1"])
    c.add_port("o3", port=outer_component.ports["o2"])
    c.add_port("o4", port=inner_component.ports["o2"])
    c.flatten()
    return c

coupler_bent

coupler_broadband

coupler_broadband

coupler_broadband(
    w_sc: float = 0.5,
    gap_sc: float = 0.2,
    w_top: float = 0.6,
    gap_pc: float = 0.3,
    legnth_taper: float = 1.0,
    bend: ComponentSpec = "bend_euler",
    coupler_straight: ComponentSpec = "coupler_straight",
    length_coupler_straight: float = 12.4,
    lenght_coupler_big_gap: float = 4.7,
    cross_section: CrossSectionSpec = "strip",
    radius: float = 10.0,
) -> Component

Returns broadband coupler component.

https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/BroadbandDirectionalCoupler.html proposed in Zeqin Lu, Han Yun, Yun Wang, Zhitian Chen, Fan Zhang, Nicolas A. F. Jaeger, and Lukas Chrostowski, "Broadband silicon photonic directional coupler using asymmetric-waveguide based phase control," Opt. Express 23, 3795-3808 (2015), DOI: 10.1364/OE.23.003795.

Parameters:

Name Type Description Default
w_sc float

width of waveguides in the symmetric coupler section.

0.5
gap_sc float

gap size between the waveguides in the symmetric coupler section.

0.2
w_top float

width of the top waveguide in the phase control section.

0.6
gap_pc float

gap size in the phase control section.

0.3
legnth_taper float

length of the tapers.

1.0
bend ComponentSpec

bend factory.

'bend_euler'
coupler_straight ComponentSpec

coupler_straight factory.

'coupler_straight'
length_coupler_straight float

optimal L_1 from the 3d fdtd analysis.

12.4
lenght_coupler_big_gap float

optimal L_2 from the 3d fdtd analysis.

4.7
cross_section CrossSectionSpec

cross_section of the waveguides.

'strip'
radius float

bend radius.

10.0
Source code in gdsfactory/components/couplers/coupler_broadband.py
 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
 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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_broadband(
    w_sc: float = 0.5,  # width of waveguides in the symmetric coupler section
    gap_sc: float = 0.2,  # gap size between the waveguides in the symmetric coupler section
    w_top: float = 0.6,  # width of the top waveguide in the phase control section
    gap_pc: float = 0.3,  # gap size in the phase control section
    legnth_taper: float = 1.0,  # length of the tapers
    bend: ComponentSpec = "bend_euler",
    coupler_straight: ComponentSpec = "coupler_straight",
    length_coupler_straight: float = 12.4,  # optimal L_1 from the 3d fdtd analysis
    lenght_coupler_big_gap: float = 4.7,  # optimal L_2 from the 3d fdtd analysis
    cross_section: CrossSectionSpec = "strip",
    radius: float = 10.0,
) -> Component:
    """Returns broadband coupler component.

    https://docs.flexcompute.com/projects/tidy3d/en/latest/notebooks/BroadbandDirectionalCoupler.html
    proposed in Zeqin Lu, Han Yun, Yun Wang, Zhitian Chen, Fan Zhang, Nicolas A. F. Jaeger, and Lukas Chrostowski,
    "Broadband silicon photonic directional coupler using asymmetric-waveguide based phase control,"
    Opt. Express 23, 3795-3808 (2015), DOI: 10.1364/OE.23.003795.

    Args:
        w_sc: width of waveguides in the symmetric coupler section.
        gap_sc: gap size between the waveguides in the symmetric coupler section.
        w_top: width of the top waveguide in the phase control section.
        gap_pc: gap size in the phase control section.
        legnth_taper: length of the tapers.
        bend: bend factory.
        coupler_straight: coupler_straight factory.
        length_coupler_straight: optimal L_1 from the 3d fdtd analysis.
        lenght_coupler_big_gap: optimal L_2 from the 3d fdtd analysis.
        cross_section: cross_section of the waveguides.
        radius: bend radius.
    """
    c = gf.Component()

    xs = gf.get_cross_section(cross_section)
    assert xs.layer is not None
    layer = gf.get_layer(xs.layer)

    L_t = legnth_taper
    c = Component()
    L_2 = lenght_coupler_big_gap
    L_1 = length_coupler_straight

    y_coupler = -w_sc + xs.width / 2 + gap_pc / 2

    coupler = gf.get_component(
        coupler_straight, length=L_1, cross_section=cross_section, gap=gap_sc
    )
    coupler1 = c << coupler
    coupler1.xmin = -L_2 / 2 - L_t - L_1
    coupler1.y = y_coupler

    _bend = gf.get_component(bend, radius=radius, cross_section=cross_section)
    bend_lt = c << _bend
    bend_lb = c << _bend

    bend_lb.connect("o1", coupler1.ports["o1"])
    bend_lt.connect("o1", coupler1.ports["o2"], mirror=True)

    vertices_top = [
        (L_2 / 2 + L_t, 0),
        (L_2 / 2 + L_t, w_sc),
        (L_2 / 2 + L_t, w_sc),
        (L_2 / 2, w_top),
        (-L_2 / 2, w_top),
        (-L_2 / 2 - L_t, w_sc),
        (-L_2 / 2 - L_t, w_sc),
        (-L_2 / 2 - L_t, 0),
    ]

    c.add_polygon(vertices_top, layer=layer)

    # define vertices of the bottom waveguide
    vertices_bot = [
        (L_2 / 2 + L_t, -gap_sc - w_sc),
        (L_2 / 2 + L_t, -gap_sc),
        (L_2 / 2 + L_t, -gap_sc),
        (L_2 / 2, -gap_pc),
        (-L_2 / 2, -gap_pc),
        (-L_2 / 2 - L_t, -gap_sc),
        (-L_2 / 2 - L_t, -gap_sc),
        (-L_2 / 2 - L_t, -gap_sc - w_sc),
    ]
    c.add_polygon(vertices_bot, layer=layer)

    for section in xs.sections[1:]:
        w = section.width / 2
        layer_ = section.layer
        assert layer_ is not None
        vertices_top = [
            (L_2 / 2 + L_t, -w),
            (L_2 / 2 + L_t, w),
            (L_2 / 2 + L_t, w),
            (L_2 / 2, w_top + w),
            (-L_2 / 2, w_top + w),
            (-L_2 / 2 - L_t, w),
            (-L_2 / 2 - L_t, w),
            (-L_2 / 2 - L_t, -w),
        ]

        c.add_polygon(vertices_top, layer=layer_)

        # define vertices of the bottom waveguide
        vertices_bot = [
            (L_2 / 2 + L_t, -gap_sc - w),
            (L_2 / 2 + L_t, -gap_sc + w),
            (L_2 / 2 + L_t, -gap_sc + w),
            (L_2 / 2, -gap_pc + w),
            (-L_2 / 2, -gap_pc + w),
            (-L_2 / 2 - L_t, -gap_sc + w),
            (-L_2 / 2 - L_t, -gap_sc + w),
            (-L_2 / 2 - L_t, -gap_sc - w),
        ]
        c.add_polygon(vertices_bot, layer=layer_)

    coupler2 = c << coupler
    coupler2.xmax = L_2 / 2 + L_t + L_1
    coupler2.y = y_coupler

    _bend = gf.get_component(bend, radius=radius, cross_section=cross_section)
    bend_rt = c << _bend
    bend_rb = c << _bend

    bend_rb.connect("o1", coupler2.ports["o3"])
    bend_rt.connect("o1", coupler2.ports["o4"], mirror=True)

    c.add_port("o1", port=bend_lb.ports["o2"])
    c.add_port("o2", port=bend_lt.ports["o2"])
    c.add_port("o3", port=bend_rt.ports["o2"])
    c.add_port("o4", port=bend_rb.ports["o2"])
    return c

coupler_broadband

coupler_full

coupler_full

coupler_full(
    coupling_length: float = 40.0,
    dx: Delta = 10.0,
    dy: Delta = 4.8,
    gap: float = 0.5,
    dw: float = 0.1,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component

Adiabatic Full coupler.

Design based on asymmetric adiabatic full coupler designs, such as the one reported in 'Integrated Optic Adiabatic Devices on Silicon' by Y. Shani, et al (IEEE Journal of Quantum Electronics, Vol. 27, No. 3 March 1991).

  1. is the first half of the input S-bend straight where the input straights widths taper by +dw and -dw,
  2. is the second half of the S-bend straight with constant, unbalanced widths,
  3. is the coupling region where the straights from unbalanced widths to balanced widths to reverse polarity unbalanced widths,
  4. is the fixed width straight that curves away from the coupling region, 5.is the final curve where the straights taper back to the regular width specified in the straight template.

Parameters:

Name Type Description Default
coupling_length float

Length of the coupling region in um.

40.0
dx Delta

Length of the bend regions in um.

10.0
dy Delta

Port-to-port distance between the bend regions in um.

4.8
gap float

Distance between the two straights in um.

0.5
dw float

delta width. Top arm tapers to width - dw, bottom to width + dw in um.

0.1
cross_section CrossSectionSpec

cross-section spec.

'strip'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/couplers/coupler_full.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_full(
    coupling_length: float = 40.0,
    dx: Delta = 10.0,
    dy: Delta = 4.8,
    gap: float = 0.5,
    dw: float = 0.1,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component:
    """Adiabatic Full coupler.

    Design based on asymmetric adiabatic full
    coupler designs, such as the one reported in 'Integrated Optic Adiabatic
    Devices on Silicon' by Y. Shani, et al (IEEE Journal of Quantum
    Electronics, Vol. 27, No. 3 March 1991).

    1. is the first half of the input S-bend straight where the
    input straights widths taper by +dw and -dw,
    2. is the second half of the S-bend straight with constant,
    unbalanced widths,
    3. is the coupling region where the straights from unbalanced widths to
    balanced widths to reverse polarity unbalanced widths,
    4. is the fixed width straight that curves away from the coupling region,
    5.is the final curve where the straights taper back to the regular width
    specified in the straight template.

    Args:
        coupling_length: Length of the coupling region in um.
        dx: Length of the bend regions in um.
        dy: Port-to-port distance between the bend regions in um.
        gap: Distance between the two straights in um.
        dw: delta width. Top arm tapers to width - dw, bottom to width + dw in um.
        cross_section: cross-section spec.
        width: width of the waveguide. If None, it will use the width of the cross_section.

    """
    c = gf.Component()

    if width:
        x = gf.get_cross_section(cross_section=cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section=cross_section)
    x_top = x.copy(width=x.width + dw)
    x_bottom = x.copy(width=x.width - dw)

    taper_top = c << gf.components.taper(
        length=coupling_length,
        width1=x_top.width,
        width2=x_bottom.width,
        cross_section=cross_section,
    )

    taper_bottom = c << gf.components.taper(
        length=coupling_length,
        width1=x_bottom.width,
        width2=x_top.width,
        cross_section=cross_section,
    )

    bend_input_top = c << gf.c.bend_s(
        size=(dx, (dy - gap - x_top.width) / 2.0), cross_section=x_top
    )
    bend_input_top.movey((x_top.width + gap) / 2.0)

    bend_input_bottom = c << gf.c.bend_s(
        size=(dx, (-dy + gap + x_bottom.width) / 2.0), cross_section=x_bottom
    )
    bend_input_bottom.movey(-(x_bottom.width + gap) / 2.0)

    taper_top.connect("o1", bend_input_top.ports["o1"])
    taper_bottom.connect("o1", bend_input_bottom.ports["o1"])

    bend_output_top = c << gf.c.bend_s(
        size=(dx, (dy - gap - x_top.width) / 2.0), cross_section=x_bottom
    )

    bend_output_bottom = c << gf.c.bend_s(
        size=(dx, (-dy + gap + x_bottom.width) / 2.0), cross_section=x_top
    )

    bend_output_top.connect("o2", taper_top.ports["o2"], mirror=True)
    bend_output_bottom.connect("o2", taper_bottom.ports["o2"], mirror=True)

    x.add_bbox(c)

    c.add_port("o1", port=bend_input_bottom.ports["o2"])
    c.add_port("o2", port=bend_input_top.ports["o2"])
    c.add_port("o3", port=bend_output_top.ports["o1"])
    c.add_port("o4", port=bend_output_bottom.ports["o1"])
    c.auto_rename_ports()

    c.flatten()
    return c

coupler_full

coupler_pulley

coupler_pulley

coupler_pulley(
    radius: float = 10.0,
    ring_width: float | None = None,
    gap: float = 0.2,
    coupling_angle: float = 60.0,
    wg_length: float = 40.0,
    wg_height: float = 10.0,
    n_segments: int = 128,
    cross_section: CrossSectionSpec = "strip",
    layer: LayerSpec = "WG",
) -> Component

Returns a disc or ring with a pulley-coupled waveguide.

A waveguide wraps symmetrically around the top of a disc/ring over coupling_angle degrees. Bezier S-curves (following the CNST discPulley construction, eq. 2.22) route the waveguide from the coupling arc down to horizontal exits on both sides.

Parameters:

Name Type Description Default
radius float

radius of the disc, or outer radius of the ring.

10.0
ring_width float | None

width of the ring annulus. None for a solid disc.

None
gap float

gap between the waveguide inner edge and the disc/ring.

0.2
coupling_angle float

total wrap angle in degrees (symmetric about top).

60.0
wg_length float

horizontal half-length of the waveguide from the disc center to the exit end. Controls S-curve extent. Corresponds to CNST parameter L.

40.0
wg_height float

vertical drop from disc center to exit waveguide level. Corresponds to CNST parameter H.

10.0
n_segments int

number of points for each curved section.

128
cross_section CrossSectionSpec

cross-section spec for the coupling waveguide.

'strip'
layer LayerSpec

layer spec for the disc/ring.

'WG'
Source code in gdsfactory/components/couplers/coupler_pulley.py
 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
@gf.cell_with_module_name(tags=["couplers"])
def coupler_pulley(
    radius: float = 10.0,
    ring_width: float | None = None,
    gap: float = 0.2,
    coupling_angle: float = 60.0,
    wg_length: float = 40.0,
    wg_height: float = 10.0,
    n_segments: int = 128,
    cross_section: CrossSectionSpec = "strip",
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a disc or ring with a pulley-coupled waveguide.

    A waveguide wraps symmetrically around the top of a disc/ring
    over coupling_angle degrees. Bezier S-curves (following the CNST
    discPulley construction, eq. 2.22) route the waveguide from the
    coupling arc down to horizontal exits on both sides.

    Args:
        radius: radius of the disc, or outer radius of the ring.
        ring_width: width of the ring annulus. None for a solid disc.
        gap: gap between the waveguide inner edge and the disc/ring.
        coupling_angle: total wrap angle in degrees (symmetric about top).
        wg_length: horizontal half-length of the waveguide from the disc
            center to the exit end. Controls S-curve extent. Corresponds
            to CNST parameter L.
        wg_height: vertical drop from disc center to exit waveguide level.
            Corresponds to CNST parameter H.
        n_segments: number of points for each curved section.
        cross_section: cross-section spec for the coupling waveguide.
        layer: layer spec for the disc/ring.
    """
    c = Component()
    xs = gf.get_cross_section(cross_section)
    waveguide_width = xs.width

    half_angle = np.radians(coupling_angle / 2)
    wg_r = radius + gap + waveguide_width / 2
    h_exit = -wg_height

    # 1. Disc or ring
    theta_full = np.linspace(0, 2 * np.pi, n_segments * 2, endpoint=False)
    if ring_width is not None:
        inner_r = radius - ring_width
        outer_pts = np.column_stack(
            [radius * np.cos(theta_full), radius * np.sin(theta_full)]
        )
        inner_pts = np.column_stack(
            [inner_r * np.cos(theta_full[::-1]), inner_r * np.sin(theta_full[::-1])]
        )
        c.add_polygon(np.vstack([outer_pts, inner_pts]), layer=layer)
    else:
        c.add_polygon(
            np.column_stack([radius * np.cos(theta_full), radius * np.sin(theta_full)]),
            layer=layer,
        )

    # 2. Coupling arc (wraps around the top of the disc/ring)
    theta_arc = np.linspace(np.pi / 2 + half_angle, np.pi / 2 - half_angle, n_segments)
    arc_center = np.column_stack([wg_r * np.cos(theta_arc), wg_r * np.sin(theta_arc)])

    # 3. Bezier S-curves from coupling arc endpoints to horizontal exits
    # Following CNST eq. 2.22:
    #   P1 = arc endpoint, P2 = (±L, -H)
    #   C1 = P1 + R/4 * tangent_at_P1
    #   C2 = (P2x ∓ L/2, P2y)
    #   R = sqrt(H² + L²)

    L = wg_length
    H_param = wg_height + wg_r * np.sin(
        half_angle
    )  # total vertical drop from arc endpoint to exit
    R_bezier = np.sqrt(H_param**2 + L**2)

    bezier_paths = {}
    for name, arc_angle, x_sign in [
        ("left", np.pi / 2 + half_angle, -1),
        ("right", np.pi / 2 - half_angle, 1),
    ]:
        p1 = np.array([wg_r * np.cos(arc_angle), wg_r * np.sin(arc_angle)])
        p2 = np.array([x_sign * L, h_exit])
        tangent = np.array([x_sign * np.sin(arc_angle), -x_sign * np.cos(arc_angle)])
        c1 = p1 + (R_bezier / 4) * tangent
        c2 = np.array([p2[0] - x_sign * L / 2, p2[1]])

        bezier = _cubic_bezier(tuple(p1), tuple(c1), tuple(c2), tuple(p2), n_segments)
        bezier_paths[name] = bezier

    # 4. Assemble full center-line and extrude as path
    # Skip first point of each segment to avoid duplicates at junctions
    left_exit = np.array([[-L - 5, h_exit], [-L, h_exit]])
    right_exit = np.array([[L, h_exit], [L + 5, h_exit]])

    center_line = np.vstack(
        [
            left_exit,
            bezier_paths["left"][::-1][1:],
            arc_center[1:],
            bezier_paths["right"][1:],
            right_exit[1:],
        ]
    )

    wg_path = gf.Path(center_line)
    wg_path.start_angle = 0.0
    wg_path.end_angle = 0.0
    wg_ref = c << wg_path.extrude(xs)

    c.add_port("o1", port=wg_ref.ports["o2"])
    c.add_port("o2", port=wg_ref.ports["o1"])
    c.flatten()
    c.auto_rename_ports()
    return c

coupler_pulley

coupler_ring

coupler_ring

coupler_ring(
    gap: float = 0.2,
    radius: float | None = None,
    length_x: float = 4.0,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    cross_section_bend: CrossSectionSpec | None = None,
    length_extension: float | None = None,
) -> Component

Coupler for ring.

Parameters:

Name Type Description Default
gap float

spacing between parallel coupled straight waveguides.

0.2
radius float | None

of the bends. Default is None, which uses the default radius of the cross_section.

None
length_x float

length of the parallel coupled straight waveguides.

4.0
bend ComponentSpec

90 degrees bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
cross_section CrossSectionSpec

cross_section spec.

'strip'
cross_section_bend CrossSectionSpec | None

optional bend cross_section spec.

None
length_extension float | None

straight length extension at the end of the coupler bottom ports.

o2 o3 xx xx xx xx xx length_x x xx ◄───────────────► x xx xxx xx xxx xxx──────▲─────────xxx │gap o1──────▼─────────◄──────────────► o4 length_extension

None
Source code in gdsfactory/components/couplers/coupler_ring.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
@gf.cell_with_module_name(schematic_function=coupler_ring_schematic, tags=["couplers"])
def coupler_ring(
    gap: float = 0.2,
    radius: float | None = None,
    length_x: float = 4.0,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    cross_section_bend: CrossSectionSpec | None = None,
    length_extension: float | None = None,
) -> Component:
    r"""Coupler for ring.

    Args:
        gap: spacing between parallel coupled straight waveguides.
        radius: of the bends. Default is None, which uses the default radius of the cross_section.
        length_x: length of the parallel coupled straight waveguides.
        bend: 90 degrees bend spec.
        straight: straight spec.
        cross_section: cross_section spec.
        cross_section_bend: optional bend cross_section spec.
        length_extension: straight length extension at the end of the coupler bottom ports.

          o2                              o3
          xx                              xx
          xx                             xx
           xx          length_x          x
            xx     ◄───────────────►    x
             xx                       xxx
               xx                   xxx
                xxx──────▲─────────xxx
                         │gap
                 o1──────▼─────────◄──────────────► o4
                                    length_extension
    """
    if radius is None:
        radius = gf.get_cross_section(cross_section).radius
        assert radius is not None, "cross_section must have a radius"

    if length_extension is None:
        length_extension = 3.0 + radius

    c = Component()
    gap = gf.snap.snap_to_grid(gap, grid_factor=2)
    cross_section_bend = cross_section_bend or cross_section

    # define subcells
    coupler90_component = gf.get_component(
        coupler90,
        gap=gap,
        radius=radius,
        bend=bend,
        straight=straight,
        cross_section=cross_section,
        cross_section_bend=cross_section_bend,
        length_straight=length_extension,
    )
    coupler_straight_component = gf.get_component(
        coupler_straight,
        gap=gap,
        length=length_x,
        cross_section=cross_section,
    )

    # add references to subcells
    cbl = c << coupler90_component
    cbr = c << coupler90_component
    cs = c << coupler_straight_component

    # connect references
    cs.connect(port="o4", other=cbr.ports["o1"])
    cbl.connect(port="o2", other=cs.ports["o2"], mirror=True)

    c.add_port("o1", port=cbl.ports["o4"])
    c.add_port("o2", port=cbl.ports["o3"])
    c.add_port("o3", port=cbr.ports["o3"])
    c.add_port("o4", port=cbr.ports["o4"])

    c.add_ports(
        gf.port.select_ports_list(ports=cbl.ports, port_type="electrical"), prefix="cbl"
    )
    c.add_ports(
        gf.port.select_ports_list(ports=cbr.ports, port_type="electrical"), prefix="cbr"
    )

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    c.flatten()
    c.info["radius"] = radius
    return c

coupler_ring

coupler_straight

coupler_straight(
    length: float = 10.0,
    gap: float = 0.27,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Coupler_straight with two parallel straights.

Parameters:

Name Type Description Default
length float

of straight.

10.0
gap float

between straights.

0.27
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
Source code in gdsfactory/components/couplers/coupler.py
 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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_straight(
    length: float = 10.0,
    gap: float = 0.27,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Coupler_straight with two parallel straights.

    Args:
        length: of straight.
        gap: between straights.
        cross_section: specification (CrossSection, string or dict).

        o2──────▲─────────o3
                │gap
        o1──────▼─────────o4
    """
    c = Component()
    x = gf.get_cross_section(cross_section)
    _straight = gf.c.straight(length=length, cross_section=cross_section)

    top = c << _straight
    bot = c << _straight

    w = x.width
    y = w + gap

    top.movey(+y)

    if bot.ports and top.ports:
        c.add_port("o1", port=bot.ports[0])
        c.add_port("o2", port=top.ports[0])
        c.add_port("o3", port=bot.ports[1])
        c.add_port("o4", port=top.ports[1])
        c.auto_rename_ports()
    return c

coupler_straight

coupler_straight_asymmetric

coupler_straight_asymmetric

coupler_straight_asymmetric(
    length: float = 10.0,
    gap: float = 0.27,
    width_top: float = 0.5,
    width_bot: float = 1,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Coupler with two parallel straights of different widths.

Parameters:

Name Type Description Default
length float

of straight.

10.0
gap float

between straights.

0.27
width_top float

of top straight.

0.5
width_bot float

of bottom straight.

1
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/couplers/coupler_straight_asymmetric.py
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
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["couplers"])
def coupler_straight_asymmetric(
    length: float = 10.0,
    gap: float = 0.27,
    width_top: float = 0.5,
    width_bot: float = 1,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Coupler with two parallel straights of different widths.

    Args:
        length: of straight.
        gap: between straights.
        width_top: of top straight.
        width_bot: of bottom straight.
        cross_section: cross_section spec.
    """
    c = Component()

    xs_top = gf.get_cross_section(cross_section, width=width_top)
    xs_bot = gf.get_cross_section(cross_section, width=width_bot)

    top = c << gf.c.straight(length=length, cross_section=xs_top)
    bot = c << gf.c.straight(length=length, cross_section=xs_bot)

    dy = 0.5 * (width_top + width_bot) + gap
    top.movey(dy)
    c.add_port("o1", port=bot.ports[0])
    c.add_port("o2", port=top.ports[0])
    c.add_port("o3", port=top.ports[1])
    c.add_port("o4", port=bot.ports[1])
    c.flatten()
    return c

coupler_straight_asymmetric

coupler_symmetric

coupler_symmetric(
    bend: ComponentSpec = "bend_s",
    gap: float = 0.234,
    dy: Delta = 4.0,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component

Two coupled straights with bends.

Parameters:

Name Type Description Default
bend ComponentSpec

bend spec.

'bend_s'
gap float

in um.

0.234
dy Delta

port to port vertical spacing.

4.0
dx Delta

bend length in x direction.

10.0
cross_section CrossSectionSpec

section.

'strip'
allow_min_radius_violation bool

if True does not check for min bend radius.

       dx
    |-----|
       ___ o3
      /       |

o2 _/ | | o1 ___ | dy \ | ___ | o4

False
Source code in gdsfactory/components/couplers/coupler.py
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
@gf.cell_with_module_name(tags=["couplers"])
def coupler_symmetric(
    bend: ComponentSpec = "bend_s",
    gap: float = 0.234,
    dy: Delta = 4.0,
    dx: Delta = 10.0,
    cross_section: CrossSectionSpec = "strip",
    allow_min_radius_violation: bool = False,
) -> Component:
    r"""Two coupled straights with bends.

    Args:
        bend: bend spec.
        gap: in um.
        dy: port to port vertical spacing.
        dx: bend length in x direction.
        cross_section: section.
        allow_min_radius_violation: if True does not check for min bend radius.

                       dx
                    |-----|
                       ___ o3
                      /       |
             o2 _____/        |
                              |
             o1 _____         |  dy
                     \        |
                      \___    |
                           o4

    """
    c = Component()
    x = gf.get_cross_section(cross_section)
    width = x.width
    dy = (dy - gap - width) / 2

    bend_component = gf.get_component(
        bend,
        size=(dx, dy),
        cross_section=cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
    )
    top_bend = c << bend_component
    bot_bend = c << bend_component
    bend_ports = top_bend.ports.filter(port_type="optical")
    bend_port1_name = bend_ports[0].name
    bend_port2_name = bend_ports[1].name

    w = bend_component[bend_port1_name].width
    y = w + gap
    y /= 2

    bot_bend.dmirror_y()
    top_bend.movey(+y)
    bot_bend.movey(-y)

    c.add_port("o1", port=bot_bend[bend_port1_name])
    c.add_port("o2", port=top_bend[bend_port1_name])
    c.add_port("o3", port=top_bend[bend_port2_name])
    c.add_port("o4", port=bot_bend[bend_port2_name])

    c.info["length"] = bend_component.info["length"]
    c.info["min_bend_radius"] = bend_component.info["min_bend_radius"]
    return c

coupler_symmetric

detectors

ge_detector_straight_si_contacts

ge_detector_straight_si_contacts(
    length: float = 40.0,
    cross_section: CrossSectionSpec = "pn_ge_detector_si_contacts",
    via_stack: ComponentSpec = "via_stack_slab_m3",
    via_stack_width: float = 10.0,
    via_stack_spacing: float = 5.0,
    via_stack_offset: float = 0.0,
    taper_length: float = 20.0,
    taper_width: float = 0.8,
    taper_cros_section: CrossSectionSpec = "strip",
) -> Component

Returns a straight Ge on Si detector with silicon contacts.

There are no contacts on the Ge. These detectors could have lower dark current and sensitivity compared to those with contacts in the Ge. See Chen et al., "High-Responsivity Low-Voltage 28-Gb/s Ge p-i-n Photodetector With Silicon Contacts", Journal of Lightwave Technology 33(4), 2015.

https://doi.org/10.1109/JLT.2014.2367134

Parameters:

Name Type Description Default
length float

pd length.

40.0
cross_section CrossSectionSpec

for the waveguide.

'pn_ge_detector_si_contacts'
via_stack ComponentSpec

for the via_stacks. First element

'via_stack_slab_m3'
via_stack_width float

width of the via_stack.

10.0
via_stack_spacing float

spacing between via_stacks.

5.0
via_stack_offset float

with respect to the detector

0.0
taper_length float

length of the taper.

20.0
taper_width float

width of the taper.

0.8
taper_cros_section CrossSectionSpec

cross_section of the taper.

'strip'
Source code in gdsfactory/components/detectors/detector_ge.py
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
@gf.cell_with_module_name(schematic_function=photodiode_schematic, tags=["detectors"])
def ge_detector_straight_si_contacts(
    length: float = 40.0,
    cross_section: CrossSectionSpec = "pn_ge_detector_si_contacts",
    via_stack: ComponentSpec = "via_stack_slab_m3",
    via_stack_width: float = 10.0,
    via_stack_spacing: float = 5.0,
    via_stack_offset: float = 0.0,
    taper_length: float = 20.0,
    taper_width: float = 0.8,
    taper_cros_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns a straight Ge on Si detector with silicon contacts.

    There are no contacts on the Ge. These detectors could have lower
    dark current and sensitivity compared to those with contacts in the
    Ge. See Chen et al., "High-Responsivity Low-Voltage 28-Gb/s Ge p-i-n
    Photodetector With Silicon Contacts", Journal of Lightwave Technology 33(4), 2015.

    https://doi.org/10.1109/JLT.2014.2367134

    Args:
        length: pd length.
        cross_section: for the waveguide.
        via_stack: for the via_stacks. First element
        via_stack_width: width of the via_stack.
        via_stack_spacing: spacing between via_stacks.
        via_stack_offset: with respect to the detector
        taper_length: length of the taper.
        taper_width: width of the taper.
        taper_cros_section: cross_section of the taper.
    """
    c = Component()
    xs = gf.get_cross_section(taper_cros_section)

    taper = gf.c.taper(
        width1=xs.width,
        width2=taper_width,
        length=taper_length,
        cross_section=taper_cros_section,
    )

    via_stack = gf.get_component(
        via_stack,
        size=(length, via_stack_width),
    )

    wg = c << gf.components.straight(
        cross_section=cross_section,
        length=length,
    )

    t1 = c << taper
    t1.connect("o2", wg["o1"], allow_width_mismatch=True)
    c.add_port("o1", port=t1["o1"])

    via_stack_top = c << via_stack
    via_stack_bot = c << via_stack

    via_stack_bot.xmin = wg.xmin
    via_stack_top.xmin = wg.xmin

    via_stack_top.ymin = +via_stack_spacing / 2 + via_stack_offset
    via_stack_bot.ymax = -via_stack_spacing / 2 + via_stack_offset

    bot_port = c.add_port(port=via_stack_bot.ports["e3"], name="bot")
    top_port = c.add_port(port=via_stack_top.ports["e3"], name="top")
    c.create_pin(ports=[bot_port], name="bot")
    c.create_pin(ports=[top_port], name="top")
    return c

ge_detector_straight_si_contacts

dies

add_frame

add_frame(
    component: ComponentSpec = "rectangle",
    width: float = 10.0,
    spacing: float = 10.0,
    layer: LayerSpec = "WG",
) -> Component

Returns component with a frame around it.

Parameters:

Name Type Description Default
component ComponentSpec

Component to frame.

'rectangle'
width float

of the frame.

10.0
spacing float

of component to frame.

10.0
layer LayerSpec

frame layer.

'WG'
Source code in gdsfactory/components/dies/align.py
 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
@gf.cell_with_module_name(tags=["dies"])
def add_frame(
    component: ComponentSpec = "rectangle",
    width: float = 10.0,
    spacing: float = 10.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns component with a frame around it.

    Args:
        component: Component to frame.
        width: of the frame.
        spacing: of component to frame.
        layer: frame layer.
    """
    c = Component()
    layer = gf.get_layer(layer)
    component = gf.get_component(component)
    cref = c.add_ref(component)
    cref.x = 0
    cref.y = 0
    y = max([component.xsize, component.ysize]) / 2 + spacing + width / 2
    x = y
    w = width

    rh = gf.c.rectangle(size=(2 * y + w, w), layer=layer, centered=True)
    rtop = c.add_ref(rh)
    rbot = c.add_ref(rh)
    rtop.movey(+y)
    rbot.movey(-y)

    rv = gf.c.rectangle(size=(w, 2 * y), layer=layer, centered=True)
    rl = c.add_ref(rv)
    rr = c.add_ref(rv)
    rl.movex(-x)
    rr.movex(+x)
    c.flatten()
    return c

add_frame

align_wafer

align_wafer(
    width: float = 10.0,
    spacing: float = 10.0,
    cross_length: float = 80.0,
    layer: LayerSpec = "WG",
    layer_cladding: tuple[int, int] | None = None,
    square_corner: str = "bottom_left",
) -> Component

Returns cross inside a frame to align wafer.

Parameters:

Name Type Description Default
width float

in um.

10.0
spacing float

in um.

10.0
cross_length float

for the cross.

80.0
layer LayerSpec

for the cross.

'WG'
layer_cladding tuple[int, int] | None

optional.

None
square_corner str

bottom_left, bottom_right, top_right, top_left.

'bottom_left'
Source code in gdsfactory/components/dies/align.py
10
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
@gf.cell_with_module_name(tags=["dies"])
def align_wafer(
    width: float = 10.0,
    spacing: float = 10.0,
    cross_length: float = 80.0,
    layer: LayerSpec = "WG",
    layer_cladding: tuple[int, int] | None = None,
    square_corner: str = "bottom_left",
) -> Component:
    """Returns cross inside a frame to align wafer.

    Args:
        width: in um.
        spacing: in um.
        cross_length: for the cross.
        layer: for the cross.
        layer_cladding: optional.
        square_corner: bottom_left, bottom_right, top_right, top_left.
    """
    layer = gf.get_layer(layer)
    c = Component()
    cross = gf.components.cross(length=cross_length, width=width, layer=layer)
    c.add_ref(cross)

    b = cross_length / 2 + spacing + width / 2
    w = width

    rh = gf.c.rectangle(size=(2 * b + w, w), layer=layer, centered=True)
    rv = gf.c.rectangle(size=(w, 2 * b), layer=layer, centered=True)

    rtop = c.add_ref(rh)
    rbot = c.add_ref(rh)

    rtop.movey(+b)
    rbot.movey(-b)

    rl = c.add_ref(rv)
    rr = c.add_ref(rv)
    rl.movex(-b)
    rr.movex(+b)

    wsq = (cross_length + 2 * spacing) / 4
    square_mark = c << gf.c.rectangle(size=(wsq, wsq), layer=layer, centered=True)
    a = width / 2 + wsq / 2 + spacing

    corner_to_position = {
        "bottom_left": (-a, -a),
        "bottom_right": (a, -a),
        "top_right": (a, a),
        "top_left": (-a, a),
    }

    square_mark.move(corner_to_position[square_corner])

    if layer_cladding:
        rc_tile_excl = gf.c.rectangle(
            size=(2 * (b + spacing), 2 * (b + spacing)),
            layer=layer_cladding,
            centered=True,
        )
        c.add_ref(rc_tile_excl)

    return c

align_wafer

die

based on phidl.geometry.

die

die(
    size: Size = (10000.0, 10000.0),
    street_width: float = 100.0,
    street_length: float = 1000.0,
    die_name: str | None = "chip99",
    text_size: float = 100.0,
    text_location: str | Float2 = "SW",
    layer: LayerSpec | None = "FLOORPLAN",
    bbox_layer: LayerSpec | None = "FLOORPLAN",
    text_layer: LayerSpec = "WG",
    text: ComponentSpec = "text",
    draw_corners: bool = False,
) -> gf.Component

Returns die with optional markers marking the boundary of the die.

Parameters:

Name Type Description Default
size Size

x, y dimensions of the die.

(10000.0, 10000.0)
street_width float

Width of the corner marks for die-sawing.

100.0
street_length float

Length of the corner marks for die-sawing.

1000.0
die_name str | None

Label text. If None, no label is added.

'chip99'
text_size float

Label text size.

100.0
text_location str | Float2

{'NW', 'N', 'NE', 'SW', 'S', 'SE'} or (x, y) coordinate.

'SW'
layer LayerSpec | None

For street widths. None to not draw the street widths.

'FLOORPLAN'
bbox_layer LayerSpec | None

optional bbox layer drawn bounding box around the die.

'FLOORPLAN'
text_layer LayerSpec

Layer for the die name text.

'WG'
text ComponentSpec

function use for generating text. Needs to accept text, size, layer.

'text'
draw_corners bool

True draws only corners. False draws a square die.

False
Source code in gdsfactory/components/dies/die.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
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
@gf.cell_with_module_name(tags=["dies"])
def die(
    size: Size = (10000.0, 10000.0),
    street_width: float = 100.0,
    street_length: float = 1000.0,
    die_name: str | None = "chip99",
    text_size: float = 100.0,
    text_location: str | Float2 = "SW",
    layer: LayerSpec | None = "FLOORPLAN",
    bbox_layer: LayerSpec | None = "FLOORPLAN",
    text_layer: LayerSpec = "WG",
    text: ComponentSpec = "text",
    draw_corners: bool = False,
) -> gf.Component:
    """Returns die with optional markers marking the boundary of the die.

    Args:
        size: x, y dimensions of the die.
        street_width: Width of the corner marks for die-sawing.
        street_length: Length of the corner marks for die-sawing.
        die_name: Label text. If None, no label is added.
        text_size: Label text size.
        text_location: {'NW', 'N', 'NE', 'SW', 'S', 'SE'} or (x, y) coordinate.
        layer: For street widths. None to not draw the street widths.
        bbox_layer: optional bbox layer drawn bounding box around the die.
        text_layer: Layer for the die name text.
        text: function use for generating text. Needs to accept text, size, layer.
        draw_corners: True draws only corners. False draws a square die.
    """
    c = gf.Component()
    sx, sy = size[0] / 2, size[1] / 2

    if layer:
        if not draw_corners:
            street_length = sx
        xpts = np.array(
            [
                sx,
                sx,
                sx - street_width,
                sx - street_width,
                sx - street_length,
                sx - street_length,
            ]
        )
        if not draw_corners:
            street_length = sy
        ypts = np.array(
            [
                sy,
                sy - street_length,
                sy - street_length,
                sy - street_width,
                sy - street_width,
                sy,
            ]
        )
        c.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)
        c.add_polygon(list(zip(-xpts, ypts, strict=False)), layer=layer)
        c.add_polygon(list(zip(xpts, -ypts, strict=False)), layer=layer)
        c.add_polygon(list(zip(-xpts, -ypts, strict=False)), layer=layer)

    if bbox_layer:
        c.add_polygon([(sx, sy), (sx, -sy), (-sx, -sy), (-sx, sy)], layer=bbox_layer)

    if die_name:
        text_component = gf.get_component(
            text, text=die_name, size=text_size, layer=text_layer
        )
        t = c.add_ref(text_component)

        d = street_width + 20
        if isinstance(text_location, str):
            text_location = text_location.upper()
            if text_location == "N":
                t.x, t.ymax = [0, sy - d]
            elif text_location == "NE":
                t.xmax, t.ymax = [sx - d, sy - d]
            elif text_location == "NW":
                t.xmin, t.ymax = [-sx + d, sy - d]
            elif text_location == "S":
                t.x, t.ymin = [0, -sy + d]
            elif text_location == "SE":
                t.xmax, t.ymin = [sx - d, -sy + d]
            elif text_location == "SW":
                t.xmin, t.ymin = [-sx + d, -sy + d]
            else:
                raise ValueError(
                    f"Invalid text_location: {text_location} not in N, NE, NW, S, SE, SW"
                )
        else:
            t.x, t.y = text_location

    return c

die

die_frame

die_frame(
    size: Size = (11200.0, 5000.0),
    layer_floorplan: LayerSpec = "FLOORPLAN",
) -> gf.Component
Source code in gdsfactory/components/dies/die_frame_with_pads.py
15
16
17
18
19
20
21
22
@gf.cell(tags=["dies"])
def die_frame(
    size: Size = (11200.0, 5000.0),
    layer_floorplan: LayerSpec = "FLOORPLAN",
) -> gf.Component:
    return gf.c.rectangle(
        size=size, layer=layer_floorplan, centered=True, port_type=None
    )

die_frame

die_frame_phix

die_frame_phix(
    die_frame: ComponentSpec = "die_frame",
    nfibers: int = 32,
    npads: int = 60,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: (
        ComponentSpec | None
    ) = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    edge_to_pad_distance_left: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    pad_port_name_rf: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    fiber_coupler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = True,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = "text_rectangular",
    pad_side_distance: float = 1160.0,
    xoffset_rf_pads: float = 50,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
    pad_rotation_rf: float = 0,
    with_loopback: bool = True,
) -> Component

A die_frame with grating couplers and pads.

Parameters:

Name Type Description Default
die_frame ComponentSpec

die_frame spec.

'die_frame'
nfibers int

the number of grating couplers.

32
npads int

the number of pads.

60
npads_rf int

the number of RF pads on the left side.

6
fiber_pitch float

the pitch of the grating couplers, in um.

127.0
pad_pitch float

the pitch of the pads, in um.

150.0
pad_pitch_gsg float

the pitch of the GSG pads, in um.

720.0
edge_coupler ComponentSpec | None

the grating coupler component.

'edge_coupler_silicon'
grating_coupler ComponentSpec | None

Optional grating coupler.

None
cross_section CrossSectionSpec

the cross section.

'strip'
pad ComponentSpec

the pad component.

'pad'
pad_gsg ComponentSpec

the GSG pad component.

'pad_gsg'
edge_to_pad_distance float

the distance from the edge to the pads, in um.

200.0
edge_to_pad_distance_left float | None

Optional distance from the left edge to the pads, in um. If None, uses edge_to_pad_distance for both sides.

None
pad_port_name_top str

name of the pad port name at the top facing south.

'e4'
pad_port_name_bot str

name of the pad port name at the bottom facing north.

'e2'
pad_port_name_rf str

name of the RF pad port name.

'e2'
layer_fiducial LayerSpec

layer for fiducials.

'M3'
layer_ruler LayerSpec

layer for ruler.

'WG'
ruler_bbox_layers tuple[LayerSpec, ...] | None

layers for bbox.

None
ruler_bbox_offset float

offset for bbox.

3.0
ruler_yoffset float

y-offset for ruler.

0
ruler_xoffset float

x-offset for ruler.

0
fiber_coupler_xoffset float

x-offset for fiber couplers.

0
with_right_fiber_coupler bool

if True, adds edge couplers on the right side.

True
with_left_fiber_coupler bool

if True, adds edge couplers on the left side.

True
text_offset Float2

offset for text.

(20, 10)
text ComponentSpec | None

text component spec.

'text_rectangular'
pad_side_distance float

distance from the die frame side to the first pad, in um.

1160.0
xoffset_rf_pads float

RF pads x-offset.

50
pad_rotation_dc_north float

rotation for DC pads.

0
pad_rotation_dc_south float

rotation for DC pads.

0
pad_rotation_rf float

rotation for RF pads.

0
with_loopback bool

if True, adds loopback structures.

True
Source code in gdsfactory/components/dies/die_frame_with_pads.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
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
def die_frame_phix(
    die_frame: ComponentSpec = "die_frame",
    nfibers: int = 32,
    npads: int = 60,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: ComponentSpec | None = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    edge_to_pad_distance_left: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    pad_port_name_rf: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    fiber_coupler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = True,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = "text_rectangular",
    pad_side_distance: float = 1160.0,
    xoffset_rf_pads: float = 50,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
    pad_rotation_rf: float = 0,
    with_loopback: bool = True,
) -> Component:
    """A die_frame with grating couplers and pads.

    Args:
        die_frame: die_frame spec.
        nfibers: the number of grating couplers.
        npads: the number of pads.
        npads_rf: the number of RF pads on the left side.
        fiber_pitch: the pitch of the grating couplers, in um.
        pad_pitch: the pitch of the pads, in um.
        pad_pitch_gsg: the pitch of the GSG pads, in um.
        edge_coupler: the grating coupler component.
        grating_coupler: Optional grating coupler.
        cross_section: the cross section.
        pad: the pad component.
        pad_gsg: the GSG pad component.
        edge_to_pad_distance: the distance from the edge to the pads, in um.
        edge_to_pad_distance_left: Optional distance from the left edge to the pads, in um. If None, uses edge_to_pad_distance for both sides.
        pad_port_name_top: name of the pad port name at the top facing south.
        pad_port_name_bot: name of the pad port name at the bottom facing north.
        pad_port_name_rf: name of the RF pad port name.
        layer_fiducial: layer for fiducials.
        layer_ruler: layer for ruler.
        ruler_bbox_layers: layers for bbox.
        ruler_bbox_offset: offset for bbox.
        ruler_yoffset: y-offset for ruler.
        ruler_xoffset: x-offset for ruler.
        fiber_coupler_xoffset: x-offset for fiber couplers.
        with_right_fiber_coupler: if True, adds edge couplers on the right side.
        with_left_fiber_coupler: if True, adds edge couplers on the left side.
        text_offset: offset for text.
        text: text component spec.
        pad_side_distance: distance from the die frame side to the first pad, in um.
        xoffset_rf_pads: RF pads x-offset.
        pad_rotation_dc_north: rotation for DC pads.
        pad_rotation_dc_south: rotation for DC pads.
        pad_rotation_rf: rotation for RF pads.
        with_loopback: if True, adds loopback structures.
    """
    if npads > 60:
        raise ValueError("npads should be <= 60. Reach out to PHIX for support.")

    c = Component()

    d = gf.get_component(die_frame)
    fp = c << d
    fp.x = 0
    fp.y = 0
    xs, ys = fp.xsize, fp.ysize

    # Add optical ports
    x0 = xs / 2

    edge_to_pad_distance_left = edge_to_pad_distance_left or edge_to_pad_distance

    if edge_coupler or grating_coupler:
        if edge_coupler:
            if with_loopback:
                gca = gf.c.edge_coupler_array_with_loopback(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    cross_section=cross_section,
                    text_offset=text_offset,
                    text=text,
                    x_reflection=False,
                )
                gca_left = gf.c.edge_coupler_array_with_loopback(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    cross_section=cross_section,
                    text_offset=(-text_offset[0], text_offset[1]),
                    text=text,
                    x_reflection=True,
                )
            else:
                gca = gf.c.edge_coupler_array(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    text_offset=text_offset,
                    text=text,
                    x_reflection=False,
                )
                gca_left = gf.c.edge_coupler_array(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    text_offset=(-text_offset[0], text_offset[1]),
                    text=text,
                    x_reflection=True,
                )

            if with_left_fiber_coupler:
                left = c << gca_left
                left.xmin = -xs / 2 - fiber_coupler_xoffset
                left.y = fp.y
                c.add_ports(left.ports, prefix="W")

            if with_right_fiber_coupler:
                right = c << gca
                right.xmax = xs / 2 + fiber_coupler_xoffset
                right.y = fp.y
                c.add_ports(right.ports, prefix="E")

        else:
            gca = gf.c.grating_coupler_array(
                n=nfibers,
                pitch=fiber_pitch,
                cross_section=cross_section,
                with_loopback=True,
            )
            gca_left = gf.c.grating_coupler_array(
                n=nfibers,
                pitch=fiber_pitch,
                cross_section=cross_section,
                with_loopback=True,
            )
            fiber_coupler_xoffset -= 750

            if with_left_fiber_coupler:
                left = c << gca_left
                left.rotate(-90)
                left.xmin = -xs / 2 - fiber_coupler_xoffset
                left.y = fp.y
                c.add_ports(left.ports, prefix="W")

            if with_right_fiber_coupler:
                right = c << gca
                right.rotate(+90)
                right.xmax = xs / 2 + fiber_coupler_xoffset
                right.y = fp.y
                c.add_ports(right.ports, prefix="E")
    ruler = gf.c.ruler(
        layer=layer_ruler,
        bbox_layers=ruler_bbox_layers,
        bbox_offset=ruler_bbox_offset,
    )

    if with_right_fiber_coupler:
        ruler_top_right = c << ruler
        ruler_top_right.xmax = fp.xmax - ruler_xoffset
        ruler_top_right.ymax = fp.ymax - 300 + ruler_yoffset

        ruler_bot_right = c << ruler
        ruler_bot_right.xmax = fp.xmax - ruler_xoffset
        ruler_bot_right.ymin = fp.ymin + 300 - ruler_yoffset

    if with_left_fiber_coupler:
        ruler_top_left = c << ruler
        ruler_top_left.rotate(180)
        ruler_top_left.xmin = fp.xmin + ruler_xoffset
        ruler_top_left.ymax = fp.ymax - 300 + ruler_yoffset

        ruler_bot_left = c << ruler
        ruler_bot_left.rotate(180)
        ruler_bot_left.xmin = fp.xmin + ruler_xoffset
        ruler_bot_left.ymin = fp.ymin + 300 - ruler_yoffset

    else:
        # left RF pads
        y0 = fp.ymax - 390 - pad_pitch_gsg / 2 + 50
        for i in range(npads_rf):
            pad_ref = c << gf.get_component(pad_gsg)
            pad_ref.rotate(pad_rotation_rf)
            pad_ref.y = y0 - i * pad_pitch_gsg
            pad_ref.xmin = fp.xmin + xoffset_rf_pads
            c.add_port(
                name=f"e{i}",
                port=pad_ref.ports[pad_port_name_rf],
            )

    # Add electrical ports
    pad = gf.get_component(pad)

    x0_pads = -xs / 2 + pad_side_distance
    x0 = x0_pads

    top_left = c << gf.c.cross(layer=layer_fiducial, length=150, width=20)
    top_left.xmax = x0 - 75
    top_left.y = +ys / 2 - edge_to_pad_distance - 50

    # north pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.rotate(pad_rotation_dc_north)
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymax = ys / 2 - edge_to_pad_distance
        c.add_port(
            name=f"N{i}",
            port=pad_ref.ports[pad_port_name_top],
        )
    top_right = c << gf.c.circle(layer=layer_fiducial, radius=75)
    top_right.xmin = pad_ref.xmax + 480
    top_right.y = +ys / 2 - edge_to_pad_distance - 50

    bot_left = c << gf.c.circle(layer=layer_fiducial, radius=75)
    bot_left.xmax = x0 - 75
    bot_left.y = -ys / 2 + edge_to_pad_distance + 50

    x0 = x0_pads

    # south pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.rotate(pad_rotation_dc_south)
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymin = -ys / 2 + edge_to_pad_distance
        c.add_port(
            name=f"S{i}",
            port=pad_ref.ports[pad_port_name_bot],
        )

    bot_right = c << gf.c.circle(layer=layer_fiducial, radius=75)
    bot_right.xmin = pad_ref.xmax + 480
    bot_right.ymin = -ys / 2 + edge_to_pad_distance

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

die_frame_phix

die_frame_phix_dc

die_frame_phix_dc(
    die_frame: ComponentSpec = "die_frame",
    nfibers: int = 32,
    npads: int = 59,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: (
        ComponentSpec | None
    ) = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = True,
    fiber_coupler_xoffset: float = 0,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = None,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
    pad_side_distance: float = 1160.0,
) -> Component
Source code in gdsfactory/components/dies/die_frame_with_pads.py
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
@gf.cell_with_module_name(tags=["dies"])
def die_frame_phix_dc(
    die_frame: ComponentSpec = "die_frame",
    nfibers: int = 32,
    npads: int = 59,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: ComponentSpec | None = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = True,
    fiber_coupler_xoffset: float = 0,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = None,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
    pad_side_distance: float = 1160.0,
) -> Component:
    return die_frame_phix(
        die_frame=die_frame,
        nfibers=nfibers,
        npads=npads,
        npads_rf=npads_rf,
        fiber_pitch=fiber_pitch,
        pad_pitch=pad_pitch,
        pad_pitch_gsg=pad_pitch_gsg,
        edge_coupler=edge_coupler,
        grating_coupler=grating_coupler,
        cross_section=cross_section,
        pad=pad,
        pad_gsg=pad_gsg,
        edge_to_pad_distance=edge_to_pad_distance,
        pad_port_name_top=pad_port_name_top,
        pad_port_name_bot=pad_port_name_bot,
        layer_fiducial=layer_fiducial,
        layer_ruler=layer_ruler,
        ruler_bbox_layers=ruler_bbox_layers,
        ruler_bbox_offset=ruler_bbox_offset,
        ruler_yoffset=ruler_yoffset,
        ruler_xoffset=ruler_xoffset,
        with_right_fiber_coupler=with_right_fiber_coupler,
        with_left_fiber_coupler=with_left_fiber_coupler,
        text_offset=text_offset,
        text=text,
        fiber_coupler_xoffset=fiber_coupler_xoffset,
        pad_rotation_dc_north=pad_rotation_dc_north,
        pad_rotation_dc_south=pad_rotation_dc_south,
        pad_side_distance=pad_side_distance,
    )

die_frame_phix_dc

die_frame_phix_rf

die_frame_phix_rf(
    die_frame: ComponentSpec = "die_frame_rf",
    nfibers: int = 32,
    npads: int = 59,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: (
        ComponentSpec | None
    ) = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    pad_port_name_rf: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = False,
    fiber_coupler_xoffset: float = 0,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = None,
    pad_side_distance: float = 350.0,
    xoffset_rf_pads: float = 50,
    pad_rotation_rf: float = 0,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
) -> Component
Source code in gdsfactory/components/dies/die_frame_with_pads.py
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
@gf.cell_with_module_name(tags=["dies"])
def die_frame_phix_rf(
    die_frame: ComponentSpec = "die_frame_rf",
    nfibers: int = 32,
    npads: int = 59,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: ComponentSpec | None = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    pad_port_name_rf: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = False,
    fiber_coupler_xoffset: float = 0,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = None,
    pad_side_distance: float = 350.0,
    xoffset_rf_pads: float = 50,
    pad_rotation_rf: float = 0,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
) -> Component:
    return die_frame_phix(
        die_frame=die_frame,
        nfibers=nfibers,
        npads=npads,
        npads_rf=npads_rf,
        fiber_pitch=fiber_pitch,
        pad_pitch=pad_pitch,
        pad_pitch_gsg=pad_pitch_gsg,
        edge_coupler=edge_coupler,
        grating_coupler=grating_coupler,
        cross_section=cross_section,
        pad=pad,
        pad_gsg=pad_gsg,
        edge_to_pad_distance=edge_to_pad_distance,
        pad_port_name_top=pad_port_name_top,
        pad_port_name_bot=pad_port_name_bot,
        pad_port_name_rf=pad_port_name_rf,
        layer_fiducial=layer_fiducial,
        layer_ruler=layer_ruler,
        ruler_bbox_layers=ruler_bbox_layers,
        ruler_bbox_offset=ruler_bbox_offset,
        ruler_yoffset=ruler_yoffset,
        ruler_xoffset=ruler_xoffset,
        with_right_fiber_coupler=with_right_fiber_coupler,
        with_left_fiber_coupler=with_left_fiber_coupler,
        text_offset=text_offset,
        text=text,
        pad_side_distance=pad_side_distance,
        fiber_coupler_xoffset=fiber_coupler_xoffset,
        xoffset_rf_pads=xoffset_rf_pads,
        pad_rotation_dc_south=pad_rotation_dc_south,
        pad_rotation_dc_north=pad_rotation_dc_north,
        pad_rotation_rf=pad_rotation_rf,
    )

die_frame_phix_rf

die_frame_rf

die_frame_rf(
    size: Size = (10400.0, 5000.0),
    layer_floorplan: LayerSpec = "FLOORPLAN",
) -> gf.Component
Source code in gdsfactory/components/dies/die_frame_with_pads.py
25
26
27
28
29
30
31
32
@gf.cell(tags=["dies"])
def die_frame_rf(
    size: Size = (10400.0, 5000.0),
    layer_floorplan: LayerSpec = "FLOORPLAN",
) -> gf.Component:
    return gf.c.rectangle(
        size=size, layer=layer_floorplan, centered=True, port_type=None
    )

die_frame_rf

die_frame_with_pads

die_frame_phix

die_frame_phix(
    die_frame: ComponentSpec = "die_frame",
    nfibers: int = 32,
    npads: int = 60,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: (
        ComponentSpec | None
    ) = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    edge_to_pad_distance_left: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    pad_port_name_rf: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    fiber_coupler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = True,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = "text_rectangular",
    pad_side_distance: float = 1160.0,
    xoffset_rf_pads: float = 50,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
    pad_rotation_rf: float = 0,
    with_loopback: bool = True,
) -> Component

A die_frame with grating couplers and pads.

Parameters:

Name Type Description Default
die_frame ComponentSpec

die_frame spec.

'die_frame'
nfibers int

the number of grating couplers.

32
npads int

the number of pads.

60
npads_rf int

the number of RF pads on the left side.

6
fiber_pitch float

the pitch of the grating couplers, in um.

127.0
pad_pitch float

the pitch of the pads, in um.

150.0
pad_pitch_gsg float

the pitch of the GSG pads, in um.

720.0
edge_coupler ComponentSpec | None

the grating coupler component.

'edge_coupler_silicon'
grating_coupler ComponentSpec | None

Optional grating coupler.

None
cross_section CrossSectionSpec

the cross section.

'strip'
pad ComponentSpec

the pad component.

'pad'
pad_gsg ComponentSpec

the GSG pad component.

'pad_gsg'
edge_to_pad_distance float

the distance from the edge to the pads, in um.

200.0
edge_to_pad_distance_left float | None

Optional distance from the left edge to the pads, in um. If None, uses edge_to_pad_distance for both sides.

None
pad_port_name_top str

name of the pad port name at the top facing south.

'e4'
pad_port_name_bot str

name of the pad port name at the bottom facing north.

'e2'
pad_port_name_rf str

name of the RF pad port name.

'e2'
layer_fiducial LayerSpec

layer for fiducials.

'M3'
layer_ruler LayerSpec

layer for ruler.

'WG'
ruler_bbox_layers tuple[LayerSpec, ...] | None

layers for bbox.

None
ruler_bbox_offset float

offset for bbox.

3.0
ruler_yoffset float

y-offset for ruler.

0
ruler_xoffset float

x-offset for ruler.

0
fiber_coupler_xoffset float

x-offset for fiber couplers.

0
with_right_fiber_coupler bool

if True, adds edge couplers on the right side.

True
with_left_fiber_coupler bool

if True, adds edge couplers on the left side.

True
text_offset Float2

offset for text.

(20, 10)
text ComponentSpec | None

text component spec.

'text_rectangular'
pad_side_distance float

distance from the die frame side to the first pad, in um.

1160.0
xoffset_rf_pads float

RF pads x-offset.

50
pad_rotation_dc_north float

rotation for DC pads.

0
pad_rotation_dc_south float

rotation for DC pads.

0
pad_rotation_rf float

rotation for RF pads.

0
with_loopback bool

if True, adds loopback structures.

True
Source code in gdsfactory/components/dies/die_frame_with_pads.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
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
def die_frame_phix(
    die_frame: ComponentSpec = "die_frame",
    nfibers: int = 32,
    npads: int = 60,
    npads_rf: int = 6,
    fiber_pitch: float = 127.0,
    pad_pitch: float = 150.0,
    pad_pitch_gsg: float = 720.0,
    edge_coupler: ComponentSpec | None = "edge_coupler_silicon",
    grating_coupler: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    pad_gsg: ComponentSpec = "pad_gsg",
    edge_to_pad_distance: float = 200.0,
    edge_to_pad_distance_left: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
    pad_port_name_rf: str = "e2",
    layer_fiducial: LayerSpec = "M3",
    layer_ruler: LayerSpec = "WG",
    ruler_bbox_layers: tuple[LayerSpec, ...] | None = None,
    ruler_bbox_offset: float = 3.0,
    ruler_yoffset: float = 0,
    ruler_xoffset: float = 0,
    fiber_coupler_xoffset: float = 0,
    with_right_fiber_coupler: bool = True,
    with_left_fiber_coupler: bool = True,
    text_offset: Float2 = (20, 10),
    text: ComponentSpec | None = "text_rectangular",
    pad_side_distance: float = 1160.0,
    xoffset_rf_pads: float = 50,
    pad_rotation_dc_north: float = 0,
    pad_rotation_dc_south: float = 0,
    pad_rotation_rf: float = 0,
    with_loopback: bool = True,
) -> Component:
    """A die_frame with grating couplers and pads.

    Args:
        die_frame: die_frame spec.
        nfibers: the number of grating couplers.
        npads: the number of pads.
        npads_rf: the number of RF pads on the left side.
        fiber_pitch: the pitch of the grating couplers, in um.
        pad_pitch: the pitch of the pads, in um.
        pad_pitch_gsg: the pitch of the GSG pads, in um.
        edge_coupler: the grating coupler component.
        grating_coupler: Optional grating coupler.
        cross_section: the cross section.
        pad: the pad component.
        pad_gsg: the GSG pad component.
        edge_to_pad_distance: the distance from the edge to the pads, in um.
        edge_to_pad_distance_left: Optional distance from the left edge to the pads, in um. If None, uses edge_to_pad_distance for both sides.
        pad_port_name_top: name of the pad port name at the top facing south.
        pad_port_name_bot: name of the pad port name at the bottom facing north.
        pad_port_name_rf: name of the RF pad port name.
        layer_fiducial: layer for fiducials.
        layer_ruler: layer for ruler.
        ruler_bbox_layers: layers for bbox.
        ruler_bbox_offset: offset for bbox.
        ruler_yoffset: y-offset for ruler.
        ruler_xoffset: x-offset for ruler.
        fiber_coupler_xoffset: x-offset for fiber couplers.
        with_right_fiber_coupler: if True, adds edge couplers on the right side.
        with_left_fiber_coupler: if True, adds edge couplers on the left side.
        text_offset: offset for text.
        text: text component spec.
        pad_side_distance: distance from the die frame side to the first pad, in um.
        xoffset_rf_pads: RF pads x-offset.
        pad_rotation_dc_north: rotation for DC pads.
        pad_rotation_dc_south: rotation for DC pads.
        pad_rotation_rf: rotation for RF pads.
        with_loopback: if True, adds loopback structures.
    """
    if npads > 60:
        raise ValueError("npads should be <= 60. Reach out to PHIX for support.")

    c = Component()

    d = gf.get_component(die_frame)
    fp = c << d
    fp.x = 0
    fp.y = 0
    xs, ys = fp.xsize, fp.ysize

    # Add optical ports
    x0 = xs / 2

    edge_to_pad_distance_left = edge_to_pad_distance_left or edge_to_pad_distance

    if edge_coupler or grating_coupler:
        if edge_coupler:
            if with_loopback:
                gca = gf.c.edge_coupler_array_with_loopback(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    cross_section=cross_section,
                    text_offset=text_offset,
                    text=text,
                    x_reflection=False,
                )
                gca_left = gf.c.edge_coupler_array_with_loopback(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    cross_section=cross_section,
                    text_offset=(-text_offset[0], text_offset[1]),
                    text=text,
                    x_reflection=True,
                )
            else:
                gca = gf.c.edge_coupler_array(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    text_offset=text_offset,
                    text=text,
                    x_reflection=False,
                )
                gca_left = gf.c.edge_coupler_array(
                    n=nfibers,
                    pitch=fiber_pitch,
                    edge_coupler=edge_coupler,
                    text_offset=(-text_offset[0], text_offset[1]),
                    text=text,
                    x_reflection=True,
                )

            if with_left_fiber_coupler:
                left = c << gca_left
                left.xmin = -xs / 2 - fiber_coupler_xoffset
                left.y = fp.y
                c.add_ports(left.ports, prefix="W")

            if with_right_fiber_coupler:
                right = c << gca
                right.xmax = xs / 2 + fiber_coupler_xoffset
                right.y = fp.y
                c.add_ports(right.ports, prefix="E")

        else:
            gca = gf.c.grating_coupler_array(
                n=nfibers,
                pitch=fiber_pitch,
                cross_section=cross_section,
                with_loopback=True,
            )
            gca_left = gf.c.grating_coupler_array(
                n=nfibers,
                pitch=fiber_pitch,
                cross_section=cross_section,
                with_loopback=True,
            )
            fiber_coupler_xoffset -= 750

            if with_left_fiber_coupler:
                left = c << gca_left
                left.rotate(-90)
                left.xmin = -xs / 2 - fiber_coupler_xoffset
                left.y = fp.y
                c.add_ports(left.ports, prefix="W")

            if with_right_fiber_coupler:
                right = c << gca
                right.rotate(+90)
                right.xmax = xs / 2 + fiber_coupler_xoffset
                right.y = fp.y
                c.add_ports(right.ports, prefix="E")
    ruler = gf.c.ruler(
        layer=layer_ruler,
        bbox_layers=ruler_bbox_layers,
        bbox_offset=ruler_bbox_offset,
    )

    if with_right_fiber_coupler:
        ruler_top_right = c << ruler
        ruler_top_right.xmax = fp.xmax - ruler_xoffset
        ruler_top_right.ymax = fp.ymax - 300 + ruler_yoffset

        ruler_bot_right = c << ruler
        ruler_bot_right.xmax = fp.xmax - ruler_xoffset
        ruler_bot_right.ymin = fp.ymin + 300 - ruler_yoffset

    if with_left_fiber_coupler:
        ruler_top_left = c << ruler
        ruler_top_left.rotate(180)
        ruler_top_left.xmin = fp.xmin + ruler_xoffset
        ruler_top_left.ymax = fp.ymax - 300 + ruler_yoffset

        ruler_bot_left = c << ruler
        ruler_bot_left.rotate(180)
        ruler_bot_left.xmin = fp.xmin + ruler_xoffset
        ruler_bot_left.ymin = fp.ymin + 300 - ruler_yoffset

    else:
        # left RF pads
        y0 = fp.ymax - 390 - pad_pitch_gsg / 2 + 50
        for i in range(npads_rf):
            pad_ref = c << gf.get_component(pad_gsg)
            pad_ref.rotate(pad_rotation_rf)
            pad_ref.y = y0 - i * pad_pitch_gsg
            pad_ref.xmin = fp.xmin + xoffset_rf_pads
            c.add_port(
                name=f"e{i}",
                port=pad_ref.ports[pad_port_name_rf],
            )

    # Add electrical ports
    pad = gf.get_component(pad)

    x0_pads = -xs / 2 + pad_side_distance
    x0 = x0_pads

    top_left = c << gf.c.cross(layer=layer_fiducial, length=150, width=20)
    top_left.xmax = x0 - 75
    top_left.y = +ys / 2 - edge_to_pad_distance - 50

    # north pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.rotate(pad_rotation_dc_north)
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymax = ys / 2 - edge_to_pad_distance
        c.add_port(
            name=f"N{i}",
            port=pad_ref.ports[pad_port_name_top],
        )
    top_right = c << gf.c.circle(layer=layer_fiducial, radius=75)
    top_right.xmin = pad_ref.xmax + 480
    top_right.y = +ys / 2 - edge_to_pad_distance - 50

    bot_left = c << gf.c.circle(layer=layer_fiducial, radius=75)
    bot_left.xmax = x0 - 75
    bot_left.y = -ys / 2 + edge_to_pad_distance + 50

    x0 = x0_pads

    # south pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.rotate(pad_rotation_dc_south)
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymin = -ys / 2 + edge_to_pad_distance
        c.add_port(
            name=f"S{i}",
            port=pad_ref.ports[pad_port_name_bot],
        )

    bot_right = c << gf.c.circle(layer=layer_fiducial, radius=75)
    bot_right.xmin = pad_ref.xmax + 480
    bot_right.ymin = -ys / 2 + edge_to_pad_distance

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

die_frame_with_pads

die_frame_with_pads(
    die_frame: ComponentSpec = "die_frame",
    ngratings: int = 14,
    npads: int = 31,
    grating_pitch: float = 250.0,
    pad_pitch: float = 300.0,
    grating_coupler: (
        ComponentSpec | None
    ) = "grating_coupler_te",
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    edge_to_pad_distance: float = 150.0,
    edge_to_grating_distance: float = 150.0,
    with_loopback: bool = True,
    loopback_radius: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
) -> Component

A die_frame with grating couplers and pads.

Parameters:

Name Type Description Default
die_frame ComponentSpec

die_frame spec.

'die_frame'
ngratings int

the number of grating couplers.

14
npads int

the number of pads.

31
grating_pitch float

the pitch of the grating couplers, in um.

250.0
pad_pitch float

the pitch of the pads, in um.

300.0
grating_coupler ComponentSpec | None

the grating coupler component.

'grating_coupler_te'
cross_section CrossSectionSpec

the cross section.

'strip'
pad ComponentSpec

the pad component.

'pad'
edge_to_pad_distance float

the distance from the edge to the pads, in um.

150.0
edge_to_grating_distance float

the distance from the edge to the grating couplers, in um.

150.0
with_loopback bool

if True, adds a loopback between edge GCs. Only works for rotation = 90 for now.

True
loopback_radius float | None

optional radius for loopback.

None
pad_port_name_top str

name of the pad port name at the btop facing south.

'e4'
pad_port_name_bot str

name of the pad port name at the bottom facing north.

'e2'
Source code in gdsfactory/components/dies/die_frame_with_pads.py
 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
@gf.cell_with_module_name(tags=["dies"])
def die_frame_with_pads(
    die_frame: ComponentSpec = "die_frame",
    ngratings: int = 14,
    npads: int = 31,
    grating_pitch: float = 250.0,
    pad_pitch: float = 300.0,
    grating_coupler: ComponentSpec | None = "grating_coupler_te",
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    edge_to_pad_distance: float = 150.0,
    edge_to_grating_distance: float = 150.0,
    with_loopback: bool = True,
    loopback_radius: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
) -> Component:
    """A die_frame with grating couplers and pads.

    Args:
        die_frame: die_frame spec.
        ngratings: the number of grating couplers.
        npads: the number of pads.
        grating_pitch: the pitch of the grating couplers, in um.
        pad_pitch: the pitch of the pads, in um.
        grating_coupler: the grating coupler component.
        cross_section: the cross section.
        pad: the pad component.
        edge_to_pad_distance: the distance from the edge to the pads, in um.
        edge_to_grating_distance: the distance from the edge to the grating couplers, in um.
        with_loopback: if True, adds a loopback between edge GCs. Only works for rotation = 90 for now.
        loopback_radius: optional radius for loopback.
        pad_port_name_top: name of the pad port name at the btop facing south.
        pad_port_name_bot: name of the pad port name at the bottom facing north.
    """
    c = Component()

    d = gf.get_component(die_frame)
    fp = c << d
    fp.x = 0
    fp.y = 0
    xs, ys = fp.xsize, fp.ysize

    # Add optical ports
    x0 = xs / 2 + edge_to_grating_distance

    if grating_coupler:
        gca = gf.c.grating_coupler_array(
            n=ngratings,
            pitch=grating_pitch,
            with_loopback=with_loopback,
            grating_coupler=grating_coupler,
            cross_section=cross_section,
            radius=loopback_radius,
        )
        left = c << gca
        left.rotate(-90)
        left.xmin = -xs / 2 + edge_to_grating_distance
        left.y = fp.y
        c.add_ports(left.ports, prefix="W")

        right = c << gca
        right.rotate(+90)
        right.xmax = xs / 2 - edge_to_grating_distance
        right.y = fp.y
        c.add_ports(right.ports, prefix="E")

    # Add electrical ports
    pad = gf.get_component(pad)
    x0 = -npads * pad_pitch / 2 + edge_to_pad_distance

    # north pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymax = ys / 2 - edge_to_pad_distance
        c.add_port(
            name=f"N{i}",
            port=pad_ref.ports[pad_port_name_top],
        )

    x0 = -npads * pad_pitch / 2 + edge_to_pad_distance

    # south pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymin = -ys / 2 + edge_to_pad_distance
        c.add_port(
            name=f"S{i}",
            port=pad_ref.ports[pad_port_name_bot],
        )

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

die_frame_with_pads

die_with_pads

die_with_pads

die_with_pads(
    size: Size = (11470.0, 4900.0),
    ngratings: int = 14,
    npads: int = 31,
    grating_pitch: float = 250.0,
    pad_pitch: float = 300.0,
    grating_coupler: (
        ComponentSpec | None
    ) = "grating_coupler_te",
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    layer_floorplan: LayerSpec = "FLOORPLAN",
    edge_to_pad_distance: float = 150.0,
    edge_to_grating_distance: float = 150.0,
    with_loopback: bool = True,
    loopback_radius: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
) -> Component

A die with grating couplers and pads.

Parameters:

Name Type Description Default
size Size

the size of the die, in um.

(11470.0, 4900.0)
ngratings int

the number of grating couplers.

14
npads int

the number of pads.

31
grating_pitch float

the pitch of the grating couplers, in um.

250.0
pad_pitch float

the pitch of the pads, in um.

300.0
grating_coupler ComponentSpec | None

the grating coupler component.

'grating_coupler_te'
cross_section CrossSectionSpec

the cross section.

'strip'
pad ComponentSpec

the pad component.

'pad'
layer_floorplan LayerSpec

the layer of the floorplan.

'FLOORPLAN'
edge_to_pad_distance float

the distance from the edge to the pads, in um.

150.0
edge_to_grating_distance float

the distance from the edge to the grating couplers, in um.

150.0
with_loopback bool

if True, adds a loopback between edge GCs. Only works for rotation = 90 for now.

True
loopback_radius float | None

optional radius for loopback.

None
pad_port_name_top str

name of the pad port name at the btop facing south.

'e4'
pad_port_name_bot str

name of the pad port name at the bottom facing north.

'e2'
Source code in gdsfactory/components/dies/die_with_pads.py
 10
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@gf.cell_with_module_name(tags=["dies"])
def die_with_pads(
    size: Size = (11470.0, 4900.0),
    ngratings: int = 14,
    npads: int = 31,
    grating_pitch: float = 250.0,
    pad_pitch: float = 300.0,
    grating_coupler: ComponentSpec | None = "grating_coupler_te",
    cross_section: CrossSectionSpec = "strip",
    pad: ComponentSpec = "pad",
    layer_floorplan: LayerSpec = "FLOORPLAN",
    edge_to_pad_distance: float = 150.0,
    edge_to_grating_distance: float = 150.0,
    with_loopback: bool = True,
    loopback_radius: float | None = None,
    pad_port_name_top: str = "e4",
    pad_port_name_bot: str = "e2",
) -> Component:
    """A die with grating couplers and pads.

    Args:
        size: the size of the die, in um.
        ngratings: the number of grating couplers.
        npads: the number of pads.
        grating_pitch: the pitch of the grating couplers, in um.
        pad_pitch: the pitch of the pads, in um.
        grating_coupler: the grating coupler component.
        cross_section: the cross section.
        pad: the pad component.
        layer_floorplan: the layer of the floorplan.
        edge_to_pad_distance: the distance from the edge to the pads, in um.
        edge_to_grating_distance: the distance from the edge to the grating couplers, in um.
        with_loopback: if True, adds a loopback between edge GCs. Only works for rotation = 90 for now.
        loopback_radius: optional radius for loopback.
        pad_port_name_top: name of the pad port name at the btop facing south.
        pad_port_name_bot: name of the pad port name at the bottom facing north.
    """
    warnings.warn(
        "die_with_pads is deprecated and will be removed soon. Please use die_frame_with_pads instead",
        stacklevel=2,
    )

    c = Component()
    fp = c << gf.c.rectangle(
        size=size, layer=layer_floorplan, centered=True, port_type=None
    )
    xs, ys = size

    # Add optical ports
    x0 = xs / 2 + edge_to_grating_distance

    if grating_coupler:
        gca = gf.c.grating_coupler_array(
            n=ngratings,
            pitch=grating_pitch,
            with_loopback=with_loopback,
            grating_coupler=grating_coupler,
            cross_section=cross_section,
            radius=loopback_radius,
        )
        left = c << gca
        left.rotate(-90)
        left.xmin = -xs / 2 + edge_to_grating_distance
        left.y = fp.y
        c.add_ports(left.ports, prefix="W")

        right = c << gca
        right.rotate(+90)
        right.xmax = xs / 2 - edge_to_grating_distance
        right.y = fp.y
        c.add_ports(right.ports, prefix="E")

    # Add electrical ports
    pad = gf.get_component(pad)
    x0 = -npads * pad_pitch / 2 + edge_to_pad_distance

    # north pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymax = ys / 2 - edge_to_pad_distance
        c.add_port(
            name=f"N{i}",
            port=pad_ref.ports[pad_port_name_top],
        )

    x0 = -npads * pad_pitch / 2 + edge_to_pad_distance

    # south pads
    for i in range(npads):
        pad_ref = c << pad
        pad_ref.xmin = x0 + i * pad_pitch
        pad_ref.ymin = -ys / 2 + edge_to_pad_distance
        c.add_port(
            name=f"S{i}",
            port=pad_ref.ports[pad_port_name_bot],
        )

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

die_with_pads

seal_ring

seal_ring

seal_ring(
    size: Float2 = (500, 500),
    seal: ComponentSpec = "via_stack",
    width: float = 10,
    padding: float = 10.0,
    with_north: bool = True,
    with_south: bool = True,
    with_east: bool = True,
    with_west: bool = True,
) -> gf.Component

Returns a continuous seal ring boundary at the chip/die.

Prevents cracks from spreading and shields when connected to ground.

Parameters:

Name Type Description Default
size Float2

of the seal.

(500, 500)
seal ComponentSpec

function for the seal.

'via_stack'
width float

of the seal.

10
padding float

from component to seal.

10.0
with_north bool

includes seal.

True
with_south bool

includes seal.

True
with_east bool

includes seal.

True
with_west bool

includes seal.

True
Source code in gdsfactory/components/dies/seal_ring.py
10
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
@gf.cell_with_module_name(tags=["dies"])
def seal_ring(
    size: Float2 = (500, 500),
    seal: ComponentSpec = "via_stack",
    width: float = 10,
    padding: float = 10.0,
    with_north: bool = True,
    with_south: bool = True,
    with_east: bool = True,
    with_west: bool = True,
) -> gf.Component:
    """Returns a continuous seal ring boundary at the chip/die.

    Prevents cracks from spreading and shields when connected to ground.

    Args:
        size: of the seal.
        seal: function for the seal.
        width: of the seal.
        padding: from component to seal.
        with_north: includes seal.
        with_south: includes seal.
        with_east: includes seal.
        with_west: includes seal.
    """
    c = gf.Component()

    xmin, ymin = 0, 0
    xmax = size[0]
    ymax = size[1]
    x = (xmax + xmin) / 2
    sx = xmax - xmin
    sy = ymax - ymin

    sx = snap_to_grid(sx, grid_factor=2)
    sy = snap_to_grid(sy, grid_factor=2)

    ymin_north = snap_to_grid(ymax + padding, grid_factor=2)
    ymax_south = snap_to_grid(ymax - sy - padding, grid_factor=2)

    # north south
    size_north_south = (sx + 2 * padding + 2 * width, width)
    size_east_west = (width, sy + 2 * padding)

    if with_north:
        north = c << gf.get_component(
            seal, size=size_north_south, port_orientations=None
        )
        north.ymin = ymin_north
        north.x = x

    if with_east:
        east = c << gf.get_component(seal, size=size_east_west, port_orientations=None)
        east.xmin = xmax + padding
        east.ymax = ymin_north

    if with_west:
        west = c << gf.get_component(seal, size=size_east_west, port_orientations=None)
        west.xmax = xmin - padding
        west.ymax = ymin_north

    if with_south:
        south = c << gf.get_component(
            seal, size=size_north_south, port_orientations=None
        )
        south.ymax = ymax_south
        south.x = x

    return c

seal_ring_segmented

seal_ring_segmented(
    size: Float2 = (500, 500),
    length_segment: float = 10,
    width_segment: float = 3,
    spacing_segment: float = 2,
    corner: ComponentSpec = "via_stack_corner45_extended",
    via_stack: ComponentSpec = "via_stack_m1_mtop",
    with_north: bool = True,
    with_south: bool = True,
    with_east: bool = True,
    with_west: bool = True,
) -> gf.Component

Segmented Seal ring.

Parameters:

Name Type Description Default
size Float2

of the seal ring.

(500, 500)
length_segment float

length of each segment.

10
width_segment float

width of each segment.

3
spacing_segment float

spacing between segments.

2
corner ComponentSpec

corner component.

'via_stack_corner45_extended'
via_stack ComponentSpec

via_stack component.

'via_stack_m1_mtop'
with_north bool

includes seal.

True
with_south bool

includes seal.

True
with_east bool

includes seal.

True
with_west bool

includes seal.

True
Source code in gdsfactory/components/dies/seal_ring.py
 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
@gf.cell_with_module_name(tags=["dies"])
def seal_ring_segmented(
    size: Float2 = (500, 500),
    length_segment: float = 10,
    width_segment: float = 3,
    spacing_segment: float = 2,
    corner: ComponentSpec = "via_stack_corner45_extended",
    via_stack: ComponentSpec = "via_stack_m1_mtop",
    with_north: bool = True,
    with_south: bool = True,
    with_east: bool = True,
    with_west: bool = True,
) -> gf.Component:
    """Segmented Seal ring.

    Args:
        size: of the seal ring.
        length_segment: length of each segment.
        width_segment: width of each segment.
        spacing_segment: spacing between segments.
        corner: corner component.
        via_stack: via_stack component.
        with_north: includes seal.
        with_south: includes seal.
        with_east: includes seal.
        with_west: includes seal.
    """
    c = gf.Component()
    corner_component = gf.get_component(corner, width=width_segment)

    xmin, ymin = 0, 0
    xmax = size[0]
    ymax = size[1]

    tl = c << corner_component
    tr = c << corner_component

    tl.xmin = xmin
    tl.ymax = ymax

    tr.dmirror()
    tr.xmax = xmax
    tr.ymax = ymax

    bl = c << corner_component
    br = c << corner_component
    br.dmirror()
    br.dmirror_y()
    bl.dmirror_y()

    bl.xmin = xmin
    bl.ymin = ymin
    br.xmax = xmax
    br.ymin = ymin

    pitch = length_segment + spacing_segment

    # horizontal
    dx = abs(tl.xmax - tr.xmin)
    segment_horizontal = gf.get_component(
        via_stack, size=(length_segment, width_segment), port_orientations=None
    )
    horizontal = gf.c.array(
        component=segment_horizontal, columns=int(dx / pitch), column_pitch=pitch
    )

    if with_north:
        top = c << horizontal
        top.ymax = tl.ymax
        top.xmin = tl.xmax + spacing_segment

        # horizontal inner
        topi = c << horizontal
        topi.ymax = top.ymin - spacing_segment
        topi.xmin = top.xmin + pitch / 2

    if with_south:
        bot = c << horizontal
        bot.ymin = ymin
        bot.xmin = tl.xmax + spacing_segment

        boti = c << horizontal
        boti.ymin = bot.ymax + spacing_segment
        boti.xmin = bot.xmin + spacing_segment

    # vertical
    segment_vertical = gf.get_component(
        via_stack, size=(width_segment, length_segment), port_orientations=None
    )
    dy = abs(tl.ymin - bl.ymax)

    vertical = gf.c.array(
        component=segment_vertical, rows=int(dy / pitch), columns=1, row_pitch=pitch
    )

    if with_east:
        right = c << vertical
        right.xmax = xmax
        right.ymin = bl.ymax
        righti = c << vertical
        righti.xmax = right.xmin - spacing_segment
        righti.ymin = right.ymin + pitch / 2

    if with_west:
        left = c << vertical
        left.xmin = xmin
        left.ymin = bl.ymax

        # vertical inner
        lefti = c << vertical
        lefti.xmin = left.xmax + spacing_segment
        lefti.ymin = left.ymin + pitch / 2

    return c

seal_ring

seal_ring_segmented

seal_ring_segmented(
    size: Float2 = (500, 500),
    length_segment: float = 10,
    width_segment: float = 3,
    spacing_segment: float = 2,
    corner: ComponentSpec = "via_stack_corner45_extended",
    via_stack: ComponentSpec = "via_stack_m1_mtop",
    with_north: bool = True,
    with_south: bool = True,
    with_east: bool = True,
    with_west: bool = True,
) -> gf.Component

Segmented Seal ring.

Parameters:

Name Type Description Default
size Float2

of the seal ring.

(500, 500)
length_segment float

length of each segment.

10
width_segment float

width of each segment.

3
spacing_segment float

spacing between segments.

2
corner ComponentSpec

corner component.

'via_stack_corner45_extended'
via_stack ComponentSpec

via_stack component.

'via_stack_m1_mtop'
with_north bool

includes seal.

True
with_south bool

includes seal.

True
with_east bool

includes seal.

True
with_west bool

includes seal.

True
Source code in gdsfactory/components/dies/seal_ring.py
 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
@gf.cell_with_module_name(tags=["dies"])
def seal_ring_segmented(
    size: Float2 = (500, 500),
    length_segment: float = 10,
    width_segment: float = 3,
    spacing_segment: float = 2,
    corner: ComponentSpec = "via_stack_corner45_extended",
    via_stack: ComponentSpec = "via_stack_m1_mtop",
    with_north: bool = True,
    with_south: bool = True,
    with_east: bool = True,
    with_west: bool = True,
) -> gf.Component:
    """Segmented Seal ring.

    Args:
        size: of the seal ring.
        length_segment: length of each segment.
        width_segment: width of each segment.
        spacing_segment: spacing between segments.
        corner: corner component.
        via_stack: via_stack component.
        with_north: includes seal.
        with_south: includes seal.
        with_east: includes seal.
        with_west: includes seal.
    """
    c = gf.Component()
    corner_component = gf.get_component(corner, width=width_segment)

    xmin, ymin = 0, 0
    xmax = size[0]
    ymax = size[1]

    tl = c << corner_component
    tr = c << corner_component

    tl.xmin = xmin
    tl.ymax = ymax

    tr.dmirror()
    tr.xmax = xmax
    tr.ymax = ymax

    bl = c << corner_component
    br = c << corner_component
    br.dmirror()
    br.dmirror_y()
    bl.dmirror_y()

    bl.xmin = xmin
    bl.ymin = ymin
    br.xmax = xmax
    br.ymin = ymin

    pitch = length_segment + spacing_segment

    # horizontal
    dx = abs(tl.xmax - tr.xmin)
    segment_horizontal = gf.get_component(
        via_stack, size=(length_segment, width_segment), port_orientations=None
    )
    horizontal = gf.c.array(
        component=segment_horizontal, columns=int(dx / pitch), column_pitch=pitch
    )

    if with_north:
        top = c << horizontal
        top.ymax = tl.ymax
        top.xmin = tl.xmax + spacing_segment

        # horizontal inner
        topi = c << horizontal
        topi.ymax = top.ymin - spacing_segment
        topi.xmin = top.xmin + pitch / 2

    if with_south:
        bot = c << horizontal
        bot.ymin = ymin
        bot.xmin = tl.xmax + spacing_segment

        boti = c << horizontal
        boti.ymin = bot.ymax + spacing_segment
        boti.xmin = bot.xmin + spacing_segment

    # vertical
    segment_vertical = gf.get_component(
        via_stack, size=(width_segment, length_segment), port_orientations=None
    )
    dy = abs(tl.ymin - bl.ymax)

    vertical = gf.c.array(
        component=segment_vertical, rows=int(dy / pitch), columns=1, row_pitch=pitch
    )

    if with_east:
        right = c << vertical
        right.xmax = xmax
        right.ymin = bl.ymax
        righti = c << vertical
        righti.xmax = right.xmin - spacing_segment
        righti.ymin = right.ymin + pitch / 2

    if with_west:
        left = c << vertical
        left.xmin = xmin
        left.ymin = bl.ymax

        # vertical inner
        lefti = c << vertical
        lefti.xmin = left.xmax + spacing_segment
        lefti.ymin = left.ymin + pitch / 2

    return c

seal_ring_segmented

wafer

wafer

wafer(
    reticle: ComponentSpec = "die",
    cols: tuple[int, ...] = _cols_200mm_wafer,
    xspacing: float | None = None,
    yspacing: float | None = None,
    die_name_col_row: bool = False,
) -> Component

Returns complete wafer. Useful for mask aligner steps.

Parameters:

Name Type Description Default
reticle ComponentSpec

spec for each wafer reticle.

'die'
cols tuple[int, ...]

how many columns per row.

_cols_200mm_wafer
xspacing float | None

optional spacing, defaults to reticle.xsize.

None
yspacing float | None

optional spacing, defaults to reticle.ysize.

None
die_name_col_row bool

if True, die name is row_col, otherwise is a number

False
Source code in gdsfactory/components/dies/wafer.py
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
@gf.cell_with_module_name(tags=["dies"])
def wafer(
    reticle: ComponentSpec = "die",
    cols: tuple[int, ...] = _cols_200mm_wafer,
    xspacing: float | None = None,
    yspacing: float | None = None,
    die_name_col_row: bool = False,
) -> Component:
    """Returns complete wafer. Useful for mask aligner steps.

    Args:
        reticle: spec for each wafer reticle.
        cols: how many columns per row.
        xspacing: optional spacing, defaults to reticle.xsize.
        yspacing: optional spacing, defaults to reticle.ysize.
        die_name_col_row: if True, die name is row_col, otherwise is a number
    """
    c = gf.Component()
    die = gf.get_component(reticle)
    xspacing = xspacing or die.xsize
    yspacing = yspacing or die.ysize

    i = 1
    for col in range(len(cols)):
        for row in range(cols[col]):
            die_name = f"{col + 1}_{row + 1}" if die_name_col_row else str(i)
            die = gf.get_component(reticle, die_name=die_name)
            ref = c.add_ref(die)
            ref.movex((row - cols[col] / 2) * xspacing)
            ref.movey(col * yspacing)
            i += 1

    return c

wafer

edge_couplers

edge_coupler_array

edge_coupler_array

edge_coupler_array(
    edge_coupler: ComponentSpec = "edge_coupler_silicon",
    n: int = 5,
    pitch: float = 127.0,
    x_reflection: bool = False,
    text: ComponentSpec | None = "text_rectangular",
    text_offset: Float2 = (10, 20),
    text_rotation: float = 0,
) -> Component

Fiber array edge coupler based on an inverse taper.

Each edge coupler adds a ruler for polishing.

Parameters:

Name Type Description Default
edge_coupler ComponentSpec

edge coupler spec.

'edge_coupler_silicon'
n int

number of channels.

5
pitch float

Fiber pitch.

127.0
x_reflection bool

horizontal mirror.

False
text ComponentSpec | None

text spec.

'text_rectangular'
text_offset Float2

from edge coupler.

(10, 20)
text_rotation float

text rotation in degrees.

0
Source code in gdsfactory/components/edge_couplers/edge_coupler_array.py
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
@gf.cell_with_module_name(tags=["edge_couplers"])
def edge_coupler_array(
    edge_coupler: ComponentSpec = "edge_coupler_silicon",
    n: int = 5,
    pitch: float = 127.0,
    x_reflection: bool = False,
    text: ComponentSpec | None = "text_rectangular",
    text_offset: Float2 = (10, 20),
    text_rotation: float = 0,
) -> Component:
    """Fiber array edge coupler based on an inverse taper.

    Each edge coupler adds a ruler for polishing.

    Args:
        edge_coupler: edge coupler spec.
        n: number of channels.
        pitch: Fiber pitch.
        x_reflection: horizontal mirror.
        text: text spec.
        text_offset: from edge coupler.
        text_rotation: text rotation in degrees.
    """
    edge_coupler = gf.get_component(edge_coupler)

    c = Component()
    for i in range(n):
        ref = c.add_ref(edge_coupler)
        ref.name = f"ec_{i}"
        ref.y = i * pitch

        if x_reflection:
            ref.mirror()

        for port in ref.ports:
            if port.port_type == "optical":
                c.add_port(name=f"o{i}", port=port)

        if text:
            t = c << gf.get_component(text, text=str(i + 1))
            t.rotate(text_rotation)
            t.movex(text_offset[0])
            t.movey(i * pitch + text_offset[1])

    c.auto_rename_ports()
    return c

edge_coupler_array_with_loopback

edge_coupler_array_with_loopback(
    edge_coupler: ComponentSpec = "edge_coupler_silicon",
    cross_section: CrossSectionSpec = "strip",
    radius: float | None = None,
    n: int = 8,
    pitch: float = 127.0,
    x_reflection: bool = False,
    text: ComponentSpec | None = "text_rectangular",
    text_offset: Float2 = (0, 10),
    text_rotation: float = 0,
) -> Component

Fiber array edge coupler.

Parameters:

Name Type Description Default
edge_coupler ComponentSpec

edge coupler.

'edge_coupler_silicon'
cross_section CrossSectionSpec

spec.

'strip'
radius float | None

bend radius loopback (um).

None
n int

number of channels.

8
pitch float

Fiber pitch (um).

127.0
x_reflection bool

horizontal mirror.

False
text ComponentSpec | None

Optional text spec.

'text_rectangular'
text_offset Float2

x, y.

(0, 10)
text_rotation float

text rotation in degrees.

0
Source code in gdsfactory/components/edge_couplers/edge_coupler_array.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
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
@gf.cell_with_module_name(tags=["edge_couplers"])
def edge_coupler_array_with_loopback(
    edge_coupler: ComponentSpec = "edge_coupler_silicon",
    cross_section: CrossSectionSpec = "strip",
    radius: float | None = None,
    n: int = 8,
    pitch: float = 127.0,
    x_reflection: bool = False,
    text: ComponentSpec | None = "text_rectangular",
    text_offset: Float2 = (0, 10),
    text_rotation: float = 0,
) -> Component:
    """Fiber array edge coupler.

    Args:
        edge_coupler: edge coupler.
        cross_section: spec.
        radius: bend radius loopback (um).
        n: number of channels.
        pitch: Fiber pitch (um).
        x_reflection: horizontal mirror.
        text: Optional text spec.
        text_offset: x, y.
        text_rotation: text rotation in degrees.
    """
    xs = gf.get_cross_section(cross_section)
    radius = radius or xs.radius

    c = Component()
    ec = edge_coupler_array(
        edge_coupler=edge_coupler,
        n=n,
        pitch=pitch,
        x_reflection=x_reflection,
        text=text,
        text_offset=text_offset,
        text_rotation=text_rotation,
    )
    ec_ref = c << ec
    if x_reflection:
        ec_ref_ports = ec_ref.ports.filter(orientation=0)
    else:
        ec_ref_ports = ec_ref.ports.filter(orientation=180)

    p1 = ec_ref_ports[0]
    p2 = ec_ref_ports[1]
    p3 = ec_ref_ports[-2]
    p4 = ec_ref_ports[-1]

    gf.routing.route_single(
        c,
        p1,
        p2,
        cross_section=cross_section,
        radius=radius,
    )
    gf.routing.route_single(
        c,
        p4,
        p3,
        cross_section=cross_section,
        radius=radius,
    )

    for i, port in enumerate(ec_ref_ports):
        if port not in [p1, p2, p3, p4]:
            c.add_port(str(i), port=port)

    c.auto_rename_ports()
    return c

edge_coupler_silicon

edge_coupler_silicon(
    length: float = 100,
    width1: float = 0.5,
    width2: float = 0.2,
    with_two_ports: bool = True,
    port_names: tuple[str, str] = ("o1", "o2"),
    port_types: tuple[str, str] = (
        "optical",
        "edge_coupler",
    ),
    cross_section: CrossSectionSpec = "strip",
) -> Component

Edge coupler for silicon photonics.

Parameters:

Name Type Description Default
length float

length of the taper.

100
width1 float

width1 of the taper.

0.5
width2 float

width2 of the taper.

0.2
with_two_ports bool

add two ports.

True
port_names tuple[str, str]

tuple with port names.

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

tuple with port types.

('optical', 'edge_coupler')
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/edge_couplers/edge_coupler_array.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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["edge_couplers"])
def edge_coupler_silicon(
    length: float = 100,
    width1: float = 0.5,
    width2: float = 0.2,
    with_two_ports: bool = True,
    port_names: tuple[str, str] = ("o1", "o2"),
    port_types: tuple[str, str] = ("optical", "edge_coupler"),
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Edge coupler for silicon photonics.

    Args:
        length: length of the taper.
        width1: width1 of the taper.
        width2: width2 of the taper.
        with_two_ports: add two ports.
        port_names: tuple with port names.
        port_types: tuple with port types.
        cross_section: cross_section spec.

    """
    return gf.c.taper(
        width1=width1,
        width2=width2,
        length=length,
        with_two_ports=with_two_ports,
        port_names=port_names,
        port_types=port_types,
        cross_section=cross_section,
    )

edge_coupler_array

edge_coupler_array_with_loopback

edge_coupler_array_with_loopback(
    edge_coupler: ComponentSpec = "edge_coupler_silicon",
    cross_section: CrossSectionSpec = "strip",
    radius: float | None = None,
    n: int = 8,
    pitch: float = 127.0,
    x_reflection: bool = False,
    text: ComponentSpec | None = "text_rectangular",
    text_offset: Float2 = (0, 10),
    text_rotation: float = 0,
) -> Component

Fiber array edge coupler.

Parameters:

Name Type Description Default
edge_coupler ComponentSpec

edge coupler.

'edge_coupler_silicon'
cross_section CrossSectionSpec

spec.

'strip'
radius float | None

bend radius loopback (um).

None
n int

number of channels.

8
pitch float

Fiber pitch (um).

127.0
x_reflection bool

horizontal mirror.

False
text ComponentSpec | None

Optional text spec.

'text_rectangular'
text_offset Float2

x, y.

(0, 10)
text_rotation float

text rotation in degrees.

0
Source code in gdsfactory/components/edge_couplers/edge_coupler_array.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
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
@gf.cell_with_module_name(tags=["edge_couplers"])
def edge_coupler_array_with_loopback(
    edge_coupler: ComponentSpec = "edge_coupler_silicon",
    cross_section: CrossSectionSpec = "strip",
    radius: float | None = None,
    n: int = 8,
    pitch: float = 127.0,
    x_reflection: bool = False,
    text: ComponentSpec | None = "text_rectangular",
    text_offset: Float2 = (0, 10),
    text_rotation: float = 0,
) -> Component:
    """Fiber array edge coupler.

    Args:
        edge_coupler: edge coupler.
        cross_section: spec.
        radius: bend radius loopback (um).
        n: number of channels.
        pitch: Fiber pitch (um).
        x_reflection: horizontal mirror.
        text: Optional text spec.
        text_offset: x, y.
        text_rotation: text rotation in degrees.
    """
    xs = gf.get_cross_section(cross_section)
    radius = radius or xs.radius

    c = Component()
    ec = edge_coupler_array(
        edge_coupler=edge_coupler,
        n=n,
        pitch=pitch,
        x_reflection=x_reflection,
        text=text,
        text_offset=text_offset,
        text_rotation=text_rotation,
    )
    ec_ref = c << ec
    if x_reflection:
        ec_ref_ports = ec_ref.ports.filter(orientation=0)
    else:
        ec_ref_ports = ec_ref.ports.filter(orientation=180)

    p1 = ec_ref_ports[0]
    p2 = ec_ref_ports[1]
    p3 = ec_ref_ports[-2]
    p4 = ec_ref_ports[-1]

    gf.routing.route_single(
        c,
        p1,
        p2,
        cross_section=cross_section,
        radius=radius,
    )
    gf.routing.route_single(
        c,
        p4,
        p3,
        cross_section=cross_section,
        radius=radius,
    )

    for i, port in enumerate(ec_ref_ports):
        if port not in [p1, p2, p3, p4]:
            c.add_port(str(i), port=port)

    c.auto_rename_ports()
    return c

edge_coupler_array_with_loopback

edge_coupler_silicon

edge_coupler_silicon(
    length: float = 100,
    width1: float = 0.5,
    width2: float = 0.2,
    with_two_ports: bool = True,
    port_names: tuple[str, str] = ("o1", "o2"),
    port_types: tuple[str, str] = (
        "optical",
        "edge_coupler",
    ),
    cross_section: CrossSectionSpec = "strip",
) -> Component

Edge coupler for silicon photonics.

Parameters:

Name Type Description Default
length float

length of the taper.

100
width1 float

width1 of the taper.

0.5
width2 float

width2 of the taper.

0.2
with_two_ports bool

add two ports.

True
port_names tuple[str, str]

tuple with port names.

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

tuple with port types.

('optical', 'edge_coupler')
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/edge_couplers/edge_coupler_array.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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["edge_couplers"])
def edge_coupler_silicon(
    length: float = 100,
    width1: float = 0.5,
    width2: float = 0.2,
    with_two_ports: bool = True,
    port_names: tuple[str, str] = ("o1", "o2"),
    port_types: tuple[str, str] = ("optical", "edge_coupler"),
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Edge coupler for silicon photonics.

    Args:
        length: length of the taper.
        width1: width1 of the taper.
        width2: width2 of the taper.
        with_two_ports: add two ports.
        port_names: tuple with port names.
        port_types: tuple with port types.
        cross_section: cross_section spec.

    """
    return gf.c.taper(
        width1=width1,
        width2=width2,
        length=length,
        with_two_ports=with_two_ports,
        port_names=port_names,
        port_types=port_types,
        cross_section=cross_section,
    )

edge_coupler_silicon

filters

awg

Sample AWG.

awg

awg(
    arms: int = 10,
    outputs: int = 3,
    free_propagation_region_input_function: ComponentSpec = free_propagation_region_input,
    free_propagation_region_output_function: ComponentSpec = free_propagation_region_output,
    fpr_spacing: float = 50.0,
    arm_spacing: float = 1.0,
    length_increment: float = 0.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns an Arrayed Waveguide grating.

To simulate you can use https://github.com/dnrobin/awg-python

Parameters:

Name Type Description Default
arms int

number of arms.

10
outputs int

number of outputs.

3
free_propagation_region_input_function ComponentSpec

for input.

free_propagation_region_input
free_propagation_region_output_function ComponentSpec

for output.

free_propagation_region_output
fpr_spacing float

x separation between input/output free propagation region.

50.0
arm_spacing float

y separation between arms (used when length_increment == 0).

1.0
length_increment float

constant length step dL (um); when > 0 the arms form a nested fan where arm i is exactly i*dL longer than arm 0 -- the property that makes an AWG disperse light by wavelength.

0.0
cross_section CrossSectionSpec

cross_section function.

'strip'
Source code in gdsfactory/components/filters/awg.py
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
@gf.cell_with_module_name(tags=["filters"])
def awg(
    arms: int = 10,
    outputs: int = 3,
    free_propagation_region_input_function: ComponentSpec = free_propagation_region_input,
    free_propagation_region_output_function: ComponentSpec = free_propagation_region_output,
    fpr_spacing: float = 50.0,
    arm_spacing: float = 1.0,
    length_increment: float = 0.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns an Arrayed Waveguide grating.

    To simulate you can use
    https://github.com/dnrobin/awg-python

    Args:
        arms: number of arms.
        outputs: number of outputs.
        free_propagation_region_input_function: for input.
        free_propagation_region_output_function: for output.
        fpr_spacing: x separation between input/output free propagation region.
        arm_spacing: y separation between arms (used when length_increment == 0).
        length_increment: constant length step dL (um); when > 0 the arms form a
            nested fan where arm i is exactly i*dL longer than arm 0 -- the property
            that makes an AWG disperse light by wavelength.
        cross_section: cross_section function.
    """
    c = Component()
    fpr_in = gf.get_component(
        free_propagation_region_input_function,
        inputs=1,
        outputs=arms,
        cross_section=cross_section,
    )
    fpr_out = gf.get_component(
        free_propagation_region_output_function,
        inputs=outputs,
        outputs=arms,
        cross_section=cross_section,
    )

    fpr_in_ref = c.add_ref(fpr_in)
    fpr_out_ref = c.add_ref(fpr_out)

    if length_increment <= 0:
        fpr_in_ref.rotate(90)
        fpr_out_ref.rotate(90)
        fpr_out_ref.x += fpr_spacing
        _ = gf.routing.route_bundle(
            c,
            gf.port.get_ports_list(fpr_out_ref, prefix="E"),
            gf.port.get_ports_list(fpr_in_ref, prefix="E"),
            sort_ports=True,
            separation=arm_spacing,
            cross_section=cross_section,
        )
    else:
        fpr_out_ref.mirror_x()
        e0 = fpr_in_ref.ports["E0"]
        fpr_out_ref.movex(e0.x + fpr_spacing - fpr_out_ref.ports["E0"].x)
        fpr_out_ref.movey(e0.y - fpr_out_ref.ports["E0"].y)
        gap = fpr_out_ref.ports["E0"].x - e0.x
        xs = gf.get_cross_section(cross_section)
        bend_radius = xs.radius or 10.0
        margin = max(4.0, bend_radius)
        max_rise = (gap - 2 * bend_radius) / 2
        stagger = max(0.0, min(4.0, (max_rise - margin) / max(arms - 2, 1)))
        lengths: list[float] = []
        for i in range(arms):
            p_in = fpr_in_ref.ports[f"E{i}"]
            p_out = fpr_out_ref.ports[f"E{i}"]
            rise_x = margin + (arms - 1 - i) * stagger
            h = i * length_increment / 2.0
            if h > 0:
                steps: list[Step] = [
                    {"dx": rise_x},
                    {"dy": h},
                    {"dx": gap - 2 * rise_x},
                    {"dy": -h},
                ]
                route = gf.routing.route_single(
                    c, p_in, p_out, cross_section=cross_section, steps=steps
                )
            else:
                route = gf.routing.route_single(
                    c, p_in, p_out, cross_section=cross_section
                )
            lengths.append(route.length_backbone / 1000.0)
        c.info["arm_lengths"] = [round(x, 4) for x in lengths]

    c.add_port("o1", port=fpr_in_ref.ports["o1"])
    for i, port in enumerate(gf.port.get_ports_list(fpr_out_ref, prefix="W")):
        c.add_port(f"E{i}", port=port)

    return c

free_propagation_region

free_propagation_region(
    width1: float = 2.0,
    width2: float = 20.0,
    length: float = 20.0,
    wg_width: float = 0.5,
    inputs: int = 1,
    outputs: int = 10,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Free propagation region.

Parameters:

Name Type Description Default
width1 float

width of the input region.

2.0
width2 float

width of the output region.

20.0
length float

length of the free propagation region.

20.0
wg_width float

waveguide width.

0.5
inputs int

number of inputs.

1
outputs int

number of outputs.

10
cross_section CrossSectionSpec

cross_section function.

 length
 <-->
   /|
  / |

width1| | width2 \ | |

'strip'
Source code in gdsfactory/components/filters/awg.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
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
@gf.cell_with_module_name(tags=["filters"])
def free_propagation_region(
    width1: float = 2.0,
    width2: float = 20.0,
    length: float = 20.0,
    wg_width: float = 0.5,
    inputs: int = 1,
    outputs: int = 10,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Free propagation region.

    Args:
        width1: width of the input region.
        width2: width of the output region.
        length: length of the free propagation region.
        wg_width: waveguide width.
        inputs: number of inputs.
        outputs: number of outputs.
        cross_section: cross_section function.

                 length
                 <-->
                   /|
                  / |
           width1|  | width2
                  \ |
                   \|
    """
    y1 = width1 / 2
    y2 = width2 / 2
    xs = gf.get_cross_section(cross_section)
    layer = xs.layer
    assert layer is not None

    xpts = [0, length, length, 0]
    ypts = [y1, y2, -y2, -y1]

    c = gf.Component()
    c.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)

    if inputs == 1:
        c.add_port(
            "o1",
            center=(0, 0),
            width=wg_width,
            orientation=180,
            layer=layer,
        )
    else:
        y = np.linspace(-width1 / 2 + wg_width / 2, width1 / 2 - wg_width / 2, inputs)
        y = gf.snap.snap_to_grid(y)
        for i, yi in enumerate(y):
            c.add_port(
                f"W{i}",
                center=(0, float(yi)),
                width=wg_width,
                orientation=180,
                layer=layer,
            )

    y = np.linspace(-width2 / 2 + wg_width / 2, width2 / 2 - wg_width / 2, outputs)
    y = gf.snap.snap_to_grid(y)
    for i, yi in enumerate(y):
        c.add_port(
            f"E{i}",
            center=(length, float(yi)),
            width=wg_width,
            orientation=0,
            layer=layer,
        )

    c.info["length"] = length
    c.info["width1"] = width1
    c.info["width2"] = width2
    return c

awg

dbr

DBR gratings.

wavelength = 2periodneff period = wavelength/2/neff

dbr default parameters are from Stephen Lin thesis https://open.library.ubc.ca/cIRcle/collections/ubctheses/24/items/1.0388871

Period: 318nm, width: 500nm, dw: 20 ~ 120 nm.

dbr

dbr(
    w1: float = w1,
    w2: float = w2,
    l1: float = period / 2,
    l2: float = period / 2,
    n: int = 10,
    cross_section: CrossSectionSpec = "strip",
    straight_length: float = 0.01,
) -> Component

Distributed Bragg Reflector.

Parameters:

Name Type Description Default
w1 float

thin width in um.

w1
w2 float

thick width in um.

w2
l1 float

thin length in um.

period / 2
l2 float

thick length in um.

period / 2
n int

number of periods.

10
cross_section CrossSectionSpec

cross_section spec.

'strip'
straight_length float

length of the straight section between cutbacks.

l1 l2

0.01
Source code in gdsfactory/components/filters/dbr.py
 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
@gf.cell_with_module_name(tags=["filters"])
def dbr(
    w1: float = w1,
    w2: float = w2,
    l1: float = period / 2,
    l2: float = period / 2,
    n: int = 10,
    cross_section: CrossSectionSpec = "strip",
    straight_length: float = 10e-3,
) -> Component:
    """Distributed Bragg Reflector.

    Args:
        w1: thin width in um.
        w2: thick width in um.
        l1: thin length in um.
        l2: thick length in um.
        n: number of periods.
        cross_section: cross_section spec.
        straight_length: length of the straight section between cutbacks.

           l1      l2
        <-----><-------->
                _________
        _______|

          w1       w2       ...  n times
        _______
               |_________
    """
    c = Component()
    xs = gf.get_cross_section(cross_section)
    s1 = c << gf.c.straight(cross_section=xs, length=straight_length)
    s2 = c << gf.c.straight(cross_section=xs, length=straight_length)

    cell = dbr_cell(w1=w1, w2=w2, l1=l1, l2=l2, cross_section=cross_section)
    ref = c.add_ref(cell, columns=n, rows=1, column_pitch=l1 + l2)

    s1.connect(port="o1", other=cell.ports["o1"], allow_width_mismatch=True)
    s2.connect(port="o1", other=cell.ports["o2"], allow_width_mismatch=True)
    s2.xmin = ref.xmax

    c.add_port("o1", port=s1.ports["o2"])
    return c

dbr_cell

dbr_cell(
    w1: float = w1,
    w2: float = w2,
    l1: float = period / 2,
    l2: float = period / 2,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Distributed Bragg Reflector unit cell.

Parameters:

Name Type Description Default
w1 float

thin width in um.

w1
l1 float

thin length in um.

period / 2
w2 float

thick width in um.

w2
l2 float

thick length in um.

period / 2
n

number of periods.

required
cross_section CrossSectionSpec

cross_section spec.

l1 l2

'strip'
Source code in gdsfactory/components/filters/dbr.py
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
@gf.cell_with_module_name(tags=["filters"])
def dbr_cell(
    w1: float = w1,
    w2: float = w2,
    l1: float = period / 2,
    l2: float = period / 2,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Distributed Bragg Reflector unit cell.

    Args:
        w1: thin width in um.
        l1: thin length in um.
        w2: thick width in um.
        l2: thick length in um.
        n: number of periods.
        cross_section: cross_section spec.

           l1      l2
        <-----><-------->
                _________
        _______|

          w1       w2
        _______
               |_________
    """
    l1 = snap_to_grid(l1)
    l2 = snap_to_grid(l2)
    w1 = snap_to_grid(w1, 2)
    w2 = snap_to_grid(w2, 2)
    xs1 = gf.get_cross_section(cross_section, width=w1)
    xs2 = gf.get_cross_section(cross_section, width=w2)

    c = Component()
    c1 = c << gf.c.straight(length=l1, cross_section=xs1)
    c2 = c << gf.c.straight(length=l2, cross_section=xs2)
    c2.connect(port="o1", other=c1.ports["o2"], allow_width_mismatch=True)
    c.add_port("o1", port=c1.ports["o1"])
    c.add_port("o2", port=c2.ports["o2"])
    c.flatten()
    return c

dbr

dbr_cell

dbr_cell(
    w1: float = w1,
    w2: float = w2,
    l1: float = period / 2,
    l2: float = period / 2,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Distributed Bragg Reflector unit cell.

Parameters:

Name Type Description Default
w1 float

thin width in um.

w1
l1 float

thin length in um.

period / 2
w2 float

thick width in um.

w2
l2 float

thick length in um.

period / 2
n

number of periods.

required
cross_section CrossSectionSpec

cross_section spec.

l1 l2

'strip'
Source code in gdsfactory/components/filters/dbr.py
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
@gf.cell_with_module_name(tags=["filters"])
def dbr_cell(
    w1: float = w1,
    w2: float = w2,
    l1: float = period / 2,
    l2: float = period / 2,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Distributed Bragg Reflector unit cell.

    Args:
        w1: thin width in um.
        l1: thin length in um.
        w2: thick width in um.
        l2: thick length in um.
        n: number of periods.
        cross_section: cross_section spec.

           l1      l2
        <-----><-------->
                _________
        _______|

          w1       w2
        _______
               |_________
    """
    l1 = snap_to_grid(l1)
    l2 = snap_to_grid(l2)
    w1 = snap_to_grid(w1, 2)
    w2 = snap_to_grid(w2, 2)
    xs1 = gf.get_cross_section(cross_section, width=w1)
    xs2 = gf.get_cross_section(cross_section, width=w2)

    c = Component()
    c1 = c << gf.c.straight(length=l1, cross_section=xs1)
    c2 = c << gf.c.straight(length=l2, cross_section=xs2)
    c2.connect(port="o1", other=c1.ports["o2"], allow_width_mismatch=True)
    c.add_port("o1", port=c1.ports["o1"])
    c.add_port("o2", port=c2.ports["o2"])
    c.flatten()
    return c

dbr_cell

dbr_tapered

dbr_tapered

dbr_tapered(
    length: float = 10.0,
    period: float = 0.85,
    dc: float = 0.5,
    w1: float = 0.4,
    w2: float = 1.0,
    taper_length: float = 20.0,
    fins: bool = False,
    fin_size: Size = (0.2, 0.05),
    cross_section: CrossSectionSpec = "strip",
) -> Component

Distributed Bragg Reflector Cell class.

Tapers the input straight to a periodic straight structure with varying width (1-D photonic crystal).

Parameters:

Name Type Description Default
length float

Length of the DBR region.

10.0
period float

Period of the repeated unit.

0.85
dc float

Duty cycle of the repeated unit (must be a float between 0 and 1.0).

0.5
w1 float

thin section width. w1 = 0 corresponds to disconnected periodic blocks.

0.4
w2 float

wide section width.

1.0
taper_length float

between the input/output straight and the DBR region.

20.0
fins bool

If True, adds fins to the input/output straights.

False
fin_size Size

Specifies the x- and y-size of the fins. Defaults to 200 nm x 50 nm

(0.2, 0.05)
cross_section CrossSectionSpec

cross_section spec.

   period

<-----><--------> ___ _|

w1 w2 ... n times


 |_________
'strip'
Source code in gdsfactory/components/filters/dbr_tapered.py
 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
@gf.cell_with_module_name(tags=["filters"])
def dbr_tapered(
    length: float = 10.0,
    period: float = 0.85,
    dc: float = 0.5,
    w1: float = 0.4,
    w2: float = 1.0,
    taper_length: float = 20.0,
    fins: bool = False,
    fin_size: Size = (0.2, 0.05),
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Distributed Bragg Reflector Cell class.

    Tapers the input straight to a
    periodic straight structure with varying width (1-D photonic crystal).

    Args:
       length: Length of the DBR region.
       period: Period of the repeated unit.
       dc: Duty cycle of the repeated unit (must be a float between 0 and 1.0).
       w1: thin section width. w1 = 0 corresponds to disconnected periodic blocks.
       w2: wide section width.
       taper_length: between the input/output straight and the DBR region.
       fins: If `True`, adds fins to the input/output straights.
       fin_size: Specifies the x- and y-size of the `fins`. Defaults to 200 nm x 50 nm
       cross_section: cross_section spec.

                 period
        <-----><-------->
                _________
        _______|

          w1       w2       ...  n times
        _______
               |_________
    """
    c = gf.Component()

    xs = gf.get_cross_section(cross_section=cross_section, width=w2)

    input_taper = c << gf.components.taper(
        length=taper_length,
        width1=xs.width,
        width2=w1,
        cross_section=cross_section,
    )

    straight = c << gf.components.straight(
        length=length, cross_section=cross_section, width=w1
    )
    straight.x = 0
    straight.y = 0

    output_taper = c << gf.components.taper(
        length=taper_length,
        width1=w1,
        width2=xs.width,
        cross_section=cross_section,
    )

    input_taper.connect("o2", straight.ports["o1"])
    output_taper.connect("o1", straight.ports["o2"])
    num = (2 * taper_length + length) // period

    size = cast("tuple[float, float]", tuple(snap_to_grid2x((period * dc, w2))))
    assert xs.layer is not None
    teeth = gf.components.rectangle(size=size, layer=xs.layer, port_type=None)

    periodic_structures = c << gf.components.array(
        component=teeth, columns=int(num), column_pitch=period
    )
    periodic_structures.x = 0
    periodic_structures.y = 0

    if fins:
        _generate_fins(
            c=c,
            fin_size=fin_size,
            taper_length=taper_length,
            length=length,
            cross_section=xs,
        )

    xs.add_bbox(c)
    c.add_port("o1", port=input_taper.ports["o1"])
    c.add_port("o2", port=output_taper.ports["o2"])
    return c

dbr_tapered

fiber

fiber

fiber(
    core_diameter: float = 10,
    cladding_diameter: float = 125,
    layer_core: LayerSpec = "WG",
    layer_cladding: LayerSpec = "WGCLAD",
) -> Component

Returns a fiber.

Parameters:

Name Type Description Default
core_diameter float

in um.

10
cladding_diameter float

in um.

125
layer_core LayerSpec

layer spec for fiber core.

'WG'
layer_cladding LayerSpec

layer spec for fiber cladding.

'WGCLAD'
Source code in gdsfactory/components/filters/fiber.py
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
@gf.cell_with_module_name(tags=["filters"])
def fiber(
    core_diameter: float = 10,
    cladding_diameter: float = 125,
    layer_core: LayerSpec = "WG",
    layer_cladding: LayerSpec = "WGCLAD",
) -> Component:
    """Returns a fiber.

    Args:
        core_diameter: in um.
        cladding_diameter: in um.
        layer_core: layer spec for fiber core.
        layer_cladding: layer spec for fiber cladding.
    """
    c = Component()

    c.add_ref(circle(radius=core_diameter / 2, layer=layer_core))
    c.add_ref(circle(radius=cladding_diameter / 2, layer=layer_cladding))

    layer_core = gf.get_layer(layer_core)
    c.add_port(
        name="F0", width=core_diameter, orientation=0, center=(0, 0), layer=layer_core
    )
    return c

fiber

fiber_array

fiber_array

fiber_array(
    n: int = 8,
    pitch: float = 127.0,
    core_diameter: float = 10,
    cladding_diameter: float = 125,
    layer_core: LayerSpec = "WG",
    layer_cladding: LayerSpec = "WGCLAD",
) -> Component

Returns a fiber array.

Parameters:

Name Type Description Default
n int

number of fibers.

8
pitch float

spacing.

127.0
core_diameter float

10um.

10
cladding_diameter float

in um.

125
layer_core LayerSpec

layer spec for fiber core.

'WG'
layer_cladding LayerSpec

layer spec for fiber cladding.

'WGCLAD'
    pitch
     <->
    _________
   |         | lid
   | o o o o |
   |         | base
   |_________|
      length
Source code in gdsfactory/components/filters/fiber_array.py
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
@gf.cell_with_module_name(tags=["filters"])
def fiber_array(
    n: int = 8,
    pitch: float = 127.0,
    core_diameter: float = 10,
    cladding_diameter: float = 125,
    layer_core: LayerSpec = "WG",
    layer_cladding: LayerSpec = "WGCLAD",
) -> Component:
    """Returns a fiber array.

    Args:
        n: number of fibers.
        pitch: spacing.
        core_diameter: 10um.
        cladding_diameter: in um.
        layer_core: layer spec for fiber core.
        layer_cladding: layer spec for fiber cladding.

    ```text
        pitch
         <->
        _________
       |         | lid
       | o o o o |
       |         | base
       |_________|
          length
    ```
    """
    c = Component()
    layer_core = gf.get_layer(layer_core)

    for i in range(n):
        core = c.add_ref(circle(radius=core_diameter / 2, layer=layer_core))
        cladding = c.add_ref(circle(radius=cladding_diameter / 2, layer=layer_cladding))
        core.movex(i * pitch)
        cladding.movex(i * pitch)
        c.add_port(
            name=f"F{i}",
            width=core_diameter,
            orientation=0,
            layer=layer_core,
            center=(i * pitch, 0),
        )

    return c

fiber_array

free_propagation_region

free_propagation_region(
    width1: float = 2.0,
    width2: float = 20.0,
    length: float = 20.0,
    wg_width: float = 0.5,
    inputs: int = 1,
    outputs: int = 10,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Free propagation region.

Parameters:

Name Type Description Default
width1 float

width of the input region.

2.0
width2 float

width of the output region.

20.0
length float

length of the free propagation region.

20.0
wg_width float

waveguide width.

0.5
inputs int

number of inputs.

1
outputs int

number of outputs.

10
cross_section CrossSectionSpec

cross_section function.

 length
 <-->
   /|
  / |

width1| | width2 \ | |

'strip'
Source code in gdsfactory/components/filters/awg.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
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
@gf.cell_with_module_name(tags=["filters"])
def free_propagation_region(
    width1: float = 2.0,
    width2: float = 20.0,
    length: float = 20.0,
    wg_width: float = 0.5,
    inputs: int = 1,
    outputs: int = 10,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Free propagation region.

    Args:
        width1: width of the input region.
        width2: width of the output region.
        length: length of the free propagation region.
        wg_width: waveguide width.
        inputs: number of inputs.
        outputs: number of outputs.
        cross_section: cross_section function.

                 length
                 <-->
                   /|
                  / |
           width1|  | width2
                  \ |
                   \|
    """
    y1 = width1 / 2
    y2 = width2 / 2
    xs = gf.get_cross_section(cross_section)
    layer = xs.layer
    assert layer is not None

    xpts = [0, length, length, 0]
    ypts = [y1, y2, -y2, -y1]

    c = gf.Component()
    c.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)

    if inputs == 1:
        c.add_port(
            "o1",
            center=(0, 0),
            width=wg_width,
            orientation=180,
            layer=layer,
        )
    else:
        y = np.linspace(-width1 / 2 + wg_width / 2, width1 / 2 - wg_width / 2, inputs)
        y = gf.snap.snap_to_grid(y)
        for i, yi in enumerate(y):
            c.add_port(
                f"W{i}",
                center=(0, float(yi)),
                width=wg_width,
                orientation=180,
                layer=layer,
            )

    y = np.linspace(-width2 / 2 + wg_width / 2, width2 / 2 - wg_width / 2, outputs)
    y = gf.snap.snap_to_grid(y)
    for i, yi in enumerate(y):
        c.add_port(
            f"E{i}",
            center=(length, float(yi)),
            width=wg_width,
            orientation=0,
            layer=layer,
        )

    c.info["length"] = length
    c.info["width1"] = width1
    c.info["width2"] = width2
    return c

free_propagation_region

loop_mirror

Sagnac loop_mirror.

loop_mirror

loop_mirror(
    component: ComponentSpec = "mmi1x2",
    bend90: ComponentSpec = "bend_euler",
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns Sagnac loop_mirror.

Parameters:

Name Type Description Default
component ComponentSpec

1x2 splitter.

'mmi1x2'
bend90 ComponentSpec

90 deg bend.

'bend_euler'
cross_section CrossSectionSpec

cross_section settings.

'strip'
Source code in gdsfactory/components/filters/loop_mirror.py
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
@gf.cell_with_module_name(tags=["filters"])
def loop_mirror(
    component: ComponentSpec = "mmi1x2",
    bend90: ComponentSpec = "bend_euler",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns Sagnac loop_mirror.

    Args:
        component: 1x2 splitter.
        bend90: 90 deg bend.
        cross_section: cross_section settings.

    """
    c = Component()
    component = gf.get_component(component)
    bend90 = gf.get_component(bend90)
    cref = c.add_ref(component)
    gf.routing.route_single(
        c,
        cref.ports["o3"],
        cref.ports["o2"],
        straight=gf.components.straight,
        bend=bend90,
        cross_section=cross_section,
    )
    c.add_port(name="o1", port=cref.ports["o1"])
    return c

loop_mirror

mode_converter

mode_converter

mode_converter(
    gap: float = 0.3,
    length: float = 10,
    coupler_straight_asymmetric: ComponentSpec = "coupler_straight_asymmetric",
    bend: ComponentSpec = partial(bend_s, size=(25, 3)),
    taper: ComponentSpec = "taper",
    mm_width: float = 1.2,
    mc_mm_width: float = 1,
    sm_width: float = 0.5,
    taper_length: float = 25,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns Mode converter from TE0 to TE1.

By matching the effective indices of two waveguides with different widths, light can couple from different transverse modes e.g. TE0 <-> TE1. https://doi.org/10.1109/JPHOT.2019.2941742

Parameters:

Name Type Description Default
gap float

directional coupler gap.

0.3
length float

coupler length interaction.

10
coupler_straight_asymmetric ComponentSpec

spec.

'coupler_straight_asymmetric'
bend ComponentSpec

spec.

partial(bend_s, size=(25, 3))
taper ComponentSpec

spec.

'taper'
mm_width float

input/output multimode waveguide width.

1.2
mc_mm_width float

mode converter multimode waveguide width

1
sm_width float

single mode waveguide width.

0.5
taper_length float

taper length.

25
cross_section CrossSectionSpec

cross_section spec.

'strip'
=

multimode width

required
-

singlemode width

required
Source code in gdsfactory/components/filters/mode_converter.py
 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
 96
 97
 98
 99
100
@gf.cell_with_module_name(schematic_function=ckt_schematic, tags=["filters"])
def mode_converter(
    gap: float = 0.3,
    length: float = 10,
    coupler_straight_asymmetric: ComponentSpec = "coupler_straight_asymmetric",
    bend: ComponentSpec = partial(bend_s, size=(25, 3)),
    taper: ComponentSpec = "taper",
    mm_width: float = 1.2,
    mc_mm_width: float = 1,
    sm_width: float = 0.5,
    taper_length: float = 25,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Returns Mode converter from TE0 to TE1.

    By matching the effective indices of two waveguides with different widths,
    light can couple from different transverse modes e.g. TE0 <-> TE1.
    https://doi.org/10.1109/JPHOT.2019.2941742

    Args:
        gap: directional coupler gap.
        length: coupler length interaction.
        coupler_straight_asymmetric: spec.
        bend: spec.
        taper: spec.
        mm_width: input/output multimode waveguide width.
        mc_mm_width: mode converter multimode waveguide width
        sm_width: single mode waveguide width.
        taper_length: taper length.
        cross_section: cross_section spec.

        o2 ---           --- o4
              \         /
               \       /
                -------
        o1 -----=======----- o3
                |-----|
                length

        = : multimode width
        - : singlemode width
    """
    c = Component()

    coupler = gf.get_component(
        coupler_straight_asymmetric,
        length=length,
        gap=gap,
        width_bot=mc_mm_width,
        width_top=sm_width,
        cross_section=cross_section,
    )

    bend = gf.get_component(bend, cross_section=cross_section)

    bot_taper = gf.get_component(
        taper,
        width1=mc_mm_width,
        width2=mm_width,
        length=taper_length,
        cross_section=cross_section,
    )

    # directional coupler
    dc = c << coupler

    # straight waveguides at the bottom
    l_bot_straight = c << bot_taper
    r_bot_straight = c << bot_taper

    l_bot_straight.connect("o1", dc.ports["o1"])
    r_bot_straight.connect("o1", dc.ports["o4"])

    # top right bend with termination
    r_bend = c << bend
    l_bend = c << bend

    l_bend.connect("o1", dc.ports["o2"], mirror=True)
    r_bend.connect("o1", dc.ports["o3"])

    # define ports of mode converter
    c.add_port("o1", port=l_bot_straight.ports["o2"])
    c.add_port("o3", port=r_bot_straight.ports["o2"])
    c.add_port("o2", port=l_bend.ports["o2"])
    c.add_port("o4", port=r_bend.ports["o2"])
    return c

mode_converter

polarization_splitter_rotator

polarization_splitter_rotator

polarization_splitter_rotator(
    width_taper_in: Float3 = (0.54, 0.69, 0.83),
    length_taper_in: Float2 | Float3 = (4.0, 44.0),
    width_coupler: Float2 = (0.9, 0.404),
    length_coupler: float = 7.0,
    gap: float = 0.15,
    width_out: float = 0.54,
    length_out: float = 14.33,
    dy: Delta = 5.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns polarization splitter rotator.

"Novel concept for ultracompact polarization splitter-rotator based on silicon nanowires." By D. Dai, and J. E. Bowers (Optics express vol 19, no. 11 pp. 10940-10949 (2011)).

Parameters:

Name Type Description Default
width_taper_in Float3

Three west widths of the input tapers in um.

(0.54, 0.69, 0.83)
length_taper_in Float2 | Float3

Two or three length of the bend regions in um.

(4.0, 44.0)
width_coupler Float2

Top and bottom widths of the coupling region in um.

(0.9, 0.404)
length_coupler float

Length of the coupling region in um.

7.0
gap float

Distance between the coupler in um.

0.15
width_out float

Width of the splitter region in um.

0.54
length_out float

Length of the splitter region in um.

14.33
dy Delta

Port-to-port distance between the splitter region in um.

5.0
cross_section CrossSectionSpec

cross-section spec.

'strip'
Notes

The length of third input taper is automatically determined if only two lengths are in arguments.

Source code in gdsfactory/components/filters/polarization_splitter_rotator.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
@gf.cell_with_module_name(tags=["filters"])
def polarization_splitter_rotator(
    width_taper_in: Float3 = (0.54, 0.69, 0.83),
    length_taper_in: Float2 | Float3 = (4.0, 44.0),
    width_coupler: Float2 = (0.9, 0.404),
    length_coupler: float = 7.0,
    gap: float = 0.15,
    width_out: float = 0.54,
    length_out: float = 14.33,
    dy: Delta = 5.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns polarization splitter rotator.

    "Novel concept for ultracompact polarization splitter-rotator
    based on silicon nanowires." By D. Dai, and J. E. Bowers
    (Optics express vol 19, no. 11 pp. 10940-10949 (2011)).

    Args:
        width_taper_in: Three west widths of the input tapers in um.
        length_taper_in: Two or three length of the bend regions in um.
        width_coupler: Top and bottom widths of the coupling region in um.
        length_coupler: Length of the coupling region in um.
        gap: Distance between the coupler in um.
        width_out: Width of the splitter region in um.
        length_out: Length of the splitter region in um.
        dy: Port-to-port distance between the splitter region in um.
        cross_section: cross-section spec.


    Notes:
        The length of third input taper is automatically determined
        if only two lengths are in arguments.
    """
    c = gf.Component()
    x = gf.get_cross_section(cross_section=cross_section)

    w0, w1, w2 = width_taper_in
    w3, w4 = width_coupler
    if len(length_taper_in) == 2:
        l1, l2 = length_taper_in
        l3 = l1 * (w3 - w2) / (w1 - w0)
    else:
        l1, l2, l3 = length_taper_in

    taper_in1 = c << gf.c.taper(
        length=l1, width1=w0, width2=w1, cross_section=cross_section
    )
    taper_in2 = c << gf.c.taper(
        length=l2, width1=w1, width2=w2, cross_section=cross_section
    )
    taper_in3 = c << gf.c.taper(
        length=l3, width1=w2, width2=w3, cross_section=cross_section
    )

    coupler = c << gf.c.coupler_straight_asymmetric(
        length=length_coupler,
        gap=gap,
        width_top=w4,
        width_bot=w3,
        cross_section=cross_section,
    )

    def bend_s_width(t: npt.NDArray[np.floating[Any]]) -> npt.NDArray[np.floating[Any]]:
        return w4 + (width_out - w4) * t

    x_bend = x.copy(width_function=bend_s_width)

    bend_s_var = c << bezier(
        control_points=(
            (0, 0),
            (length_out / 2, 0),
            (length_out / 2, dy),
            (length_out, dy),
        ),
        cross_section=x_bend,
    )

    taper_out = c << gf.c.taper(
        length=length_out, width1=w3, width2=width_out, cross_section=cross_section
    )

    taper_in3.connect("o2", other=coupler.ports["o1"])
    taper_in2.connect("o2", other=taper_in3.ports["o1"])
    taper_in1.connect("o2", other=taper_in2.ports["o1"])
    taper_out.connect("o1", other=coupler.ports["o4"])
    bend_s_var.connect("o1", other=coupler.ports["o3"])

    c.add_port("o1", port=taper_in1.ports["o1"])
    c.add_port("o2", port=bend_s_var.ports["o2"])
    c.add_port("o3", port=taper_out.ports["o2"])

    c.auto_rename_ports()
    c.flatten()
    return c

polarization_splitter_rotator

terminator

terminator

terminator(
    length: float | None = 50,
    cross_section_input: CrossSectionSpec = strip,
    cross_section_tip: CrossSectionSpec | None = None,
    tapered_width: float = 0.2,
    doping_layers: LayerSpecs = ("NPP",),
    doping_offset: float = 1.0,
) -> gf.Component

Returns doped taper to terminate waveguides.

Parameters:

Name Type Description Default
length float | None

distance between input and narrow tapered end.

50
cross_section_input CrossSectionSpec

input cross-section.

strip
cross_section_tip CrossSectionSpec | None

cross-section at the end of the termination.

None
tapered_width float

width of the default cross-section at the end of the termination. Only used if cross_section_tip is not None.

0.2
doping_layers LayerSpecs

doping layers to superimpose on the taper. Default N++.

('NPP',)
doping_offset float

offset of the doping layer beyond the bbox

1.0
Source code in gdsfactory/components/filters/terminator.py
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
@gf.cell_with_module_name(schematic_function=terminator_schematic, tags=["filters"])
def terminator(
    length: float | None = 50,
    cross_section_input: CrossSectionSpec = strip,
    cross_section_tip: CrossSectionSpec | None = None,
    tapered_width: float = 0.2,
    doping_layers: LayerSpecs = ("NPP",),
    doping_offset: float = 1.0,
) -> gf.Component:
    """Returns doped taper to terminate waveguides.

    Args:
        length: distance between input and narrow tapered end.
        cross_section_input: input cross-section.
        cross_section_tip: cross-section at the end of the termination.
        tapered_width: width of the default cross-section at the end of the termination.
            Only used if cross_section_tip is not None.
        doping_layers: doping layers to superimpose on the taper. Default N++.
        doping_offset: offset of the doping layer beyond the bbox
    """
    c = Component()

    cross_section_tip = cross_section_tip or gf.get_cross_section(
        cross_section_input, width=tapered_width
    )

    taper = c << gf.get_component(
        gf.c.taper_cross_section,
        length=length,
        cross_section1=cross_section_input,
        cross_section2=cross_section_tip,
    )

    points = get_padding_points(
        taper, default=0, top=doping_offset, bottom=doping_offset
    )
    for layer in doping_layers:
        c.add_polygon(points, layer=layer)
    c.add_port(name="o1", port=taper.ports["o1"])
    return c

terminator

terminator_spiral

terminator_spiral

terminator_spiral(
    separation: float = 3.0,
    width_tip: float = 0.2,
    number_of_loops: float = 1,
    npoints: int = 1000,
    min_bend_radius: float | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> gf.Component

Returns doped taper to terminate waveguides.

Parameters:

Name Type Description Default
separation float

separation between the loops.

3.0
width_tip float

width of the default cross-section at the end of the termination. Only used if cross_section_tip is not None.

0.2
number_of_loops float

number of loops in the spiral.

1
npoints int

points for the spiral.

1000
min_bend_radius float | None

minimum bend radius for the spiral.

None
cross_section CrossSectionSpec

input cross-section.

'strip'
Source code in gdsfactory/components/filters/terminator_spiral.py
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
@gf.cell_with_module_name(schematic_function=terminator_schematic, tags=["filters"])
def terminator_spiral(
    separation: float = 3.0,
    width_tip: float = 0.2,
    number_of_loops: float = 1,
    npoints: int = 1000,
    min_bend_radius: float | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> gf.Component:
    """Returns doped taper to terminate waveguides.

    Args:
        separation: separation between the loops.
        width_tip: width of the default cross-section at the end of the termination.
            Only used if cross_section_tip is not None.
        number_of_loops: number of loops in the spiral.
        npoints: points for the spiral.
        min_bend_radius: minimum bend radius for the spiral.
        cross_section: input cross-section.
    """
    cross_section_main = gf.get_cross_section(cross_section)
    cross_section_tip = gf.get_cross_section(cross_section, width=width_tip)

    xs = transition(
        cross_section2=cross_section_main,
        cross_section1=cross_section_tip,
        width_type="linear",
    )

    min_bend_radius = min_bend_radius or cross_section_main.radius_min
    assert min_bend_radius

    path = spiral_archimedean(
        min_bend_radius=min_bend_radius,
        separation=separation / 2,
        number_of_loops=number_of_loops,
        npoints=npoints,
    )
    path.start_angle = 0
    path.end_angle = 0

    spiral = extrude_transition(path, transition=xs)
    c = gf.Component()
    ref = c << spiral
    c.add_port("o1", port=ref["o2"])
    c.flatten()
    return c

terminator_spiral

grating_couplers

grating_coupler_array

grating_coupler_array

grating_coupler_array(
    grating_coupler: ComponentSpec = "grating_coupler_elliptical",
    pitch: float = 127.0,
    n: int = 6,
    port_name: str = "o1",
    rotation: int = -90,
    with_loopback: bool = False,
    cross_section: CrossSectionSpec = "strip",
    straight_to_grating_spacing: float = 10.0,
    centered: bool = True,
    radius: float | None = None,
    bend: ComponentSpec = "bend_euler",
    mirror_grating_coupler: bool = False,
) -> Component

Array of grating couplers.

Parameters:

Name Type Description Default
grating_coupler ComponentSpec

ComponentSpec.

'grating_coupler_elliptical'
pitch float

x spacing.

127.0
n int

number of grating couplers.

6
port_name str

port name.

'o1'
rotation int

rotation angle for each reference.

-90
with_loopback bool

if True, adds a loopback between edge GCs. Only works for rotation = 90 for now.

False
cross_section CrossSectionSpec

cross_section for the routing.

'strip'
straight_to_grating_spacing float

spacing between the last grating coupler and the loopback.

10.0
centered bool

if True, centers the array around the origin.

True
radius float | None

optional radius for routing the loopback.

None
bend ComponentSpec

ComponentSpec for the bend used in the loopback.

'bend_euler'
mirror_grating_coupler bool

if True, mirrors the grating coupler.

False
Source code in gdsfactory/components/grating_couplers/grating_coupler_array.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
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
@gf.cell_with_module_name(tags=["grating_couplers"])
def grating_coupler_array(
    grating_coupler: ComponentSpec = "grating_coupler_elliptical",
    pitch: float = 127.0,
    n: int = 6,
    port_name: str = "o1",
    rotation: int = -90,
    with_loopback: bool = False,
    cross_section: CrossSectionSpec = "strip",
    straight_to_grating_spacing: float = 10.0,
    centered: bool = True,
    radius: float | None = None,
    bend: ComponentSpec = "bend_euler",
    mirror_grating_coupler: bool = False,
) -> Component:
    """Array of grating couplers.

    Args:
        grating_coupler: ComponentSpec.
        pitch: x spacing.
        n: number of grating couplers.
        port_name: port name.
        rotation: rotation angle for each reference.
        with_loopback: if True, adds a loopback between edge GCs. Only works for rotation = 90 for now.
        cross_section: cross_section for the routing.
        straight_to_grating_spacing: spacing between the last grating coupler and the loopback.
        centered: if True, centers the array around the origin.
        radius: optional radius for routing the loopback.
        bend: ComponentSpec for the bend used in the loopback.
        mirror_grating_coupler: if True, mirrors the grating coupler.
    """
    c = Component()
    grating_coupler = gf.get_component(grating_coupler)
    if mirror_grating_coupler:
        grating_coupler = gf.functions.mirror(grating_coupler)
    ports: dict[str, kf.DPort] = {}

    for i in range(n):
        gc = c << grating_coupler
        gc.rotate(rotation)
        gc.x = (i - (n - 1) / 2) * pitch if centered else i * pitch
        port_name_new = f"o{i}"
        ports[port_name_new] = gc.ports[port_name]
        if not with_loopback or i not in [0, n - 1]:
            c.add_port(port=gc.ports[port_name], name=port_name_new)

    if with_loopback:
        if rotation != -90:
            raise ValueError(
                f"with_loopback works only with rotation = -90, got {rotation=}"
            )
        routing_xs = gf.get_cross_section(cross_section)
        radius = radius or routing_xs.radius
        if radius is None:
            bend_component = gf.get_component(bend, cross_section=cross_section)
            try:
                radius = _get_routing_radius(bend_component, cross_section)
                bend = bend_component
            except KeyError as err:
                raise ValueError(
                    "Radius must be set in the cross_section or bend component if not provided explicitly."
                ) from err

        port0 = ports["o0"]
        port1 = ports[f"o{n - 1}"]
        assert radius is not None
        radius_dbu = c.kcl.to_dbu(radius)
        d_loop_um = straight_to_grating_spacing + max(
            [
                grating_coupler.ysize,
                grating_coupler.xsize,
            ]
        )
        d_loop = c.kcl.to_dbu(d_loop_um) + radius_dbu

        port0 = add_auto_tapers(c, [port0], cross_section)[0]
        port1 = add_auto_tapers(c, [port1], cross_section)[0]
        waypoints = kf.routing.optical.route_loopback(
            port0.to_itype(),
            port1.to_itype(),
            bend90_radius=radius_dbu,
            d_loop=d_loop,
        )

        waypoints_ = [point.to_dtype(c.kcl.dbu) for point in waypoints]

        gf.routing.route_single(
            c,
            port0,
            port1,
            waypoints=waypoints_,
            cross_section=cross_section,
            radius=radius,
            bend=bend,
        )

    return c

grating_coupler_array

grating_coupler_dual_pol

grating_coupler_dual_pol

grating_coupler_dual_pol(
    unit_cell: ComponentSpec = _unit_cell,
    period_x: float = 0.58,
    period_y: float = 0.58,
    x_span: float = 11,
    y_span: float = 11,
    length_taper: float = 150.0,
    width_taper: float = 10.0,
    polarization: str = "te",
    wavelength: float = 1.55,
    taper: ComponentSpec = "taper",
    base_layer: LayerSpec = "WG",
    cross_section: CrossSectionSpec = "strip",
) -> Component

2 dimensional, dual polarization grating coupler.

Based on a photonic crystal with a unit cell that is usually an ellipse, a rectangle or a circle. The default values are loosely based on Taillaert et al, "A Compact Two-Dimensional Grating Coupler Used as a Polarization Splitter", IEEE Phot. Techn. Lett. 15(9), 2003.

Parameters:

Name Type Description Default
unit_cell ComponentSpec

component describing the unit cell of the photonic crystal.

_unit_cell
period_x float

spacing between unit cells in the x direction [um].

0.58
period_y float

spacing between unit cells in the y direction [um].

0.58
x_span float

full x span of the photonic crystal.

11
y_span float

full y span of the photonic crystal.

11
length_taper float

taper length [um].

150.0
width_taper float

width of the taper at the grating coupler side [um].

10.0
polarization str

polarization of the grating coupler.

'te'
wavelength float

operation wavelength [um]

1.55
taper ComponentSpec

function to generate the tapers.

'taper'
base_layer LayerSpec

layer to draw over the whole photonic crystal (necessary if the unit cells are etched into a base layer).

'WG'
cross_section CrossSectionSpec

for the routing waveguides.

'strip'
Source code in gdsfactory/components/grating_couplers/grating_coupler_dual_pol.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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_dual_pol(
    unit_cell: ComponentSpec = _unit_cell,
    period_x: float = 0.58,
    period_y: float = 0.58,
    x_span: float = 11,
    y_span: float = 11,
    length_taper: float = 150.0,
    width_taper: float = 10.0,
    polarization: str = "te",
    wavelength: float = 1.55,
    taper: ComponentSpec = "taper",
    base_layer: LayerSpec = "WG",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""2 dimensional, dual polarization grating coupler.

    Based on a photonic crystal with a unit cell that is usually an ellipse,
    a rectangle or a circle.
    The default values are loosely based on Taillaert et al,
    "A Compact Two-Dimensional Grating Coupler Used as a Polarization Splitter",
    IEEE Phot. Techn. Lett. 15(9), 2003.

    Args:
        unit_cell: component describing the unit cell of the photonic crystal.
        period_x: spacing between unit cells in the x direction [um].
        period_y: spacing between unit cells in the y direction [um].
        x_span: full x span of the photonic crystal.
        y_span: full y span of the photonic crystal.
        length_taper: taper length [um].
        width_taper: width of the taper at the grating coupler side [um].
        polarization: polarization of the grating coupler.
        wavelength: operation wavelength [um]
        taper: function to generate the tapers.
        base_layer: layer to draw over the whole photonic crystal
            (necessary if the unit cells are etched into a base layer).
        cross_section: for the routing waveguides.

        side view
                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___  --> unit_cells
                   base_layer |
            o1  ______________|


        top view

                   -------------
               // | o   o   o  |
        o1 __ //  | o   o   o  |
              \\  | o   o   o  |
               \\ | o   o   o  |
                   -------------
                   \\         //
                    \\       //
                         |
                         o2

    """
    xs = gf.get_cross_section(cross_section)
    wg_width = xs.width
    layer = xs.layer

    c = Component()

    _ = c << gf.c.rectangle(
        size=(x_span, y_span), layer=base_layer, centered=True, port_type=None
    )

    # Photonic crystal
    num_x = int(np.floor(x_span / period_x))
    num_y = int(np.floor(y_span / period_y))
    x_start = -(num_x * period_x) / 2
    y_start = -(num_y * period_y) / 2

    unit_cell_grating = gf.get_component(unit_cell)
    g = c.add_ref(
        unit_cell_grating,
        columns=num_x,
        rows=num_y,
        column_pitch=period_x,
        row_pitch=period_y,
    )
    g.xmin = x_start
    g.ymin = y_start

    port_type = f"vertical_{polarization.lower()}"
    c.add_port(
        name=port_type,
        port_type=port_type,
        center=(0, 0),
        orientation=0,
        width=x_span,
        layer=layer,
    )
    c.info["polarization"] = polarization
    c.info["wavelength"] = wavelength
    taper = gf.get_component(
        taper,
        length=length_taper,
        width2=width_taper,
        width1=wg_width,
        cross_section=cross_section,
    )

    taper1 = c << taper
    taper1.xmax = -x_span / 2
    taper1.y = 0
    c.add_port(port=taper1.ports["o1"], name="o1")

    taper2 = c << taper
    taper2.rotate(90)
    taper2.x = 0
    taper2.ymax = -y_span / 2
    c.add_port(port=taper2.ports["o1"], name="o2")

    xs.add_bbox(c)
    return c

grating_coupler_dual_pol

grating_coupler_elliptical

grating_coupler_elliptical

grating_coupler_elliptical(
    polarization: str = "te",
    taper_length: float = 16.6,
    taper_angle: float = 40.0,
    wavelength: float = 1.554,
    fiber_angle: float = 15.0,
    grating_line_width: float = 0.343,
    neff: float = 2.638,
    nclad: float = 1.443,
    n_periods: int = 30,
    big_last_tooth: bool = False,
    layer_slab: LayerSpec | None = "SLAB150",
    slab_xmin: float = -1.0,
    slab_offset: float = 2.0,
    spiked: bool = True,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Grating coupler with parametrization based on Lumerical FDTD simulation.

Parameters:

Name Type Description Default
polarization str

te or tm.

'te'
taper_length float

taper length from input.

16.6
taper_angle float

grating flare angle.

40.0
wavelength float

grating transmission central wavelength (um).

1.554
fiber_angle float

fibre angle in degrees determines ellipticity.

15.0
grating_line_width float

in um.

0.343
neff float

tooth effective index.

2.638
nclad float

cladding effective index.

1.443
n_periods int

number of periods.

30
big_last_tooth bool

adds a big_last_tooth.

False
layer_slab LayerSpec | None

layer that protects the slab under the grating.

'SLAB150'
slab_xmin float

where 0 is at the start of the taper.

-1.0
slab_offset float

in um.

2.0
spiked bool

grating teeth have sharp spikes to avoid non-manhattan drc errors.

True
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

      fiber

   /  /  /  /
  /  /  /  /

_|-|_|-|_|-|___ layer
   layer_slab |

o1 __|

'strip'
Source code in gdsfactory/components/grating_couplers/grating_coupler_elliptical.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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_elliptical(
    polarization: str = "te",
    taper_length: float = 16.6,
    taper_angle: float = 40.0,
    wavelength: float = 1.554,
    fiber_angle: float = 15.0,
    grating_line_width: float = 0.343,
    neff: float = 2.638,  # tooth effective index
    nclad: float = 1.443,
    n_periods: int = 30,
    big_last_tooth: bool = False,
    layer_slab: LayerSpec | None = "SLAB150",
    slab_xmin: float = -1.0,
    slab_offset: float = 2.0,
    spiked: bool = True,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Grating coupler with parametrization based on Lumerical FDTD simulation.

    Args:
        polarization: te or tm.
        taper_length: taper length from input.
        taper_angle: grating flare angle.
        wavelength: grating transmission central wavelength (um).
        fiber_angle: fibre angle in degrees determines ellipticity.
        grating_line_width: in um.
        neff: tooth effective index.
        nclad: cladding effective index.
        n_periods: number of periods.
        big_last_tooth: adds a big_last_tooth.
        layer_slab: layer that protects the slab under the grating.
        slab_xmin: where 0 is at the start of the taper.
        slab_offset: in um.
        spiked: grating teeth have sharp spikes to avoid non-manhattan drc errors.
        cross_section: specification (CrossSection, string or dict).

                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___ layer
                   layer_slab |
            o1  ______________|

    """
    xs = gf.get_cross_section(cross_section)

    wg_width = xs.width
    layer = xs.layer
    assert layer is not None

    # Compute some ellipse parameters
    sthc = np.sin(fiber_angle * DEG2RAD)
    d = neff**2 - nclad**2 * sthc**2
    a1 = wavelength * neff / d
    b1 = wavelength / np.sqrt(d)
    x1 = wavelength * nclad * sthc / d

    a1 = float(round(a1, 3))
    b1 = float(round(b1, 3))
    x1 = float(round(x1, 3))

    period = a1 + x1

    c = gf.Component()
    c.info["polarization"] = polarization
    c.info["wavelength"] = wavelength

    # Make the taper
    p = taper_length / period
    a_taper = a1 * p
    b_taper = b1 * p
    x_taper = x1 * p

    x_output = a_taper + x_taper - taper_length
    pts = grating_taper_points(
        a=a_taper,
        b=b_taper,
        x0=x_output,
        taper_length=x_taper,
        taper_angle=taper_angle,
        wg_width=wg_width,
    )
    c.add_polygon(pts, layer)

    width = gf.snap.snap_to_grid(grating_line_width)
    gap = gf.snap.snap_to_grid(period - grating_line_width)

    xi = taper_length
    for p in range(n_periods):
        xi += gap + width / 2
        p = xi / period
        pts = grating_tooth_points(
            p * a1, p * b1, p * x1, width, taper_angle, spiked=spiked
        )
        c.add_polygon(pts, layer)
        xi += width / 2

    w = 1.0
    total_length = (
        period * n_periods
        + taper_length
        + grating_line_width / 2
        + period
        - grating_line_width
        + w / 2
    )

    if big_last_tooth:
        # Add last "large tooth" after the standard grating teeth
        a = total_length / (1 + x1 / a1)
        b = b1 / a1 * a
        x = x1 / a1 * a

        pts = grating_tooth_points(a, b, x, w, taper_angle, spiked=False)
        c.add_polygon(pts, layer)

    x = np.round(taper_length + x_output, 3)

    c.add_port(
        name="o1",
        center=(x_output, 0),
        width=wg_width,
        orientation=180,
        layer=layer,
        port_type="optical",
    )

    if layer_slab:
        slab_xmin += x_output + taper_length
        slab_length = total_length + slab_offset
        slab_width = (c.ysize + 2 * slab_offset) / 2
        c.add_polygon(
            [
                (slab_xmin, slab_width),
                (slab_length, slab_width),
                (slab_length, -slab_width),
                (slab_xmin, -slab_width),
            ],
            layer_slab,
        )

    xs.add_bbox(c)
    c.add_port(
        name="o2",
        center=(x, 0),
        width=10,
        orientation=0,
        layer=layer,
        port_type=f"vertical_{polarization}",
    )
    return c

grating_coupler_elliptical

grating_coupler_elliptical_arbitrary

grating_coupler_elliptical_arbitrary

grating_coupler_elliptical_arbitrary(
    gaps: Floats = _gaps,
    widths: Floats = _widths,
    taper_length: float = 16.6,
    taper_angle: float = 60.0,
    wavelength: float = 1.554,
    fiber_angle: float = 15.0,
    nclad: float = 1.443,
    layer_slab: LayerSpec | None = "SLAB150",
    layer_grating: LayerSpec | None = None,
    taper_to_slab_offset: float = -3.0,
    polarization: str = "te",
    spiked: bool = True,
    bias_gap: float = 0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Grating coupler with parametrization based on Lumerical FDTD simulation.

The ellipticity is derived from Lumerical knowledge base it depends on fiber_angle (degrees), neff, and nclad

Parameters:

Name Type Description Default
gaps Floats

list of gaps.

_gaps
widths Floats

list of widths.

_widths
taper_length float

taper length from input.

16.6
taper_angle float

grating flare angle.

60.0
wavelength float

grating transmission central wavelength (um).

1.554
fiber_angle float

fibre angle in degrees determines ellipticity.

15.0
nclad float

cladding effective index to compute ellipticity.

1.443
layer_slab LayerSpec | None

Optional slab.

'SLAB150'
layer_grating LayerSpec | None

Optional layer for grating. by default None uses cross_section.layer. if different from cross_section.layer expands taper.

None
taper_to_slab_offset float

0 is where taper ends.

-3.0
polarization str

te or tm.

'te'
spiked bool

grating teeth have spikes to avoid drc errors.

True
bias_gap float

etch gap (um). Positive bias increases gap and reduces width to keep period constant.

0
cross_section CrossSectionSpec

cross_section spec for waveguide port.

'strip'

https://en.wikipedia.org/wiki/Ellipse c = (a1 ** 2 - b1 ** 2) ** 0.5 e = (1 - (b1 / a1) ** 2) ** 0.5 print(e)

              fiber

           /  /  /  /
          /  /  /  /

        _|-|_|-|_|-|___ layer
           layer_slab |
    o1  ______________|
Source code in gdsfactory/components/grating_couplers/grating_coupler_elliptical_arbitrary.py
 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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_elliptical_arbitrary(
    gaps: Floats = _gaps,
    widths: Floats = _widths,
    taper_length: float = 16.6,
    taper_angle: float = 60.0,
    wavelength: float = 1.554,
    fiber_angle: float = 15.0,
    nclad: float = 1.443,
    layer_slab: LayerSpec | None = "SLAB150",
    layer_grating: LayerSpec | None = None,
    taper_to_slab_offset: float = -3.0,
    polarization: str = "te",
    spiked: bool = True,
    bias_gap: float = 0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Grating coupler with parametrization based on Lumerical FDTD simulation.

    The ellipticity is derived from Lumerical knowledge base
    it depends on fiber_angle (degrees), neff, and nclad

    Args:
        gaps: list of gaps.
        widths: list of widths.
        taper_length: taper length from input.
        taper_angle: grating flare angle.
        wavelength: grating transmission central wavelength (um).
        fiber_angle: fibre angle in degrees determines ellipticity.
        nclad: cladding effective index to compute ellipticity.
        layer_slab: Optional slab.
        layer_grating: Optional layer for grating.
            by default None uses cross_section.layer.
            if different from cross_section.layer expands taper.
        taper_to_slab_offset: 0 is where taper ends.
        polarization: te or tm.
        spiked: grating teeth have spikes to avoid drc errors.
        bias_gap: etch gap (um).
            Positive bias increases gap and reduces width to keep period constant.
        cross_section: cross_section spec for waveguide port.

    https://en.wikipedia.org/wiki/Ellipse
    c = (a1 ** 2 - b1 ** 2) ** 0.5
    e = (1 - (b1 / a1) ** 2) ** 0.5
    print(e)

                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___ layer
                   layer_slab |
            o1  ______________|

    """
    xs = gf.get_cross_section(cross_section)
    wg_width = xs.width
    assert xs.layer is not None
    layer_wg = gf.get_layer(xs.layer)

    layer_grating = layer_grating or layer_wg
    layer_grating = gf.get_layer(layer_grating)
    sthc = np.sin(fiber_angle * DEG2RAD)

    # generate component
    c = gf.Component()
    c.info["polarization"] = polarization
    c.info["wavelength"] = wavelength

    # get the physical parameters needed to compute ellipses
    gaps_array = gf.snap.snap_to_grid(np.array(gaps) + bias_gap)
    widths_array = gf.snap.snap_to_grid(np.array(widths) - bias_gap)
    periods = [g + w for g, w in zip(gaps_array, widths_array, strict=False)]
    neffs = [wavelength / p + nclad * sthc for p in periods]
    ds = [neff**2 - nclad**2 * sthc**2 for neff in neffs]
    a1s = [round(wavelength * neff / d, 3) for neff, d in zip(neffs, ds, strict=False)]
    b1s = [round(wavelength / np.sqrt(d), 3) for d in ds]
    x1s = [round(wavelength * nclad * sthc / d, 3) for d in ds]
    xis = np.add(
        taper_length + np.cumsum(periods), -widths_array / 2
    )  # position of middle of each tooth
    ps = np.divide(xis, periods)

    # grating teeth
    for a1, b1, x1, p, width in zip(a1s, b1s, x1s, ps, widths_array, strict=False):
        pts = grating_tooth_points(
            p * a1, p * b1, p * x1, float(width), taper_angle, spiked=spiked
        )
        c.add_polygon(pts, layer_grating)

    # taper
    p = taper_length / periods[0]  # (gaps[0]+widths[0])
    a_taper = p * a1s[0]
    b_taper = p * b1s[0]
    x_taper = p * x1s[0]
    x_output = a_taper + x_taper - taper_length + widths_array[0] / 2

    if layer_grating == layer_wg:
        pts = grating_taper_points(
            a_taper, b_taper, x_output, x_taper, taper_angle, wg_width=wg_width
        )
        c.add_polygon(pts, layer_wg)

    else:
        pts = grating_taper_points(
            a_taper,
            b_taper,
            x_output,
            x_taper + np.sum(widths_array) + np.sum(gaps_array) + 1,
            taper_angle,
            wg_width=wg_width,
        )
        c.add_polygon(pts, layer=layer_wg)

    c.add_port(
        name="o1",
        center=(x_output, 0),
        width=wg_width,
        orientation=180,
        layer=layer_wg,
        cross_section=xs,
    )

    if layer_slab:
        slab_xmin = taper_length + taper_to_slab_offset
        slab_xmax = c.xmax + 0.5
        slab_ysize = c.ysize + 2.0
        yslab = slab_ysize / 2
        c.add_polygon(
            [
                (slab_xmin, yslab),
                (slab_xmax, yslab),
                (slab_xmax, -yslab),
                (slab_xmin, -yslab),
            ],
            layer_slab,
        )

    xs.add_bbox(c)
    x = (taper_length + xis[-1]) / 2
    c.add_port(
        name="o2",
        center=(x, 0),
        width=10,
        orientation=0,
        layer=xs.layer,
        port_type=f"vertical_{polarization}",
    )
    return c

grating_coupler_elliptical_uniform

grating_coupler_elliptical_uniform(
    n_periods: int = 20,
    period: float = 0.75,
    fill_factor: float = 0.5,
    **kwargs: Any
) -> Component

Grating coupler with parametrization based on Lumerical FDTD simulation.

The ellipticity is derived from Lumerical knowledge base it depends on fiber_angle (degrees), neff, and nclad

Parameters:

Name Type Description Default
n_periods int

number of grating periods.

20
period float

grating pitch in um.

0.75
fill_factor float

ratio of grating width vs gap.

0.5

Other Parameters:

Name Type Description
taper_length

taper length from input.

taper_angle

grating flare angle.

wavelength

grating transmission central wavelength (um).

fiber_angle

fibre angle in degrees determines ellipticity.

neff

tooth effective index to compute ellipticity.

nclad

cladding effective index to compute ellipticity.

layer_slab

Optional slab.

taper_to_slab_offset

where 0 is at the start of the taper.

polarization

te or tm.

spiked

grating teeth have spikes to avoid drc errors..

bias_gap

etch gap (um). Positive bias increases gap and reduces width to keep period constant.

cross_section

cross_section spec for waveguide port.

kwargs Any

cross_section settings.

      fiber

   /  /  /  /
  /  /  /  /

_|-|_|-|_|-|___ layer
   layer_slab |

o1 __|

Source code in gdsfactory/components/grating_couplers/grating_coupler_elliptical_arbitrary.py
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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_elliptical_uniform(
    n_periods: int = 20,
    period: float = 0.75,
    fill_factor: float = 0.5,
    **kwargs: Any,
) -> Component:
    r"""Grating coupler with parametrization based on Lumerical FDTD simulation.

    The ellipticity is derived from Lumerical knowledge base
    it depends on fiber_angle (degrees), neff, and nclad

    Args:
        n_periods: number of grating periods.
        period: grating pitch in um.
        fill_factor: ratio of grating width vs gap.

    Keyword Args:
        taper_length: taper length from input.
        taper_angle: grating flare angle.
        wavelength: grating transmission central wavelength (um).
        fiber_angle: fibre angle in degrees determines ellipticity.
        neff: tooth effective index to compute ellipticity.
        nclad: cladding effective index to compute ellipticity.
        layer_slab: Optional slab.
        taper_to_slab_offset: where 0 is at the start of the taper.
        polarization: te or tm.
        spiked: grating teeth have spikes to avoid drc errors..
        bias_gap: etch gap (um).
            Positive bias increases gap and reduces width to keep period constant.
        cross_section: cross_section spec for waveguide port.
        kwargs: cross_section settings.

                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___ layer
                   layer_slab |
            o1  ______________|

    """
    widths = (period * fill_factor,) * n_periods
    gaps = (period * (1 - fill_factor),) * n_periods
    return grating_coupler_elliptical_arbitrary(gaps=gaps, widths=widths, **kwargs)

grating_coupler_elliptical_arbitrary

grating_coupler_elliptical_lumerical

grating_coupler_elliptical_lumerical

grating_coupler_elliptical_lumerical(
    parameters: Floats = parameters,
    layer_slab: LayerSpec | None = "SLAB150",
    taper_angle: float = 55,
    taper_length: float = 12.24 + 0.36,
    fiber_angle: float = 5,
    info: dict[str, Any] | None = None,
    bias_gap: float = 0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns a grating coupler from lumerical inverse design 3D optimization.

this is a wrapper of components.grating_coupler_elliptical_arbitrary https://support.lumerical.com/hc/en-us/articles/1500000306621 https://support.lumerical.com/hc/en-us/articles/360042800573

Here are the simulation settings used in lumerical

n_bg=1.44401 #Refractive index of the background material (cladding)
wg=3.47668   # Refractive index of the waveguide material (core)
lambda0=1550e-9
bandwidth = 0e-9
polarization = 'TE'
wg_width=500e-9 # Waveguide width
wg_height=220e-9 # Waveguide height
etch_depth=80e-9 # etch depth
theta_fib_mat = 5 # Angle of the fiber mode in material
theta_taper=30
efficiency=0.55 # 5.2 dB

Parameters:

Name Type Description Default
parameters Floats

xinput, gap1, width1, gap2, width2 ...

parameters
layer

for waveguide.

required
layer_slab LayerSpec | None

for slab.

'SLAB150'
taper_angle float

in deg.

55
taper_length float

in um.

12.24 + 0.36
fiber_angle float

used to compute ellipticity.

5
info dict[str, Any] | None

optional simulation settings.

None
bias_gap float

gap/trenches bias (um) to compensate for etching bias.

0

Other Parameters:

Name Type Description
taper_length float

taper length from input in um.

taper_angle float

grating flare angle in degrees.

wavelength

grating transmission central wavelength (um).

fiber_angle float

fibre angle in degrees determines ellipticity.

neff

tooth effective index.

nclad

cladding effective index.

polarization

te or tm.

spiked

grating teeth include sharp spikes to avoid non-manhattan drc errors.

cross_section CrossSectionSpec

cross_section spec for waveguide port.

Source code in gdsfactory/components/grating_couplers/grating_coupler_elliptical_lumerical.py
 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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_elliptical_lumerical(
    parameters: Floats = parameters,
    layer_slab: LayerSpec | None = "SLAB150",
    taper_angle: float = 55,
    taper_length: float = 12.24 + 0.36,
    fiber_angle: float = 5,
    info: dict[str, Any] | None = None,
    bias_gap: float = 0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns a grating coupler from lumerical inverse design 3D optimization.

    this is a wrapper of components.grating_coupler_elliptical_arbitrary
    https://support.lumerical.com/hc/en-us/articles/1500000306621
    https://support.lumerical.com/hc/en-us/articles/360042800573

    Here are the simulation settings used in lumerical

        n_bg=1.44401 #Refractive index of the background material (cladding)
        wg=3.47668   # Refractive index of the waveguide material (core)
        lambda0=1550e-9
        bandwidth = 0e-9
        polarization = 'TE'
        wg_width=500e-9 # Waveguide width
        wg_height=220e-9 # Waveguide height
        etch_depth=80e-9 # etch depth
        theta_fib_mat = 5 # Angle of the fiber mode in material
        theta_taper=30
        efficiency=0.55 # 5.2 dB

    Args:
        parameters: xinput, gap1, width1, gap2, width2 ...
        layer: for waveguide.
        layer_slab: for slab.
        taper_angle: in deg.
        taper_length: in um.
        fiber_angle: used to compute ellipticity.
        info: optional simulation settings.
        bias_gap: gap/trenches bias (um) to compensate for etching bias.

    Keyword Args:
        taper_length: taper length from input in um.
        taper_angle: grating flare angle in degrees.
        wavelength: grating transmission central wavelength (um).
        fiber_angle: fibre angle in degrees determines ellipticity.
        neff: tooth effective index.
        nclad: cladding effective index.
        polarization: te or tm.
        spiked: grating teeth include sharp spikes to avoid non-manhattan drc errors.
        cross_section: cross_section spec for waveguide port.
    """
    parameters = tuple(parameters)
    xinput = parameters[0]
    teeth_list = parameters[1:]
    gaps = teeth_list[::2]
    widths = teeth_list[1::2]
    info = info or {}
    gaps = tuple(gap + bias_gap for gap in gaps)

    c = grating_coupler_elliptical_arbitrary(
        gaps=gaps,
        widths=widths,
        taper_angle=taper_angle,
        taper_length=taper_length,
        layer_slab=layer_slab,
        fiber_angle=fiber_angle,
        cross_section=cross_section,
    )
    c.info.update(info)
    c.info["xinput"] = xinput
    return c

grating_coupler_elliptical_lumerical

grating_coupler_elliptical_lumerical_etch70 module-attribute

grating_coupler_elliptical_lumerical_etch70 = partial(
    grating_coupler_elliptical_lumerical,
    info=dict(
        etch_depth=0.08,
        link="https://support.lumerical.com/hc/en-us/articles/1500000306621",
        fiber_angle=5,
        width_min=0.1,
        gap_min=0.1,
        efficiency=0.55,
    ),
)

grating_coupler_elliptical_lumerical_etch70

grating_coupler_elliptical_trenches

grating_coupler_elliptical_trenches

grating_coupler_elliptical_trenches(
    polarization: str = "te",
    taper_length: float = 16.6,
    taper_angle: float = 30.0,
    trenches_extra_angle: float = 9.0,
    wavelength: float = 1.53,
    fiber_angle: float = 15.0,
    grating_line_width: float = 0.343,
    neff: float = 2.638,
    ncladding: float = 1.443,
    layer_trench: LayerSpec = "SHALLOW_ETCH",
    p_start: int = 26,
    n_periods: int = 30,
    end_straight_length: float = 0.2,
    taper: ComponentSpec = "taper",
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns Grating coupler with defined trenches.

Some foundries define the grating coupler by a shallow etch step (trenches) Others define the slab that they keep (see grating_coupler_elliptical)

Parameters:

Name Type Description Default
polarization str

'te' or 'tm'.

'te'
taper_length float

taper length from straight I/O.

16.6
taper_angle float

grating flare angle.

30.0
trenches_extra_angle float

extra angle for the trenches.

9.0
wavelength float

grating transmission central wavelength.

1.53
fiber_angle float

fibre polish angle in degrees.

15.0
grating_line_width float

of the 220 ridge.

0.343
neff float

tooth effective index.

2.638
ncladding float

cladding index.

1.443
layer_trench LayerSpec

for the trench.

'SHALLOW_ETCH'
p_start int

first tooth.

26
n_periods int

number of grating teeth.

30
end_straight_length float

at the end of straight.

0.2
taper ComponentSpec

taper function.

'taper'
cross_section CrossSectionSpec

cross_section spec.

      fiber

   /  /  /  /
  /  /  /  /
_|-|_|-|_|-|___
'strip'
Source code in gdsfactory/components/grating_couplers/grating_coupler_elliptical_trenches.py
 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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_elliptical_trenches(
    polarization: str = "te",
    taper_length: float = 16.6,
    taper_angle: float = 30.0,
    trenches_extra_angle: float = 9.0,
    wavelength: float = 1.53,
    fiber_angle: float = 15.0,
    grating_line_width: float = 0.343,
    neff: float = 2.638,  # tooth effective index
    ncladding: float = 1.443,  # cladding index
    layer_trench: LayerSpec = "SHALLOW_ETCH",
    p_start: int = 26,
    n_periods: int = 30,
    end_straight_length: float = 0.2,
    taper: ComponentSpec = "taper",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Returns Grating coupler with defined trenches.

    Some foundries define the grating coupler by a shallow etch step (trenches)
    Others define the slab that they keep (see grating_coupler_elliptical)

    Args:
        polarization: 'te' or 'tm'.
        taper_length: taper length from straight I/O.
        taper_angle: grating flare angle.
        trenches_extra_angle: extra angle for the trenches.
        wavelength: grating transmission central wavelength.
        fiber_angle: fibre polish angle in degrees.
        grating_line_width: of the 220 ridge.
        neff: tooth effective index.
        ncladding: cladding index.
        layer_trench: for the trench.
        p_start: first tooth.
        n_periods: number of grating teeth.
        end_straight_length: at the end of straight.
        taper: taper function.
        cross_section: cross_section spec.

                      fiber

                   /  /  /  /
                  /  /  /  /
                _|-|_|-|_|-|___
        WG  o1  ______________|

    """
    xs = gf.get_cross_section(cross_section)
    wg_width = xs.width
    layer = xs.layer

    # Compute some ellipse parameters
    sthc = np.sin(fiber_angle * DEG2RAD)
    d = neff**2 - ncladding**2 * sthc**2
    a1 = wavelength * neff / d
    b1 = wavelength / np.sqrt(d)
    x1 = wavelength * ncladding * sthc / d

    a1 = round(a1, 3)
    b1 = round(b1, 3)
    x1 = round(x1, 3)

    period = float(a1 + x1)
    trench_line_width = period - grating_line_width

    c = gf.Component()

    # Make each grating line
    for p in range(p_start, p_start + n_periods + 1):
        pts = grating_tooth_points(
            p * a1,
            p * b1,
            p * x1,
            width=trench_line_width,
            taper_angle=taper_angle + trenches_extra_angle,
        )
        c.add_polygon(pts, layer_trench)

    # Make the taper
    p_taper = p_start - 1
    p_taper_eff = p_taper
    a_taper = a1 * p_taper_eff
    # b_taper = b1 * p_taper_eff
    x_taper = x1 * p_taper_eff
    x_output = a_taper + x_taper - taper_length + grating_line_width / 2

    xmax = x_output + taper_length + n_periods * period + 3
    y = wg_width / 2 + np.tan(taper_angle / 2 * np.pi / 180) * xmax

    taper_length2 = (xmax + end_straight_length) - x_output
    taper_component = c << gf.get_component(
        taper,
        width1=wg_width,
        width2=2 * y,
        length=taper_length2,
        cross_section=cross_section,
    )
    taper_component.xmin = x_output

    c.add_port(
        name="o1",
        port=taper_component.ports["o1"],
    )
    c.info["period"] = float(np.round(period, 3))
    c.info["polarization"] = polarization
    c.info["wavelength"] = wavelength

    x = np.round(taper_length + period * n_periods / 2, 3)
    c.flatten()
    c.add_port(
        name="o2",
        center=(x, 0),
        width=10,
        orientation=0,
        layer=layer,
        port_type=f"vertical_{polarization}",
    )
    return c

grating_coupler_elliptical_trenches

grating_coupler_elliptical_uniform

grating_coupler_elliptical_uniform(
    n_periods: int = 20,
    period: float = 0.75,
    fill_factor: float = 0.5,
    **kwargs: Any
) -> Component

Grating coupler with parametrization based on Lumerical FDTD simulation.

The ellipticity is derived from Lumerical knowledge base it depends on fiber_angle (degrees), neff, and nclad

Parameters:

Name Type Description Default
n_periods int

number of grating periods.

20
period float

grating pitch in um.

0.75
fill_factor float

ratio of grating width vs gap.

0.5

Other Parameters:

Name Type Description
taper_length

taper length from input.

taper_angle

grating flare angle.

wavelength

grating transmission central wavelength (um).

fiber_angle

fibre angle in degrees determines ellipticity.

neff

tooth effective index to compute ellipticity.

nclad

cladding effective index to compute ellipticity.

layer_slab

Optional slab.

taper_to_slab_offset

where 0 is at the start of the taper.

polarization

te or tm.

spiked

grating teeth have spikes to avoid drc errors..

bias_gap

etch gap (um). Positive bias increases gap and reduces width to keep period constant.

cross_section

cross_section spec for waveguide port.

kwargs Any

cross_section settings.

      fiber

   /  /  /  /
  /  /  /  /

_|-|_|-|_|-|___ layer
   layer_slab |

o1 __|

Source code in gdsfactory/components/grating_couplers/grating_coupler_elliptical_arbitrary.py
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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_elliptical_uniform(
    n_periods: int = 20,
    period: float = 0.75,
    fill_factor: float = 0.5,
    **kwargs: Any,
) -> Component:
    r"""Grating coupler with parametrization based on Lumerical FDTD simulation.

    The ellipticity is derived from Lumerical knowledge base
    it depends on fiber_angle (degrees), neff, and nclad

    Args:
        n_periods: number of grating periods.
        period: grating pitch in um.
        fill_factor: ratio of grating width vs gap.

    Keyword Args:
        taper_length: taper length from input.
        taper_angle: grating flare angle.
        wavelength: grating transmission central wavelength (um).
        fiber_angle: fibre angle in degrees determines ellipticity.
        neff: tooth effective index to compute ellipticity.
        nclad: cladding effective index to compute ellipticity.
        layer_slab: Optional slab.
        taper_to_slab_offset: where 0 is at the start of the taper.
        polarization: te or tm.
        spiked: grating teeth have spikes to avoid drc errors..
        bias_gap: etch gap (um).
            Positive bias increases gap and reduces width to keep period constant.
        cross_section: cross_section spec for waveguide port.
        kwargs: cross_section settings.

                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___ layer
                   layer_slab |
            o1  ______________|

    """
    widths = (period * fill_factor,) * n_periods
    gaps = (period * (1 - fill_factor),) * n_periods
    return grating_coupler_elliptical_arbitrary(gaps=gaps, widths=widths, **kwargs)

grating_coupler_elliptical_uniform

grating_coupler_loss

grating_coupler_loss

grating_coupler_loss(
    pitch: float = 127.0,
    grating_coupler: ComponentSpec = "grating_coupler_elliptical_trenches",
    cross_section: CrossSectionSpec = "strip",
    port_name: str = "o1",
    rotation: float = -90,
    nfibers: int = 10,
    grating_coupler_spacing: float = 5.0,
) -> Component

Grating coupler test structure for de-embeding fiber array.

Connects channel 1->3, 1->5 ... 1->nfibers with grating couplers.

Only odd channels are connected to the grating couplers as even channels in the align_tree.

Parameters:

Name Type Description Default
pitch float

um.

127.0
grating_coupler ComponentSpec

spec.

'grating_coupler_elliptical_trenches'
cross_section CrossSectionSpec

spec.

'strip'
port_name str

for the grating_coupler port.

'o1'
rotation float

degrees.

-90
nfibers int

number of fibers to connect.

10
grating_coupler_spacing float

um.

5.0
Source code in gdsfactory/components/grating_couplers/grating_coupler_loss.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
@gf.cell_with_module_name(tags=["grating_couplers"])
def grating_coupler_loss(
    pitch: float = 127.0,
    grating_coupler: ComponentSpec = "grating_coupler_elliptical_trenches",
    cross_section: CrossSectionSpec = "strip",
    port_name: str = "o1",
    rotation: float = -90,
    nfibers: int = 10,
    grating_coupler_spacing: float = 5.0,
) -> Component:
    """Grating coupler test structure for de-embeding fiber array.

    Connects channel 1->3, 1->5 ... 1->nfibers with grating couplers.

    Only odd channels are connected to the grating couplers as even channels in the align_tree.

    Args:
        pitch: um.
        grating_coupler: spec.
        cross_section: spec.
        port_name: for the grating_coupler port.
        rotation: degrees.
        nfibers: number of fibers to connect.
        grating_coupler_spacing: um.
    """
    gc = gf.get_component(grating_coupler)
    c = gf.Component()
    xmin = 0.0

    for i in range(2, nfibers - 1, 2):
        g1 = c << gc
        g1.rotate(rotation)
        g1.x = xmin

        g2 = c << gc
        g2.rotate(rotation)
        g2.x = xmin + i * pitch

        route_bundle(
            c,
            g1[port_name],
            g2[port_name],
            start_straight_length=40.0,
            cross_section=cross_section,
        )

        xmin = g2.xmax + grating_coupler_spacing + gc.xsize / 2

    return c

grating_coupler_loss

grating_coupler_rectangular

grating_coupler_rectangular

grating_coupler_rectangular(
    n_periods: int = 20,
    period: float = 0.75,
    fill_factor: float = 0.5,
    width_grating: float = 11.0,
    length_taper: float = 150.0,
    polarization: str = "te",
    wavelength: float = 1.55,
    taper: ComponentSpec = "taper",
    layer_slab: LayerSpec | None = "SLAB150",
    layer_grating: LayerSpec | None = None,
    fiber_angle: float = 15,
    slab_xmin: float = -1.0,
    slab_offset: float = 1.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Grating coupler with rectangular shapes (not elliptical).

Needs longer taper than elliptical. Grating teeth are straight. For a focusing grating take a look at grating_coupler_elliptical.

Parameters:

Name Type Description Default
n_periods int

number of grating teeth.

20
period float

grating pitch.

0.75
fill_factor float

ratio of grating width vs gap.

0.5
width_grating float

11.

11.0
length_taper float

150.

150.0
polarization str

'te' or 'tm'.

'te'
wavelength float

in um.

1.55
taper ComponentSpec

function.

'taper'
layer_slab LayerSpec | None

layer that protects the slab under the grating.

'SLAB150'
layer_grating LayerSpec | None

layer for the grating.

None
fiber_angle float

in degrees.

15
slab_xmin float

where 0 is at the start of the taper.

-1.0
slab_offset float

from edge of grating to edge of the slab.

1.0
cross_section CrossSectionSpec

for input waveguide port.

'strip'
Source code in gdsfactory/components/grating_couplers/grating_coupler_rectangular.py
 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
 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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_rectangular(
    n_periods: int = 20,
    period: float = 0.75,
    fill_factor: float = 0.5,
    width_grating: float = 11.0,
    length_taper: float = 150.0,
    polarization: str = "te",
    wavelength: float = 1.55,
    taper: ComponentSpec = "taper",
    layer_slab: LayerSpec | None = "SLAB150",
    layer_grating: LayerSpec | None = None,
    fiber_angle: float = 15,
    slab_xmin: float = -1.0,
    slab_offset: float = 1.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Grating coupler with rectangular shapes (not elliptical).

    Needs longer taper than elliptical.
    Grating teeth are straight.
    For a focusing grating take a look at grating_coupler_elliptical.

    Args:
        n_periods: number of grating teeth.
        period: grating pitch.
        fill_factor: ratio of grating width vs gap.
        width_grating: 11.
        length_taper: 150.
        polarization: 'te' or 'tm'.
        wavelength: in um.
        taper: function.
        layer_slab: layer that protects the slab under the grating.
        layer_grating: layer for the grating.
        fiber_angle: in degrees.
        slab_xmin: where 0 is at the start of the taper.
        slab_offset: from edge of grating to edge of the slab.
        cross_section: for input waveguide port.

        side view
                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___ layer
                   layer_slab |
            o1  ______________|


        top view     _________
                    /| | | | |
                   / | | | | |
                  /taper_angle
                 /_ _| | | | |
        wg_width |   | | | | |
                 \   | | | | |
                  \  | | | | |
                   \ | | | | |
                    \|_|_|_|_|
                 <-->
                taper_length
    """
    xs = gf.get_cross_section(cross_section)
    wg_width = xs.width
    layer = layer_grating or xs.layer
    assert layer is not None

    c = Component()
    taper_ref = c << gf.get_component(
        taper,
        length=length_taper,
        width2=width_grating,
        width1=wg_width,
        cross_section=cross_section,
    )

    c.add_port(port=taper_ref.ports["o1"], name="o1")
    x0 = length_taper
    for i in range(n_periods):
        xsize = gf.snap.snap_to_grid(period * fill_factor)
        cgrating = c.add_ref(
            gf.c.rectangle(size=(xsize, width_grating), layer=layer, port_type=None)
        )
        cgrating.xmin = gf.snap.snap_to_grid(x0 + i * period)
        cgrating.y = 0

    c.info["polarization"] = polarization
    c.info["wavelength"] = wavelength
    c.info["fiber_angle"] = fiber_angle

    if layer_slab:
        slab_xmin = length_taper - slab_offset
        slab_xmax = length_taper + n_periods * period + slab_offset
        slab_ysize = width_grating + 2 * slab_offset
        yslab = slab_ysize / 2
        c.add_polygon(
            [
                (slab_xmin, yslab),
                (slab_xmax, yslab),
                (slab_xmax, -yslab),
                (slab_xmin, -yslab),
            ],
            layer_slab,
        )
    xs.add_bbox(c)
    xport = np.round((x0 + cgrating.x) / 2, 3)
    c.add_port(
        name="o2",
        port_type=f"vertical_{polarization}",
        center=(xport, 0),
        orientation=0,
        width=width_grating,
        layer=layer,
    )
    c.flatten()
    return c

grating_coupler_rectangular

grating_coupler_rectangular_arbitrary

grating_coupler_rectangular_arbitrary

grating_coupler_rectangular_arbitrary(
    gaps: Floats = _gaps,
    widths: Floats = _widths,
    width_grating: float = 11.0,
    length_taper: float = 150.0,
    polarization: str = "te",
    wavelength: float = 1.55,
    layer_grating: LayerSpec | None = None,
    layer_slab: LayerSpec | None = None,
    slab_xmin: float = -1.0,
    slab_offset: float = 1.0,
    fiber_angle: float = 15,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Grating coupler uniform with rectangular shape (not elliptical).

Therefore it needs a longer taper. Grating teeth are straight instead of elliptical.

Parameters:

Name Type Description Default
gaps Floats

list of gaps between grating teeth.

_gaps
widths Floats

list of grating widths.

_widths
width_grating float

grating teeth width.

11.0
length_taper float

taper length (um).

150.0
polarization str

'te' or 'tm'.

'te'
wavelength float

in um.

1.55
layer_grating LayerSpec | None

Optional layer for grating. \ by default None uses cross_section.layer. \ if different from cross_section.layer expands taper.

None
layer_slab LayerSpec | None

layer that protects the slab under the grating.

None
slab_xmin float

where 0 is at the start of the taper.

-1.0
slab_offset float

from edge of grating to edge of the slab.

1.0
fiber_angle float

in degrees.

15
cross_section CrossSectionSpec

for input waveguide port.

      fiber

   /  /  /  /
  /  /  /  /

_|-|_|-|_|-|___ layer
   layer_slab |

o1 __|

'strip'
Source code in gdsfactory/components/grating_couplers/grating_coupler_rectangular_arbitrary.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
 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
@gf.cell_with_module_name(
    schematic_function=grating_coupler_schematic, tags=["grating_couplers"]
)
def grating_coupler_rectangular_arbitrary(
    gaps: Floats = _gaps,
    widths: Floats = _widths,
    width_grating: float = 11.0,
    length_taper: float = 150.0,
    polarization: str = "te",
    wavelength: float = 1.55,
    layer_grating: LayerSpec | None = None,
    layer_slab: LayerSpec | None = None,
    slab_xmin: float = -1.0,
    slab_offset: float = 1.0,
    fiber_angle: float = 15,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Grating coupler uniform with rectangular shape (not elliptical).

    Therefore it needs a longer taper.
    Grating teeth are straight instead of elliptical.

    Args:
        gaps: list of gaps between grating teeth.
        widths: list of grating widths.
        width_grating: grating teeth width.
        length_taper: taper length (um).
        polarization: 'te' or 'tm'.
        wavelength: in um.
        layer_grating: Optional layer for grating. \
                by default None uses cross_section.layer. \
                if different from cross_section.layer expands taper.
        layer_slab: layer that protects the slab under the grating.
        slab_xmin: where 0 is at the start of the taper.
        slab_offset: from edge of grating to edge of the slab.
        fiber_angle: in degrees.
        cross_section: for input waveguide port.

                      fiber

                   /  /  /  /
                  /  /  /  /

                _|-|_|-|_|-|___ layer
                   layer_slab |
            o1  ______________|



        top view     _________
                    /| | | | |
                   / | | | | |
                  /taper_angle
                 /_ _| | | | |
        wg_width |   | | | | |
                 \   | | | | |
                  \  | | | | |
                   \ | | | | |
                    \|_|_|_|_|
                 <-->
                taper_length

    """
    xs = gf.get_cross_section(cross_section)
    assert xs.layer is not None
    layer_wg = gf.get_layer(xs.layer)
    layer_grating = layer_grating or layer_wg

    layer_grating = gf.get_layer(layer_grating)
    c = Component()

    taper_ref = c << taper(
        length=length_taper,
        width1=xs.width,
        width2=width_grating,
        cross_section=cross_section,
    )

    c.add_port(port=taper_ref.ports["o1"], name="o1")
    xi = length_taper

    y0 = width_grating / 2

    for width, gap in zip(widths, gaps, strict=False):
        xi += gap
        points = np.array(
            [
                [xi, -y0],
                [xi, +y0],
                [xi + width, +y0],
                [xi + width, -y0],
            ]
        )
        c.add_polygon(
            points,
            layer_grating,
        )
        xi += width

    if layer_slab:
        slab_xmin = length_taper - slab_offset
        slab_xmax = length_taper + np.sum(widths) + np.sum(gaps) + slab_offset
        slab_ysize = width_grating + 2 * slab_offset
        yslab = slab_ysize / 2
        c.add_polygon(
            [
                (slab_xmin, yslab),
                (slab_xmax, yslab),
                (slab_xmax, -yslab),
                (slab_xmin, -yslab),
            ],
            layer_slab,
        )
    xport = np.round((xi + length_taper) / 2, 3)
    c.add_port(
        name="o2",
        port_type=f"vertical_{polarization}",
        center=(xport, 0),
        orientation=0,
        width=width_grating,
        layer=xs.layer,
    )
    c.info["polarization"] = polarization
    c.info["wavelength"] = wavelength
    c.info["fiber_angle"] = fiber_angle

    xs.add_bbox(c)
    return c

grating_coupler_rectangular_arbitrary

grating_coupler_tree

grating_coupler_tree

grating_coupler_tree(
    n: int = 4,
    straight_spacing: float = 4.0,
    grating_coupler: ComponentSpec = "grating_coupler_elliptical_te",
    with_loopback: bool = False,
    bend: ComponentSpec = "bend_euler",
    fanout_length: float = 0.0,
    cross_section: CrossSectionSpec = "strip",
    **kwargs: Any
) -> Component

Array of straights connected with grating couplers.

useful to align the 4 corners of the chip

Parameters:

Name Type Description Default
n int

number of gratings.

4
straight_spacing float

in um.

4.0
grating_coupler ComponentSpec

spec.

'grating_coupler_elliptical_te'
with_loopback bool

adds loopback.

False
bend ComponentSpec

bend spec.

'bend_euler'
fanout_length float

in um.

0.0
cross_section CrossSectionSpec

cross_section function.

'strip'
kwargs Any

additional arguments.

{}
Source code in gdsfactory/components/grating_couplers/grating_coupler_tree.py
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
@gf.cell_with_module_name(tags=["grating_couplers"])
def grating_coupler_tree(
    n: int = 4,
    straight_spacing: float = 4.0,
    grating_coupler: ComponentSpec = "grating_coupler_elliptical_te",
    with_loopback: bool = False,
    bend: ComponentSpec = "bend_euler",
    fanout_length: float = 0.0,
    cross_section: CrossSectionSpec = "strip",
    **kwargs: Any,
) -> Component:
    """Array of straights connected with grating couplers.

    useful to align the 4 corners of the chip

    Args:
        n: number of gratings.
        straight_spacing: in um.
        grating_coupler: spec.
        with_loopback: adds loopback.
        bend: bend spec.
        fanout_length: in um.
        cross_section: cross_section function.
        kwargs: additional arguments.
    """
    c = gf.c.straight_array(
        n=n,
        spacing=straight_spacing,
    )

    return gf.routing.add_fiber_array(
        component=c,
        with_loopback=with_loopback,
        grating_coupler=grating_coupler,
        fanout_length=fanout_length,
        bend=bend,
        cross_section=cross_section,
        **kwargs,
    )

grating_coupler_tree

mems

anchored_flexure

anchored_flexure

anchored_flexure(
    hinge_width: float = 0.3,
    hinge_length: float = 5.0,
    pad_width: float = 10.0,
    pad_length: float = 10.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

Returns a flexure hinge between two pads.

Two rectangular pads connected by a thin hinge, all centered vertically.

Parameters:

Name Type Description Default
hinge_width float

width of the thin flexure hinge.

0.3
hinge_length float

length of the hinge connecting the two pads.

5.0
pad_width float

width (vertical) of each pad.

10.0
pad_length float

length (horizontal) of each pad.

10.0
layer LayerSpec

layer spec.

'WG'
port_type str

port type for electrical ports.

'electrical'
Source code in gdsfactory/components/mems/anchored_flexure.py
10
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
96
@gf.cell_with_module_name(tags=["mems"])
def anchored_flexure(
    hinge_width: float = 0.3,
    hinge_length: float = 5.0,
    pad_width: float = 10.0,
    pad_length: float = 10.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """Returns a flexure hinge between two pads.

    Two rectangular pads connected by a thin hinge, all centered vertically.

    Args:
        hinge_width: width of the thin flexure hinge.
        hinge_length: length of the hinge connecting the two pads.
        pad_width: width (vertical) of each pad.
        pad_length: length (horizontal) of each pad.
        layer: layer spec.
        port_type: port type for electrical ports.
    """
    c = Component()

    total_length = 2 * pad_length + hinge_length
    x_start = -total_length / 2

    # Left pad
    c.add_polygon(
        [
            (x_start, -pad_width / 2),
            (x_start + pad_length, -pad_width / 2),
            (x_start + pad_length, pad_width / 2),
            (x_start, pad_width / 2),
        ],
        layer=layer,
    )

    # Thin hinge
    hinge_x0 = x_start + pad_length
    c.add_polygon(
        [
            (hinge_x0, -hinge_width / 2),
            (hinge_x0 + hinge_length, -hinge_width / 2),
            (hinge_x0 + hinge_length, hinge_width / 2),
            (hinge_x0, hinge_width / 2),
        ],
        layer=layer,
    )

    # Right pad
    right_x0 = hinge_x0 + hinge_length
    c.add_polygon(
        [
            (right_x0, -pad_width / 2),
            (right_x0 + pad_length, -pad_width / 2),
            (right_x0 + pad_length, pad_width / 2),
            (right_x0, pad_width / 2),
        ],
        layer=layer,
    )

    # Port at left pad outer edge
    c.add_port(
        "e1",
        center=(x_start, 0),
        width=pad_width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )

    # Port at right pad outer edge
    c.add_port(
        "e2",
        center=(right_x0 + pad_length, 0),
        width=pad_width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

anchored_flexure

bent_beam

bent_beam

bent_beam(
    beam_width: float = 1.0,
    beam_length: float = 40.0,
    bend_angle: float = 170.0,
    anchor_width: float = 5.0,
    anchor_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

Returns a V-shaped bent beam thermal actuator.

Two straight beam segments meeting at an apex that points upward, with anchor pads at both ends.

Parameters:

Name Type Description Default
beam_width float

width of the beam.

1.0
beam_length float

length of each beam segment (center-line).

40.0
bend_angle float

angle between the two beam segments in degrees.

170.0
anchor_width float

width (vertical) of each anchor pad.

5.0
anchor_length float

length (horizontal) of each anchor pad.

5.0
layer LayerSpec

layer spec.

'WG'
port_type str

port type for electrical ports.

'electrical'
Source code in gdsfactory/components/mems/bent_beam.py
 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
 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
@gf.cell_with_module_name(tags=["mems"])
def bent_beam(
    beam_width: float = 1.0,
    beam_length: float = 40.0,
    bend_angle: float = 170.0,
    anchor_width: float = 5.0,
    anchor_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """Returns a V-shaped bent beam thermal actuator.

    Two straight beam segments meeting at an apex that points upward,
    with anchor pads at both ends.

    Args:
        beam_width: width of the beam.
        beam_length: length of each beam segment (center-line).
        bend_angle: angle between the two beam segments in degrees.
        anchor_width: width (vertical) of each anchor pad.
        anchor_length: length (horizontal) of each anchor pad.
        layer: layer spec.
        port_type: port type for electrical ports.
    """
    c = Component()

    # The bend_angle is the angle between the two segments.
    # The half-angle from horizontal for each arm:
    half_angle = (180.0 - bend_angle) / 2.0
    half_angle_rad = np.radians(half_angle)

    # Segment projected lengths
    dx = beam_length * np.cos(half_angle_rad)
    dy = beam_length * np.sin(half_angle_rad)

    # Apex at (0, dy), left anchor at (-dx, 0), right anchor at (dx, 0)
    # Build the V-shape as a polygon with width

    # Build as two arms merged into one polygon

    # Left arm direction: from (-dx, 0) to (0, dy)
    left_dir = np.array([dx, dy])
    left_dir = left_dir / np.linalg.norm(left_dir)
    left_norm = np.array([-left_dir[1], left_dir[0]])  # points "up-left"

    # Right arm direction: from (0, dy) to (dx, 0)
    right_dir = np.array([dx, -dy])
    right_dir = right_dir / np.linalg.norm(right_dir)
    right_norm = np.array([-right_dir[1], right_dir[0]])  # points "up-right"

    hw = beam_width / 2

    # Left arm polygon corners
    la_start = np.array([-dx, 0.0])
    la_end = np.array([0.0, dy])

    # Right arm polygon corners
    ra_start = np.array([0.0, dy])
    ra_end = np.array([dx, 0.0])

    # Build the full V as a single polygon (outer contour going clockwise)
    poly_points = [
        tuple(la_start + left_norm * hw),
        tuple(la_end + left_norm * hw),
        tuple(ra_start + right_norm * hw),
        tuple(ra_end + right_norm * hw),
        tuple(ra_end - right_norm * hw),
        tuple(ra_start - right_norm * hw),
        tuple(la_end - left_norm * hw),
        tuple(la_start - left_norm * hw),
    ]

    c.add_polygon(poly_points, layer=layer)

    # Left anchor pad
    c.add_polygon(
        [
            (-dx - anchor_length, -anchor_width / 2),
            (-dx, -anchor_width / 2),
            (-dx, anchor_width / 2),
            (-dx - anchor_length, anchor_width / 2),
        ],
        layer=layer,
    )

    # Right anchor pad
    c.add_polygon(
        [
            (dx, -anchor_width / 2),
            (dx + anchor_length, -anchor_width / 2),
            (dx + anchor_length, anchor_width / 2),
            (dx, anchor_width / 2),
        ],
        layer=layer,
    )

    # Port at left anchor center
    c.add_port(
        "e1",
        center=(-dx - anchor_length, 0),
        width=anchor_width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )

    # Port at right anchor center
    c.add_port(
        "e2",
        center=(dx + anchor_length, 0),
        width=anchor_width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

bent_beam

bolometer

bolometer

bolometer(
    absorber_width: float = 20.0,
    absorber_length: float = 20.0,
    leg_width: float = 0.5,
    leg_length: float = 15.0,
    n_legs: int = 4,
    pad_width: float = 5.0,
    pad_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

Returns a bolometer thermal detector.

A central absorber rectangle with thin L-shaped support legs extending outward to anchor pads distributed around the perimeter.

Parameters:

Name Type Description Default
absorber_width float

width of the central absorber.

20.0
absorber_length float

length of the central absorber.

20.0
leg_width float

width of each support leg.

0.5
leg_length float

length of each support leg.

15.0
n_legs int

number of support legs (distributed around perimeter).

4
pad_width float

width of each anchor pad.

5.0
pad_length float

length of each anchor pad.

5.0
layer LayerSpec

layer spec.

'WG'
port_type str

port type for electrical ports.

'electrical'
Source code in gdsfactory/components/mems/bolometer.py
 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
 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
@gf.cell_with_module_name(tags=["mems"])
def bolometer(
    absorber_width: float = 20.0,
    absorber_length: float = 20.0,
    leg_width: float = 0.5,
    leg_length: float = 15.0,
    n_legs: int = 4,
    pad_width: float = 5.0,
    pad_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """Returns a bolometer thermal detector.

    A central absorber rectangle with thin L-shaped support legs extending
    outward to anchor pads distributed around the perimeter.

    Args:
        absorber_width: width of the central absorber.
        absorber_length: length of the central absorber.
        leg_width: width of each support leg.
        leg_length: length of each support leg.
        n_legs: number of support legs (distributed around perimeter).
        pad_width: width of each anchor pad.
        pad_length: length of each anchor pad.
        layer: layer spec.
        port_type: port type for electrical ports.
    """
    c = Component()

    ahw = absorber_width / 2
    ahl = absorber_length / 2

    # Central absorber rectangle centered at origin
    c.add_polygon(
        [
            (-ahl, -ahw),
            (ahl, -ahw),
            (ahl, ahw),
            (-ahl, ahw),
        ],
        layer=layer,
    )

    # Distribute legs around the absorber perimeter
    # Place legs at evenly spaced angles
    lhw = leg_width / 2

    for i in range(n_legs):
        angle = 2 * np.pi * i / n_legs
        cos_a = np.cos(angle)
        sin_a = np.sin(angle)

        # Determine attachment point on absorber edge
        # Find which edge the ray from center at this angle hits first
        if abs(cos_a) < 1e-10:
            # Vertical ray
            attach_x = 0.0
            attach_y = ahw * np.sign(sin_a)
        elif abs(sin_a) < 1e-10:
            # Horizontal ray
            attach_x = ahl * np.sign(cos_a)
            attach_y = 0.0
        else:
            # Check intersection with vertical edges (x = +/-ahl)
            tx = ahl / abs(cos_a)
            # Check intersection with horizontal edges (y = +/-ahw)
            ty = ahw / abs(sin_a)
            if tx < ty:
                attach_x = ahl * np.sign(cos_a)
                attach_y = tx * sin_a
            else:
                attach_x = ty * cos_a
                attach_y = ahw * np.sign(sin_a)

        # L-shaped leg: first segment goes outward radially, second goes
        # along the tangential direction. For simplicity, use straight legs
        # going outward from attachment point.

        # Determine primary direction (outward from center)
        # Use the dominant axis for the leg direction
        if abs(cos_a) >= abs(sin_a):
            # Horizontal leg
            sign_x = np.sign(cos_a)
            leg_end_x = attach_x + sign_x * leg_length
            leg_end_y = attach_y

            # Horizontal leg segment
            x0 = attach_x
            x1 = leg_end_x
            if x0 > x1:
                x0, x1 = x1, x0

            c.add_polygon(
                [
                    (x0, attach_y - lhw),
                    (x1, attach_y - lhw),
                    (x1, attach_y + lhw),
                    (x0, attach_y + lhw),
                ],
                layer=layer,
            )

            # Pad at end of leg
            pad_cx = leg_end_x + sign_x * pad_length / 2
            pad_cy = leg_end_y
            c.add_polygon(
                [
                    (pad_cx - pad_length / 2, pad_cy - pad_width / 2),
                    (pad_cx + pad_length / 2, pad_cy - pad_width / 2),
                    (pad_cx + pad_length / 2, pad_cy + pad_width / 2),
                    (pad_cx - pad_length / 2, pad_cy + pad_width / 2),
                ],
                layer=layer,
            )

            # Port at outer edge of pad
            port_x = pad_cx + sign_x * pad_length / 2
            c.add_port(
                f"e{i + 1}",
                center=(port_x, pad_cy),
                width=pad_width,
                orientation=0 if sign_x > 0 else 180,
                layer=layer,
                port_type=port_type,
            )
        else:
            # Vertical leg
            sign_y = np.sign(sin_a)
            leg_end_x = attach_x
            leg_end_y = attach_y + sign_y * leg_length

            # Vertical leg segment
            y0 = attach_y
            y1 = leg_end_y
            if y0 > y1:
                y0, y1 = y1, y0

            c.add_polygon(
                [
                    (attach_x - lhw, y0),
                    (attach_x + lhw, y0),
                    (attach_x + lhw, y1),
                    (attach_x - lhw, y1),
                ],
                layer=layer,
            )

            # Pad at end of leg
            pad_cx = leg_end_x
            pad_cy = leg_end_y + sign_y * pad_width / 2
            c.add_polygon(
                [
                    (pad_cx - pad_length / 2, pad_cy - pad_width / 2),
                    (pad_cx + pad_length / 2, pad_cy - pad_width / 2),
                    (pad_cx + pad_length / 2, pad_cy + pad_width / 2),
                    (pad_cx - pad_length / 2, pad_cy + pad_width / 2),
                ],
                layer=layer,
            )

            # Port at outer edge of pad
            port_y = pad_cy + sign_y * pad_width / 2
            c.add_port(
                f"e{i + 1}",
                center=(pad_cx, port_y),
                width=pad_length,
                orientation=90 if sign_y > 0 else 270,
                layer=layer,
                port_type=port_type,
            )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

bolometer

cantilever

cantilever

cantilever(
    beam_width: float = 2.0,
    beam_length: float = 20.0,
    anchor_width: float = 5.0,
    anchor_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

Returns a simple cantilever beam with an anchor.

A rectangular anchor on the left with a thinner beam extending to the right.

Parameters:

Name Type Description Default
beam_width float

width of the cantilever beam.

2.0
beam_length float

length of the cantilever beam.

20.0
anchor_width float

width (vertical) of the anchor pad.

5.0
anchor_length float

length (horizontal) of the anchor pad.

5.0
layer LayerSpec

layer spec.

'WG'
port_type str

port type for electrical ports.

'electrical'
Source code in gdsfactory/components/mems/cantilever.py
10
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
@gf.cell_with_module_name(tags=["mems"])
def cantilever(
    beam_width: float = 2.0,
    beam_length: float = 20.0,
    anchor_width: float = 5.0,
    anchor_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """Returns a simple cantilever beam with an anchor.

    A rectangular anchor on the left with a thinner beam extending to the right.

    Args:
        beam_width: width of the cantilever beam.
        beam_length: length of the cantilever beam.
        anchor_width: width (vertical) of the anchor pad.
        anchor_length: length (horizontal) of the anchor pad.
        layer: layer spec.
        port_type: port type for electrical ports.
    """
    c = Component()

    # Anchor rectangle on the left, centered vertically at y=0
    c.add_polygon(
        [
            (0, -anchor_width / 2),
            (anchor_length, -anchor_width / 2),
            (anchor_length, anchor_width / 2),
            (0, anchor_width / 2),
        ],
        layer=layer,
    )

    # Beam rectangle extending right from anchor, centered vertically at y=0
    c.add_polygon(
        [
            (anchor_length, -beam_width / 2),
            (anchor_length + beam_length, -beam_width / 2),
            (anchor_length + beam_length, beam_width / 2),
            (anchor_length, beam_width / 2),
        ],
        layer=layer,
    )

    # Port at the left edge of the anchor
    c.add_port(
        "e1",
        center=(0, 0),
        width=anchor_width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )

    # Port at the free tip of the beam
    c.add_port(
        "e2",
        center=(anchor_length + beam_length, 0),
        width=beam_width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

cantilever

comb_drive

comb_drive

comb_drive(
    finger_width: float = 0.5,
    finger_length: float = 10.0,
    finger_gap: float = 0.5,
    n_fingers: int = 20,
    finger_overlap: float = 5.0,
    shuttle_width: float = 5.0,
    shuttle_length: float = 30.0,
    spring_width: float = 0.5,
    spring_length: float = 20.0,
    n_spring_folds: int = 4,
    anchor_size: float = 10.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a comb drive actuator with interdigitated fingers and folded springs.

A central shuttle with comb fingers on both sides, fixed electrodes with interleaving fingers, and folded springs connecting the shuttle to corner anchor pads.

Parameters:

Name Type Description Default
finger_width float

width of each comb finger.

0.5
finger_length float

length of each comb finger.

10.0
finger_gap float

gap between adjacent moving and fixed fingers.

0.5
n_fingers int

number of moving fingers on each side.

20
finger_overlap float

overlap length between moving and fixed fingers in the actuation direction.

5.0
shuttle_width float

width (vertical) of the shuttle mass.

5.0
shuttle_length float

length (horizontal) of the shuttle mass.

30.0
spring_width float

width of spring beam segments.

0.5
spring_length float

length of each spring fold segment.

20.0
n_spring_folds int

number of folds in each folded spring.

4
anchor_size float

size of each square anchor pad.

10.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/mems/comb_drive.py
 10
 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
 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
@gf.cell_with_module_name(tags=["mems"])
def comb_drive(
    finger_width: float = 0.5,
    finger_length: float = 10.0,
    finger_gap: float = 0.5,
    n_fingers: int = 20,
    finger_overlap: float = 5.0,
    shuttle_width: float = 5.0,
    shuttle_length: float = 30.0,
    spring_width: float = 0.5,
    spring_length: float = 20.0,
    n_spring_folds: int = 4,
    anchor_size: float = 10.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a comb drive actuator with interdigitated fingers and folded springs.

    A central shuttle with comb fingers on both sides, fixed electrodes
    with interleaving fingers, and folded springs connecting the shuttle
    to corner anchor pads.

    Args:
        finger_width: width of each comb finger.
        finger_length: length of each comb finger.
        finger_gap: gap between adjacent moving and fixed fingers.
        n_fingers: number of moving fingers on each side.
        finger_overlap: overlap length between moving and fixed fingers in the actuation direction.
        shuttle_width: width (vertical) of the shuttle mass.
        shuttle_length: length (horizontal) of the shuttle mass.
        spring_width: width of spring beam segments.
        spring_length: length of each spring fold segment.
        n_spring_folds: number of folds in each folded spring.
        anchor_size: size of each square anchor pad.
        layer: layer spec.
    """
    c = Component()

    shw = shuttle_width / 2
    shl = shuttle_length / 2

    # 1. Shuttle rectangle centered at origin
    c.add_polygon(
        [(-shl, -shw), (shl, -shw), (shl, shw), (-shl, shw)],
        layer=layer,
    )

    # Finger pitch (center-to-center of same-side fingers)
    finger_pitch = 2 * (finger_width + finger_gap)

    # Compute total finger array height
    total_finger_height = n_fingers * finger_pitch
    finger_start_y = -total_finger_height / 2

    # 2. Moving fingers extending left and right from shuttle
    for i in range(n_fingers):
        fy = finger_start_y + i * finger_pitch

        # Right-extending moving fingers
        c.add_polygon(
            [
                (shl, fy),
                (shl + finger_length, fy),
                (shl + finger_length, fy + finger_width),
                (shl, fy + finger_width),
            ],
            layer=layer,
        )

        # Left-extending moving fingers
        c.add_polygon(
            [
                (-shl - finger_length, fy),
                (-shl, fy),
                (-shl, fy + finger_width),
                (-shl - finger_length, fy + finger_width),
            ],
            layer=layer,
        )

    # 3. Fixed electrode bars and interleaving fingers
    fixed_bar_width = shuttle_width
    fixed_bar_x_right = shl + 2 * finger_length - finger_overlap
    fixed_bar_x_left = -fixed_bar_x_right

    # Right fixed electrode bar
    c.add_polygon(
        [
            (fixed_bar_x_right, -total_finger_height / 2 - fixed_bar_width),
            (
                fixed_bar_x_right + shuttle_width,
                -total_finger_height / 2 - fixed_bar_width,
            ),
            (
                fixed_bar_x_right + shuttle_width,
                total_finger_height / 2 + fixed_bar_width,
            ),
            (fixed_bar_x_right, total_finger_height / 2 + fixed_bar_width),
        ],
        layer=layer,
    )

    # Left fixed electrode bar
    c.add_polygon(
        [
            (
                fixed_bar_x_left - shuttle_width,
                -total_finger_height / 2 - fixed_bar_width,
            ),
            (fixed_bar_x_left, -total_finger_height / 2 - fixed_bar_width),
            (fixed_bar_x_left, total_finger_height / 2 + fixed_bar_width),
            (
                fixed_bar_x_left - shuttle_width,
                total_finger_height / 2 + fixed_bar_width,
            ),
        ],
        layer=layer,
    )

    # Fixed fingers interleaving with moving fingers
    for i in range(n_fingers):
        fy = finger_start_y + i * finger_pitch + finger_width + finger_gap

        # Right fixed fingers (extending left from right bar)
        c.add_polygon(
            [
                (fixed_bar_x_right - finger_length, fy),
                (fixed_bar_x_right, fy),
                (fixed_bar_x_right, fy + finger_width),
                (fixed_bar_x_right - finger_length, fy + finger_width),
            ],
            layer=layer,
        )

        # Left fixed fingers (extending right from left bar)
        c.add_polygon(
            [
                (fixed_bar_x_left, fy),
                (fixed_bar_x_left + finger_length, fy),
                (fixed_bar_x_left + finger_length, fy + finger_width),
                (fixed_bar_x_left, fy + finger_width),
            ],
            layer=layer,
        )

    # 4. Folded springs connecting shuttle to anchors (top and bottom)
    spring_fold_pitch = spring_width + finger_gap
    for y_sign in [1, -1]:  # top and bottom
        for x_sign in [1, -1]:  # left and right springs
            # Spring attachment point on shuttle
            attach_x = x_sign * shl * 0.5
            attach_y = y_sign * shw

            # Spring extends in y direction away from shuttle
            spring_dir = y_sign

            # Build folded spring segments
            x_cursor = attach_x
            y_cursor = attach_y

            for fold in range(n_spring_folds):
                # Vertical segment
                y_end = y_cursor + spring_dir * spring_length

                c.add_polygon(
                    [
                        (x_cursor - spring_width / 2, min(y_cursor, y_end)),
                        (x_cursor + spring_width / 2, min(y_cursor, y_end)),
                        (x_cursor + spring_width / 2, max(y_cursor, y_end)),
                        (x_cursor - spring_width / 2, max(y_cursor, y_end)),
                    ],
                    layer=layer,
                )

                # Connecting horizontal segment at the end (if not last fold)
                if fold < n_spring_folds - 1:
                    next_x = x_cursor + x_sign * spring_fold_pitch
                    conn_y = y_end

                    c.add_polygon(
                        [
                            (
                                min(x_cursor, next_x) - spring_width / 2,
                                conn_y - spring_width / 2,
                            ),
                            (
                                max(x_cursor, next_x) + spring_width / 2,
                                conn_y - spring_width / 2,
                            ),
                            (
                                max(x_cursor, next_x) + spring_width / 2,
                                conn_y + spring_width / 2,
                            ),
                            (
                                min(x_cursor, next_x) - spring_width / 2,
                                conn_y + spring_width / 2,
                            ),
                        ],
                        layer=layer,
                    )

                    x_cursor = next_x
                    spring_dir = -spring_dir  # Reverse direction for next fold

                y_cursor = y_end

    # 5. Anchor pads at the four corners
    for x_sign in [1, -1]:
        for y_sign in [1, -1]:
            # Compute where the last spring fold ends
            last_x = (
                x_sign * shl * 0.5 + (n_spring_folds - 1) * x_sign * spring_fold_pitch
            )
            if n_spring_folds % 2 == 1:
                last_y = y_sign * (shw + spring_length)
            else:
                last_y = y_sign * shw

            c.add_polygon(
                [
                    (
                        last_x - anchor_size / 2,
                        last_y - anchor_size / 2 * y_sign + anchor_size * y_sign / 2,
                    ),
                    (
                        last_x + anchor_size / 2,
                        last_y - anchor_size / 2 * y_sign + anchor_size * y_sign / 2,
                    ),
                    (
                        last_x + anchor_size / 2,
                        last_y + anchor_size / 2 * y_sign + anchor_size * y_sign / 2,
                    ),
                    (
                        last_x - anchor_size / 2,
                        last_y + anchor_size / 2 * y_sign + anchor_size * y_sign / 2,
                    ),
                ],
                layer=layer,
            )

    return c

comb_drive

doubly_clamped_beam

doubly_clamped_beam

doubly_clamped_beam(
    beam_width: float = 1.0,
    beam_length: float = 30.0,
    anchor_width: float = 5.0,
    anchor_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

Returns a doubly clamped beam fixed at both ends.

Two anchor pads connected by a thin beam, centered at the origin.

Parameters:

Name Type Description Default
beam_width float

width of the beam.

1.0
beam_length float

length of the beam between the two anchors.

30.0
anchor_width float

width (vertical) of each anchor pad.

5.0
anchor_length float

length (horizontal) of each anchor pad.

5.0
layer LayerSpec

layer spec.

'WG'
port_type str

port type for electrical ports.

'electrical'
Source code in gdsfactory/components/mems/doubly_clamped_beam.py
10
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
96
@gf.cell_with_module_name(tags=["mems"])
def doubly_clamped_beam(
    beam_width: float = 1.0,
    beam_length: float = 30.0,
    anchor_width: float = 5.0,
    anchor_length: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """Returns a doubly clamped beam fixed at both ends.

    Two anchor pads connected by a thin beam, centered at the origin.

    Args:
        beam_width: width of the beam.
        beam_length: length of the beam between the two anchors.
        anchor_width: width (vertical) of each anchor pad.
        anchor_length: length (horizontal) of each anchor pad.
        layer: layer spec.
        port_type: port type for electrical ports.
    """
    c = Component()

    total_length = 2 * anchor_length + beam_length
    x_start = -total_length / 2

    # Left anchor
    c.add_polygon(
        [
            (x_start, -anchor_width / 2),
            (x_start + anchor_length, -anchor_width / 2),
            (x_start + anchor_length, anchor_width / 2),
            (x_start, anchor_width / 2),
        ],
        layer=layer,
    )

    # Central beam
    beam_x0 = x_start + anchor_length
    c.add_polygon(
        [
            (beam_x0, -beam_width / 2),
            (beam_x0 + beam_length, -beam_width / 2),
            (beam_x0 + beam_length, beam_width / 2),
            (beam_x0, beam_width / 2),
        ],
        layer=layer,
    )

    # Right anchor
    right_x0 = beam_x0 + beam_length
    c.add_polygon(
        [
            (right_x0, -anchor_width / 2),
            (right_x0 + anchor_length, -anchor_width / 2),
            (right_x0 + anchor_length, anchor_width / 2),
            (right_x0, anchor_width / 2),
        ],
        layer=layer,
    )

    # Port at left anchor outside edge
    c.add_port(
        "e1",
        center=(x_start, 0),
        width=anchor_width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )

    # Port at right anchor outside edge
    c.add_port(
        "e2",
        center=(right_x0 + anchor_length, 0),
        width=anchor_width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

doubly_clamped_beam

folded_spring

folded_spring

folded_spring(
    beam_width: float = 0.5,
    beam_length: float = 20.0,
    n_folds: int = 4,
    fold_gap: float = 1.0,
    anchor_width: float = 5.0,
    anchor_length: float = 3.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

Returns a folded flexure spring (serpentine meander).

Alternating horizontal beams connected at their ends forming a serpentine pattern. Starts at a bottom anchor and ends at a top anchor.

Parameters:

Name Type Description Default
beam_width float

width of each beam segment.

0.5
beam_length float

length of each horizontal beam segment.

20.0
n_folds int

number of horizontal beam segments.

4
fold_gap float

vertical gap between adjacent beams.

1.0
anchor_width float

width (horizontal) of the anchor pads.

5.0
anchor_length float

length (vertical) of the anchor pads.

3.0
layer LayerSpec

layer spec.

'WG'
port_type str

port type for electrical ports.

'electrical'
Source code in gdsfactory/components/mems/folded_spring.py
 10
 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
 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
@gf.cell_with_module_name(tags=["mems"])
def folded_spring(
    beam_width: float = 0.5,
    beam_length: float = 20.0,
    n_folds: int = 4,
    fold_gap: float = 1.0,
    anchor_width: float = 5.0,
    anchor_length: float = 3.0,
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """Returns a folded flexure spring (serpentine meander).

    Alternating horizontal beams connected at their ends forming a
    serpentine pattern. Starts at a bottom anchor and ends at a top anchor.

    Args:
        beam_width: width of each beam segment.
        beam_length: length of each horizontal beam segment.
        n_folds: number of horizontal beam segments.
        fold_gap: vertical gap between adjacent beams.
        anchor_width: width (horizontal) of the anchor pads.
        anchor_length: length (vertical) of the anchor pads.
        layer: layer spec.
        port_type: port type for electrical ports.
    """
    c = Component()

    # Bottom anchor, centered horizontally at x=0
    anchor_bottom_y = -anchor_length
    c.add_polygon(
        [
            (-anchor_width / 2, anchor_bottom_y),
            (anchor_width / 2, anchor_bottom_y),
            (anchor_width / 2, 0),
            (-anchor_width / 2, 0),
        ],
        layer=layer,
    )

    # Draw serpentine beams
    # Each beam is horizontal. Even-indexed beams go from x=0 to x=beam_length,
    # odd-indexed beams go from x=0 to x=beam_length as well, but connected
    # on alternating sides.
    y_cursor = 0.0

    for i in range(n_folds):
        y_bottom = y_cursor
        y_top = y_cursor + beam_width

        if i % 2 == 0:
            # Beam extends to the right from x=0
            c.add_polygon(
                [
                    (0, y_bottom),
                    (beam_length, y_bottom),
                    (beam_length, y_top),
                    (0, y_top),
                ],
                layer=layer,
            )
        else:
            # Beam extends to the left from x=beam_length
            c.add_polygon(
                [
                    (0, y_bottom),
                    (beam_length, y_bottom),
                    (beam_length, y_top),
                    (0, y_top),
                ],
                layer=layer,
            )

        # Add connecting segment at the end to the next beam
        if i < n_folds - 1:
            conn_y_bottom = y_top
            conn_y_top = y_top + fold_gap

            if i % 2 == 0:
                # Connect on the right side
                c.add_polygon(
                    [
                        (beam_length - beam_width, conn_y_bottom),
                        (beam_length, conn_y_bottom),
                        (beam_length, conn_y_top),
                        (beam_length - beam_width, conn_y_top),
                    ],
                    layer=layer,
                )
            else:
                # Connect on the left side
                c.add_polygon(
                    [
                        (0, conn_y_bottom),
                        (beam_width, conn_y_bottom),
                        (beam_width, conn_y_top),
                        (0, conn_y_top),
                    ],
                    layer=layer,
                )

        y_cursor += beam_width + fold_gap

    # Top anchor
    top_y = y_cursor - fold_gap  # top of last beam
    # Determine x position of top anchor based on last beam's exit side
    if (n_folds - 1) % 2 == 0:
        # Last beam exits on right side (x=beam_length)
        anchor_top_x = beam_length
    else:
        # Last beam exits on left side (x=0)
        anchor_top_x = 0.0

    c.add_polygon(
        [
            (anchor_top_x - anchor_width / 2, top_y),
            (anchor_top_x + anchor_width / 2, top_y),
            (anchor_top_x + anchor_width / 2, top_y + anchor_length),
            (anchor_top_x - anchor_width / 2, top_y + anchor_length),
        ],
        layer=layer,
    )

    # Port at bottom anchor
    c.add_port(
        "e1",
        center=(0, anchor_bottom_y),
        width=anchor_width,
        orientation=270,
        layer=layer,
        port_type=port_type,
    )

    # Port at top anchor
    c.add_port(
        "e2",
        center=(anchor_top_x, top_y + anchor_length),
        width=anchor_width,
        orientation=90,
        layer=layer,
        port_type=port_type,
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

folded_spring

gear

gear

gear(
    n_teeth: int = 20,
    module_size: float = 2.0,
    pressure_angle: float = 20.0,
    hub_radius: float | None = None,
    hub_hole_radius: float = 0.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a gear with simplified trapezoidal teeth.

Parameters:

Name Type Description Default
n_teeth int

number of teeth.

20
module_size float

gear module (pitch diameter / number of teeth).

2.0
pressure_angle float

pressure angle in degrees.

20.0
hub_radius float | None

radius of the central hub disc. Defaults to root_radius * 0.6.

None
hub_hole_radius float

radius of a center hole (0 to disable).

0.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/mems/gear.py
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
@gf.cell_with_module_name(tags=["mems"])
def gear(
    n_teeth: int = 20,
    module_size: float = 2.0,
    pressure_angle: float = 20.0,
    hub_radius: float | None = None,
    hub_hole_radius: float = 0.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a gear with simplified trapezoidal teeth.

    Args:
        n_teeth: number of teeth.
        module_size: gear module (pitch diameter / number of teeth).
        pressure_angle: pressure angle in degrees.
        hub_radius: radius of the central hub disc. Defaults to root_radius * 0.6.
        hub_hole_radius: radius of a center hole (0 to disable).
        layer: layer spec.
    """
    c = Component()

    pitch_radius = n_teeth * module_size / 2
    outer_radius = pitch_radius + module_size
    root_radius = pitch_radius - 1.25 * module_size

    if hub_radius is None:
        hub_radius = root_radius * 0.6

    pa_rad = np.radians(pressure_angle)

    # Angular pitch (full tooth + gap)
    angular_pitch = 2 * np.pi / n_teeth
    # Half-tooth angular width at pitch circle
    half_tooth_angle = angular_pitch / 4

    # Build gear outline as polygon points
    points = []

    for i in range(n_teeth):
        theta = i * angular_pitch

        # Tooth tip is narrower, root is wider based on pressure angle
        tip_half_angle = half_tooth_angle - np.tan(pa_rad) * module_size / pitch_radius
        root_half_angle = (
            half_tooth_angle + np.tan(pa_rad) * 1.25 * module_size / pitch_radius
        )

        # Root start (leading edge)
        a = theta - root_half_angle
        points.append((root_radius * np.cos(a), root_radius * np.sin(a)))

        # Tooth tip leading edge
        a = theta - tip_half_angle
        points.append((outer_radius * np.cos(a), outer_radius * np.sin(a)))

        # Tooth tip trailing edge
        a = theta + tip_half_angle
        points.append((outer_radius * np.cos(a), outer_radius * np.sin(a)))

        # Root end (trailing edge)
        a = theta + root_half_angle
        points.append((root_radius * np.cos(a), root_radius * np.sin(a)))

    c.add_polygon(points, layer=layer)

    # Hub disc
    n_hub_pts = 64
    hub_angles = np.linspace(0, 2 * np.pi, n_hub_pts, endpoint=False)
    hub_points = [(hub_radius * np.cos(a), hub_radius * np.sin(a)) for a in hub_angles]
    c.add_polygon(hub_points, layer=layer)

    # Center hole cutout
    if hub_hole_radius > 0:
        n_hole_pts = 64
        hole_angles = np.linspace(0, 2 * np.pi, n_hole_pts, endpoint=False)
        hole_points = [
            (hub_hole_radius * np.cos(a), hub_hole_radius * np.sin(a))
            for a in hole_angles
        ]
        c.add_polygon(hole_points, layer=layer)

    return c

gear

microfluidics

arrow_junction

arrow_junction

arrow_junction(
    main_width: float = 1.0,
    branch_width: float = 1.0,
    main_length: float = 20.0,
    branch_length: float = 10.0,
    branch_angle: float = 35.0,
    reservoir_radius: float = 0.0,
    n_reservoir_points: int = 64,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component

Returns a microfluidic arrow (Y) junction.

A main horizontal channel on the right side with two angled branches diverging to the left at +/- branch_angle from horizontal. The junction point is at the origin.

Parameters:

Name Type Description Default
main_width float

width of the main horizontal channel.

1.0
branch_width float

width of each angled branch channel.

1.0
main_length float

length of the main channel (extends to the right).

20.0
branch_length float

length of each angled branch.

10.0
branch_angle float

angle of each branch from horizontal (degrees).

35.0
reservoir_radius float

radius of circular reservoirs at endpoints (0 to disable).

0.0
n_reservoir_points int

number of polygon points for reservoir circles.

64
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

'optical'
Source code in gdsfactory/components/microfluidics/arrow_junction.py
 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
 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
@gf.cell_with_module_name(tags=["microfluidics"])
def arrow_junction(
    main_width: float = 1.0,
    branch_width: float = 1.0,
    main_length: float = 20.0,
    branch_length: float = 10.0,
    branch_angle: float = 35.0,
    reservoir_radius: float = 0.0,
    n_reservoir_points: int = 64,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component:
    """Returns a microfluidic arrow (Y) junction.

    A main horizontal channel on the right side with two angled branches
    diverging to the left at +/- branch_angle from horizontal. The
    junction point is at the origin.

    Args:
        main_width: width of the main horizontal channel.
        branch_width: width of each angled branch channel.
        main_length: length of the main channel (extends to the right).
        branch_length: length of each angled branch.
        branch_angle: angle of each branch from horizontal (degrees).
        reservoir_radius: radius of circular reservoirs at endpoints (0 to disable).
        n_reservoir_points: number of polygon points for reservoir circles.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    c = Component()
    mhw = main_width / 2
    bhw = branch_width / 2
    angle_rad = np.radians(branch_angle)

    # Main horizontal channel from origin going right
    c.add_polygon(
        [
            (0, -mhw),
            (main_length, -mhw),
            (main_length, mhw),
            (0, mhw),
        ],
        layer=layer,
    )

    # Direction vectors for each branch (going to the left)
    for sign in [1, -1]:
        a = sign * angle_rad
        # Branch direction (pointing left/away from junction)
        dx = -np.cos(a)
        dy = np.sin(a)
        # Perpendicular to branch direction
        nx = -dy
        ny = dx

        # Four corners of branch rectangle
        x0 = 0.0
        y0 = 0.0
        x1 = x0 + branch_length * dx
        y1 = y0 + branch_length * dy

        pts = [
            (x0 + bhw * nx, y0 + bhw * ny),
            (x0 - bhw * nx, y0 - bhw * ny),
            (x1 - bhw * nx, y1 - bhw * ny),
            (x1 + bhw * nx, y1 + bhw * ny),
        ]
        c.add_polygon(pts, layer=layer)

    # Branch endpoint positions
    upper_end_x = -branch_length * np.cos(angle_rad)
    upper_end_y = branch_length * np.sin(angle_rad)
    lower_end_x = -branch_length * np.cos(angle_rad)
    lower_end_y = -branch_length * np.sin(angle_rad)

    # Reservoir circles at endpoints
    if reservoir_radius > 0:
        angles = np.linspace(0, 2 * np.pi, n_reservoir_points, endpoint=False)
        for cx, cy in [
            (main_length, 0),
            (upper_end_x, upper_end_y),
            (lower_end_x, lower_end_y),
        ]:
            pts = [
                (cx + reservoir_radius * np.cos(a), cy + reservoir_radius * np.sin(a))
                for a in angles
            ]
            c.add_polygon(pts, layer=layer)

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        # Main end (right)
        c.add_port(
            f"{prefix}1",
            center=(main_length, 0),
            width=main_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
        # Upper branch end
        c.add_port(
            f"{prefix}2",
            center=(upper_end_x, upper_end_y),
            width=branch_width,
            orientation=180 + branch_angle,
            layer=layer,
            port_type=port_type,
        )
        # Lower branch end
        c.add_port(
            f"{prefix}3",
            center=(lower_end_x, lower_end_y),
            width=branch_width,
            orientation=180 - branch_angle,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()

    return c

arrow_junction

h_junction

h_junction

h_junction(
    main_width: float = 1.0,
    branch_width: float = 1.0,
    main_length: float = 20.0,
    branch_length: float = 10.0,
    reservoir_radius: float = 0.0,
    n_reservoir_points: int = 64,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component

Returns a microfluidic H-junction.

A horizontal main channel with two vertical branches extending up and down from the center. Optionally adds circular reservoirs at the four endpoints.

Parameters:

Name Type Description Default
main_width float

width of the main horizontal channel.

1.0
branch_width float

width of the vertical branch channels.

1.0
main_length float

total length of the main channel.

20.0
branch_length float

length of each vertical branch.

10.0
reservoir_radius float

radius of circular reservoirs at endpoints (0 to disable).

0.0
n_reservoir_points int

number of polygon points for reservoir circles.

64
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

'optical'
Source code in gdsfactory/components/microfluidics/h_junction.py
 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
 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
@gf.cell_with_module_name(tags=["microfluidics"])
def h_junction(
    main_width: float = 1.0,
    branch_width: float = 1.0,
    main_length: float = 20.0,
    branch_length: float = 10.0,
    reservoir_radius: float = 0.0,
    n_reservoir_points: int = 64,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component:
    """Returns a microfluidic H-junction.

    A horizontal main channel with two vertical branches extending
    up and down from the center. Optionally adds circular reservoirs
    at the four endpoints.

    Args:
        main_width: width of the main horizontal channel.
        branch_width: width of the vertical branch channels.
        main_length: total length of the main channel.
        branch_length: length of each vertical branch.
        reservoir_radius: radius of circular reservoirs at endpoints (0 to disable).
        n_reservoir_points: number of polygon points for reservoir circles.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    c = Component()
    mhw = main_width / 2
    bhw = branch_width / 2
    half_main = main_length / 2

    # Main horizontal channel centered at origin
    c.add_polygon(
        [
            (-half_main, -mhw),
            (half_main, -mhw),
            (half_main, mhw),
            (-half_main, mhw),
        ],
        layer=layer,
    )

    # Vertical branch going up from center
    c.add_polygon(
        [
            (-bhw, mhw),
            (bhw, mhw),
            (bhw, mhw + branch_length),
            (-bhw, mhw + branch_length),
        ],
        layer=layer,
    )

    # Vertical branch going down from center
    c.add_polygon(
        [
            (-bhw, -mhw),
            (bhw, -mhw),
            (bhw, -mhw - branch_length),
            (-bhw, -mhw - branch_length),
        ],
        layer=layer,
    )

    # Reservoir circles at endpoints
    if reservoir_radius > 0:
        angles = np.linspace(0, 2 * np.pi, n_reservoir_points, endpoint=False)
        for cx, cy in [
            (-half_main, 0),
            (half_main, 0),
            (0, mhw + branch_length),
            (0, -mhw - branch_length),
        ]:
            pts = [
                (cx + reservoir_radius * np.cos(a), cy + reservoir_radius * np.sin(a))
                for a in angles
            ]
            c.add_polygon(pts, layer=layer)

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            center=(-half_main, 0),
            width=main_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(half_main, 0),
            width=main_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}3",
            center=(0, mhw + branch_length),
            width=branch_width,
            orientation=90,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}4",
            center=(0, -mhw - branch_length),
            width=branch_width,
            orientation=270,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()

    return c

h_junction

meander_channel

meander_channel

meander_channel(
    channel_width: float = 1.0,
    n_turns: int = 5,
    turn_spacing: float = 5.0,
    straight_length: float = 20.0,
    reservoir_length: float = 5.0,
    reservoir_height: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component

Returns a microfluidic meander (serpentine) channel.

Builds a series of connected rectangles forming a serpentine path. Starts from the left, goes right for straight_length, turns up/down by turn_spacing, goes back left, and repeats for n_turns. Rectangular reservoirs are added at the inlet and outlet when reservoir_length > 0.

Parameters:

Name Type Description Default
channel_width float

width of the channel.

1.0
n_turns int

number of straight segments (horizontal passes).

5
turn_spacing float

center-to-center vertical spacing between passes.

5.0
straight_length float

length of each horizontal segment.

20.0
reservoir_length float

length of the reservoirs at inlet/outlet.

5.0
reservoir_height float

height of the reservoirs at inlet/outlet.

5.0
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

'optical'
Source code in gdsfactory/components/microfluidics/meander_channel.py
 10
 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
 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
@gf.cell_with_module_name(tags=["microfluidics"])
def meander_channel(
    channel_width: float = 1.0,
    n_turns: int = 5,
    turn_spacing: float = 5.0,
    straight_length: float = 20.0,
    reservoir_length: float = 5.0,
    reservoir_height: float = 5.0,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component:
    """Returns a microfluidic meander (serpentine) channel.

    Builds a series of connected rectangles forming a serpentine path.
    Starts from the left, goes right for straight_length, turns up/down
    by turn_spacing, goes back left, and repeats for n_turns.
    Rectangular reservoirs are added at the inlet and outlet when
    reservoir_length > 0.

    Args:
        channel_width: width of the channel.
        n_turns: number of straight segments (horizontal passes).
        turn_spacing: center-to-center vertical spacing between passes.
        straight_length: length of each horizontal segment.
        reservoir_length: length of the reservoirs at inlet/outlet.
        reservoir_height: height of the reservoirs at inlet/outlet.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    c = Component()
    hw = channel_width / 2

    x = 0.0
    y = 0.0

    for i in range(n_turns):
        going_right = i % 2 == 0

        # Horizontal segment
        if going_right:
            x_start = x
            x_end = x + straight_length
        else:
            x_start = x
            x_end = x - straight_length

        c.add_polygon(
            [
                (x_start, y - hw),
                (x_end, y - hw),
                (x_end, y + hw),
                (x_start, y + hw),
            ],
            layer=layer,
        )

        if going_right:
            x = x_end
        else:
            x = x_end

        # Vertical connector to next pass (skip after last segment)
        if i < n_turns - 1:
            y_next = y + turn_spacing
            cx = x
            c.add_polygon(
                [
                    (cx - hw, y - hw),
                    (cx + hw, y - hw),
                    (cx + hw, y_next + hw),
                    (cx - hw, y_next + hw),
                ],
                layer=layer,
            )
            y = y_next

    # Determine inlet and outlet positions
    # Inlet is at the start of the first segment
    inlet_x = 0.0
    inlet_y = 0.0

    # Outlet is at the end of the last segment
    last_going_right = (n_turns - 1) % 2 == 0
    outlet_x = x
    outlet_y = y

    # Add reservoirs
    if reservoir_length > 0:
        rh = reservoir_height / 2
        # Inlet reservoir (extends to the left)
        c.add_polygon(
            [
                (inlet_x - reservoir_length, inlet_y - rh),
                (inlet_x, inlet_y - rh),
                (inlet_x, inlet_y + rh),
                (inlet_x - reservoir_length, inlet_y + rh),
            ],
            layer=layer,
        )
        # Outlet reservoir (extends beyond the outlet)
        if last_going_right:
            c.add_polygon(
                [
                    (outlet_x, outlet_y - rh),
                    (outlet_x + reservoir_length, outlet_y - rh),
                    (outlet_x + reservoir_length, outlet_y + rh),
                    (outlet_x, outlet_y + rh),
                ],
                layer=layer,
            )
            outlet_port_x = outlet_x + reservoir_length
            outlet_orientation = 0
        else:
            c.add_polygon(
                [
                    (outlet_x - reservoir_length, outlet_y - rh),
                    (outlet_x, outlet_y - rh),
                    (outlet_x, outlet_y + rh),
                    (outlet_x - reservoir_length, outlet_y + rh),
                ],
                layer=layer,
            )
            outlet_port_x = outlet_x - reservoir_length
            outlet_orientation = 180

        inlet_port_x = inlet_x - reservoir_length
        inlet_port_width = reservoir_height
        outlet_port_width = reservoir_height
    else:
        inlet_port_x = inlet_x
        inlet_port_width = channel_width
        if last_going_right:
            outlet_port_x = outlet_x
            outlet_orientation = 0
        else:
            outlet_port_x = outlet_x
            outlet_orientation = 180
        outlet_port_width = channel_width

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            center=(inlet_port_x, inlet_y),
            width=inlet_port_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(outlet_port_x, outlet_y),
            width=outlet_port_width,
            orientation=outlet_orientation,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()

    return c

meander_channel

t_junction

t_junction

t_junction(
    main_width: float = 1.0,
    branch_width: float = 1.0,
    main_length: float = 20.0,
    branch_length: float = 10.0,
    reservoir_radius: float = 0.0,
    n_reservoir_points: int = 64,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component

Returns a microfluidic T-junction.

A horizontal main channel with a vertical branch going up from the center. Optionally adds circular reservoirs at the three endpoints.

Parameters:

Name Type Description Default
main_width float

width of the main horizontal channel.

1.0
branch_width float

width of the vertical branch channel.

1.0
main_length float

total length of the main channel.

20.0
branch_length float

length of the vertical branch.

10.0
reservoir_radius float

radius of circular reservoirs at endpoints (0 to disable).

0.0
n_reservoir_points int

number of polygon points for reservoir circles.

64
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

'optical'
Source code in gdsfactory/components/microfluidics/t_junction.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@gf.cell_with_module_name(tags=["microfluidics"])
def t_junction(
    main_width: float = 1.0,
    branch_width: float = 1.0,
    main_length: float = 20.0,
    branch_length: float = 10.0,
    reservoir_radius: float = 0.0,
    n_reservoir_points: int = 64,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component:
    """Returns a microfluidic T-junction.

    A horizontal main channel with a vertical branch going up from
    the center. Optionally adds circular reservoirs at the three
    endpoints.

    Args:
        main_width: width of the main horizontal channel.
        branch_width: width of the vertical branch channel.
        main_length: total length of the main channel.
        branch_length: length of the vertical branch.
        reservoir_radius: radius of circular reservoirs at endpoints (0 to disable).
        n_reservoir_points: number of polygon points for reservoir circles.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    c = Component()
    mhw = main_width / 2
    bhw = branch_width / 2
    half_main = main_length / 2

    # Main horizontal channel centered at origin
    c.add_polygon(
        [
            (-half_main, -mhw),
            (half_main, -mhw),
            (half_main, mhw),
            (-half_main, mhw),
        ],
        layer=layer,
    )

    # Vertical branch going up from center
    c.add_polygon(
        [
            (-bhw, mhw),
            (bhw, mhw),
            (bhw, mhw + branch_length),
            (-bhw, mhw + branch_length),
        ],
        layer=layer,
    )

    # Reservoir circles at endpoints
    if reservoir_radius > 0:
        angles = np.linspace(0, 2 * np.pi, n_reservoir_points, endpoint=False)
        for cx, cy in [
            (-half_main, 0),
            (half_main, 0),
            (0, mhw + branch_length),
        ]:
            pts = [
                (cx + reservoir_radius * np.cos(a), cy + reservoir_radius * np.sin(a))
                for a in angles
            ]
            c.add_polygon(pts, layer=layer)

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            center=(-half_main, 0),
            width=main_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(half_main, 0),
            width=main_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}3",
            center=(0, mhw + branch_length),
            width=branch_width,
            orientation=90,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()

    return c

t_junction

mmis

mmi

mmi

mmi(
    inputs: int = 1,
    outputs: int = 4,
    width: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_mmi: float = 5.5,
    width_mmi: float = 5,
    gap_input_tapers: float = 0.25,
    gap_output_tapers: float = 0.25,
    taper: ComponentSpec = "taper",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    input_positions: list[float] | None = None,
    output_positions: list[float] | None = None,
) -> Component

Mxn MultiMode Interferometer (MMI).

Parameters:

Name Type Description Default
inputs int

number of inputs.

1
outputs int

number of outputs.

4
width float | None

input and output straight width. Defaults to cross_section.

None
width_taper float

interface between input straights and mmi region.

1.0
length_taper float

into the mmi region.

10.0
length_mmi float

in x direction.

5.5
width_mmi float

in y direction.

5
gap_input_tapers float

gap between input tapers from edge to edge.

0.25
gap_output_tapers float

gap between output tapers from edge to edge.

0.25
taper ComponentSpec

taper function.

'taper'
straight ComponentSpec

straight function.

'straight'
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
input_positions list[float] | None

optional positions of the inputs.

None
output_positions list[float] | None

optional positions of the outputs.

   length_mmi
    <------>
    ________
   |        |
__/          \__

o2 __ __ o3 \ / _ _ _ | | _ _ _ | gap_output_tapers / _ o1 __ __ o4 \ / |_____| | | <-> length_taper

None
Source code in gdsfactory/components/mmis/mmi.py
 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
 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
@gf.cell_with_module_name(schematic_function=ckt_schematic, tags=["mmis"])
def mmi(
    inputs: int = 1,
    outputs: int = 4,
    width: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_mmi: float = 5.5,
    width_mmi: float = 5,
    gap_input_tapers: float = 0.25,
    gap_output_tapers: float = 0.25,
    taper: ComponentSpec = "taper",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    input_positions: list[float] | None = None,
    output_positions: list[float] | None = None,
) -> Component:
    r"""Mxn MultiMode Interferometer (MMI).

    Args:
        inputs: number of inputs.
        outputs: number of outputs.
        width: input and output straight width. Defaults to cross_section.
        width_taper: interface between input straights and mmi region.
        length_taper: into the mmi region.
        length_mmi: in x direction.
        width_mmi: in y direction.
        gap_input_tapers: gap between input tapers from edge to edge.
        gap_output_tapers: gap between output tapers from edge to edge.
        taper: taper function.
        straight: straight function.
        cross_section: specification (CrossSection, string or dict).
        input_positions: optional positions of the inputs.
        output_positions: optional positions of the outputs.

                   length_mmi
                    <------>
                    ________
                   |        |
                __/          \__
            o2  __            __  o3
                  \          /_ _ _ _
                  |         | _ _ _ _| gap_output_tapers
                __/          \__
            o1  __            __  o4
                  \          /
                   |________|
                 | |
                 <->
            length_taper
    """
    c = Component()
    gap_input_tapers = gf.snap.snap_to_grid(gap_input_tapers, grid_factor=2)
    gap_output_tapers = gf.snap.snap_to_grid(gap_output_tapers, grid_factor=2)
    w_taper = width_taper
    x = gf.get_cross_section(cross_section)
    xs_mmi = gf.get_cross_section(cross_section, width=width_mmi)
    width = width or x.width

    _taper = gf.get_component(
        taper,
        length=length_taper,
        width1=width,
        width2=w_taper,
        cross_section=cross_section,
    )

    _ = c << gf.get_component(straight, length=length_mmi, cross_section=xs_mmi)
    wg_spacing_input = gap_input_tapers + width_taper
    wg_spacing_output = gap_output_tapers + width_taper

    yi = -(inputs - 1) * wg_spacing_input / 2
    yo = -(outputs - 1) * wg_spacing_output / 2

    input_positions = input_positions or [
        yi + i * wg_spacing_input for i in range(inputs)
    ]
    output_positions = output_positions or [
        yo + i * wg_spacing_output for i in range(outputs)
    ]

    temp_component = Component()

    ports = [
        temp_component.add_port(
            name=f"in_{i}",
            orientation=180,
            center=(0, y),
            width=w_taper,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        )
        for i, y in enumerate(input_positions)
    ]

    ports += [
        temp_component.add_port(
            name=f"out_{i}",
            orientation=0,
            center=(+length_mmi, y),
            width=w_taper,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        )
        for i, y in enumerate(output_positions)
    ]

    for port in ports:
        taper_ref = c << _taper
        taper_ref.connect("o2", port, allow_width_mismatch=True)
        c.add_port(name=port.name, port=taper_ref.ports["o1"])

    x.add_bbox(c)
    c.auto_rename_ports()
    c.flatten()
    return c

mmi

mmi1x2

mmi1x2

mmi1x2(
    width: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_mmi: float = 5.5,
    width_mmi: float = 2.5,
    gap_mmi: float = 0.25,
    taper: ComponentSpec = taper_function,
    straight: ComponentSpec = straight_function,
    cross_section: CrossSectionSpec = "strip",
) -> Component

1x2 MultiMode Interferometer (MMI).

Parameters:

Name Type Description Default
width float | None

input and output straight width. Defaults to cross_section width.

None
width_taper float

interface between input straights and mmi region.

1.0
length_taper float

into the mmi region.

10.0
length_mmi float

in x direction.

5.5
width_mmi float

in y direction.

2.5
gap_mmi float

gap between tapered wg.

0.25
taper ComponentSpec

taper function.

taper
straight ComponentSpec

straight function.

straight
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

length_mmi <------> _ | | | _ | __ o2 / / _ _ _ o1 __ | _ _ _ | gap_mmi \ _ | __ o3 | / |_____|

<->

'strip'
Source code in gdsfactory/components/mmis/mmi1x2.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@gf.cell_with_module_name(schematic_function=mmi_1x2_schematic, tags=["mmis"])
def mmi1x2(
    width: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_mmi: float = 5.5,
    width_mmi: float = 2.5,
    gap_mmi: float = 0.25,
    taper: ComponentSpec = taper_function,
    straight: ComponentSpec = straight_function,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""1x2 MultiMode Interferometer (MMI).

    Args:
        width: input and output straight width. Defaults to cross_section width.
        width_taper: interface between input straights and mmi region.
        length_taper: into the mmi region.
        length_mmi: in x direction.
        width_mmi: in y direction.
        gap_mmi:  gap between tapered wg.
        taper: taper function.
        straight: straight function.
        cross_section: specification (CrossSection, string or dict).

               length_mmi
                <------>
                ________
               |        |
               |         \__
               |          __  o2
            __/          /_ _ _ _
         o1 __          | _ _ _ _| gap_mmi
              \          \__
               |          __  o3
               |         /
               |________|

             <->
        length_taper

    """
    c = Component()
    gap_mmi = gf.snap.snap_to_grid(gap_mmi, grid_factor=2)
    x = gf.get_cross_section(cross_section)
    xs_mmi = gf.get_cross_section(cross_section, width=width_mmi)
    width = width or x.width

    _taper = gf.get_component(
        taper,
        length=length_taper,
        width1=width,
        width2=width_taper,
        cross_section=cross_section,
    )

    a = gap_mmi / 2 + width_taper / 2
    _ = c << gf.get_component(straight, length=length_mmi, cross_section=xs_mmi)

    temp_component = Component()

    ports = [
        temp_component.add_port(
            name="o1",
            orientation=180,
            center=(0, 0),
            width=width_taper,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        ),
        temp_component.add_port(
            name="o2",
            orientation=0,
            center=(+length_mmi, +a),
            width=width_taper,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        ),
        temp_component.add_port(
            name="o3",
            orientation=0,
            center=(+length_mmi, -a),
            width=width_taper,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        ),
    ]

    for port in ports:
        taper_ref = c << _taper
        taper_ref.connect(port="o2", other=port, allow_width_mismatch=True)
        c.add_port(name=port.name, port=taper_ref.ports["o1"])

    c.flatten()
    return c

mmi1x2

mmi1x2_with_sbend

mmi1x2_with_sbend

mmi1x2_with_sbend(
    with_sbend: bool = True,
    s_bend: ComponentFactory = bend_s,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns 1x2 splitter for Cband.

https://opg.optica.org/oe/fulltext.cfm?uri=oe-21-1-1310&id=248418

Parameters:

Name Type Description Default
with_sbend bool

add sbend.

True
s_bend ComponentFactory

S-bend spec.

bend_s
cross_section CrossSectionSpec

spec.

'strip'
Source code in gdsfactory/components/mmis/mmi1x2_with_sbend.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
@gf.cell_with_module_name(schematic_function=mmi_1x2_schematic, tags=["mmis"])
def mmi1x2_with_sbend(
    with_sbend: bool = True,
    s_bend: ComponentFactory = bend_s,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns 1x2 splitter for Cband.

    https://opg.optica.org/oe/fulltext.cfm?uri=oe-21-1-1310&id=248418

    Args:
        with_sbend: add sbend.
        s_bend: S-bend spec.
        cross_section: spec.
    """
    c = gf.Component()

    P = gf.path.straight(length=2, npoints=100)
    xs = gf.get_cross_section(cross_section)
    xs0 = xs.copy(width_function=mmi_widths)
    _ = c << gf.path.extrude(P, cross_section=xs0)

    # Add "stub" straight sections for ports
    straight = gf.components.straight(length=0.25, cross_section=cross_section)
    sl = c << straight
    sl.center = (-0.125, 0)
    s_topr = c << straight
    s_topr.center = (2.125, 0.35)
    s_botr = c << straight
    s_botr.center = (2.125, -0.35)

    if with_sbend:
        sbend = s_bend(cross_section=cross_section)
        top_sbend = c << sbend
        bot_sbend = c << sbend
        top_sbend.connect("o1", other=s_topr.ports["o2"])
        bot_sbend.connect("o1", other=s_botr.ports["o2"], mirror=True)
        c.add_port("o1", port=sl.ports["o1"])
        c.add_port("o2", port=top_sbend.ports["o2"])
        c.add_port("o3", port=bot_sbend.ports["o2"])

    else:
        c.add_port("o1", port=sl.ports["o1"])
        c.add_port("o2", port=s_topr.ports["o2"])
        c.add_port("o3", port=s_botr.ports["o2"])

    c.flatten()
    return c

mmi1x2_with_sbend

mmi2x2

mmi2x2

mmi2x2(
    width: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_mmi: float = 5.5,
    width_mmi: float = 2.5,
    gap_mmi: float = 0.25,
    taper: ComponentSpec = taper_function,
    straight: ComponentSpec = straight_function,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Mmi 2x2.

Parameters:

Name Type Description Default
width float | None

input and output straight width.

None
width_taper float

interface between input straights and mmi region.

1.0
length_taper float

into the mmi region.

10.0
length_mmi float

in x direction.

5.5
width_mmi float

in y direction.

2.5
gap_mmi float

(width_taper + gap between tapered wg)/2.

0.25
taper ComponentSpec

taper function.

taper
straight ComponentSpec

straight function.

straight
cross_section CrossSectionSpec

spec.

   length_mmi
    <------>
    ________
   |        |
__/          \__

o2 __ __ o3 \ / _ _ _ | | _ _ _ | gap_mmi / _ o1 __ __ o4 \ / |_____|

 <->

length_taper

'strip'
Source code in gdsfactory/components/mmis/mmi2x2.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
@gf.cell_with_module_name(schematic_function=mmi_2x2_schematic, tags=["mmis"])
def mmi2x2(
    width: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_mmi: float = 5.5,
    width_mmi: float = 2.5,
    gap_mmi: float = 0.25,
    taper: ComponentSpec = taper_function,
    straight: ComponentSpec = straight_function,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Mmi 2x2.

    Args:
        width: input and output straight width.
        width_taper: interface between input straights and mmi region.
        length_taper: into the mmi region.
        length_mmi: in x direction.
        width_mmi: in y direction.
        gap_mmi: (width_taper + gap between tapered wg)/2.
        taper: taper function.
        straight: straight function.
        cross_section: spec.

                   length_mmi
                    <------>
                    ________
                   |        |
                __/          \__
            o2  __            __  o3
                  \          /_ _ _ _
                  |         | _ _ _ _| gap_mmi
                __/          \__
            o1  __            __  o4
                  \          /
                   |________|

                 <->
            length_taper

    """
    c = gf.Component()
    gap_mmi = gf.snap.snap_to_grid(gap_mmi, grid_factor=2)
    w_taper = width_taper
    x = gf.get_cross_section(cross_section)
    width = width or x.width

    _taper = gf.get_component(
        taper,
        length=length_taper,
        width1=width,
        width2=w_taper,
        cross_section=cross_section,
    )

    a = gap_mmi / 2 + width_taper / 2
    _ = c << gf.get_component(
        straight, length=length_mmi, width=width_mmi, cross_section=cross_section
    )

    temp_component = Component()

    ports = [
        temp_component.add_port(
            name="o1", orientation=180, center=(0, -a), width=w_taper, cross_section=x
        ),
        temp_component.add_port(
            name="o2", orientation=180, center=(0, +a), width=w_taper, cross_section=x
        ),
        temp_component.add_port(
            name="o3",
            orientation=0,
            center=(length_mmi, +a),
            width=w_taper,
            cross_section=x,
        ),
        temp_component.add_port(
            name="o4",
            orientation=0,
            center=(length_mmi, -a),
            width=w_taper,
            cross_section=x,
        ),
    ]

    for port in ports:
        taper_ref = c << _taper
        taper_ref.connect(port="o2", other=port, allow_width_mismatch=True)
        c.add_port(name=port.name, port=taper_ref.ports["o1"])

    c.flatten()
    return c

mmi2x2

mmi2x2_with_sbend

mmi2x2_with_sbend

mmi2x2_with_sbend(
    with_sbend: bool = True,
    s_bend: ComponentFactory = bend_s,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns mmi2x2 for Cband.

C_band 2x2MMI in 220nm thick silicon https://opg.optica.org/oe/fulltext.cfm?uri=oe-25-23-28957&id=376719

Parameters:

Name Type Description Default
with_sbend bool

add sbend.

True
s_bend ComponentFactory

S-bend function.

bend_s
cross_section CrossSectionSpec

spec.

'strip'
Source code in gdsfactory/components/mmis/mmi2x2_with_sbend.py
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
@gf.cell_with_module_name(schematic_function=mmi_2x2_schematic, tags=["mmis"])
def mmi2x2_with_sbend(
    with_sbend: bool = True,
    s_bend: ComponentFactory = bend_s,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns mmi2x2 for Cband.

    C_band 2x2MMI in 220nm thick silicon
    https://opg.optica.org/oe/fulltext.cfm?uri=oe-25-23-28957&id=376719

    Args:
        with_sbend: add sbend.
        s_bend: S-bend function.
        cross_section: spec.
    """

    def mmi_widths(t: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
        return np.array([2 * 0.7 + 0.2, 1.48, 1.48, 1.48, 1.6])

    c = gf.Component()

    P = gf.path.straight(length=2 * 2.4 + 2 * 1.6, npoints=5)
    xs = gf.get_cross_section(cross_section)
    xs0 = xs.copy(width_function=mmi_widths)
    _ = c << gf.path.extrude(P, cross_section=xs0)

    # Add input and output tapers
    taper = gf.components.taper(
        length=1, width1=0.5, width2=0.7, cross_section=cross_section
    )
    topl_taper = c << taper
    topl_taper.move((-1, 0.45))
    botl_taper = c << taper
    botl_taper.move((-1, -0.45))

    topr_taper = c << taper
    topr_taper.dmirror(p1=(0, 1), p2=(0, 0))
    topr_taper.move((9, 0.45))

    botr_taper = c << taper
    botr_taper.dmirror(p1=(0, 1), p2=(0, 0))
    botr_taper.move((9, -0.45))

    if with_sbend:
        sbend = s_bend(cross_section=cross_section)

        topl_sbend = c << sbend
        botl_sbend = c << sbend
        topr_sbend = c << sbend
        botr_sbend = c << sbend

        topl_sbend.connect("o1", other=topl_taper.ports["o1"], mirror=True)
        botl_sbend.connect("o1", other=botl_taper.ports["o1"])
        topr_sbend.connect("o1", other=topr_taper.ports["o1"])
        botr_sbend.connect("o1", other=botr_taper.ports["o1"], mirror=True)

        c.add_port("o1", port=botl_sbend.ports["o2"])
        c.add_port("o2", port=topl_sbend.ports["o2"])
        c.add_port("o3", port=topr_sbend.ports["o2"])
        c.add_port("o4", port=botr_sbend.ports["o2"])

    else:
        c.add_port("o2", port=topl_taper.ports["o1"])
        c.add_port("o1", port=botl_taper.ports["o1"])
        c.add_port("o3", port=topr_taper.ports["o1"])
        c.add_port("o4", port=botr_taper.ports["o1"])

    c.flatten()
    return c

mmi2x2_with_sbend

mmi_90degree_hybrid

mmi_90degree_hybrid

mmi_90degree_hybrid(
    width: float = 0.5,
    width_taper: float = 1.7,
    length_taper: float = 40.0,
    length_mmi: float = 175.0,
    width_mmi: float = 10.0,
    gap_mmi: float = 0.8,
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
) -> Component

90 degree hybrid based on a 4x4 MMI.

Default values from Watanabe et al., "Coherent few mode demultiplexer realized as a 2D grating coupler array in silicon", Optics Express 28(24), 2020

It could be interesting to consider the design in Guan et al., "Compact and low loss 90° optical hybrid on a silicon-on-insulator platform", Optics Express 25(23), 2017

Parameters:

Name Type Description Default
width float

input and output straight width.

0.5
width_taper float

interface between input straights and mmi region.

1.7
length_taper float

into the mmi region.

40.0
length_mmi float

in x direction.

175.0
width_mmi float

in y direction.

10.0
gap_mmi float

(width_taper + gap between tapered wg)/2.

0.8
straight ComponentSpec

straight function.

'straight'
with_bbox

box in bbox_layers and bbox_offsets avoid DRC sharp edges.

required
cross_section CrossSectionSpec

spec.

'strip'
               length_mmi
                <------>
                ________
               |        |
            __/          \__
 signal_in  __            __  I_out1
              \          /_ _ _ _
              |         | _ _ _ _| gap_mmi
              |          \__
              |           __  Q_out1
              |          /
              |        |
              |
            __/          \__
    LO_in   __            __  Q_out2
              \          /_ _ _ _
              |         | _ _ _ _| gap_mmi
              |          \__
              |           __  I_out2
              |          /
              | ________|
         <->
    length_taper
Source code in gdsfactory/components/mmis/mmi_90degree_hybrid.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
 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
@gf.cell_with_module_name(schematic_function=ckt_schematic, tags=["mmis"])
def mmi_90degree_hybrid(
    width: float = 0.5,
    width_taper: float = 1.7,
    length_taper: float = 40.0,
    length_mmi: float = 175.0,
    width_mmi: float = 10.0,
    gap_mmi: float = 0.8,
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""90 degree hybrid based on a 4x4 MMI.

    Default values from Watanabe et al.,
    "Coherent few mode demultiplexer realized as a
    2D grating coupler array in silicon", Optics Express 28(24), 2020

    It could be interesting to consider the design in Guan et al.,
    "Compact and low loss 90° optical hybrid on a silicon-on-insulator
    platform", Optics Express 25(23), 2017

    Args:
        width: input and output straight width.
        width_taper: interface between input straights and mmi region.
        length_taper: into the mmi region.
        length_mmi: in x direction.
        width_mmi: in y direction.
        gap_mmi: (width_taper + gap between tapered wg)/2.
        straight: straight function.
        with_bbox: box in bbox_layers and bbox_offsets avoid DRC sharp edges.
        cross_section: spec.

    ```text
                   length_mmi
                    <------>
                    ________
                   |        |
                __/          \__
     signal_in  __            __  I_out1
                  \          /_ _ _ _
                  |         | _ _ _ _| gap_mmi
                  |          \__
                  |           __  Q_out1
                  |          /
                  |        |
                  |
                __/          \__
        LO_in   __            __  Q_out2
                  \          /_ _ _ _
                  |         | _ _ _ _| gap_mmi
                  |          \__
                  |           __  I_out2
                  |          /
                  | ________|
    ```


                 <->
            length_taper
    """
    c = gf.Component()

    gap_mmi = gf.snap.snap_to_grid(gap_mmi, grid_factor=2)
    w_mmi = width_mmi
    w_taper = width_taper

    taper = taper_function(
        length=length_taper,
        width1=width,
        width2=w_taper,
        cross_section=cross_section,
    )

    x = gf.get_cross_section(cross_section)

    _ = c << gf.get_component(
        straight,
        length=length_mmi,
        width=w_mmi,
        cross_section=cross_section,
    )

    y_signal_in = gap_mmi * 3 / 2 + width_taper * 3 / 2
    y_lo_in = -gap_mmi / 2 - width_taper / 2

    temp_component = Component()

    ports = [
        # Inputs
        temp_component.add_port(
            name="signal_in",
            orientation=180,
            center=(0, y_signal_in),
            width=w_taper,
            cross_section=x,
        ),
        temp_component.add_port(
            name="LO_in",
            orientation=180,
            center=(0, y_lo_in),
            width=w_taper,
            cross_section=x,
        ),
        # Outputs
        temp_component.add_port(
            name="I_out1",
            orientation=0,
            center=(length_mmi, y_signal_in),
            width=w_taper,
            cross_section=x,
        ),
        temp_component.add_port(
            name="Q_out1",
            orientation=0,
            center=(length_mmi, y_signal_in - gap_mmi - w_taper),
            width=w_taper,
            cross_section=x,
        ),
        temp_component.add_port(
            name="Q_out2",
            orientation=0,
            center=(length_mmi, y_lo_in),
            width=w_taper,
            cross_section=x,
        ),
        temp_component.add_port(
            name="I_out2",
            orientation=0,
            center=(length_mmi, y_lo_in - gap_mmi - w_taper),
            width=w_taper,
            cross_section=x,
        ),
    ]

    for port in ports:
        taper_ref = c << taper
        taper_ref.connect(port="o2", other=port)
        c.add_port(name=port.name, port=taper_ref.ports["o1"])

    c.flatten()
    x.add_bbox(c)
    return c

mmi_90degree_hybrid

mmi_tapered

mmi_tapered

mmi_tapered(
    inputs: int = 1,
    outputs: int = 2,
    width: float | None = None,
    width_taper_in: float = 2.0,
    length_taper_in: float = 1.0,
    width_taper_out: float | None = None,
    length_taper_out: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_taper_start: float | None = None,
    length_taper_end: float | None = None,
    length_mmi: float = 5.5,
    width_mmi: float = 5,
    width_mmi_inner: float | None = None,
    gap_input_tapers: float = 0.25,
    gap_output_tapers: float = 0.25,
    taper: ComponentFactory = taper_function,
    cross_section: CrossSectionSpec = "strip",
    input_positions: list[float] | None = None,
    output_positions: list[float] | None = None,
) -> Component

Mxn MultiMode Interferometer (MMI).

This is jut a more general version of the mmi component. Make sure you simulate and optimize the component before using it.

Parameters:

Name Type Description Default
inputs int

number of inputs.

1
outputs int

number of outputs.

2
width float | None

input and output straight width. Defaults to cross_section.

None
width_taper_in float

interface between input straights and mmi region.

2.0
length_taper_in float

into the mmi region.

1.0
width_taper_out float | None

interface between mmi region and output straights.

None
length_taper_out float | None

into the mmi region.

None
width_taper float

interface between mmi region and output straights.

1.0
length_taper float

into the mmi region.

10.0
length_taper_start float | None

length of the taper at the start. Defaults to length_taper.

None
length_taper_end float | None

length of the taper at the end. Defaults to length_taper.

None
length_mmi float

in x direction.

5.5
width_mmi float

in y direction.

5
width_mmi_inner float | None

allows adding a different width for the inner mmi region.

None
gap_input_tapers float

gap between input tapers from edge to edge.

0.25
gap_output_tapers float

gap between output tapers from edge to edge.

0.25
taper ComponentFactory

taper function.

taper
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
input_positions list[float] | None

optional positions of the inputs.

None
output_positions list[float] | None

optional positions of the outputs.

                       ┌───────────┐
                       │           ├───────────────┐
                       │           │               ├────────────┐

width_taper │ │ │ │ ▲ ┌────────────────┤ │ ├────────────┘ │ │ │ ├───────────────┘

None
Source code in gdsfactory/components/mmis/mmi_tapered.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
 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
@gf.cell_with_module_name(schematic_function=mmi_1x2_schematic, tags=["mmis"])
def mmi_tapered(
    inputs: int = 1,
    outputs: int = 2,
    width: float | None = None,
    width_taper_in: float = 2.0,
    length_taper_in: float = 1.0,
    width_taper_out: float | None = None,
    length_taper_out: float | None = None,
    width_taper: float = 1.0,
    length_taper: float = 10.0,
    length_taper_start: float | None = None,
    length_taper_end: float | None = None,
    length_mmi: float = 5.5,
    width_mmi: float = 5,
    width_mmi_inner: float | None = None,
    gap_input_tapers: float = 0.25,
    gap_output_tapers: float = 0.25,
    taper: ComponentFactory = taper_function,
    cross_section: CrossSectionSpec = "strip",
    input_positions: list[float] | None = None,
    output_positions: list[float] | None = None,
) -> Component:
    r"""Mxn MultiMode Interferometer (MMI).

    This is jut a more general version of the mmi component.
    Make sure you simulate and optimize the component before using it.

    Args:
        inputs: number of inputs.
        outputs: number of outputs.
        width: input and output straight width. Defaults to cross_section.
        width_taper_in: interface between input straights and mmi region.
        length_taper_in: into the mmi region.
        width_taper_out: interface between mmi region and output straights.
        length_taper_out: into the mmi region.
        width_taper: interface between mmi region and output straights.
        length_taper: into the mmi region.
        length_taper_start: length of the taper at the start. Defaults to length_taper.
        length_taper_end: length of the taper at the end. Defaults to length_taper.
        length_mmi: in x direction.
        width_mmi: in y direction.
        width_mmi_inner: allows adding a different width for the inner mmi region.
        gap_input_tapers: gap between input tapers from edge to edge.
        gap_output_tapers: gap between output tapers from edge to edge.
        taper: taper function.
        cross_section: specification (CrossSection, string or dict).
        input_positions: optional positions of the inputs.
        output_positions: optional positions of the outputs.

                                       ┌───────────┐
                                       │           ├───────────────┐
                                       │           │               ├────────────┐
               width_taper             │           │               │            │
                    ▲ ┌────────────────┤           │               ├────────────┘
                    │ │                │           ├───────────────┘
        ┌───────────┼─┤                │           │
        │           │ │                │           │
        ◄───────────┼─►                │           ├───────────────┐
        └───────────┼─┐                │           │               ├─────────────┐
                    ▼ └────────────────┤           │               │             │
                      ◄───────────────►│           │               ├─────────────┘
        length_taper    length_taper_in│           ├───────────────┘ length_taper
        ◄────────────►                 └───────────┘◄────────────►  ◄────────────►
            start                                  length_taper_out      end
                                       ◄───────────►
                                        length_mmi
    """
    c = Component()
    gap_input_tapers = gf.snap.snap_to_grid(gap_input_tapers, grid_factor=2)
    gap_output_tapers = gf.snap.snap_to_grid(gap_output_tapers, grid_factor=2)
    x = gf.get_cross_section(cross_section)
    width = width or x.width
    width_taper_out = width_taper_out or width_taper_in

    _taper_in = taper(
        length=length_taper_in,
        width1=width_taper,
        width2=width_taper_in,
        cross_section=cross_section,
    )
    _taper_out = taper(
        length=length_taper_out or length_taper_in,
        width2=width_taper_out,
        width1=width_taper,
        cross_section=cross_section,
    )
    _taper_start = taper(
        length=length_taper_start or length_taper,
        width1=width,
        width2=width_taper,
        cross_section=cross_section,
    )

    _taper_end = taper(
        length=length_taper_end or length_taper,
        width2=width_taper,
        width1=width,
        cross_section=cross_section,
    )

    width_mmi_inner = width_mmi_inner or width_mmi

    # _ = c << straight(length=length_mmi, cross_section=xs_mmi)
    mmi_left = c << taper(
        length=length_mmi / 2,
        width1=width_mmi,
        width2=width_mmi_inner,
        cross_section=cross_section,
    )
    mmi_right = c << taper(
        length=length_mmi / 2,
        width1=width_mmi_inner,
        width2=width_mmi,
        cross_section=cross_section,
    )
    mmi_right.connect("o1", mmi_left.ports["o2"])

    wg_spacing_input = gap_input_tapers + width_taper_in
    wg_spacing_output = gap_output_tapers + width_taper_out

    yi = -(inputs - 1) * wg_spacing_input / 2
    yo = -(outputs - 1) * wg_spacing_output / 2

    input_positions = input_positions or [
        yi + i * wg_spacing_input for i in range(inputs)
    ]
    output_positions = output_positions or [
        yo + i * wg_spacing_output for i in range(outputs)
    ]

    temp_component = Component()

    in_ports = [
        temp_component.add_port(
            name=f"in_{i}",
            orientation=180,
            center=(0, y),
            width=width_taper_in,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        )
        for i, y in enumerate(input_positions)
    ]

    out_ports = [
        temp_component.add_port(
            name=f"out_{i}",
            orientation=0,
            center=(+length_mmi, y),
            width=width_taper_out,
            layer=gf.get_layer(x.layer),
            cross_section=x,
        )
        for i, y in enumerate(output_positions)
    ]

    for port in in_ports:
        taper_ref = c << _taper_in
        taper_ref.connect("o2", port, allow_width_mismatch=True)
        taper_outer_ref = c << _taper_start
        taper_outer_ref.connect("o2", taper_ref["o1"], allow_width_mismatch=True)
        c.add_port(name=port.name, port=taper_outer_ref.ports["o1"])

    for port in out_ports:
        taper_ref = c << _taper_out
        taper_ref.connect("o2", port, allow_width_mismatch=True)
        taper_outer_ref = c << _taper_end
        taper_outer_ref.connect("o2", taper_ref["o1"], allow_width_mismatch=True)
        c.add_port(name=port.name, port=taper_outer_ref.ports["o1"])

    x.add_bbox(c)
    c.auto_rename_ports()
    c.flatten()
    return c

mmi_tapered

mzis

mzi

mzi

mzi(
    delta_length: float = 10.0,
    length_y: float = 2.0,
    length_x: float | None = 0.1,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_y: ComponentSpec | None = None,
    straight_x_top: ComponentSpec | None = None,
    straight_x_bot: ComponentSpec | None = None,
    splitter: ComponentSpec = "mmi1x2",
    combiner: ComponentSpec | None = None,
    with_splitter: bool = True,
    port_e1_splitter: str = "o2",
    port_e0_splitter: str = "o3",
    port_e1_combiner: str = "o2",
    port_e0_combiner: str = "o3",
    port1: str = "o1",
    port2: str = "o2",
    nbends: int = 2,
    cross_section: CrossSectionSpec = "strip",
    cross_section_x_top: CrossSectionSpec | None = None,
    cross_section_x_bot: CrossSectionSpec | None = None,
    mirror_bot: bool = False,
    add_optical_ports_arms: bool = False,
    min_length: float = 0.01,
    auto_rename_ports: bool = True,
    auto_detect_port_names: bool = False,
) -> Component

Mzi.

Parameters:

Name Type Description Default
delta_length float

bottom arm vertical extra length.

10.0
length_y float

vertical length for both and top arms.

2.0
length_x float | None

horizontal length. None uses to the straight_x_bot/top defaults.

0.1
bend ComponentSpec

90 degrees bend library.

'bend_euler'
straight ComponentSpec

straight function.

'straight'
straight_y ComponentSpec | None

straight for length_y and delta_length.

None
straight_x_top ComponentSpec | None

top straight for length_x.

None
straight_x_bot ComponentSpec | None

bottom straight for length_x.

None
splitter ComponentSpec

splitter function.

'mmi1x2'
combiner ComponentSpec | None

combiner function.

None
with_splitter bool

if False removes splitter.

True
port_e1_splitter str

east top splitter port.

'o2'
port_e0_splitter str

east bot splitter port.

'o3'
port_e1_combiner str

east top combiner port.

'o2'
port_e0_combiner str

east bot combiner port.

'o3'
port1 str

input port name.

'o1'
port2 str

output port name.

'o2'
nbends int

from straight top/bot to combiner (at least 2).

2
cross_section CrossSectionSpec

for routing (sxtop/sxbot to combiner).

'strip'
cross_section_x_top CrossSectionSpec | None

optional top cross_section (defaults to cross_section).

None
cross_section_x_bot CrossSectionSpec | None

optional bottom cross_section (defaults to cross_section).

None
mirror_bot bool

if true, mirrors the bottom arm.

False
add_optical_ports_arms bool

add all other optical ports in the arms with top\ and bot\ prefix.

False
min_length float

minimum length for the straight.

0.01
auto_rename_ports bool

if True, renames ports.

True
auto_detect_port_names bool

whether to auto detect ports names. Ignores port_e* arguments if True.

       b2______b3
      |  sxtop  |

straight_y | | | b1 b4 splitter==| |==combiner b5 b8 | | straight_y | | |

False
Source code in gdsfactory/components/mzis/mzi.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
280
281
282
283
284
285
286
287
288
289
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzi(
    delta_length: float = 10.0,
    length_y: float = 2.0,
    length_x: float | None = 0.1,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_y: ComponentSpec | None = None,
    straight_x_top: ComponentSpec | None = None,
    straight_x_bot: ComponentSpec | None = None,
    splitter: ComponentSpec = "mmi1x2",
    combiner: ComponentSpec | None = None,
    with_splitter: bool = True,
    port_e1_splitter: str = "o2",
    port_e0_splitter: str = "o3",
    port_e1_combiner: str = "o2",
    port_e0_combiner: str = "o3",
    port1: str = "o1",
    port2: str = "o2",
    nbends: int = 2,
    cross_section: CrossSectionSpec = "strip",
    cross_section_x_top: CrossSectionSpec | None = None,
    cross_section_x_bot: CrossSectionSpec | None = None,
    mirror_bot: bool = False,
    add_optical_ports_arms: bool = False,
    min_length: float = 10e-3,
    auto_rename_ports: bool = True,
    auto_detect_port_names: bool = False,
) -> Component:
    r"""Mzi.

    Args:
        delta_length: bottom arm vertical extra length.
        length_y: vertical length for both and top arms.
        length_x: horizontal length. None uses to the straight_x_bot/top defaults.
        bend: 90 degrees bend library.
        straight: straight function.
        straight_y: straight for length_y and delta_length.
        straight_x_top: top straight for length_x.
        straight_x_bot: bottom straight for length_x.
        splitter: splitter function.
        combiner: combiner function.
        with_splitter: if False removes splitter.
        port_e1_splitter: east top splitter port.
        port_e0_splitter: east bot splitter port.
        port_e1_combiner: east top combiner port.
        port_e0_combiner: east bot combiner port.
        port1: input port name.
        port2: output port name.
        nbends: from straight top/bot to combiner (at least 2).
        cross_section: for routing (sxtop/sxbot to combiner).
        cross_section_x_top: optional top cross_section (defaults to cross_section).
        cross_section_x_bot: optional bottom cross_section (defaults to cross_section).
        mirror_bot: if true, mirrors the bottom arm.
        add_optical_ports_arms: add all other optical ports in the arms
            with top\\_ and bot\\_ prefix.
        min_length: minimum length for the straight.
        auto_rename_ports: if True, renames ports.
        auto_detect_port_names: whether to auto detect ports names. Ignores port_e* arguments if True.

                       b2______b3
                      |  sxtop  |
              straight_y        |
                      |         |
                      b1        b4
            splitter==|         |==combiner
                      b5        b8
                      |         |
              straight_y        |
                      |         |
        delta_length/2          |
                      |         |
                     b6__sxbot__b7
                          Lx
    """
    if auto_detect_port_names:
        splitter_instance = gf.get_component(splitter)
        combiner_instance = gf.get_component(combiner or splitter)
        splitter_ports = splitter_instance.get_ports_list(
            port_type="optical", orientation=0
        )
        combiner_ports = combiner_instance.get_ports_list(
            port_type="optical", orientation=0
        )
        _name1 = splitter_ports[0].name
        _name2 = splitter_ports[1].name
        _name3 = combiner_ports[0].name
        _name4 = combiner_ports[1].name
        assert _name1 is not None, "splitter port 1 must have a name"
        assert _name2 is not None, "splitter port 2 must have a name"
        assert _name3 is not None, "combiner port 1 must have a name"
        assert _name4 is not None, "combiner port 2 must have a name"
        port_e1_splitter = _name1
        port_e0_splitter = _name2
        port_e1_combiner = _name3
        port_e0_combiner = _name4

    combiner = combiner or splitter

    straight_x_top = straight_x_top or straight
    straight_x_bot = straight_x_bot or straight
    straight_y = straight_y or straight

    cross_section_x_bot = cross_section_x_bot or cross_section
    cross_section_x_top = cross_section_x_top or cross_section
    bend = gf.get_component(bend, cross_section=cross_section)

    c = Component()
    cp1 = gf.get_component(splitter)
    cp2 = gf.get_component(combiner) if combiner else cp1

    if with_splitter:
        cp1 = c << cp1  # type: ignore[assignment]
        cp1.name = "cp1"

    cp2_reference = c << cp2
    b5 = c << bend
    b5.connect(port1, cp1.ports[port_e0_splitter], mirror=True)
    b5.name = "b5"

    gap_ports_splitter = cp1.ports[port_e0_splitter].y - cp1.ports[port_e1_splitter].y
    gap_ports_combiner = (
        cp2_reference.ports[port_e0_combiner].y
        - cp2_reference.ports[port_e1_combiner].y
    )
    delta_gap_ports = gap_ports_splitter - gap_ports_combiner

    # Offset applied only to the right-side vertical sections so that
    # b4/b8 end at the combiner port y-positions (not the splitter ones).
    combiner_offset = -delta_gap_ports / 2

    # Use sign of ``delta_length`` to determine which arm to lengthen
    short_arm_length = length_y
    if short_arm_length + combiner_offset < 0:
        raise ValueError(
            "Computed arm length is negative, which would result in a negative "
            "straight section length. This usually happens when `length_y` is too "
            "small relative to the splitter/combiner port gaps. "
            f"Got length_y={length_y}, gap_ports_combiner={gap_ports_combiner}, "
            f"gap_ports_splitter={gap_ports_splitter}, delta_gap_ports={delta_gap_ports}, "
            f"combiner_offset={combiner_offset}."
        )
    long_arm_length = abs(delta_length) / 2 + short_arm_length

    # Keep to the previous convention of the bottom arm being longer
    # for positive ``delta_length``
    if delta_length > 0:
        # Make bottom arm longer
        bot_arm_length = long_arm_length
        top_arm_length = short_arm_length
    else:
        # Make top arm longer
        bot_arm_length = short_arm_length
        top_arm_length = long_arm_length

    syl = c << gf.get_component(
        straight_y, length=bot_arm_length, cross_section=cross_section
    )
    syl.connect(port1, b5.ports[port2])
    b6 = c << bend
    b6.connect(port1, syl.ports[port2])
    b6.name = "b6"

    straight_x_top = (
        gf.get_component(
            straight_x_top, length=length_x, cross_section=cross_section_x_top
        )
        if length_x
        else gf.get_component(straight_x_top, cross_section=cross_section_x_top)
    )
    sxt = c << straight_x_top

    length_x = length_x or abs(sxt.ports[port1].x - sxt.ports[port2].x)

    straight_x_bot = (
        gf.get_component(
            straight_x_bot, length=length_x, cross_section=cross_section_x_bot
        )
        if length_x
        else gf.get_component(straight_x_bot, cross_section=cross_section_x_bot)
    )
    sxb = c << straight_x_bot
    sxb.connect(port1, b6.ports[port2], mirror=mirror_bot)

    b1 = c << bend
    b1.connect(port1, cp1.ports[port_e1_splitter])
    b1.name = "b1"

    sytl = c << gf.get_component(
        straight_y, length=top_arm_length, cross_section=cross_section
    )
    sytl.connect(port1, b1.ports[port2])

    b2 = c << bend
    b2.connect(port2, sytl.ports[port2])
    b2.name = "b2"

    sxt.connect(port1, b2.ports[port1])
    cp2_reference.mirror_x()
    cp2_reference.xmin = (
        sxt.ports[port2].x + bend.info["radius"] * nbends + 2 * min_length
    )

    # Top arm
    b3 = c << bend
    b3.connect(port2, sxt.ports[port2])
    b3.name = "b3"

    sytr = c << gf.get_component(
        straight_y, length=top_arm_length + combiner_offset, cross_section=cross_section
    )
    sytr.connect(port2, b3.ports[port1])
    b4 = c << bend
    b4.connect(port1, sytr.ports[port1])
    b4.name = "b4"

    # Bot arm
    b7 = c << bend
    b7.connect(port1, sxb.ports[port2])
    b7.name = "b7"

    sybr = c << gf.get_component(
        straight_y,
        length=bot_arm_length + combiner_offset,
        cross_section=cross_section,
    )
    sybr.connect(port1, b7.ports[port2])
    b8 = c << bend
    b8.connect(port2, sybr.ports[port2])
    b8.name = "b8"

    cp2_reference.connect(port_e1_combiner, b4.ports[port2])

    sytl.name = "sytl"
    syl.name = "syl"
    sxt.name = "sxt"
    sxb.name = "sxb"
    cp2_reference.name = "cp2"

    sytr.name = "sytr"
    sybr.name = "sybr"

    if with_splitter:
        c.add_ports(cp1.ports.filter(orientation=180), prefix="in_")
    else:
        c.add_port(port1, port=b1.ports[port1])
        c.add_port(port2, port=b5.ports[port1])

    c.add_ports(cp2_reference.ports.filter(orientation=0), prefix="ou_")
    c.add_ports(sxt.ports.filter(port_type="electrical"), prefix="top_")
    c.add_ports(sxb.ports.filter(port_type="electrical"), prefix="bot_")
    c.add_ports(sxt.ports.filter(port_type="placement"), prefix="top_")
    c.add_ports(sxb.ports.filter(port_type="placement"), prefix="bot_")

    if add_optical_ports_arms:
        c.add_ports(sxt.ports.filter(port_type="optical"), prefix="top_")
        c.add_ports(sxb.ports.filter(port_type="optical"), prefix="bot_")

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    if auto_rename_ports:
        c.auto_rename_ports()
    return c

mzi

mzi1x2_2x2 module-attribute

mzi1x2_2x2 = partial(
    mzi,
    combiner="mmi2x2",
    port_e1_combiner="o3",
    port_e0_combiner="o4",
)

mzi1x2_2x2

mzi2x2_2x2_phase_shifter module-attribute

mzi2x2_2x2_phase_shifter = partial(
    mzi2x2_2x2,
    straight_x_top="straight_heater_metal",
    length_x=200,
)

mzi2x2_2x2_phase_shifter

mzi_lattice

mzi_lattice

mzi_lattice(
    coupler_lengths: Sequence[float] = (10.0, 20.0),
    coupler_gaps: Sequence[float] = (0.2, 0.3),
    delta_lengths: Sequence[float] = (10.0,),
    mzi: str = "mzi_coupler",
    splitter: str = "coupler",
    **kwargs: Any
) -> Component

Mzi lattice filter.

Parameters:

Name Type Description Default
coupler_lengths Sequence[float]

list of length for each coupler.

(10.0, 20.0)
coupler_gaps Sequence[float]

list of coupler gaps.

(0.2, 0.3)
delta_lengths Sequence[float]

list of length differences.

(10.0,)
mzi str

function for the mzi.

'mzi_coupler'
splitter str

splitter function.

'coupler'
kwargs Any

additional settings.

{}

Other Parameters:

Name Type Description
length_y

vertical length for both and top arms.

length_x

horizontal length.

bend

90 degrees bend library.

straight

straight function.

straight_y

straight for length_y and delta_length.

straight_x_top

top straight for length_x.

straight_x_bot

bottom straight for length_x.

cross_section

for routing (sxtop/sxbot to combiner).

__ _ | | | | | | | | cp1==| |===cp2=====| |=== .... ===cplast=== | | | | | | | | DL1 | DL2 | | | | | |_| | | |___|

Source code in gdsfactory/components/mzis/mzi_lattice.py
 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
 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
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzi_lattice(
    coupler_lengths: Sequence[float] = (10.0, 20.0),
    coupler_gaps: Sequence[float] = (0.2, 0.3),
    delta_lengths: Sequence[float] = (10.0,),
    mzi: str = "mzi_coupler",
    splitter: str = "coupler",
    **kwargs: Any,
) -> Component:
    r"""Mzi lattice filter.

    Args:
        coupler_lengths: list of length for each coupler.
        coupler_gaps: list of coupler gaps.
        delta_lengths: list of length differences.
        mzi: function for the mzi.
        splitter: splitter function.
        kwargs: additional settings.

    Keyword Args:
        length_y: vertical length for both and top arms.
        length_x: horizontal length.
        bend: 90 degrees bend library.
        straight: straight function.
        straight_y: straight for length_y and delta_length.
        straight_x_top: top straight for length_x.
        straight_x_bot: bottom straight for length_x.
        cross_section: for routing (sxtop/sxbot to combiner).

               ______             ______
              |      |           |      |
              |      |           |      |
         cp1==|      |===cp2=====|      |=== .... ===cp_last===
              |      |           |      |
              |      |           |      |
             DL1     |          DL2     |
              |      |           |      |
              |______|           |      |
                                 |______|

    """
    if len(coupler_lengths) != len(coupler_gaps):
        raise ValueError(
            f"Got {len(coupler_lengths)} coupler_lengths and "
            f"{len(coupler_gaps)} coupler_gaps"
        )
    if len(coupler_lengths) != len(delta_lengths) + 1:
        raise ValueError(
            f"Got {len(coupler_lengths)} coupler_lengths and "
            f"{len(delta_lengths)} delta_lengths. "
            "You need one more coupler_length than delta_lengths "
        )

    c = Component()

    cp1 = splitter1 = gf.get_component(
        splitter, gap=coupler_gaps[0], length=coupler_lengths[0]
    )
    combiner1 = gf.get_component(
        splitter, gap=coupler_gaps[1], length=coupler_lengths[1]
    )

    sprevious = c << gf.get_component(
        mzi,
        splitter=splitter1,
        combiner=combiner1,
        with_splitter=True,
        delta_length=delta_lengths[0],
        **kwargs,
    )
    c.add_ports(sprevious.ports.filter(port_type="electrical"))

    stages: list[ComponentReference] = []

    for length, gap, delta_length in zip(
        coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:], strict=False
    ):
        splitter_settings = dict(gap=coupler_gaps[1], length=coupler_lengths[1])
        combiner_settings = dict(length=length, gap=gap)
        splitter1 = gf.get_component(splitter, settings=None, **splitter_settings)
        combiner1 = gf.get_component(splitter, settings=None, **combiner_settings)

        stage = c << gf.get_component(
            mzi,
            splitter=splitter1,
            combiner=combiner1,
            with_splitter=False,
            delta_length=delta_length,
            **kwargs,
        )
        splitter_settings = combiner_settings

        stages.append(stage)
        c.add_ports(stage.ports.filter(port_type="electrical"))

    for stage in stages:
        stage.connect("o1", sprevious.ports["o4"])
        # stage.connect('o2', sprevious.ports['o1'])
        sprevious = stage

    for port in cp1.ports.filter(orientation=180, port_type="optical"):
        c.add_port(port.name, port=port)

    for port in sprevious.ports.filter(orientation=0, port_type="optical"):
        c.add_port(f"o_{port.name}", port=port)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

mzi_lattice_mmi

mzi_lattice_mmi(
    coupler_widths: tuple[float | None, float | None] = (
        None,
        None,
    ),
    coupler_widths_tapers: tuple[float, ...] = (1.0, 1.0),
    coupler_lengths_tapers: tuple[float, ...] = (
        10.0,
        10.0,
    ),
    coupler_lengths_mmis: tuple[float, ...] = (5.5, 5.5),
    coupler_widths_mmis: tuple[float, ...] = (2.5, 2.5),
    coupler_gaps_mmis: tuple[float, ...] = (0.25, 0.25),
    taper_functions_mmis: tuple[str, ...] = (
        "taper",
        "taper",
    ),
    straight_functions_mmis: tuple[str, ...] = (
        "straight",
        "straight",
    ),
    cross_sections_mmis: tuple[str, ...] = (
        "strip",
        "strip",
    ),
    delta_lengths: tuple[float, ...] = (10.0,),
    mzi: str = "mzi2x2_2x2",
    splitter: str = "mmi2x2",
    **kwargs: Any
) -> Component

Mzi lattice filter, with MMI couplers.

Parameters:

Name Type Description Default
coupler_widths tuple[float | None, float | None]

(for each MMI coupler, list of) input and output straight width.

(None, None)
coupler_widths_tapers tuple[float, ...]

(for each MMI coupler, list of) interface between input straights and mmi region.

(1.0, 1.0)
coupler_lengths_tapers tuple[float, ...]

(for each MMI coupler, list of) into the mmi region.

(10.0, 10.0)
coupler_lengths_mmis tuple[float, ...]

(for each MMI coupler, list of) in x direction.

(5.5, 5.5)
coupler_widths_mmis tuple[float, ...]

(for each MMI coupler, list of) in y direction.

(2.5, 2.5)
coupler_gaps_mmis tuple[float, ...]

(for each MMI coupler, list of) (width_taper + gap between tapered wg)/2.

(0.25, 0.25)
taper_functions_mmis tuple[str, ...]

(for each MMI coupler, list of) taper function.

('taper', 'taper')
straight_functions_mmis tuple[str, ...]

(for each MMI coupler, list of) straight function.

('straight', 'straight')
cross_sections_mmis tuple[str, ...]

(for each MMI coupler, list of) spec.

('strip', 'strip')
delta_lengths tuple[float, ...]

list of length differences.

(10.0,)
mzi str

function for the mzi.

'mzi2x2_2x2'
splitter str

splitter function.

'mmi2x2'
kwargs Any

additional settings.

{}

Other Parameters:

Name Type Description
length_y

vertical length for both and top arms.

length_x

horizontal length.

bend

90 degrees bend library.

straight

straight function.

straight_y

straight for length_y and delta_length.

straight_x_top

top straight for length_x.

straight_x_bot

bottom straight for length_x.

cross_section

for routing (sxtop/sxbot to combiner).

__ _ | | | | | | | | cp1==| |===cp2=====| |=== .... ===cplast=== | | | | | | | | DL1 | DL2 | | | | | |_| | | |___|

Source code in gdsfactory/components/mzis/mzi_lattice.py
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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzi_lattice_mmi(
    coupler_widths: tuple[float | None, float | None] = (None, None),
    coupler_widths_tapers: tuple[float, ...] = (
        1.0,
        1.0,
    ),
    coupler_lengths_tapers: tuple[float, ...] = (
        10.0,
        10.0,
    ),
    coupler_lengths_mmis: tuple[float, ...] = (
        5.5,
        5.5,
    ),
    coupler_widths_mmis: tuple[float, ...] = (
        2.5,
        2.5,
    ),
    coupler_gaps_mmis: tuple[float, ...] = (
        0.25,
        0.25,
    ),
    taper_functions_mmis: tuple[str, ...] = (
        "taper",
        "taper",
    ),
    straight_functions_mmis: tuple[str, ...] = ("straight", "straight"),
    cross_sections_mmis: tuple[str, ...] = ("strip", "strip"),
    delta_lengths: tuple[float, ...] = (10.0,),
    mzi: str = "mzi2x2_2x2",
    splitter: str = "mmi2x2",
    **kwargs: Any,
) -> Component:
    r"""Mzi lattice filter, with MMI couplers.

    Args:
        coupler_widths: (for each MMI coupler, list of) input and output straight width.
        coupler_widths_tapers: (for each MMI coupler, list of) interface between input straights and mmi region.
        coupler_lengths_tapers: (for each MMI coupler, list of) into the mmi region.
        coupler_lengths_mmis: (for each MMI coupler, list of) in x direction.
        coupler_widths_mmis: (for each MMI coupler, list of) in y direction.
        coupler_gaps_mmis: (for each MMI coupler, list of) (width_taper + gap between tapered wg)/2.
        taper_functions_mmis: (for each MMI coupler, list of) taper function.
        straight_functions_mmis: (for each MMI coupler, list of) straight function.
        cross_sections_mmis: (for each MMI coupler, list of) spec.
        delta_lengths: list of length differences.
        mzi: function for the mzi.
        splitter: splitter function.
        kwargs: additional settings.

    Keyword Args:
        length_y: vertical length for both and top arms.
        length_x: horizontal length.
        bend: 90 degrees bend library.
        straight: straight function.
        straight_y: straight for length_y and delta_length.
        straight_x_top: top straight for length_x.
        straight_x_bot: bottom straight for length_x.
        cross_section: for routing (sxtop/sxbot to combiner).

               ______             ______
              |      |           |      |
              |      |           |      |
         cp1==|      |===cp2=====|      |=== .... ===cp_last===
              |      |           |      |
              |      |           |      |
             DL1     |          DL2     |
              |      |           |      |
              |______|           |      |
                                 |______|

    """
    length = len(coupler_widths)
    if all(
        len(lst) != length
        for lst in [
            coupler_widths_tapers,
            coupler_lengths_tapers,
            coupler_lengths_mmis,
            coupler_widths_mmis,
            coupler_gaps_mmis,
            taper_functions_mmis,
            straight_functions_mmis,
            cross_sections_mmis,
        ]
    ):
        raise ValueError("All MMI-related argument lists must be the same length.")
    if len(coupler_widths) != len(delta_lengths) + 1:
        raise ValueError(
            f"Got {len(coupler_widths)} coupler_widths and "
            f"{len(delta_lengths)} delta_lengths. "
            "You need one more coupler_width than delta_lengths "
        )

    c = Component()

    splitter_settings = dict(
        width=coupler_widths[0],
        width_taper=coupler_widths_tapers[0],
        length_taper=coupler_lengths_tapers[0],
        length_mmi=coupler_lengths_mmis[0],
        width_mmi=coupler_widths_mmis[0],
        gap_mmi=coupler_gaps_mmis[0],
        taper=taper_functions_mmis[0],
        straight=straight_functions_mmis[0],
        cross_section=cross_sections_mmis[0],
    )
    combiner_settings = dict(
        width=coupler_widths[1],
        width_taper=coupler_widths_tapers[1],
        length_taper=coupler_lengths_tapers[1],
        length_mmi=coupler_lengths_mmis[1],
        width_mmi=coupler_widths_mmis[1],
        gap_mmi=coupler_gaps_mmis[1],
        taper=taper_functions_mmis[1],
        straight=straight_functions_mmis[1],
        cross_section=cross_sections_mmis[1],
    )

    cp1 = splitter1 = gf.get_component(splitter, settings=None, **splitter_settings)
    combiner1 = gf.get_component(splitter, settings=None, **combiner_settings)

    sprevious = c << gf.get_component(
        mzi,
        splitter=splitter1,
        combiner=combiner1,
        with_splitter=True,
        delta_length=delta_lengths[0],
        **kwargs,
    )
    c.add_ports(sprevious.ports.filter(port_type="electrical"))

    stages: list[ComponentReference] = []

    for (
        coupler_width,
        coupler_width_taper,
        coupler_length_taper,
        coupler_length_mmi,
        coupler_width_mmi,
        coupler_gap_mmi,
        taper,
        straight,
        cross_section,
        delta_length,
    ) in zip(
        coupler_widths[2:],
        coupler_widths_tapers[2:],
        coupler_lengths_tapers[2:],
        coupler_lengths_mmis[2:],
        coupler_widths_mmis[2:],
        coupler_gaps_mmis[2:],
        taper_functions_mmis[2:],
        straight_functions_mmis[2:],
        cross_sections_mmis[2:],
        delta_lengths[1:],
        strict=False,
    ):
        splitter_settings = dict(
            width=coupler_widths[1],
            width_taper=coupler_widths_tapers[1],
            length_taper=coupler_lengths_tapers[1],
            length_mmi=coupler_lengths_mmis[1],
            width_mmi=coupler_widths_mmis[1],
            gap_mmi=coupler_gaps_mmis[1],
            taper=taper_functions_mmis[1],
            straight=straight_functions_mmis[1],
            cross_section=cross_sections_mmis[1],
        )
        combiner_settings = dict(
            width=coupler_width,
            width_taper=coupler_width_taper,
            length_taper=coupler_length_taper,
            length_mmi=coupler_length_mmi,
            width_mmi=coupler_width_mmi,
            gap_mmi=coupler_gap_mmi,
            taper=taper,
            straight=straight,
            cross_section=cross_section,
        )
        splitter1 = gf.get_component(splitter, settings=None, **splitter_settings)
        combiner1 = gf.get_component(splitter, settings=None, **combiner_settings)

        stage = c << gf.get_component(
            mzi,
            splitter=splitter1,
            combiner=combiner1,
            with_splitter=False,
            delta_length=delta_length,
            **kwargs,
        )
        splitter_settings = combiner_settings

        stages.append(stage)
        c.add_ports(stage.ports.filter(port_type="electrical"))

    for stage in stages:
        stage.connect("o1", sprevious.ports["o4"])
        # stage.connect('o2', sprevious.ports['o1'])
        sprevious = stage

    for port in cp1.ports.filter(orientation=180, port_type="optical"):
        c.add_port(port.name, port=port)

    for port in sprevious.ports.filter(orientation=0, port_type="optical"):
        c.add_port(f"o_{port.name}", port=port)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

mzi_lattice

mzi_lattice_mmi

mzi_lattice_mmi(
    coupler_widths: tuple[float | None, float | None] = (
        None,
        None,
    ),
    coupler_widths_tapers: tuple[float, ...] = (1.0, 1.0),
    coupler_lengths_tapers: tuple[float, ...] = (
        10.0,
        10.0,
    ),
    coupler_lengths_mmis: tuple[float, ...] = (5.5, 5.5),
    coupler_widths_mmis: tuple[float, ...] = (2.5, 2.5),
    coupler_gaps_mmis: tuple[float, ...] = (0.25, 0.25),
    taper_functions_mmis: tuple[str, ...] = (
        "taper",
        "taper",
    ),
    straight_functions_mmis: tuple[str, ...] = (
        "straight",
        "straight",
    ),
    cross_sections_mmis: tuple[str, ...] = (
        "strip",
        "strip",
    ),
    delta_lengths: tuple[float, ...] = (10.0,),
    mzi: str = "mzi2x2_2x2",
    splitter: str = "mmi2x2",
    **kwargs: Any
) -> Component

Mzi lattice filter, with MMI couplers.

Parameters:

Name Type Description Default
coupler_widths tuple[float | None, float | None]

(for each MMI coupler, list of) input and output straight width.

(None, None)
coupler_widths_tapers tuple[float, ...]

(for each MMI coupler, list of) interface between input straights and mmi region.

(1.0, 1.0)
coupler_lengths_tapers tuple[float, ...]

(for each MMI coupler, list of) into the mmi region.

(10.0, 10.0)
coupler_lengths_mmis tuple[float, ...]

(for each MMI coupler, list of) in x direction.

(5.5, 5.5)
coupler_widths_mmis tuple[float, ...]

(for each MMI coupler, list of) in y direction.

(2.5, 2.5)
coupler_gaps_mmis tuple[float, ...]

(for each MMI coupler, list of) (width_taper + gap between tapered wg)/2.

(0.25, 0.25)
taper_functions_mmis tuple[str, ...]

(for each MMI coupler, list of) taper function.

('taper', 'taper')
straight_functions_mmis tuple[str, ...]

(for each MMI coupler, list of) straight function.

('straight', 'straight')
cross_sections_mmis tuple[str, ...]

(for each MMI coupler, list of) spec.

('strip', 'strip')
delta_lengths tuple[float, ...]

list of length differences.

(10.0,)
mzi str

function for the mzi.

'mzi2x2_2x2'
splitter str

splitter function.

'mmi2x2'
kwargs Any

additional settings.

{}

Other Parameters:

Name Type Description
length_y

vertical length for both and top arms.

length_x

horizontal length.

bend

90 degrees bend library.

straight

straight function.

straight_y

straight for length_y and delta_length.

straight_x_top

top straight for length_x.

straight_x_bot

bottom straight for length_x.

cross_section

for routing (sxtop/sxbot to combiner).

__ _ | | | | | | | | cp1==| |===cp2=====| |=== .... ===cplast=== | | | | | | | | DL1 | DL2 | | | | | |_| | | |___|

Source code in gdsfactory/components/mzis/mzi_lattice.py
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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzi_lattice_mmi(
    coupler_widths: tuple[float | None, float | None] = (None, None),
    coupler_widths_tapers: tuple[float, ...] = (
        1.0,
        1.0,
    ),
    coupler_lengths_tapers: tuple[float, ...] = (
        10.0,
        10.0,
    ),
    coupler_lengths_mmis: tuple[float, ...] = (
        5.5,
        5.5,
    ),
    coupler_widths_mmis: tuple[float, ...] = (
        2.5,
        2.5,
    ),
    coupler_gaps_mmis: tuple[float, ...] = (
        0.25,
        0.25,
    ),
    taper_functions_mmis: tuple[str, ...] = (
        "taper",
        "taper",
    ),
    straight_functions_mmis: tuple[str, ...] = ("straight", "straight"),
    cross_sections_mmis: tuple[str, ...] = ("strip", "strip"),
    delta_lengths: tuple[float, ...] = (10.0,),
    mzi: str = "mzi2x2_2x2",
    splitter: str = "mmi2x2",
    **kwargs: Any,
) -> Component:
    r"""Mzi lattice filter, with MMI couplers.

    Args:
        coupler_widths: (for each MMI coupler, list of) input and output straight width.
        coupler_widths_tapers: (for each MMI coupler, list of) interface between input straights and mmi region.
        coupler_lengths_tapers: (for each MMI coupler, list of) into the mmi region.
        coupler_lengths_mmis: (for each MMI coupler, list of) in x direction.
        coupler_widths_mmis: (for each MMI coupler, list of) in y direction.
        coupler_gaps_mmis: (for each MMI coupler, list of) (width_taper + gap between tapered wg)/2.
        taper_functions_mmis: (for each MMI coupler, list of) taper function.
        straight_functions_mmis: (for each MMI coupler, list of) straight function.
        cross_sections_mmis: (for each MMI coupler, list of) spec.
        delta_lengths: list of length differences.
        mzi: function for the mzi.
        splitter: splitter function.
        kwargs: additional settings.

    Keyword Args:
        length_y: vertical length for both and top arms.
        length_x: horizontal length.
        bend: 90 degrees bend library.
        straight: straight function.
        straight_y: straight for length_y and delta_length.
        straight_x_top: top straight for length_x.
        straight_x_bot: bottom straight for length_x.
        cross_section: for routing (sxtop/sxbot to combiner).

               ______             ______
              |      |           |      |
              |      |           |      |
         cp1==|      |===cp2=====|      |=== .... ===cp_last===
              |      |           |      |
              |      |           |      |
             DL1     |          DL2     |
              |      |           |      |
              |______|           |      |
                                 |______|

    """
    length = len(coupler_widths)
    if all(
        len(lst) != length
        for lst in [
            coupler_widths_tapers,
            coupler_lengths_tapers,
            coupler_lengths_mmis,
            coupler_widths_mmis,
            coupler_gaps_mmis,
            taper_functions_mmis,
            straight_functions_mmis,
            cross_sections_mmis,
        ]
    ):
        raise ValueError("All MMI-related argument lists must be the same length.")
    if len(coupler_widths) != len(delta_lengths) + 1:
        raise ValueError(
            f"Got {len(coupler_widths)} coupler_widths and "
            f"{len(delta_lengths)} delta_lengths. "
            "You need one more coupler_width than delta_lengths "
        )

    c = Component()

    splitter_settings = dict(
        width=coupler_widths[0],
        width_taper=coupler_widths_tapers[0],
        length_taper=coupler_lengths_tapers[0],
        length_mmi=coupler_lengths_mmis[0],
        width_mmi=coupler_widths_mmis[0],
        gap_mmi=coupler_gaps_mmis[0],
        taper=taper_functions_mmis[0],
        straight=straight_functions_mmis[0],
        cross_section=cross_sections_mmis[0],
    )
    combiner_settings = dict(
        width=coupler_widths[1],
        width_taper=coupler_widths_tapers[1],
        length_taper=coupler_lengths_tapers[1],
        length_mmi=coupler_lengths_mmis[1],
        width_mmi=coupler_widths_mmis[1],
        gap_mmi=coupler_gaps_mmis[1],
        taper=taper_functions_mmis[1],
        straight=straight_functions_mmis[1],
        cross_section=cross_sections_mmis[1],
    )

    cp1 = splitter1 = gf.get_component(splitter, settings=None, **splitter_settings)
    combiner1 = gf.get_component(splitter, settings=None, **combiner_settings)

    sprevious = c << gf.get_component(
        mzi,
        splitter=splitter1,
        combiner=combiner1,
        with_splitter=True,
        delta_length=delta_lengths[0],
        **kwargs,
    )
    c.add_ports(sprevious.ports.filter(port_type="electrical"))

    stages: list[ComponentReference] = []

    for (
        coupler_width,
        coupler_width_taper,
        coupler_length_taper,
        coupler_length_mmi,
        coupler_width_mmi,
        coupler_gap_mmi,
        taper,
        straight,
        cross_section,
        delta_length,
    ) in zip(
        coupler_widths[2:],
        coupler_widths_tapers[2:],
        coupler_lengths_tapers[2:],
        coupler_lengths_mmis[2:],
        coupler_widths_mmis[2:],
        coupler_gaps_mmis[2:],
        taper_functions_mmis[2:],
        straight_functions_mmis[2:],
        cross_sections_mmis[2:],
        delta_lengths[1:],
        strict=False,
    ):
        splitter_settings = dict(
            width=coupler_widths[1],
            width_taper=coupler_widths_tapers[1],
            length_taper=coupler_lengths_tapers[1],
            length_mmi=coupler_lengths_mmis[1],
            width_mmi=coupler_widths_mmis[1],
            gap_mmi=coupler_gaps_mmis[1],
            taper=taper_functions_mmis[1],
            straight=straight_functions_mmis[1],
            cross_section=cross_sections_mmis[1],
        )
        combiner_settings = dict(
            width=coupler_width,
            width_taper=coupler_width_taper,
            length_taper=coupler_length_taper,
            length_mmi=coupler_length_mmi,
            width_mmi=coupler_width_mmi,
            gap_mmi=coupler_gap_mmi,
            taper=taper,
            straight=straight,
            cross_section=cross_section,
        )
        splitter1 = gf.get_component(splitter, settings=None, **splitter_settings)
        combiner1 = gf.get_component(splitter, settings=None, **combiner_settings)

        stage = c << gf.get_component(
            mzi,
            splitter=splitter1,
            combiner=combiner1,
            with_splitter=False,
            delta_length=delta_length,
            **kwargs,
        )
        splitter_settings = combiner_settings

        stages.append(stage)
        c.add_ports(stage.ports.filter(port_type="electrical"))

    for stage in stages:
        stage.connect("o1", sprevious.ports["o4"])
        # stage.connect('o2', sprevious.ports['o1'])
        sprevious = stage

    for port in cp1.ports.filter(orientation=180, port_type="optical"):
        c.add_port(port.name, port=port)

    for port in sprevious.ports.filter(orientation=0, port_type="optical"):
        c.add_port(f"o_{port.name}", port=port)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.auto_rename_ports()
    return c

mzi_lattice_mmi

mzi_pads_center

mzi_pads_center

mzi_pads_center(
    ps_top: ComponentSpec = "straight_heater_metal",
    ps_bot: ComponentSpec = "straight_heater_metal",
    mzi: ComponentSpec = "mzi",
    pad: ComponentSpec = "pad_small",
    length_x: float = 500,
    length_y: float = 40,
    mzi_sig_top: str | None = "top_r_e2",
    mzi_gnd_top: str | None = "top_l_e2",
    mzi_sig_bot: str | None = "bot_l_e2",
    mzi_gnd_bot: str | None = "bot_r_e2",
    pad_sig_bot: str = "e1_1_1",
    pad_sig_top: str = "e3_1_3",
    pad_gnd_bot: str = "e4_1_2",
    pad_gnd_top: str = "e2_1_2",
    delta_length: float = 40.0,
    cross_section: CrossSectionSpec = "strip",
    cross_section_metal: CrossSectionSpec = "metal_routing",
    pad_pitch: float | str = "pad_pitch",
    auto_taper: bool = False,
    **kwargs: Any
) -> gf.Component

Return Mzi phase shifter with pads in the middle.

GND is the middle pad and is shared between top and bottom phase shifters.

Parameters:

Name Type Description Default
ps_top ComponentSpec

phase shifter top.

'straight_heater_metal'
ps_bot ComponentSpec

phase shifter bottom.

'straight_heater_metal'
mzi ComponentSpec

interferometer.

'mzi'
pad ComponentSpec

pad function.

'pad_small'
length_x float

horizontal length.

500
length_y float

vertical length.

40
mzi_sig_top str | None

port name for top phase shifter signal. None if no connection.

'top_r_e2'
mzi_gnd_top str | None

port name for top phase shifter GND. None if no connection.

'top_l_e2'
mzi_sig_bot str | None

port name for top phase shifter signal. None if no connection.

'bot_l_e2'
mzi_gnd_bot str | None

port name for top phase shifter GND. None if no connection.

'bot_r_e2'
pad_sig_bot str

port name for top pad.

'e1_1_1'
pad_sig_top str

port name for top pad.

'e3_1_3'
pad_gnd_bot str

port name for top pad.

'e4_1_2'
pad_gnd_top str

port name for top pad.

'e2_1_2'
delta_length float

mzi length imbalance.

40.0
cross_section CrossSectionSpec

for the mzi.

'strip'
cross_section_metal CrossSectionSpec

for routing metal.

'metal_routing'
pad_pitch float | str

pad pitch in um.

'pad_pitch'
auto_taper bool

add taper if cross_section width is different between mzi and pad.

False
kwargs Any

routing settings.

{}
Source code in gdsfactory/components/mzis/mzi_pads_center.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
 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
@gf.cell_with_module_name(schematic_function=ckt_schematic, tags=["mzis"])
def mzi_pads_center(
    ps_top: ComponentSpec = "straight_heater_metal",
    ps_bot: ComponentSpec = "straight_heater_metal",
    mzi: ComponentSpec = "mzi",
    pad: ComponentSpec = "pad_small",
    length_x: float = 500,
    length_y: float = 40,
    mzi_sig_top: str | None = "top_r_e2",
    mzi_gnd_top: str | None = "top_l_e2",
    mzi_sig_bot: str | None = "bot_l_e2",
    mzi_gnd_bot: str | None = "bot_r_e2",
    pad_sig_bot: str = "e1_1_1",
    pad_sig_top: str = "e3_1_3",
    pad_gnd_bot: str = "e4_1_2",
    pad_gnd_top: str = "e2_1_2",
    delta_length: float = 40.0,
    cross_section: CrossSectionSpec = "strip",
    cross_section_metal: CrossSectionSpec = "metal_routing",
    pad_pitch: float | str = "pad_pitch",
    auto_taper: bool = False,
    **kwargs: Any,
) -> gf.Component:
    """Return Mzi phase shifter with pads in the middle.

    GND is the middle pad and is shared between top and bottom phase shifters.

    Args:
        ps_top: phase shifter top.
        ps_bot: phase shifter bottom.
        mzi: interferometer.
        pad: pad function.
        length_x: horizontal length.
        length_y: vertical length.
        mzi_sig_top: port name for top phase shifter signal. None if no connection.
        mzi_gnd_top: port name for top phase shifter GND. None if no connection.
        mzi_sig_bot: port name for top phase shifter signal. None if no connection.
        mzi_gnd_bot: port name for top phase shifter GND. None if no connection.
        pad_sig_bot: port name for top pad.
        pad_sig_top: port name for top pad.
        pad_gnd_bot: port name for top pad.
        pad_gnd_top: port name for top pad.
        delta_length: mzi length imbalance.
        cross_section: for the mzi.
        cross_section_metal: for routing metal.
        pad_pitch: pad pitch in um.
        auto_taper: add taper if cross_section width is different between mzi and pad.
        kwargs: routing settings.
    """
    c = gf.Component()

    pad_pitch = gf.get_constant(pad_pitch)

    assert isinstance(pad_pitch, float)

    mzi_ps = gf.get_component(
        mzi,
        length_x=length_x,
        straight_x_top=ps_top,
        straight_x_bot=ps_bot,
        length_y=length_y,
        delta_length=delta_length,
        cross_section=cross_section,
        auto_rename_ports=False,
    )

    port_names = [p.name for p in mzi_ps.ports]
    for port_name in [mzi_sig_top, mzi_gnd_top, mzi_sig_bot, mzi_gnd_bot]:
        if port_name and port_name not in port_names:
            raise ValueError(f"port {port_name!r} not in {port_names}")

    m = c << mzi_ps
    pads = c << gf.components.array(
        component=pad, columns=3, rows=1, column_pitch=pad_pitch
    )
    pads.x = m.x
    pads.y = m.y

    if mzi_sig_top is not None:
        gf.routing.route_bundle_electrical(
            c,
            m.ports[mzi_sig_bot],
            pads.ports[pad_sig_bot],
            cross_section=cross_section_metal,
            auto_taper=auto_taper,
            **kwargs,
        )

    if mzi_gnd_bot:
        gf.routing.route_bundle_electrical(
            c,
            m.ports[mzi_gnd_bot],
            pads.ports[pad_gnd_bot],
            cross_section=cross_section_metal,
            auto_taper=auto_taper,
            **kwargs,
        )

    if mzi_gnd_top:
        gf.routing.route_bundle_electrical(
            c,
            m.ports[mzi_gnd_top],
            pads.ports[pad_gnd_top],
            cross_section=cross_section_metal,
            auto_taper=auto_taper,
            **kwargs,
        )

    if mzi_sig_top:
        gf.routing.route_bundle_electrical(
            c,
            m.ports[mzi_sig_top],
            pads.ports[pad_sig_top],
            cross_section=cross_section_metal,
            auto_taper=auto_taper,
            **kwargs,
        )

    c.add_ports(m.ports)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

mzi_pads_center

mzi_phase_shifter module-attribute

mzi_phase_shifter = partial(
    mzi,
    straight_x_top="straight_heater_metal",
    length_x=200,
)

mzi_phase_shifter

mzi_phase_shifter_top_heater_metal module-attribute

mzi_phase_shifter_top_heater_metal = partial(
    mzi_phase_shifter,
    straight_x_top="straight_heater_metal",
)

mzi_phase_shifter_top_heater_metal

mzi_pin module-attribute

mzi_pin = partial(
    mzi,
    straight_x_top="straight_pin",
    cross_section_x_top="pin",
    delta_length=0.0,
    length_x=100,
)

mzi_pin

mzit

mzit

mzit(
    w0: float = 0.5,
    w1: float = 0.45,
    w2: float = 0.55,
    dy: Delta = 2.0,
    delta_length: float = 10.0,
    length: float = 1.0,
    coupler_length1: float = 5.0,
    coupler_length2: float = 10.0,
    coupler_gap1: float = 0.2,
    coupler_gap2: float = 0.3,
    taper: ComponentSpec = "taper",
    taper_length: float = 5.0,
    bend90: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    coupler1: ComponentSpec | None = "coupler",
    coupler2: ComponentSpec = "coupler",
    cross_section: str = "strip",
) -> Component

Mzi tolerant to fabrication variations.

based on Yufei Xing thesis http://photonics.intec.ugent.be/publications/PhD.asp?ID=250

Parameters:

Name Type Description Default
w0 float

input waveguide width (um).

0.5
w1 float

narrow waveguide width (um).

0.45
w2 float

wide waveguide width (um).

0.55
dy Delta

port to port vertical spacing.

2.0
delta_length float

length difference between arms (um).

10.0
length float

shared length for w1 and w2.

1.0
coupler_length1 float

length of coupler1.

5.0
coupler_length2 float

length of coupler2.

10.0
coupler_gap1 float

coupler1.

0.2
coupler_gap2 float

coupler2.

0.3
taper ComponentSpec

taper spec.

'taper'
taper_length float

from w0 to w1.

5.0
bend90 ComponentSpec

bend spec.

'bend_euler'
straight ComponentSpec

spec.

'straight'
coupler1 ComponentSpec | None

coupler1 spec (optional).

'coupler'
coupler2 ComponentSpec

coupler2 spec.

'coupler'
cross_section str

cross_section spec.

           cp1

4 2 __ __ 3w0t2 _w2_ \ / \ \ length1 / | ============== gap1 | / \ | / _____w0___t1 w1 | 3 1 4 \ | | | 2 2 | | __ __w0_t1_w1/ | \ / | \ length2 / | ============== gap2 | / \ | | __/ \ E0_w0__t2 __w1______/ 1 1 cp2

'strip'
Source code in gdsfactory/components/mzis/mzit.py
 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
 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
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzit(
    w0: float = 0.5,
    w1: float = 0.45,
    w2: float = 0.55,
    dy: Delta = 2.0,
    delta_length: float = 10.0,
    length: float = 1.0,
    coupler_length1: float = 5.0,
    coupler_length2: float = 10.0,
    coupler_gap1: float = 0.2,
    coupler_gap2: float = 0.3,
    taper: ComponentSpec = "taper",
    taper_length: float = 5.0,
    bend90: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    coupler1: ComponentSpec | None = "coupler",
    coupler2: ComponentSpec = "coupler",
    cross_section: str = "strip",
) -> Component:
    r"""Mzi tolerant to fabrication variations.

    based on Yufei Xing thesis
    http://photonics.intec.ugent.be/publications/PhD.asp?ID=250

    Args:
        w0: input waveguide width (um).
        w1: narrow waveguide width (um).
        w2: wide waveguide width (um).
        dy: port to port vertical spacing.
        delta_length: length difference between arms (um).
        length: shared length for w1 and w2.
        coupler_length1: length of coupler1.
        coupler_length2: length of coupler2.
        coupler_gap1: coupler1.
        coupler_gap2: coupler2.
        taper: taper spec.
        taper_length: from w0 to w1.
        bend90: bend spec.
        straight: spec.
        coupler1: coupler1 spec (optional).
        coupler2: coupler2 spec.
        cross_section: cross_section spec.

                           cp1
            4   2 __                  __  3___w0_t2   _w2___
                    \                /                      \
                     \    length1   /                        |
                      ============== gap1                    |
                     /              \                        |
                  __/                \_____w0___t1   _w1     |
            3   1                        4               \   |
                                                         |   |
            2   2                                        |   |
                  __                  __w0____t1____w1___/   |
                    \                /                       |
                     \    length2   /                        |
                      ============== gap2                    |
                     /               \                       |                       |
                  __/                 \ E0_w0__t2 __w1______/
            1   1
                           cp2


    """
    c = gf.Component()

    cp2 = c << gf.get_component(
        coupler2,
        length=coupler_length2,
        gap=coupler_gap2,
        dy=dy,
        cross_section=cross_section,
    )

    # inner arm (w1)
    t1 = c << gf.get_component(
        taper,
        width1=w0,
        width2=w1,
        length=taper_length,
        cross_section=cross_section,
    )
    t1.connect("o1", cp2.ports["o3"])

    b1 = gf.get_component(bend90, cross_section=cross_section, width=w1)
    b1t = c << b1
    b1b = c << b1

    b1b.connect("o1", t1.ports["o2"])
    b1t.connect("o1", b1b.ports["o2"])

    t3b = c << gf.get_component(
        taper,
        width1=w1,
        width2=w2,
        length=taper_length,
        cross_section=cross_section,
    )
    t3b.connect("o1", b1t.ports["o2"])
    wgs2 = c << gf.get_component(
        straight, length=length, cross_section=cross_section, width=w2
    )
    wgs2.connect("o1", t3b.ports["o2"])
    t20i = c << gf.get_component(
        taper,
        width1=w2,
        width2=w0,
        length=taper_length,
        cross_section=cross_section,
    )
    t20i.connect("o1", wgs2.ports["o2"])

    # outer_arm (w2)
    t2 = c << gf.get_component(
        taper,
        width1=w0,
        width2=w2,
        length=taper_length,
        cross_section=cross_section,
    )
    t2.connect("o1", cp2.ports["o4"])

    dx = (delta_length - 2 * dy) / 2
    assert delta_length >= 4 * dy, (
        f"`delta_length`={delta_length} needs to be at least {4 * dy}"
    )

    wg2b = c << gf.get_component(
        straight, length=dx, cross_section=cross_section, width=w2
    )
    wg2b.connect("o1", t2.ports["o2"])

    b2 = gf.get_component(bend90, cross_section=cross_section, width=w2)
    b2t = c << b2
    b2b = c << b2
    wy = gf.get_component(
        straight, length=2 * dy, cross_section=cross_section, width=w2
    )
    wx = gf.get_component(straight, length=dx, cross_section=cross_section, width=w2)

    b2b.connect("o1", wg2b.ports["o2"])

    # vertical straight
    wg2y = c << wy
    wg2y.connect("o1", b2b.ports["o2"])
    b2t.connect("o1", wg2y.ports["o2"])

    wg2t = c << wx
    wg2t.connect("o1", b2t.ports["o2"])

    t3t = c << gf.get_component(
        taper,
        width1=w2,
        width2=w1,
        length=taper_length,
        cross_section=cross_section,
    )
    t3t.connect("o1", wg2t.ports["o2"])
    wgs1 = c << gf.get_component(
        straight, length=length, cross_section=cross_section, width=w1
    )
    wgs1.connect("o1", t3t.ports["o2"])
    t20o = c << gf.get_component(
        taper,
        width1=w1,
        width2=w0,
        length=taper_length,
        cross_section=cross_section,
    )
    t20o.connect("o1", wgs1.ports["o2"])

    if coupler1 is not None:
        cp1 = c << gf.get_component(
            coupler1,
            length=coupler_length1,
            gap=coupler_gap1,
            dy=dy,
            cross_section=cross_section,
        )

        cp1.connect("o3", t20o.ports["o2"])
        cp1.connect("o4", t20i.ports["o2"])
        c.add_port("W3", port=cp1.ports["o2"])
        c.add_port("W2", port=cp1.ports["o1"])
    else:
        c.add_port("W3", port=t20o.ports["o2"])
        c.add_port("W2", port=t20i.ports["o2"])

    c.add_port("o2", port=cp2.ports["o2"])
    c.add_port("o1", port=cp2.ports["o1"])
    c.auto_rename_ports()
    return c

mzit_lattice

mzit_lattice(
    coupler_lengths: Sequence[float] = (10.0, 20.0),
    coupler_gaps: Sequence[float] = (0.2, 0.3),
    delta_lengths: Sequence[float] = (10.0,),
    mzi: ComponentSpec = mzit,
) -> Component

Mzi fab tolerant lattice filter.

                cp1
o4  o2 __                  __ o3___w0_t2   _w2___
         \                /                      \
                 \    length1   /                        |
           ============== gap1                    |
          /              \                        |
       __/                \_____w0___t1   _w1     |
o3  o1                       o4               \   | .
                 ...                          |   | .
o2  o2                    o3                  |   | .
       __                  _____w0___t1___w1__/   |
         \                /                       |
          \    lengthN   /                        |
           ============== gapN                    |
          /               \                       |
       __/                 \_                     |
o1  o1                      \___w0___t2___w1_____/
                cpN       o4
Source code in gdsfactory/components/mzis/mzit.py
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
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzit_lattice(
    coupler_lengths: Sequence[float] = (10.0, 20.0),
    coupler_gaps: Sequence[float] = (0.2, 0.3),
    delta_lengths: Sequence[float] = (10.0,),
    mzi: ComponentSpec = mzit,
) -> Component:
    r"""Mzi fab tolerant lattice filter.

    ```text
                    cp1
    o4  o2 __                  __ o3___w0_t2   _w2___
             \                /                      \
                     \    length1   /                        |
               ============== gap1                    |
              /              \                        |
           __/                \_____w0___t1   _w1     |
    o3  o1                       o4               \   | .
                     ...                          |   | .
    o2  o2                    o3                  |   | .
           __                  _____w0___t1___w1__/   |
             \                /                       |
              \    lengthN   /                        |
               ============== gapN                    |
              /               \                       |
           __/                 \_                     |
    o1  o1                      \___w0___t2___w1_____/
                    cpN       o4
    ```


    """
    if len(coupler_lengths) != len(coupler_gaps):
        raise ValueError(
            f"Got {len(coupler_lengths)} coupler_lengths and "
            f"{len(coupler_gaps)} coupler_gaps"
        )
    if len(coupler_lengths) != len(delta_lengths) + 1:
        raise ValueError(
            f"Got {len(coupler_lengths)} coupler_lengths and "
            f"{len(delta_lengths)} delta_lengths. "
            "You need one more coupler_length than delta_lengths "
        )

    assert len(coupler_lengths) >= 2

    c = Component()

    cp1 = coupler0 = c << gf.get_component(
        mzi,
        coupler_gap1=coupler_gaps[0],
        coupler_gap2=coupler_gaps[1],
        coupler_length1=coupler_lengths[0],
        coupler_length2=coupler_lengths[1],
        delta_length=delta_lengths[0],
    )

    couplers = [
        c
        << gf.get_component(
            mzi,
            coupler_gap2=coupler_gap,
            coupler_length2=coupler_length,
            coupler1=None,
            delta_length=delta_length,
        )
        for coupler_length, coupler_gap, delta_length in zip(
            coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:], strict=False
        )
    ]

    for i, coupler in enumerate(couplers):
        if i % 2 == 0:
            coupler.dmirror()
        coupler.connect("o3", coupler0.ports["o1"])
        coupler.connect("o4", coupler0.ports["o2"])
        coupler0 = coupler

    c.add_port("o1", port=coupler0.ports["o1"])
    c.add_port("o2", port=coupler0.ports["o2"])
    c.add_port("o3", port=cp1.ports["o3"])
    c.add_port("o4", port=cp1.ports["o4"])
    return c

mzit

mzit_lattice

mzit_lattice(
    coupler_lengths: Sequence[float] = (10.0, 20.0),
    coupler_gaps: Sequence[float] = (0.2, 0.3),
    delta_lengths: Sequence[float] = (10.0,),
    mzi: ComponentSpec = mzit,
) -> Component

Mzi fab tolerant lattice filter.

                cp1
o4  o2 __                  __ o3___w0_t2   _w2___
         \                /                      \
                 \    length1   /                        |
           ============== gap1                    |
          /              \                        |
       __/                \_____w0___t1   _w1     |
o3  o1                       o4               \   | .
                 ...                          |   | .
o2  o2                    o3                  |   | .
       __                  _____w0___t1___w1__/   |
         \                /                       |
          \    lengthN   /                        |
           ============== gapN                    |
          /               \                       |
       __/                 \_                     |
o1  o1                      \___w0___t2___w1_____/
                cpN       o4
Source code in gdsfactory/components/mzis/mzit.py
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
@gf.cell_with_module_name(schematic_function=mzi_2x2_schematic, tags=["mzis"])
def mzit_lattice(
    coupler_lengths: Sequence[float] = (10.0, 20.0),
    coupler_gaps: Sequence[float] = (0.2, 0.3),
    delta_lengths: Sequence[float] = (10.0,),
    mzi: ComponentSpec = mzit,
) -> Component:
    r"""Mzi fab tolerant lattice filter.

    ```text
                    cp1
    o4  o2 __                  __ o3___w0_t2   _w2___
             \                /                      \
                     \    length1   /                        |
               ============== gap1                    |
              /              \                        |
           __/                \_____w0___t1   _w1     |
    o3  o1                       o4               \   | .
                     ...                          |   | .
    o2  o2                    o3                  |   | .
           __                  _____w0___t1___w1__/   |
             \                /                       |
              \    lengthN   /                        |
               ============== gapN                    |
              /               \                       |
           __/                 \_                     |
    o1  o1                      \___w0___t2___w1_____/
                    cpN       o4
    ```


    """
    if len(coupler_lengths) != len(coupler_gaps):
        raise ValueError(
            f"Got {len(coupler_lengths)} coupler_lengths and "
            f"{len(coupler_gaps)} coupler_gaps"
        )
    if len(coupler_lengths) != len(delta_lengths) + 1:
        raise ValueError(
            f"Got {len(coupler_lengths)} coupler_lengths and "
            f"{len(delta_lengths)} delta_lengths. "
            "You need one more coupler_length than delta_lengths "
        )

    assert len(coupler_lengths) >= 2

    c = Component()

    cp1 = coupler0 = c << gf.get_component(
        mzi,
        coupler_gap1=coupler_gaps[0],
        coupler_gap2=coupler_gaps[1],
        coupler_length1=coupler_lengths[0],
        coupler_length2=coupler_lengths[1],
        delta_length=delta_lengths[0],
    )

    couplers = [
        c
        << gf.get_component(
            mzi,
            coupler_gap2=coupler_gap,
            coupler_length2=coupler_length,
            coupler1=None,
            delta_length=delta_length,
        )
        for coupler_length, coupler_gap, delta_length in zip(
            coupler_lengths[2:], coupler_gaps[2:], delta_lengths[1:], strict=False
        )
    ]

    for i, coupler in enumerate(couplers):
        if i % 2 == 0:
            coupler.dmirror()
        coupler.connect("o3", coupler0.ports["o1"])
        coupler.connect("o4", coupler0.ports["o2"])
        coupler0 = coupler

    c.add_port("o1", port=coupler0.ports["o1"])
    c.add_port("o2", port=coupler0.ports["o2"])
    c.add_port("o3", port=cp1.ports["o3"])
    c.add_port("o4", port=cp1.ports["o4"])
    return c

mzit_lattice

mzm module-attribute

mzm = partial(
    mzi_phase_shifter,
    straight_x_top="straight_pin",
    straight_x_bot="straight_pin",
)

mzm

pads

bump_pad

bump_pad

bump_pad(
    size: float = 36.244,
    layer: LayerSpec = "MTOP",
    port_width: float = 10.0,
    port_layer: LayerSpec = "M2",
    port_type: str = "pad",
    add_via: bool = True,
) -> Component

Returns rectangular pad with ports.

Parameters:

Name Type Description Default
size float

octagon edge of octagon.

36.244
layer LayerSpec

bump pad layer.

'MTOP'
port_width float

width of the port for electrical routing.

10.0
port_layer LayerSpec

layer of the port for electrical routing.

'M2'
port_type str

port type for pad port.

'pad'
add_via bool

whether to add a via stack.

True
Source code in gdsfactory/components/pads/bump_pad.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
@gf.cell_with_module_name(tags=["pads"])
def bump_pad(
    size: float = 36.244,
    layer: LayerSpec = "MTOP",
    port_width: float = 10.0,
    port_layer: LayerSpec = "M2",
    port_type: str = "pad",
    add_via: bool = True,
) -> Component:
    """Returns rectangular pad with ports.

    Args:
        size: octagon edge of octagon.
        layer: bump pad layer.
        port_width: width of the port for electrical routing.
        port_layer: layer of the port for electrical routing.
        port_type: port type for pad port.
        add_via: whether to add a via stack.
    """
    c = Component()
    layer = gf.get_layer(layer)
    size_ = gf.get_constant(size)
    rect = octagon(
        side_length=size, layer=layer, port_width=port_width, port_type=port_type
    )
    c_ref = c.add_ref(rect)
    if add_via:
        via_n = c << via_stack()
        via_n.y = c_ref.ports["o5"].y - via_n.ports["e1"].width / 2
        p = via_n.ports["e2"]
        c.add_port(
            name="e2",
            center=p.center,
            width=p.width,
            orientation=p.orientation,
            port_type=p.port_type,
            layer=port_layer,
        )

        via_e = c << via_stack()
        via_e.x = c_ref.ports["o3"].x - via_e.ports["e1"].width / 2
        p = via_e.ports["e3"]
        c.add_port(
            name="e3",
            center=p.center,
            width=p.width,
            orientation=p.orientation,
            port_type=p.port_type,
            layer=port_layer,
        )

        via_s = c << via_stack()
        via_s.y = c_ref.ports["o1"].y + via_s.ports["e1"].width / 2
        p = via_s.ports["e4"]
        c.add_port(
            name="e4",
            center=p.center,
            width=p.width,
            orientation=p.orientation,
            port_type=p.port_type,
            layer=port_layer,
        )

        via_w = c << via_stack()
        via_w.x = c_ref.ports["o7"].x + via_w.ports["e1"].width / 2
        p = via_w.ports["e1"]
        c.add_port(
            name="e1",
            center=p.center,
            width=p.width,
            orientation=p.orientation,
            port_type=p.port_type,
            layer=port_layer,
        )
    else:
        for i, j in enumerate([1, 3, 5, 7]):
            p = c_ref.ports[f"o{j}"]
            c.add_port(
                name=f"e{i + 1}",
                center=p.center,
                width=port_width,
                orientation=p.orientation,
                port_type=port_type,
                layer=layer,
            )
    c.info["size"] = size_

    elec = [p for p in c.ports if p.port_type in {"electrical", "pad"}]
    if elec:
        c.create_pin(ports=elec, name="pad")

    return c

bump_pad_grid

bump_pad_grid(
    columns: int = 6,
    rows: int = 6,
    column_pitch: float = 121.89,
    row_pitch: float = 132.66,
    offset: float = 66.33,
    port_width: float = 10,
    port_layer: LayerSpec = "M2",
    size: float = 36.244,
    layer: LayerSpec = "MTOP",
    auto_rename_ports: bool = False,
    skip_pads: list[tuple[int, int]] | None = None,
    add_via: bool = True,
) -> Component

Returns 2D array of bump pads.

Parameters:

Name Type Description Default
columns int

number of columns.

6
rows int

number of rows.

6
column_pitch float

x pitch.

121.89
row_pitch float

y pitch.

132.66
offset float

offset for alternating columns.

66.33
port_width float

width of the port for electrical routing.

10
port_layer LayerSpec

layer of the port for electrical routing.

'M2'
size float

pad size.

36.244
layer LayerSpec

bump pad layer.

'MTOP'
auto_rename_ports bool

True to auto rename ports.

False
skip_pads list[tuple[int, int]] | None

list of (col, row) tuples to skip.

None
add_via bool

whether to add a via stack.

True
Source code in gdsfactory/components/pads/bump_pad.py
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
@gf.cell_with_module_name(tags=["pads"])
def bump_pad_grid(
    columns: int = 6,
    rows: int = 6,
    column_pitch: float = 121.89,
    row_pitch: float = 132.66,
    offset: float = 66.33,
    port_width: float = 10,
    port_layer: LayerSpec = "M2",
    size: float = 36.244,
    layer: LayerSpec = "MTOP",
    auto_rename_ports: bool = False,
    skip_pads: list[tuple[int, int]] | None = None,
    add_via: bool = True,
) -> Component:
    """Returns 2D array of bump pads.

    Args:
        columns: number of columns.
        rows: number of rows.
        column_pitch: x pitch.
        row_pitch: y pitch.
        offset: offset for alternating columns.
        port_width: width of the port for electrical routing.
        port_layer: layer of the port for electrical routing.
        size: pad size.
        layer: bump pad layer.
        auto_rename_ports: True to auto rename ports.
        skip_pads: list of (col, row) tuples to skip.
        add_via: whether to add a via stack.
    """
    c = Component()

    pad_kwargs: dict[str, Any] = {}
    if layer is not None:
        pad_kwargs["layer"] = layer
    if size is not None:
        pad_kwargs["size"] = size

    pad_component = bump_pad(
        size=size,
        layer=layer,
        port_width=port_width,
        port_layer=port_layer,
        add_via=add_via,
    )

    for col in range(columns):
        for row in range(rows):
            if skip_pads is not None and (col, row) in skip_pads:
                continue
            pad = c << pad_component
            center = (col * column_pitch, row * row_pitch + (col % 2) * offset)
            pad.center = center
            c.add_ports(pad.ports, prefix=f"e{row + 1}_{col + 1}_")

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    if auto_rename_ports:
        c.auto_rename_ports()
    return c

bump_pad

bump_pad_grid

bump_pad_grid(
    columns: int = 6,
    rows: int = 6,
    column_pitch: float = 121.89,
    row_pitch: float = 132.66,
    offset: float = 66.33,
    port_width: float = 10,
    port_layer: LayerSpec = "M2",
    size: float = 36.244,
    layer: LayerSpec = "MTOP",
    auto_rename_ports: bool = False,
    skip_pads: list[tuple[int, int]] | None = None,
    add_via: bool = True,
) -> Component

Returns 2D array of bump pads.

Parameters:

Name Type Description Default
columns int

number of columns.

6
rows int

number of rows.

6
column_pitch float

x pitch.

121.89
row_pitch float

y pitch.

132.66
offset float

offset for alternating columns.

66.33
port_width float

width of the port for electrical routing.

10
port_layer LayerSpec

layer of the port for electrical routing.

'M2'
size float

pad size.

36.244
layer LayerSpec

bump pad layer.

'MTOP'
auto_rename_ports bool

True to auto rename ports.

False
skip_pads list[tuple[int, int]] | None

list of (col, row) tuples to skip.

None
add_via bool

whether to add a via stack.

True
Source code in gdsfactory/components/pads/bump_pad.py
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
@gf.cell_with_module_name(tags=["pads"])
def bump_pad_grid(
    columns: int = 6,
    rows: int = 6,
    column_pitch: float = 121.89,
    row_pitch: float = 132.66,
    offset: float = 66.33,
    port_width: float = 10,
    port_layer: LayerSpec = "M2",
    size: float = 36.244,
    layer: LayerSpec = "MTOP",
    auto_rename_ports: bool = False,
    skip_pads: list[tuple[int, int]] | None = None,
    add_via: bool = True,
) -> Component:
    """Returns 2D array of bump pads.

    Args:
        columns: number of columns.
        rows: number of rows.
        column_pitch: x pitch.
        row_pitch: y pitch.
        offset: offset for alternating columns.
        port_width: width of the port for electrical routing.
        port_layer: layer of the port for electrical routing.
        size: pad size.
        layer: bump pad layer.
        auto_rename_ports: True to auto rename ports.
        skip_pads: list of (col, row) tuples to skip.
        add_via: whether to add a via stack.
    """
    c = Component()

    pad_kwargs: dict[str, Any] = {}
    if layer is not None:
        pad_kwargs["layer"] = layer
    if size is not None:
        pad_kwargs["size"] = size

    pad_component = bump_pad(
        size=size,
        layer=layer,
        port_width=port_width,
        port_layer=port_layer,
        add_via=add_via,
    )

    for col in range(columns):
        for row in range(rows):
            if skip_pads is not None and (col, row) in skip_pads:
                continue
            pad = c << pad_component
            center = (col * column_pitch, row * row_pitch + (col % 2) * offset)
            pad.center = center
            c.add_ports(pad.ports, prefix=f"e{row + 1}_{col + 1}_")

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    if auto_rename_ports:
        c.auto_rename_ports()
    return c

bump_pad_grid

pad

pad

pad(
    size: Size | str = (100.0, 100.0),
    layer: LayerSpec = "MTOP",
    bbox_layers: tuple[LayerSpec, ...] | None = None,
    bbox_offsets: tuple[float, ...] | None = None,
    port_inclusion: float = 0,
    port_orientation: AngleInDegrees | None = 0,
    port_orientations: Ints | None = (180, 90, 0, -90),
    port_type: str = "pad",
) -> Component

Returns rectangular pad with ports.

Parameters:

Name Type Description Default
size Size | str

x, y size.

(100.0, 100.0)
layer LayerSpec

pad layer.

'MTOP'
bbox_layers tuple[LayerSpec, ...] | None

list of layers.

None
bbox_offsets tuple[float, ...] | None

Optional offsets for each layer with respect to size. positive grows, negative shrinks the size.

None
port_inclusion float

from edge.

0
port_orientation AngleInDegrees | None

in degrees for the center port.

0
port_orientations Ints | None

list of port_orientations to add. None does not add ports.

(180, 90, 0, -90)
port_type str

port type for pad port.

'pad'
Source code in gdsfactory/components/pads/pad.py
 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
@gf.cell_with_module_name(schematic_function=pad_schematic, tags=["pads"])
def pad(
    size: Size | str = (100.0, 100.0),
    layer: LayerSpec = "MTOP",
    bbox_layers: tuple[LayerSpec, ...] | None = None,
    bbox_offsets: tuple[float, ...] | None = None,
    port_inclusion: float = 0,
    port_orientation: AngleInDegrees | None = 0,
    port_orientations: Ints | None = (180, 90, 0, -90),
    port_type: str = "pad",
) -> Component:
    """Returns rectangular pad with ports.

    Args:
        size: x, y size.
        layer: pad layer.
        bbox_layers: list of layers.
        bbox_offsets: Optional offsets for each layer with respect to size.
            positive grows, negative shrinks the size.
        port_inclusion: from edge.
        port_orientation: in degrees for the center port.
        port_orientations: list of port_orientations to add. None does not add ports.
        port_type: port type for pad port.
    """
    c = Component()
    layer = gf.get_layer(layer)
    size_ = gf.get_constant(size)
    rect = gf.c.compass(
        size=size_,
        layer=layer,
        port_inclusion=port_inclusion,
        port_type="electrical",
        port_orientations=port_orientations,
    )
    c_ref = c.add_ref(rect)
    c.add_ports(c_ref.ports)
    c.info["size"] = size_
    c.info["xsize"] = size_[0]
    c.info["ysize"] = size_[1]

    if port_orientation is not None and port_orientation not in valid_port_orientations:
        raise ValueError(f"{port_orientation=} must be in {valid_port_orientations}")

    width = size_[1] if port_orientation in {0, 180} else size_[0]

    if port_orientation is not None:
        c.add_port(
            name="pad",
            port_type=port_type,
            layer=layer,
            center=(0, 0),
            orientation=port_orientation,
            width=width,
        )

    if bbox_layers and bbox_offsets:
        sizes: list[Size] = []
        for cladding_offset in bbox_offsets:
            size_new = (size_[0] + 2 * cladding_offset, size_[1] + 2 * cladding_offset)
            sizes.append(size_new)

        for layer, size_new in zip(bbox_layers, sizes, strict=False):
            c.add_ref(
                gf.c.compass(
                    size=size_new,
                    layer=layer,
                )
            )
    c.flatten()
    elec = [p for p in c.ports if p.port_type in {"electrical", "pad"}]
    if elec:
        c.create_pin(ports=elec, name="pad")
    return c

pad_array

pad_array(
    pad: ComponentSpec = "pad",
    columns: int = 6,
    rows: int = 1,
    column_pitch: float = 150.0,
    row_pitch: float = 150.0,
    port_orientation: AngleInDegrees = 0,
    size: Float2 | None = None,
    layer: LayerSpec | None = "MTOP",
    centered_ports: bool = False,
    auto_rename_ports: bool = False,
) -> Component

Returns 2D array of pads.

Parameters:

Name Type Description Default
pad ComponentSpec

pad element.

'pad'
columns int

number of columns.

6
rows int

number of rows.

1
column_pitch float

x pitch.

150.0
row_pitch float

y pitch.

150.0
port_orientation AngleInDegrees

port orientation in deg. None for low speed DC ports.

0
size Float2 | None

pad size.

None
layer LayerSpec | None

pad layer.

'MTOP'
centered_ports bool

True add ports to center. False add ports to the edge.

False
auto_rename_ports bool

True to auto rename ports.

False
Source code in gdsfactory/components/pads/pad.py
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
@gf.cell_with_module_name(schematic_function=pad_schematic, tags=["pads"])
def pad_array(
    pad: ComponentSpec = "pad",
    columns: int = 6,
    rows: int = 1,
    column_pitch: float = 150.0,
    row_pitch: float = 150.0,
    port_orientation: AngleInDegrees = 0,
    size: Float2 | None = None,
    layer: LayerSpec | None = "MTOP",
    centered_ports: bool = False,
    auto_rename_ports: bool = False,
) -> Component:
    """Returns 2D array of pads.

    Args:
        pad: pad element.
        columns: number of columns.
        rows: number of rows.
        column_pitch: x pitch.
        row_pitch: y pitch.
        port_orientation: port orientation in deg. None for low speed DC ports.
        size: pad size.
        layer: pad layer.
        centered_ports: True add ports to center. False add ports to the edge.
        auto_rename_ports: True to auto rename ports.
    """
    c = Component()

    pad_kwargs: dict[str, Any] = {}
    if layer is not None:
        pad_kwargs["layer"] = layer
    if size is not None:
        pad_kwargs["size"] = size
    pad_component = gf.get_component(
        pad,
        port_orientation=port_orientation,
        port_orientations=(port_orientation,) if not centered_ports else None,
        **pad_kwargs,
    )

    pad_size: Float2 = size or pad_component.info["size"]
    pad_layer: LayerSpec = layer or pad_component.ports[0].layer

    c.add_ref(
        pad_component,
        columns=columns,
        rows=rows,
        column_pitch=column_pitch,
        row_pitch=row_pitch,
    )
    width = pad_size[0] if int(port_orientation) in {90, 270} else pad_size[1]

    for col in range(columns):
        for row in range(rows):
            center = (col * column_pitch, row * row_pitch)
            port_orientation = int(port_orientation)
            center_list = [center[0], center[1]]

            if not centered_ports:
                if port_orientation == 0:
                    center_list[0] += pad_size[0] / 2
                elif port_orientation == 90:
                    center_list[1] += pad_size[1] / 2
                elif port_orientation == 180:
                    center_list[0] -= pad_size[0] / 2
                elif port_orientation == 270:
                    center_list[1] -= pad_size[1] / 2

            center = (center_list[0], center_list[1])
            c.add_port(
                name=f"e{row + 1}{col + 1}",
                center=center,
                width=width,
                orientation=port_orientation,
                port_type="electrical",
                layer=pad_layer,
            )
    if auto_rename_ports:
        c.auto_rename_ports()
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=f"pad_{port.name}")
    return c

pad

pad_array

pad_array(
    pad: ComponentSpec = "pad",
    columns: int = 6,
    rows: int = 1,
    column_pitch: float = 150.0,
    row_pitch: float = 150.0,
    port_orientation: AngleInDegrees = 0,
    size: Float2 | None = None,
    layer: LayerSpec | None = "MTOP",
    centered_ports: bool = False,
    auto_rename_ports: bool = False,
) -> Component

Returns 2D array of pads.

Parameters:

Name Type Description Default
pad ComponentSpec

pad element.

'pad'
columns int

number of columns.

6
rows int

number of rows.

1
column_pitch float

x pitch.

150.0
row_pitch float

y pitch.

150.0
port_orientation AngleInDegrees

port orientation in deg. None for low speed DC ports.

0
size Float2 | None

pad size.

None
layer LayerSpec | None

pad layer.

'MTOP'
centered_ports bool

True add ports to center. False add ports to the edge.

False
auto_rename_ports bool

True to auto rename ports.

False
Source code in gdsfactory/components/pads/pad.py
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
@gf.cell_with_module_name(schematic_function=pad_schematic, tags=["pads"])
def pad_array(
    pad: ComponentSpec = "pad",
    columns: int = 6,
    rows: int = 1,
    column_pitch: float = 150.0,
    row_pitch: float = 150.0,
    port_orientation: AngleInDegrees = 0,
    size: Float2 | None = None,
    layer: LayerSpec | None = "MTOP",
    centered_ports: bool = False,
    auto_rename_ports: bool = False,
) -> Component:
    """Returns 2D array of pads.

    Args:
        pad: pad element.
        columns: number of columns.
        rows: number of rows.
        column_pitch: x pitch.
        row_pitch: y pitch.
        port_orientation: port orientation in deg. None for low speed DC ports.
        size: pad size.
        layer: pad layer.
        centered_ports: True add ports to center. False add ports to the edge.
        auto_rename_ports: True to auto rename ports.
    """
    c = Component()

    pad_kwargs: dict[str, Any] = {}
    if layer is not None:
        pad_kwargs["layer"] = layer
    if size is not None:
        pad_kwargs["size"] = size
    pad_component = gf.get_component(
        pad,
        port_orientation=port_orientation,
        port_orientations=(port_orientation,) if not centered_ports else None,
        **pad_kwargs,
    )

    pad_size: Float2 = size or pad_component.info["size"]
    pad_layer: LayerSpec = layer or pad_component.ports[0].layer

    c.add_ref(
        pad_component,
        columns=columns,
        rows=rows,
        column_pitch=column_pitch,
        row_pitch=row_pitch,
    )
    width = pad_size[0] if int(port_orientation) in {90, 270} else pad_size[1]

    for col in range(columns):
        for row in range(rows):
            center = (col * column_pitch, row * row_pitch)
            port_orientation = int(port_orientation)
            center_list = [center[0], center[1]]

            if not centered_ports:
                if port_orientation == 0:
                    center_list[0] += pad_size[0] / 2
                elif port_orientation == 90:
                    center_list[1] += pad_size[1] / 2
                elif port_orientation == 180:
                    center_list[0] -= pad_size[0] / 2
                elif port_orientation == 270:
                    center_list[1] -= pad_size[1] / 2

            center = (center_list[0], center_list[1])
            c.add_port(
                name=f"e{row + 1}{col + 1}",
                center=center,
                width=width,
                orientation=port_orientation,
                port_type="electrical",
                layer=pad_layer,
            )
    if auto_rename_ports:
        c.auto_rename_ports()
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=f"pad_{port.name}")
    return c

pad_array

pad_array0 module-attribute

pad_array0 = partial(
    pad_array, port_orientation=0, columns=1, rows=3
)

pad_array0

pad_array180 module-attribute

pad_array180 = partial(
    pad_array, port_orientation=180, columns=1, rows=3
)

pad_array180

pad_array270 module-attribute

pad_array270 = partial(pad_array, port_orientation=270)

pad_array270

pad_array90 module-attribute

pad_array90 = partial(pad_array, port_orientation=90)

pad_array90

pad_gs

pad_gs(
    length: float = 100, cross_section: str = "gs"
) -> gf.Component
Source code in gdsfactory/components/pads/pad_gsg.py
89
90
91
@gf.cell_with_module_name(tags=["pads"])
def pad_gs(length: float = 100, cross_section: str = "gs") -> gf.Component:
    return gf.c.straight(cross_section=cross_section, length=length)

pad_gs

pad_gsg

High speed GSG pads.

pad_gsg_short

pad_gsg_short(
    size: Float2 = (22, 7),
    layer_metal: LayerSpec = "MTOP",
    metal_spacing: float = 5.0,
    short: bool = True,
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150,
    route_xsize: float = 50,
) -> gf.Component

Returns high speed GSG pads for calibrating the RF probes.

Parameters:

Name Type Description Default
size Float2

for the short.

(22, 7)
layer_metal LayerSpec

for the short.

'MTOP'
metal_spacing float

in um.

5.0
short bool

if False returns an open.

True
pad ComponentSpec

function for pad.

'pad'
pad_pitch float

in um.

150
route_xsize float

in um.

50
Source code in gdsfactory/components/pads/pad_gsg.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
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
@gf.cell_with_module_name(tags=["pads"])
def pad_gsg_short(
    size: Float2 = (22, 7),
    layer_metal: LayerSpec = "MTOP",
    metal_spacing: float = 5.0,
    short: bool = True,
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150,
    route_xsize: float = 50,
) -> gf.Component:
    """Returns high speed GSG pads for calibrating the RF probes.

    Args:
        size: for the short.
        layer_metal: for the short.
        metal_spacing: in um.
        short: if False returns an open.
        pad: function for pad.
        pad_pitch: in um.
        route_xsize: in um.
    """
    c = gf.Component()
    via = gf.c.rectangle(size=size, layer=layer_metal)
    gnd_top = c << via

    if short:
        _ = c << via
    gnd_bot = c << via

    gnd_bot.ymax = via.ymin
    gnd_top.ymin = via.ymax

    gnd_top.movex(-metal_spacing)
    gnd_bot.movex(-metal_spacing)

    pads = c << gf.components.array(
        pad, columns=1, rows=3, column_pitch=0, row_pitch=pad_pitch, centered=True
    )
    pads.xmin = via.xmax + route_xsize
    pads.y = 0

    gf.routing.route_quad(
        c, gnd_bot.ports["e4"], pads.ports["e1_1_1"], layer=layer_metal
    )
    gf.routing.route_quad(
        c,
        cast("kf.DPort", gnd_top.ports["e2"]),  # type: ignore[redundant-cast]
        cast("kf.DPort", pads.ports["e1_3_1"]),  # type: ignore[redundant-cast]
        layer=layer_metal,
    )
    gf.routing.route_quad(
        c,
        cast("kf.DPort", via.ports["e3"]),  # type: ignore[redundant-cast]
        cast("kf.DPort", pads.ports["e1_2_1"]),  # type: ignore[redundant-cast]
        layer=layer_metal,
    )
    return c

pad_gsg

pad_gsg_open module-attribute

pad_gsg_open = partial(pad_gsg_short, short=False)

pad_gsg_open

pad_gsg_short

pad_gsg_short(
    size: Float2 = (22, 7),
    layer_metal: LayerSpec = "MTOP",
    metal_spacing: float = 5.0,
    short: bool = True,
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150,
    route_xsize: float = 50,
) -> gf.Component

Returns high speed GSG pads for calibrating the RF probes.

Parameters:

Name Type Description Default
size Float2

for the short.

(22, 7)
layer_metal LayerSpec

for the short.

'MTOP'
metal_spacing float

in um.

5.0
short bool

if False returns an open.

True
pad ComponentSpec

function for pad.

'pad'
pad_pitch float

in um.

150
route_xsize float

in um.

50
Source code in gdsfactory/components/pads/pad_gsg.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
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
@gf.cell_with_module_name(tags=["pads"])
def pad_gsg_short(
    size: Float2 = (22, 7),
    layer_metal: LayerSpec = "MTOP",
    metal_spacing: float = 5.0,
    short: bool = True,
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150,
    route_xsize: float = 50,
) -> gf.Component:
    """Returns high speed GSG pads for calibrating the RF probes.

    Args:
        size: for the short.
        layer_metal: for the short.
        metal_spacing: in um.
        short: if False returns an open.
        pad: function for pad.
        pad_pitch: in um.
        route_xsize: in um.
    """
    c = gf.Component()
    via = gf.c.rectangle(size=size, layer=layer_metal)
    gnd_top = c << via

    if short:
        _ = c << via
    gnd_bot = c << via

    gnd_bot.ymax = via.ymin
    gnd_top.ymin = via.ymax

    gnd_top.movex(-metal_spacing)
    gnd_bot.movex(-metal_spacing)

    pads = c << gf.components.array(
        pad, columns=1, rows=3, column_pitch=0, row_pitch=pad_pitch, centered=True
    )
    pads.xmin = via.xmax + route_xsize
    pads.y = 0

    gf.routing.route_quad(
        c, gnd_bot.ports["e4"], pads.ports["e1_1_1"], layer=layer_metal
    )
    gf.routing.route_quad(
        c,
        cast("kf.DPort", gnd_top.ports["e2"]),  # type: ignore[redundant-cast]
        cast("kf.DPort", pads.ports["e1_3_1"]),  # type: ignore[redundant-cast]
        layer=layer_metal,
    )
    gf.routing.route_quad(
        c,
        cast("kf.DPort", via.ports["e3"]),  # type: ignore[redundant-cast]
        cast("kf.DPort", pads.ports["e1_2_1"]),  # type: ignore[redundant-cast]
        layer=layer_metal,
    )
    return c

pad_gsg_short

pad_rectangular module-attribute

pad_rectangular = partial(pad, size='pad_size')

pad_rectangular

pad_small module-attribute

pad_small = partial(pad, size=(80, 80))

pad_small

pads_shorted

pads_shorted

pads_shorted(
    pad: ComponentSpec = "pad",
    columns: int = 8,
    pad_pitch: float = 150.0,
    layer_metal: LayerSpec = "MTOP",
    metal_width: float = 10,
) -> Component

Returns a 1D array of shorted_pads.

Parameters:

Name Type Description Default
pad ComponentSpec

pad spec.

'pad'
columns int

number of columns.

8
pad_pitch float

in um

150.0
layer_metal LayerSpec

for the short.

'MTOP'
metal_width float

for the short.

10
Source code in gdsfactory/components/pads/pads_shorted.py
10
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
@gf.cell_with_module_name(tags=["pads"])
def pads_shorted(
    pad: ComponentSpec = "pad",
    columns: int = 8,
    pad_pitch: float = 150.0,
    layer_metal: LayerSpec = "MTOP",
    metal_width: float = 10,
) -> Component:
    """Returns a 1D array of shorted_pads.

    Args:
        pad: pad spec.
        columns: number of columns.
        pad_pitch: in um
        layer_metal: for the short.
        metal_width: for the short.
    """
    c = Component()
    pad = gf.get_component(pad)
    for i in range(columns):
        pad_ref = c.add_ref(pad)
        pad_ref.movex(i * pad_pitch - columns / 2 * pad_pitch + pad_pitch / 2)

    short = gf.c.rectangle(
        size=(pad_pitch * (columns - 1), metal_width),
        layer=layer_metal,
        centered=True,
    )
    c.add_ref(short)
    elec = [p for p in c.ports if p.port_type == "electrical"]
    if elec:
        c.create_pin(ports=elec, name="pad")
    return c

pads_shorted

rectangle_with_slits

rectangle_with_slits

rectangle_with_slits(
    size: Size = (100.0, 200.0),
    layer: LayerSpec = "WG",
    layer_slit: LayerSpec | None = None,
    centered: bool = False,
    port_type: str | None = None,
    slit_size: Size = (1.0, 1.0),
    slit_column_pitch: float = 20,
    slit_row_pitch: float = 20,
    slit_enclosure: float = 10,
) -> Component

Returns a rectangle with slits.

Metal slits reduce stress.

Parameters:

Name Type Description Default
size Size

(tuple) Width and height of rectangle.

(100.0, 200.0)
layer LayerSpec

Specific layer to put polygon geometry on.

'WG'
layer_slit LayerSpec | None

does a boolean NOT when None.

None
centered bool

True sets center to (0, 0), False sets south-west to (0, 0)

False
port_type str | None

for the rectangle.

None
slit_size Size

x, y slit size.

(1.0, 1.0)
slit_column_pitch float

pitch for columns of slits.

20
slit_row_pitch float

pitch for rows of slits.

20
slit_enclosure float

from slit to rectangle edge.

10
Source code in gdsfactory/components/pads/rectangle_with_slits.py
 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
 96
 97
 98
 99
100
101
102
103
104
@gf.cell_with_module_name(tags=["pads"])
def rectangle_with_slits(
    size: Size = (100.0, 200.0),
    layer: LayerSpec = "WG",
    layer_slit: LayerSpec | None = None,
    centered: bool = False,
    port_type: str | None = None,
    slit_size: Size = (1.0, 1.0),
    slit_column_pitch: float = 20,
    slit_row_pitch: float = 20,
    slit_enclosure: float = 10,
) -> Component:
    """Returns a rectangle with slits.

    Metal slits reduce stress.

    Args:
        size: (tuple) Width and height of rectangle.
        layer: Specific layer to put polygon geometry on.
        layer_slit: does a boolean NOT when None.
        centered: True sets center to (0, 0), False sets south-west to (0, 0)
        port_type: for the rectangle.
        slit_size: x, y slit size.
        slit_column_pitch: pitch for columns of slits.
        slit_row_pitch: pitch for rows of slits.
        slit_enclosure: from slit to rectangle edge.

        slit_enclosure
        _____________________________________
        |<--->                              |
        |                                   |
        |      ______________________       |
        |     |                      |      |
        |     |                      | slit_size[1]
        |  _  |______________________|      |
        |  |                                |
        |  | slit_row_pitch                 |
        |  |                                |  size[1]
        |  |   ______________________       |
        |  |  |                      |      |
        |  |  |                      |      |
        |  _  |______________________|      |
        |     <--------------------->       |
        |            slit_size[0]           |
        |___________________________________|
                        size[0]
    """
    c = Component()
    layer_tuple = gf.get_layer_tuple(layer)

    rectangle = gf.c.rectangle(
        size=size, layer=layer, port_type=port_type, centered=centered
    )
    r = c << rectangle
    c.add_ports(r.ports)
    columns = int(np.floor((size[0] - 2 * slit_enclosure) / slit_column_pitch))
    rows = int(np.floor((size[1] - 2 * slit_enclosure) / slit_row_pitch))

    if layer_slit is None:
        layer2 = (layer_tuple[0], layer_tuple[1] + 1)
        slit = gf.c.rectangle(size=slit_size, port_type=None, layer=layer2)
        slits = gf.c.array(
            slit,
            columns=columns,
            rows=rows,
            column_pitch=slit_column_pitch,
            row_pitch=slit_row_pitch,
            centered=centered,
        )
        slits_ref = c << slits
        slits_ref.center = r.center
        c = gf.boolean(
            rectangle,
            slits_ref,
            operation="not",
            layer1=layer,
            layer2=layer2,
            layer=layer,
        )
        c.add_ports(rectangle.ports)

    else:
        slit = gf.c.rectangle(size=slit_size, port_type=None, layer=layer_slit)
        slits_ref = c << gf.c.array(
            slit,
            columns=columns,
            rows=rows,
            column_pitch=slit_column_pitch,
            row_pitch=slit_row_pitch,
            centered=centered,
        )
        slits_ref.center = r.center
    return c

rectangle_with_slits

pcms

alignment_mark_cross

alignment_mark_cross

alignment_mark_cross(
    arm_width: float = 2.0,
    arm_length: float = 20.0,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component

Custom alignment cross mark for lithography alignment.

Creates a cross shape centered at the origin composed of two overlapping rectangles (horizontal and vertical arms).

Parameters:

Name Type Description Default
arm_width float

Width of each arm in um.

2.0
arm_length float

Length of each arm in um (full extent from tip to tip is 2 * arm_length).

20.0
layer LayerSpec

Layer specification for the cross geometry.

'WG'
port_type str | None

Optional port type. If provided, ports are added at the four arm tips.

None
Source code in gdsfactory/components/pcms/alignment_mark_cross.py
10
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
@gf.cell_with_module_name(tags=["pcms"])
def alignment_mark_cross(
    arm_width: float = 2.0,
    arm_length: float = 20.0,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component:
    """Custom alignment cross mark for lithography alignment.

    Creates a cross shape centered at the origin composed of two
    overlapping rectangles (horizontal and vertical arms).

    Args:
        arm_width: Width of each arm in um.
        arm_length: Length of each arm in um (full extent from tip to tip
            is 2 * arm_length).
        layer: Layer specification for the cross geometry.
        port_type: Optional port type. If provided, ports are added at the
            four arm tips.
    """
    c = Component()

    hw = arm_width / 2

    # Horizontal arm
    c.add_polygon(
        [(-arm_length, -hw), (arm_length, -hw), (arm_length, hw), (-arm_length, hw)],
        layer=layer,
    )

    # Vertical arm
    c.add_polygon(
        [(-hw, -arm_length), (hw, -arm_length), (hw, arm_length), (-hw, arm_length)],
        layer=layer,
    )

    if port_type is not None:
        c.add_port(
            name="o1",
            center=(-arm_length, 0),
            width=arm_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            name="o2",
            center=(arm_length, 0),
            width=arm_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            name="o3",
            center=(0, -arm_length),
            width=arm_width,
            orientation=270,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            name="o4",
            center=(0, arm_length),
            width=arm_width,
            orientation=90,
            layer=layer,
            port_type=port_type,
        )

    return c

alignment_mark_cross

cavity

cavity

cavity(
    component: ComponentSpec = "dbr",
    coupler: ComponentSpec = "coupler",
    length: float = 0.1,
    gap: float = 0.2,
    **kwargs: Any
) -> Component

Returns cavity from a coupler and a mirror.

connects the W0 port of the mirror to E1 and W1 coupler ports creating a resonant cavity

Parameters:

Name Type Description Default
component ComponentSpec

mirror.

'dbr'
coupler ComponentSpec

coupler library.

'coupler'
length float

coupler length.

0.1
gap float

coupler gap.

0.2
kwargs Any

coupler_settings.

{}
  ml (mirror left)              mr (mirror right)
   |                               |
   |o1 - o2__             __o3 - o1|
   |         \           /         |
              \         /
            ---=========---
     o1  o1    length      o4    o2
Source code in gdsfactory/components/pcms/cavity.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cavity(
    component: ComponentSpec = "dbr",
    coupler: ComponentSpec = "coupler",
    length: float = 0.1,
    gap: float = 0.2,
    **kwargs: Any,
) -> Component:
    r"""Returns  cavity from a coupler and a mirror.

    connects the W0 port of the mirror to E1 and W1 coupler ports
    creating a resonant cavity

    Args:
        component: mirror.
        coupler: coupler library.
        length: coupler length.
        gap: coupler gap.
        kwargs: coupler_settings.

    ```text
      ml (mirror left)              mr (mirror right)
       |                               |
       |o1 - o2__             __o3 - o1|
       |         \           /         |
                  \         /
                ---=========---
         o1  o1    length      o4    o2
    ```

    """
    mirror = gf.get_component(component)
    coupler = gf.get_component(coupler, length=length, gap=gap, **kwargs)

    c = gf.Component()
    cr = c << coupler
    ml = c << mirror
    mr = c << mirror

    ml.connect("o1", other=cr.ports["o2"])
    mr.connect("o1", other=cr.ports["o3"])
    c.add_port("o1", port=cr.ports["o1"])
    c.add_port("o2", port=cr.ports["o4"])
    c.copy_child_info(mirror)
    return c

cavity

cdsem_all

CD SEM structures.

cdsem_all

cdsem_all(
    widths: tuple[float, ...] = (
        0.4,
        0.45,
        0.5,
        0.6,
        0.8,
        1.0,
    ),
    dense_lines_width: float | None = 0.3,
    dense_lines_width_difference: float = 0.02,
    dense_lines_gap: float = 0.3,
    dense_lines_labels: tuple[str, ...] = (
        "DL",
        "DM",
        "DH",
    ),
    straight: ComponentSpec = "straight",
    bend90: ComponentSpec | None = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
    text: ComponentSpec = "text_rectangular",
    spacing: float = 5,
    cdsem_bend180: ComponentSpec = "cdsem_bend180",
    text_size: float = 1,
) -> Component

Column with all optical PCMs.

Parameters:

Name Type Description Default
widths tuple[float, ...]

for straight lines.

(0.4, 0.45, 0.5, 0.6, 0.8, 1.0)
dense_lines_width float | None

in um.

0.3
dense_lines_width_difference float

in um.

0.02
dense_lines_gap float

in um.

0.3
dense_lines_labels tuple[str, ...]

strings.

('DL', 'DM', 'DH')
straight ComponentSpec

spec.

'straight'
bend90 ComponentSpec | None

spec.

'bend_circular'
cross_section CrossSectionSpec

spec.

'strip'
text ComponentSpec

spec.

'text_rectangular'
spacing float

from group to group.

5
cdsem_bend180 ComponentSpec

spec.

'cdsem_bend180'
text_size float

in um.

1
Source code in gdsfactory/components/pcms/cdsem_all.py
 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
 96
 97
 98
 99
100
101
@gf.cell_with_module_name(tags=["pcms"])
def cdsem_all(
    widths: tuple[float, ...] = (0.4, 0.45, 0.5, 0.6, 0.8, 1.0),
    dense_lines_width: float | None = 0.3,
    dense_lines_width_difference: float = 20e-3,
    dense_lines_gap: float = 0.3,
    dense_lines_labels: tuple[str, ...] = ("DL", "DM", "DH"),
    straight: ComponentSpec = "straight",
    bend90: ComponentSpec | None = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
    text: ComponentSpec = "text_rectangular",
    spacing: float = 5,
    cdsem_bend180: ComponentSpec = "cdsem_bend180",
    text_size: float = 1,
) -> Component:
    """Column with all optical PCMs.

    Args:
        widths: for straight lines.
        dense_lines_width: in um.
        dense_lines_width_difference: in um.
        dense_lines_gap: in um.
        dense_lines_labels: strings.
        straight: spec.
        bend90: spec.
        cross_section: spec.
        text: spec.
        spacing: from group to group.
        cdsem_bend180: spec.
        text_size: in um.
    """
    c = Component()
    _c1 = gf.get_component(
        "cdsem_straight",
        widths=widths,
        cross_section=cross_section,
    )

    all_devices = [_c1]

    if bend90:
        all_devices += [
            gf.get_component(
                cdsem_bend180,
                width=width,
                straight=straight,
                bend90=bend90,
                cross_section=cross_section,
                text=text,
                text_size=text_size,
            )
            for width in widths
        ]

    if dense_lines_width:
        density_params = [
            (
                dense_lines_width - dense_lines_width_difference,
                dense_lines_gap - dense_lines_width_difference,
                dense_lines_labels[0],
            ),
            (dense_lines_width, dense_lines_gap, dense_lines_labels[1]),
            (
                dense_lines_width + dense_lines_width_difference,
                dense_lines_gap + dense_lines_width_difference,
                dense_lines_labels[2],
            ),
        ]

        all_devices += [
            gf.get_component(
                "cdsem_straight_density",
                widths=(w,) * 10,
                gaps=(g,) * 10,
                label=lbl,
                cross_section=cross_section,
                text=text,
                text_size=text_size,
            )
            for w, g, lbl in density_params
        ]

    ymin = 0.0
    for d in all_devices:
        ref = c.add_ref(d)
        ref.xmin = 0
        ref.ymin = ymin
        ymin += ref.ysize + spacing

    return c

cdsem_all

cdsem_bend180

CD SEM structures.

cdsem_bend180

cdsem_bend180(
    width: float = 0.5,
    radius: float = 10.0,
    wg_length: float | None = LINE_LENGTH,
    straight: ComponentSpec = "straight",
    bend90: ComponentSpec = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
    text: ComponentSpec = "text_rectangular",
    text_size: float = 1.0,
) -> Component

Returns CDSEM structures.

Parameters:

Name Type Description Default
width float

of the line.

0.5
radius float

um.

10.0
wg_length float | None

in um.

LINE_LENGTH
straight ComponentSpec

spec.

'straight'
bend90 ComponentSpec

spec.

'bend_circular'
cross_section CrossSectionSpec

spec.

'strip'
text ComponentSpec

spec.

'text_rectangular'
text_size float

um.

1.0
Source code in gdsfactory/components/pcms/cdsem_bend180.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cdsem_bend180(
    width: float = 0.5,
    radius: float = 10.0,
    wg_length: float | None = LINE_LENGTH,
    straight: ComponentSpec = "straight",
    bend90: ComponentSpec = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
    text: ComponentSpec = "text_rectangular",
    text_size: float = 1.0,
) -> Component:
    """Returns CDSEM structures.

    Args:
        width: of the line.
        radius: um.
        wg_length: in um.
        straight: spec.
        bend90: spec.
        cross_section: spec.
        text: spec.
        text_size: um.
    """
    c = Component()
    r = radius

    if wg_length is None:
        wg_length = 2 * r

    bend90 = gf.get_component(
        bend90,
        cross_section=cross_section,
        radius=r,
        width=width,
        allow_min_radius_violation=True,
    )
    wg = gf.get_component(
        straight, cross_section=cross_section, length=wg_length, width=width
    )

    # Add the U-turn on straight layer
    b1 = c.add_ref(bend90)
    b2 = c.add_ref(bend90)
    b2.connect("o2", b1.ports["o1"])

    wg1 = c.add_ref(wg)
    wg1.connect("o1", b1.ports["o2"])

    wg2 = c.add_ref(wg)
    wg2.connect("o1", b2.ports["o1"])

    label = c << gf.get_component(text, text=str(int(width * 1e3)), size=text_size)
    label.ymax = b2.ymin - 5
    label.x = 0

    c2 = gf.Component()
    ref = c2 << c
    ref.rotate(90)
    c2.flatten()
    return c2

cdsem_bend180

cdsem_coupler

CD SEM structures.

cdsem_coupler

cdsem_coupler(
    length: float = 420.0,
    gaps: Sequence[float] = (0.15, 0.2, 0.25),
    cross_section: CrossSectionSpec = "strip_no_ports",
    text: ComponentSpec | None = "text_rectangular",
    spacing: float = 7.0,
    positions: Sequence[float | None] | None = None,
    width: float | None = None,
    text_size: float = 1.0,
) -> Component

Returns 2 coupled waveguides gap sweep.

Parameters:

Name Type Description Default
length float

for the line.

420.0
gaps Sequence[float]

list of gaps for the sweep.

(0.15, 0.2, 0.25)
cross_section CrossSectionSpec

for the lines.

'strip_no_ports'
text ComponentSpec | None

optional text for labels.

'text_rectangular'
spacing float

Optional center to center spacing.

7.0
positions Sequence[float | None] | None

Optional positions for the text labels.

None
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
text_size float

size of the text.

1.0
Source code in gdsfactory/components/pcms/cdsem_coupler.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cdsem_coupler(
    length: float = 420.0,
    gaps: Sequence[float] = (0.15, 0.2, 0.25),
    cross_section: CrossSectionSpec = "strip_no_ports",
    text: ComponentSpec | None = "text_rectangular",
    spacing: float = 7.0,
    positions: Sequence[float | None] | None = None,
    width: float | None = None,
    text_size: float = 1.0,
) -> Component:
    """Returns 2 coupled waveguides gap sweep.

    Args:
        length: for the line.
        gaps: list of gaps for the sweep.
        cross_section: for the lines.
        text: optional text for labels.
        spacing: Optional center to center spacing.
        positions: Optional positions for the text labels.
        width: width of the waveguide. If None, it will use the width of the cross_section.
        text_size: size of the text.
    """
    c = Component()
    if width:
        xs = gf.get_cross_section(cross_section, width=width)
    else:
        xs = gf.get_cross_section(cross_section)
    p = 0.0

    if positions is not None:
        positions = positions or [None] * len(gaps)
    else:
        positions = [i * spacing for i in range(len(gaps))]

    for gap, position in zip(gaps, positions, strict=False):
        line = c << gf.c.coupler_straight(length=length, cross_section=xs, gap=gap)
        p = position or p
        line.ymin = p
        if text:
            t = c << gf.get_component(text, text=str(int(gap * 1e3)), size=text_size)
            t.xmin = line.xmax + 5
            t.ymin = p

    return c

cdsem_coupler

cdsem_straight

CD SEM structures.

cdsem_straight

cdsem_straight(
    widths: Sequence[float] = (
        0.4,
        0.45,
        0.5,
        0.6,
        0.8,
        1.0,
    ),
    length: float = LINE_LENGTH,
    cross_section: CrossSectionSpec = "strip_no_ports",
    text: ComponentSpec | None = "text_rectangular",
    spacing: float = 7.0,
    positions: Sequence[float | None] | None = None,
    text_size: float = 1,
) -> Component

Returns straight waveguide lines width sweep.

Parameters:

Name Type Description Default
widths Sequence[float]

for the sweep.

(0.4, 0.45, 0.5, 0.6, 0.8, 1.0)
length float

for the line.

LINE_LENGTH
cross_section CrossSectionSpec

for the lines.

'strip_no_ports'
text ComponentSpec | None

optional text for labels.

'text_rectangular'
spacing float

Optional center to center spacing.

7.0
positions Sequence[float | None] | None

Optional positions for the text labels.

None
text_size float

in um.

1
Source code in gdsfactory/components/pcms/cdsem_straight.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
@gf.cell_with_module_name(tags=["pcms"])
def cdsem_straight(
    widths: Sequence[float] = (0.4, 0.45, 0.5, 0.6, 0.8, 1.0),
    length: float = LINE_LENGTH,
    cross_section: CrossSectionSpec = "strip_no_ports",
    text: ComponentSpec | None = "text_rectangular",
    spacing: float = 7.0,
    positions: Sequence[float | None] | None = None,
    text_size: float = 1,
) -> Component:
    """Returns straight waveguide lines width sweep.

    Args:
        widths: for the sweep.
        length: for the line.
        cross_section: for the lines.
        text: optional text for labels.
        spacing: Optional center to center spacing.
        positions: Optional positions for the text labels.
        text_size: in um.
    """
    c = Component()
    p = 0.0
    if positions is not None:
        positions = positions or [None] * len(widths)
    else:
        positions = [i * spacing for i in range(len(widths))]

    for width, position in zip(widths, positions, strict=False):
        line = c << gf.c.straight(
            length=length, cross_section=cross_section, width=width
        )
        p = position or p
        line.ymin = p
        if text:
            t = c << gf.get_component(text, text=str(int(width * 1e3)), size=text_size)
            t.xmin = line.xmax + 5
            t.ymin = p

    return c

cdsem_straight

cdsem_straight_density

CD SEM structures.

cdsem_straight_density

cdsem_straight_density(
    widths: Floats = widths,
    gaps: Floats = gaps,
    length: float = 420.0,
    label: str = "",
    cross_section: CrossSectionSpec = "strip_no_ports",
    text: ComponentSpec | None = "text_rectangular",
    text_size: float = 1.0,
) -> Component

Returns sweep of dense straight lines.

Parameters:

Name Type Description Default
widths Floats

list of widths.

widths
gaps Floats

list of gaps.

gaps
length float

of the lines.

420.0
label str

defaults to widths[0] gaps[0].

''
cross_section CrossSectionSpec

spec.

'strip_no_ports'
text ComponentSpec | None

optional function for text.

'text_rectangular'
text_size float

size of the text.

1.0
Source code in gdsfactory/components/pcms/cdsem_straight_density.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cdsem_straight_density(
    widths: Floats = widths,
    gaps: Floats = gaps,
    length: float = 420.0,
    label: str = "",
    cross_section: CrossSectionSpec = "strip_no_ports",
    text: ComponentSpec | None = "text_rectangular",
    text_size: float = 1.0,
) -> Component:
    """Returns sweep of dense straight lines.

    Args:
        widths: list of widths.
        gaps: list of gaps.
        length: of the lines.
        label: defaults to widths[0] gaps[0].
        cross_section: spec.
        text: optional function for text.
        text_size: size of the text.
    """
    c = Component()
    label = label or f"{int(widths[0] * 1e3)} {int(gaps[0] * 1e3)}"

    ymin = 0.0
    tooth_ref: ComponentReference | None = None
    for width, gap in zip(widths, gaps, strict=False):
        tooth_ref = c << gf.c.straight(
            length=length, cross_section=cross_section, width=width
        )
        tooth_ref.ymin = ymin
        ymin += width + gap

    if text and tooth_ref is not None:
        marker_label = c << gf.get_component(text, text=f"{label}", size=text_size)
        marker_label.xmin = tooth_ref.xmax + 5
    return c

cdsem_straight_density

cutback_2x2

cutback_2x2

cutback_2x2(
    component: ComponentSpec = "mmi2x2",
    cols: int = 4,
    rows: int = 5,
    port1: str = "o1",
    port2: str = "o2",
    port3: str = "o3",
    port4: str = "o4",
    bend180: ComponentSpec = "bend_circular180",
    mirror: bool = False,
    straight_length: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    straight: ComponentSpec = "straight",
) -> Component

Returns a daisy chain of splitters for measuring their loss.

Parameters:

Name Type Description Default
component ComponentSpec

for cutback.

'mmi2x2'
cols int

number of columns.

4
rows int

number of rows.

5
port1 str

name of first optical port.

'o1'
port2 str

name of second optical port.

'o2'
port3 str

name of third optical port.

'o3'
port4 str

name of fourth optical port.

'o4'
bend180 ComponentSpec

ubend.

'bend_circular180'
mirror bool

Flips component. Useful when 'o2' is the port that you want to route to.

False
straight_length float | None

length of the straight section between cutbacks.

None
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
straight ComponentSpec

straight spec.

'straight'
Source code in gdsfactory/components/pcms/cutback_2x2.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_2x2(
    component: ComponentSpec = "mmi2x2",
    cols: int = 4,
    rows: int = 5,
    port1: str = "o1",
    port2: str = "o2",
    port3: str = "o3",
    port4: str = "o4",
    bend180: ComponentSpec = "bend_circular180",
    mirror: bool = False,
    straight_length: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    straight: ComponentSpec = "straight",
) -> Component:
    """Returns a daisy chain of splitters for measuring their loss.

    Args:
        component: for cutback.
        cols: number of columns.
        rows: number of rows.
        port1: name of first optical port.
        port2: name of second optical port.
        port3: name of third optical port.
        port4: name of fourth optical port.
        bend180: ubend.
        mirror: Flips component. Useful when 'o2' is the port that you want to route to.
        straight_length: length of the straight section between cutbacks.
        cross_section: specification (CrossSection, string or dict).
        straight: straight spec.
    """
    component = gf.get_component(component)

    bendu = _bendu_double(
        component=component,
        cross_section=cross_section,
        bend180=bend180,
        port1=port1,
        port2=port2,
    )

    straight_component = _straight_double(
        component=component,
        cross_section=cross_section,
        straight_length=straight_length,
        port1=port1,
        port2=port2,
        straight=straight,
    )

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (component, port1, port3),
        "B": (component, port4, port2),
        "D": (bendu, "o2", "o3"),
        "C": (bendu, "o4", "o1"),
        "-": (straight_component, "o1", "o3"),
        "_": (straight_component, "o2", "o4"),
    }

    # Generate the sequence of staircases
    s = ""
    for i in range(rows):
        s += "AB" * cols
        if mirror:
            s += "C" if i % 2 == 0 else "D"
        else:
            s += "D" if i % 2 == 0 else "C"

    s = s[:-1]
    s += "-_"

    for i in range(rows):
        s += "AB" * cols
        s += "D" if (i + rows) % 2 == 0 else "C"

    s = s[:-1]
    n = cols * rows * 2
    c = component_sequence(sequence=s, symbol_to_component=symbol_to_component)
    c.ports.clear()
    c.add_port("o1", port=c.insts["A1"].ports["o1"])
    c.add_port("o2", port=c.insts["A1"].ports["o2"])

    c.add_port("o3", port=c.insts[f"B{n}"].ports["o2"])
    c.add_port("o4", port=c.insts[f"B{n}"].ports["o1"])

    c.info["components"] = 2 * n
    return c

cutback_2x2

cutback_bend

cutback_bend

cutback_bend(
    component: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 5,
    **kwargs: Any
) -> Component

We recommend using cutback_bend90 instead for a smaller footprint.

Parameters:

Name Type Description Default
component ComponentSpec

bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
straight_length float

in um.

5.0
rows int

number of rows.

6
cols int

number of cols.

5
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_bend(
    component: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 5,
    **kwargs: Any,
) -> Component:
    """We recommend using cutback_bend90 instead for a smaller footprint.

    Args:
        component: bend spec.
        straight: straight spec.
        straight_length: in um.
        rows: number of rows.
        cols: number of cols.
        kwargs: cross_section settings.

        this is a column
            _
          _|
        _|

        _ this is a row
    """
    from gdsfactory.pdk import get_component

    bend90 = get_component(component, **kwargs)
    straightx = gf.get_component(straight, length=straight_length, **kwargs)

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (bend90, "o1", "o2"),
        "B": (bend90, "o2", "o1"),
        "S": (straightx, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = ""
    for i in range(cols):
        s += "ASBS" * rows
        s += "ASAS" if i % 2 == 0 else "BSBS"
    s = s[:-4]

    c = component_sequence(
        sequence=s, symbol_to_component=symbol_to_component, start_orientation=90
    )
    c.info["components"] = rows * cols * 2 + cols * 2 - 2
    return c

cutback_bend180

cutback_bend180(
    component: ComponentSpec = "bend_euler180",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: float = 3.0,
    **kwargs: Any
) -> Component

Returns cutback to measure u bend loss.

Parameters:

Name Type Description Default
component ComponentSpec

bend spec.

'bend_euler180'
straight ComponentSpec

straight spec.

'straight'
straight_length float

in um.

5.0
rows int

number of rows.

6
cols int

number of cols.

6
spacing float

in um.

3.0
kwargs Any

cross_section settings.

_

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_bend180(
    component: ComponentSpec = "bend_euler180",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: float = 3.0,
    **kwargs: Any,
) -> Component:
    """Returns cutback to measure u bend loss.

    Args:
        component: bend spec.
        straight: straight spec.
        straight_length: in um.
        rows: number of rows.
        cols: number of cols.
        spacing: in um.
        kwargs: cross_section settings.

          _
        _| |_  this is a row

        _ this is a column
    """
    bend180 = gf.get_component(component, **kwargs)
    straightx = gf.get_component(straight, length=straight_length, **kwargs)
    wg_vertical = gf.get_component(
        straight,
        length=2 * bend180.xsize + straight_length + spacing,
        **kwargs,
    )

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "D": (bend180, "o1", "o2"),
        "C": (bend180, "o2", "o1"),
        "-": (straightx, "o1", "o2"),
        "|": (wg_vertical, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = "".join(
        "D-C-" * rows + "|" if i % 2 == 0 else "C-D-" * rows + "|" for i in range(cols)
    )

    s = s[:-1]

    c = component_sequence(
        sequence=s, symbol_to_component=symbol_to_component, start_orientation=0
    )
    c.info["components"] = rows * cols * 2 + cols * 2 - 2
    return c

cutback_bend90

cutback_bend90(
    component: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: int = 5,
    **kwargs: Any
) -> Component

Returns bend90 cutback.

Parameters:

Name Type Description Default
component ComponentSpec

bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
straight_length float

in um.

5.0
rows int

number of rows.

6
cols int

number of cols.

6
spacing int

in um.

5
kwargs Any

cross_section settings.

_

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
 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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_bend90(
    component: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: int = 5,
    **kwargs: Any,
) -> Component:
    """Returns bend90 cutback.

    Args:
        component: bend spec.
        straight: straight spec.
        straight_length: in um.
        rows: number of rows.
        cols: number of cols.
        spacing: in um.
        kwargs: cross_section settings.

           _
        |_| |
    """
    bend90 = gf.get_component(component, **kwargs)
    straightx = gf.get_component(straight, length=straight_length, **kwargs)
    straight_length = 2 * _get_bend_size(bend90) + spacing + straight_length
    straighty = gf.get_component(straight, length=straight_length, **kwargs)

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (bend90, "o1", "o2"),
        "B": (bend90, "o2", "o1"),
        "-": (straightx, "o1", "o2"),
        "|": (straighty, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = "".join(
        "A-A-B-B-" * rows + "|" if i % 2 == 0 else "B-B-A-A-" * rows + "|"
        for i in range(cols)
    )
    s = s[:-1]

    # Create the component from the sequence
    c = component_sequence(
        sequence=s, symbol_to_component=symbol_to_component, start_orientation=0
    )
    c.info["components"] = rows * cols * 4
    return c

staircase

staircase(
    component: ComponentSpec | Component = "bend_euler",
    straight: ComponentSpec = "straight",
    length_v: float = 5.0,
    length_h: float = 5.0,
    rows: int = 4,
    **kwargs: Any
) -> Component

Returns staircase.

Parameters:

Name Type Description Default
component ComponentSpec | Component

bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
length_v float

vertical length.

5.0
length_h float

vertical length.

5.0
rows int

number of rows.

4
cols

number of cols.

required
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
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
@gf.cell_with_module_name(tags=["pcms"])
def staircase(
    component: ComponentSpec | Component = "bend_euler",
    straight: ComponentSpec = "straight",
    length_v: float = 5.0,
    length_h: float = 5.0,
    rows: int = 4,
    **kwargs: Any,
) -> Component:
    """Returns staircase.

    Args:
        component: bend spec.
        straight: straight spec.
        length_v: vertical length.
        length_h: vertical length.
        rows: number of rows.
        cols: number of cols.
        kwargs: cross_section settings.
    """
    bend90 = (
        component
        if isinstance(component, Component)
        else gf.get_component(component, **kwargs)
    )

    wgh = gf.get_component(straight, length=length_h, **kwargs)
    wgv = gf.get_component(straight, length=length_v, **kwargs)

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (bend90, "o1", "o2"),
        "B": (bend90, "o2", "o1"),
        "-": (wgh, "o1", "o2"),
        "|": (wgv, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = "-A|B" * rows + "-"

    c = component_sequence(
        sequence=s,
        symbol_to_component=symbol_to_component,
        start_orientation=0,
    )
    c.info["components"] = 2 * rows
    return c

cutback_bend

cutback_bend180

cutback_bend180(
    component: ComponentSpec = "bend_euler180",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: float = 3.0,
    **kwargs: Any
) -> Component

Returns cutback to measure u bend loss.

Parameters:

Name Type Description Default
component ComponentSpec

bend spec.

'bend_euler180'
straight ComponentSpec

straight spec.

'straight'
straight_length float

in um.

5.0
rows int

number of rows.

6
cols int

number of cols.

6
spacing float

in um.

3.0
kwargs Any

cross_section settings.

_

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_bend180(
    component: ComponentSpec = "bend_euler180",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: float = 3.0,
    **kwargs: Any,
) -> Component:
    """Returns cutback to measure u bend loss.

    Args:
        component: bend spec.
        straight: straight spec.
        straight_length: in um.
        rows: number of rows.
        cols: number of cols.
        spacing: in um.
        kwargs: cross_section settings.

          _
        _| |_  this is a row

        _ this is a column
    """
    bend180 = gf.get_component(component, **kwargs)
    straightx = gf.get_component(straight, length=straight_length, **kwargs)
    wg_vertical = gf.get_component(
        straight,
        length=2 * bend180.xsize + straight_length + spacing,
        **kwargs,
    )

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "D": (bend180, "o1", "o2"),
        "C": (bend180, "o2", "o1"),
        "-": (straightx, "o1", "o2"),
        "|": (wg_vertical, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = "".join(
        "D-C-" * rows + "|" if i % 2 == 0 else "C-D-" * rows + "|" for i in range(cols)
    )

    s = s[:-1]

    c = component_sequence(
        sequence=s, symbol_to_component=symbol_to_component, start_orientation=0
    )
    c.info["components"] = rows * cols * 2 + cols * 2 - 2
    return c

cutback_bend180

cutback_bend180circular module-attribute

cutback_bend180circular = partial(
    cutback_bend180, component="bend_circular180"
)

cutback_bend180circular

cutback_bend90

cutback_bend90(
    component: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: int = 5,
    **kwargs: Any
) -> Component

Returns bend90 cutback.

Parameters:

Name Type Description Default
component ComponentSpec

bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
straight_length float

in um.

5.0
rows int

number of rows.

6
cols int

number of cols.

6
spacing int

in um.

5
kwargs Any

cross_section settings.

_

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
 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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_bend90(
    component: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    straight_length: float = 5.0,
    rows: int = 6,
    cols: int = 6,
    spacing: int = 5,
    **kwargs: Any,
) -> Component:
    """Returns bend90 cutback.

    Args:
        component: bend spec.
        straight: straight spec.
        straight_length: in um.
        rows: number of rows.
        cols: number of cols.
        spacing: in um.
        kwargs: cross_section settings.

           _
        |_| |
    """
    bend90 = gf.get_component(component, **kwargs)
    straightx = gf.get_component(straight, length=straight_length, **kwargs)
    straight_length = 2 * _get_bend_size(bend90) + spacing + straight_length
    straighty = gf.get_component(straight, length=straight_length, **kwargs)

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (bend90, "o1", "o2"),
        "B": (bend90, "o2", "o1"),
        "-": (straightx, "o1", "o2"),
        "|": (straighty, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = "".join(
        "A-A-B-B-" * rows + "|" if i % 2 == 0 else "B-B-A-A-" * rows + "|"
        for i in range(cols)
    )
    s = s[:-1]

    # Create the component from the sequence
    c = component_sequence(
        sequence=s, symbol_to_component=symbol_to_component, start_orientation=0
    )
    c.info["components"] = rows * cols * 4
    return c

cutback_bend90

cutback_bend90circular module-attribute

cutback_bend90circular = partial(
    cutback_bend90, component="bend_circular"
)

cutback_bend90circular

cutback_component

cutback_component

cutback_component(
    component: ComponentSpec = "taper_0p5_to_3_l36",
    cols: int = 4,
    rows: int = 5,
    port1: str = "o1",
    port2: str = "o2",
    bend180: ComponentSpec = "bend_euler180",
    mirror: bool = False,
    mirror1: bool = False,
    mirror2: bool = False,
    straight_length: float | None = None,
    straight_length_pair: float | None = None,
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    radius: float | None = None,
    **kwargs: Any
) -> Component

Returns a daisy chain of components for measuring their loss.

Works only for components with 2 ports (input, output).

Parameters:

Name Type Description Default
component ComponentSpec

for cutback.

'taper_0p5_to_3_l36'
cols int

number of columns.

4
rows int

number of rows.

5
port1 str

name of first optical port.

'o1'
port2 str

name of second optical port.

'o2'
bend180 ComponentSpec

ubend.

'bend_euler180'
mirror bool

Flips component. Useful when 'o2' is the port that you want to route to.

False
mirror1 bool

mirrors first component.

False
mirror2 bool

mirrors second component.

False
straight_length float | None

length of the straight section between cutbacks.

None
straight_length_pair float | None

length of the straight section between each component pair.

None
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
straight ComponentSpec

straight spec.

'straight'
radius float | None

radius for the bends. Defaults to cross_section radius.

None
kwargs Any

component settings.

{}
Source code in gdsfactory/components/pcms/cutback_component.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
@gf.cell_with_module_name(tags=["pcms"])
def cutback_component(
    component: ComponentSpec = "taper_0p5_to_3_l36",
    cols: int = 4,
    rows: int = 5,
    port1: str = "o1",
    port2: str = "o2",
    bend180: ComponentSpec = "bend_euler180",
    mirror: bool = False,
    mirror1: bool = False,
    mirror2: bool = False,
    straight_length: float | None = None,
    straight_length_pair: float | None = None,
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    radius: float | None = None,
    **kwargs: Any,
) -> Component:
    """Returns a daisy chain of components for measuring their loss.

    Works only for components with 2 ports (input, output).

    Args:
        component: for cutback.
        cols: number of columns.
        rows: number of rows.
        port1: name of first optical port.
        port2: name of second optical port.
        bend180: ubend.
        mirror: Flips component. Useful when 'o2' is the port that you want to route to.
        mirror1: mirrors first component.
        mirror2: mirrors second component.
        straight_length: length of the straight section between cutbacks.
        straight_length_pair: length of the straight section between each component pair.
        cross_section: specification (CrossSection, string or dict).
        straight: straight spec.
        radius: radius for the bends. Defaults to cross_section radius.
        kwargs: component settings.
    """
    xs = gf.get_cross_section(cross_section)

    component = gf.get_component(component, **kwargs)
    bendu = gf.get_component(bend180, cross_section=xs)

    radius = radius or xs.radius
    assert radius is not None
    straight_length = radius * 2 if straight_length is None else straight_length
    straight_component = gf.get_component(
        straight, length=straight_length, cross_section=xs
    )
    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (component, port1, port2),
        "B": (component, port2, port1),
        "D": (bendu, "o1", "o2"),
        "C": (bendu, "o2", "o1"),
        "-": (straight_component, "o1", "o2"),
        "_": (straight_component, "o2", "o1"),
    }
    if straight_length_pair:
        straight_pair = gf.get_component(
            straight, length=straight_length_pair, cross_section=xs
        )
        symbol_to_component["."] = (straight_pair, "o2", "o1")

    # Generate the sequence of staircases
    s = ""
    a = "!A" if mirror1 else "A"
    b = "!B" if mirror2 else "B"

    for i in range(rows):
        if straight_length_pair:
            s += f"{a}.{b}" * cols
        else:
            s += (a + b) * cols

        if mirror:
            s += "C" if i % 2 == 0 else "D"
        else:
            s += "D" if i % 2 == 0 else "C"

    s = s[:-1]
    s += "-_"

    for i in range(rows):
        if straight_length_pair:
            s += f"{a}.{b}" * cols
        else:
            s += (a + b) * cols
        s += "D" if (i + rows) % 2 == 0 else "C"

    s = s[:-1]

    c = component_sequence(sequence=s, symbol_to_component=symbol_to_component)
    n = 2 * s.count("A")
    c.info["components"] = n
    return c

cutback_component

cutback_component_mirror module-attribute

cutback_component_mirror = partial(
    cutback_component, mirror=True
)

cutback_component_mirror

cutback_splitter

cutback_splitter

cutback_splitter(
    component: ComponentSpec = "mmi1x2",
    cols: int = 4,
    rows: int = 5,
    port1: str = "o1",
    port2: str = "o2",
    port3: str = "o3",
    bend180: ComponentSpec = "bend_euler180",
    mirror: bool = False,
    straight: ComponentSpec = "straight",
    straight_length: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    **kwargs: Any
) -> Component

Returns a daisy chain of splitters for measuring their loss.

Parameters:

Name Type Description Default
component ComponentSpec

for cutback.

'mmi1x2'
cols int

number of columns.

4
rows int

number of rows.

5
port1 str

name of first optical port.

'o1'
port2 str

name of second optical port.

'o2'
port3 str

name of third optical port.

'o3'
bend180 ComponentSpec

ubend.

'bend_euler180'
mirror bool

Flips component. Useful when 'o2' is the port that you want to route to.

False
straight ComponentSpec

waveguide spec to connect both sides.

'straight'
straight_length float | None

length of the straight section between cutbacks.

None
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/pcms/cutback_splitter.py
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
@gf.cell_with_module_name(tags=["pcms"])
def cutback_splitter(
    component: ComponentSpec = "mmi1x2",
    cols: int = 4,
    rows: int = 5,
    port1: str = "o1",
    port2: str = "o2",
    port3: str = "o3",
    bend180: ComponentSpec = "bend_euler180",
    mirror: bool = False,
    straight: ComponentSpec = "straight",
    straight_length: float | None = None,
    cross_section: CrossSectionSpec = "strip",
    **kwargs: Any,
) -> Component:
    """Returns a daisy chain of splitters for measuring their loss.

    Args:
        component: for cutback.
        cols: number of columns.
        rows: number of rows.
        port1: name of first optical port.
        port2: name of second optical port.
        port3: name of third optical port.
        bend180: ubend.
        mirror: Flips component. Useful when 'o2' is the port that you want to route to.
        straight: waveguide spec to connect both sides.
        straight_length: length of the straight section between cutbacks.
        cross_section: specification (CrossSection, string or dict).
        kwargs: cross_section settings.
    """
    xs = gf.get_cross_section(cross_section, **kwargs)

    component = gf.get_component(component)
    bendu = gf.get_component(bend180, cross_section=xs)
    radius = xs.radius
    assert radius is not None
    straight_component = gf.get_component(
        straight,
        length=straight_length or radius * 2,
        cross_section=xs,
    )

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (component, port1, port2),
        "B": (component, port3, port1),
        "D": (bendu, "o1", "o2"),
        "C": (bendu, "o2", "o1"),
        "-": (straight_component, "o1", "o2"),
        "_": (straight_component, "o2", "o1"),
    }

    s = ""
    for i in range(rows):
        s += "AB" * cols
        if mirror:
            s += "C" if i % 2 == 0 else "D"
        else:
            s += "D" if i % 2 == 0 else "C"

    s = s[:-1]
    s += "-_"

    for i in range(rows):
        s += "AB" * cols
        s += "D" if (i + rows) % 2 == 0 else "C"

    s = s[:-1]

    c = component_sequence(sequence=s, symbol_to_component=symbol_to_component)
    n = len(s) - 2
    c.info["components"] = n
    return c

cutback_splitter

greek_cross

Greek cross test structure.

greek_cross

greek_cross(
    length: float = 30,
    layers: LayerSpecs = ("WG", "N"),
    widths: Floats = (2.0, 3.0),
    offsets: Floats | None = None,
    via_stack: ComponentSpec = "via_stack_npp_m1",
    layer_index: int = 0,
) -> gf.Component

Simple greek cross with via stacks at the endpoints.

Process control monitor for dopant sheet resistivity and linewidth variation.

Parameters:

Name Type Description Default
length float

length of cross arms.

30
layers LayerSpecs

list of layers.

('WG', 'N')
widths Floats

list of widths (same order as layers).

(2.0, 3.0)
offsets Floats | None

how much to extend each layer beyond the cross length negative shorter, positive longer.

None
via_stack ComponentSpec

via component to attach to the cross.

'via_stack_npp_m1'
layer_index int

index of the layer to connect the via_stack to.

via_stack <-------> ___ length __ | |<-------------------->| |

0

References: - Walton, Anthony J.. “MICROELECTRONIC TEST STRUCTURES.” (1999). - W. Versnel, Analysis of the Greek cross, a Van der Pauw structure with finite contacts, Solid-State Electronics, Volume 22, Issue 11, 1979, Pages 911-914, ISSN 0038-1101, https://doi.org/10.1016/0038-1101(79)90061-3. - S. Enderling et al., "Sheet resistance measurement of non-standard cleanroom materials using suspended Greek cross test structures," IEEE Transactions on Semiconductor Manufacturing, vol. 19, no. 1, pp. 2-9, Feb. 2006, doi: 10.1109/TSM.2005.863248. - https://download.tek.com/document/S530_VanDerPauwSheetRstnce.pdf

Source code in gdsfactory/components/pcms/greek_cross.py
10
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
@gf.cell_with_module_name(tags=["pcms"])
def greek_cross(
    length: float = 30,
    layers: LayerSpecs = ("WG", "N"),
    widths: Floats = (2.0, 3.0),
    offsets: Floats | None = None,
    via_stack: ComponentSpec = "via_stack_npp_m1",
    layer_index: int = 0,
) -> gf.Component:
    """Simple greek cross with via stacks at the endpoints.

    Process control monitor for dopant sheet resistivity and linewidth variation.

    Args:
        length: length of cross arms.
        layers: list of layers.
        widths: list of widths (same order as layers).
        offsets: how much to extend each layer beyond the cross length
            negative shorter, positive longer.
        via_stack: via component to attach to the cross.
        layer_index: index of the layer to connect the via_stack to.

            via_stack
            <------->
            _________       length          ________
            |       |<-------------------->|        |
        2x  |       |     |   ↓       |<-->|        |
            |       |======== width =======|        |
            |_______|<--> |   ↑       |<-->|________|
                    offset            offset


    References:
    - Walton, Anthony J.. “MICROELECTRONIC TEST STRUCTURES.” (1999).
    - W. Versnel, Analysis of the Greek cross, a Van der Pauw structure with finite
      contacts, Solid-State Electronics, Volume 22, Issue 11, 1979, Pages 911-914,
      ISSN 0038-1101, https://doi.org/10.1016/0038-1101(79)90061-3.
    - S. Enderling et al., "Sheet resistance measurement of non-standard cleanroom
      materials using suspended Greek cross test structures," IEEE Transactions on
      Semiconductor Manufacturing, vol. 19, no. 1, pp. 2-9, Feb. 2006,
      doi: 10.1109/TSM.2005.863248.
    - https://download.tek.com/document/S530_VanDerPauwSheetRstnce.pdf

    """
    c = gf.Component()

    if len(layers) != len(widths):
        raise ValueError("len(layers) must equal len(widths).")

    offsets = offsets or (0.0,) * len(layers)

    for index, (layer, width, offset) in enumerate(
        zip(layers, widths, offsets, strict=False)
    ):
        ref = c << gf.c.cross(
            length=length + 2 * offset,
            width=width,
            layer=layer,
            port_type="electrical",
        )
        if index == layer_index:
            cross_ref = ref

    # Add via
    for port in cross_ref.ports:
        via_stack_ref = c << gf.get_component(via_stack)
        via_stack_ref.connect(
            "e1",
            port,
            allow_layer_mismatch=True,
            allow_width_mismatch=True,
        )
        c.add_port(name=port.name, port=via_stack_ref.ports["e3"])

    c.flatten()
    c.auto_rename_ports()
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=port.name)
    return c

greek_cross_with_pads

greek_cross_with_pads(
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150.0,
    greek_cross_component: ComponentSpec = "greek_cross",
    pad_via: ComponentSpec = "via_stack_m1_mtop",
    cross_section: CrossSectionSpec = metal1,
    pad_port_name: str = "e4",
) -> gf.Component

Greek cross under 4 DC pads, ready to test.

Parameters:

Name Type Description Default
pad ComponentSpec

component to use for probe pads.

'pad'
pad_pitch float

spacing between pads.

150.0
greek_cross_component ComponentSpec

component to use for greek cross.

'greek_cross'
pad_via ComponentSpec

via to add to the pad.

'via_stack_m1_mtop'
cross_section CrossSectionSpec

cross-section for cross via to pad via wiring.

metal1
pad_port_name str

name of the port to connect to the greek cross.

'e4'
Source code in gdsfactory/components/pcms/greek_cross.py
 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
@gf.cell_with_module_name(tags=["pcms"])
def greek_cross_with_pads(
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150.0,
    greek_cross_component: ComponentSpec = "greek_cross",
    pad_via: ComponentSpec = "via_stack_m1_mtop",
    cross_section: CrossSectionSpec = metal1,
    pad_port_name: str = "e4",
) -> gf.Component:
    """Greek cross under 4 DC pads, ready to test.

    Arguments:
        pad: component to use for probe pads.
        pad_pitch: spacing between pads.
        greek_cross_component: component to use for greek cross.
        pad_via: via to add to the pad.
        cross_section: cross-section for cross via to pad via wiring.
        pad_port_name: name of the port to connect to the greek cross.
    """
    c = gf.Component()

    # Cross
    cross_ref = c << gf.get_component(greek_cross_component)
    cross_ref.x = (
        2 * pad_pitch - (pad_pitch - gf.get_component(pad).info["size"][0]) / 2
    )

    cross_pad_via_port_pairs = {
        0: ("e1", "e2"),
        1: ("e4", "e2"),
        2: ("e2", "e4"),
        3: ("e3", "e4"),
    }

    # Vias to pads
    for index in range(4):
        pad_ref = c << gf.get_component(pad)
        pad_ref.x = index * pad_pitch + pad_ref.xsize / 2
        via_ref = c << gf.get_component(pad_via)
        if index < 2:
            via_ref.connect(
                "e2",
                other=pad_ref.ports["e4"],
                allow_layer_mismatch=True,
                allow_width_mismatch=True,
            )
        else:
            via_ref.connect(
                "e4",
                other=pad_ref.ports["e2"],
                allow_layer_mismatch=True,
                allow_width_mismatch=True,
            )

        gf.routing.route_single_electrical(
            c,
            cross_ref[cross_pad_via_port_pairs[index][0]],
            via_ref[cross_pad_via_port_pairs[index][1]],
            cross_section=cross_section,
            start_straight_length=5,
            end_straight_length=5,
        )
        c.add_port(
            name=f"e{index + 1}",
            port=pad_ref.ports[pad_port_name],
        )

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

greek_cross

greek_cross_with_pads

greek_cross_with_pads(
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150.0,
    greek_cross_component: ComponentSpec = "greek_cross",
    pad_via: ComponentSpec = "via_stack_m1_mtop",
    cross_section: CrossSectionSpec = metal1,
    pad_port_name: str = "e4",
) -> gf.Component

Greek cross under 4 DC pads, ready to test.

Parameters:

Name Type Description Default
pad ComponentSpec

component to use for probe pads.

'pad'
pad_pitch float

spacing between pads.

150.0
greek_cross_component ComponentSpec

component to use for greek cross.

'greek_cross'
pad_via ComponentSpec

via to add to the pad.

'via_stack_m1_mtop'
cross_section CrossSectionSpec

cross-section for cross via to pad via wiring.

metal1
pad_port_name str

name of the port to connect to the greek cross.

'e4'
Source code in gdsfactory/components/pcms/greek_cross.py
 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
@gf.cell_with_module_name(tags=["pcms"])
def greek_cross_with_pads(
    pad: ComponentSpec = "pad",
    pad_pitch: float = 150.0,
    greek_cross_component: ComponentSpec = "greek_cross",
    pad_via: ComponentSpec = "via_stack_m1_mtop",
    cross_section: CrossSectionSpec = metal1,
    pad_port_name: str = "e4",
) -> gf.Component:
    """Greek cross under 4 DC pads, ready to test.

    Arguments:
        pad: component to use for probe pads.
        pad_pitch: spacing between pads.
        greek_cross_component: component to use for greek cross.
        pad_via: via to add to the pad.
        cross_section: cross-section for cross via to pad via wiring.
        pad_port_name: name of the port to connect to the greek cross.
    """
    c = gf.Component()

    # Cross
    cross_ref = c << gf.get_component(greek_cross_component)
    cross_ref.x = (
        2 * pad_pitch - (pad_pitch - gf.get_component(pad).info["size"][0]) / 2
    )

    cross_pad_via_port_pairs = {
        0: ("e1", "e2"),
        1: ("e4", "e2"),
        2: ("e2", "e4"),
        3: ("e3", "e4"),
    }

    # Vias to pads
    for index in range(4):
        pad_ref = c << gf.get_component(pad)
        pad_ref.x = index * pad_pitch + pad_ref.xsize / 2
        via_ref = c << gf.get_component(pad_via)
        if index < 2:
            via_ref.connect(
                "e2",
                other=pad_ref.ports["e4"],
                allow_layer_mismatch=True,
                allow_width_mismatch=True,
            )
        else:
            via_ref.connect(
                "e4",
                other=pad_ref.ports["e2"],
                allow_layer_mismatch=True,
                allow_width_mismatch=True,
            )

        gf.routing.route_single_electrical(
            c,
            cross_ref[cross_pad_via_port_pairs[index][0]],
            via_ref[cross_pad_via_port_pairs[index][1]],
            cross_section=cross_section,
            start_straight_length=5,
            end_straight_length=5,
        )
        c.add_port(
            name=f"e{index + 1}",
            port=pad_ref.ports[pad_port_name],
        )

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

greek_cross_with_pads

litho_calipers

litho_calipers

litho_calipers(
    notch_size: Size = (2.0, 5.0),
    notch_spacing: float = 2.0,
    num_notches: int = 11,
    offset_per_notch: float = 0.1,
    row_spacing: float = 0.0,
    layer1: LayerSpec = "WG",
    layer2: LayerSpec = "SLAB150",
) -> Component

Vernier caliper structure to test lithography alignment.

Only the middle finger is aligned and the rest are offset. adapted from phidl

Parameters:

Name Type Description Default
notch_size Size

[xwidth, yheight].

(2.0, 5.0)
notch_spacing float

in um.

2.0
num_notches int

number of notches.

11
offset_per_notch float

in um.

0.1
row_spacing float

0

0.0
layer1 LayerSpec

layer.

'WG'
layer2 LayerSpec

layer.

'SLAB150'
Source code in gdsfactory/components/pcms/litho_calipers.py
10
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
@gf.cell_with_module_name(tags=["pcms"])
def litho_calipers(
    notch_size: Size = (2.0, 5.0),
    notch_spacing: float = 2.0,
    num_notches: int = 11,
    offset_per_notch: float = 0.1,
    row_spacing: float = 0.0,
    layer1: LayerSpec = "WG",
    layer2: LayerSpec = "SLAB150",
) -> Component:
    """Vernier caliper structure to test lithography alignment.

    Only the middle finger is aligned and the rest are offset.
    adapted from phidl

    Args:
        notch_size: [xwidth, yheight].
        notch_spacing: in um.
        num_notches: number of notches.
        offset_per_notch: in um.
        row_spacing: 0
        layer1: layer.
        layer2: layer.
    """
    D = gf.Component()
    num_notches_total = num_notches * 2 + 1
    centre_notch = num_notches
    R1 = gf.c.rectangle(size=notch_size, layer=layer1, port_type=None)
    R2 = gf.c.rectangle(size=notch_size, layer=layer2, port_type=None)

    for i in range(num_notches_total):
        if i == centre_notch:
            ref = D.add_ref(R1)
            ref.movex(i * (notch_size[0] + notch_spacing)).movey(notch_size[1])
            ref = D.add_ref(R2)
            ref.movex(
                i * (notch_size[0] + notch_spacing)
                + offset_per_notch * (centre_notch - i)
            ).movey(-2 * notch_size[1] - row_spacing)
        ref = D.add_ref(R1)
        ref.movex(i * (notch_size[0] + notch_spacing))
        ref = D.add_ref(R2)
        ref.movex(
            i * (notch_size[0] + notch_spacing) + offset_per_notch * (centre_notch - i)
        )
        ref.movey(-notch_size[1] - row_spacing)
    return D

litho_calipers

litho_ruler

litho_ruler

litho_ruler(
    height: float = 2,
    width: float = 0.5,
    spacing: float = 2.0,
    scale: tuple[float, ...] = (
        3,
        1,
        1,
        1,
        1,
        2,
        1,
        1,
        1,
        1,
    ),
    num_marks: int = 21,
    layer: LayerSpec = "WG",
) -> gf.Component

Ruler structure for lithographic measurement.

Includes marks of varying scales to allow for easy reading by eye.

based on phidl.geometry

Parameters:

Name Type Description Default
height float

Height of the ruling marks in um.

2
width float

Width of the ruling marks in um.

0.5
spacing float

Center-to-center spacing of the ruling marks in um.

2.0
scale tuple[float, ...]

Height scale pattern of marks.

(3, 1, 1, 1, 1, 2, 1, 1, 1, 1)
num_marks int

Total number of marks to generate.

21
layer LayerSpec

Specific layer to put the ruler geometry on.

'WG'
Source code in gdsfactory/components/pcms/litho_ruler.py
 9
10
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
@gf.cell_with_module_name(tags=["pcms"])
def litho_ruler(
    height: float = 2,
    width: float = 0.5,
    spacing: float = 2.0,
    scale: tuple[float, ...] = (3, 1, 1, 1, 1, 2, 1, 1, 1, 1),
    num_marks: int = 21,
    layer: LayerSpec = "WG",
) -> gf.Component:
    """Ruler structure for lithographic measurement.

    Includes marks of varying scales to allow for easy reading by eye.

    based on phidl.geometry

    Args:
        height: Height of the ruling marks in um.
        width: Width of the ruling marks in um.
        spacing: Center-to-center spacing of the ruling marks in um.
        scale: Height scale pattern of marks.
        num_marks: Total number of marks to generate.
        layer: Specific layer to put the ruler geometry on.
    """
    pitch = spacing + width
    c = gf.Component()
    for n in range(num_marks):
        h = height * scale[n % len(scale)]
        ref = c << gf.components.rectangle(size=(width, h), layer=layer)
        ref.movex((n - num_marks / 2) * pitch + spacing / 2.0)

    return c

litho_ruler

litho_steps

litho_steps

litho_steps(
    line_widths: tuple[float, ...] = (
        1.0,
        2.0,
        4.0,
        8.0,
        16.0,
    ),
    line_spacing: float = 10.0,
    height: float = 100.0,
    layer: LayerSpec = "WG",
) -> Component

Positive + negative tone linewidth test.

used for lithography resolution test patterning based on phidl

Parameters:

Name Type Description Default
line_widths tuple[float, ...]

in um.

(1.0, 2.0, 4.0, 8.0, 16.0)
line_spacing float

in um.

10.0
height float

in um.

100.0
layer LayerSpec

Specific layer to put the ruler geometry on.

'WG'
Source code in gdsfactory/components/pcms/litho_steps.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
@gf.cell_with_module_name(tags=["pcms"])
def litho_steps(
    line_widths: tuple[float, ...] = (1.0, 2.0, 4.0, 8.0, 16.0),
    line_spacing: float = 10.0,
    height: float = 100.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Positive + negative tone linewidth test.

    used for lithography resolution test patterning
    based on phidl

    Args:
        line_widths: in um.
        line_spacing: in um.
        height: in um.
        layer: Specific layer to put the ruler geometry on.
    """
    D = gf.Component()

    height /= 2
    T1 = pc.text(
        text=f"{line_widths[-1]!s}", size=height, justify="center", layer=layer
    )

    ref = D.add_ref(T1)
    ref.rotate(90)
    ref.movex(-height / 10)

    R1 = pc.rectangle(size=(line_spacing, height), layer=layer)
    D.add_ref(R1).movey(-height)
    count = 0.0
    for i in reversed(line_widths):
        count += line_spacing + i
        R2 = pc.rectangle(size=(i, height), layer=layer)
        r = D.add_ref(R1)
        r.movex(count)
        r.movey(-height)
        r = D.add_ref(R2)
        r.movex(count - i)

    return D

litho_steps

pixel

pixel(size: int = 1, layer: LayerSpec = 'WG') -> Component
Source code in gdsfactory/components/pcms/version_stamp.py
15
16
17
18
19
20
@gf.cell_with_module_name(tags=["pcms"])
def pixel(size: int = 1, layer: LayerSpec = "WG") -> Component:
    c = gf.Component()
    a = size / 2
    c.add_polygon([(a, a), (a, -a), (-a, -a), (-a, a)], layer)
    return c

pixel

qrcode

qrcode(
    data: str = "mask01",
    psize: int = 1,
    layer: LayerSpec = "WG",
) -> Component

Returns QRCode.

Parameters:

Name Type Description Default
data str

string to encode.

'mask01'
psize int

pixel size.

1
layer LayerSpec

layer to use.

'WG'
Source code in gdsfactory/components/pcms/version_stamp.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@gf.cell_with_module_name(tags=["pcms"])
def qrcode(data: str = "mask01", psize: int = 1, layer: LayerSpec = "WG") -> Component:
    """Returns QRCode.

    Args:
        data: string to encode.
        psize: pixel size.
        layer: layer to use.
    """
    import qrcode

    pix = pixel(size=psize, layer=layer)
    q = qrcode.QRCode()
    q.add_data(data)
    matrix = q.get_matrix()
    c = gf.Component()
    for i, row in enumerate(matrix):
        for j, value in enumerate(row):
            if value:
                ref = c << pix
                ref.center = (i * psize, j * psize)
    c.flatten()
    return c

qrcode

resistance_meander

resistance_meander

resistance_meander(
    name: str = "net",
    pad_size: Size = (50.0, 50.0),
    num_squares: int = 1000,
    width: float = 1.0,
    res_layer: LayerSpec = "MTOP",
    pad_layer: LayerSpec = "MTOP",
) -> Component

Return meander to test resistance.

based on phidl.geometry

Parameters:

Name Type Description Default
name str

Name of the component.

'net'
pad_size Size

Size of the two matched impedance pads (microns).

(50.0, 50.0)
num_squares int

Number of squares comprising the resonator wire.

1000
width float

The width of the squares (microns).

1.0
res_layer LayerSpec

resistance layer.

'MTOP'
pad_layer LayerSpec

pad layer.

'MTOP'
Source code in gdsfactory/components/pcms/resistance_meander.py
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
96
97
98
@gf.cell_with_module_name(tags=["pcms"])
def resistance_meander(
    name: str = "net",
    pad_size: Size = (50.0, 50.0),
    num_squares: int = 1000,
    width: float = 1.0,
    res_layer: LayerSpec = "MTOP",
    pad_layer: LayerSpec = "MTOP",
) -> Component:
    """Return meander to test resistance.

    based on phidl.geometry

    Args:
        name: Name of the component.
        pad_size: Size of the two matched impedance pads (microns).
        num_squares: Number of squares comprising the resonator wire.
        width: The width of the squares (microns).
        res_layer: resistance layer.
        pad_layer: pad layer.
    """
    x = pad_size[0]
    z = pad_size[1]

    # Checking validity of input
    if x <= 0 or z <= 0:
        raise ValueError("Pad must have positive, real dimensions")
    if width > z:
        raise ValueError("Width of cell cannot be greater than height of pad")
    if num_squares <= 0:
        raise ValueError("Number of squares must be a positive real number")
    if width <= 0:
        raise ValueError("Width of cell must be a positive real number")

    # Performing preliminary calculations
    num_rows = int(np.floor(z / (2 * width)))
    if num_rows % 2 == 0:
        num_rows -= 1
    num_columns = num_rows - 1
    squares_in_row = (num_squares - num_columns - 2) / num_rows

    # Compensating for weird edge cases
    if squares_in_row < 1:
        num_rows = round(num_rows / 2) - 2
        squares_in_row = 1
    if width * 2 > z:
        num_rows = 1
        squares_in_row = num_squares - 2

    length_row = squares_in_row * width

    # Creating row/column corner combination structure
    T = Component()
    Row = gf.c.rectangle(size=(length_row, width), layer=res_layer)
    Col = gf.c.rectangle(size=(width, width), layer=res_layer)

    T.add_ref(Row)
    col = T.add_ref(Col)
    col.move((length_row - width, -width))

    # Creating entire straight net
    N = Component(name=name)
    n = 1
    for i in range(num_rows):
        d = N.add_ref(T) if i != num_rows - 1 else N.add_ref(Row)
        if n % 2 == 0:
            d.dmirror_x(d.x)
        d.movey(-(n - 1) * T.ysize)
        n += 1
    ref = N.add_ref(Col)
    ref.movex(-width)

    end = N.add_ref(Col)
    end.movey(-(n - 2) * T.ysize)
    end.movex(length_row)

    # Creating pads
    P = Component()
    pad = gf.c.rectangle(size=(x, z), layer=pad_layer)
    pad1 = P.add_ref(pad)
    pad1.movex(-x - width)
    pad2 = P.add_ref(pad)
    pad2.movex(length_row + width)
    net = P.add_ref(N)
    net.ymin = pad1.ymin
    P.flatten()
    return P

resistance_meander

resistance_sheet

resistance_sheet

resistance_sheet(
    width: float = 10.0,
    layers: LayerSpecs = ("HEATER",),
    layer_offsets: Floats = (0, 0.2),
    pad: ComponentSpec = "via_stack_heater_mtop",
    pad_size: Size = (50.0, 50.0),
    pad_pitch: float = 100.0,
    ohms_per_square: float | None = None,
    pad_port_name: str = "e4",
) -> Component

Returns Sheet resistance.

keeps connectivity for pads and first layer in layers

Parameters:

Name Type Description Default
width float

in um.

10.0
layers LayerSpecs

for the middle part.

('HEATER',)
layer_offsets Floats

from edge, positive: over, negative: inclusion.

(0, 0.2)
pad ComponentSpec

function to create a pad.

'via_stack_heater_mtop'
pad_size Size

in um.

(50.0, 50.0)
pad_pitch float

in um.

100.0
ohms_per_square float | None

optional sheet resistance to compute info.resistance.

None
pad_port_name str

port name for the pad.

'e4'
Source code in gdsfactory/components/pcms/resistance_sheet.py
10
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
@gf.cell_with_module_name(tags=["pcms"])
def resistance_sheet(
    width: float = 10.0,
    layers: LayerSpecs = ("HEATER",),
    layer_offsets: Floats = (0, 0.2),
    pad: ComponentSpec = "via_stack_heater_mtop",
    pad_size: Size = (50.0, 50.0),
    pad_pitch: float = 100.0,
    ohms_per_square: float | None = None,
    pad_port_name: str = "e4",
) -> Component:
    """Returns Sheet resistance.

    keeps connectivity for pads and first layer in layers

    Args:
        width: in um.
        layers: for the middle part.
        layer_offsets: from edge, positive: over, negative: inclusion.
        pad: function to create a pad.
        pad_size: in um.
        pad_pitch: in um.
        ohms_per_square: optional sheet resistance to compute info.resistance.
        pad_port_name: port name for the pad.
    """
    c = Component()

    pad = gf.get_component(pad, size=pad_size)
    length = pad_pitch - pad_size[0]

    pad1 = c << pad
    pad2 = c << pad
    r0 = c << gf.c.compass(
        size=(length + layer_offsets[0], width + layer_offsets[0]), layer=layers[0]
    )

    for layer, offset in zip(layers[1:], layer_offsets[1:], strict=False):
        _ = c << gf.c.compass(
            size=(length + 2 * offset, width + 2 * offset), layer=layer
        )

    pad1.connect(
        "e3", r0.ports["e1"], allow_width_mismatch=True, allow_layer_mismatch=True
    )
    pad2.connect(
        "e1", r0.ports["e3"], allow_width_mismatch=True, allow_layer_mismatch=True
    )

    c.info["resistance"] = ohms_per_square * width * length if ohms_per_square else 0
    c.info["length"] = length
    c.info["width"] = width
    p1 = c.add_port(
        name="pad1",
        port=pad1.ports[pad_port_name],
    )
    p2 = c.add_port(
        name="pad2",
        port=pad2.ports[pad_port_name],
    )
    if p1.port_type == "electrical":
        c.create_pin(ports=[p1], name="pad1")
    if p2.port_type == "electrical":
        c.create_pin(ports=[p2], name="pad2")
    return c

resistance_sheet

resolution_test_pattern

resolution_test_pattern

resolution_test_pattern(
    radius: float = 50.0,
    n_spokes: int = 36,
    width: float = 1.0,
    layer: LayerSpec = "WG",
) -> Component

Radial Siemens star resolution test pattern.

Creates a circle of alternating filled/empty pie-shaped wedges. The pattern is useful for evaluating lithographic resolution, as the feature size decreases toward the center.

Parameters:

Name Type Description Default
radius float

Outer radius of the star pattern in um.

50.0
n_spokes int

Total number of spokes (filled + empty). Must be even.

36
width float

Target outer edge width of each spoke in um (informational; actual angular width is 360/n_spokes degrees).

1.0
layer LayerSpec

Layer specification for the filled wedges.

'WG'
Source code in gdsfactory/components/pcms/resolution_test_pattern.py
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
@gf.cell_with_module_name(tags=["pcms"])
def resolution_test_pattern(
    radius: float = 50.0,
    n_spokes: int = 36,
    width: float = 1.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Radial Siemens star resolution test pattern.

    Creates a circle of alternating filled/empty pie-shaped wedges.
    The pattern is useful for evaluating lithographic resolution,
    as the feature size decreases toward the center.

    Args:
        radius: Outer radius of the star pattern in um.
        n_spokes: Total number of spokes (filled + empty). Must be even.
        width: Target outer edge width of each spoke in um (informational;
            actual angular width is 360/n_spokes degrees).
        layer: Layer specification for the filled wedges.
    """
    c = Component()

    angle_step = 2 * np.pi / n_spokes
    n_arc_pts = 64  # points along each wedge arc for smoothness

    for i in range(0, n_spokes, 2):
        theta_start = i * angle_step
        theta_end = theta_start + angle_step

        arc_angles = np.linspace(theta_start, theta_end, n_arc_pts)
        arc_x = radius * np.cos(arc_angles)
        arc_y = radius * np.sin(arc_angles)

        points = [(0.0, 0.0)]
        points.extend(zip(arc_x, arc_y, strict=False))
        points.append((0.0, 0.0))

        c.add_polygon(points, layer=layer)

    return c

resolution_test_pattern

ruler

ruler

ruler(
    height_long: float = 55,
    height_short: float = 5,
    height_numbered: float = 10,
    width: float = 2,
    spacing: float = 5.0,
    marks: tuple[float | None, ...] = (
        -100,
        None,
        -90,
        None,
        -80,
        None,
        -70,
        None,
        -60,
        None,
        -50,
        None,
        -40,
        None,
        -30,
        None,
        -20,
        None,
        -10,
        None,
        0,
    ),
    layer: LayerSpec = "WG",
    bbox_layers: tuple[LayerSpec, ...] | None = None,
    bbox_offset: float = 3.0,
    long_marks: tuple[float, ...] = (-50, 0),
    text_size: float = 3.5,
) -> gf.Component

Ruler structure for lithographic measurement.

Includes marks of varying scales to allow for easy reading by eye.

Parameters:

Name Type Description Default
height_long float

Height of the long ruling marks in um.

55
height_short float

Height of the short ruling marks in um.

5
height_numbered float

Height of the numbered ruling marks in um.

10
width float

Width of the ruling marks in um.

2
spacing float

Center-to-center spacing of the ruling marks in um.

5.0
marks tuple[float | None, ...]

Height scale pattern of marks.

(-100, None, -90, None, -80, None, -70, None, -60, None, -50, None, -40, None, -30, None, -20, None, -10, None, 0)
layer LayerSpec

Specific layer to put the ruler geometry on.

'WG'
bbox_layers tuple[LayerSpec, ...] | None

Layers to include in the bounding box.

None
bbox_offset float

Offsets for each bounding box layer.

3.0
cross_section

Cross-section spec for the ruler. Overrides layer if provided.

required
long_marks tuple[float, ...]

Marks that are long.

(-50, 0)
text_size float

Size of the text in um.

3.5
Source code in gdsfactory/components/pcms/ruler.py
 9
10
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
@gf.cell_with_module_name(tags=["pcms"])
def ruler(
    height_long: float = 55,
    height_short: float = 5,
    height_numbered: float = 10,
    width: float = 2,
    spacing: float = 5.0,
    marks: tuple[float | None, ...] = (
        -100,
        None,
        -90,
        None,
        -80,
        None,
        -70,
        None,
        -60,
        None,
        -50,
        None,
        -40,
        None,
        -30,
        None,
        -20,
        None,
        -10,
        None,
        0,
    ),
    layer: LayerSpec = "WG",
    bbox_layers: tuple[LayerSpec, ...] | None = None,
    bbox_offset: float = 3.0,
    long_marks: tuple[float, ...] = (-50, 0),
    text_size: float = 3.5,
) -> gf.Component:
    """Ruler structure for lithographic measurement.

    Includes marks of varying scales to allow for easy reading by eye.

    Args:
        height_long: Height of the long ruling marks in um.
        height_short: Height of the short ruling marks in um.
        height_numbered: Height of the numbered ruling marks in um.
        width: Width of the ruling marks in um.
        spacing: Center-to-center spacing of the ruling marks in um.
        marks: Height scale pattern of marks.
        layer: Specific layer to put the ruler geometry on.
        bbox_layers: Layers to include in the bounding box.
        bbox_offset: Offsets for each bounding box layer.
        cross_section: Cross-section spec for the ruler. Overrides layer if provided.
        long_marks: Marks that are long.
        text_size: Size of the text in um.
    """
    ymin = 0.0
    c = gf.Component()
    for i, mark in enumerate(marks):
        h = height_numbered if mark else height_short
        h = height_long if mark in long_marks else h

        if mark in long_marks:
            ymin = 0.0
        else:
            ymin += height_short

        ref = c << gf.components.rectangle(size=(width, h), layer=layer, port_type=None)
        ref.xmin = i * spacing
        ref.ymin = ymin

        if mark is not None:
            t = c << gf.c.text_rectangular(
                text=str(mark), size=text_size / 5, layer=layer
            )
            t.rotate(90)
            t.ymin = ref.ymin + 1
            t.xmax = ref.xmin - 1
    if bbox_layers:
        gf.add_padding(c, layers=bbox_layers, default=bbox_offset)
    return c

ruler

staircase

staircase(
    component: ComponentSpec | Component = "bend_euler",
    straight: ComponentSpec = "straight",
    length_v: float = 5.0,
    length_h: float = 5.0,
    rows: int = 4,
    **kwargs: Any
) -> Component

Returns staircase.

Parameters:

Name Type Description Default
component ComponentSpec | Component

bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
length_v float

vertical length.

5.0
length_h float

vertical length.

5.0
rows int

number of rows.

4
cols

number of cols.

required
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/pcms/cutback_bend.py
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
@gf.cell_with_module_name(tags=["pcms"])
def staircase(
    component: ComponentSpec | Component = "bend_euler",
    straight: ComponentSpec = "straight",
    length_v: float = 5.0,
    length_h: float = 5.0,
    rows: int = 4,
    **kwargs: Any,
) -> Component:
    """Returns staircase.

    Args:
        component: bend spec.
        straight: straight spec.
        length_v: vertical length.
        length_h: vertical length.
        rows: number of rows.
        cols: number of cols.
        kwargs: cross_section settings.
    """
    bend90 = (
        component
        if isinstance(component, Component)
        else gf.get_component(component, **kwargs)
    )

    wgh = gf.get_component(straight, length=length_h, **kwargs)
    wgv = gf.get_component(straight, length=length_v, **kwargs)

    # Define a map between symbols and (component, input port, output port)
    symbol_to_component = {
        "A": (bend90, "o1", "o2"),
        "B": (bend90, "o2", "o1"),
        "-": (wgh, "o1", "o2"),
        "|": (wgv, "o1", "o2"),
    }

    # Generate the sequence of staircases
    s = "-A|B" * rows + "-"

    c = component_sequence(
        sequence=s,
        symbol_to_component=symbol_to_component,
        start_orientation=0,
    )
    c.info["components"] = 2 * rows
    return c

staircase

vernier_scale

vernier_scale

vernier_scale(
    n_divisions: int = 10,
    pitch_main: float = 10.0,
    pitch_vernier: float = 9.8,
    mark_width: float = 1.0,
    mark_height_main: float = 20.0,
    mark_height_vernier: float = 15.0,
    layer_main: LayerSpec = "WG",
    layer_vernier: LayerSpec = (2, 0),
) -> Component

Vernier scale for overlay measurement.

Creates two rows of rectangular marks: a main scale and a vernier scale. The slight pitch difference between the two scales allows sub-pitch overlay measurement. The zero marks of both scales are centered at the origin.

Parameters:

Name Type Description Default
n_divisions int

Number of marks on each side of the center mark (total marks per scale = 2 * n_divisions + 1).

10
pitch_main float

Center-to-center spacing of main scale marks in um.

10.0
pitch_vernier float

Center-to-center spacing of vernier scale marks in um.

9.8
mark_width float

Width of each rectangular mark in um.

1.0
mark_height_main float

Height of each main scale mark in um.

20.0
mark_height_vernier float

Height of each vernier scale mark in um.

15.0
layer_main LayerSpec

Layer specification for the main scale marks.

'WG'
layer_vernier LayerSpec

Layer specification for the vernier scale marks.

(2, 0)
Source code in gdsfactory/components/pcms/vernier_scale.py
10
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
@gf.cell_with_module_name(tags=["pcms"])
def vernier_scale(
    n_divisions: int = 10,
    pitch_main: float = 10.0,
    pitch_vernier: float = 9.8,
    mark_width: float = 1.0,
    mark_height_main: float = 20.0,
    mark_height_vernier: float = 15.0,
    layer_main: LayerSpec = "WG",
    layer_vernier: LayerSpec = (2, 0),
) -> Component:
    """Vernier scale for overlay measurement.

    Creates two rows of rectangular marks: a main scale and a vernier scale.
    The slight pitch difference between the two scales allows sub-pitch
    overlay measurement. The zero marks of both scales are centered at the
    origin.

    Args:
        n_divisions: Number of marks on each side of the center mark
            (total marks per scale = 2 * n_divisions + 1).
        pitch_main: Center-to-center spacing of main scale marks in um.
        pitch_vernier: Center-to-center spacing of vernier scale marks in um.
        mark_width: Width of each rectangular mark in um.
        mark_height_main: Height of each main scale mark in um.
        mark_height_vernier: Height of each vernier scale mark in um.
        layer_main: Layer specification for the main scale marks.
        layer_vernier: Layer specification for the vernier scale marks.
    """
    c = Component()

    hw = mark_width / 2

    # Main scale marks (below y=0)
    for i in range(-n_divisions, n_divisions + 1):
        x = i * pitch_main
        c.add_polygon(
            [
                (x - hw, 0),
                (x + hw, 0),
                (x + hw, -mark_height_main),
                (x - hw, -mark_height_main),
            ],
            layer=layer_main,
        )

    # Vernier scale marks (above y=0)
    for i in range(-n_divisions, n_divisions + 1):
        x = i * pitch_vernier
        c.add_polygon(
            [
                (x - hw, 0),
                (x + hw, 0),
                (x + hw, mark_height_vernier),
                (x - hw, mark_height_vernier),
            ],
            layer=layer_vernier,
        )

    return c

vernier_scale

verniers

verniers

verniers(
    widths: Floats = (0.1, 0.2, 0.3, 0.4, 0.5),
    gap: float = 0.1,
    xsize: float = 100.0,
    layer_label: LayerSpec = "TEXT",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip_no_ports",
    **kwargs: Any
) -> Component

Returns a component with verniers.

Parameters:

Name Type Description Default
widths Floats

list of widths.

(0.1, 0.2, 0.3, 0.4, 0.5)
gap float

gap between verniers.

0.1
xsize float

size of the component.

100.0
layer_label LayerSpec

layer for the labels.

'TEXT'
straight ComponentSpec

straight function.

'straight'
cross_section CrossSectionSpec

cross_section spec.

'strip_no_ports'
kwargs Any

straight settings.

{}
Source code in gdsfactory/components/pcms/verniers.py
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
@gf.cell_with_module_name(tags=["pcms"])
def verniers(
    widths: Floats = (0.1, 0.2, 0.3, 0.4, 0.5),
    gap: float = 0.1,
    xsize: float = 100.0,
    layer_label: LayerSpec = "TEXT",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip_no_ports",
    **kwargs: Any,
) -> Component:
    """Returns a component with verniers.

    Args:
        widths: list of widths.
        gap: gap between verniers.
        xsize: size of the component.
        layer_label: layer for the labels.
        straight: straight function.
        cross_section: cross_section spec.
        kwargs: straight settings.
    """
    c = gf.Component()
    y = 0.0

    for width in widths:
        w = c << gf.get_component(
            straight, width=width, length=xsize, cross_section=cross_section, **kwargs
        )
        y += width / 2
        w.y = y
        c.add_label(text=str(int(width * 1e3)), position=(0, y), layer=layer_label)
        y += width / 2 + gap

    return c

verniers

version_stamp

qrcode

qrcode(
    data: str = "mask01",
    psize: int = 1,
    layer: LayerSpec = "WG",
) -> Component

Returns QRCode.

Parameters:

Name Type Description Default
data str

string to encode.

'mask01'
psize int

pixel size.

1
layer LayerSpec

layer to use.

'WG'
Source code in gdsfactory/components/pcms/version_stamp.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@gf.cell_with_module_name(tags=["pcms"])
def qrcode(data: str = "mask01", psize: int = 1, layer: LayerSpec = "WG") -> Component:
    """Returns QRCode.

    Args:
        data: string to encode.
        psize: pixel size.
        layer: layer to use.
    """
    import qrcode

    pix = pixel(size=psize, layer=layer)
    q = qrcode.QRCode()
    q.add_data(data)
    matrix = q.get_matrix()
    c = gf.Component()
    for i, row in enumerate(matrix):
        for j, value in enumerate(row):
            if value:
                ref = c << pix
                ref.center = (i * psize, j * psize)
    c.flatten()
    return c

version_stamp

version_stamp(
    labels: tuple[str, ...] = ("demo_label",),
    with_qr_code: bool = False,
    layer: LayerSpec = "WG",
    pixel_size: int = 1,
    version: str | None = None,
    text_size: int = 10,
) -> Component

Component with module version and date.

Parameters:

Name Type Description Default
labels tuple[str, ...]

Iterable of labels.

('demo_label',)
with_qr_code bool

Whether to add a QR code with the date.

False
layer LayerSpec

Layer to use.

'WG'
pixel_size int

Pixel size.

1
version str | None

Version string.

None
text_size int

Text size.

10
Source code in gdsfactory/components/pcms/version_stamp.py
 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
@gf.cell_with_module_name(tags=["pcms"])
def version_stamp(
    labels: tuple[str, ...] = ("demo_label",),
    with_qr_code: bool = False,
    layer: LayerSpec = "WG",
    pixel_size: int = 1,
    version: str | None = None,
    text_size: int = 10,
) -> Component:
    """Component with module version and date.

    Args:
        labels: Iterable of labels.
        with_qr_code: Whether to add a QR code with the date.
        layer: Layer to use.
        pixel_size: Pixel size.
        version: Version string.
        text_size: Text size.

    """
    now = datetime.datetime.now()
    timestamp = f"{now:%Y-%m-%d %H:%M:%S}"
    short_stamp = f"{now:%y.%m.%d.%H.%M.%S}"

    c = gf.Component()
    if with_qr_code:
        data = f"{timestamp}/{platform.node()}"
        q = c << qrcode(layer=layer, data=data, psize=pixel_size)
        q.center = (0, 0)
        x = q.xsize * 0.5 + 10

    else:
        x = 0

    _ = c << text(
        position=(x, text_size + 2 * pixel_size),
        text=short_stamp,
        layer=layer,
        justify="left",
        size=text_size,
    )

    if version:
        _ = c << text(
            position=(x, 0), text=version, layer=layer, justify="left", size=text_size
        )

    for i, line in enumerate(labels):
        _ = c << text(
            position=(x, -(i + 1) * (text_size + 2 * pixel_size)),
            text=line,
            layer=layer,
            justify="left",
            size=text_size,
        )

    c.flatten()
    return c

version_stamp

quantum

coupler_capacitive

coupler_capacitive

coupler_capacitive(
    pad_width: float = 20.0,
    pad_height: float = 50.0,
    gap: float = 2.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates a capacitive coupler for quantum circuits.

A capacitive coupler consists of two metal pads separated by a small gap, providing capacitive coupling between circuit elements like qubits and resonators.

            ______               ______
  _______  |      |             |      | _______
 |       | |      |             |      ||       |
 | feed1 | | pad1 | ====gap==== | pad2 || feed2 |
 |       | |      |             |      ||       |
 |_______| |      |             |      ||_______|
           |______|             |______|

Parameters:

Name Type Description Default
pad_width float

Width of each coupling pad in μm.

20.0
pad_height float

Height of each coupling pad in μm.

50.0
gap float

Gap between the coupling pads in μm.

2.0
feed_width float

Width of the feed lines in μm.

10.0
feed_length float

Length of the feed lines in μm.

30.0
layer_metal LayerSpec

Layer for the metal structures.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the capacitive coupler geometry.

Source code in gdsfactory/components/quantum/coupler_capacitive.py
 10
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
@gf.cell_with_module_name(tags=["quantum"])
def coupler_capacitive(
    pad_width: float = 20.0,
    pad_height: float = 50.0,
    gap: float = 2.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a capacitive coupler for quantum circuits.

    A capacitive coupler consists of two metal pads separated by a small gap,
    providing capacitive coupling between circuit elements like qubits and resonators.

                    ______               ______
          _______  |      |             |      | _______
         |       | |      |             |      ||       |
         | feed1 | | pad1 | ====gap==== | pad2 || feed2 |
         |       | |      |             |      ||       |
         |_______| |      |             |      ||_______|
                   |______|             |______|

    Args:
        pad_width: Width of each coupling pad in μm.
        pad_height: Height of each coupling pad in μm.
        gap: Gap between the coupling pads in μm.
        feed_width: Width of the feed lines in μm.
        feed_length: Length of the feed lines in μm.
        layer_metal: Layer for the metal structures.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the capacitive coupler geometry.
    """
    c = Component()

    # Create left coupling pad
    left_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
    )
    left_pad_ref = c.add_ref(left_pad)
    left_pad_ref.move((-pad_width - gap / 2, -pad_height / 2))

    # Create right coupling pad
    right_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
    )
    right_pad_ref = c.add_ref(right_pad)
    right_pad_ref.move((gap / 2, -pad_height / 2))

    # Create left feed line
    left_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    left_feed_ref = c.add_ref(left_feed)
    left_feed_ref.move((-pad_width - gap / 2 - feed_length, -feed_width / 2))

    # Create right feed line
    right_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    right_feed_ref = c.add_ref(right_feed)
    right_feed_ref.move((gap / 2 + pad_width, -feed_width / 2))

    # Add ports
    c.add_port(
        name="left",
        center=(-pad_width - gap / 2 - feed_length, 0),
        width=feed_width,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="right",
        center=(gap / 2 + pad_width + feed_length, 0),
        width=feed_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["coupler_type"] = "capacitive"
    c.info["pad_width"] = pad_width
    c.info["pad_height"] = pad_height
    c.info["gap"] = gap
    c.info["coupling_area"] = pad_width * pad_height

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

coupler_interdigital

coupler_interdigital(
    fingers: int = 6,
    finger_length: float = 30.0,
    finger_width: float = 2.0,
    finger_gap_vertical: float = 2.0,
    finger_gap_horizontal: float = 3.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates an interdigital capacitive coupler.

Each side includes a base column (a vertical metal block) to which the fingers are attached.

  • The width of the base column is equal to the height of the fingers.
  • The finger_length parameter refers only to the length of the fingers extending from the base, and does NOT include the base column width

Parameters:

Name Type Description Default
fingers int

Number of fingers per side.

6
finger_length float

Length of each finger in μm (see note above).

30.0
finger_width float

Width of each finger in μm.

2.0
finger_gap_vertical float

Vertical gap between fingers in μm (g1).

2.0
finger_gap_horizontal float

Horizontal gap between fingers in μm (g2).

3.0
feed_width float

Width of the feed lines in μm.

10.0
feed_length float

Length of the feed lines in μm.

30.0
layer_metal LayerSpec

Layer for the metal structures.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the interdigital coupler geometry.

    ┌────────┐
   base columns
   ↓                    ↓

┌────────┐ ┌────────┐ │ │█████████████ █│ │ │ │█ g1 █│ │ │ │█ <─g2─> █████████████│ │ │ │█ █│ │ │ feed1 │█████████████ █│ feed2 │ │ │█ █│ │ │ │█ █████████████│ │ │ │█ █│ │ │ │█████████████ █│ │ └────────┘█ █└────────┘

Source code in gdsfactory/components/quantum/coupler_capacitive.py
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
@gf.cell_with_module_name(tags=["quantum"])
def coupler_interdigital(
    fingers: int = 6,
    finger_length: float = 30.0,
    finger_width: float = 2.0,
    finger_gap_vertical: float = 2.0,
    finger_gap_horizontal: float = 3.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates an interdigital capacitive coupler.

    Each side includes a base column (a vertical metal block) to which the fingers are attached.

    - The width of the base column is equal to the height of the fingers.
    - The finger_length parameter refers only to the length of the fingers *extending from the base*,
      and does NOT include the base column width

    Args:
        fingers: Number of fingers per side.
        finger_length: Length of each finger in μm (see note above).
        finger_width: Width of each finger in μm.
        finger_gap_vertical: Vertical gap between fingers in μm (g1).
        finger_gap_horizontal: Horizontal gap between fingers in μm (g2).
        feed_width: Width of the feed lines in μm.
        feed_length: Length of the feed lines in μm.
        layer_metal: Layer for the metal structures.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the interdigital coupler geometry.

                    ┌────────┐
                   base columns
                   ↓                    ↓
         ┌────────┐                      ┌────────┐
         │        │█████████████        █│        │
         │        │█        g1          █│        │
         │        │█ <─g2─> █████████████│        │
         │        │█                    █│        │
         │ feed1  │█████████████        █│ feed2  │
         │        │█                    █│        │
         │        │█        █████████████│        │
         │        │█                    █│        │
         │        │█████████████        █│        │
         └────────┘█                    █└────────┘

    """
    c = Component()

    # Calculate total dimensions
    total_width = finger_length + finger_gap_horizontal
    total_height = fingers * finger_width + (fingers - 1) * finger_gap_vertical

    # Create left side base column
    left_base = gf.components.rectangle(
        size=(finger_width, total_height),
        layer=layer_metal,
    )
    left_base_ref = c.add_ref(left_base)
    left_base_ref.move((-total_width / 2 - finger_width, -total_height / 2))

    # Create right side base column
    right_base = gf.components.rectangle(
        size=(finger_width, total_height),
        layer=layer_metal,
    )
    right_base_ref = c.add_ref(right_base)
    right_base_ref.move((total_width / 2, -total_height / 2))

    # Create interdigital fingers
    for i in range(fingers):
        left_finger = gf.components.rectangle(
            size=(finger_length, finger_width),
            layer=layer_metal,
        )
        left_finger_ref = c.add_ref(left_finger)

        # We start from a left finger
        x_pos = -finger_length / 2 + (-1) ** (i + 1) * finger_gap_horizontal / 2
        y_pos = (
            total_height / 2 - finger_width - i * (finger_width + finger_gap_vertical)
        )
        left_finger_ref.move((x_pos, y_pos))

    # Create feed lines
    left_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    left_feed_ref = c.add_ref(left_feed)
    left_feed_ref.move((-total_width / 2 - finger_width - feed_length, -feed_width / 2))

    right_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    right_feed_ref = c.add_ref(right_feed)
    right_feed_ref.move((total_width / 2 + finger_width, -feed_width / 2))

    # Add ports
    c.add_port(
        name="left",
        center=(-total_width / 2 - finger_width - feed_length, 0),
        width=feed_width,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="right",
        center=(total_width / 2 + finger_width + feed_length, 0),
        width=feed_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["coupler_type"] = "interdigital"
    c.info["fingers"] = fingers
    c.info["finger_length"] = finger_length
    c.info["finger_width"] = finger_width
    c.info["finger_gap_horizontal"] = finger_gap_horizontal
    c.info["finger_gap_vertical"] = finger_gap_vertical

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

coupler_tunable

coupler_tunable(
    pad_width: float = 30.0,
    pad_height: float = 40.0,
    gap: float = 3.0,
    tuning_pad_width: float = 15.0,
    tuning_pad_height: float = 20.0,
    tuning_gap: float = 1.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    layer_tuning: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component

Creates a tunable capacitive coupler with voltage control.

A tunable coupler includes additional electrodes that can be voltage-biased to change the coupling strength dynamically.

Parameters:

Name Type Description Default
pad_width float

Width of main coupling pads in μm.

30.0
pad_height float

Height of main coupling pads in μm.

40.0
gap float

Gap between main coupling pads in μm.

3.0
tuning_pad_width float

Width of tuning pads in μm.

15.0
tuning_pad_height float

Height of tuning pads in μm.

20.0
tuning_gap float

Gap to tuning pads in μm.

1.0
feed_width float

Width of feed lines in μm.

10.0
feed_length float

Length of feed lines in μm.

30.0
layer_metal LayerSpec

Layer for main metal structures.

(1, 0)
layer_tuning LayerSpec

Layer for tuning electrodes.

(3, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the tunable coupler geometry.

    (connected to feed)
         _______
        |       |
        | tpad1 |
        |       |
        |_______|
        tuning gap
   ______        ______

_ | | | | _

Component

| | | | | || |

Component

| feed1 | | pad1 | gap | pad2 || feed2 |

Component

| | | | | || |

Component

|_| | | | ||_| |__| |_| tuning gap _ | | | tpad2 | | | |____| (connected to feed)

Source code in gdsfactory/components/quantum/coupler_capacitive.py
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
@gf.cell_with_module_name(tags=["quantum"])
def coupler_tunable(
    pad_width: float = 30.0,
    pad_height: float = 40.0,
    gap: float = 3.0,
    tuning_pad_width: float = 15.0,
    tuning_pad_height: float = 20.0,
    tuning_gap: float = 1.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    layer_tuning: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a tunable capacitive coupler with voltage control.

    A tunable coupler includes additional electrodes that can be voltage-biased
    to change the coupling strength dynamically.


    Args:
        pad_width: Width of main coupling pads in μm.
        pad_height: Height of main coupling pads in μm.
        gap: Gap between main coupling pads in μm.
        tuning_pad_width: Width of tuning pads in μm.
        tuning_pad_height: Height of tuning pads in μm.
        tuning_gap: Gap to tuning pads in μm.
        feed_width: Width of feed lines in μm.
        feed_length: Length of feed lines in μm.
        layer_metal: Layer for main metal structures.
        layer_tuning: Layer for tuning electrodes.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the tunable coupler geometry.

                    (connected to feed)
                         _______
                        |       |
                        | tpad1 |
                        |       |
                        |_______|
                        tuning gap
                   ______        ______
         _______  |      |      |      | _______
        |       | |      |      |      ||       |
        | feed1 | | pad1 | gap  | pad2 || feed2 |
        |       | |      |      |      ||       |
        |_______| |      |      |      ||_______|
                  |______|      |______|
                        tuning gap
                         _______
                        |       |
                        | tpad2 |
                        |       |
                        |_______|
                    (connected to feed)
    """
    c = Component()

    # Create main coupling pads
    left_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
    )
    left_pad_ref = c.add_ref(left_pad)
    left_pad_ref.move((-pad_width - gap / 2, -pad_height / 2))

    right_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
    )
    right_pad_ref = c.add_ref(right_pad)
    right_pad_ref.move((gap / 2, -pad_height / 2))

    # Create tuning pads above and below
    top_tuning_pad = gf.components.rectangle(
        size=(tuning_pad_width, tuning_pad_height),
        layer=layer_tuning,
    )
    top_tuning_ref = c.add_ref(top_tuning_pad)
    top_tuning_ref.move((-tuning_pad_width / 2, pad_height / 2 + tuning_gap))

    bottom_tuning_pad = gf.components.rectangle(
        size=(tuning_pad_width, tuning_pad_height),
        layer=layer_tuning,
    )
    bottom_tuning_ref = c.add_ref(bottom_tuning_pad)
    bottom_tuning_ref.move(
        (-tuning_pad_width / 2, -pad_height / 2 - tuning_gap - tuning_pad_height)
    )

    # Create feed lines for main pads
    left_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    left_feed_ref = c.add_ref(left_feed)
    left_feed_ref.move((-pad_width - gap / 2 - feed_length, -feed_width / 2))

    right_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    right_feed_ref = c.add_ref(right_feed)
    right_feed_ref.move((gap / 2 + pad_width, -feed_width / 2))

    # Create tuning feed lines
    top_tuning_feed = gf.components.rectangle(
        size=(feed_width, feed_length),
        layer=layer_tuning,
    )
    top_tuning_feed_ref = c.add_ref(top_tuning_feed)
    top_tuning_feed_ref.move(
        (-feed_width / 2, pad_height / 2 + tuning_gap + tuning_pad_height)
    )

    bottom_tuning_feed = gf.components.rectangle(
        size=(feed_width, feed_length),
        layer=layer_tuning,
    )
    bottom_tuning_feed_ref = c.add_ref(bottom_tuning_feed)
    bottom_tuning_feed_ref.move(
        (
            -feed_width / 2,
            -pad_height / 2 - tuning_gap - tuning_pad_height - feed_length,
        )
    )

    # Add ports
    c.add_port(
        name="left",
        center=(-pad_width - gap / 2 - feed_length, 0),
        width=feed_width,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="right",
        center=(gap / 2 + pad_width + feed_length, 0),
        width=feed_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="tuning_top",
        center=(0, pad_height / 2 + tuning_gap + tuning_pad_height + feed_length),
        width=feed_width,
        orientation=90,
        layer=layer_tuning,
        port_type=port_type,
    )

    c.add_port(
        name="tuning_bottom",
        center=(0, -pad_height / 2 - tuning_gap - tuning_pad_height - feed_length),
        width=feed_width,
        orientation=270,
        layer=layer_tuning,
        port_type=port_type,
    )

    # Add metadata
    c.info["coupler_type"] = "tunable"
    c.info["pad_width"] = pad_width
    c.info["pad_height"] = pad_height
    c.info["gap"] = gap
    c.info["tuning_pad_width"] = tuning_pad_width
    c.info["tuning_pad_height"] = tuning_pad_height
    c.info["tuning_gap"] = tuning_gap

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

coupler_capacitive

coupler_interdigital

coupler_interdigital(
    fingers: int = 6,
    finger_length: float = 30.0,
    finger_width: float = 2.0,
    finger_gap_vertical: float = 2.0,
    finger_gap_horizontal: float = 3.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates an interdigital capacitive coupler.

Each side includes a base column (a vertical metal block) to which the fingers are attached.

  • The width of the base column is equal to the height of the fingers.
  • The finger_length parameter refers only to the length of the fingers extending from the base, and does NOT include the base column width

Parameters:

Name Type Description Default
fingers int

Number of fingers per side.

6
finger_length float

Length of each finger in μm (see note above).

30.0
finger_width float

Width of each finger in μm.

2.0
finger_gap_vertical float

Vertical gap between fingers in μm (g1).

2.0
finger_gap_horizontal float

Horizontal gap between fingers in μm (g2).

3.0
feed_width float

Width of the feed lines in μm.

10.0
feed_length float

Length of the feed lines in μm.

30.0
layer_metal LayerSpec

Layer for the metal structures.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the interdigital coupler geometry.

    ┌────────┐
   base columns
   ↓                    ↓

┌────────┐ ┌────────┐ │ │█████████████ █│ │ │ │█ g1 █│ │ │ │█ <─g2─> █████████████│ │ │ │█ █│ │ │ feed1 │█████████████ █│ feed2 │ │ │█ █│ │ │ │█ █████████████│ │ │ │█ █│ │ │ │█████████████ █│ │ └────────┘█ █└────────┘

Source code in gdsfactory/components/quantum/coupler_capacitive.py
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
@gf.cell_with_module_name(tags=["quantum"])
def coupler_interdigital(
    fingers: int = 6,
    finger_length: float = 30.0,
    finger_width: float = 2.0,
    finger_gap_vertical: float = 2.0,
    finger_gap_horizontal: float = 3.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates an interdigital capacitive coupler.

    Each side includes a base column (a vertical metal block) to which the fingers are attached.

    - The width of the base column is equal to the height of the fingers.
    - The finger_length parameter refers only to the length of the fingers *extending from the base*,
      and does NOT include the base column width

    Args:
        fingers: Number of fingers per side.
        finger_length: Length of each finger in μm (see note above).
        finger_width: Width of each finger in μm.
        finger_gap_vertical: Vertical gap between fingers in μm (g1).
        finger_gap_horizontal: Horizontal gap between fingers in μm (g2).
        feed_width: Width of the feed lines in μm.
        feed_length: Length of the feed lines in μm.
        layer_metal: Layer for the metal structures.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the interdigital coupler geometry.

                    ┌────────┐
                   base columns
                   ↓                    ↓
         ┌────────┐                      ┌────────┐
         │        │█████████████        █│        │
         │        │█        g1          █│        │
         │        │█ <─g2─> █████████████│        │
         │        │█                    █│        │
         │ feed1  │█████████████        █│ feed2  │
         │        │█                    █│        │
         │        │█        █████████████│        │
         │        │█                    █│        │
         │        │█████████████        █│        │
         └────────┘█                    █└────────┘

    """
    c = Component()

    # Calculate total dimensions
    total_width = finger_length + finger_gap_horizontal
    total_height = fingers * finger_width + (fingers - 1) * finger_gap_vertical

    # Create left side base column
    left_base = gf.components.rectangle(
        size=(finger_width, total_height),
        layer=layer_metal,
    )
    left_base_ref = c.add_ref(left_base)
    left_base_ref.move((-total_width / 2 - finger_width, -total_height / 2))

    # Create right side base column
    right_base = gf.components.rectangle(
        size=(finger_width, total_height),
        layer=layer_metal,
    )
    right_base_ref = c.add_ref(right_base)
    right_base_ref.move((total_width / 2, -total_height / 2))

    # Create interdigital fingers
    for i in range(fingers):
        left_finger = gf.components.rectangle(
            size=(finger_length, finger_width),
            layer=layer_metal,
        )
        left_finger_ref = c.add_ref(left_finger)

        # We start from a left finger
        x_pos = -finger_length / 2 + (-1) ** (i + 1) * finger_gap_horizontal / 2
        y_pos = (
            total_height / 2 - finger_width - i * (finger_width + finger_gap_vertical)
        )
        left_finger_ref.move((x_pos, y_pos))

    # Create feed lines
    left_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    left_feed_ref = c.add_ref(left_feed)
    left_feed_ref.move((-total_width / 2 - finger_width - feed_length, -feed_width / 2))

    right_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    right_feed_ref = c.add_ref(right_feed)
    right_feed_ref.move((total_width / 2 + finger_width, -feed_width / 2))

    # Add ports
    c.add_port(
        name="left",
        center=(-total_width / 2 - finger_width - feed_length, 0),
        width=feed_width,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="right",
        center=(total_width / 2 + finger_width + feed_length, 0),
        width=feed_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["coupler_type"] = "interdigital"
    c.info["fingers"] = fingers
    c.info["finger_length"] = finger_length
    c.info["finger_width"] = finger_width
    c.info["finger_gap_horizontal"] = finger_gap_horizontal
    c.info["finger_gap_vertical"] = finger_gap_vertical

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

coupler_interdigital

coupler_tunable

coupler_tunable(
    pad_width: float = 30.0,
    pad_height: float = 40.0,
    gap: float = 3.0,
    tuning_pad_width: float = 15.0,
    tuning_pad_height: float = 20.0,
    tuning_gap: float = 1.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    layer_tuning: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component

Creates a tunable capacitive coupler with voltage control.

A tunable coupler includes additional electrodes that can be voltage-biased to change the coupling strength dynamically.

Parameters:

Name Type Description Default
pad_width float

Width of main coupling pads in μm.

30.0
pad_height float

Height of main coupling pads in μm.

40.0
gap float

Gap between main coupling pads in μm.

3.0
tuning_pad_width float

Width of tuning pads in μm.

15.0
tuning_pad_height float

Height of tuning pads in μm.

20.0
tuning_gap float

Gap to tuning pads in μm.

1.0
feed_width float

Width of feed lines in μm.

10.0
feed_length float

Length of feed lines in μm.

30.0
layer_metal LayerSpec

Layer for main metal structures.

(1, 0)
layer_tuning LayerSpec

Layer for tuning electrodes.

(3, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the tunable coupler geometry.

    (connected to feed)
         _______
        |       |
        | tpad1 |
        |       |
        |_______|
        tuning gap
   ______        ______

_ | | | | _

Component

| | | | | || |

Component

| feed1 | | pad1 | gap | pad2 || feed2 |

Component

| | | | | || |

Component

|_| | | | ||_| |__| |_| tuning gap _ | | | tpad2 | | | |____| (connected to feed)

Source code in gdsfactory/components/quantum/coupler_capacitive.py
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
@gf.cell_with_module_name(tags=["quantum"])
def coupler_tunable(
    pad_width: float = 30.0,
    pad_height: float = 40.0,
    gap: float = 3.0,
    tuning_pad_width: float = 15.0,
    tuning_pad_height: float = 20.0,
    tuning_gap: float = 1.0,
    feed_width: float = 10.0,
    feed_length: float = 30.0,
    layer_metal: LayerSpec = (1, 0),
    layer_tuning: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a tunable capacitive coupler with voltage control.

    A tunable coupler includes additional electrodes that can be voltage-biased
    to change the coupling strength dynamically.


    Args:
        pad_width: Width of main coupling pads in μm.
        pad_height: Height of main coupling pads in μm.
        gap: Gap between main coupling pads in μm.
        tuning_pad_width: Width of tuning pads in μm.
        tuning_pad_height: Height of tuning pads in μm.
        tuning_gap: Gap to tuning pads in μm.
        feed_width: Width of feed lines in μm.
        feed_length: Length of feed lines in μm.
        layer_metal: Layer for main metal structures.
        layer_tuning: Layer for tuning electrodes.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the tunable coupler geometry.

                    (connected to feed)
                         _______
                        |       |
                        | tpad1 |
                        |       |
                        |_______|
                        tuning gap
                   ______        ______
         _______  |      |      |      | _______
        |       | |      |      |      ||       |
        | feed1 | | pad1 | gap  | pad2 || feed2 |
        |       | |      |      |      ||       |
        |_______| |      |      |      ||_______|
                  |______|      |______|
                        tuning gap
                         _______
                        |       |
                        | tpad2 |
                        |       |
                        |_______|
                    (connected to feed)
    """
    c = Component()

    # Create main coupling pads
    left_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
    )
    left_pad_ref = c.add_ref(left_pad)
    left_pad_ref.move((-pad_width - gap / 2, -pad_height / 2))

    right_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
    )
    right_pad_ref = c.add_ref(right_pad)
    right_pad_ref.move((gap / 2, -pad_height / 2))

    # Create tuning pads above and below
    top_tuning_pad = gf.components.rectangle(
        size=(tuning_pad_width, tuning_pad_height),
        layer=layer_tuning,
    )
    top_tuning_ref = c.add_ref(top_tuning_pad)
    top_tuning_ref.move((-tuning_pad_width / 2, pad_height / 2 + tuning_gap))

    bottom_tuning_pad = gf.components.rectangle(
        size=(tuning_pad_width, tuning_pad_height),
        layer=layer_tuning,
    )
    bottom_tuning_ref = c.add_ref(bottom_tuning_pad)
    bottom_tuning_ref.move(
        (-tuning_pad_width / 2, -pad_height / 2 - tuning_gap - tuning_pad_height)
    )

    # Create feed lines for main pads
    left_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    left_feed_ref = c.add_ref(left_feed)
    left_feed_ref.move((-pad_width - gap / 2 - feed_length, -feed_width / 2))

    right_feed = gf.components.rectangle(
        size=(feed_length, feed_width),
        layer=layer_metal,
    )
    right_feed_ref = c.add_ref(right_feed)
    right_feed_ref.move((gap / 2 + pad_width, -feed_width / 2))

    # Create tuning feed lines
    top_tuning_feed = gf.components.rectangle(
        size=(feed_width, feed_length),
        layer=layer_tuning,
    )
    top_tuning_feed_ref = c.add_ref(top_tuning_feed)
    top_tuning_feed_ref.move(
        (-feed_width / 2, pad_height / 2 + tuning_gap + tuning_pad_height)
    )

    bottom_tuning_feed = gf.components.rectangle(
        size=(feed_width, feed_length),
        layer=layer_tuning,
    )
    bottom_tuning_feed_ref = c.add_ref(bottom_tuning_feed)
    bottom_tuning_feed_ref.move(
        (
            -feed_width / 2,
            -pad_height / 2 - tuning_gap - tuning_pad_height - feed_length,
        )
    )

    # Add ports
    c.add_port(
        name="left",
        center=(-pad_width - gap / 2 - feed_length, 0),
        width=feed_width,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="right",
        center=(gap / 2 + pad_width + feed_length, 0),
        width=feed_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="tuning_top",
        center=(0, pad_height / 2 + tuning_gap + tuning_pad_height + feed_length),
        width=feed_width,
        orientation=90,
        layer=layer_tuning,
        port_type=port_type,
    )

    c.add_port(
        name="tuning_bottom",
        center=(0, -pad_height / 2 - tuning_gap - tuning_pad_height - feed_length),
        width=feed_width,
        orientation=270,
        layer=layer_tuning,
        port_type=port_type,
    )

    # Add metadata
    c.info["coupler_type"] = "tunable"
    c.info["pad_width"] = pad_width
    c.info["pad_height"] = pad_height
    c.info["gap"] = gap
    c.info["tuning_pad_width"] = tuning_pad_width
    c.info["tuning_pad_height"] = tuning_pad_height
    c.info["tuning_gap"] = tuning_gap

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

coupler_tunable

flux_qubit

flux_qubit

flux_qubit(
    loop_width: float = 50.0,
    loop_height: float = 50.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    alpha_junction_width: float = 0.12,
    alpha_junction_height: float = 0.25,
    wire_width: float = 2.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_alpha_junction: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component

Creates a flux qubit (persistent current qubit).

A flux qubit consists of a superconducting loop interrupted by three Josephson junctions. Two junctions are identical (beta junctions) while the third is smaller (alpha junction) with roughly 0.5-0.8 times the critical current.

Parameters:

Name Type Description Default
loop_width float

Width of the superconducting loop in μm.

50.0
loop_height float

Height of the superconducting loop in μm.

50.0
junction_width float

Width of the beta Josephson junctions in μm.

0.15
junction_height float

Height of the beta Josephson junctions in μm.

0.3
alpha_junction_width float

Width of the alpha Josephson junction in μm.

0.12
alpha_junction_height float

Height of the alpha Josephson junction in μm.

0.25
wire_width float

Width of the superconducting wires in μm.

2.0
layer_metal LayerSpec

Layer for the metal wires.

(1, 0)
layer_junction LayerSpec

Layer for the beta Josephson junctions.

(2, 0)
layer_alpha_junction LayerSpec

Layer for the alpha Josephson junction.

(3, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the flux qubit geometry.

Source code in gdsfactory/components/quantum/flux_qubit.py
 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
 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
@gf.cell_with_module_name(tags=["quantum"])
def flux_qubit(
    loop_width: float = 50.0,
    loop_height: float = 50.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    alpha_junction_width: float = 0.12,
    alpha_junction_height: float = 0.25,
    wire_width: float = 2.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_alpha_junction: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a flux qubit (persistent current qubit).

    A flux qubit consists of a superconducting loop interrupted by three Josephson junctions.
    Two junctions are identical (beta junctions) while the third is smaller (alpha junction)
    with roughly 0.5-0.8 times the critical current.

    Args:
        loop_width: Width of the superconducting loop in μm.
        loop_height: Height of the superconducting loop in μm.
        junction_width: Width of the beta Josephson junctions in μm.
        junction_height: Height of the beta Josephson junctions in μm.
        alpha_junction_width: Width of the alpha Josephson junction in μm.
        alpha_junction_height: Height of the alpha Josephson junction in μm.
        wire_width: Width of the superconducting wires in μm.
        layer_metal: Layer for the metal wires.
        layer_junction: Layer for the beta Josephson junctions.
        layer_alpha_junction: Layer for the alpha Josephson junction.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the flux qubit geometry.
    """
    c = Component()

    # Create the superconducting loop as a hollow rectangle
    outer_rect = gf.components.rectangle(
        size=(loop_width, loop_height),
        layer=layer_metal,
    )

    inner_rect = gf.components.rectangle(
        size=(loop_width - 2 * wire_width, loop_height - 2 * wire_width),
        layer=layer_metal,
    )

    # Create the loop by boolean difference
    loop = gf.boolean(
        outer_rect,
        inner_rect,
        operation="not",
        layer=layer_metal,
    )

    # Move loop to be centered at origin (use a temporary component)
    loop_centered = Component()
    loop_ref = loop_centered.add_ref(loop)
    loop_ref.move((-loop_width / 2, -loop_height / 2))
    loop_centered.flatten()

    # Create gaps for junctions (standalone components, not added to c)
    # Bottom gap for alpha junction
    alpha_gap = Component()
    alpha_gap_rect = alpha_gap.add_ref(
        gf.components.rectangle(
            size=(alpha_junction_width + 0.1, wire_width + 0.1),
            layer=layer_metal,
        )
    )
    alpha_gap_rect.move((-alpha_junction_width / 2 - 0.05, -loop_height / 2 - 0.05))
    alpha_gap.flatten()

    # Left gap for beta junction
    beta_gap_left = Component()
    beta_gap_left_rect = beta_gap_left.add_ref(
        gf.components.rectangle(
            size=(wire_width + 0.1, junction_height + 0.1),
            layer=layer_metal,
        )
    )
    beta_gap_left_rect.move((-loop_width / 2 - 0.05, -junction_height / 2 - 0.05))
    beta_gap_left.flatten()

    # Right gap for beta junction
    beta_gap_right = Component()
    beta_gap_right_rect = beta_gap_right.add_ref(
        gf.components.rectangle(
            size=(wire_width + 0.1, junction_height + 0.1),
            layer=layer_metal,
        )
    )
    beta_gap_right_rect.move(
        (loop_width / 2 - wire_width - 0.05, -junction_height / 2 - 0.05)
    )
    beta_gap_right.flatten()

    # Remove gaps from the loop sequentially
    loop_with_gaps = gf.boolean(
        loop_centered,
        alpha_gap,
        operation="not",
        layer=layer_metal,
    )
    loop_with_gaps = gf.boolean(
        loop_with_gaps,
        beta_gap_left,
        operation="not",
        layer=layer_metal,
    )
    loop_with_gaps = gf.boolean(
        loop_with_gaps,
        beta_gap_right,
        operation="not",
        layer=layer_metal,
    )
    c.add_ref(loop_with_gaps)

    # Create the alpha junction (smaller)
    alpha_junction = gf.components.rectangle(
        size=(alpha_junction_width, alpha_junction_height),
        layer=layer_alpha_junction,
    )
    alpha_junction_ref = c.add_ref(alpha_junction)
    alpha_junction_ref.move(
        (
            -alpha_junction_width / 2,
            -loop_height / 2 + wire_width / 2 - alpha_junction_height / 2,
        )
    )

    # Create the beta junctions (larger, identical)
    beta_junction_left = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    beta_junction_left_ref = c.add_ref(beta_junction_left)
    beta_junction_left_ref.move(
        (-loop_width / 2 + wire_width / 2 - junction_width / 2, -junction_height / 2)
    )

    beta_junction_right = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    beta_junction_right_ref = c.add_ref(beta_junction_right)
    beta_junction_right_ref.move(
        (loop_width / 2 - wire_width / 2 - junction_width / 2, -junction_height / 2)
    )

    # Add control lines for flux bias
    control_line_left = gf.components.rectangle(
        size=(20.0, 2.0),
        layer=layer_metal,
    )
    control_line_left_ref = c.add_ref(control_line_left)
    control_line_left_ref.move((-loop_width / 2 - 30.0, -1.0))

    control_line_right = gf.components.rectangle(
        size=(20.0, 2.0),
        layer=layer_metal,
    )
    control_line_right_ref = c.add_ref(control_line_right)
    control_line_right_ref.move((loop_width / 2 + 10.0, -1.0))
    c.flatten()

    # Add ports for flux control
    c.add_port(
        name="flux_control_left",
        center=(-loop_width / 2 - 40.0, 0),
        width=2.0,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="flux_control_right",
        center=(loop_width / 2 + 40.0, 0),
        width=2.0,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add readout connection
    c.add_port(
        name="readout",
        center=(0, loop_height / 2 + 10.0),
        width=wire_width,
        orientation=90,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["qubit_type"] = "flux_qubit"
    c.info["loop_width"] = loop_width
    c.info["loop_height"] = loop_height
    c.info["beta_junction_area"] = junction_width * junction_height
    c.info["alpha_junction_area"] = alpha_junction_width * alpha_junction_height
    c.info["alpha_beta_ratio"] = (alpha_junction_width * alpha_junction_height) / (
        junction_width * junction_height
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

flux_qubit_asymmetric

flux_qubit_asymmetric(
    loop_width: float = 60.0,
    loop_height: float = 40.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    alpha_junction_width: float = 0.12,
    alpha_junction_height: float = 0.25,
    wire_width: float = 2.0,
    asymmetry_angle: float = 15.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_alpha_junction: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component

Creates an asymmetric flux qubit for reduced flux noise sensitivity.

An asymmetric flux qubit has a loop geometry that is not perfectly symmetric, which can help reduce sensitivity to flux noise while maintaining controllability.

Parameters:

Name Type Description Default
loop_width float

Width of the superconducting loop in μm.

60.0
loop_height float

Height of the superconducting loop in μm.

40.0
junction_width float

Width of the beta Josephson junctions in μm.

0.15
junction_height float

Height of the beta Josephson junctions in μm.

0.3
alpha_junction_width float

Width of the alpha Josephson junction in μm.

0.12
alpha_junction_height float

Height of the alpha Josephson junction in μm.

0.25
wire_width float

Width of the superconducting wires in μm.

2.0
asymmetry_angle float

Angle of asymmetry in degrees.

15.0
layer_metal LayerSpec

Layer for the metal wires.

(1, 0)
layer_junction LayerSpec

Layer for the beta Josephson junctions.

(2, 0)
layer_alpha_junction LayerSpec

Layer for the alpha Josephson junction.

(3, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the asymmetric flux qubit geometry.

Source code in gdsfactory/components/quantum/flux_qubit.py
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
@gf.cell_with_module_name(tags=["quantum"])
def flux_qubit_asymmetric(
    loop_width: float = 60.0,
    loop_height: float = 40.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    alpha_junction_width: float = 0.12,
    alpha_junction_height: float = 0.25,
    wire_width: float = 2.0,
    asymmetry_angle: float = 15.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_alpha_junction: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates an asymmetric flux qubit for reduced flux noise sensitivity.

    An asymmetric flux qubit has a loop geometry that is not perfectly symmetric,
    which can help reduce sensitivity to flux noise while maintaining controllability.

    Args:
        loop_width: Width of the superconducting loop in μm.
        loop_height: Height of the superconducting loop in μm.
        junction_width: Width of the beta Josephson junctions in μm.
        junction_height: Height of the beta Josephson junctions in μm.
        alpha_junction_width: Width of the alpha Josephson junction in μm.
        alpha_junction_height: Height of the alpha Josephson junction in μm.
        wire_width: Width of the superconducting wires in μm.
        asymmetry_angle: Angle of asymmetry in degrees.
        layer_metal: Layer for the metal wires.
        layer_junction: Layer for the beta Josephson junctions.
        layer_alpha_junction: Layer for the alpha Josephson junction.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the asymmetric flux qubit geometry.
    """
    c = Component()

    angle_rad = np.radians(asymmetry_angle)
    x_offset = loop_height * np.tan(angle_rad)

    # Create asymmetric loop as outer/inner polygon boolean
    # The right side is tilted by asymmetry_angle
    outer_points = [
        (-loop_width / 2, -loop_height / 2),
        (loop_width / 2, -loop_height / 2),
        (loop_width / 2 + x_offset, loop_height / 2),
        (-loop_width / 2, loop_height / 2),
    ]

    inner_points = [
        (-loop_width / 2 + wire_width, -loop_height / 2 + wire_width),
        (loop_width / 2 - wire_width, -loop_height / 2 + wire_width),
        (loop_width / 2 - wire_width + x_offset, loop_height / 2 - wire_width),
        (-loop_width / 2 + wire_width, loop_height / 2 - wire_width),
    ]

    outer_comp = Component()
    outer_comp.add_polygon(outer_points, layer=layer_metal)

    inner_comp = Component()
    inner_comp.add_polygon(inner_points, layer=layer_metal)

    loop = gf.boolean(outer_comp, inner_comp, operation="not", layer=layer_metal)
    c.add_ref(loop)

    # Create the alpha junction (smaller, at bottom)
    alpha_junction = gf.components.rectangle(
        size=(alpha_junction_width, alpha_junction_height),
        layer=layer_alpha_junction,
    )
    alpha_junction_ref = c.add_ref(alpha_junction)
    alpha_junction_ref.move(
        (
            -alpha_junction_width / 2,
            -loop_height / 2 + wire_width / 2 - alpha_junction_height / 2,
        )
    )

    # Create the beta junctions (larger, identical, at sides)
    beta_junction_left = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    beta_junction_left_ref = c.add_ref(beta_junction_left)
    beta_junction_left_ref.move(
        (-loop_width / 2 + wire_width / 2 - junction_width / 2, -junction_height / 2)
    )

    # Right side center x is shifted by half the x_offset
    right_center_x = loop_width / 2 - wire_width / 2 + x_offset / 2
    beta_junction_right = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    beta_junction_right_ref = c.add_ref(beta_junction_right)
    beta_junction_right_ref.move(
        (right_center_x - junction_width / 2, -junction_height / 2)
    )

    # Add control and readout ports
    c.add_port(
        name="flux_control",
        center=(0, loop_height / 2 + 10.0),
        width=wire_width,
        orientation=90,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="readout",
        center=(loop_width / 2 + x_offset + 10.0, 0),
        width=wire_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["qubit_type"] = "flux_qubit_asymmetric"
    c.info["loop_width"] = loop_width
    c.info["loop_height"] = loop_height
    c.info["asymmetry_angle"] = asymmetry_angle
    c.info["beta_junction_area"] = junction_width * junction_height
    c.info["alpha_junction_area"] = alpha_junction_width * alpha_junction_height
    c.info["alpha_beta_ratio"] = (alpha_junction_width * alpha_junction_height) / (
        junction_width * junction_height
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    c.flatten()
    return c

flux_qubit

flux_qubit_asymmetric

flux_qubit_asymmetric(
    loop_width: float = 60.0,
    loop_height: float = 40.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    alpha_junction_width: float = 0.12,
    alpha_junction_height: float = 0.25,
    wire_width: float = 2.0,
    asymmetry_angle: float = 15.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_alpha_junction: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component

Creates an asymmetric flux qubit for reduced flux noise sensitivity.

An asymmetric flux qubit has a loop geometry that is not perfectly symmetric, which can help reduce sensitivity to flux noise while maintaining controllability.

Parameters:

Name Type Description Default
loop_width float

Width of the superconducting loop in μm.

60.0
loop_height float

Height of the superconducting loop in μm.

40.0
junction_width float

Width of the beta Josephson junctions in μm.

0.15
junction_height float

Height of the beta Josephson junctions in μm.

0.3
alpha_junction_width float

Width of the alpha Josephson junction in μm.

0.12
alpha_junction_height float

Height of the alpha Josephson junction in μm.

0.25
wire_width float

Width of the superconducting wires in μm.

2.0
asymmetry_angle float

Angle of asymmetry in degrees.

15.0
layer_metal LayerSpec

Layer for the metal wires.

(1, 0)
layer_junction LayerSpec

Layer for the beta Josephson junctions.

(2, 0)
layer_alpha_junction LayerSpec

Layer for the alpha Josephson junction.

(3, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the asymmetric flux qubit geometry.

Source code in gdsfactory/components/quantum/flux_qubit.py
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
@gf.cell_with_module_name(tags=["quantum"])
def flux_qubit_asymmetric(
    loop_width: float = 60.0,
    loop_height: float = 40.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    alpha_junction_width: float = 0.12,
    alpha_junction_height: float = 0.25,
    wire_width: float = 2.0,
    asymmetry_angle: float = 15.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_alpha_junction: LayerSpec = (3, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates an asymmetric flux qubit for reduced flux noise sensitivity.

    An asymmetric flux qubit has a loop geometry that is not perfectly symmetric,
    which can help reduce sensitivity to flux noise while maintaining controllability.

    Args:
        loop_width: Width of the superconducting loop in μm.
        loop_height: Height of the superconducting loop in μm.
        junction_width: Width of the beta Josephson junctions in μm.
        junction_height: Height of the beta Josephson junctions in μm.
        alpha_junction_width: Width of the alpha Josephson junction in μm.
        alpha_junction_height: Height of the alpha Josephson junction in μm.
        wire_width: Width of the superconducting wires in μm.
        asymmetry_angle: Angle of asymmetry in degrees.
        layer_metal: Layer for the metal wires.
        layer_junction: Layer for the beta Josephson junctions.
        layer_alpha_junction: Layer for the alpha Josephson junction.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the asymmetric flux qubit geometry.
    """
    c = Component()

    angle_rad = np.radians(asymmetry_angle)
    x_offset = loop_height * np.tan(angle_rad)

    # Create asymmetric loop as outer/inner polygon boolean
    # The right side is tilted by asymmetry_angle
    outer_points = [
        (-loop_width / 2, -loop_height / 2),
        (loop_width / 2, -loop_height / 2),
        (loop_width / 2 + x_offset, loop_height / 2),
        (-loop_width / 2, loop_height / 2),
    ]

    inner_points = [
        (-loop_width / 2 + wire_width, -loop_height / 2 + wire_width),
        (loop_width / 2 - wire_width, -loop_height / 2 + wire_width),
        (loop_width / 2 - wire_width + x_offset, loop_height / 2 - wire_width),
        (-loop_width / 2 + wire_width, loop_height / 2 - wire_width),
    ]

    outer_comp = Component()
    outer_comp.add_polygon(outer_points, layer=layer_metal)

    inner_comp = Component()
    inner_comp.add_polygon(inner_points, layer=layer_metal)

    loop = gf.boolean(outer_comp, inner_comp, operation="not", layer=layer_metal)
    c.add_ref(loop)

    # Create the alpha junction (smaller, at bottom)
    alpha_junction = gf.components.rectangle(
        size=(alpha_junction_width, alpha_junction_height),
        layer=layer_alpha_junction,
    )
    alpha_junction_ref = c.add_ref(alpha_junction)
    alpha_junction_ref.move(
        (
            -alpha_junction_width / 2,
            -loop_height / 2 + wire_width / 2 - alpha_junction_height / 2,
        )
    )

    # Create the beta junctions (larger, identical, at sides)
    beta_junction_left = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    beta_junction_left_ref = c.add_ref(beta_junction_left)
    beta_junction_left_ref.move(
        (-loop_width / 2 + wire_width / 2 - junction_width / 2, -junction_height / 2)
    )

    # Right side center x is shifted by half the x_offset
    right_center_x = loop_width / 2 - wire_width / 2 + x_offset / 2
    beta_junction_right = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    beta_junction_right_ref = c.add_ref(beta_junction_right)
    beta_junction_right_ref.move(
        (right_center_x - junction_width / 2, -junction_height / 2)
    )

    # Add control and readout ports
    c.add_port(
        name="flux_control",
        center=(0, loop_height / 2 + 10.0),
        width=wire_width,
        orientation=90,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="readout",
        center=(loop_width / 2 + x_offset + 10.0, 0),
        width=wire_width,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["qubit_type"] = "flux_qubit_asymmetric"
    c.info["loop_width"] = loop_width
    c.info["loop_height"] = loop_height
    c.info["asymmetry_angle"] = asymmetry_angle
    c.info["beta_junction_area"] = junction_width * junction_height
    c.info["alpha_junction_area"] = alpha_junction_width * alpha_junction_height
    c.info["alpha_beta_ratio"] = (alpha_junction_width * alpha_junction_height) / (
        junction_width * junction_height
    )

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    c.flatten()
    return c

flux_qubit_asymmetric

resonator_cpw

resonator_cpw(
    length: float = 1000.0,
    width: float = 10.0,
    gap: float = 6.0,
    meander_pitch: float = 50.0,
    meander_width: float = 200.0,
    coupling_gap: float = 5.0,
    coupling_length: float = 100.0,
    layer_metal: LayerSpec = (1, 0),
    layer_gap: LayerSpec = (2, 0),
    port_type: str = "electrical",
) -> Component

Creates a coplanar waveguide (CPW) resonator.

A CPW resonator consists of a meandered coplanar waveguide with coupling gaps for capacitive coupling to feedlines or qubits.

Parameters:

Name Type Description Default
length float

Total length of the resonator in μm.

1000.0
width float

Width of the center conductor in μm.

10.0
gap float

Gap width on each side of the center conductor in μm.

6.0
meander_pitch float

Pitch between meander segments in μm.

50.0
meander_width float

Width of each meander section in μm.

200.0
coupling_gap float

Gap for capacitive coupling in μm.

5.0
coupling_length float

Length of the coupling region in μm.

100.0
layer_metal LayerSpec

Layer for the metal conductor.

(1, 0)
layer_gap LayerSpec

Layer for the gaps (ground plane).

(2, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the CPW resonator geometry.

Source code in gdsfactory/components/quantum/resonator.py
 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
 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
@gf.cell_with_module_name(tags=["quantum"])
def resonator_cpw(
    length: float = 1000.0,
    width: float = 10.0,
    gap: float = 6.0,
    meander_pitch: float = 50.0,
    meander_width: float = 200.0,
    coupling_gap: float = 5.0,
    coupling_length: float = 100.0,
    layer_metal: LayerSpec = (1, 0),
    layer_gap: LayerSpec = (2, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a coplanar waveguide (CPW) resonator.

    A CPW resonator consists of a meandered coplanar waveguide with coupling gaps
    for capacitive coupling to feedlines or qubits.

    Args:
        length: Total length of the resonator in μm.
        width: Width of the center conductor in μm.
        gap: Gap width on each side of the center conductor in μm.
        meander_pitch: Pitch between meander segments in μm.
        meander_width: Width of each meander section in μm.
        coupling_gap: Gap for capacitive coupling in μm.
        coupling_length: Length of the coupling region in μm.
        layer_metal: Layer for the metal conductor.
        layer_gap: Layer for the gaps (ground plane).
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the CPW resonator geometry.
    """
    c = Component()

    # Calculate number of meander sections needed
    num_meanders = int(length / meander_width) if meander_width > 0 else 1
    actual_length = num_meanders * meander_width

    # Create meander path
    path_points = []
    x, y = 0.0, 0.0

    for i in range(num_meanders):
        if i == 0:
            # First segment
            path_points.extend([(x, y), (x + meander_width, y)])
            x += meander_width
        else:
            # Alternate up and down
            if i % 2 == 1:
                y += meander_pitch
                path_points.extend([(x, y), (x - meander_width, y)])
                x -= meander_width
            else:
                y += meander_pitch
                path_points.extend([(x, y), (x + meander_width, y)])
                x += meander_width

    # Create the CPW path
    path = gf.Path(np.array(path_points))

    # Create cross section for CPW
    cpw_xs = gf.cross_section.strip(width=width, layer=layer_metal)

    # Create the resonator structure
    resonator_path = gf.path.extrude(path, cpw_xs)
    c.add_ref(resonator_path)

    # Create ground plane with gaps
    total_width = meander_width + 2 * meander_pitch
    total_height = (num_meanders - 1) * meander_pitch + width + 2 * gap

    # Ground plane
    ground_plane = gf.components.rectangle(
        size=(total_width + 2 * gap, total_height + 2 * gap),
        layer=layer_metal,
    )
    ground_ref = c.add_ref(ground_plane)
    ground_ref.move((-gap, -gap))

    # Create gaps by boolean subtraction
    gap_width = width + 2 * gap

    # Create gap path along the resonator
    gap_path = gf.Path(np.array(path_points))
    gap_xs = gf.cross_section.strip(width=gap_width, layer=layer_gap)
    gap_structure = gf.path.extrude(gap_path, gap_xs)

    # Subtract gaps from ground plane
    gf.boolean(
        ground_ref,
        gap_structure,
        operation="not",
        layer=layer_metal,
    )

    # Add coupling regions
    # Input coupling
    coupling_in = gf.components.rectangle(
        size=(coupling_length, coupling_gap),
        layer=layer_gap,
    )
    coupling_in_ref = c.add_ref(coupling_in)
    coupling_in_ref.move((-coupling_length / 2, -width / 2 - gap - coupling_gap / 2))

    # Output coupling
    coupling_out = gf.components.rectangle(
        size=(coupling_length, coupling_gap),
        layer=layer_gap,
    )
    coupling_out_ref = c.add_ref(coupling_out)
    coupling_out_ref.move(
        (x - coupling_length / 2, y + width / 2 + gap + coupling_gap / 2)
    )

    # Add ports for coupling
    c.add_port(
        name="input",
        center=(0, -width / 2 - gap - coupling_gap),
        width=coupling_length,
        orientation=270,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="output",
        center=(x, y + width / 2 + gap + coupling_gap),
        width=coupling_length,
        orientation=90,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["resonator_type"] = "cpw"
    c.info["length"] = actual_length
    c.info["width"] = width
    c.info["gap"] = gap
    c.info["frequency_estimate"] = (
        3e8 / (2 * actual_length * 1e-6) / 1e9
    )  # GHz, rough estimate

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    c.flatten()
    return c

resonator_cpw

resonator_lumped

resonator_lumped(
    capacitor_fingers: int = 4,
    capacitor_finger_length: float = 20.0,
    capacitor_finger_gap: float = 2.0,
    capacitor_thickness: float = 5.0,
    inductor_width: float = 2.0,
    inductor_turns: int = 3,
    inductor_radius: float = 20.0,
    coupling_gap: float = 5.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates a lumped element resonator with interdigital capacitor and spiral inductor.

A lumped resonator consists of a capacitive element (interdigital capacitor) and an inductive element (spiral inductor) forming an LC circuit.

Parameters:

Name Type Description Default
capacitor_fingers int

Number of fingers in the interdigital capacitor.

4
capacitor_finger_length float

Length of each capacitor finger in μm.

20.0
capacitor_finger_gap float

Gap between capacitor fingers in μm.

2.0
capacitor_thickness float

Thickness of capacitor fingers in μm.

5.0
inductor_width float

Width of the inductor wire in μm.

2.0
inductor_turns int

Number of turns in the spiral inductor.

3
inductor_radius float

Radius of the spiral inductor in μm.

20.0
coupling_gap float

Gap for capacitive coupling in μm.

5.0
layer_metal LayerSpec

Layer for the metal structures.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the lumped resonator geometry.

Source code in gdsfactory/components/quantum/resonator.py
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
@gf.cell_with_module_name(tags=["quantum"])
def resonator_lumped(
    capacitor_fingers: int = 4,
    capacitor_finger_length: float = 20.0,
    capacitor_finger_gap: float = 2.0,
    capacitor_thickness: float = 5.0,
    inductor_width: float = 2.0,
    inductor_turns: int = 3,
    inductor_radius: float = 20.0,
    coupling_gap: float = 5.0,
    layer_metal: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a lumped element resonator with interdigital capacitor and spiral inductor.

    A lumped resonator consists of a capacitive element (interdigital capacitor)
    and an inductive element (spiral inductor) forming an LC circuit.

    Args:
        capacitor_fingers: Number of fingers in the interdigital capacitor.
        capacitor_finger_length: Length of each capacitor finger in μm.
        capacitor_finger_gap: Gap between capacitor fingers in μm.
        capacitor_thickness: Thickness of capacitor fingers in μm.
        inductor_width: Width of the inductor wire in μm.
        inductor_turns: Number of turns in the spiral inductor.
        inductor_radius: Radius of the spiral inductor in μm.
        coupling_gap: Gap for capacitive coupling in μm.
        layer_metal: Layer for the metal structures.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the lumped resonator geometry.
    """
    c = Component()

    # Create interdigital capacitor
    capacitor = gf.get_component(
        "interdigital_capacitor",
        fingers=capacitor_fingers,
        finger_length=capacitor_finger_length,
        finger_gap=capacitor_finger_gap,
        thickness=capacitor_thickness,
        layer=layer_metal,
    )
    c.add_ref(capacitor)

    # Create spiral inductor
    # Create a cross section with the specified width
    inductor_cross_section = gf.cross_section.strip(
        width=inductor_width,
        layer=layer_metal,
    )
    inductor = gf.components.spiral(
        n_loops=inductor_turns,
        cross_section=inductor_cross_section,
    )
    ind_ref = c.add_ref(inductor)

    # Position inductor next to capacitor
    cap_width = 2 * capacitor_thickness + capacitor_finger_length + capacitor_finger_gap
    ind_ref.move((cap_width + 20.0, 0))

    # Connect capacitor and inductor
    connection = gf.components.rectangle(
        size=(20.0, inductor_width),
        layer=layer_metal,
    )
    conn_ref = c.add_ref(connection)
    conn_ref.move((cap_width, -inductor_width / 2))

    # Connect to inductor input
    connection2 = gf.components.rectangle(
        size=(inductor_width, 20.0),
        layer=layer_metal,
    )
    conn2_ref = c.add_ref(connection2)
    conn2_ref.move((cap_width + 20.0 - inductor_width / 2, -20.0))

    # Add coupling elements for input/output
    coupling_cap_in = gf.components.rectangle(
        size=(capacitor_thickness, coupling_gap),
        layer=layer_metal,
    )
    coupling_in_ref = c.add_ref(coupling_cap_in)
    coupling_in_ref.move(
        (
            -coupling_gap - capacitor_thickness,
            capacitor_fingers * capacitor_thickness / 2,
        )
    )

    coupling_cap_out = gf.components.rectangle(
        size=(capacitor_thickness, coupling_gap),
        layer=layer_metal,
    )
    coupling_out_ref = c.add_ref(coupling_cap_out)
    coupling_out_ref.move(
        (cap_width + coupling_gap, capacitor_fingers * capacitor_thickness / 2)
    )

    # Add ports
    c.add_port(
        name="input",
        center=(
            -coupling_gap - capacitor_thickness / 2,
            capacitor_fingers * capacitor_thickness / 2,
        ),
        width=coupling_gap,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    c.add_port(
        name="output",
        center=(
            cap_width + coupling_gap + capacitor_thickness / 2,
            capacitor_fingers * capacitor_thickness / 2,
        ),
        width=coupling_gap,
        orientation=0,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["resonator_type"] = "lumped"
    c.info["capacitor_fingers"] = capacitor_fingers
    c.info["inductor_turns"] = inductor_turns
    c.info["inductor_radius"] = inductor_radius

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

resonator_lumped

resonator_quarter_wave

resonator_quarter_wave(
    length: float = 2500.0,
    width: float = 10.0,
    gap: float = 6.0,
    short_stub_length: float = 50.0,
    coupling_gap: float = 5.0,
    coupling_length: float = 100.0,
    layer_metal: LayerSpec = (1, 0),
    layer_gap: LayerSpec = (2, 0),
    port_type: str = "electrical",
) -> Component

Creates a quarter-wave coplanar waveguide resonator.

A quarter-wave resonator is shorted at one end and has maximum electric field at the open end, making it suitable for capacitive coupling.

Parameters:

Name Type Description Default
length float

Length of the quarter-wave resonator in μm.

2500.0
width float

Width of the center conductor in μm.

10.0
gap float

Gap width on each side of the center conductor in μm.

6.0
short_stub_length float

Length of the shorting stub in μm.

50.0
coupling_gap float

Gap for capacitive coupling in μm.

5.0
coupling_length float

Length of the coupling region in μm.

100.0
layer_metal LayerSpec

Layer for the metal conductor.

(1, 0)
layer_gap LayerSpec

Layer for the gaps.

(2, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the quarter-wave resonator geometry.

Source code in gdsfactory/components/quantum/resonator.py
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
@gf.cell_with_module_name(tags=["quantum"])
def resonator_quarter_wave(
    length: float = 2500.0,
    width: float = 10.0,
    gap: float = 6.0,
    short_stub_length: float = 50.0,
    coupling_gap: float = 5.0,
    coupling_length: float = 100.0,
    layer_metal: LayerSpec = (1, 0),
    layer_gap: LayerSpec = (2, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a quarter-wave coplanar waveguide resonator.

    A quarter-wave resonator is shorted at one end and has maximum electric field
    at the open end, making it suitable for capacitive coupling.

    Args:
        length: Length of the quarter-wave resonator in μm.
        width: Width of the center conductor in μm.
        gap: Gap width on each side of the center conductor in μm.
        short_stub_length: Length of the shorting stub in μm.
        coupling_gap: Gap for capacitive coupling in μm.
        coupling_length: Length of the coupling region in μm.
        layer_metal: Layer for the metal conductor.
        layer_gap: Layer for the gaps.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the quarter-wave resonator geometry.
    """
    c = Component()

    # Create main resonator line
    main_line = gf.components.rectangle(
        size=(length, width),
        layer=layer_metal,
    )
    c.add_ref(main_line)

    # Create shorting stub at one end
    short_stub = gf.components.rectangle(
        size=(short_stub_length, width + 2 * gap),
        layer=layer_metal,
    )
    short_ref = c.add_ref(short_stub)
    short_ref.move((length, -gap))

    # Create ground planes
    ground_top = gf.components.rectangle(
        size=(length + short_stub_length + 2 * gap, gap),
        layer=layer_metal,
    )
    ground_top_ref = c.add_ref(ground_top)
    ground_top_ref.move((-gap, width))

    ground_bottom = gf.components.rectangle(
        size=(length + short_stub_length + 2 * gap, gap),
        layer=layer_metal,
    )
    ground_bottom_ref = c.add_ref(ground_bottom)
    ground_bottom_ref.move((-gap, -gap))

    # Create coupling region at open end
    coupling_region = gf.components.rectangle(
        size=(coupling_length, coupling_gap),
        layer=layer_gap,
    )
    coupling_ref = c.add_ref(coupling_region)
    coupling_ref.move((-coupling_length, width / 2 - coupling_gap / 2))

    # Add port for coupling
    c.add_port(
        name="coupling",
        center=(-coupling_length / 2, width / 2),
        width=coupling_gap,
        orientation=180,
        layer=layer_metal,
        port_type=port_type,
    )

    # Add metadata
    c.info["resonator_type"] = "quarter_wave"
    c.info["length"] = length
    c.info["width"] = width
    c.info["gap"] = gap
    c.info["frequency_estimate"] = (
        3e8 / (4 * length * 1e-6) / 1e9
    )  # GHz, rough estimate

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

resonator_quarter_wave

transmon

transmon

transmon(
    pad_width: float = 200.0,
    pad_height: float = 100.0,
    pad_gap: float = 6.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    island_width: float = 10.0,
    island_height: float = 4.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_island: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates a transmon qubit with Josephson junction.

A transmon qubit consists of two capacitor pads connected by a Josephson junction. The junction creates an anharmonic oscillator that can be used as a qubit.

Parameters:

Name Type Description Default
pad_width float

Width of each capacitor pad in μm.

200.0
pad_height float

Height of each capacitor pad in μm.

100.0
pad_gap float

Gap between the two pads in μm.

6.0
junction_width float

Width of the Josephson junction in μm.

0.15
junction_height float

Height of the Josephson junction in μm.

0.3
island_width float

Width of the central island in μm.

10.0
island_height float

Height of the central island in μm.

4.0
layer_metal LayerSpec

Layer for the metal pads.

(1, 0)
layer_junction LayerSpec

Layer for the Josephson junction.

(2, 0)
layer_island LayerSpec

Layer for the central island.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the transmon geometry.

Source code in gdsfactory/components/quantum/transmon.py
 10
 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
 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
@gf.cell_with_module_name(tags=["quantum"])
def transmon(
    pad_width: float = 200.0,
    pad_height: float = 100.0,
    pad_gap: float = 6.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    island_width: float = 10.0,
    island_height: float = 4.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_island: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a transmon qubit with Josephson junction.

    A transmon qubit consists of two capacitor pads connected by a Josephson junction.
    The junction creates an anharmonic oscillator that can be used as a qubit.

    Args:
        pad_width: Width of each capacitor pad in μm.
        pad_height: Height of each capacitor pad in μm.
        pad_gap: Gap between the two pads in μm.
        junction_width: Width of the Josephson junction in μm.
        junction_height: Height of the Josephson junction in μm.
        island_width: Width of the central island in μm.
        island_height: Height of the central island in μm.
        layer_metal: Layer for the metal pads.
        layer_junction: Layer for the Josephson junction.
        layer_island: Layer for the central island.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the transmon geometry.
    """
    c = Component()

    # Create left capacitor pad
    left_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
        port_type=port_type,
    )
    left_pad_ref = c.add_ref(left_pad)
    left_pad_ref.move((-pad_width - pad_gap / 2, -pad_height / 2))

    # Create right capacitor pad
    right_pad = gf.components.rectangle(
        size=(pad_width, pad_height),
        layer=layer_metal,
        port_type=port_type,
    )
    right_pad_ref = c.add_ref(right_pad)
    right_pad_ref.move((pad_gap / 2, -pad_height / 2))

    # Create central island
    island = gf.components.rectangle(
        size=(island_width, island_height),
        layer=layer_island,
    )
    island_ref = c.add_ref(island)
    island_ref.move((-island_width / 2, -island_height / 2))

    # Create Josephson junction
    junction = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    junction_ref = c.add_ref(junction)
    junction_ref.move((-junction_width / 2, -junction_height / 2))

    # Add connection lines from pads to island
    # Only add connections if there is a gap between island and pads
    connection_width = abs(pad_gap / 2 - island_width / 2)
    if pad_gap / 2 > island_width / 2:
        left_connection = gf.components.rectangle(
            size=(connection_width, junction_height / 2),
            layer=layer_metal,
        )
        left_conn_ref = c.add_ref(left_connection)
        left_conn_ref.move((-pad_gap / 2, -junction_height / 4))

        right_connection = gf.components.rectangle(
            size=(connection_width, junction_height / 2),
            layer=layer_metal,
        )
        right_conn_ref = c.add_ref(right_connection)
        right_conn_ref.move((island_width / 2, -junction_height / 4))

    # Add ports for connections
    c.add_port(
        name="left_pad",
        center=(-pad_width - pad_gap / 2, 0),
        width=pad_height,
        orientation=180,
        layer=layer_metal,
    )

    c.add_port(
        name="right_pad",
        center=(pad_width + pad_gap / 2, 0),
        width=pad_height,
        orientation=0,
        layer=layer_metal,
    )

    # Add metadata
    c.info["qubit_type"] = "transmon"
    c.info["pad_width"] = pad_width
    c.info["pad_height"] = pad_height
    c.info["pad_gap"] = pad_gap
    c.info["junction_area"] = junction_width * junction_height

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

transmon_circular

transmon_circular(
    pad_radius: float = 100.0,
    pad_gap: float = 6.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    island_radius: float = 5.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_island: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates a circular transmon qubit with Josephson junction.

A circular variant of the transmon qubit with circular capacitor pads.

Parameters:

Name Type Description Default
pad_radius float

Radius of each circular capacitor pad in μm.

100.0
pad_gap float

Gap between the two pads in μm.

6.0
junction_width float

Width of the Josephson junction in μm.

0.15
junction_height float

Height of the Josephson junction in μm.

0.3
island_radius float

Radius of the central circular island in μm.

5.0
layer_metal LayerSpec

Layer for the metal pads.

(1, 0)
layer_junction LayerSpec

Layer for the Josephson junction.

(2, 0)
layer_island LayerSpec

Layer for the central island.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the circular transmon geometry.

Source code in gdsfactory/components/quantum/transmon.py
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
@gf.cell_with_module_name(tags=["quantum"])
def transmon_circular(
    pad_radius: float = 100.0,
    pad_gap: float = 6.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    island_radius: float = 5.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_island: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a circular transmon qubit with Josephson junction.

    A circular variant of the transmon qubit with circular capacitor pads.

    Args:
        pad_radius: Radius of each circular capacitor pad in μm.
        pad_gap: Gap between the two pads in μm.
        junction_width: Width of the Josephson junction in μm.
        junction_height: Height of the Josephson junction in μm.
        island_radius: Radius of the central circular island in μm.
        layer_metal: Layer for the metal pads.
        layer_junction: Layer for the Josephson junction.
        layer_island: Layer for the central island.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the circular transmon geometry.
    """
    c = Component()

    # Create left circular pad
    left_pad = gf.components.circle(
        radius=pad_radius,
        layer=layer_metal,
    )
    left_pad_ref = c.add_ref(left_pad)
    left_pad_ref.move((-pad_radius - pad_gap / 2, 0))

    # Create right circular pad
    right_pad = gf.components.circle(
        radius=pad_radius,
        layer=layer_metal,
    )
    right_pad_ref = c.add_ref(right_pad)
    right_pad_ref.move((pad_radius + pad_gap / 2, 0))

    # Create central circular island
    island = gf.components.circle(
        radius=island_radius,
        layer=layer_island,
    )
    c.add_ref(island)

    # Create Josephson junction
    junction = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    junction_ref = c.add_ref(junction)
    junction_ref.move((-junction_width / 2, -junction_height / 2))

    # Add connection lines from pads to island
    # Only add connections if there is a gap between island and pads
    connection_width = abs(pad_gap / 2 - island_radius)
    if pad_gap / 2 > island_radius:
        left_connection = gf.components.rectangle(
            size=(connection_width, junction_height / 2),
            layer=layer_metal,
        )
        left_conn_ref = c.add_ref(left_connection)
        left_conn_ref.move((-pad_gap / 2, -junction_height / 4))

        right_connection = gf.components.rectangle(
            size=(connection_width, junction_height / 2),
            layer=layer_metal,
        )
        right_conn_ref = c.add_ref(right_connection)
        right_conn_ref.move((island_radius, -junction_height / 4))

    # Add ports for connections
    c.add_port(
        name="left_pad",
        center=(-2 * pad_radius - pad_gap / 2, 0),
        width=2 * pad_radius,
        orientation=180,
        layer=layer_metal,
    )

    c.add_port(
        name="right_pad",
        center=(2 * pad_radius + pad_gap / 2, 0),
        width=2 * pad_radius,
        orientation=0,
        layer=layer_metal,
    )

    # Add metadata
    c.info["qubit_type"] = "transmon_circular"
    c.info["pad_radius"] = pad_radius
    c.info["pad_gap"] = pad_gap
    c.info["junction_area"] = junction_width * junction_height

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

transmon

transmon_circular

transmon_circular(
    pad_radius: float = 100.0,
    pad_gap: float = 6.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    island_radius: float = 5.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_island: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates a circular transmon qubit with Josephson junction.

A circular variant of the transmon qubit with circular capacitor pads.

Parameters:

Name Type Description Default
pad_radius float

Radius of each circular capacitor pad in μm.

100.0
pad_gap float

Gap between the two pads in μm.

6.0
junction_width float

Width of the Josephson junction in μm.

0.15
junction_height float

Height of the Josephson junction in μm.

0.3
island_radius float

Radius of the central circular island in μm.

5.0
layer_metal LayerSpec

Layer for the metal pads.

(1, 0)
layer_junction LayerSpec

Layer for the Josephson junction.

(2, 0)
layer_island LayerSpec

Layer for the central island.

(1, 0)
port_type str

Type of port to add to the component.

'electrical'

Returns:

Name Type Description
Component Component

A gdsfactory component with the circular transmon geometry.

Source code in gdsfactory/components/quantum/transmon.py
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
@gf.cell_with_module_name(tags=["quantum"])
def transmon_circular(
    pad_radius: float = 100.0,
    pad_gap: float = 6.0,
    junction_width: float = 0.15,
    junction_height: float = 0.3,
    island_radius: float = 5.0,
    layer_metal: LayerSpec = (1, 0),
    layer_junction: LayerSpec = (2, 0),
    layer_island: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates a circular transmon qubit with Josephson junction.

    A circular variant of the transmon qubit with circular capacitor pads.

    Args:
        pad_radius: Radius of each circular capacitor pad in μm.
        pad_gap: Gap between the two pads in μm.
        junction_width: Width of the Josephson junction in μm.
        junction_height: Height of the Josephson junction in μm.
        island_radius: Radius of the central circular island in μm.
        layer_metal: Layer for the metal pads.
        layer_junction: Layer for the Josephson junction.
        layer_island: Layer for the central island.
        port_type: Type of port to add to the component.

    Returns:
        Component: A gdsfactory component with the circular transmon geometry.
    """
    c = Component()

    # Create left circular pad
    left_pad = gf.components.circle(
        radius=pad_radius,
        layer=layer_metal,
    )
    left_pad_ref = c.add_ref(left_pad)
    left_pad_ref.move((-pad_radius - pad_gap / 2, 0))

    # Create right circular pad
    right_pad = gf.components.circle(
        radius=pad_radius,
        layer=layer_metal,
    )
    right_pad_ref = c.add_ref(right_pad)
    right_pad_ref.move((pad_radius + pad_gap / 2, 0))

    # Create central circular island
    island = gf.components.circle(
        radius=island_radius,
        layer=layer_island,
    )
    c.add_ref(island)

    # Create Josephson junction
    junction = gf.components.rectangle(
        size=(junction_width, junction_height),
        layer=layer_junction,
    )
    junction_ref = c.add_ref(junction)
    junction_ref.move((-junction_width / 2, -junction_height / 2))

    # Add connection lines from pads to island
    # Only add connections if there is a gap between island and pads
    connection_width = abs(pad_gap / 2 - island_radius)
    if pad_gap / 2 > island_radius:
        left_connection = gf.components.rectangle(
            size=(connection_width, junction_height / 2),
            layer=layer_metal,
        )
        left_conn_ref = c.add_ref(left_connection)
        left_conn_ref.move((-pad_gap / 2, -junction_height / 4))

        right_connection = gf.components.rectangle(
            size=(connection_width, junction_height / 2),
            layer=layer_metal,
        )
        right_conn_ref = c.add_ref(right_connection)
        right_conn_ref.move((island_radius, -junction_height / 4))

    # Add ports for connections
    c.add_port(
        name="left_pad",
        center=(-2 * pad_radius - pad_gap / 2, 0),
        width=2 * pad_radius,
        orientation=180,
        layer=layer_metal,
    )

    c.add_port(
        name="right_pad",
        center=(2 * pad_radius + pad_gap / 2, 0),
        width=2 * pad_radius,
        orientation=0,
        layer=layer_metal,
    )

    # Add metadata
    c.info["qubit_type"] = "transmon_circular"
    c.info["pad_radius"] = pad_radius
    c.info["pad_gap"] = pad_gap
    c.info["junction_area"] = junction_width * junction_height

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    return c

transmon_circular

rings

coupler_bend

coupler_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 120.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
) -> Component

Compact curved coupler with bezier escape.

TODO: fix for euler bends.

Parameters:

Name Type Description Default
radius float | None

um.

None
coupler_gap float

um.

0.2
coupling_angle_coverage float

degrees.

120.0
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

'strip'
bend AnyComponentFactory

for bend.

bend_circular_all_angle
bend_output ComponentSpec

for bend.

r 4 | | | / ___3 | / /

'bend_euler'
Source code in gdsfactory/components/rings/ring_single_bend_coupler.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
90
91
92
93
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["rings"])
def coupler_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 120.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
) -> Component:
    r"""Compact curved coupler with bezier escape.

    TODO: fix for euler bends.

    Args:
        radius: um.
        coupler_gap: um.
        coupling_angle_coverage: degrees.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.
        bend: for bend.
        bend_output: for bend.

            r   4
            |   |
            |  / ___3
            | / /
        2____/ /
        1_____/
    """
    c = Component()

    xi = gf.get_cross_section(cross_section_inner)
    xo = gf.get_cross_section(cross_section_outer)

    angle_inner = 90
    angle_outer = coupling_angle_coverage / 2
    gap = coupler_gap

    width = xo.width / 2 + xi.width / 2
    spacing = gap + width

    if radius is None:
        radius = xi.radius or xo.radius
        assert radius is not None, "cross_section must have a radius"

    bend90_inner_right = gf.get_component(
        bend,  # type: ignore[arg-type]
        radius=radius,
        cross_section=cross_section_inner,
        angle=angle_inner,
    )
    bend_output_right = gf.get_component(
        bend,  # type: ignore[arg-type]
        radius=radius + spacing,
        cross_section=cross_section_outer,
        angle=angle_outer,
    )
    bend_inner_ref = c.add_ref_off_grid(bend90_inner_right)
    bend_output_ref = c.add_ref_off_grid(bend_output_right)

    output = gf.get_component(
        bend_output, angle=angle_outer, cross_section=cross_section_outer
    )
    output_ref = c.add_ref_off_grid(output)
    output_ref.connect("o1", bend_output_ref.ports["o2"], mirror=True)

    pbw = bend_inner_ref.ports["o1"]
    bend_inner_ref.movey(pbw.center[1] + spacing)

    c.add_port("o1", port=bend_output_ref.ports["o1"])
    c.add_port("o2", port=bend_inner_ref.ports["o1"])
    c.add_port("o3", port=output_ref.ports["o2"])
    c.add_port("o4", port=bend_inner_ref.ports["o2"])
    return c

coupler_bend

coupler_ring_bend

coupler_ring_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 90.0,
    length_x: float = 0.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
) -> Component

Two back-to-back coupler_bend.

Parameters:

Name Type Description Default
radius float | None

um. Default is None, which uses the default radius of the cross_section.

None
coupler_gap float

um.

0.2
angle_inner

of the inner bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.

required
angle_outer

of the outer bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.

required
coupling_angle_coverage float

degrees.

90.0
length_x float

horizontal straight length.

0.0
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

'strip'
bend AnyComponentFactory

for bend.

bend_circular_all_angle
bend_output ComponentSpec

for bend.

'bend_euler'
straight ComponentSpec

for straight.

'straight'
Source code in gdsfactory/components/rings/ring_single_bend_coupler.py
 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
@gf.cell_with_module_name(schematic_function=coupler_ring_schematic, tags=["rings"])
def coupler_ring_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 90.0,
    length_x: float = 0.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
) -> Component:
    r"""Two back-to-back coupler_bend.

    Args:
        radius: um. Default is None, which uses the default radius of the cross_section.
        coupler_gap: um.
        angle_inner: of the inner bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.
        angle_outer: of the outer bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.
        coupling_angle_coverage: degrees.
        length_x: horizontal straight length.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.
        bend: for bend.
        bend_output: for bend.
        straight: for straight.
    """
    c = Component()
    cp = coupler_bend(
        radius=radius,
        coupler_gap=coupler_gap,
        coupling_angle_coverage=coupling_angle_coverage,
        cross_section_inner=cross_section_inner,
        cross_section_outer=cross_section_outer,
        bend=bend,
        bend_output=bend_output,
    )
    sin = gf.get_component(straight, length=length_x, cross_section=cross_section_inner)
    sout = gf.get_component(
        straight, length=length_x, cross_section=cross_section_outer
    )

    coupler_right = c << cp
    coupler_left = c << cp
    straight_inner = c << sin
    straight_inner.movex(-length_x / 2)
    straight_outer = c << sout
    straight_outer.movex(-length_x / 2)

    coupler_left.connect("o1", straight_outer.ports["o1"])
    straight_inner.connect("o1", coupler_left.ports["o2"])
    coupler_right.connect("o2", straight_inner.ports["o2"], mirror=True)
    straight_outer.connect("o2", coupler_right.ports["o1"])

    c.add_port("o1", port=coupler_left.ports["o3"])
    c.add_port("o2", port=coupler_left.ports["o4"])
    c.add_port("o4", port=coupler_right.ports["o3"])
    c.add_port("o3", port=coupler_right.ports["o4"])
    # c.flatten()
    return c

coupler_ring_bend

disk

disk

disk(
    radius: float = 10.0,
    gap: float = 0.2,
    wrap_angle_deg: float = 180.0,
    parity: int = 1,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Disk Resonator.

Parameters:

Name Type Description Default
radius float

disk resonator radius.

10.0
gap float

Distance between the bus straight and resonator.

0.2
wrap_angle_deg float

Angle in degrees between 0 and 180. determines how much the bus straight wraps along the resonator. 0 corresponds to a straight bus straight. 180 corresponds to a bus straight wrapped around half of the resonator.

180.0
parity 1 or -1

1, resonator left from bus straight, -1 resonator to the right.

1
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/rings/disk.py
 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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def disk(
    radius: float = 10.0,
    gap: float = 0.2,
    wrap_angle_deg: float = 180.0,
    parity: int = 1,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Disk Resonator.

    Args:
       radius: disk resonator radius.
       gap: Distance between the bus straight and resonator.
       wrap_angle_deg: Angle in degrees between 0 and 180.
        determines how much the bus straight wraps along the resonator.
        0 corresponds to a straight bus straight.
        180 corresponds to a bus straight wrapped around half of the resonator.
       parity (1 or -1): 1, resonator left from bus straight, -1 resonator to the right.
       cross_section: cross_section spec.

    """
    if parity not in (1, -1):
        raise ValueError("parity must be 1 or -1")

    if wrap_angle_deg < 0.0 or wrap_angle_deg > 180.0:
        raise ValueError("wrap_angle_deg must be between 0.0 and 180.0")

    c = gf.Component()

    xs = gf.get_cross_section(cross_section=cross_section)
    radius_disk = radius
    radius = radius + xs.width / 2.0 + gap
    xs_bend = xs.copy(radius=radius)

    r_bend, size_x, dy, bus_length = _compute_parameters(
        xs_bend, wrap_angle_deg, radius
    )

    c, bend_input, bend_middle, bend_output = _generate_bends(
        c, r_bend, wrap_angle_deg, xs_bend
    )

    c, straight_left, straight_right = _generate_straights(
        c, bus_length, size_x, bend_input, bend_output, xs_bend
    )
    assert xs.layer is not None
    circle = c << gf.components.circle(radius=radius_disk, layer=xs.layer)

    circle_cladding = None
    if bend_middle is not None:
        dx = (bend_middle.ports["o1"].x + bend_middle.ports["o2"].x) / 2.0
        dy = straight_left.ports["o2"].y - 2 * dy + r_bend
        circle.move((dx, dy))
    else:
        center = straight_left.ports["o2"].center
        circle.move((center[0], center[1] + r_bend))

    if circle_cladding:
        circle_cladding.move(circle.center)

    c.add_port("o1", port=straight_left.ports["o1"])
    c.add_port("o2", port=straight_right.ports["o2"])
    xs.add_bbox(c)
    if parity == -1:
        c = c.rotate(180)

    c.flatten()
    return c

disk_heater

disk_heater(
    radius: float = 10.0,
    gap: float = 0.2,
    wrap_angle_deg: float = 180.0,
    parity: int = 1,
    cross_section: CrossSectionSpec = "strip",
    heater_layer: LayerSpec = "HEATER",
    via_stack: ComponentSpec = "via_stack_heater_mtop",
    heater_width: float = 5.0,
    heater_extent: float = 2.0,
    via_width: float = 10.0,
    port_orientation: AngleInDegrees | None = 90,
) -> Component

Disk Resonator with top metal heater.

Parameters:

Name Type Description Default
radius float

disk resonator radius.

10.0
gap float

Distance between the bus straight and resonator.

0.2
wrap_angle_deg float

Angle in degrees between 0 and 180. determines how much the bus straight wraps along the resonator. 0 corresponds to a straight bus straight. 180 corresponds to a bus straight wrapped around half of the resonator.

180.0
parity 1 or -1

1, resonator left from bus straight, -1 resonator to the right.

1
cross_section CrossSectionSpec

cross_section spec.

'strip'
heater_layer LayerSpec

layer of the heater.

'HEATER'
via_stack ComponentSpec

via stack component.

'via_stack_heater_mtop'
heater_width float

width of the heater.

5.0
heater_extent float

length of heater beyond disk.

2.0
via_width float

size of the square via at the end of the heater.

10.0
port_orientation AngleInDegrees | None

in degrees.

90
Source code in gdsfactory/components/rings/disk.py
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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def disk_heater(
    radius: float = 10.0,
    gap: float = 0.2,
    wrap_angle_deg: float = 180.0,
    parity: int = 1,
    cross_section: CrossSectionSpec = "strip",
    heater_layer: LayerSpec = "HEATER",
    via_stack: ComponentSpec = "via_stack_heater_mtop",
    heater_width: float = 5.0,
    heater_extent: float = 2.0,
    via_width: float = 10.0,
    port_orientation: AngleInDegrees | None = 90,
) -> Component:
    """Disk Resonator with top metal heater.

    Args:
       radius: disk resonator radius.
       gap: Distance between the bus straight and resonator.
       wrap_angle_deg: Angle in degrees between 0 and 180.
        determines how much the bus straight wraps along the resonator.
        0 corresponds to a straight bus straight.
        180 corresponds to a bus straight wrapped around half of the resonator.
       parity (1 or -1): 1, resonator left from bus straight, -1 resonator to the right.
       cross_section: cross_section spec.
       heater_layer: layer of the heater.
       via_stack: via stack component.
       heater_width: width of the heater.
       heater_extent: length of heater beyond disk.
       via_width: size of the square via at the end of the heater.
       port_orientation: in degrees.
    """
    c = gf.Component()
    xs = gf.get_cross_section(cross_section=cross_section)

    disk_instance = c << disk(
        radius=radius,
        gap=gap,
        wrap_angle_deg=wrap_angle_deg,
        parity=parity,
        cross_section=cross_section,
    )

    dx = disk_instance.xmax - disk_instance.xmin
    dy = disk_instance.ymax - disk_instance.ymin
    heater = c << gf.get_component(
        gf.components.rectangle,
        size=(dx + 2 * heater_extent, heater_width),
        layer=heater_layer,
    )
    heater.x = disk_instance.x
    heater.y = dy / 2 + disk_instance.ymin + (xs.width + gap) / 2

    via = gf.get_component(via_stack, size=(via_width, via_width))
    c1 = c << via
    c2 = c << via
    c1.xmax = heater.xmin
    c1.y = heater.y
    c2.xmin = heater.xmax
    c2.y = heater.y
    c.add_ports(disk_instance.ports)
    c.add_ports(c1.ports.filter(orientation=port_orientation), prefix="e1")
    c.add_ports(c2.ports.filter(orientation=port_orientation), prefix="e2")

    e1_ports = [p for p in c.ports if p.name and p.name.startswith("e1")]
    e2_ports = [p for p in c.ports if p.name and p.name.startswith("e2")]
    if e1_ports:
        c.create_pin(ports=e1_ports, name="e1")
    if e2_ports:
        c.create_pin(ports=e2_ports, name="e2")

    c.auto_rename_ports()
    return c

disk

disk_heater

disk_heater(
    radius: float = 10.0,
    gap: float = 0.2,
    wrap_angle_deg: float = 180.0,
    parity: int = 1,
    cross_section: CrossSectionSpec = "strip",
    heater_layer: LayerSpec = "HEATER",
    via_stack: ComponentSpec = "via_stack_heater_mtop",
    heater_width: float = 5.0,
    heater_extent: float = 2.0,
    via_width: float = 10.0,
    port_orientation: AngleInDegrees | None = 90,
) -> Component

Disk Resonator with top metal heater.

Parameters:

Name Type Description Default
radius float

disk resonator radius.

10.0
gap float

Distance between the bus straight and resonator.

0.2
wrap_angle_deg float

Angle in degrees between 0 and 180. determines how much the bus straight wraps along the resonator. 0 corresponds to a straight bus straight. 180 corresponds to a bus straight wrapped around half of the resonator.

180.0
parity 1 or -1

1, resonator left from bus straight, -1 resonator to the right.

1
cross_section CrossSectionSpec

cross_section spec.

'strip'
heater_layer LayerSpec

layer of the heater.

'HEATER'
via_stack ComponentSpec

via stack component.

'via_stack_heater_mtop'
heater_width float

width of the heater.

5.0
heater_extent float

length of heater beyond disk.

2.0
via_width float

size of the square via at the end of the heater.

10.0
port_orientation AngleInDegrees | None

in degrees.

90
Source code in gdsfactory/components/rings/disk.py
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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def disk_heater(
    radius: float = 10.0,
    gap: float = 0.2,
    wrap_angle_deg: float = 180.0,
    parity: int = 1,
    cross_section: CrossSectionSpec = "strip",
    heater_layer: LayerSpec = "HEATER",
    via_stack: ComponentSpec = "via_stack_heater_mtop",
    heater_width: float = 5.0,
    heater_extent: float = 2.0,
    via_width: float = 10.0,
    port_orientation: AngleInDegrees | None = 90,
) -> Component:
    """Disk Resonator with top metal heater.

    Args:
       radius: disk resonator radius.
       gap: Distance between the bus straight and resonator.
       wrap_angle_deg: Angle in degrees between 0 and 180.
        determines how much the bus straight wraps along the resonator.
        0 corresponds to a straight bus straight.
        180 corresponds to a bus straight wrapped around half of the resonator.
       parity (1 or -1): 1, resonator left from bus straight, -1 resonator to the right.
       cross_section: cross_section spec.
       heater_layer: layer of the heater.
       via_stack: via stack component.
       heater_width: width of the heater.
       heater_extent: length of heater beyond disk.
       via_width: size of the square via at the end of the heater.
       port_orientation: in degrees.
    """
    c = gf.Component()
    xs = gf.get_cross_section(cross_section=cross_section)

    disk_instance = c << disk(
        radius=radius,
        gap=gap,
        wrap_angle_deg=wrap_angle_deg,
        parity=parity,
        cross_section=cross_section,
    )

    dx = disk_instance.xmax - disk_instance.xmin
    dy = disk_instance.ymax - disk_instance.ymin
    heater = c << gf.get_component(
        gf.components.rectangle,
        size=(dx + 2 * heater_extent, heater_width),
        layer=heater_layer,
    )
    heater.x = disk_instance.x
    heater.y = dy / 2 + disk_instance.ymin + (xs.width + gap) / 2

    via = gf.get_component(via_stack, size=(via_width, via_width))
    c1 = c << via
    c2 = c << via
    c1.xmax = heater.xmin
    c1.y = heater.y
    c2.xmin = heater.xmax
    c2.y = heater.y
    c.add_ports(disk_instance.ports)
    c.add_ports(c1.ports.filter(orientation=port_orientation), prefix="e1")
    c.add_ports(c2.ports.filter(orientation=port_orientation), prefix="e2")

    e1_ports = [p for p in c.ports if p.name and p.name.startswith("e1")]
    e2_ports = [p for p in c.ports if p.name and p.name.startswith("e2")]
    if e1_ports:
        c.create_pin(ports=e1_ports, name="e1")
    if e2_ports:
        c.create_pin(ports=e2_ports, name="e2")

    c.auto_rename_ports()
    return c

disk_heater

ring

ring

ring(
    radius: float = 10.0,
    width: float = 0.5,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
    angle: float = 360,
    distance_resolution: float | None = None,
) -> Component

Returns a ring.

Parameters:

Name Type Description Default
radius float

ring radius.

10.0
width float

of the ring.

0.5
angle_resolution float

max number of degrees per point.

2.5
layer LayerSpec

layer.

'WG'
angle float

angular coverage of the ring

360
distance_resolution float | None

max distance between points. This is an alternate way to describe the resolution besides setting angle_resolution. If distance_resolution and angle_resolution are both set, distance_resolution determines the resolution.

None
Source code in gdsfactory/components/rings/ring.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
@gf.cell_with_module_name(tags=["rings"])
def ring(
    radius: float = 10.0,
    width: float = 0.5,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
    angle: float = 360,
    distance_resolution: float | None = None,
) -> Component:
    """Returns a ring.

    Args:
        radius: ring radius.
        width: of the ring.
        angle_resolution: max number of degrees per point.
        layer: layer.
        angle: angular coverage of the ring
        distance_resolution: max distance between points. This is an alternate way to describe the resolution besides setting angle_resolution. If distance_resolution and angle_resolution are both set, distance_resolution determines the resolution.
    """
    if radius < width / 2:
        raise ValueError(
            f"Error: radius is {radius} and width is {width}. radius must be >= width / 2."
        )

    if width < 0:
        raise ValueError(f"Error: width is {width}, but it must be nonnegative.")

    if angle > 360 or angle < 0:
        raise ValueError(f"Error: angle is {angle}, but it must be in [0, 360].")

    if distance_resolution is not None and distance_resolution <= 0:
        raise ValueError(
            f"Error: distance_resolution is {distance_resolution}, but it must be positive if given."
        )

    if distance_resolution is None and angle_resolution <= 0:
        raise ValueError(
            f"Error: angle_resolution is {angle_resolution}, but it must be positive."
        )

    D = gf.Component()
    inner_radius = radius - width / 2
    outer_radius = radius + width / 2
    if distance_resolution is not None:
        num_points = int(
            np.ceil(2 * pi * outer_radius * angle / 360 / distance_resolution)
        )
    else:
        num_points = int(np.ceil(angle / angle_resolution))
    t = np.linspace(0, angle, num_points + 1) * pi / 180
    inner_points_x = inner_radius * cos(t)
    inner_points_y = inner_radius * sin(t)
    outer_points_x = outer_radius * cos(t)
    outer_points_y = outer_radius * sin(t)
    xpts = np.concatenate([inner_points_x, outer_points_x[::-1]])
    ypts = np.concatenate([inner_points_y, outer_points_y[::-1]])
    D.add_polygon(points=list(zip(xpts, ypts, strict=False)), layer=layer)
    return D

ring

ring_asymmetric

ring_asymmetric(
    radius: float = 10.0,
    length_x: float = 2.0,
    length_y: float = 4.0,
    straight: ComponentSpec = "straight",
    bend: ComponentSpec = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
) -> Component

An asymmetric ring with straight waveguides between the bends.

Parameters:

Name Type Description Default
radius float

of the ring.

10.0
length_x float

horizontal straight length.

2.0
length_y float

vertical straight length.

4.0
straight ComponentSpec

straight component spec.

'straight'
bend ComponentSpec

bend component spec.

'bend_circular'
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/rings/ring_crow.py
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
@gf.cell_with_module_name(tags=["rings"])
def ring_asymmetric(
    radius: float = 10.0,
    length_x: float = 2.0,
    length_y: float = 4.0,
    straight: ComponentSpec = "straight",
    bend: ComponentSpec = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """An asymmetric ring with straight waveguides between the bends.

    Args:
        radius: of the ring.
        length_x: horizontal straight length.
        length_y: vertical straight length.
        straight: straight component spec.
        bend: bend component spec.
        cross_section: cross_section spec.
    """
    ring = Component()

    bend_c = gf.get_component(bend, radius=radius, cross_section=cross_section)

    bend1 = ring.add_ref(bend_c, name="bot_right_bend_ring")
    bend2 = ring.add_ref(bend_c, name="top_right_bend_ring")
    bend3 = ring.add_ref(bend_c, name="top_left_bend_ring")
    bend4 = ring.add_ref(bend_c, name="bot_left_bend_ring")

    straight_hor_c = gf.get_component(
        straight, length=length_x, cross_section=cross_section
    )
    straight_ver_c = gf.get_component(
        straight, length=length_y, cross_section=cross_section
    )
    straight_hor1 = ring.add_ref(straight_hor_c, name="bot_hor_waveguide_ring")
    straight_hor2 = ring.add_ref(straight_hor_c, name="top_hor_waveguide_ring")
    straight_ver1 = ring.add_ref(straight_ver_c, name="right_ver_waveguide_ring")
    straight_ver2 = ring.add_ref(straight_ver_c, name="left_ver_waveguide_ring")

    bend1.connect("o1", straight_hor1.ports["o2"])
    straight_ver1.connect("o1", bend1.ports["o2"])
    bend2.connect("o1", straight_ver1.ports["o2"])
    straight_hor2.connect("o1", bend2.ports["o2"])
    bend3.connect("o1", straight_hor2.ports["o2"])
    straight_ver2.connect("o1", bend3.ports["o2"])
    bend4.connect("o1", straight_ver2.ports["o2"])

    return ring

ring_asymmetric

ring_crow

ring_asymmetric

ring_asymmetric(
    radius: float = 10.0,
    length_x: float = 2.0,
    length_y: float = 4.0,
    straight: ComponentSpec = "straight",
    bend: ComponentSpec = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
) -> Component

An asymmetric ring with straight waveguides between the bends.

Parameters:

Name Type Description Default
radius float

of the ring.

10.0
length_x float

horizontal straight length.

2.0
length_y float

vertical straight length.

4.0
straight ComponentSpec

straight component spec.

'straight'
bend ComponentSpec

bend component spec.

'bend_circular'
cross_section CrossSectionSpec

cross_section spec.

'strip'
Source code in gdsfactory/components/rings/ring_crow.py
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
@gf.cell_with_module_name(tags=["rings"])
def ring_asymmetric(
    radius: float = 10.0,
    length_x: float = 2.0,
    length_y: float = 4.0,
    straight: ComponentSpec = "straight",
    bend: ComponentSpec = "bend_circular",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """An asymmetric ring with straight waveguides between the bends.

    Args:
        radius: of the ring.
        length_x: horizontal straight length.
        length_y: vertical straight length.
        straight: straight component spec.
        bend: bend component spec.
        cross_section: cross_section spec.
    """
    ring = Component()

    bend_c = gf.get_component(bend, radius=radius, cross_section=cross_section)

    bend1 = ring.add_ref(bend_c, name="bot_right_bend_ring")
    bend2 = ring.add_ref(bend_c, name="top_right_bend_ring")
    bend3 = ring.add_ref(bend_c, name="top_left_bend_ring")
    bend4 = ring.add_ref(bend_c, name="bot_left_bend_ring")

    straight_hor_c = gf.get_component(
        straight, length=length_x, cross_section=cross_section
    )
    straight_ver_c = gf.get_component(
        straight, length=length_y, cross_section=cross_section
    )
    straight_hor1 = ring.add_ref(straight_hor_c, name="bot_hor_waveguide_ring")
    straight_hor2 = ring.add_ref(straight_hor_c, name="top_hor_waveguide_ring")
    straight_ver1 = ring.add_ref(straight_ver_c, name="right_ver_waveguide_ring")
    straight_ver2 = ring.add_ref(straight_ver_c, name="left_ver_waveguide_ring")

    bend1.connect("o1", straight_hor1.ports["o2"])
    straight_ver1.connect("o1", bend1.ports["o2"])
    bend2.connect("o1", straight_ver1.ports["o2"])
    straight_hor2.connect("o1", bend2.ports["o2"])
    bend3.connect("o1", straight_hor2.ports["o2"])
    straight_ver2.connect("o1", bend3.ports["o2"])
    bend4.connect("o1", straight_ver2.ports["o2"])

    return ring

ring_crow

ring_crow(
    gaps: tuple[float, ...] = (0.2, 0.2, 0.2, 0.2),
    radius: tuple[float, ...] = (10.0, 10.0, 10.0),
    bends: tuple[ComponentSpec, ...] | None = None,
    ring_cross_sections: tuple[CrossSectionSpec, ...] = (
        "strip",
        "strip",
        "strip",
    ),
    length_x: float = 0,
    lengths_y: tuple[float, ...] = (0, 0, 0),
    input_straight_cross_section: (
        CrossSectionSpec | None
    ) = None,
    output_straight_cross_section: (
        CrossSectionSpec | None
    ) = None,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Coupled ring resonators.

Parameters:

Name Type Description Default
gaps tuple[float, ...]

gap between rings.

(0.2, 0.2, 0.2, 0.2)
radius tuple[float, ...]

for each ring.

(10.0, 10.0, 10.0)
bends tuple[ComponentSpec, ...] | None

bend spec for each ring.

None
ring_cross_sections tuple[CrossSectionSpec, ...]

cross_section spec for each ring.

('strip', 'strip', 'strip')
length_x float

ring coupler length.

0
lengths_y tuple[float, ...]

vertical straight length.

(0, 0, 0)
input_straight_cross_section CrossSectionSpec | None

cross_section spec for input and output straight. Defaults to cross_section.

None
output_straight_cross_section CrossSectionSpec | None

cross_section spec for input and output straight. Defaults to cross_section.

None
cross_section CrossSectionSpec

cross_section spec for input and output straight.

--==ct==-- gap[N-1] | | sl sr ring[N-1] | | --==cb==-- gap[N-2]

. . .

--==ct==-- | | sl sr lengths_y[1], ring[1] | | --==cb==-- gap[1]

--==ct==-- | | sl sr lengths_y[0], ring[0] | | --==cb==-- gap[0]

length_x

'strip'
Source code in gdsfactory/components/rings/ring_crow.py
 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
 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
@gf.cell_with_module_name(schematic_function=ring_double_schematic, tags=["rings"])
def ring_crow(
    gaps: tuple[float, ...] = (0.2, 0.2, 0.2, 0.2),
    radius: tuple[float, ...] = (10.0, 10.0, 10.0),
    bends: tuple[ComponentSpec, ...] | None = None,
    ring_cross_sections: tuple[CrossSectionSpec, ...] = ("strip", "strip", "strip"),
    length_x: float = 0,
    lengths_y: tuple[float, ...] = (0, 0, 0),
    input_straight_cross_section: CrossSectionSpec | None = None,
    output_straight_cross_section: CrossSectionSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Coupled ring resonators.

    Args:
        gaps: gap between rings.
        radius: for each ring.
        bends: bend spec for each ring.
        ring_cross_sections: cross_section spec for each ring.
        length_x: ring coupler length.
        lengths_y: vertical straight length.
        input_straight_cross_section: cross_section spec for input and output straight. Defaults to cross_section.
        output_straight_cross_section: cross_section spec for input and output straight. Defaults to cross_section.
        cross_section: cross_section spec for input and output straight.

         --==ct==-- gap[N-1]
          |      |
          sl     sr ring[N-1]
          |      |
         --==cb==-- gap[N-2]

             .
             .
             .

         --==ct==--
          |      |
          sl     sr lengths_y[1], ring[1]
          |      |
         --==cb==-- gap[1]

         --==ct==--
          |      |
          sl     sr lengths_y[0], ring[0]
          |      |
         --==cb==-- gap[0]

          length_x
    """
    c = Component()

    bends = bends or (gf.c.bend_circular,) * len(radius)
    input_straight_cross_section = input_straight_cross_section or cross_section
    output_straight_cross_section = output_straight_cross_section or cross_section

    output_straight_cross_section = gf.get_cross_section(output_straight_cross_section)
    input_straight_cross_section = gf.get_cross_section(input_straight_cross_section)

    straight = gf.c.straight

    # Input bus
    input_straight = gf.get_component(
        straight,
        length=2 * radius[0] + length_x,
        cross_section=input_straight_cross_section,
    )
    input_straight_cross_section = gf.get_cross_section(input_straight_cross_section)
    input_straight_width = input_straight_cross_section.width

    input_straight_waveguide = c.add_ref(input_straight).movex(-radius[0])
    c.add_port(name="o1", port=input_straight_waveguide.ports["o1"])
    c.add_port(name="o2", port=input_straight_waveguide.ports["o2"])

    # Cascade rings
    cum_y_dist = input_straight_width / 2

    for gap, r, bend, cross_section, length_y in zip(
        gaps, radius, bends, ring_cross_sections, lengths_y, strict=False
    ):
        gap = gf.snap.snap_to_grid(gap, grid_factor=2)
        ring = ring_asymmetric(
            radius=r,
            length_x=length_x,
            length_y=length_y,
            straight=straight,
            bend=bend,
            cross_section=cross_section,
        )
        xs = gf.get_cross_section(cross_section)
        bend_width = xs.width
        ring_ref = c.add_ref(ring)
        ring_ref.movey(cum_y_dist + gap + bend_width / 2)
        cum_y_dist += gap + bend_width + 2 * r + length_y

    # Output bus
    output_straight = gf.get_component(
        straight,
        length=2 * radius[-1] + length_x,
        cross_section=output_straight_cross_section,
    )
    output_straight_width = output_straight_cross_section.width
    output_straight_waveguide = (
        c.add_ref(output_straight)
        .movey(cum_y_dist + gaps[-1] + output_straight_width / 2)
        .movex(-radius[-1])
    )
    c.add_port(name="o3", port=output_straight_waveguide.ports["o1"])
    c.add_port(name="o4", port=output_straight_waveguide.ports["o2"])
    return c

ring_crow

ring_crow_couplers

ring_crow_couplers

ring_crow_couplers(
    radius: Sequence[float] = (10.0,) * 3,
    bends: Sequence[ComponentSpec] = ("bend_circular",) * 3,
    ring_cross_sections: Sequence[CrossSectionSpec] = (
        "strip",
    )
    * 3,
    couplers: Sequence[ComponentSpec] = ("coupler",) * 4,
) -> Component

Coupled ring resonators with coupler components between gaps.

Parameters:

Name Type Description Default
radius Sequence[float]

for the bend and coupler.

(10.0,) * 3
bends Sequence[ComponentSpec]

bend specs.

('bend_circular',) * 3
ring_cross_sections Sequence[CrossSectionSpec]

cross_section for the ring.

('strip',) * 3
couplers Sequence[ComponentSpec]

coupling component between rings and bus.

--==ct==-- gap[N-1] <------- couplers[N-1] | | sl sr ring[N-1] | | --==cb==-- gap[N-2] <------- couplers[N-2]

. . .

--==ct==-- | | sl sr lengths_y[1], ring[1] | | --==cb==-- gap[1] <------- couplers[1] --==ct==-- | | sl sr lengths_y[0], ring[0] | | --==cb==-- gap[0] <------- couplers[0]

length_x

('coupler',) * 4
Source code in gdsfactory/components/rings/ring_crow_couplers.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
 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
@gf.cell_with_module_name(schematic_function=ring_double_schematic, tags=["rings"])
def ring_crow_couplers(
    radius: Sequence[float] = (10.0,) * 3,
    bends: Sequence[ComponentSpec] = ("bend_circular",) * 3,
    ring_cross_sections: Sequence[CrossSectionSpec] = ("strip",) * 3,
    couplers: Sequence[ComponentSpec] = ("coupler",) * 4,
) -> Component:
    """Coupled ring resonators with coupler components between gaps.

    Args:
        radius: for the bend and coupler.
        bends: bend specs.
        ring_cross_sections: cross_section for the ring.
        couplers: coupling component between rings and bus.

         --==ct==-- gap[N-1]   <------- couplers[N-1]
          |      |
          sl     sr ring[N-1]
          |      |
         --==cb==-- gap[N-2]   <------- couplers[N-2]

             .
             .
             .

         --==ct==--
          |      |
          sl     sr lengths_y[1], ring[1]
          |      |
         --==cb==-- gap[1]
                                <------- couplers[1]
         --==ct==--
          |      |
          sl     sr lengths_y[0], ring[0]
          |      |
         --==cb==-- gap[0]      <------- couplers[0]

          length_x
    """
    c = Component()

    couplers_refs: list[ComponentReference] = []
    for cp in couplers:
        coupler_ref = c.add_ref(gf.get_component(cp))
        couplers_refs.append(coupler_ref)

    # Input bus
    c.add_port(name="o1", port=couplers_refs[0].ports["o1"])
    c.add_port(name="o2", port=couplers_refs[0].ports["o4"])

    # Cascade rings
    for index, (r, bend, cross_section) in enumerate(
        zip(radius, bends, ring_cross_sections, strict=False)
    ):
        # Add ring
        bend_c = gf.get_component(bend, radius=r, cross_section=cross_section)
        bend1 = c.add_ref(bend_c, name=f"bot_right_bend_ring_{index}")
        bend2 = c.add_ref(bend_c, name=f"top_right_bend_ring_{index}")
        bend3 = c.add_ref(bend_c, name=f"top_left_bend_ring_{index}")
        bend4 = c.add_ref(bend_c, name=f"bot_left_bend_ring_{index}")

        # We need to account for the chance that the top and bottom couplers
        # have a different length --> In this case we need to add straights
        coup1_extent = couplers_refs[index].xmax - couplers_refs[index].xmin
        coup2_extent = couplers_refs[index + 1].xmax - couplers_refs[index + 1].xmin

        if coup1_extent == coup2_extent:
            # Length of the couplers is the same -- we are good
            bend1.connect("o1", couplers_refs[index].ports["o3"])
            bend2.connect("o1", bend1.ports["o2"])
            couplers_refs[index + 1].connect("o4", bend2.ports["o2"])
            bend3.connect("o1", couplers_refs[index + 1].ports["o1"])
        else:
            str_len = np.abs(coup1_extent - coup2_extent) / 2
            str_sec = gf.components.straight(
                cross_section=cross_section, length=str_len
            )

            str1 = c << str_sec
            str2 = c << str_sec

            if coup1_extent > coup2_extent:
                # The straight are connected to coupler 2
                bend1.connect("o1", couplers_refs[index].ports["o3"])
                bend2.connect("o1", bend1.ports["o2"])
                str1.connect("o1", bend2.ports["o2"])
                couplers_refs[index + 1].connect("o4", str1.ports["o2"])
                str2.connect("o1", couplers_refs[index + 1].ports["o1"])
                bend3.connect("o1", str2.ports["o2"])
            else:
                # The straights are connected to coupler 1
                str1.connect("o1", couplers_refs[index].ports["o3"])
                str2.connect("o2", couplers_refs[index].ports["o2"])
                bend1.connect("o1", str1.ports["o2"])
                bend2.connect("o1", bend1.ports["o2"])
                couplers_refs[index + 1].connect("o4", bend2.ports["o2"])
                bend3.connect("o1", couplers_refs[index + 1].ports["o1"])
        bend4.connect("o1", bend3.ports["o2"])

    # Output bus
    c.add_port(name="o3", port=couplers_refs[-1].ports["o2"])
    c.add_port(name="o4", port=couplers_refs[-1].ports["o3"])
    return c

ring_crow_couplers

ring_double

ring_double

ring_double(
    gap: float = 0.2,
    gap_top: float | None = None,
    gap_bot: float | None = None,
    radius: float | None = None,
    length_x: float = 0.01,
    length_y: float = 0.01,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    coupler_ring: ComponentSpec = "coupler_ring",
    coupler_ring_top: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    length_extension: float | None = None,
) -> Component

Returns a double bus ring.

two couplers (ct: top, cb: bottom) connected with two vertical straights (sl: left, sr: right)

Parameters:

Name Type Description Default
gap float

gap between for coupler.

0.2
gap_top float | None

gap for the top coupler. Defaults to gap.

None
gap_bot float | None

gap for the bottom coupler. Defaults to gap.

None
radius float | None

for the bend and coupler.

None
length_x float

ring coupler length.

0.01
length_y float

vertical straight length.

0.01
bend ComponentSpec

90 degrees bend spec.

'bend_euler'
straight ComponentSpec

straight spec.

'straight'
coupler_ring ComponentSpec

ring coupler spec.

'coupler_ring'
coupler_ring_top ComponentSpec | None

top ring coupler spec. Defaults to coupler_ring.

None
cross_section CrossSectionSpec

cross_section spec.

'strip'
length_extension float | None

straight length extension at the end of the coupler bottom ports.

o2──────▲─────────o3 │gap_top xx──────▼─────────xxx xxx xxx

None

xx xxx x xxx xx xx▲ xx xx│length_y xx xx▼ xx xx xx length_x x xx ◄───────────────► x xx xxx xx xxx xxx──────▲─────────xxx │gap o1──────▼─────────◄──────────────► o4 length_extension

Source code in gdsfactory/components/rings/ring_double.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
@gf.cell_with_module_name(schematic_function=ring_double_schematic, tags=["rings"])
def ring_double(
    gap: float = 0.2,
    gap_top: float | None = None,
    gap_bot: float | None = None,
    radius: float | None = None,
    length_x: float = 0.01,
    length_y: float = 0.01,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    coupler_ring: ComponentSpec = "coupler_ring",
    coupler_ring_top: ComponentSpec | None = None,
    cross_section: CrossSectionSpec = "strip",
    length_extension: float | None = None,
) -> Component:
    """Returns a double bus ring.

    two couplers (ct: top, cb: bottom)
    connected with two vertical straights (sl: left, sr: right)

    Args:
        gap: gap between for coupler.
        gap_top: gap for the top coupler. Defaults to gap.
        gap_bot: gap for the bottom coupler. Defaults to gap.
        radius: for the bend and coupler.
        length_x: ring coupler length.
        length_y: vertical straight length.
        bend: 90 degrees bend spec.
        straight: straight spec.
        coupler_ring: ring coupler spec.
        coupler_ring_top: top ring coupler spec. Defaults to coupler_ring.
        cross_section: cross_section spec.
        length_extension: straight length extension at the end of the coupler bottom ports.

           o2──────▲─────────o3
                   │gap_top
           xx──────▼─────────xxx
          xxx                   xxx
        xxx                       xxx
       xx                           xxx
       x                             xxx
      xx                              xx▲
      xx                              xx│length_y
      xx                              xx▼
      xx                             xx
       xx          length_x          x
        xx     ◄───────────────►    x
         xx                       xxx
           xx                   xxx
            xxx──────▲─────────xxx
                     │gap
             o1──────▼─────────◄──────────────► o4
                                length_extension
    """
    gap_top = gap_top or gap
    gap_bot = gap_bot or gap
    coupler_component_bot = gf.get_component(
        coupler_ring,
        gap=gap_bot,
        radius=radius,
        length_x=length_x,
        cross_section=cross_section,
        straight=straight,
        bend=bend,
        length_extension=length_extension,
    )
    coupler_component_top = gf.get_component(
        coupler_ring_top or coupler_ring,
        gap=gap_top,
        radius=radius,
        length_x=length_x,
        cross_section=cross_section,
        straight=straight,
        bend=bend,
        length_extension=length_extension,
    )

    c = Component()
    cb = c.add_ref(coupler_component_bot)
    ct = c.add_ref(coupler_component_top)

    if length_y > 0:
        # Add vertical straights when length_y > 0
        straight_component = gf.get_component(
            straight,
            length=length_y,
            cross_section=cross_section,
        )
        sl = c << straight_component
        sr = c << straight_component

        sl.connect(port="o1", other=cb.ports["o2"])
        sr.connect(port="o2", other=cb.ports["o3"])
        ct.connect(port="o3", other=sl.ports["o2"])
    else:
        # When length_y=0, connect couplers directly
        ct.connect(port="o3", other=cb.ports["o2"])

    c.add_port("o1", port=cb.ports["o1"])
    c.add_port("o2", port=cb.ports["o4"])
    c.add_port("o3", port=ct.ports["o4"])
    c.add_port("o4", port=ct.ports["o1"])
    c.info["radius"] = coupler_component_bot.info["radius"]
    return c

ring_double

ring_double_bend_coupler

ring_double_bend_coupler

ring_double_bend_coupler(
    radius: float = 5.0,
    gap: float = 0.2,
    coupling_angle_coverage: float = 70.0,
    bend: ComponentAllAngleFactory = bend_circular_all_angle,
    length_x: float = 0.6,
    length_y: float = 0.6,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
) -> Component

Returns ring with double curved couplers.

Parameters:

Name Type Description Default
radius float

um.

5.0
gap float

um.

0.2
coupling_angle_coverage float

degrees.

70.0
bend ComponentAllAngleFactory

for bend.

bend_circular_all_angle
length_x float

horizontal straight length.

0.6
length_y float

vertical straight length.

0.6
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

'strip'
Source code in gdsfactory/components/rings/ring_double_bend_coupler.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
@gf.cell_with_module_name(schematic_function=ring_double_schematic, tags=["rings"])
def ring_double_bend_coupler(
    radius: float = 5.0,
    gap: float = 0.2,
    coupling_angle_coverage: float = 70.0,
    bend: ComponentAllAngleFactory = bend_circular_all_angle,
    length_x: float = 0.6,
    length_y: float = 0.6,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
) -> Component:
    r"""Returns ring with double curved couplers.

    Args:
        radius: um.
        gap: um.
        coupling_angle_coverage: degrees.
        bend: for bend.
        length_x: horizontal straight length.
        length_y: vertical straight length.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.
    """
    c = Component()

    c_halfring = gf.c.coupler_ring_bend(
        radius=radius,
        coupler_gap=gap,
        coupling_angle_coverage=coupling_angle_coverage,
        length_x=length_x,
        cross_section_inner=cross_section_inner,
        cross_section_outer=cross_section_outer,
        bend=bend,
    )

    xi = gf.get_cross_section(cross_section_inner)
    xo = gf.get_cross_section(cross_section_outer)
    half_height = radius + xi.width / 2 + gap + xo.width + length_y / 2

    if c_halfring.ysize > half_height:
        raise ValueError(
            "The coupling_angle_coverage is too large for the given bend radius: "
            + "the coupling waveguides will overlap."
        )

    cb = c << c_halfring
    ct = c << c_halfring

    cross_section = cross_section_inner
    sy = gf.c.straight(length=length_y, cross_section=cross_section)
    sl = c << sy
    sr = c << sy

    sl.connect(port="o1", other=cb.ports["o2"])
    ct.connect(port="o3", other=sl.ports["o2"])
    sr.connect(port="o1", other=ct.ports["o2"])
    cb.connect(port="o3", other=sr.ports["o2"])

    c.add_port("o1", port=cb.ports["o1"])
    c.add_port("o2", port=ct.ports["o4"])
    c.add_port("o3", port=ct.ports["o1"])
    c.add_port("o4", port=cb.ports["o4"])
    c.flatten()
    return c

ring_double_bend_coupler

ring_double_heater

ring_double_heater(
    gap: float = 0.2,
    gap_top: float | None = None,
    gap_bot: float | None = None,
    radius: float | None = None,
    length_x: float = 1.0,
    length_y: float = 0.01,
    coupler_ring: ComponentSpec = "coupler_ring",
    coupler_ring_top: ComponentSpec | None = None,
    straight: ComponentSpec = "straight",
    bend: ComponentSpec = "bend_euler",
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    cross_section: CrossSectionSpec = "strip",
    via_stack: ComponentSpec = "via_stack_heater_mtop_mini",
    port_orientation: AngleInDegrees | None = None,
    via_stack_offset: Float2 = (1, 0),
    via_stack_size: Float2 | None = None,
    with_drop: bool = True,
    length_extension: float | None = None,
    length_extension_top: float | None = None,
    length_extension_bot: float | None = None,
) -> Component

Returns a double bus ring with heater on top.

two couplers (ct: top, cb: bottom) connected with two vertical straights (sl: left, sr: right)

Parameters:

Name Type Description Default
gap float

gap between for coupler.

0.2
gap_top float | None

gap for the top coupler. Defaults to gap.

None
gap_bot float | None

gap for the bottom coupler. Defaults to gap.

None
radius float | None

for the bend and coupler.

None
length_x float

ring coupler length.

1.0
length_y float

vertical straight length.

0.01
coupler_ring ComponentSpec

ring coupler spec.

'coupler_ring'
coupler_ring_top ComponentSpec | None

ring coupler spec for coupler away from vias (defaults to coupler_ring)

None
straight ComponentSpec

straight spec.

'straight'
bend ComponentSpec

bend spec.

'bend_euler'
cross_section_heater CrossSectionSpec

for heater.

'heater_metal'
cross_section_waveguide_heater CrossSectionSpec

for waveguide with heater.

'strip_heater_metal'
cross_section CrossSectionSpec

for regular waveguide.

'strip'
via_stack ComponentSpec

for heater to routing metal.

'via_stack_heater_mtop_mini'
port_orientation AngleInDegrees | None

for electrical ports to promote from via_stack.

None
via_stack_size Float2 | None

size of via_stack.

None
via_stack_offset Float2

x,y offset for via_stack.

(1, 0)
with_drop bool

adds drop ports.

True
length_extension float | None

straight length extension at the end of the coupler bottom ports.

None
length_extension_top float | None

straight length extension at the end of the coupler top ports.

None
length_extension_bot float | None

straight length extension at the end of the coupler bottom ports.

o2──────▲─────────o3 │gap_top xx──────▼─────────xxx xxx xxx

None

xx xxx x xxx xx xx▲ xx xx│length_y xx xx▼ xx xx xx length_x x xx ◄───────────────► x xx xxx xx xxx xxx──────▲─────────xxx │gap o1──────▼─────────o4

Source code in gdsfactory/components/rings/ring_heater.py
 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
 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
@gf.cell_with_module_name(schematic_function=ring_double_schematic, tags=["rings"])
def ring_double_heater(
    gap: float = 0.2,
    gap_top: float | None = None,
    gap_bot: float | None = None,
    radius: float | None = None,
    length_x: float = 1.0,
    length_y: float = 0.01,
    coupler_ring: ComponentSpec = "coupler_ring",
    coupler_ring_top: ComponentSpec | None = None,
    straight: ComponentSpec = "straight",
    bend: ComponentSpec = "bend_euler",
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    cross_section: CrossSectionSpec = "strip",
    via_stack: ComponentSpec = "via_stack_heater_mtop_mini",
    port_orientation: AngleInDegrees | None = None,
    via_stack_offset: Float2 = (1, 0),
    via_stack_size: Float2 | None = None,
    with_drop: bool = True,
    length_extension: float | None = None,
    length_extension_top: float | None = None,
    length_extension_bot: float | None = None,
) -> Component:
    """Returns a double bus ring with heater on top.

    two couplers (ct: top, cb: bottom)
    connected with two vertical straights (sl: left, sr: right)

    Args:
        gap: gap between for coupler.
        gap_top: gap for the top coupler. Defaults to gap.
        gap_bot: gap for the bottom coupler. Defaults to gap.
        radius: for the bend and coupler.
        length_x: ring coupler length.
        length_y: vertical straight length.
        coupler_ring: ring coupler spec.
        coupler_ring_top: ring coupler spec for coupler away from vias (defaults to coupler_ring)
        straight: straight spec.
        bend: bend spec.
        cross_section_heater: for heater.
        cross_section_waveguide_heater: for waveguide with heater.
        cross_section: for regular waveguide.
        via_stack: for heater to routing metal.
        port_orientation: for electrical ports to promote from via_stack.
        via_stack_size: size of via_stack.
        via_stack_offset: x,y offset for via_stack.
        with_drop: adds drop ports.
        length_extension: straight length extension at the end of the coupler bottom ports.
        length_extension_top: straight length extension at the end of the coupler top ports.
        length_extension_bot: straight length extension at the end of the coupler bottom ports.

           o2──────▲─────────o3
                   │gap_top
           xx──────▼─────────xxx
          xxx                   xxx
        xxx                       xxx
       xx                           xxx
       x                             xxx
      xx                              xx▲
      xx                              xx│length_y
      xx                              xx▼
      xx                             xx
       xx          length_x          x
        xx     ◄───────────────►    x
         xx                       xxx
           xx                   xxx
            xxx──────▲─────────xxx
                     │gap
             o1──────▼─────────o4
    """
    gap_top = gap_top or gap
    gap_bot = gap_bot or gap

    gap = gf.snap.snap_to_grid(gap, grid_factor=2)
    gap_top = gf.snap.snap_to_grid(gap_top, grid_factor=2)
    gap_bot = gf.snap.snap_to_grid(gap_bot, grid_factor=2)

    coupler_ring_top = coupler_ring_top or coupler_ring

    if length_extension_bot is None:
        length_extension_bot = length_extension

    if length_extension_top is None:
        length_extension_top = length_extension

    coupler_component = gf.get_component(
        coupler_ring,
        gap=gap_bot,
        radius=radius,
        length_x=length_x,
        bend=bend,
        cross_section=cross_section,
        cross_section_bend=cross_section_waveguide_heater,
        length_extension=length_extension_bot,
    )
    coupler_component_top = gf.get_component(
        coupler_ring_top,
        gap=gap_top,
        radius=radius,
        length_x=length_x,
        bend=bend,
        cross_section=cross_section,
        cross_section_bend=cross_section_waveguide_heater,
        length_extension=length_extension_top,
    )
    straight_component = gf.get_component(
        straight,
        length=length_y,
        cross_section=cross_section_waveguide_heater,
    )

    c = Component()

    cb = c.add_ref(coupler_component)
    sl = c.add_ref(straight_component)
    sr = c.add_ref(straight_component)
    c.add_port("o1", port=cb.ports["o1"])
    c.add_port("o2", port=cb.ports["o4"])

    if with_drop:
        ct = c.add_ref(coupler_component_top)
        sl.connect(port="o1", other=cb.ports["o2"])
        ct.connect(port="o3", other=sl.ports["o2"])
        sr.connect(port="o2", other=ct.ports["o2"])
        c.add_port("o3", port=ct.ports["o4"])
        c.add_port("o4", port=ct.ports["o1"])
        heater_top = c << gf.get_component(
            straight,
            length=length_x,
            cross_section=cross_section_heater,
        )
        heater_top.connect("e1", ct["e1"])

    else:
        straight_top = gf.get_component(
            straight,
            length=length_x,
            cross_section=cross_section_waveguide_heater,
        )
        bend = gf.get_component(
            bend,
            radius=radius,
            cross_section=cross_section_waveguide_heater,
        )
        bl = c << bend
        br = c << bend
        st = c << straight_top

        sl.connect(port="o1", other=cb.ports["o2"])
        bl.connect(port="o2", other=sl.ports["o2"])

        st.connect(port="o2", other=bl.ports["o1"])
        br.connect(port="o2", other=st.ports["o1"])
        sr.connect(port="o1", other=br.ports["o1"])
        sr.connect(port="o2", other=cb.ports["o3"])

    if via_stack_size:
        via = gf.get_component(via_stack, size=via_stack_size)

    else:
        via = gf.get_component(via_stack)

    c1 = c << via
    c2 = c << via
    c1.xmax = -length_x / 2 + cb.x - via_stack_offset[0]
    c2.xmin = +length_x / 2 + cb.x + via_stack_offset[0]
    c1.movey(via_stack_offset[1])
    c2.movey(via_stack_offset[1])

    p1 = c1.ports.filter(orientation=port_orientation)
    p2 = c2.ports.filter(orientation=port_orientation)
    valid_orientations = {p.orientation for p in via.ports}

    if not p1:
        raise ValueError(
            f"No ports found for port_orientation {port_orientation} in {valid_orientations}"
        )

    c.flatten()
    c.add_ports(p1, prefix="l_")
    c.add_ports(p2, prefix="r_")

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    return c

ring_double_heater

ring_double_pn

ring_double_pn(
    add_gap: float = 0.3,
    drop_gap: float = 0.3,
    radius: float = 5.0,
    doping_angle: float = 85,
    cross_section: CrossSectionFactory = rib,
    pn_cross_section: CrossSectionFactory = cross_section_pn,
    doped_heater: bool = True,
    doped_heater_angle_buffer: float = 10,
    doped_heater_layer: LayerSpec = "NPP",
    doped_heater_width: float = 0.5,
    doped_heater_waveguide_offset: float = 2.175,
    heater_vias: ComponentSpec = _heater_vias,
    with_drop: bool = True,
    **kwargs: Any
) -> gf.Component

Returns add-drop pn ring with optional doped heater.

Parameters:

Name Type Description Default
add_gap float

gap to add waveguide. Bottom gap.

0.3
drop_gap float

gap to drop waveguide. Top gap.

0.3
radius float

for the bend and coupler.

5.0
doping_angle float

angle in degrees representing portion of ring that is doped.

85
length_x

ring coupler length.

required
length_y

vertical straight length.

required
cross_section CrossSectionFactory

cross_section spec for non-PN doped rib waveguide sections.

rib
pn_cross_section CrossSectionFactory

cross section of pn junction.

cross_section_pn
doped_heater bool

boolean for if we include doped heater or not.

True
doped_heater_angle_buffer float

angle in degrees buffering heater from pn junction.

10
doped_heater_layer LayerSpec

doping layer for heater.

'NPP'
doped_heater_width float

width of doped heater.

0.5
doped_heater_waveguide_offset float

distance from the center of the ring waveguide to the center of the doped heater.

2.175
heater_vias ComponentSpec

components specifications for heater vias

_heater_vias
with_drop bool

boolean for if we include drop waveguide or not.

True
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/rings/ring_pn.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
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
@gf.cell_with_module_name(schematic_function=ring_double_schematic, tags=["rings"])
def ring_double_pn(
    add_gap: float = 0.3,
    drop_gap: float = 0.3,
    radius: float = 5.0,
    doping_angle: float = 85,
    cross_section: CrossSectionFactory = rib,
    pn_cross_section: CrossSectionFactory = cross_section_pn,
    doped_heater: bool = True,
    doped_heater_angle_buffer: float = 10,
    doped_heater_layer: LayerSpec = "NPP",
    doped_heater_width: float = 0.5,
    doped_heater_waveguide_offset: float = 2.175,
    heater_vias: ComponentSpec = _heater_vias,
    with_drop: bool = True,
    **kwargs: Any,
) -> gf.Component:
    """Returns add-drop pn ring with optional doped heater.

    Args:
        add_gap: gap to add waveguide. Bottom gap.
        drop_gap: gap to drop waveguide. Top gap.
        radius: for the bend and coupler.
        doping_angle: angle in degrees representing portion of ring that is doped.
        length_x: ring coupler length.
        length_y: vertical straight length.
        cross_section: cross_section spec for non-PN doped rib waveguide sections.
        pn_cross_section: cross section of pn junction.
        doped_heater: boolean for if we include doped heater or not.
        doped_heater_angle_buffer: angle in degrees buffering heater from pn junction.
        doped_heater_layer: doping layer for heater.
        doped_heater_width: width of doped heater.
        doped_heater_waveguide_offset: distance from the center of the ring waveguide to the center of the doped heater.
        heater_vias: components specifications for heater vias
        with_drop: boolean for if we include drop waveguide or not.
        kwargs: cross_section settings.

    """
    add_gap = gf.snap.snap_to_grid(add_gap, grid_factor=2)
    drop_gap = gf.snap.snap_to_grid(drop_gap, grid_factor=2)
    c = gf.Component()

    pn_cross_section_ = gf.get_cross_section(pn_cross_section, **kwargs)
    cross_section_ = gf.get_cross_section(cross_section, **kwargs)
    cross_section_ = cross_section_.copy(**kwargs)

    heater_vias = gf.get_component(heater_vias)
    undoping_angle = 180 - doping_angle

    th_waveguide_path = gf.Path()
    th_waveguide_path.append(
        gf.path.straight(length=2 * radius * np.sin(np.pi / 360 * undoping_angle))
    )
    th_waveguide = c << th_waveguide_path.extrude(cross_section=cross_section_)
    th_waveguide.x = 0
    th_waveguide.y = (
        -radius
        - add_gap
        - th_waveguide.ports["o1"].width / 2
        - pn_cross_section_.width / 2
    )

    doped_path = gf.Path()
    doped_path.append(gf.path.arc(radius=radius, angle=-doping_angle))
    undoped_path = gf.Path()
    undoped_path.append(gf.path.arc(radius=radius, angle=undoping_angle))

    r = gf.ComponentAllAngle()
    left_doped_ring_ref = r.add_ref_off_grid(
        doped_path.extrude(cross_section=pn_cross_section_, all_angle=True)
    )
    right_doped_ring_ref = r.add_ref_off_grid(
        doped_path.extrude(cross_section=pn_cross_section_, all_angle=True)
    )
    bottom_undoped_ring_ref = r.add_ref_off_grid(
        undoped_path.extrude(cross_section=cross_section_, all_angle=True)
    )
    top_undoped_ring_ref = r.add_ref_off_grid(
        undoped_path.extrude(cross_section=cross_section_, all_angle=True)
    )

    bottom_undoped_ring_ref.rotate(-undoping_angle / 2)
    bottom_undoped_ring_ref.x = th_waveguide.x

    left_doped_ring_ref.connect("o1", bottom_undoped_ring_ref.ports["o1"])
    right_doped_ring_ref.connect("o2", bottom_undoped_ring_ref.ports["o2"])
    top_undoped_ring_ref.connect("o2", left_doped_ring_ref.ports["o2"])

    ring = c.add_ref_off_grid(r)
    ring.center = (0, 0)

    drop_waveguide_dy = (
        radius
        + drop_gap
        + th_waveguide.ports["o1"].width / 2
        + pn_cross_section_.width / 2
    )

    if doped_heater:
        heater_radius = radius - doped_heater_waveguide_offset
        heater_path = gf.Path()
        heater_path.append(
            gf.path.arc(
                radius=heater_radius, angle=undoping_angle - doped_heater_angle_buffer
            )
        )

        heater = heater_path.extrude(width=0.5, layer=doped_heater_layer)

        bottom_heater_ref = c << heater
        bottom_heater_ref.rotate(-(undoping_angle - doped_heater_angle_buffer) / 2)
        bottom_heater_ref.x = th_waveguide.x
        bottom_heater_ref.y = th_waveguide.y + (
            doped_heater_waveguide_offset + doped_heater_width / 2 + add_gap
        )

        bottom_l_heater_via = c << heater_vias
        bottom_r_heater_via = c << heater_vias
        bottom_l_heater_via.x = bottom_heater_ref.ports["o1"].x
        bottom_l_heater_via.y = bottom_heater_ref.ports["o1"].y
        bottom_r_heater_via.x = bottom_heater_ref.ports["o2"].x
        bottom_r_heater_via.y = bottom_heater_ref.ports["o2"].y

        top_heater_ref = c << heater
        top_heater_ref.rotate(180 - (undoping_angle - doped_heater_angle_buffer) / 2)
        top_heater_ref.x = th_waveguide.x
        top_heater_ref.y = drop_waveguide_dy - (
            doped_heater_waveguide_offset + doped_heater_width / 2 + drop_gap
        )

        top_l_heater_via = c << heater_vias
        top_r_heater_via = c << heater_vias
        top_l_heater_via.x = top_heater_ref.ports["o1"].x
        top_l_heater_via.y = top_heater_ref.ports["o1"].y
        top_r_heater_via.x = top_heater_ref.ports["o2"].x
        top_r_heater_via.y = top_heater_ref.ports["o2"].y

    c.add_port("o1", port=th_waveguide.ports["o1"])
    c.add_port("o2", port=th_waveguide.ports["o2"])

    htr_top_sig = c.add_port(name="htr_top_sig", port=top_l_heater_via["e2"])
    htr_top_gnd = c.add_port(name="htr_top_gnd", port=top_r_heater_via["e2"])
    htr_bot_sig = c.add_port(name="htr_bot_sig", port=bottom_l_heater_via["e2"])
    htr_bot_gnd = c.add_port(name="htr_bot_gnd", port=bottom_r_heater_via["e2"])
    c.create_pin(ports=[htr_top_sig], name="htr_top_sig")
    c.create_pin(ports=[htr_top_gnd], name="htr_top_gnd")
    c.create_pin(ports=[htr_bot_sig], name="htr_bot_sig")
    c.create_pin(ports=[htr_bot_gnd], name="htr_bot_gnd")

    if with_drop:
        drop_waveguide_path = gf.Path()
        drop_waveguide_path.append(
            gf.path.straight(length=2 * radius * np.sin(np.pi / 360 * undoping_angle))
        )
        drop_waveguide = c << drop_waveguide_path.extrude(cross_section=cross_section_)
        drop_waveguide.x = 0
        drop_waveguide.y = drop_waveguide_dy
        c.add_port("o3", port=drop_waveguide.ports["o2"])
        c.add_port("o4", port=drop_waveguide.ports["o1"])
    c.flatten()
    return c

ring_double_pn

ring_single

ring_single

ring_single(
    gap: float = 0.2,
    radius: float | None = None,
    length_x: float = 4.0,
    length_y: float = 0.6,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    coupler_ring: ComponentSpec = "coupler_ring",
    cross_section: CrossSectionSpec = "strip",
    length_extension: float | None = None,
) -> gf.Component

Returns a single ring resonator with a directional coupler.

This component creates a ring resonator that consists of: - A directional coupler (cb) at the bottom - Two vertical straights (sl, sr) on the left and right sides - Two bends (bl, br) connecting the vertical straights - A horizontal straight (st) at the top

The ring resonator is commonly used in photonic integrated circuits for: - Wavelength filtering - Optical modulation - Sensing applications - Optical switching

Parameters:

Name Type Description Default
gap float

Gap between the ring and the straight waveguide in the coupler (μm).

0.2
radius float | None

Radius of the ring bends (μm). If None, it will use the radius from the cross section.

None
length_x float

Length of the horizontal straight section (μm).

4.0
length_y float

Length of the vertical straight sections (μm).

0.6
bend ComponentSpec

Component spec for the 90-degree bends. Default is "bend_euler".

'bend_euler'
straight ComponentSpec

Component spec for the straight waveguides. Default is "straight".

'straight'
coupler_ring ComponentSpec

Component spec for the ring coupler. Default is "coupler_ring".

'coupler_ring'
cross_section CrossSectionSpec

Cross section spec for all waveguides. Default is "strip".

'strip'
length_extension float | None

straight length extension at the end of the coupler bottom ports.

None

Returns:

Name Type Description
Component Component

A gdsfactory Component containing the ring resonator with: - Two ports: "o1" (input) and "o2" (through) - All waveguide sections properly connected - Cross section applied to all waveguides

Raises:

Type Description
ValueError

If length_x or length_y is negative.

    xxxxxxxxxxxxx
xxxxx           xxxx

xxx xxx xxx xxx xx xxx x xxx xx xx▲ xx xx│length_y xx xx▼ xx xx xx length_x x xx ◄───────────────► x xx xxx xx xxx xxx──────▲─────────xxx │gap o1──────▼─────────o2◄──────────────► length_extension

Source code in gdsfactory/components/rings/ring_single.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
 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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def ring_single(
    gap: float = 0.2,
    radius: float | None = None,
    length_x: float = 4.0,
    length_y: float = 0.6,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    coupler_ring: ComponentSpec = "coupler_ring",
    cross_section: CrossSectionSpec = "strip",
    length_extension: float | None = None,
) -> gf.Component:
    """Returns a single ring resonator with a directional coupler.

    This component creates a ring resonator that consists of:
    - A directional coupler (cb) at the bottom
    - Two vertical straights (sl, sr) on the left and right sides
    - Two bends (bl, br) connecting the vertical straights
    - A horizontal straight (st) at the top

    The ring resonator is commonly used in photonic integrated circuits for:
    - Wavelength filtering
    - Optical modulation
    - Sensing applications
    - Optical switching

    Args:
        gap: Gap between the ring and the straight waveguide in the coupler (μm).
        radius: Radius of the ring bends (μm). If None, it will use the radius from the cross section.
        length_x: Length of the horizontal straight section (μm).
        length_y: Length of the vertical straight sections (μm).
        bend: Component spec for the 90-degree bends. Default is "bend_euler".
        straight: Component spec for the straight waveguides. Default is "straight".
        coupler_ring: Component spec for the ring coupler. Default is "coupler_ring".
        cross_section: Cross section spec for all waveguides. Default is "strip".
        length_extension: straight length extension at the end of the coupler bottom ports.

    Returns:
        Component: A gdsfactory Component containing the ring resonator with:
            - Two ports: "o1" (input) and "o2" (through)
            - All waveguide sections properly connected
            - Cross section applied to all waveguides

    Raises:
        ValueError: If length_x or length_y is negative.

                    xxxxxxxxxxxxx
                xxxxx           xxxx
              xxx                   xxx
            xxx                       xxx
           xx                           xxx
           x                             xxx
          xx                              xx▲
          xx                              xx│length_y
          xx                              xx▼
          xx                             xx
           xx          length_x          x
            xx     ◄───────────────►    x
             xx                       xxx
               xx                   xxx
                xxx──────▲─────────xxx
                         │gap
                 o1──────▼─────────o2◄──────────────►
                                     length_extension
    """
    if length_y < 0:
        raise ValueError(f"length_y={length_y} must be >= 0")

    if length_x < 0:
        raise ValueError(f"length_x={length_x} must be >= 0")

    # Create main component
    c = gf.Component()

    settings = dict(
        gap=gap,
        radius=radius,
        length_x=length_x,
        cross_section=cross_section,
        bend=bend,
        straight=straight,
    )

    if length_extension is not None:
        settings["length_extension"] = length_extension

    # Create and place the coupler
    coupler = gf.get_component(coupler_ring, settings=settings)
    cb = c << coupler

    # Create waveguide components
    b = gf.get_component(bend, cross_section=cross_section, radius=radius)

    # Place waveguide components
    bl = c << b  # Left bend
    br = c << b  # Right bend

    if length_y > 0 and length_x > 0:
        sx = gf.get_component(straight, length=length_x, cross_section=cross_section)
        sy = gf.get_component(straight, length=length_y, cross_section=cross_section)
        st = c << sx
        sl = c << sy
        sr = c << sy

        sl.connect(port="o1", other=cb.ports["o2"])
        bl.connect(port="o2", other=sl.ports["o2"])
        st.connect(port="o2", other=bl.ports["o1"])
        br.connect(port="o2", other=st.ports["o1"])
        sr.connect(port="o1", other=br.ports["o1"])
        sr.connect(port="o2", other=cb.ports["o3"])
    elif length_y > 0:
        sy = gf.get_component(straight, length=length_y, cross_section=cross_section)
        sl = c << sy
        sr = c << sy

        sl.connect(port="o1", other=cb.ports["o2"])
        bl.connect(port="o2", other=sl.ports["o2"])
        br.connect(port="o2", other=bl.ports["o1"])
        sr.connect(port="o1", other=br.ports["o1"])
        sr.connect(port="o2", other=cb.ports["o3"])
    elif length_x > 0:
        sx = gf.get_component(straight, length=length_x, cross_section=cross_section)
        st = c << sx

        bl.connect(port="o2", other=cb.ports["o2"])
        st.connect(port="o2", other=bl.ports["o1"])
        br.connect(port="o2", other=st.ports["o1"])
        br.connect(port="o1", other=cb.ports["o3"])
    else:
        bl.connect(port="o2", other=cb.ports["o2"])
        br.connect(port="o2", other=bl.ports["o1"])
        br.connect(port="o1", other=cb.ports["o3"])

    # Add ports
    c.add_port("o2", port=cb.ports["o4"])
    c.add_port("o1", port=cb.ports["o1"])
    c.info["radius"] = coupler.info["radius"]
    return c

ring_single

ring_single_array

ring_single_array

ring_single_array(
    ring: ComponentSpec = "ring_single",
    spacing: float = 15.0,
    list_of_dicts: tuple[dict[str, Any], ...] | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Ring of single bus connected with straights.

Parameters:

Name Type Description Default
ring ComponentSpec

ring spec.

'ring_single'
spacing float

between rings.

15.0
list_of_dicts tuple[dict[str, Any], ...] | None

settings for each ring.

None
cross_section CrossSectionSpec

spec.

__ ____ | | | | | | length_y | | | | | | --======-- spacing ----==gap==--

length_x

'strip'
Source code in gdsfactory/components/rings/ring_single_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
@gf.cell_with_module_name(tags=["rings"])
def ring_single_array(
    ring: ComponentSpec = "ring_single",
    spacing: float = 15.0,
    list_of_dicts: tuple[dict[str, Any], ...] | None = None,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Ring of single bus connected with straights.

    Args:
        ring: ring spec.
        spacing: between rings.
        list_of_dicts: settings for each ring.
        cross_section: spec.

           ______               ______
          |      |             |      |
          |      |  length_y   |      |
          |      |             |      |
         --======-- spacing ----==gap==--

          length_x
    """
    list_of_dicts = list_of_dicts or _list_of_dicts
    c = Component()
    settings0 = list_of_dicts[0]
    ring1 = c << gf.get_component(ring, cross_section=cross_section, **settings0)

    ring0 = ring1
    wg = gf.c.straight(length=spacing, cross_section=cross_section)

    for settings in list_of_dicts[1:]:
        ringi = c << gf.get_component(ring, cross_section=cross_section, **settings)
        wgi = c << wg
        wgi.connect("o1", ring0.ports["o2"])
        ringi.connect("o1", wgi.ports["o2"])
        ring0 = ringi

    c.add_port("o1", port=ring1.ports["o1"])
    c.add_port("o2", port=ringi.ports["o2"])
    return c

ring_single_array

ring_single_bend_coupler

coupler_bend

coupler_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 120.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
) -> Component

Compact curved coupler with bezier escape.

TODO: fix for euler bends.

Parameters:

Name Type Description Default
radius float | None

um.

None
coupler_gap float

um.

0.2
coupling_angle_coverage float

degrees.

120.0
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

'strip'
bend AnyComponentFactory

for bend.

bend_circular_all_angle
bend_output ComponentSpec

for bend.

r 4 | | | / ___3 | / /

'bend_euler'
Source code in gdsfactory/components/rings/ring_single_bend_coupler.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
90
91
92
93
@gf.cell_with_module_name(schematic_function=coupler_schematic, tags=["rings"])
def coupler_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 120.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
) -> Component:
    r"""Compact curved coupler with bezier escape.

    TODO: fix for euler bends.

    Args:
        radius: um.
        coupler_gap: um.
        coupling_angle_coverage: degrees.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.
        bend: for bend.
        bend_output: for bend.

            r   4
            |   |
            |  / ___3
            | / /
        2____/ /
        1_____/
    """
    c = Component()

    xi = gf.get_cross_section(cross_section_inner)
    xo = gf.get_cross_section(cross_section_outer)

    angle_inner = 90
    angle_outer = coupling_angle_coverage / 2
    gap = coupler_gap

    width = xo.width / 2 + xi.width / 2
    spacing = gap + width

    if radius is None:
        radius = xi.radius or xo.radius
        assert radius is not None, "cross_section must have a radius"

    bend90_inner_right = gf.get_component(
        bend,  # type: ignore[arg-type]
        radius=radius,
        cross_section=cross_section_inner,
        angle=angle_inner,
    )
    bend_output_right = gf.get_component(
        bend,  # type: ignore[arg-type]
        radius=radius + spacing,
        cross_section=cross_section_outer,
        angle=angle_outer,
    )
    bend_inner_ref = c.add_ref_off_grid(bend90_inner_right)
    bend_output_ref = c.add_ref_off_grid(bend_output_right)

    output = gf.get_component(
        bend_output, angle=angle_outer, cross_section=cross_section_outer
    )
    output_ref = c.add_ref_off_grid(output)
    output_ref.connect("o1", bend_output_ref.ports["o2"], mirror=True)

    pbw = bend_inner_ref.ports["o1"]
    bend_inner_ref.movey(pbw.center[1] + spacing)

    c.add_port("o1", port=bend_output_ref.ports["o1"])
    c.add_port("o2", port=bend_inner_ref.ports["o1"])
    c.add_port("o3", port=output_ref.ports["o2"])
    c.add_port("o4", port=bend_inner_ref.ports["o2"])
    return c

coupler_ring_bend

coupler_ring_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 90.0,
    length_x: float = 0.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
) -> Component

Two back-to-back coupler_bend.

Parameters:

Name Type Description Default
radius float | None

um. Default is None, which uses the default radius of the cross_section.

None
coupler_gap float

um.

0.2
angle_inner

of the inner bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.

required
angle_outer

of the outer bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.

required
coupling_angle_coverage float

degrees.

90.0
length_x float

horizontal straight length.

0.0
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

'strip'
bend AnyComponentFactory

for bend.

bend_circular_all_angle
bend_output ComponentSpec

for bend.

'bend_euler'
straight ComponentSpec

for straight.

'straight'
Source code in gdsfactory/components/rings/ring_single_bend_coupler.py
 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
@gf.cell_with_module_name(schematic_function=coupler_ring_schematic, tags=["rings"])
def coupler_ring_bend(
    radius: float | None = None,
    coupler_gap: float = 0.2,
    coupling_angle_coverage: float = 90.0,
    length_x: float = 0.0,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    bend: AnyComponentFactory = bend_circular_all_angle,
    bend_output: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
) -> Component:
    r"""Two back-to-back coupler_bend.

    Args:
        radius: um. Default is None, which uses the default radius of the cross_section.
        coupler_gap: um.
        angle_inner: of the inner bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.
        angle_outer: of the outer bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.
        coupling_angle_coverage: degrees.
        length_x: horizontal straight length.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.
        bend: for bend.
        bend_output: for bend.
        straight: for straight.
    """
    c = Component()
    cp = coupler_bend(
        radius=radius,
        coupler_gap=coupler_gap,
        coupling_angle_coverage=coupling_angle_coverage,
        cross_section_inner=cross_section_inner,
        cross_section_outer=cross_section_outer,
        bend=bend,
        bend_output=bend_output,
    )
    sin = gf.get_component(straight, length=length_x, cross_section=cross_section_inner)
    sout = gf.get_component(
        straight, length=length_x, cross_section=cross_section_outer
    )

    coupler_right = c << cp
    coupler_left = c << cp
    straight_inner = c << sin
    straight_inner.movex(-length_x / 2)
    straight_outer = c << sout
    straight_outer.movex(-length_x / 2)

    coupler_left.connect("o1", straight_outer.ports["o1"])
    straight_inner.connect("o1", coupler_left.ports["o2"])
    coupler_right.connect("o2", straight_inner.ports["o2"], mirror=True)
    straight_outer.connect("o2", coupler_right.ports["o1"])

    c.add_port("o1", port=coupler_left.ports["o3"])
    c.add_port("o2", port=coupler_left.ports["o4"])
    c.add_port("o4", port=coupler_right.ports["o3"])
    c.add_port("o3", port=coupler_right.ports["o4"])
    # c.flatten()
    return c

ring_single_bend_coupler

ring_single_bend_coupler(
    radius: float = 5.0,
    gap: float = 0.2,
    coupling_angle_coverage: float = 180.0,
    bend_all_angle: AnyComponentFactory = bend_circular_all_angle,
    bend: ComponentSpec = "bend_circular",
    bend_output: ComponentSpec = "bend_euler",
    length_x: float = 0.6,
    length_y: float = 0.6,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    **kwargs: Any
) -> Component

Returns ring with curved coupler.

TODO: enable euler bends.

Parameters:

Name Type Description Default
radius float

um.

5.0
gap float

um.

0.2
coupling_angle_coverage float

degrees.

180.0
angle_inner

of the inner bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.

required
angle_outer

of the outer bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.

required
bend_all_angle AnyComponentFactory

for bend.

bend_circular_all_angle
bend ComponentSpec

for bend.

'bend_circular'
bend_output ComponentSpec

for bend.

'bend_euler'
length_x float

horizontal straight length.

0.6
length_y float

vertical straight length.

0.6
cross_section_inner CrossSectionSpec

spec inner bend.

'strip'
cross_section_outer CrossSectionSpec

spec outer bend.

'strip'
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/rings/ring_single_bend_coupler.py
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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def ring_single_bend_coupler(
    radius: float = 5.0,
    gap: float = 0.2,
    coupling_angle_coverage: float = 180.0,
    bend_all_angle: AnyComponentFactory = bend_circular_all_angle,
    bend: ComponentSpec = "bend_circular",
    bend_output: ComponentSpec = "bend_euler",
    length_x: float = 0.6,
    length_y: float = 0.6,
    cross_section_inner: CrossSectionSpec = "strip",
    cross_section_outer: CrossSectionSpec = "strip",
    **kwargs: Any,
) -> Component:
    r"""Returns ring with curved coupler.

    TODO: enable euler bends.

    Args:
        radius: um.
        gap: um.
        coupling_angle_coverage: degrees.
        angle_inner: of the inner bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.
        angle_outer: of the outer bend, from beginning to end. Depending on the bend chosen, gap may not be preserved.
        bend_all_angle: for bend.
        bend: for bend.
        bend_output: for bend.
        length_x: horizontal straight length.
        length_y: vertical straight length.
        cross_section_inner: spec inner bend.
        cross_section_outer: spec outer bend.
        kwargs: cross_section settings.
    """
    c = Component()

    coupler = coupler_ring_bend(
        radius=radius,
        coupler_gap=gap,
        coupling_angle_coverage=coupling_angle_coverage,
        length_x=length_x,
        cross_section_inner=cross_section_inner,
        cross_section_outer=cross_section_outer,
        bend=bend_all_angle,
        bend_output=bend_output,
    )
    cb = c << coupler

    cross_section = cross_section_inner
    straight = gf.c.straight
    sx = gf.get_component(
        straight, length=length_x, cross_section=cross_section, **kwargs
    )
    sy = gf.get_component(
        straight, length=length_y, cross_section=cross_section, **kwargs
    )
    b = gf.get_component(bend, cross_section=cross_section, radius=radius, **kwargs)
    sl = c << sy
    sr = c << sy
    bl = c << b
    br = c << b
    st = c << sx

    sl.connect(port="o1", other=cb["o2"])
    bl.connect(port="o2", other=sl["o2"], mirror=True)
    st.connect(port="o2", other=bl["o1"])
    sr.connect(port="o1", other=br["o1"])
    sr.connect(port="o2", other=cb["o3"])
    br.connect(port="o2", other=st["o1"], mirror=True)

    c.add_port("o2", port=cb["o4"])
    c.add_port("o1", port=cb["o1"])
    c.flatten()
    return c

ring_single_bend_coupler

ring_single_dut

ring_single_dut

ring_single_dut(
    component: ComponentSpec = "straight",
    gap: float = 0.2,
    length_x: float = 4,
    length_y: float = 0,
    radius: float | None = None,
    coupler: ComponentSpec = "coupler_ring",
    bend: ComponentSpec = "bend_euler",
    with_component: bool = True,
    port_name: str = "o1",
    length_extension: float | None = None,
    **kwargs: Any
) -> Component

Single bus ring made of two couplers (ct: top, cb: bottom) connected.

with two vertical straights (wyl: left, wyr: right) (Component Under Test) in the middle to extract loss from quality factor.

Parameters:

Name Type Description Default
component ComponentSpec

device under test.

'straight'
gap float

in um.

0.2
length_x float

in um.

4
length_y float

in um.

0
radius float | None

in um. Default is None, which uses the default radius of the cross_section.

None
coupler ComponentSpec

coupler function.

'coupler_ring'
bend ComponentSpec

bend function.

'bend_euler'
with_component bool

True adds component. False adds waveguide.

True
port_name str

for component input.

'o1'
length_extension float | None

optional length extension for the coupler bottom ports.

None
kwargs Any

cross_section settings.

{}

Parameters:

Name Type Description Default
with_component bool

if False changes component for just a straight.

bl-wt-br | | length_y wl component | | --==cb==-- gap

length_x

True
Source code in gdsfactory/components/rings/ring_single_dut.py
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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def ring_single_dut(
    component: ComponentSpec = "straight",
    gap: float = 0.2,
    length_x: float = 4,
    length_y: float = 0,
    radius: float | None = None,
    coupler: ComponentSpec = "coupler_ring",
    bend: ComponentSpec = "bend_euler",
    with_component: bool = True,
    port_name: str = "o1",
    length_extension: float | None = None,
    **kwargs: Any,
) -> Component:
    """Single bus ring made of two couplers (ct: top, cb: bottom) connected.

    with two vertical straights (wyl: left, wyr: right) (Component Under Test) in
    the middle to extract loss from quality factor.

    Args:
        component: device under test.
        gap: in um.
        length_x: in um.
        length_y: in um.
        radius: in um. Default is None, which uses the default radius of the cross_section.
        coupler: coupler function.
        bend: bend function.
        with_component: True adds component. False adds waveguide.
        port_name: for component input.
        length_extension: optional length extension for the coupler bottom ports.
        kwargs: cross_section settings.

    Args:
        with_component: if False changes component for just a straight.

          bl-wt-br
          |      | length_y
          wl     component
          |      |
         --==cb==-- gap

          length_x
    """
    component = gf.get_component(component)
    assert_on_2x_grid(gap)

    coupler = gf.get_component(
        coupler,
        gap=gap,
        length_x=length_x,
        radius=radius,
        length_extension=length_extension,
        **kwargs,
    )

    component_xsize = component.xsize
    straight_side = gf.c.straight(length=length_y + component_xsize, **kwargs)
    straight_top = gf.c.straight(length=length_x, **kwargs)
    bend = gf.get_component(bend, radius=radius, **kwargs)

    c = Component()
    cb = c << coupler
    wl = c << straight_side
    dut = c << component if with_component else c << straight_side
    bl = c << bend
    br = c << bend
    wt = c << straight_top

    wl.connect(port="o2", other=cb.ports["o2"])
    bl.connect(port="o2", other=wl.ports["o1"])

    wt.connect(port="o1", other=bl.ports["o1"])
    br.connect(port="o2", other=wt.ports["o2"])
    dut.connect(port=port_name, other=br.ports["o1"])

    c.add_port("o2", port=cb.ports["o4"])
    c.add_port("o1", port=cb.ports["o1"])
    return c

ring_single_dut

ring_single_heater module-attribute

ring_single_heater = partial(
    ring_double_heater, with_drop=False
)

ring_single_heater

ring_single_pn

ring_single_pn(
    gap: float = 0.3,
    radius: float = 5.0,
    doping_angle: float = 250,
    cross_section: CrossSectionSpec = rib,
    pn_cross_section: CrossSectionSpec = cross_section_pn,
    doped_heater: bool = True,
    doped_heater_angle_buffer: float = 10,
    doped_heater_layer: LayerSpec = "NPP",
    doped_heater_width: float = 0.5,
    doped_heater_waveguide_offset: float = 1.175,
    heater_vias: ComponentSpec = _heater_vias,
    pn_vias: ComponentSpec = "via_stack_slab_m3",
    pn_vias_width: float = 3,
) -> gf.Component

Returns single pn ring with optional doped heater.

Parameters:

Name Type Description Default
gap float

gap between for coupler.

0.3
radius float

for the bend and coupler.

5.0
doping_angle float

angle in degrees representing portion of ring that is doped.

250
length_x

ring coupler length.

required
length_y

vertical straight length.

required
cross_section CrossSectionSpec

cross_section spec for non-PN doped rib waveguide sections.

rib
pn_cross_section CrossSectionSpec

cross section of pn junction.

cross_section_pn
doped_heater bool

boolean for if we include doped heater or not.

True
doped_heater_angle_buffer float

angle in degrees buffering heater from pn junction.

10
doped_heater_layer LayerSpec

doping layer for heater.

'NPP'
doped_heater_width float

width of doped heater.

0.5
doped_heater_waveguide_offset float

distance from the center of the ring waveguide to the center of the doped heater.

1.175
heater_vias ComponentSpec

components specifications for heater vias.

_heater_vias
pn_vias ComponentSpec

components specifications for pn vias.

'via_stack_slab_m3'
pn_vias_width float

width of pn vias.

3
Source code in gdsfactory/components/rings/ring_pn.py
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
@gf.cell_with_module_name(schematic_function=ring_single_schematic, tags=["rings"])
def ring_single_pn(
    gap: float = 0.3,
    radius: float = 5.0,
    doping_angle: float = 250,
    cross_section: CrossSectionSpec = rib,
    pn_cross_section: CrossSectionSpec = cross_section_pn,
    doped_heater: bool = True,
    doped_heater_angle_buffer: float = 10,
    doped_heater_layer: LayerSpec = "NPP",
    doped_heater_width: float = 0.5,
    doped_heater_waveguide_offset: float = 1.175,
    heater_vias: ComponentSpec = _heater_vias,
    pn_vias: ComponentSpec = "via_stack_slab_m3",
    pn_vias_width: float = 3,
) -> gf.Component:
    """Returns single pn ring with optional doped heater.

    Args:
        gap: gap between for coupler.
        radius: for the bend and coupler.
        doping_angle: angle in degrees representing portion of ring that is doped.
        length_x: ring coupler length.
        length_y: vertical straight length.
        cross_section: cross_section spec for non-PN doped rib waveguide sections.
        pn_cross_section: cross section of pn junction.
        doped_heater: boolean for if we include doped heater or not.
        doped_heater_angle_buffer: angle in degrees buffering heater from pn junction.
        doped_heater_layer: doping layer for heater.
        doped_heater_width: width of doped heater.
        doped_heater_waveguide_offset: distance from the center of the ring waveguide to the center of the doped heater.
        heater_vias: components specifications for heater vias.
        pn_vias: components specifications for pn vias.
        pn_vias_width: width of pn vias.
    """
    gap = gf.snap.snap_to_grid(gap, grid_factor=2)
    c = gf.Component()

    undoping_angle = 360 - doping_angle

    pn_xs = gf.get_cross_section(pn_cross_section)
    bus_waveguide_path = gf.Path()
    bus_waveguide_path.append(
        gf.path.straight(length=2 * radius * np.sin(np.pi / 360 * undoping_angle))
    )
    bus_waveguide = c << bus_waveguide_path.extrude(cross_section=cross_section)
    bus_waveguide.x = 0
    bus_waveguide.y = (
        -radius
        - gap
        - bus_waveguide.ports["o1"].width / 2
        - pn_xs.width / 2
        + 0.576  # adjust gap # TODO: remove this
    )

    r = gf.Component()
    doped_path = gf.Path()
    doped_path.append(gf.path.arc(radius=radius, angle=-doping_angle))
    undoped_path = gf.Path()
    undoped_path.append(gf.path.arc(radius=radius, angle=undoping_angle))

    doped_ring_ref = r << doped_path.extrude(cross_section=pn_xs, all_angle=False)
    undoped_ring_ref = r << undoped_path.extrude(
        cross_section=cross_section, all_angle=False
    )
    undoped_ring_ref.rotate(-undoping_angle / 2)
    undoped_ring_ref.center = (0, 0)
    doped_ring_ref.connect("o1", undoped_ring_ref.ports["o1"])

    via = gf.get_component(pn_vias, size=(pn_vias_width, pn_vias_width))
    gnd = r << via
    gnd.x = doped_ring_ref.ports["e1_top"].x
    gnd.y = doped_ring_ref.ports["e1_top"].y

    sig = r << via
    sig.x = doped_ring_ref.ports["e2_bot"].x
    sig.y = doped_ring_ref.ports["e2_bot"].y
    r.add_port("sig", port=sig["e2"])
    r.add_port("gnd", port=gnd["e2"])

    ring = c << r
    ring.center = (0, 0)

    if doped_heater:
        heater_radius = radius - doped_heater_waveguide_offset
        heater_path = gf.Path()
        heater_path.append(
            gf.path.arc(
                radius=heater_radius, angle=undoping_angle - doped_heater_angle_buffer
            )
        )

        bottom_heater_ref = c << heater_path.extrude(
            width=0.5, layer=doped_heater_layer
        )
        bottom_heater_ref.rotate(-(undoping_angle - doped_heater_angle_buffer) / 2)
        bottom_heater_ref.x = bus_waveguide.x
        bottom_heater_ref.y = (
            bus_waveguide.y
            + doped_heater_waveguide_offset
            + doped_heater_width / 2
            + gap
            + radius / 4
        )

        heater_vias = gf.get_component(heater_vias)

        bottom_l_heater_via = c << heater_vias
        bottom_r_heater_via = c << heater_vias
        bottom_l_heater_via.xmin = bottom_heater_ref.ports["o1"].x
        bottom_l_heater_via.ymax = bottom_heater_ref.ports["o1"].y

        bottom_r_heater_via.xmax = bottom_heater_ref.ports["o2"].x
        bottom_r_heater_via.ymax = bottom_heater_ref.ports["o2"].y

        c.add_port(name="heater_sig", port=bottom_l_heater_via["e4"])
        c.add_port(name="heater_gnd", port=bottom_r_heater_via["e4"])

    c.add_port("o1", port=bus_waveguide.ports["o1"])
    c.add_port("o2", port=bus_waveguide.ports["o2"])
    c.add_ports(ring.ports)

    elec_ports = [p for p in c.ports if p.name and p.port_type == "electrical"]
    for p in elec_ports:
        c.create_pin(ports=[p], name=p.name)

    c.flatten()
    return c

ring_single_pn

shapes

C

C

C(
    width: float = 1.0,
    size: Size = (10.0, 20.0),
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component

C geometry with ports on both ends.

based on phidl.

Parameters:

Name Type Description Default
width float

of the line.

1.0
size Size

length and height of the base.

(10.0, 20.0)
layer LayerSpec

layer spec.

'WG'
port_type str

optical or electrical.


'electrical'
Source code in gdsfactory/components/shapes/C.py
10
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
@gf.cell_with_module_name(tags=["shapes"])
def C(
    width: float = 1.0,
    size: Size = (10.0, 20.0),
    layer: LayerSpec = "WG",
    port_type: str = "electrical",
) -> Component:
    """C geometry with ports on both ends.

    based on phidl.

    Args:
        width: of the line.
        size: length and height of the base.
        layer: layer spec.
        port_type: optical or electrical.

         ______
        |       o1
        |   ___
        |  |
        |  |___
        ||<---> size[0]
        |______ o2
    """
    layer = gf.get_layer(layer)
    c = Component()
    w = width / 2
    s1, s2 = size
    points = [
        (-w, -w),
        (s1, -w),
        (s1, w),
        (w, w),
        (w, s2 - w),
        (s1, s2 - w),
        (s1, s2 + w),
        (-w, s2 + w),
        (-w, -w),
    ]
    c.add_polygon(points, layer=layer)

    for name, center in (("o1", (s1, s2)), ("o2", (s1, 0))):
        c.add_port(
            name=name,
            center=center,
            width=width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
    if port_type == "electrical":
        for port in c.ports:
            c.create_pin(ports=[port], name=port.name)
    return c

C

L

L

L(
    width: int | float = 1,
    size: tuple[int, int] = (10, 20),
    layer: LayerSpec = "MTOP",
    port_type: str = "electrical",
) -> Component

Generates an 'L' geometry with ports on both ends.

Based on phidl.

Parameters:

Name Type Description Default
width int | float

of the line.

1
size tuple[int, int]

length and height of the base.

(10, 20)
layer LayerSpec

spec.

'MTOP'
port_type str

for port.

'electrical'
Source code in gdsfactory/components/shapes/L.py
10
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
@gf.cell_with_module_name(tags=["shapes"])
def L(
    width: int | float = 1,
    size: tuple[int, int] = (10, 20),
    layer: LayerSpec = "MTOP",
    port_type: str = "electrical",
) -> Component:
    """Generates an 'L' geometry with ports on both ends.

    Based on phidl.

    Args:
        width: of the line.
        size: length and height of the base.
        layer: spec.
        port_type: for port.
    """
    D = Component()
    w = width / 2
    s1, s2 = size
    points = [(-w, -w), (s1, -w), (s1, w), (w, w), (w, s2), (-w, s2), (-w, -w)]
    D.add_polygon(points, layer=layer)
    D.add_port(
        name="e1",
        center=(0, s2),
        width=width,
        orientation=90,
        port_type=port_type,
        layer=layer,
    )
    D.add_port(
        name="e2",
        center=(s1, 0),
        width=width,
        orientation=0,
        port_type=port_type,
        layer=layer,
    )
    if port_type == "electrical":
        for port in D.ports:
            D.create_pin(ports=[port], name=port.name)
    return D

L

circle

circle

circle(
    radius: float = 10.0,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
) -> Component

Generate a circle geometry.

Parameters:

Name Type Description Default
radius float

of the circle.

10.0
angle_resolution float

number of degrees per point.

2.5
layer LayerSpec

layer.

'WG'
Source code in gdsfactory/components/shapes/circle.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@gf.cell_with_module_name(tags=["shapes"])
def circle(
    radius: float = 10.0,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
) -> Component:
    """Generate a circle geometry.

    Args:
        radius: of the circle.
        angle_resolution: number of degrees per point.
        layer: layer.
    """
    if radius <= 0:
        raise ValueError(f"radius={radius} must be > 0")
    c = Component()
    num_points = int(np.round(360.0 / angle_resolution)) + 1
    theta = np.deg2rad(np.linspace(0, 360, num_points, endpoint=True))
    points = np.stack((radius * np.cos(theta), radius * np.sin(theta)), axis=-1)
    c.add_polygon(points=points, layer=layer)
    return c

circle

circle_wave

circle_wave

circle_wave(
    radius: float = 10.0,
    amplitude: float = 1.0,
    n_oscillations: int = 8,
    angle_resolution: float = 1.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a circle with a sinusoidal boundary variation.

The boundary radius varies as r(theta) = radius + amplitude * sin(n * theta).

Parameters:

Name Type Description Default
radius float

mean radius.

10.0
amplitude float

amplitude of sinusoidal modulation.

1.0
n_oscillations int

number of oscillations around the boundary.

8
angle_resolution float

degrees per point.

1.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/shapes/circle_wave.py
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
@gf.cell_with_module_name(tags=["shapes"])
def circle_wave(
    radius: float = 10.0,
    amplitude: float = 1.0,
    n_oscillations: int = 8,
    angle_resolution: float = 1.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a circle with a sinusoidal boundary variation.

    The boundary radius varies as r(theta) = radius + amplitude * sin(n * theta).

    Args:
        radius: mean radius.
        amplitude: amplitude of sinusoidal modulation.
        n_oscillations: number of oscillations around the boundary.
        angle_resolution: degrees per point.
        layer: layer spec.
    """
    c = Component()
    n_points = int(np.round(360.0 / angle_resolution)) + 1
    theta = np.linspace(0, 2 * np.pi, n_points, endpoint=True)
    r = radius + amplitude * np.sin(n_oscillations * theta)
    points = np.stack((r * np.cos(theta), r * np.sin(theta)), axis=-1)
    c.add_polygon(points=points, layer=layer)
    return c

circle_wave

compass

compass

compass(
    size: Size = (4.0, 2.0),
    layer: LayerSpec = "WG",
    port_type: str | None = "electrical",
    port_inclusion: float = 0.0,
    port_orientations: Ints | None = (180, 90, 0, -90),
    auto_rename_ports: bool = True,
) -> Component

Rectangle with ports on each edge (north, south, east, and west).

Parameters:

Name Type Description Default
size Size

rectangle size.

(4.0, 2.0)
layer LayerSpec

tuple (int, int).

'WG'
port_type str | None

optical, electrical.

'electrical'
port_inclusion float

from edge.

0.0
port_orientations Ints | None

list of port_orientations to add. None does not add ports.

(180, 90, 0, -90)
auto_rename_ports bool

auto rename ports.

True
Source code in gdsfactory/components/shapes/compass.py
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
96
97
98
99
@gf.cell_with_module_name(tags=["shapes"])
def compass(
    size: Size = (4.0, 2.0),
    layer: LayerSpec = "WG",
    port_type: str | None = "electrical",
    port_inclusion: float = 0.0,
    port_orientations: Ints | None = (180, 90, 0, -90),
    auto_rename_ports: bool = True,
) -> Component:
    """Rectangle with ports on each edge (north, south, east, and west).

    Args:
        size: rectangle size.
        layer: tuple (int, int).
        port_type: optical, electrical.
        port_inclusion: from edge.
        port_orientations: list of port_orientations to add. None does not add ports.
        auto_rename_ports: auto rename ports.
    """
    c = gf.Component()
    _temp = snap_to_grid2x(size)
    dx = float(_temp[0])
    dy = float(_temp[1])
    port_orientations_list = port_orientations if port_orientations is not None else []

    if dx <= 0 or dy <= 0:
        raise ValueError(f"dx={dx} and dy={dy} must be > 0")

    points = [
        (-dx / 2.0, -dy / 2.0),
        (-dx / 2.0, dy / 2),
        (dx / 2, dy / 2),
        (dx / 2, -dy / 2.0),
    ]

    c.add_polygon(points, layer=layer)

    if port_type:
        for port_orientation in port_orientations_list:
            if port_orientation not in valid_port_orientations:
                raise ValueError(
                    f"{port_orientation=} must be in {valid_port_orientations}"
                )

        if 180 in port_orientations_list:
            c.add_port(
                name="e1",
                center=(-dx / 2 + port_inclusion, 0),
                width=dy,
                orientation=180,
                layer=layer,
                port_type=port_type,
            )
        if 90 in port_orientations_list:
            c.add_port(
                name="e2",
                center=(0, dy / 2 - port_inclusion),
                width=dx,
                orientation=90,
                layer=layer,
                port_type=port_type,
            )
        if 0 in port_orientations_list:
            c.add_port(
                name="e3",
                center=(dx / 2 - port_inclusion, 0),
                width=dy,
                orientation=0,
                layer=layer,
                port_type=port_type,
            )
        if -90 in port_orientations_list or 270 in port_orientations_list:
            c.add_port(
                name="e4",
                center=(0, -dy / 2 + port_inclusion),
                width=dx,
                orientation=-90,
                layer=layer,
                port_type=port_type,
            )

        if auto_rename_ports:
            c.auto_rename_ports()
        if port_type == "electrical":
            elec = [p for p in c.ports if p.port_type == "electrical"]
            if elec:
                c.create_pin(ports=elec, name="pad")
    return c

compass

cross

cross

cross(
    length: float = 10.0,
    width: float = 3.0,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component

Returns a cross from two rectangles of length and width.

Parameters:

Name Type Description Default
length float

float Length of the cross from one end to the other.

10.0
width float

float Width of the arms of the cross.

3.0
layer LayerSpec

layer for geometry.

'WG'
port_type str | None

None, optical, electrical.

None
Source code in gdsfactory/components/shapes/cross.py
10
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
@gf.cell_with_module_name(tags=["shapes"])
def cross(
    length: float = 10.0,
    width: float = 3.0,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component:
    """Returns a cross from two rectangles of length and width.

    Args:
        length: float Length of the cross from one end to the other.
        width: float Width of the arms of the cross.
        layer: layer for geometry.
        port_type: None, optical, electrical.
    """
    layer = gf.get_layer(layer)
    c = gf.Component()
    R = gf.components.rectangle(size=(width, length), layer=layer)
    r1 = c.add_ref(R).rotate(90)
    r2 = c.add_ref(R)
    r1.center = (0, 0)
    r2.center = (0, 0)
    c.flatten()

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            width=width,
            layer=layer,
            orientation=0,
            center=(+length / 2, 0),
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            width=width,
            layer=layer,
            orientation=180,
            center=(-length / 2, 0),
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}3",
            width=width,
            layer=layer,
            orientation=90,
            center=(0, length / 2),
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}4",
            width=width,
            layer=layer,
            orientation=270,
            center=(0, -length / 2),
            port_type=port_type,
        )
        c.auto_rename_ports()
    if port_type == "electrical":
        elec = [p for p in c.ports if p.port_type == "electrical"]
        if elec:
            c.create_pin(ports=elec, name="pad")
    return c

cross

dash

dash

dash(
    width: float = 10.0,
    width_end: float = 1.0,
    length: float = 20.0,
    taper_length: float = 5.0,
    tip_length: float = 2.0,
    n_bezier_points: int = 30,
    layer: LayerSpec = "WG",
) -> Component

Returns a dash shape with Bezier-curved tapered tips.

An elongated shape wider in the middle (width) that tapers via Bezier curves to a narrower tip (width_end) at each end. Based on the pyNISTtoolbox Dash pattern.

Parameters:

Name Type Description Default
width float

width at the center/body of the dash.

10.0
width_end float

width at the tips.

1.0
length float

total length of the straight body section.

20.0
taper_length float

length of each tapered transition.

5.0
tip_length float

length of each rounded tip beyond the taper.

2.0
n_bezier_points int

points per Bezier curve segment.

30
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/shapes/dash.py
 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
@gf.cell_with_module_name(tags=["shapes"])
def dash(
    width: float = 10.0,
    width_end: float = 1.0,
    length: float = 20.0,
    taper_length: float = 5.0,
    tip_length: float = 2.0,
    n_bezier_points: int = 30,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a dash shape with Bezier-curved tapered tips.

    An elongated shape wider in the middle (width) that tapers via
    Bezier curves to a narrower tip (width_end) at each end. Based on
    the pyNISTtoolbox Dash pattern.

    Args:
        width: width at the center/body of the dash.
        width_end: width at the tips.
        length: total length of the straight body section.
        taper_length: length of each tapered transition.
        tip_length: length of each rounded tip beyond the taper.
        n_bezier_points: points per Bezier curve segment.
        layer: layer spec.
    """
    c = Component()
    W = width
    Wend = width_end
    L = length
    Ltap = taper_length
    Lt = tip_length

    alpha = np.pi / 2 - np.arctan((W - Wend) / (2 * Ltap))

    # Top tip
    pxup = Lt * np.cos(alpha)
    pyup = Lt * np.sin(alpha)
    px = [-Wend / 2, -Wend / 2 + pxup, Wend / 2 - pxup, Wend / 2]
    py = [L / 2 + Ltap, L / 2 + Ltap + pyup, L / 2 + Ltap + pyup, L / 2 + Ltap]
    xtip_t, ytip_t = _bezier3(px, py, n_bezier_points)

    # Left upper taper
    px = [-W / 2, -W / 2, -W / 2, -Wend / 2]
    py = [L / 3, L / 2, L / 2, L / 2 + Ltap]
    xangle_lu, yangle_lu = _bezier3(px, py, n_bezier_points)

    # Right upper taper
    px = [Wend / 2, W / 2, W / 2, W / 2]
    py = [L / 2 + Ltap, L / 2, L / 2, L / 3]
    xangle_ru, yangle_ru = _bezier3(px, py, n_bezier_points)

    # Right lower taper
    px = [W / 2, W / 2, W / 2, Wend / 2]
    py = [-L / 3, -L / 2, -L / 2, -L / 2 - Ltap]
    xangle_rb, yangle_rb = _bezier3(px, py, n_bezier_points)

    # Bottom tip
    px = [Wend / 2, Wend / 2 - pxup, -Wend / 2 + pxup, -Wend / 2]
    py = [-L / 2 - Ltap, -L / 2 - Ltap - pyup, -L / 2 - Ltap - pyup, -L / 2 - Ltap]
    xtip_b, ytip_b = _bezier3(px, py, n_bezier_points)

    # Left lower taper
    px = [-Wend / 2, -W / 2, -W / 2, -W / 2]
    py = [-L / 2 - Ltap, -L / 2, -L / 2, -L / 3]
    xangle_lb, yangle_lb = _bezier3(px, py, n_bezier_points)

    # Assemble outline
    xdash = xangle_lu + xtip_t + xangle_ru + xangle_rb + xtip_b + xangle_lb
    ydash = yangle_lu + ytip_t + yangle_ru + yangle_rb + ytip_b + yangle_lb

    points = list(zip(xdash, ydash, strict=False))
    c.add_polygon(points, layer=layer)
    return c

dash

ellipse

ellipse

ellipse(
    radii: tuple[float, float] = (10.0, 5.0),
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
) -> Component

Returns ellipse component.

Parameters:

Name Type Description Default
radii tuple[float, float]

Semimajor and semiminor axis lengths of the ellipse.

(10.0, 5.0)
angle_resolution float

number of degrees per point.

2.5
layer LayerSpec

Specific layer(s) to put polygon geometry on.

'WG'

The orientation of the ellipse is determined by the order of the radii variables; if the first element is larger, the ellipse will be horizontal and if the second element is larger, the ellipse will be vertical.

Source code in gdsfactory/components/shapes/ellipse.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
@gf.cell_with_module_name(tags=["shapes"])
def ellipse(
    radii: tuple[float, float] = (10.0, 5.0),
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns ellipse component.

    Args:
        radii: Semimajor and semiminor axis lengths of the ellipse.
        angle_resolution: number of degrees per point.
        layer: Specific layer(s) to put polygon geometry on.

    The orientation of the ellipse is determined by the order of the radii variables;
    if the first element is larger, the ellipse will be horizontal and if the second
    element is larger, the ellipse will be vertical.
    """
    c = gf.Component()
    a = radii[0]
    b = radii[1]
    t = np.linspace(0, 360, int(360 / angle_resolution) + 1) * pi / 180
    r = a * b / (sqrt((b * cos(t)) ** 2 + (a * sin(t)) ** 2))
    xpts = r * cos(t)
    ypts = r * sin(t)
    c.add_polygon(points=list(zip(xpts, ypts, strict=False)), layer=layer)
    return c

ellipse

fiducial_squares

fiducial_squares

fiducial_squares(
    layers: LayerSpecs = ("WG",),
    size: Float2 = (5, 5),
    offset: float = 0.14,
) -> gf.Component

Returns fiducials with two squares.

Parameters:

Name Type Description Default
layers LayerSpecs

list of layers to draw the squares.

('WG',)
size Float2

size of each square in um.

(5, 5)
offset float

space between squares in x and y.

0.14
Source code in gdsfactory/components/shapes/fiducial_squares.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
@gf.cell_with_module_name(tags=["shapes"])
def fiducial_squares(
    layers: LayerSpecs = ("WG",), size: Float2 = (5, 5), offset: float = 0.14
) -> gf.Component:
    """Returns fiducials with two squares.

    Args:
        layers: list of layers to draw the squares.
        size: size of each square in um.
        offset: space between squares in x and y.
    """
    c = gf.Component()

    dx, dy = (np.array(size) + np.array([offset, offset])) / 2

    for layer in layers:
        r = c << gf.c.rectangle(size=size, layer=layer, centered=True)
        r.move((dx, dy))

    for layer in layers:
        r = c << gf.c.rectangle(size=size, layer=layer, centered=True)
        r.move((-dx, -dy))

    return c

fiducial_squares

fractal

fractal

fractal(
    fractal_type: Literal[
        "sierpinski_triangle",
        "sierpinski_carpet",
        "vicsek_cross",
        "vicsek_saltire",
    ] = "sierpinski_triangle",
    depth: int = 4,
    size: float = 100.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a fractal pattern.

Parameters:

Name Type Description Default
fractal_type Literal['sierpinski_triangle', 'sierpinski_carpet', 'vicsek_cross', 'vicsek_saltire']

type of fractal.

'sierpinski_triangle'
depth int

recursion depth (max recommended: 6).

4
size float

overall size of the fractal.

100.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/shapes/fractal.py
 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
@gf.cell_with_module_name(tags=["shapes"])
def fractal(
    fractal_type: Literal[
        "sierpinski_triangle",
        "sierpinski_carpet",
        "vicsek_cross",
        "vicsek_saltire",
    ] = "sierpinski_triangle",
    depth: int = 4,
    size: float = 100.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a fractal pattern.

    Args:
        fractal_type: type of fractal.
        depth: recursion depth (max recommended: 6).
        size: overall size of the fractal.
        layer: layer spec.
    """
    if depth > 6:
        raise ValueError(f"depth={depth} too large, max 6 recommended")

    generators = {
        "sierpinski_triangle": _sierpinski_triangle,
        "sierpinski_carpet": _sierpinski_carpet,
        "vicsek_cross": _vicsek_cross,
        "vicsek_saltire": _vicsek_saltire,
    }

    c = Component()
    polygons = generators[fractal_type](depth, size)
    for poly in polygons:
        c.add_polygon(poly, layer=layer)
    return c

fractal

hexagon module-attribute

hexagon = partial(regular_polygon, sides=6)

hexagon

marker_te module-attribute

marker_te = partial(
    rectangle,
    size=(fiber_size, fiber_size),
    layer="TE",
    centered=True,
)

marker_te

marker_tm module-attribute

marker_tm = partial(
    rectangle,
    size=(fiber_size, fiber_size),
    layer="TM",
    centered=True,
)

marker_tm

nxn

nxn

nxn(
    west: int = 1,
    east: int = 4,
    north: int = 0,
    south: int = 0,
    xsize: float = 8.0,
    ysize: float = 8.0,
    wg_width: float = 0.5,
    layer: LayerSpec = "WG",
    wg_margin: float = 1.0,
    **kwargs: Any
) -> Component

Returns a nxn component with nxn ports (west, east, north, south).

Parameters:

Name Type Description Default
west int

number of west ports.

1
east int

number of east ports.

4
north int

number of north ports.

0
south int

number of south ports.

0
xsize float

size in X.

8.0
ysize float

size in Y.

8.0
wg_width float

width of the straight ports.

0.5
layer LayerSpec

layer.

'WG'
wg_margin float

margin from straight to component edge.

1.0
kwargs Any

port_settings.

3 4 |___|_

{}
Source code in gdsfactory/components/shapes/nxn.py
 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
 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
@gf.cell_with_module_name(tags=["shapes"])
def nxn(
    west: int = 1,
    east: int = 4,
    north: int = 0,
    south: int = 0,
    xsize: float = 8.0,
    ysize: float = 8.0,
    wg_width: float = 0.5,
    layer: LayerSpec = "WG",
    wg_margin: float = 1.0,
    **kwargs: Any,
) -> Component:
    """Returns a nxn component with nxn ports (west, east, north, south).

    Args:
        west: number of west ports.
        east: number of east ports.
        north: number of north ports.
        south: number of south ports.
        xsize: size in X.
        ysize: size in Y.
        wg_width: width of the straight ports.
        layer: layer.
        wg_margin: margin from straight to component edge.
        kwargs: port_settings.

            3   4
            |___|_
        2 -|      |- 5
           |      |
        1 -|______|- 6
            |   |
            8   7
    """
    c = gf.Component()
    _ = c << gf.components.rectangle(size=(xsize, ysize), layer=layer)

    if west > 0:
        x_west = 0
        y_west = (
            [ysize / 2]
            if west == 1
            else list(
                np.linspace(
                    wg_margin + wg_width / 2, ysize - wg_margin - wg_width / 2, west
                )
            )
        )
        orientation = 180

        for i, yi in enumerate(y_west):
            c.add_port(
                f"W{i}",
                center=(float(x_west), float(yi)),
                width=wg_width,
                orientation=orientation,
                layer=layer,
                **kwargs,
            )

    if east > 0:
        x_east = xsize
        y_east = (
            [ysize / 2]
            if east == 1
            else list(
                np.linspace(
                    wg_margin + wg_width / 2, ysize - wg_margin - wg_width / 2, east
                )
            )
        )
        orientation = 0

        for i, yi in enumerate(y_east):
            c.add_port(
                f"E{i}",
                center=(float(x_east), float(yi)),
                width=wg_width,
                orientation=orientation,
                layer=layer,
                **kwargs,
            )

    if north > 0:
        y_north = ysize
        x_north = (
            [xsize / 2]
            if north == 1
            else list(
                np.linspace(
                    wg_margin + wg_width / 2, xsize - wg_margin - wg_width / 2, north
                )
            )
        )
        orientation = 90

        for i, xi in enumerate(x_north):
            c.add_port(
                f"N{i}",
                center=(float(xi), float(y_north)),
                width=wg_width,
                orientation=orientation,
                layer=layer,
                **kwargs,
            )
    if south > 0:
        y_south = 0
        x_south = (
            [xsize / 2]
            if south == 1
            else list(
                np.linspace(
                    wg_margin + wg_width / 2, xsize - wg_margin - wg_width / 2, south
                )
            )
        )
        orientation = 270

        for i, xi in enumerate(x_south):
            c.add_port(
                f"S{i}",
                center=(float(xi), float(y_south)),
                width=wg_width,
                orientation=orientation,
                layer=layer,
                **kwargs,
            )

    c.auto_rename_ports()
    return c

nxn

octagon module-attribute

octagon = partial(regular_polygon, sides=8)

octagon

pie_arc

pie_arc

pie_arc(
    radius: float = 10.0,
    radius_y: float | None = None,
    start_angle: float = 0.0,
    end_angle: float = 90.0,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
) -> Component

Returns a pie-shaped arc (sector/wedge), centered at origin.

The shape is a closed polygon from the origin along two radial lines connected by an elliptical arc.

Parameters:

Name Type Description Default
radius float

x-radius (or uniform radius if radius_y is None).

10.0
radius_y float | None

y-radius for elliptical arc. Defaults to radius.

None
start_angle float

start angle in degrees.

0.0
end_angle float

end angle in degrees.

90.0
angle_resolution float

degrees per arc point.

2.5
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/shapes/pie_arc.py
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
@gf.cell_with_module_name(tags=["shapes"])
def pie_arc(
    radius: float = 10.0,
    radius_y: float | None = None,
    start_angle: float = 0.0,
    end_angle: float = 90.0,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a pie-shaped arc (sector/wedge), centered at origin.

    The shape is a closed polygon from the origin along two radial lines
    connected by an elliptical arc.

    Args:
        radius: x-radius (or uniform radius if radius_y is None).
        radius_y: y-radius for elliptical arc. Defaults to radius.
        start_angle: start angle in degrees.
        end_angle: end angle in degrees.
        angle_resolution: degrees per arc point.
        layer: layer spec.
    """
    if radius_y is None:
        radius_y = radius

    c = Component()
    sweep = end_angle - start_angle
    n_points = max(int(abs(sweep) / angle_resolution), 2)
    theta = np.deg2rad(np.linspace(start_angle, end_angle, n_points, endpoint=True))

    arc_points = list(
        zip(radius * np.cos(theta), radius_y * np.sin(theta), strict=False)
    )
    points = [(0, 0)] + arc_points
    c.add_polygon(points, layer=layer)
    return c

pie_arc

rect_su_shape

rect_su_shape

rect_su_shape(
    L1: float = 10.0,
    L2: float = 10.0,
    L3: float = 20.0,
    width: float = 1.0,
    layer: LayerSpec = "WG",
    port_type: str | None = "electrical",
) -> Component

Returns a rectangular S- or U-shaped routing structure.

The shape consists of three connected rectangular segments forming an S or U pattern. Positive and negative values of L1, L2, L3 produce different orientations (S-shape, U-shape, etc.).

Parameters:

Name Type Description Default
L1 float

length of first vertical segment.

10.0
L2 float

length of horizontal segment.

10.0
L3 float

length of second vertical segment.

20.0
width float

width of all segments.

1.0
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

'electrical'
Source code in gdsfactory/components/shapes/rect_su_shape.py
 10
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@gf.cell_with_module_name(tags=["shapes"])
def rect_su_shape(
    L1: float = 10.0,
    L2: float = 10.0,
    L3: float = 20.0,
    width: float = 1.0,
    layer: LayerSpec = "WG",
    port_type: str | None = "electrical",
) -> Component:
    """Returns a rectangular S- or U-shaped routing structure.

    The shape consists of three connected rectangular segments forming
    an S or U pattern. Positive and negative values of L1, L2, L3
    produce different orientations (S-shape, U-shape, etc.).

    Args:
        L1: length of first vertical segment.
        L2: length of horizontal segment.
        L3: length of second vertical segment.
        width: width of all segments.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    c = Component()
    w = width / 2

    # Build as three connected rectangles
    # Segment 1: vertical from (0,0) going up by L1
    # Segment 2: horizontal at top of seg1, going right by L2
    # Segment 3: vertical from end of seg2, going up by L3
    # The overall shape is an S/U polygon

    if L1 >= 0 and L2 >= 0 and L3 >= 0:
        points = [
            (-w, 0),
            (w, 0),
            (w, L1),
            (L2 + w, L1),
            (L2 + w, L1 + L3),
            (L2 - w, L1 + L3),
            (L2 - w, L1 + width),
            (-w, L1 + width),
            (-w, 0),
        ]
    else:
        # General case: compose three rectangles
        r1 = gf.components.rectangle(size=(width, abs(L1)), layer=layer)
        r2 = gf.components.rectangle(size=(abs(L2), width), layer=layer)
        r3 = gf.components.rectangle(size=(width, abs(L3)), layer=layer)

        ref1 = c.add_ref(r1)
        ref1.center = (0, 0)

        ref2 = c.add_ref(r2)
        if L1 >= 0:
            ref2.xmin = -w
            ref2.ymin = abs(L1) / 2 - w
        else:
            ref2.xmin = -w
            ref2.ymax = -abs(L1) / 2 + w

        ref3 = c.add_ref(r3)
        if L2 >= 0:
            ref3.xmin = abs(L2) - w
        else:
            ref3.xmax = -abs(L2) + w
        if L3 >= 0:
            ref3.ymin = ref2.ymin
        else:
            ref3.ymax = ref2.ymax

        c.flatten()
        return c

    c.add_polygon(points, layer=layer)

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            center=(0, 0),
            width=width,
            orientation=270,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(L2, L1 + L3),
            width=width,
            orientation=90,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()
    if port_type == "electrical":
        for port in c.ports:
            c.create_pin(ports=[port], name=port.name)
    return c

rect_su_shape

rect_taper

rect_taper

rect_taper(
    rect_width: float = 1.0,
    rect_length: float = 10.0,
    taper_length: float = 5.0,
    taper_width: float = 4.0,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component

Returns a rectangle connected to a linear taper.

The rectangle of width rect_width and length rect_length is connected on the right to a taper that linearly expands from rect_width to taper_width over taper_length.

Parameters:

Name Type Description Default
rect_width float

width of the rectangular section.

1.0
rect_length float

length of the rectangular section.

10.0
taper_length float

length of the taper section.

5.0
taper_width float

end width of the taper.

4.0
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

'optical'
Source code in gdsfactory/components/shapes/rect_taper.py
10
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
@gf.cell_with_module_name(tags=["shapes"])
def rect_taper(
    rect_width: float = 1.0,
    rect_length: float = 10.0,
    taper_length: float = 5.0,
    taper_width: float = 4.0,
    layer: LayerSpec = "WG",
    port_type: str | None = "optical",
) -> Component:
    """Returns a rectangle connected to a linear taper.

    The rectangle of width rect_width and length rect_length is connected
    on the right to a taper that linearly expands from rect_width to
    taper_width over taper_length.

    Args:
        rect_width: width of the rectangular section.
        rect_length: length of the rectangular section.
        taper_length: length of the taper section.
        taper_width: end width of the taper.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    c = Component()
    hw = rect_width / 2
    tw = taper_width / 2
    total_length = rect_length + taper_length

    points = [
        (0, -hw),
        (rect_length, -hw),
        (total_length, -tw),
        (total_length, tw),
        (rect_length, hw),
        (0, hw),
    ]
    c.add_polygon(points, layer=layer)

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            center=(0, 0),
            width=rect_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(total_length, 0),
            width=taper_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()
    if port_type == "electrical":
        for port in c.ports:
            c.create_pin(ports=[port], name=port.name)
    return c

rect_taper

rectangle

rectangle

rectangle(
    size: Size = (4.0, 2.0),
    layer: LayerSpec = "WG",
    centered: bool = False,
    port_type: str | None = "electrical",
    port_orientations: Ints | None = (180, 90, 0, -90),
) -> Component

Returns a rectangle.

Parameters:

Name Type Description Default
size Size

(tuple) Width and height of rectangle.

(4.0, 2.0)
layer LayerSpec

Specific layer to put polygon geometry on.

'WG'
centered bool

True sets center to (0, 0), False sets south-west to (0, 0).

False
port_type str | None

optical, electrical.

'electrical'
port_orientations Ints | None

list of port_orientations to add. None adds no ports.

(180, 90, 0, -90)
Source code in gdsfactory/components/shapes/rectangle.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
@gf.cell_with_module_name(tags=["shapes"])
def rectangle(
    size: Size = (4.0, 2.0),
    layer: LayerSpec = "WG",
    centered: bool = False,
    port_type: str | None = "electrical",
    port_orientations: Ints | None = (180, 90, 0, -90),
) -> 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 = 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()
    if port_type == "electrical":
        elec = [p for p in c.ports if p.port_type == "electrical"]
        if elec:
            c.create_pin(ports=elec, name="pad")
    return c

rectangles

rectangles(
    size: Size = (4.0, 2.0),
    offsets: Sequence[float] | None = None,
    layers: LayerSpecs = ("WG", "SLAB150"),
    centered: bool = True,
    **kwargs: Any
) -> Component

Returns overimposed rectangles.

Parameters:

Name Type Description Default
size Size

(tuple) Width and height of rectangle.

(4.0, 2.0)
layers LayerSpecs

Specific layer to put polygon geometry on.

('WG', 'SLAB150')
offsets Sequence[float] | None

list of offsets. If None, all rectangles have a zero offset.

None
centered bool

True sets center to (0, 0), False sets south-west of first rectangle to (0, 0).

True
kwargs Any

additional arguments to pass to rectangle.

{}

Other Parameters:

Name Type Description
port_type

optical, electrical.

port_orientations

list of port_orientations to add.

┌──────────────┐ │ │ │ ┌──────┐ │ │ │ │ │ │ │ ├───► │ │ │offset │ └──────┘ │ │ │ └──────────────┘

Source code in gdsfactory/components/shapes/rectangle.py
 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
@gf.cell_with_module_name(tags=["shapes"])
def rectangles(
    size: Size = (4.0, 2.0),
    offsets: Sequence[float] | None = None,
    layers: LayerSpecs = ("WG", "SLAB150"),
    centered: bool = True,
    **kwargs: Any,
) -> Component:
    """Returns overimposed rectangles.

    Args:
        size: (tuple) Width and height of rectangle.
        layers: Specific layer to put polygon geometry on.
        offsets: list of offsets. If None, all rectangles have a zero offset.
        centered: True sets center to (0, 0), False sets south-west of first rectangle to (0, 0).
        kwargs: additional arguments to pass to rectangle.

    Keyword Args:
        port_type: optical, electrical.
        port_orientations: list of port_orientations to add.

            ┌──────────────┐
            │              │
            │   ┌──────┐   │
            │   │      │   │
            │   │      ├───►
            │   │      │offset
            │   └──────┘   │
            │              │
            └──────────────┘

    """
    c = Component()
    size_np = np.array(size, dtype=np.float64)
    ref0 = None
    offsets = offsets or [0] * len(layers)

    if len(offsets) != len(layers):
        raise ValueError(f"len(offsets) != len(layers) {len(offsets)} != {len(layers)}")
    for layer, offset in zip(layers, offsets, strict=False):
        current_size = size_np + 2 * offset
        print(f"layer={layer} offset={offset} current_size={current_size}")
        ref = c << rectangle(
            size=(current_size[0], current_size[1]),
            layer=layer,
            centered=centered,
            **kwargs,
        )
        if ref0:
            ref.center = ref0.center
        ref0 = ref

    return c

rectangle

rectangles

rectangles(
    size: Size = (4.0, 2.0),
    offsets: Sequence[float] | None = None,
    layers: LayerSpecs = ("WG", "SLAB150"),
    centered: bool = True,
    **kwargs: Any
) -> Component

Returns overimposed rectangles.

Parameters:

Name Type Description Default
size Size

(tuple) Width and height of rectangle.

(4.0, 2.0)
layers LayerSpecs

Specific layer to put polygon geometry on.

('WG', 'SLAB150')
offsets Sequence[float] | None

list of offsets. If None, all rectangles have a zero offset.

None
centered bool

True sets center to (0, 0), False sets south-west of first rectangle to (0, 0).

True
kwargs Any

additional arguments to pass to rectangle.

{}

Other Parameters:

Name Type Description
port_type

optical, electrical.

port_orientations

list of port_orientations to add.

┌──────────────┐ │ │ │ ┌──────┐ │ │ │ │ │ │ │ ├───► │ │ │offset │ └──────┘ │ │ │ └──────────────┘

Source code in gdsfactory/components/shapes/rectangle.py
 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
@gf.cell_with_module_name(tags=["shapes"])
def rectangles(
    size: Size = (4.0, 2.0),
    offsets: Sequence[float] | None = None,
    layers: LayerSpecs = ("WG", "SLAB150"),
    centered: bool = True,
    **kwargs: Any,
) -> Component:
    """Returns overimposed rectangles.

    Args:
        size: (tuple) Width and height of rectangle.
        layers: Specific layer to put polygon geometry on.
        offsets: list of offsets. If None, all rectangles have a zero offset.
        centered: True sets center to (0, 0), False sets south-west of first rectangle to (0, 0).
        kwargs: additional arguments to pass to rectangle.

    Keyword Args:
        port_type: optical, electrical.
        port_orientations: list of port_orientations to add.

            ┌──────────────┐
            │              │
            │   ┌──────┐   │
            │   │      │   │
            │   │      ├───►
            │   │      │offset
            │   └──────┘   │
            │              │
            └──────────────┘

    """
    c = Component()
    size_np = np.array(size, dtype=np.float64)
    ref0 = None
    offsets = offsets or [0] * len(layers)

    if len(offsets) != len(layers):
        raise ValueError(f"len(offsets) != len(layers) {len(offsets)} != {len(layers)}")
    for layer, offset in zip(layers, offsets, strict=False):
        current_size = size_np + 2 * offset
        print(f"layer={layer} offset={offset} current_size={current_size}")
        ref = c << rectangle(
            size=(current_size[0], current_size[1]),
            layer=layer,
            centered=centered,
            **kwargs,
        )
        if ref0:
            ref.center = ref0.center
        ref0 = ref

    return c

rectangles

regular_polygon

regular_polygon

regular_polygon(
    sides: int = 6,
    side_length: float = 10,
    layer: LayerSpec = "WG",
    port_width: float | None = None,
    port_type: str | None = "placement",
) -> Component

Returns a regular N-sided polygon, with ports on each edge.

Parameters:

Name Type Description Default
sides int

number of sides for the polygon.

6
side_length float

of the edges.

10
layer LayerSpec

Specific layer to put polygon geometry on.

'WG'
port_width float | None

the width of port of the polygon (in electrical pads, the port width may not equal to the side length).

None
port_type str | None

optical, electrical.

'placement'
Source code in gdsfactory/components/shapes/regular_polygon.py
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
@gf.cell_with_module_name(tags=["shapes"])
def regular_polygon(
    sides: int = 6,
    side_length: float = 10,
    layer: LayerSpec = "WG",
    port_width: float
    | None = None,  # port width doesn't need to be side length all the time
    port_type: str | None = "placement",
) -> Component:
    """Returns a regular N-sided polygon, with ports on each edge.

    Args:
        sides: number of sides for the polygon.
        side_length: of the edges.
        layer: Specific layer to put polygon geometry on.
        port_width: the width of port of the polygon (in electrical pads, the port width may not equal to the side length).
        port_type: optical, electrical.
    """
    c = Component()
    angle_step = 2 * np.pi / sides
    radius = side_length / (2 * np.sin(np.pi / sides))

    port_width = port_width or side_length

    # Rotate the polygon to make one facet flat
    rotation_angle = np.pi / 2 - angle_step / 2
    points = [
        (
            radius * np.cos(i * angle_step + rotation_angle),
            radius * np.sin(i * angle_step + rotation_angle),
        )
        for i in range(sides)
    ]
    c.add_polygon(points, layer=layer)
    a = side_length / (2 * np.tan(np.pi / sides))

    if port_type:
        for side_index in range(sides):
            angle = 270 + side_index * 360 / sides
            center = (a * np.cos(np.radians(angle)), a * np.sin(np.radians(angle)))
            c.add_port(
                name=f"o{side_index + 1}",
                center=center,
                width=port_width,
                layer=layer,
                port_type=port_type,
                orientation=angle,
            )

    c.auto_rename_ports()
    if port_type == "electrical":
        elec = [p for p in c.ports if p.port_type == "electrical"]
        if elec:
            c.create_pin(ports=elec, name="pad")
    return c

regular_polygon

rounded_rectangle

rounded_rectangle

rounded_rectangle(
    width: float = 20.0,
    height: float = 10.0,
    corner_radius_x: float = 3.0,
    corner_radius_y: float | None = None,
    n_corner_points: int = 20,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component

Returns a rectangle with rounded corners, centered at origin.

Parameters:

Name Type Description Default
width float

total width of the rectangle.

20.0
height float

total height of the rectangle.

10.0
corner_radius_x float

x-radius of the corner arcs.

3.0
corner_radius_y float | None

y-radius of the corner arcs. Defaults to corner_radius_x.

None
n_corner_points int

number of points per corner arc.

20
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

None
Source code in gdsfactory/components/shapes/rounded_rectangle.py
 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
 96
 97
 98
 99
100
101
102
@gf.cell_with_module_name(tags=["shapes"])
def rounded_rectangle(
    width: float = 20.0,
    height: float = 10.0,
    corner_radius_x: float = 3.0,
    corner_radius_y: float | None = None,
    n_corner_points: int = 20,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component:
    """Returns a rectangle with rounded corners, centered at origin.

    Args:
        width: total width of the rectangle.
        height: total height of the rectangle.
        corner_radius_x: x-radius of the corner arcs.
        corner_radius_y: y-radius of the corner arcs. Defaults to corner_radius_x.
        n_corner_points: number of points per corner arc.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    if corner_radius_y is None:
        corner_radius_y = corner_radius_x

    rx = min(corner_radius_x, width / 2)
    ry = min(corner_radius_y, height / 2)

    c = Component()
    hw = width / 2
    hh = height / 2

    # Trace corners CCW. Each corner is a quarter-ellipse arc centered at the
    # corner's center point. The four arcs span continuous angular ranges:
    #   top-right:    0      → pi/2
    #   top-left:     pi/2   → pi
    #   bottom-left:  pi     → 3*pi/2
    #   bottom-right: 3*pi/2 → 2*pi
    corners = [
        (hw - rx, hh - ry, 0, np.pi / 2),  # top-right
        (-hw + rx, hh - ry, np.pi / 2, np.pi),  # top-left
        (-hw + rx, -hh + ry, np.pi, 3 * np.pi / 2),  # bottom-left
        (hw - rx, -hh + ry, 3 * np.pi / 2, 2 * np.pi),  # bottom-right
    ]

    points: list[tuple[float, float]] = []
    for cx, cy, a_start, a_end in corners:
        t = np.linspace(a_start, a_end, n_corner_points, endpoint=True)
        points.extend((cx + rx * np.cos(ti), cy + ry * np.sin(ti)) for ti in t)

    c.add_polygon(points, layer=layer)

    if port_type:
        prefix = "o" if port_type == "optical" else "e"
        c.add_port(
            f"{prefix}1",
            center=(hw, 0),
            width=height - 2 * ry,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(-hw, 0),
            width=height - 2 * ry,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}3",
            center=(0, hh),
            width=width - 2 * rx,
            orientation=90,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}4",
            center=(0, -hh),
            width=width - 2 * rx,
            orientation=270,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()
    if port_type == "electrical":
        elec = [p for p in c.ports if p.port_type == "electrical"]
        if elec:
            c.create_pin(ports=elec, name="pad")
    return c

rounded_rectangle

star

star

star(
    inner_radius: float = 5.0,
    outer_radius: float = 10.0,
    n_points: int = 5,
    layer: LayerSpec = "WG",
) -> Component

Returns a star shape with alternating inner and outer radii.

Parameters:

Name Type Description Default
inner_radius float

radius of inner vertices.

5.0
outer_radius float

radius of outer vertices.

10.0
n_points int

number of star points.

5
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/shapes/star.py
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
@gf.cell_with_module_name(tags=["shapes"])
def star(
    inner_radius: float = 5.0,
    outer_radius: float = 10.0,
    n_points: int = 5,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a star shape with alternating inner and outer radii.

    Args:
        inner_radius: radius of inner vertices.
        outer_radius: radius of outer vertices.
        n_points: number of star points.
        layer: layer spec.
    """
    if n_points < 3:
        raise ValueError(f"n_points={n_points} must be >= 3")
    if inner_radius <= 0 or outer_radius <= 0:
        raise ValueError("radii must be > 0")

    c = Component()
    angles = np.linspace(0, 2 * np.pi, 2 * n_points, endpoint=False)
    points = []
    for i, a in enumerate(angles):
        r = outer_radius if i % 2 == 0 else inner_radius
        points.append((r * np.cos(a), r * np.sin(a)))
    c.add_polygon(points, layer=layer)
    return c

star

torus

torus

torus(
    inner_radius: float = 5.0,
    outer_radius: float = 10.0,
    start_angle: float = 0.0,
    end_angle: float = 360.0,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component

Returns a torus (annular sector / ring sector) centered at origin.

Parameters:

Name Type Description Default
inner_radius float

inner radius.

5.0
outer_radius float

outer radius.

10.0
start_angle float

start angle in degrees.

0.0
end_angle float

end angle in degrees.

360.0
angle_resolution float

degrees per arc point.

2.5
layer LayerSpec

layer spec.

'WG'
port_type str | None

None, optical, or electrical.

None
Source code in gdsfactory/components/shapes/torus.py
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
@gf.cell_with_module_name(tags=["shapes"])
def torus(
    inner_radius: float = 5.0,
    outer_radius: float = 10.0,
    start_angle: float = 0.0,
    end_angle: float = 360.0,
    angle_resolution: float = 2.5,
    layer: LayerSpec = "WG",
    port_type: str | None = None,
) -> Component:
    """Returns a torus (annular sector / ring sector) centered at origin.

    Args:
        inner_radius: inner radius.
        outer_radius: outer radius.
        start_angle: start angle in degrees.
        end_angle: end angle in degrees.
        angle_resolution: degrees per arc point.
        layer: layer spec.
        port_type: None, optical, or electrical.
    """
    if inner_radius < 0:
        raise ValueError(f"inner_radius={inner_radius} must be >= 0")
    if outer_radius <= inner_radius:
        raise ValueError("outer_radius must be > inner_radius")

    c = Component()
    sweep = end_angle - start_angle
    n_points = max(int(abs(sweep) / angle_resolution), 2) + 1
    theta = np.deg2rad(np.linspace(start_angle, end_angle, n_points, endpoint=True))

    outer_x = outer_radius * np.cos(theta)
    outer_y = outer_radius * np.sin(theta)
    inner_x = inner_radius * np.cos(theta[::-1])
    inner_y = inner_radius * np.sin(theta[::-1])

    points = list(
        zip(
            np.concatenate([outer_x, inner_x]),
            np.concatenate([outer_y, inner_y]),
            strict=False,
        )
    )
    c.add_polygon(points, layer=layer)

    if port_type and abs(sweep) < 360:
        width = outer_radius - inner_radius
        mid_r = (inner_radius + outer_radius) / 2
        prefix = "o" if port_type == "optical" else "e"
        sa_rad = np.deg2rad(start_angle)
        ea_rad = np.deg2rad(end_angle)
        c.add_port(
            f"{prefix}1",
            center=(mid_r * np.cos(sa_rad), mid_r * np.sin(sa_rad)),
            width=width,
            orientation=start_angle + 90,
            layer=layer,
            port_type=port_type,
        )
        c.add_port(
            f"{prefix}2",
            center=(mid_r * np.cos(ea_rad), mid_r * np.sin(ea_rad)),
            width=width,
            orientation=end_angle - 90,
            layer=layer,
            port_type=port_type,
        )
        c.auto_rename_ports()
    if port_type == "electrical":
        for port in c.ports:
            c.create_pin(ports=[port], name=port.name)
    return c

torus

torus_wave

torus_wave

torus_wave(
    inner_radius: float = 5.0,
    outer_radius: float = 10.0,
    amplitude: float = 0.5,
    n_oscillations: int = 8,
    in_phase: bool = True,
    angle_resolution: float = 1.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a torus (full ring) with sinusoidal boundary modulation.

Inner and outer boundaries oscillate sinusoidally. When in_phase=True, both boundaries are modulated in phase; when False, they are pi/2 out of phase.

Parameters:

Name Type Description Default
inner_radius float

mean inner radius.

5.0
outer_radius float

mean outer radius.

10.0
amplitude float

amplitude of boundary oscillation.

0.5
n_oscillations int

number of oscillations around the boundary.

8
in_phase bool

if True, inner and outer modulations are in phase.

True
angle_resolution float

degrees per point.

1.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/shapes/torus_wave.py
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
@gf.cell_with_module_name(tags=["shapes"])
def torus_wave(
    inner_radius: float = 5.0,
    outer_radius: float = 10.0,
    amplitude: float = 0.5,
    n_oscillations: int = 8,
    in_phase: bool = True,
    angle_resolution: float = 1.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a torus (full ring) with sinusoidal boundary modulation.

    Inner and outer boundaries oscillate sinusoidally. When in_phase=True,
    both boundaries are modulated in phase; when False, they are pi/2 out
    of phase.

    Args:
        inner_radius: mean inner radius.
        outer_radius: mean outer radius.
        amplitude: amplitude of boundary oscillation.
        n_oscillations: number of oscillations around the boundary.
        in_phase: if True, inner and outer modulations are in phase.
        angle_resolution: degrees per point.
        layer: layer spec.
    """
    c = Component()
    n_points = int(np.round(360.0 / angle_resolution)) + 1
    theta = np.linspace(0, 2 * np.pi, n_points, endpoint=True)

    r_outer = outer_radius + amplitude * np.sin(n_oscillations * theta)
    phase_shift = 0 if in_phase else np.pi / 2
    r_inner = inner_radius + amplitude * np.sin(n_oscillations * theta + phase_shift)

    outer_x = r_outer * np.cos(theta)
    outer_y = r_outer * np.sin(theta)
    inner_x = r_inner[::-1] * np.cos(theta[::-1])
    inner_y = r_inner[::-1] * np.sin(theta[::-1])

    points = list(
        zip(
            np.concatenate([outer_x, inner_x]),
            np.concatenate([outer_y, inner_y]),
            strict=False,
        )
    )
    c.add_polygon(points, layer=layer)
    return c

torus_wave

triangle

triangle(
    x: float = 10,
    xtop: float = 0,
    y: float = 20,
    ybot: float = 0,
    layer: LayerSpec = "WG",
) -> Component

Return triangle.

Parameters:

Name Type Description Default
x float

base xsize.

10
xtop float

top xsize.

0
y float

ysize.

20
ybot float

bottom ysize.

0
layer LayerSpec

layer.

'WG'
Source code in gdsfactory/components/shapes/triangles.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
@gf.cell_with_module_name(tags=["shapes"])
def triangle(
    x: float = 10,
    xtop: float = 0,
    y: float = 20,
    ybot: float = 0,
    layer: LayerSpec = "WG",
) -> Component:
    r"""Return triangle.

    Args:
        x: base xsize.
        xtop: top xsize.
        y: ysize.
        ybot: bottom ysize.
        layer: layer.

        xtop
           _
          | \
          |  \
          |   \
         y|    \
          |     \
          |      \
          |______|ybot
              x
    """
    c = Component()
    points = [(0, 0), (x, 0), (x, ybot), (xtop, y), (0, y)]
    c.add_polygon(points, layer=layer)
    return c

triangle

triangle2

triangle2(spacing: float = 3, **kwargs: Any) -> Component

Return 2 triangles (bot, top).

Parameters:

Name Type Description Default
spacing float

between top and bottom.

3
kwargs Any

triangle arguments.

{}

Other Parameters:

Name Type Description
x

base xsize.

xtop

top xsize.

y

ysize.

ybot

bottom ysize.

layer

layer.

_ | \ | \ | \ | \ | \ | \ | \ | | spacing | / | / | / | / | / |_/

Source code in gdsfactory/components/shapes/triangles.py
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
@gf.cell_with_module_name(tags=["shapes"])
def triangle2(spacing: float = 3, **kwargs: Any) -> Component:
    r"""Return 2 triangles (bot, top).

    Args:
        spacing: between top and bottom.
        kwargs: triangle arguments.

    Keyword Args:
        x: base xsize.
        xtop: top xsize.
        y: ysize.
        ybot: bottom ysize.
        layer: layer.

          _
         | \
         |  \
         |   \
         |    \
         |     \
         |      \
         |       \
         |       |  spacing
         |      /
         |     /
         |    /
         |   /
         |  /
         |_/

    """
    c = Component()
    t = triangle(**kwargs)
    tt = c << t
    tb = c << t
    tb.dmirror()
    tb.rotate(180)
    tb.ymax = tt.ymin - spacing
    return c

triangle2

triangle2_thin module-attribute

triangle2_thin = partial(triangle2, xtop=0.2, x=2, y=5)

triangle2_thin

triangle4

triangle4(**kwargs: Any) -> Component

Return 4 triangles.

Parameters:

Name Type Description Default
kwargs Any

triangle arguments.

{}

Other Parameters:

Name Type Description
x

base xsize.

xtop

top xsize.

y

ysize.

ybot

bottom ysize.

layer

layer.

  / | \
 /  |  \
/   |   \

/ | \ / | \ / | \ / | \ | | | \ | / \ | / \ | / \ | / \ | / \ |_/

Source code in gdsfactory/components/shapes/triangles.py
 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
@gf.cell_with_module_name(tags=["shapes"])
def triangle4(**kwargs: Any) -> Component:
    r"""Return 4 triangles.

    Args:
        kwargs: triangle arguments.

    Keyword Args:
        x: base xsize.
        xtop: top xsize.
        y: ysize.
        ybot: bottom ysize.
        layer: layer.

                  / | \
                 /  |  \
                /   |   \
               /    |    \
              /     |     \
             /      |      \
            /       |       \
            |       |       |
            \       |      /
             \      |     /
              \     |    /
               \    |   /
                \   |  /
                 \  |_/

    """
    c = Component()
    t = triangle2(**kwargs)
    t1 = c << t
    t2 = c << t
    t2.dmirror()
    t2.xmax = t1.xmin
    return c

triangle4

triangle4_thin module-attribute

triangle4_thin = partial(triangle4, xtop=0.2, x=2, y=5)

triangle4_thin

triangle_thin module-attribute

triangle_thin = partial(triangle, xtop=0.2, x=2, y=5)

triangle_thin

spirals

delay_snake

delay_snake

delay_snake(
    length: float = 1600.0,
    length0: float = 0.0,
    length2: float = 0.0,
    n: int = 2,
    bend180: ComponentSpec = bend_euler180,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component

Returns Snake with a starting bend and 180 bends.

Parameters:

Name Type Description Default
length float

total length.

1600.0
length0 float

start length.

0.0
length2 float

end length.

0.0
n int

number of loops.

2
bend180 ComponentSpec

ubend spec.

bend_euler180
cross_section CrossSectionSpec

cross_section spec.

'strip'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

 | length0   |
None
             >---------\
                        \bend180.info['length']
                        /
   |-------------------/
   |
   |------------------->------->|
                        length2
   |   delta_length    |        |
Source code in gdsfactory/components/spirals/delay_snake.py
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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def delay_snake(
    length: float = 1600.0,
    length0: float = 0.0,
    length2: float = 0.0,
    n: int = 2,
    bend180: ComponentSpec = bend_euler180,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component:
    r"""Returns Snake with a starting bend and 180 bends.

    Args:
        length: total length.
        length0: start length.
        length2: end length.
        n: number of loops.
        bend180: ubend spec.
        cross_section: cross_section spec.
        width: width of the waveguide. If None, it will use the width of the cross_section.

                 | length0   |

    ```text
                 >---------\
                            \bend180.info['length']
                            /
       |-------------------/
       |
       |------------------->------->|
                            length2
       |   delta_length    |        |
    ```


    """
    if n % 2:
        warnings.warn(f"rounding {n} to {n // 2 * 2}", stacklevel=3)
        n = n // 2 * 2
    bend180 = gf.get_component(bend180, cross_section=cross_section, width=width)

    delta_length = (length - length0 - length2 - n * bend180.info["length"]) / n
    if delta_length < 0:
        raise ValueError(
            "Snake is too short: either reduce length0, length2, "
            f"increase the total length, or decrease the number of loops (n = {n}). "
            f"delta_length = {int(delta_length)}\n" + _diagram
        )

    s0 = straight(cross_section=cross_section, length=length0, width=width)
    sd = straight(cross_section=cross_section, length=delta_length, width=width)
    s2 = straight(cross_section=cross_section, length=length2, width=width)

    symbol_to_component = {
        "_": (s0, "o1", "o2"),
        "-": (sd, "o1", "o2"),
        ")": (bend180, "o2", "o1"),
        "(": (bend180, "o1", "o2"),
        ".": (s2, "o1", "o2"),
    }

    sequence = "_)" + n // 2 * "-(-)"
    sequence = f"{sequence[:-1]}."
    c = component_sequence(sequence=sequence, symbol_to_component=symbol_to_component)
    c.info["length"] = length
    return c

delay_snake

delay_snake2

delay_snake2

delay_snake2(
    length: float = 1600.0,
    length0: float = 0.0,
    length2: float = 0.0,
    n: int = 2,
    bend180: ComponentSpec = "bend_euler180",
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component

Returns Snake with a starting straight and 180 bends.

Input faces west output faces east.

Parameters:

Name Type Description Default
length float

total length.

1600.0
length0 float

start length.

0.0
length2 float

end length.

0.0
n int

number of loops.

2
bend180 ComponentSpec

ubend spec.

'bend_euler180'
cross_section CrossSectionSpec

cross_section spec.

'strip'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None

| length0 | length1 |

             >---------|
                       | bend180.length
   |-------------------|
   |
   |------------------->------- |
                        length2
   |   delta_length    |        |
Source code in gdsfactory/components/spirals/delay_snake2.py
 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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def delay_snake2(
    length: float = 1600.0,
    length0: float = 0.0,
    length2: float = 0.0,
    n: int = 2,
    bend180: ComponentSpec = "bend_euler180",
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component:
    """Returns Snake with a starting straight and 180 bends.

    Input faces west output faces east.

    Args:
        length: total length.
        length0: start length.
        length2: end length.
        n: number of loops.
        bend180: ubend spec.
        cross_section: cross_section spec.
        width: width of the waveguide. If None, it will use the width of the cross_section.

       | length0 | length1 |

    ```text
                 >---------|
                           | bend180.length
       |-------------------|
       |
       |------------------->------- |
                            length2
       |   delta_length    |        |
    ```
    """
    if n % 2:
        warnings.warn(f"rounding {n} to {n // 2 * 2}", stacklevel=3)
        n = n // 2 * 2

    bend180 = gf.get_component(bend180, cross_section=cross_section, width=width)

    delta_length = (length - length0 - length2 - n * bend180.info["length"]) / (n + 1)
    length1 = delta_length - length0
    if length1 < 0:
        raise ValueError(
            "Snake is too short: either reduce length0, length2, "
            f"increase the total length, or decrease the number of loops (n = {n}). "
            f"length1 = {int(length1)}, delta_length = {int(delta_length)}\n" + diagram
        )

    s1 = gf.components.straight(
        length=length1, cross_section=cross_section, width=width
    )
    s2 = gf.components.straight(
        length=length2, cross_section=cross_section, width=width
    )
    sd = gf.components.straight(
        cross_section=cross_section, length=delta_length, width=width
    )

    symbol_to_component = {
        "_": (s1, "o1", "o2"),
        "-": (sd, "o1", "o2"),
        ")": (bend180, "o2", "o1"),
        "(": (bend180, "o1", "o2"),
        ".": (s2, "o1", "o2"),
    }

    sequence = "_)" + n // 2 * "-(-)"
    sequence = sequence[:-1]
    sequence += "."
    c = component_sequence(sequence=sequence, symbol_to_component=symbol_to_component)
    c.info["length"] = length
    return c

delay_snake2

delay_snake_sbend

delay_snake_sbend

delay_snake_sbend(
    length: float = 100.0,
    length1: float = 0.0,
    length4: float = 0.0,
    radius: float = 5.0,
    waveguide_spacing: float = 5.0,
    bend: ComponentSpec = "bend_euler",
    sbend: ComponentSpec = "bend_s",
    sbend_xsize: float = 100.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns compact Snake with sbend in the middle.

Input port faces west and output port faces east.

Parameters:

Name Type Description Default
length float

total length.

100.0
length1 float

first straight section length in um.

0.0
length4 float

fourth straight section length in um.

0.0
radius float

u bend radius in um.

5.0
waveguide_spacing float

waveguide pitch in um.

5.0
bend ComponentSpec

bend spec.

'bend_euler'
sbend ComponentSpec

sbend spec.

'bend_s'
sbend_xsize float

sbend size.

100.0
cross_section CrossSectionSpec

cross_section spec.

         length1

<---------------------------- length2 spacing | _ | | \ | | \ | bend1 radius | \sbend | bend2| \ | | \ | | __| | ---------------------->-----------> length3 length4

'strip'
Source code in gdsfactory/components/spirals/delay_snake_sbend.py
 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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def delay_snake_sbend(
    length: float = 100.0,
    length1: float = 0.0,
    length4: float = 0.0,
    radius: float = 5.0,
    waveguide_spacing: float = 5.0,
    bend: ComponentSpec = "bend_euler",
    sbend: ComponentSpec = "bend_s",
    sbend_xsize: float = 100.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    r"""Returns compact Snake with sbend in the middle.

    Input port faces west and output port faces east.

    Args:
        length: total length.
        length1: first straight section length in um.
        length4: fourth straight section length in um.
        radius: u bend radius in um.
        waveguide_spacing: waveguide pitch in um.
        bend: bend spec.
        sbend: sbend spec.
        sbend_xsize: sbend size.
        cross_section: cross_section spec.

                         length1
         <----------------------------
               length2    spacing    |
                _______              |
               |        \            |
               |          \          | bend1 radius
               |            \sbend   |
          bend2|              \      |
               |                \    |
               |                  \__|
               |
               ---------------------->----------->
                   length3              length4

        We adjust length2 and length3
    """
    c = Component()

    bend180_radius = (radius + waveguide_spacing) / 2
    bend = gf.get_component(
        bend,
        radius=bend180_radius,
        angle=180,
        cross_section=cross_section,
    )
    sbend = gf.get_component(
        sbend,
        size=(sbend_xsize, radius),
        cross_section=cross_section,
    )

    b1 = c << bend
    b2 = c << bend
    bs = c << sbend
    bs.dmirror()

    length23 = (
        length - (2 * bend.info["length"] - sbend.info["length"]) - length1 - length4
    )
    length2 = length23 / 2
    length3 = length23 / 2

    if length2 < 0:
        raise ValueError(
            f"length2 = {length2} < 0. You need to reduce length1 = {length1} "
            f"or length3 = {length3} or increase length = {length}\n" + diagram
        )

    straight1 = straight(length=length1, cross_section=cross_section)
    straight2 = straight(length=length2, cross_section=cross_section)
    straight3 = straight(length=length3, cross_section=cross_section)
    straight4 = straight(length=length4, cross_section=cross_section)

    s1 = c.add_ref(straight1, "s1")
    s2 = c.add_ref(straight2, "s2")
    s3 = c.add_ref(straight3, "s3")
    s4 = c.add_ref(straight4, "s4")

    b1.connect("o2", s1.ports["o2"])
    bs.connect("o2", b1.ports["o1"])

    s2.connect("o2", bs.ports["o1"])

    b2.connect("o1", s2.ports["o1"])
    s3.connect("o1", b2.ports["o2"])
    s4.connect("o1", s3.ports["o2"])

    c.add_port("o1", port=s1.ports["o1"])
    c.add_port("o2", port=s4.ports["o2"])

    c.info["min_bend_radius"] = float(sbend.info["min_bend_radius"])
    c.info["bend180_radius"] = bend180_radius

    # delete any straights with zero length
    for inst in s1, s2, s3, s4:
        if inst.cell.settings["length"] == 0:
            del c.insts[inst]

    # Calculate the actual total physical length
    total_length = length1  # straight1
    total_length += length2  # straight2
    total_length += length3  # straight3
    total_length += length4  # straight4
    total_length += 2 * bend.info["length"]  # two 180-degree bends
    total_length += sbend.info["length"]  # one s-bend

    c.info["length"] = total_length
    return c

delay_snake_sbend

spiral

spiral

spiral(
    length: float = 100,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    spacing: float = 3.0,
    n_loops: int = 6,
) -> gf.Component

Returns a spiral double (spiral in, and then out).

Parameters:

Name Type Description Default
length float

length of the spiral straight section.

100
bend ComponentSpec

bend component.

'bend_euler'
straight ComponentSpec

straight component.

'straight'
cross_section CrossSectionSpec

cross_section component.

'strip'
spacing float

spacing between the spiral loops.

3.0
n_loops int

number of loops.

6
Source code in gdsfactory/components/spirals/spiral.py
 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
 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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral(
    length: float = 100,
    bend: ComponentSpec = "bend_euler",
    straight: ComponentSpec = "straight",
    cross_section: CrossSectionSpec = "strip",
    spacing: float = 3.0,
    n_loops: int = 6,
) -> gf.Component:
    """Returns a spiral double (spiral in, and then out).

    Args:
        length: length of the spiral straight section.
        bend: bend component.
        straight: straight component.
        cross_section: cross_section component.
        spacing: spacing between the spiral loops.
        n_loops: number of loops.
    """
    c = gf.Component()
    b = gf.get_component(bend, cross_section=cross_section)
    bend_length = b.info["length"]

    o1 = b["o1"]
    o2 = b["o2"]
    dx = abs(o2.x - o1.x)
    dy = abs(o2.y - o1.y)

    if dx != dy:
        raise ValueError(f"bend component {b} must have dx == dy")
    radius = dx
    _length = length

    total_length = 0

    b_inners = [c << b for _ in range(4)]
    b_inners[0].dmirror()
    b_inners[1].connect("o1", b_inners[0], "o2")
    b_inners[2].connect("o1", b_inners[1], "o2")
    s_space = c << gf.get_component(
        straight, cross_section=cross_section, length=spacing
    )
    s_space.connect("o1", b_inners[2], "o2")
    b_inners[3].connect("o1", s_space, "o2")
    l0_2 = c << gf.get_component(
        straight, cross_section=cross_section, length=_length + 2 * radius + spacing
    )
    l0_2.connect("o1", b_inners[3], "o2")
    p2 = l0_2.ports["o2"]

    # Add the initial inner loop components to total_length
    total_length += 4 * bend_length  # 4 inner bends
    total_length += spacing  # s_space
    total_length += _length + 2 * radius + spacing  # l0_2

    if length > 0:
        l0_1 = c << gf.get_component(
            straight, cross_section=cross_section, length=_length
        )
        l0_1.connect("o1", b_inners[0], "o1")
        p1 = l0_1.ports["o2"].copy()
        total_length += _length
    else:
        p1 = b_inners[0].ports["o1"]
    p1.mirror = not p1.mirror
    for i in range(n_loops // 2):
        bends = [c << b for _ in range(8)]
        bends[0].connect("o1", p1)
        bends[1].connect("o1", p2)
        v1 = c << gf.get_component(
            straight, cross_section=cross_section, length=spacing * (1 + 4 * i)
        )
        v1.connect("o1", bends[0], "o2")
        v2 = c << gf.get_component(
            straight, cross_section=cross_section, length=spacing * (3 + 4 * i)
        )
        v2.connect("o1", bends[1], "o2")
        bends[2].connect("o1", v1, "o2")
        bends[3].connect("o1", v2, "o2")
        h1 = c << gf.get_component(
            straight,
            cross_section=cross_section,
            length=_length + 2 * radius + spacing * (1 + 4 * i),
        )
        h2 = c << gf.get_component(
            straight,
            cross_section=cross_section,
            length=_length + 2 * radius + spacing * (3 + 4 * i),
        )
        h1.connect("o1", bends[2], "o2")
        h2.connect("o1", bends[3], "o2")
        bends[4].connect("o1", h1, "o2")
        bends[5].connect("o1", h2, "o2")
        v3 = c << gf.get_component(
            straight, cross_section=cross_section, length=spacing * (3 + 4 * i)
        )
        v4 = c << gf.get_component(
            straight, cross_section=cross_section, length=spacing * (5 + 4 * i)
        )
        v3.connect("o1", bends[4], "o2")
        v4.connect("o1", bends[5], "o2")
        bends[6].connect("o1", v3, "o2")
        bends[7].connect("o1", v4, "o2")
        h3 = c << gf.get_component(
            straight,
            cross_section=cross_section,
            length=_length + 2 * radius + spacing * (3 + 4 * i),
        )
        h4 = c << gf.get_component(
            straight,
            cross_section=cross_section,
            length=_length + 2 * radius + spacing * (5 + 4 * i),
        )
        h3.connect("o1", bends[6], "o2")
        h4.connect("o1", bends[7], "o2")
        p1 = h3.ports["o2"]
        p2 = h4.ports["o2"]
        # Calculate lengths for this loop iteration
        # 8 bends
        total_length += 8 * bend_length
        # 4 vertical segments: v1, v2, v3, v4
        total_length += (
            spacing * (1 + 4 * i)
            + spacing * (3 + 4 * i)
            + spacing * (3 + 4 * i)
            + spacing * (5 + 4 * i)
        )
        # 4 horizontal segments: h1, h2, h3, h4
        total_length += _length + 2 * radius + spacing * (1 + 4 * i)
        total_length += _length + 2 * radius + spacing * (3 + 4 * i)
        total_length += _length + 2 * radius + spacing * (3 + 4 * i)
        total_length += _length + 2 * radius + spacing * (5 + 4 * i)

    c.add_port(name="o1", port=p1)
    c.add_port(name="o2", port=p2)

    c.info["length"] = total_length
    return c

spiral

spiral_archimedes

spiral_archimedes

spiral_archimedes(
    width: float = 1.0,
    n_turns: int = 5,
    separation: float = 2.0,
    angle_resolution: float = 2.0,
    layer: LayerSpec = "WG",
) -> Component

Returns an Archimedes spiral: r = (separation + width) / (2 * pi) * theta.

Parameters:

Name Type Description Default
width float

width of the spiral trace.

1.0
n_turns int

number of turns.

5
separation float

gap between adjacent traces.

2.0
angle_resolution float

degrees per point.

2.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/spirals/spiral_archimedes.py
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
@gf.cell_with_module_name(tags=["spirals"])
def spiral_archimedes(
    width: float = 1.0,
    n_turns: int = 5,
    separation: float = 2.0,
    angle_resolution: float = 2.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns an Archimedes spiral: r = (separation + width) / (2 * pi) * theta.

    Args:
        width: width of the spiral trace.
        n_turns: number of turns.
        separation: gap between adjacent traces.
        angle_resolution: degrees per point.
        layer: layer spec.
    """
    c = Component()
    growth_rate = (separation + width) / (2 * np.pi)

    theta_max = n_turns * 2 * np.pi
    n_points = int(np.ceil(theta_max / np.radians(angle_resolution))) + 1
    theta = np.linspace(0, theta_max, n_points)

    r_center = growth_rate * theta
    hw = width / 2.0

    # Compute tangent direction to find normals.
    # dr/dtheta = growth_rate, so tangent in Cartesian:
    #   tx = dr/dtheta * cos(theta) - r * sin(theta)
    #   ty = dr/dtheta * sin(theta) + r * cos(theta)
    dr = np.full_like(theta, growth_rate)
    tx = dr * np.cos(theta) - r_center * np.sin(theta)
    ty = dr * np.sin(theta) + r_center * np.cos(theta)
    t_len = np.sqrt(tx**2 + ty**2)
    t_len = np.where(t_len == 0, 1.0, t_len)
    # Unit normal (perpendicular to tangent, pointing inward).
    nx = -ty / t_len
    ny = tx / t_len

    x_center = r_center * np.cos(theta)
    y_center = r_center * np.sin(theta)

    outer_x = x_center - nx * hw
    outer_y = y_center - ny * hw
    inner_x = x_center + nx * hw
    inner_y = y_center + ny * hw

    # Find the point of the inner edge which has the minimum distance from the start of the outer edge.
    distances_from_edge_point = np.sqrt(
        (inner_x - outer_x[0]) ** 2 + (inner_y - outer_y[0]) ** 2
    )
    min_to_edge_id = np.argmin(distances_from_edge_point)
    # For a smooth spiral, the point of interest is the start of the inner edge, with distance = width.
    # If that is not the case, then end the spiral at the point which violates its smoothness.
    if min_to_edge_id != 0:
        outer_x = outer_x[min_to_edge_id:]
        outer_y = outer_y[min_to_edge_id:]
        inner_x = inner_x[min_to_edge_id:]
        inner_y = inner_y[min_to_edge_id:]

    # Close the polygon: outer path forward, inner path reversed.
    points_x = np.concatenate([outer_x, inner_x[::-1]])
    points_y = np.concatenate([outer_y, inner_y[::-1]])
    points = np.stack((points_x, points_y), axis=-1)

    c.add_polygon(points, layer=layer)
    return c

spiral_archimedes

spiral_double

spiral_double

spiral_double(
    min_bend_radius: float = 10.0,
    separation: float = 2.0,
    number_of_loops: float = 3,
    npoints: int = 1000,
    cross_section: CrossSectionSpec = "strip",
    bend: ComponentSpec = "bend_circular",
) -> gf.Component

Returns a spiral double (spiral in, and then out).

Parameters:

Name Type Description Default
min_bend_radius float

inner radius of the spiral.

10.0
separation float

separation between the loops.

2.0
number_of_loops float

number of loops per spiral.

3
npoints int

points for the spiral.

1000
cross_section CrossSectionSpec

cross-section to extrude the structure with.

'strip'
bend ComponentSpec

factory for the bends in the middle of the double spiral.

'bend_circular'
Source code in gdsfactory/components/spirals/spiral_double.py
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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral_double(
    min_bend_radius: float = 10.0,
    separation: float = 2.0,
    number_of_loops: float = 3,
    npoints: int = 1000,
    cross_section: CrossSectionSpec = "strip",
    bend: ComponentSpec = "bend_circular",
) -> gf.Component:
    """Returns a spiral double (spiral in, and then out).

    Args:
        min_bend_radius: inner radius of the spiral.
        separation: separation between the loops.
        number_of_loops: number of loops per spiral.
        npoints: points for the spiral.
        cross_section: cross-section to extrude the structure with.
        bend: factory for the bends in the middle of the double spiral.
    """
    component = gf.Component()

    bend = gf.get_component(
        bend, radius=min_bend_radius / 2, angle=180, cross_section=cross_section
    )
    bend1 = component.add_ref(bend)
    bend2 = component.add_ref(bend)
    bend2.connect("o2", bend1.ports["o1"], mirror=True)

    path = spiral_archimedean(
        min_bend_radius=min_bend_radius,
        separation=separation,
        number_of_loops=number_of_loops,
        npoints=npoints,
    )
    path.start_angle = 0
    path.end_angle = 0

    spiral = path.extrude(cross_section=cross_section)
    spiral1 = component.add_ref(spiral)
    spiral2 = component.add_ref(spiral)
    spiral2.mirror()

    spiral2.connect("o1", bend2.ports["o1"])
    spiral1.connect("o1", bend1.ports["o2"], mirror=True)

    component.add_port("o1", port=spiral1.ports["o2"])
    component.add_port("o2", port=spiral2.ports["o2"])
    component.info["length"] = float(path.length() + bend.info["length"]) * 2
    component.flatten()
    return component

spiral_double

spiral_fermat

spiral_fermat

spiral_fermat(
    width: float = 1.0,
    n_turns: int = 5,
    a: float = 5.0,
    angle_resolution: float = 2.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a Fermat spiral: r = a * sqrt(theta).

Parameters:

Name Type Description Default
width float

width of the spiral trace.

1.0
n_turns int

number of turns.

5
a float

scaling factor.

5.0
angle_resolution float

degrees per point.

2.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/spirals/spiral_fermat.py
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
@gf.cell_with_module_name(tags=["spirals"])
def spiral_fermat(
    width: float = 1.0,
    n_turns: int = 5,
    a: float = 5.0,
    angle_resolution: float = 2.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a Fermat spiral: r = a * sqrt(theta).

    Args:
        width: width of the spiral trace.
        n_turns: number of turns.
        a: scaling factor.
        angle_resolution: degrees per point.
        layer: layer spec.
    """
    c = Component()

    theta_max = n_turns * 2 * np.pi
    n_points = int(np.ceil(theta_max / np.radians(angle_resolution))) + 1
    # Start slightly above zero to avoid division issues at theta=0.
    theta = np.linspace(1e-6, theta_max, n_points)

    r_center = a * np.sqrt(theta)
    hw = width / 2.0

    # Tangent direction: dr/dtheta = a / (2 * sqrt(theta))
    dr = a / (2 * np.sqrt(theta))
    tx = dr * np.cos(theta) - r_center * np.sin(theta)
    ty = dr * np.sin(theta) + r_center * np.cos(theta)
    t_len = np.sqrt(tx**2 + ty**2)
    t_len = np.where(t_len == 0, 1.0, t_len)
    nx = -ty / t_len
    ny = tx / t_len

    x_center = r_center * np.cos(theta)
    y_center = r_center * np.sin(theta)

    outer_x = x_center + nx * hw
    outer_y = y_center + ny * hw
    inner_x = x_center - nx * hw
    inner_y = y_center - ny * hw

    points_x = np.concatenate([outer_x, inner_x[::-1]])
    points_y = np.concatenate([outer_y, inner_y[::-1]])
    points = np.stack((points_x, points_y), axis=-1)

    c.add_polygon(points, layer=layer)
    return c

spiral_fermat

spiral_inductor

spiral_inductor

spiral_inductor(
    width: float = 3.0,
    pitch: float = 3.0,
    turns: int = 16,
    outer_diameter: float = 800,
    tail: float = 50.0,
) -> Component

Generates a spiral inductor for superconducting resonator applications, particularly in qubit readout circuits.

This component creates a spiral inductor pattern commonly used in superconducting quantum circuits. The inductor is designed with a square spiral geometry, featuring inner and outer connection tails.

See J. M. Hornibrook, J. I. Colless, A. C. Mahoney, X. G. Croot, S. Blanvillain, H. Lu, A. C. Gossard, D. J. Reilly; Frequency multiplexing for readout of spin qubits. Appl. Phys. Lett. 10 March 2014; 104 (10): 103108. https://doi.org/10.1063/1.4868107

Parameters:

Name Type Description Default
width float

Width of the inductor track in microns. Determines the cross-sectional area of the inductor.

3.0
pitch float

Distance between adjacent inductor tracks in microns. Affects the coupling between turns.

3.0
turns int

Number of complete spiral turns. Higher values increase inductance but require more space.

16
outer_diameter float

Overall size of the inductor in microns. Defines the maximum extent of the spiral.

800
tail float

Length of the inner and outer connection tails in microns. Used for connecting to other circuit elements.

50.0

Returns:

Name Type Description
Component Component

A GDSFactory component containing the spiral inductor pattern.

Source code in gdsfactory/components/spirals/spiral_inductor.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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral_inductor(
    width: float = 3.0,
    pitch: float = 3.0,
    turns: int = 16,
    outer_diameter: float = 800,
    tail: float = 50.0,
) -> Component:
    """Generates a spiral inductor for superconducting resonator applications, particularly in qubit readout circuits.

    This component creates a spiral inductor pattern commonly used in superconducting quantum circuits.
    The inductor is designed with a square spiral geometry, featuring inner and outer connection tails.

    See J. M. Hornibrook, J. I. Colless, A. C. Mahoney, X. G. Croot, S. Blanvillain, H. Lu, A. C. Gossard, D. J. Reilly;
    Frequency multiplexing for readout of spin qubits. Appl. Phys. Lett. 10 March 2014; 104 (10): 103108. https://doi.org/10.1063/1.4868107

    Args:
        width: Width of the inductor track in microns. Determines the cross-sectional area of the inductor.
        pitch: Distance between adjacent inductor tracks in microns. Affects the coupling between turns.
        turns: Number of complete spiral turns. Higher values increase inductance but require more space.
        outer_diameter: Overall size of the inductor in microns. Defines the maximum extent of the spiral.
        tail: Length of the inner and outer connection tails in microns. Used for connecting to other circuit elements.

    Returns:
        Component: A GDSFactory component containing the spiral inductor pattern.
    """
    # create the outer tail
    P = gf.path.straight(length=tail)
    P.end_angle -= 90
    for i in range(turns * 2):
        P += gf.path.arc(radius=outer_diameter / 2 - (pitch + width) * i / 2, angle=180)

    # create the inner tail
    P.end_angle += 90  # "Turn" 90 deg (left)
    P += gf.path.straight(length=tail)

    # Store the path length in component info
    c = gf.path.extrude(P, layer=(1, 0), width=width)
    c.info["length"] = P.length()
    return c

spiral_inductor

spiral_logarithmic

spiral_logarithmic

spiral_logarithmic(
    width: float = 0.5,
    n_turns: int = 4,
    a: float = 1.0,
    b: float = 0.1,
    angle_resolution: float = 2.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a logarithmic spiral: r = a * exp(b * theta).

Parameters:

Name Type Description Default
width float

width of the spiral trace.

0.5
n_turns int

number of turns.

4
a float

initial radius scaling factor.

1.0
b float

growth rate.

0.1
angle_resolution float

degrees per point.

2.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/spirals/spiral_logarithmic.py
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
@gf.cell_with_module_name(tags=["spirals"])
def spiral_logarithmic(
    width: float = 0.5,
    n_turns: int = 4,
    a: float = 1.0,
    b: float = 0.1,
    angle_resolution: float = 2.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a logarithmic spiral: r = a * exp(b * theta).

    Args:
        width: width of the spiral trace.
        n_turns: number of turns.
        a: initial radius scaling factor.
        b: growth rate.
        angle_resolution: degrees per point.
        layer: layer spec.
    """
    c = Component()

    theta_max = n_turns * 2 * np.pi
    n_points = int(np.ceil(theta_max / np.radians(angle_resolution))) + 1
    theta = np.linspace(0, theta_max, n_points)

    r_center = a * np.exp(b * theta)
    hw = width / 2.0

    # Tangent direction: dr/dtheta = a * b * exp(b * theta) = b * r
    dr = b * r_center
    tx = dr * np.cos(theta) - r_center * np.sin(theta)
    ty = dr * np.sin(theta) + r_center * np.cos(theta)
    t_len = np.sqrt(tx**2 + ty**2)
    t_len = np.where(t_len == 0, 1.0, t_len)
    nx = -ty / t_len
    ny = tx / t_len

    x_center = r_center * np.cos(theta)
    y_center = r_center * np.sin(theta)

    outer_x = x_center + nx * hw
    outer_y = y_center + ny * hw
    inner_x = x_center - nx * hw
    inner_y = y_center - ny * hw

    points_x = np.concatenate([outer_x, inner_x[::-1]])
    points_y = np.concatenate([outer_y, inner_y[::-1]])
    points = np.stack((points_x, points_y), axis=-1)

    c.add_polygon(points, layer=layer)
    return c

spiral_logarithmic

spiral_racetrack

spiral_racetrack(
    min_radius: float | None = None,
    straight_length: float = 20.0,
    spacings: Floats = (2, 2, 3, 3, 2, 2),
    straight: ComponentSpec = straight,
    bend: ComponentSpec = bend_euler,
    bend_s: ComponentSpec = "bend_s",
    cross_section: CrossSectionSpec = "strip",
    cross_section_s: CrossSectionSpec | None = None,
    extra_90_deg_bend: bool = False,
    allow_min_radius_violation: bool = False,
) -> Component

Returns Racetrack-Spiral.

Parameters:

Name Type Description Default
min_radius float | None

smallest radius in um.

None
straight_length float

length of the straight segments in um.

20.0
spacings Floats

space between the center of neighboring waveguides in um.

(2, 2, 3, 3, 2, 2)
straight ComponentSpec

factory to generate the straight segments.

straight
bend ComponentSpec

factory to generate the bend segments.

bend_euler
bend_s ComponentSpec

factory to generate the s-bend segments.

'bend_s'
cross_section CrossSectionSpec

cross-section of the waveguides.

'strip'
cross_section_s CrossSectionSpec | None

cross-section of the s bend waveguide (optional).

None
extra_90_deg_bend bool

if True, we add an additional straight + 90 degree bent at the output, so the output port is looking down.

False
allow_min_radius_violation bool

if True, will allow the s-bend to have a smaller radius than the minimum radius.

False
Source code in gdsfactory/components/spirals/spiral_heater.py
 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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral_racetrack(
    min_radius: float | None = None,
    straight_length: float = 20.0,
    spacings: Floats = (2, 2, 3, 3, 2, 2),
    straight: ComponentSpec = straight,
    bend: ComponentSpec = bend_euler,
    bend_s: ComponentSpec = "bend_s",
    cross_section: CrossSectionSpec = "strip",
    cross_section_s: CrossSectionSpec | None = None,
    extra_90_deg_bend: bool = False,
    allow_min_radius_violation: bool = False,
) -> Component:
    """Returns Racetrack-Spiral.

    Args:
        min_radius: smallest radius in um.
        straight_length: length of the straight segments in um.
        spacings: space between the center of neighboring waveguides in um.
        straight: factory to generate the straight segments.
        bend: factory to generate the bend segments.
        bend_s: factory to generate the s-bend segments.
        cross_section: cross-section of the waveguides.
        cross_section_s: cross-section of the s bend waveguide (optional).
        extra_90_deg_bend: if True, we add an additional straight + 90 degree bent at the output, so the output port is looking down.
        allow_min_radius_violation: if True, will allow the s-bend to have a smaller radius than the minimum radius.
    """
    c = gf.Component()

    xs = gf.get_cross_section(cross_section)
    min_radius = min_radius or xs.radius
    assert min_radius

    _bend_s = gf.get_component(
        bend_s,
        size=(straight_length, -min_radius * 2 + 1 * spacings[0]),
        cross_section=cross_section_s or cross_section,
        allow_min_radius_violation=allow_min_radius_violation,
    )
    bend_s_ref = c << _bend_s
    c.info["length"] = _bend_s.info["length"]

    ports: list[Port] = []
    for port in bend_s_ref.ports:
        for i in range(len(spacings)):
            _bend = gf.get_component(
                bend,
                angle=180,
                radius=min_radius + np.sum(spacings[:i]),
                cross_section=cross_section,
            )
            bend_ref = c << _bend
            bend_ref.connect("o1", port)

            _straight = gf.get_component(
                straight, length=straight_length, cross_section=cross_section
            )
            straight_ref = c << _straight
            straight_ref.connect("o1", bend_ref.ports["o2"])
            port = straight_ref.ports["o2"]

            c.info["length"] += _bend.info["length"] + _straight.info["length"]
        ports.append(port)

    c.add_port("o1", port=ports[0])

    if extra_90_deg_bend:
        bend_ref = c << gf.get_component(
            bend,
            angle=90,
            radius=min_radius + np.sum(spacings),
            cross_section=cross_section,
        )
        bend_ref.connect("o1", ports[1])
        c.add_port("o2", port=bend_ref.ports["o2"])

    else:
        c.add_port("o2", port=ports[1])
    return c

spiral_racetrack

spiral_racetrack_fixed_length

spiral_racetrack_fixed_length(
    length: float = 1000,
    in_out_port_spacing: float = 150,
    n_straight_sections: int = 8,
    min_radius: float | None = None,
    min_spacing: float = 5.0,
    straight: ComponentSpec = straight,
    bend: ComponentSpec = "bend_circular",
    bend_s: ComponentSpec = "bend_s",
    cross_section: CrossSectionSpec = "strip",
    cross_section_s: CrossSectionSpec | None = None,
) -> Component

Returns Racetrack-Spiral with a specified total length.

The input and output ports are aligned in y. This class is meant to be used for generating interferometers with long waveguide lengths, where the most important parameter is the length difference between the arms.

Parameters:

Name Type Description Default
length float

total length of the spiral from input to output ports in um.

1000
in_out_port_spacing float

spacing between input and output ports of the spiral in um.

150
n_straight_sections int

total number of straight sections for the racetrack spiral. Has to be even.

8
min_radius float | None

smallest radius in um.

None
min_spacing float

minimum center-center spacing between adjacent waveguides.

5.0
straight ComponentSpec

factory to generate the straight segments.

straight
bend ComponentSpec

factory to generate the bend segments.

'bend_circular'
bend_s ComponentSpec

factory to generate the s-bend segments.

'bend_s'
cross_section CrossSectionSpec

cross-section of the waveguides.

'strip'
cross_section_s CrossSectionSpec | None

cross-section of the s bend waveguide (optional).

None
Source code in gdsfactory/components/spirals/spiral_heater.py
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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral_racetrack_fixed_length(
    length: float = 1000,
    in_out_port_spacing: float = 150,
    n_straight_sections: int = 8,
    min_radius: float | None = None,
    min_spacing: float = 5.0,
    straight: ComponentSpec = straight,
    bend: ComponentSpec = "bend_circular",
    bend_s: ComponentSpec = "bend_s",
    cross_section: CrossSectionSpec = "strip",
    cross_section_s: CrossSectionSpec | None = None,
) -> Component:
    """Returns Racetrack-Spiral with a specified total length.

    The input and output ports are aligned in y. This class is meant to
    be used for generating interferometers with long waveguide lengths, where
    the most important parameter is the length difference between the arms.

    Args:
        length: total length of the spiral from input to output ports in um.
        in_out_port_spacing: spacing between input and output ports of the spiral in um.
        n_straight_sections: total number of straight sections for the racetrack spiral. Has to be even.
        min_radius: smallest radius in um.
        min_spacing: minimum center-center spacing between adjacent waveguides.
        straight: factory to generate the straight segments.
        bend: factory to generate the bend segments.
        bend_s: factory to generate the s-bend segments.
        cross_section: cross-section of the waveguides.
        cross_section_s: cross-section of the s bend waveguide (optional).
    """
    c = gf.Component()

    xs_s_bend = cross_section_s or cross_section
    xs = gf.get_cross_section(xs_s_bend)
    min_radius = min_radius or xs.radius

    if np.mod(n_straight_sections, 2) != 0:
        raise ValueError("The number of straight sections has to be even!")

    # get the length of the straight sections to achieve the required length
    spacings = (min_spacing,) * (n_straight_sections // 2)

    straight_length = _req_straight_len(
        length=length,
        in_out_port_spacing=in_out_port_spacing,
        min_radius=min_radius,
        spacings=spacings,
        bend=bend,
        bend_s=bend_s,
        cross_section_s_bend=xs_s_bend,
        cross_section=cross_section,
    )

    _spiral = spiral_racetrack(
        min_radius=min_radius,
        straight_length=straight_length,
        spacings=spacings,
        straight=straight,
        bend=bend,
        bend_s=bend_s,
        cross_section=cross_section,
        cross_section_s=cross_section_s,
        extra_90_deg_bend=True,
    )

    spiral = c << _spiral
    c.info["length"] = _spiral.info["length"]
    c.info["straight_length"] = straight_length

    if spiral.ports["o1"].x > spiral.ports["o2"].x:
        spiral.mirror_x()

    # add a bit more to the spiral racetrack to make the in and out ports be aligned in y
    in_wg = c << gf.get_component(
        straight,
        length=spiral.ports["o1"].x - spiral.xmin,
        cross_section=cross_section,
    )
    if np.mod(n_straight_sections // 2, 2) == 1:
        in_wg.mirror_y()
    in_wg.connect("o1", spiral.ports["o1"])

    c.info["length"] += spiral.ports["o1"].x - spiral.xmin

    temp_component = Component()

    o2_temp = temp_component.add_port(
        name="o2_temp",
        center=(spiral.ports["o1"].x + in_out_port_spacing, spiral.ports["o1"].y),
        orientation=180,
        cross_section=gf.get_cross_section(xs_s_bend),
    )

    routes = route_bundle(
        c,
        spiral.ports["o2"],
        o2_temp,
        straight=straight,
        bend=bend,
        cross_section=xs_s_bend,
        radius=min_radius,
    )

    c.add_port(
        "o2",
        center=(spiral.ports["o1"].x + in_out_port_spacing, spiral.ports["o1"].y),
        orientation=0,
        cross_section=gf.get_cross_section(xs_s_bend),
    )
    c.add_port("o1", port=in_wg.ports["o2"])
    c.info["length"] += c.kcl.dbu * routes[0].length
    return c

spiral_racetrack_fixed_length

spiral_racetrack_heater_doped

spiral_racetrack_heater_doped(
    min_radius: float | None = None,
    straight_length: float = 30,
    spacing: float = 2,
    num: int = 8,
    straight: ComponentSpec = straight,
    bend: ComponentSpec = bend_euler,
    bend_s: ComponentSpec = "bend_s",
    waveguide_cross_section: CrossSectionSpec = "strip",
    heater_cross_section: CrossSectionSpec = "npp",
) -> Component

Returns spiral racetrack with a heater between the loops.

based on https://doi.org/10.1364/OL.400230 but with the heater between the loops.

Parameters:

Name Type Description Default
min_radius float | None

smallest radius in um. Defaults to the radius of the cross-section.

None
straight_length float

length of the straight segments in um.

30
spacing float

space between the center of neighboring waveguides in um.

2
num int

number.

8
straight ComponentSpec

factory to generate the straight segments.

straight
bend ComponentSpec

factory to generate the bend segments.

bend_euler
bend_s ComponentSpec

factory to generate the s-bend segments.

'bend_s'
waveguide_cross_section CrossSectionSpec

cross-section of the waveguides.

'strip'
heater_cross_section CrossSectionSpec

cross-section of the heater.

'npp'
Source code in gdsfactory/components/spirals/spiral_heater.py
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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral_racetrack_heater_doped(
    min_radius: float | None = None,
    straight_length: float = 30,
    spacing: float = 2,
    num: int = 8,
    straight: ComponentSpec = straight,
    bend: ComponentSpec = bend_euler,
    bend_s: ComponentSpec = "bend_s",
    waveguide_cross_section: CrossSectionSpec = "strip",
    heater_cross_section: CrossSectionSpec = "npp",
) -> Component:
    """Returns spiral racetrack with a heater between the loops.

    based on https://doi.org/10.1364/OL.400230 but with the heater between the loops.

    Args:
        min_radius: smallest radius in um. Defaults to the radius of the cross-section.
        straight_length: length of the straight segments in um.
        spacing: space between the center of neighboring waveguides in um.
        num: number.
        straight: factory to generate the straight segments.
        bend: factory to generate the bend segments.
        bend_s: factory to generate the s-bend segments.
        waveguide_cross_section: cross-section of the waveguides.
        heater_cross_section: cross-section of the heater.
    """
    xs = gf.get_cross_section(waveguide_cross_section)
    min_radius = min_radius or xs.radius or 0

    c = gf.Component()

    spiral = c << spiral_racetrack(
        min_radius=min_radius,
        straight_length=straight_length,
        spacings=(spacing,) * (num // 2)
        + (spacing + 1,) * 2
        + (spacing,) * (num // 2 - 2),
        straight=straight,
        bend=bend,
        bend_s=bend_s,
        cross_section=waveguide_cross_section,
    )

    heater_straight = gf.components.straight(
        straight_length, cross_section=heater_cross_section
    )

    heater_top = c << heater_straight
    heater_bot = c << heater_straight

    heater_bot.connect(
        "e1",
        spiral.ports["o1"].copy_polar(),
        allow_width_mismatch=True,
        allow_layer_mismatch=True,
        allow_type_mismatch=True,
    )
    heater_bot.movey(-spacing * (num // 2 - 1))
    heater_top.connect(
        "e1",
        spiral.ports["o2"].copy_polar(),
        allow_width_mismatch=True,
        allow_layer_mismatch=True,
        allow_type_mismatch=True,
    )
    heater_top.movey(spacing * (num // 2 - 1))

    c.add_ports(spiral.ports)
    c.add_ports(prefix="top_", ports=heater_top.ports)
    c.add_ports(prefix="bot_", ports=heater_bot.ports)

    top_ports = [p for p in c.ports if p.name and p.name.startswith("top_")]
    bot_ports = [p for p in c.ports if p.name and p.name.startswith("bot_")]
    if top_ports:
        c.create_pin(ports=top_ports, name="top")
    if bot_ports:
        c.create_pin(ports=bot_ports, name="bot")

    return c

spiral_racetrack_heater_doped

spiral_racetrack_heater_metal

spiral_racetrack_heater_metal(
    min_radius: float | None = None,
    straight_length: float = 30,
    spacing: float = 2,
    num: int = 8,
    straight: ComponentSpec = straight,
    bend: ComponentSpec = bend_euler,
    bend_s: ComponentSpec = "bend_s",
    waveguide_cross_section: CrossSectionSpec = "strip",
    heater_cross_section: CrossSectionSpec = "heater_metal",
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_heater_mtop",
) -> Component

Returns spiral racetrack with a heater above.

based on https://doi.org/10.1364/OL.400230 .

Parameters:

Name Type Description Default
min_radius float | None

smallest radius. Defaults to the radius of the cross-section.

None
straight_length float

length of the straight segments.

30
spacing float

space between the center of neighboring waveguides.

2
num int

number of loops.

8
straight ComponentSpec

factory to generate the straight segments.

straight
bend ComponentSpec

factory to generate the bend segments.

bend_euler
bend_s ComponentSpec

factory to generate the s-bend segments.

'bend_s'
waveguide_cross_section CrossSectionSpec

cross-section of the waveguides.

'strip'
heater_cross_section CrossSectionSpec

cross-section of the heater.

'heater_metal'
via_stack ComponentSpec | None

via stack to connect the heater to the metal layer.

'via_stack_heater_mtop'
Source code in gdsfactory/components/spirals/spiral_heater.py
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
@gf.cell_with_module_name(schematic_function=spiral_schematic, tags=["spirals"])
def spiral_racetrack_heater_metal(
    min_radius: float | None = None,
    straight_length: float = 30,
    spacing: float = 2,
    num: int = 8,
    straight: ComponentSpec = straight,
    bend: ComponentSpec = bend_euler,
    bend_s: ComponentSpec = "bend_s",
    waveguide_cross_section: CrossSectionSpec = "strip",
    heater_cross_section: CrossSectionSpec = "heater_metal",
    via_stack: ComponentSpec | None = "via_stack_heater_mtop",
) -> Component:
    """Returns spiral racetrack with a heater above.

    based on https://doi.org/10.1364/OL.400230 .

    Args:
        min_radius: smallest radius. Defaults to the radius of the cross-section.
        straight_length: length of the straight segments.
        spacing: space between the center of neighboring waveguides.
        num: number of loops.
        straight: factory to generate the straight segments.
        bend: factory to generate the bend segments.
        bend_s: factory to generate the s-bend segments.
        waveguide_cross_section: cross-section of the waveguides.
        heater_cross_section: cross-section of the heater.
        via_stack: via stack to connect the heater to the metal layer.
    """
    c = gf.Component()
    xs = gf.get_cross_section(waveguide_cross_section)
    min_radius = min_radius or xs.radius or 0

    spiral = c << spiral_racetrack(
        min_radius,
        straight_length,
        (spacing,) * num,
        straight,
        bend,
        bend_s,
        waveguide_cross_section,
    )

    heater_top = c << gf.components.straight(
        straight_length, cross_section=heater_cross_section
    )
    heater_top.connect(
        "e1",
        spiral.ports["o1"].copy().copy_polar(),
        allow_width_mismatch=True,
        allow_layer_mismatch=True,
        allow_type_mismatch=True,
    )
    heater_top.movey(spacing * num // 2)
    heater_bot = c << gf.components.straight(
        straight_length, cross_section=heater_cross_section
    )
    heater_bot.connect(
        "e1",
        spiral.ports["o2"].copy().copy_polar(),
        allow_width_mismatch=True,
        allow_layer_mismatch=True,
        allow_type_mismatch=True,
    )
    heater_bot.movey(-spacing * num // 2)

    heater_bend = c << gf.get_component(
        bend,
        angle=180,
        radius=min_radius + spacing * (num // 2 + 1),
        cross_section=heater_cross_section,
    )
    heater_bend.y = spiral.y
    heater_bend.x = spiral.x + min_radius + spacing * (num // 2 + 1)
    heater_top.connect("e1", heater_bend.ports["e1"])
    heater_bot.connect("e1", heater_bend.ports["e2"])

    c.add_ports(spiral.ports)

    if via_stack:
        via_stack = gf.get_component(via_stack)
        via_stack_top = c << via_stack
        via_stack_bot = c << via_stack
        via_stack_top.connect(
            "e3",
            heater_bot.ports["e2"],
            allow_layer_mismatch=True,
            allow_width_mismatch=True,
        )
        via_stack_bot.connect(
            "e3",
            heater_top.ports["e2"],
            allow_layer_mismatch=True,
            allow_width_mismatch=True,
        )

        p1 = via_stack_top.ports
        p2 = via_stack_bot.ports
        c.add_ports(p1, prefix="top_")
        c.add_ports(p2, prefix="bot_")

    else:
        c.add_port("e1", port=heater_bot["e2"])
        c.add_port("e2", port=heater_top["e2"])

    top_ports = [p for p in c.ports if p.name and p.name.startswith("top_")]
    bot_ports = [p for p in c.ports if p.name and p.name.startswith("bot_")]
    if top_ports:
        c.create_pin(ports=top_ports, name="top")
    if bot_ports:
        c.create_pin(ports=bot_ports, name="bot")
    e1_port = [p for p in c.ports if p.name == "e1"]
    e2_port = [p for p in c.ports if p.name == "e2"]
    if e1_port:
        c.create_pin(ports=e1_port, name="e1")
    if e2_port:
        c.create_pin(ports=e2_port, name="e2")

    return c

spiral_racetrack_heater_metal

spiral_rectangular

spiral_rectangular

spiral_rectangular(
    n_turns: int = 4,
    width: float = 1.0,
    start_length: float = 10.0,
    pitch: float = 3.0,
    layer: LayerSpec = "WG",
) -> Component

Returns a rectangular/Manhattan spiral as a polygon.

Each segment grows by pitch per half-turn, building an expanding rectangular spiral. The outline is created with the given width offset.

Parameters:

Name Type Description Default
n_turns int

number of full turns.

4
width float

width of the spiral trace.

1.0
start_length float

initial segment length.

10.0
pitch float

spacing between adjacent turns (center-to-center).

3.0
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/spirals/spiral_rectangular.py
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
@gf.cell_with_module_name(tags=["spirals"])
def spiral_rectangular(
    n_turns: int = 4,
    width: float = 1.0,
    start_length: float = 10.0,
    pitch: float = 3.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a rectangular/Manhattan spiral as a polygon.

    Each segment grows by pitch per half-turn, building an expanding
    rectangular spiral. The outline is created with the given width offset.

    Args:
        n_turns: number of full turns.
        width: width of the spiral trace.
        start_length: initial segment length.
        pitch: spacing between adjacent turns (center-to-center).
        layer: layer spec.
    """
    c = Component()

    # Build the center-line path of the rectangular spiral.
    # Directions cycle: +x, +y, -x, -y (right, up, left, down).
    dx = [1, 0, -1, 0]
    dy = [0, 1, 0, -1]

    # Each half-turn consists of 2 segments. The segment length increases
    # by pitch every 2 segments (each half-turn).
    n_segments = n_turns * 4
    lengths = []
    length = start_length
    for i in range(n_segments):
        lengths.append(length)
        if i % 2 == 1:
            length += pitch

    # Generate center-line points.
    cx, cy = [0.0], [0.0]
    x, y = 0.0, 0.0
    for i, seg_len in enumerate(lengths):
        direction = i % 4
        x += dx[direction] * seg_len
        y += dy[direction] * seg_len
        cx.append(x)
        cy.append(y)

    # Build outer and inner offset paths.
    hw = width / 2.0
    outer_points = []
    inner_points = []

    for i in range(len(cx) - 1):
        seg_dx = cx[i + 1] - cx[i]
        seg_dy = cy[i + 1] - cy[i]
        seg_len = np.sqrt(seg_dx**2 + seg_dy**2)
        if seg_len == 0:
            continue
        # Normal direction (perpendicular, pointing left of travel).
        nx = -seg_dy / seg_len
        ny = seg_dx / seg_len

        outer_points.append((cx[i] + nx * hw, cy[i] + ny * hw))
        outer_points.append((cx[i + 1] + nx * hw, cy[i + 1] + ny * hw))
        inner_points.append((cx[i] - nx * hw, cy[i] - ny * hw))
        inner_points.append((cx[i + 1] - nx * hw, cy[i + 1] - ny * hw))

    # Close the polygon: outer forward, inner reversed.
    points = outer_points + inner_points[::-1]
    c.add_polygon(points, layer=layer)
    return c

spiral_rectangular

superconductors

hline

hline

hline(
    length: float = 10.0,
    width: float = 0.5,
    layer: LayerSpec = "WG",
    port_type: str = "optical",
) -> Component

Creates a horizontal straight line component with ports on east and west sides.

This component is commonly used in photonic and superconducting circuits as a basic waveguide or transmission line element. It creates a rectangular polygon with ports at both ends for easy connection to other components.

Parameters:

Name Type Description Default
length float

Length of the line in microns. Must be positive.

10.0
width float

Width of the line in microns. Must be positive.

0.5
layer LayerSpec

Layer specification for the line (default: "WG"). Can be a string or a tuple of (layer, datatype).

'WG'
port_type str

Type of port to create (default: "optical"). Common values are "optical" for photonic circuits or "electrical" for superconducting circuits.

'optical'

Returns:

Name Type Description
Component Component

A gdsfactory Component object containing: - A rectangular polygon representing the line - Two ports named "o1" (west) and "o2" (east) - Component info with width and length values

Note
  • The line is centered vertically at y=0
  • Port "o1" is at x=0 with orientation 180° (west)
  • Port "o2" is at x=length with orientation 0° (east)
  • If length or width is 0 or negative, no polygon is created but ports are still added
Source code in gdsfactory/components/superconductors/hline.py
10
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
@gf.cell_with_module_name(tags=["superconductors"])
def hline(
    length: float = 10.0,
    width: float = 0.5,
    layer: LayerSpec = "WG",
    port_type: str = "optical",
) -> Component:
    """Creates a horizontal straight line component with ports on east and west sides.

    This component is commonly used in photonic and superconducting circuits as a basic
    waveguide or transmission line element. It creates a rectangular polygon with ports
    at both ends for easy connection to other components.

    Args:
        length: Length of the line in microns. Must be positive.
        width: Width of the line in microns. Must be positive.
        layer: Layer specification for the line (default: "WG"). Can be a string or a tuple of (layer, datatype).
        port_type: Type of port to create (default: "optical"). Common values are "optical" for photonic circuits
            or "electrical" for superconducting circuits.

    Returns:
        Component: A gdsfactory Component object containing:
            - A rectangular polygon representing the line
            - Two ports named "o1" (west) and "o2" (east)
            - Component info with width and length values

    Note:
        - The line is centered vertically at y=0
        - Port "o1" is at x=0 with orientation 180° (west)
        - Port "o2" is at x=length with orientation 0° (east)
        - If length or width is 0 or negative, no polygon is created but ports are still added
    """
    c = gf.Component()
    if length > 0 and width > 0:
        a = width / 2
        c.add_polygon([(0, -a), (length, -a), (length, a), (0, a)], layer=layer)

    c.add_port(
        name="o1",
        center=(0.0, 0.0),
        width=width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )
    c.add_port(
        name="o2",
        center=(length, 0.0),
        width=width,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    c.info["width"] = width
    c.info["length"] = length

    if port_type == "electrical":
        for p in list(c.ports):
            if p.name and p.port_type == "electrical":
                c.create_pin(ports=[p], name=p.name)

    return c

hline

optimal_90deg

optimal_90deg

optimal_90deg(
    width: float = 100,
    num_pts: int = 15,
    length_adjust: float = 1,
    layer: LayerSpec = (1, 0),
) -> Component

Returns optimally-rounded 90 degree bend that is sharp on the outer corner.

Parameters:

Name Type Description Default
width float

Width of the ports on either side of the bend.

100
num_pts int

The number of points comprising the curved section of the bend.

15
length_adjust float

Adjusts the length of the non-curved portion of the bend.

1
layer LayerSpec

Specific layer(s) to put polygon geometry on.

(1, 0)
Notes

Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510 Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in superconducting nanocircuits. Physical Review B, 84(17), 1-27.

Source code in gdsfactory/components/superconductors/optimal_90deg.py
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
@gf.cell_with_module_name(tags=["superconductors"])
def optimal_90deg(
    width: float = 100,
    num_pts: int = 15,
    length_adjust: float = 1,
    layer: LayerSpec = (1, 0),
) -> Component:
    """Returns optimally-rounded 90 degree bend that is sharp on the outer corner.

    Args:
        width: Width of the ports on either side of the bend.
        num_pts: The number of points comprising the curved section of the bend.
        length_adjust: Adjusts the length of the non-curved portion of the bend.
        layer: Specific layer(s) to put polygon geometry on.

    Notes:
        Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510
        Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in
        superconducting nanocircuits. Physical Review B, 84(17), 1-27.
    """
    D = Component()

    # Get points of ideal curve
    a = 2 * width
    v = np.logspace(-length_adjust, length_adjust, num_pts)
    xi = (
        a
        / 2.0
        * ((1 + 2 / np.pi * np.arcsinh(1 / v)) + 1j * (1 + 2 / np.pi * np.arcsinh(v)))
    )
    xpts: list[float | np.floating[Any]] = list(np.real(xi))
    ypts: list[float | np.floating[Any]] = list(np.imag(xi))

    # Add points for the rest of curve
    d = 2 * xpts[0]  # Farthest point out * 2, rounded to nearest 100
    xpts.append(width)
    ypts.append(d)
    xpts.append(0)
    ypts.append(d)
    xpts.append(0)
    ypts.append(0)
    xpts.append(d)
    ypts.append(0)
    xpts.append(d)
    ypts.append(width)
    xpts.append(xpts[0])
    ypts.append(ypts[0])

    D.add_polygon(
        list(zip(map(float, xpts), map(float, ypts), strict=False)), layer=layer
    )

    port_type = "electrical"

    D.add_port(
        name="e1",
        center=(float(a / 4), float(d)),
        width=a / 2,
        orientation=90,
        layer=layer,
        port_type=port_type,
    )
    D.add_port(
        name="e2",
        center=(float(d), float(a / 4)),
        width=a / 2,
        orientation=0,
        layer=layer,
        port_type=port_type,
    )

    for p in list(D.ports):
        if p.name and p.port_type == "electrical":
            D.create_pin(ports=[p], name=p.name)

    return D

optimal_90deg

optimal_hairpin

optimal_hairpin

optimal_hairpin(
    width: float = 0.2,
    pitch: float = 0.6,
    length: float = 10,
    turn_ratio: float = 4,
    num_pts: int = 50,
    layer: LayerSpec = (1, 0),
) -> Component

Returns an optimally-rounded hairpin geometry, with a 180 degree turn.

based on phidl.geometry

Parameters:

Name Type Description Default
width float

Width of the hairpin leads.

0.2
pitch float

Distance between the two hairpin leads. Must be greater than width.

0.6
length float

Length of the hairpin from the connectors to the opposite end of the curve.

10
turn_ratio float

int or float Specifies how much of the hairpin is dedicated to the 180 degree turn. A turn_ratio of 10 will result in 20% of the hairpin being comprised of the turn.

4
num_pts int

Number of points constituting the 180 degree turn.

50
layer LayerSpec

Specific layer(s) to put polygon geometry on.

(1, 0)
Notes

Hairpin pitch must be greater than width.

Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510 Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in superconducting nanocircuits. Physical Review B, 84(17), 1-27.

Source code in gdsfactory/components/superconductors/optimal_hairpin.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
 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
@gf.cell_with_module_name(tags=["superconductors"])
def optimal_hairpin(
    width: float = 0.2,
    pitch: float = 0.6,
    length: float = 10,
    turn_ratio: float = 4,
    num_pts: int = 50,
    layer: LayerSpec = (1, 0),
) -> Component:
    """Returns an optimally-rounded hairpin geometry, with a 180 degree turn.

    based on phidl.geometry

    Args:
        width: Width of the hairpin leads.
        pitch: Distance between the two hairpin leads. Must be greater than width.
        length: Length of the hairpin from the connectors to the opposite end of the curve.
        turn_ratio: int or float
            Specifies how much of the hairpin is dedicated to the 180 degree turn.
            A turn_ratio of 10 will result in 20% of the hairpin being comprised of the turn.
        num_pts: Number of points constituting the 180 degree turn.
        layer: Specific layer(s) to put polygon geometry on.

    Notes:
        Hairpin pitch must be greater than width.

        Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510
        Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in
        superconducting nanocircuits. Physical Review B, 84(17), 1-27.
    """
    # ==========================================================================
    #  Create the basic geometry
    # ==========================================================================
    a = (pitch + width) / 2
    y = -(pitch - width) / 2
    x = -pitch
    dl = width / (num_pts * 2)
    n = 0

    # Get points of ideal curve from conformal mapping
    # TODO This is an inefficient way of finding points that you need
    xpts = [x]
    ypts = [y]
    while (y < 0) & (n < 1e6):
        s = x + 1j * y
        w = np.sqrt(1 - np.exp(np.pi * s / a))
        wx = np.real(w)
        wy = np.imag(w)
        wx = wx / np.sqrt(wx**2 + wy**2)
        wy = wy / np.sqrt(wx**2 + wy**2)
        x = x + wx * dl
        y = y + wy * dl
        xpts.append(x)
        ypts.append(y)
        n += 1
    ypts[-1] = 0  # Set last point be on the x=0 axis for sake of cleanliness
    ds_factor = len(xpts) // num_pts
    xpts = xpts[::-ds_factor]
    xpts = xpts[::-1]  # This looks confusing, but it's just flipping the arrays around
    ypts = ypts[::-ds_factor]
    ypts = ypts[::-1]  # so the last point is guaranteed to be included when downsampled

    # Add points for the rest of meander
    xpts.append(xpts[-1] + turn_ratio * width)
    ypts.append(0)
    xpts.append(xpts[-1])
    ypts.append(-a)
    xpts.append(xpts[0])
    ypts.append(-a)
    xpts.append(max(xpts) - length)
    ypts.append(-a)
    xpts.append(xpts[-1])
    ypts.append(-a + width)
    xpts.append(xpts[0])
    ypts.append(ypts[0])

    xpts_np = snap_to_grid(xpts)
    ypts_np = snap_to_grid(ypts)

    # ==========================================================================
    #  Create a blank device, add the geometry, and define the ports
    # ==========================================================================
    c = Component()
    c.add_polygon(list(zip(xpts_np, +ypts_np, strict=False)), layer=layer)
    c.add_polygon(list(zip(xpts_np, -ypts_np, strict=False)), layer=layer)
    port_type = "electrical"

    xports = float(np.min(xpts_np))
    yports = -a + width / 2
    c.add_port(
        name="e1",
        center=(xports, -yports),
        width=width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )
    c.add_port(
        name="e2",
        center=(xports, yports),
        width=width,
        orientation=180,
        layer=layer,
        port_type=port_type,
    )

    for p in list(c.ports):
        if p.name and p.port_type == "electrical":
            c.create_pin(ports=[p], name=p.name)

    return c

optimal_hairpin

optimal_step

optimal_step

optimal_step(
    start_width: float = 10,
    end_width: float = 22,
    num_pts: int = 50,
    width_tol: float = 0.001,
    anticrowding_factor: float = 1.2,
    symmetric: bool = False,
    layer: LayerSpec = (1, 0),
) -> Component

Returns an optimally-rounded step geometry.

Parameters:

Name Type Description Default
start_width float

Width of the connector on the left end of the step.

10
end_width float

Width of the connector on the right end of the step.

22
num_pts int

number of points comprising the entire step geometry.

50
width_tol float

Point at which to terminate the calculation of the optimal step

0.001
anticrowding_factor float

Factor to reduce current crowding by elongating the structure and reducing the curvature

1.2
symmetric bool

If True, adds a mirrored copy of the step across the x-axis to the geometry and adjusts the width of the ports.

False
layer LayerSpec

layer spec to put polygon geometry on.

(1, 0)

based on phidl.geometry Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510 Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in superconducting nanocircuits. Physical Review B, 84(17), 1-27.

Source code in gdsfactory/components/superconductors/optimal_step.py
 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
 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
@gf.cell_with_module_name(tags=["superconductors"])
def optimal_step(
    start_width: float = 10,
    end_width: float = 22,
    num_pts: int = 50,
    width_tol: float = 1e-3,
    anticrowding_factor: float = 1.2,
    symmetric: bool = False,
    layer: LayerSpec = (1, 0),
) -> Component:
    """Returns an optimally-rounded step geometry.

    Args:
        start_width: Width of the connector on the left end of the step.
        end_width: Width of the connector on the right end of the step.
        num_pts: number of points comprising the entire step geometry.
        width_tol: Point at which to terminate the calculation of the optimal step
        anticrowding_factor: Factor to reduce current crowding by elongating
            the structure and reducing the curvature
        symmetric: If True, adds a mirrored copy of the step across the x-axis to the
            geometry and adjusts the width of the ports.
        layer: layer spec to put polygon geometry on.

    based on phidl.geometry
    Optimal structure from https://doi.org/10.1103/PhysRevB.84.174510
    Clem, J., & Berggren, K. (2011). Geometry-dependent critical currents in
    superconducting nanocircuits. Physical Review B, 84(17), 1-27.
    """

    def step_points(eta: float, W: complex, a: complex) -> tuple[float, float]:
        """Returns step points.

        Returns points from a unit semicircle in the w (= u + iv) plane to
        the optimal curve in the zeta (= x + iy) plane which transitions
        a wire from a width of 'W' to a width of 'a'
        eta takes value 0 to pi
        """
        gamma = (a**2 + W**2) / (a**2 - W**2)
        w = np.exp(1j * eta)
        zeta = (
            4
            * 1j
            / np.pi
            * (
                W * np.arctan(np.sqrt((w - gamma) / (gamma + 1)))
                + a * np.arctan(np.sqrt((gamma - 1) / (w - gamma)))
            )
        )
        return np.real(zeta), np.imag(zeta)

    def invert_step_point(
        x_desired: float = -10,
        y_desired: float | None = None,
        W: float = 1,
        a: float = 2,
    ) -> tuple[float, float]:
        """Finds the eta associated with x_desired or y_desired along the optimal curve."""

        def fh(eta: float) -> float:
            guessed_x, guessed_y = step_points(eta, W=W + 0j, a=a + 0j)
            if y_desired is None:
                return (guessed_x - x_desired) ** 2  # Error relative to x_desired
            return (guessed_y - y_desired) ** 2  # Error relative to y_desired

        from scipy.optimize import fminbound

        # Minimize error to find optimal eta
        found_eta = fminbound(fh, 0, np.pi)
        return step_points(found_eta, W=W + 0j, a=a + 0j)

    if start_width > end_width:
        reverse = True
        start_width, end_width = end_width, start_width
    else:
        reverse = False

    D = Component()
    xpts: list[float] = []
    ypts: list[float] = []
    if start_width == end_width:  # Just return a square
        if symmetric:
            ypts = [
                -start_width / 2,
                start_width / 2,
                start_width / 2,
                -start_width / 2,
            ]
            xpts = [0, 0, start_width, start_width]
        if not symmetric:
            ypts = [0, start_width, start_width, 0]
            xpts = [0, 0, start_width, start_width]
        D.info["num_squares"] = 1
    else:
        xmin, _ = invert_step_point(
            y_desired=start_width * (1 + width_tol), W=start_width, a=end_width
        )
        xmax, _ = invert_step_point(
            y_desired=end_width * (1 - width_tol), W=start_width, a=end_width
        )

        xpts = list(np.linspace(xmin, xmax, num_pts))
        for x in xpts:
            x, y = invert_step_point(x_desired=x, W=start_width, a=end_width)
            ypts.append(y)

        ypts[-1] = end_width
        ypts[0] = start_width
        y_num_sq = np.array(ypts)
        x_num_sq = np.array(xpts)

        if not symmetric:
            xpts.append(xpts[-1])
            ypts.append(0)
            xpts.append(xpts[0])
            ypts.append(0)
        else:
            xpts += list(xpts[::-1])
            ypts += [-y for y in ypts[::-1]]
            xpts = [x / 2 for x in xpts]
            ypts = [y / 2 for y in ypts]

        # anticrowding_factor stretches the wire out; a stretched wire is a
        # gentler transition, so there's less chance of current crowding if
        # the fabrication isn't perfect but as a result, the wire isn't as
        # short as it could be
        xpts = [x * anticrowding_factor for x in xpts]

        if reverse:
            xpts = [-x for x in xpts]
            start_width, end_width = end_width, start_width

        D.info["num_squares"] = float(
            np.round(
                np.sum(np.diff(x_num_sq) / ((y_num_sq[:-1] + y_num_sq[1:]) / 2)), 3
            )
        )

    D.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)
    port_type = "electrical"
    if not symmetric:
        D.add_port(
            name="e1",
            center=(min(xpts), start_width / 2),
            width=start_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        D.add_port(
            name="e2",
            center=(max(xpts), end_width / 2),
            width=end_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )
    if symmetric:
        D.add_port(
            name="e1",
            center=(min(xpts), 0),
            width=start_width,
            orientation=180,
            layer=layer,
            port_type=port_type,
        )
        D.add_port(
            name="e2",
            center=(max(xpts), 0),
            width=end_width,
            orientation=0,
            layer=layer,
            port_type=port_type,
        )

    for p in list(D.ports):
        if p.name and p.port_type == "electrical":
            D.create_pin(ports=[p], name=p.name)

    return D

optimal_step

snspd

snspd

snspd(
    wire_width: float = 0.2,
    wire_pitch: float = 0.6,
    size: Size = (10, 8),
    num_squares: int | None = None,
    turn_ratio: float = 4,
    terminals_same_side: bool = False,
    layer: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component

Creates an optimally-rounded SNSPD.

Parameters:

Name Type Description Default
wire_width float

Width of the wire.

0.2
wire_pitch float

Distance between two adjacent wires. Must be greater than width.

0.6
size Size

Float2 (width, height) of the rectangle formed by the outer boundary of the SNSPD.

(10, 8)
num_squares int | None

int | None = None Total number of squares inside the SNSPD length.

None
turn_ratio float

float Specifies how much of the SNSPD width is dedicated to the 180 degree turn. A turn_ratio of 10 will result in 20% of the width being comprised of the turn.

4
terminals_same_side bool

If True, both ports will be located on the same side of the SNSPD.

False
layer LayerSpec

layer spec to put polygon geometry on.

(1, 0)
port_type str

type of port to add to the component.

'electrical'
Source code in gdsfactory/components/superconductors/snspd.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@gf.cell_with_module_name(tags=["superconductors"])
def snspd(
    wire_width: float = 0.2,
    wire_pitch: float = 0.6,
    size: Size = (10, 8),
    num_squares: int | None = None,
    turn_ratio: float = 4,
    terminals_same_side: bool = False,
    layer: LayerSpec = (1, 0),
    port_type: str = "electrical",
) -> Component:
    """Creates an optimally-rounded SNSPD.

    Args:
        wire_width: Width of the wire.
        wire_pitch: Distance between two adjacent wires. Must be greater than `width`.
        size: Float2
            (width, height) of the rectangle formed by the outer boundary of the
            SNSPD.
        num_squares: int | None = None
            Total number of squares inside the SNSPD length.
        turn_ratio: float
            Specifies how much of the SNSPD width is dedicated to the 180 degree
            turn. A `turn_ratio` of 10 will result in 20% of the width being
            comprised of the turn.
        terminals_same_side: If True, both ports will be located on the same side of the SNSPD.
        layer: layer spec to put polygon geometry on.
        port_type: type of port to add to the component.

    """
    xsize, ysize = size
    if num_squares is not None:
        if xsize is None and ysize is None:
            xy = np.sqrt(num_squares * wire_pitch * wire_width)
            xsize, ysize = xy, xy
        elif xsize is None:
            xsize = num_squares * wire_pitch * wire_width / ysize
        elif ysize is None:
            ysize = num_squares * wire_pitch * wire_width / xsize

    num_meanders = int(np.ceil(ysize / wire_pitch))

    D = Component()
    hairpin = optimal_hairpin(
        width=wire_width,
        pitch=wire_pitch,
        turn_ratio=turn_ratio,
        length=xsize / 2,
        num_pts=20,
        layer=layer,
    )

    if (not terminals_same_side and (num_meanders % 2) == 0) or (
        terminals_same_side and (num_meanders % 2) == 1
    ):
        num_meanders += 1

    port_type = "electrical"

    start_nw = D.add_ref(
        gf.c.compass(size=(xsize / 2, wire_width), layer=layer, port_type=port_type)
    )
    hp_prev = D.add_ref(hairpin)
    hp_prev.connect("e1", start_nw.ports["e3"])
    alternate = True
    last_port: Port | None = None
    for _n in range(2, num_meanders):
        hp = D.add_ref(hairpin)
        if alternate:
            hp.connect("e2", hp_prev.ports["e2"])
        else:
            hp.connect("e1", hp_prev.ports["e1"])
        last_port = hp.ports["e2"] if terminals_same_side else hp.ports["e1"]
        hp_prev = hp
        alternate = not alternate

    finish_se = D.add_ref(
        gf.c.compass(size=(xsize / 2, wire_width), layer=layer, port_type=port_type)
    )
    if last_port is not None:
        finish_se.connect("e3", last_port)

    D.add_port(port=start_nw.ports["e1"], name="e1")
    D.add_port(port=finish_se.ports["e1"], name="e2")

    for p in list(D.ports):
        if p.name and p.port_type == "electrical":
            D.create_pin(ports=[p], name=p.name)

    D.info["num_squares"] = num_meanders * (xsize / wire_width)
    D.info["area"] = xsize * ysize
    D.info["xsize"] = xsize
    D.info["ysize"] = ysize
    D.flatten()
    return D

snspd

ytron_round

ytron_round(
    rho: float = 1,
    arm_lengths: tuple[float, float] = (500, 300),
    source_length: float = 500,
    arm_widths: tuple[float, float] = (200, 200),
    theta: float = 2.5,
    theta_resolution: float = 10,
    layer: LayerSpec = "WG",
) -> Component

Ytron structure for superconducting nanowires.

McCaughan, A. N., Abebe, N. S., Zhao, Q.-Y. & Berggren, K. K. Using Geometry To Sense Current. Nano Lett. 16, 7626-7631 (2016). http://dx.doi.org/10.1021/acs.nanolett.6b03593

Parameters:

Name Type Description Default
rho float

Radius of curvature of ytron intersection point.

1
arm_lengths tuple[float, float]

Lengths of the left and right arms of the yTron, respectively.

(500, 300)
source_length float

Length of the source of the yTron.

500
arm_widths tuple[float, float]

Widths of the left and right arms of the yTron, respectively.

(200, 200)
theta float

Angle between the two yTron arms.

2.5
theta_resolution float

Angle resolution for curvature of ytron intersection point.

10
layer LayerSpec

Specific layer(s) to put polygon geometry on.

'WG'

Returns:

Type Description
Component

Component containing a yTron geometry.

Source code in gdsfactory/components/superconductors/ytron.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
 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
@gf.cell_with_module_name(tags=["superconductors"])
def ytron_round(
    rho: float = 1,
    arm_lengths: tuple[float, float] = (500, 300),
    source_length: float = 500,
    arm_widths: tuple[float, float] = (200, 200),
    theta: float = 2.5,
    theta_resolution: float = 10,
    layer: LayerSpec = "WG",
) -> Component:
    """Ytron structure for superconducting nanowires.

    McCaughan, A. N., Abebe, N. S., Zhao, Q.-Y. & Berggren, K. K.
    Using Geometry To Sense Current. Nano Lett. 16, 7626-7631 (2016).
    http://dx.doi.org/10.1021/acs.nanolett.6b03593

    Args:
        rho: Radius of curvature of ytron intersection point.
        arm_lengths: Lengths of the left and right arms of the yTron, respectively.
        source_length: Length of the source of the yTron.
        arm_widths: Widths of the left and right arms of the yTron, respectively.
        theta: Angle between the two yTron arms.
        theta_resolution: Angle resolution for curvature of ytron intersection point.
        layer: Specific layer(s) to put polygon geometry on.

    Returns:
        Component containing a yTron geometry.
    """
    # ==========================================================================
    #  Create the basic geometry
    # ==========================================================================
    theta = theta * pi / 180
    theta_resolution = theta_resolution * pi / 180
    thetalist = np.linspace(
        -(pi - theta), -theta, int((pi - 2 * theta) / theta_resolution) + 2
    )
    semicircle_x = rho * cos(thetalist)
    semicircle_y = rho * sin(thetalist) + rho

    # Rest of yTron
    xc = rho * cos(theta)
    yc = rho * sin(theta)
    arm_x_left = arm_lengths[0] * sin(theta)
    arm_y_left = arm_lengths[0] * cos(theta)
    arm_x_right = arm_lengths[1] * sin(theta)
    arm_y_right = arm_lengths[1] * cos(theta)

    # Write out x and y coords for yTron polygon
    xpts = semicircle_x.tolist() + [
        xc + arm_x_right,
        xc + arm_x_right + arm_widths[1],
        xc + arm_widths[1],
        xc + arm_widths[1],
        0,
        -(xc + arm_widths[0]),
        -(xc + arm_widths[0]),
        -(xc + arm_x_left + arm_widths[0]),
        -(xc + arm_x_left),
    ]
    ypts = semicircle_y.tolist() + [
        yc + arm_y_right,
        yc + arm_y_right,
        yc,
        yc - source_length,
        yc - source_length,
        yc - source_length,
        yc,
        yc + arm_y_left,
        yc + arm_y_left,
    ]

    # ==========================================================================
    #  Create a blank device, add the geometry, and define the ports
    # ==========================================================================
    c = gf.Component()
    c.add_polygon(list(zip(xpts, ypts, strict=True)), layer=layer)
    c.add_port(
        name="left",
        center=(-(xc + arm_x_left + arm_widths[0] / 2), yc + arm_y_left),
        width=arm_widths[0],
        orientation=90,
        layer=layer,
    )
    c.add_port(
        name="right",
        center=(xc + arm_x_right + arm_widths[1] / 2, yc + arm_y_right),
        width=arm_widths[1],
        orientation=90,
        layer=layer,
    )
    c.add_port(
        name="source",
        center=(0 + (arm_widths[1] - arm_widths[0]) / 2, -source_length + yc),
        width=arm_widths[0] + arm_widths[1] + 2 * xc,
        orientation=-90,
        layer=layer,
    )

    # ==========================================================================
    #  Record any parameters you may want to access later
    # ==========================================================================
    c.info["rho"] = rho
    c.info["left_width"] = arm_widths[0]
    c.info["right_width"] = arm_widths[1]
    c.info["source_width"] = arm_widths[0] + arm_widths[1] + 2 * xc
    return c

ytron_round

tapers

ramp

ramp

ramp(
    length: float = 10.0,
    width1: float = 5.0,
    width2: float | None = 8.0,
    layer: LayerSpec = "WG",
) -> Component

Return a ramp component.

Based on phidl.

Parameters:

Name Type Description Default
length float

Length of the ramp section.

10.0
width1 float

Width of the start of the ramp section.

5.0
width2 float | None

Width of the end of the ramp section (defaults to width1).

8.0
layer LayerSpec

Specific layer to put polygon geometry on.

'WG'
Source code in gdsfactory/components/tapers/ramp.py
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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def ramp(
    length: float = 10.0,
    width1: float = 5.0,
    width2: float | None = 8.0,
    layer: LayerSpec = "WG",
) -> Component:
    """Return a ramp component.

    Based on phidl.

    Args:
        length: Length of the ramp section.
        width1: Width of the start of the ramp section.
        width2: Width of the end of the ramp section (defaults to width1).
        layer: Specific layer to put polygon geometry on.
    """
    if width2 is None:
        width2 = width1
    xpts = [0, length, length, 0]
    ypts = [width1, width2, 0, 0]
    c = Component()
    c.add_polygon(tuple(zip(xpts, ypts, strict=False)), layer=layer)
    c.add_port(
        name="o1", center=(0, width1 / 2), width=width1, orientation=180, layer=layer
    )
    c.add_port(
        name="o2",
        center=(length, width2 / 2),
        width=width2,
        orientation=0,
        layer=layer,
    )
    return c

ramp

taper

taper

taper(
    length: float = 10.0,
    width1: float = 0.5,
    width2: float | None = None,
    layer: LayerSpec | None = None,
    port: Port | None = None,
    with_two_ports: bool = True,
    cross_section: CrossSectionSpec = "strip",
    port_names: tuple[str, str] = ("o1", "o2"),
    port_types: tuple[str, str] = ("optical", "optical"),
    with_bbox: bool = True,
) -> Component

Linear taper, which tapers only the main cross section section.

Parameters:

Name Type Description Default
length float

taper length.

10.0
width1 float

width of the west/left port.

0.5
width2 float | None

width of the east/right port. Defaults to width1.

None
layer LayerSpec | None

layer for the taper.

None
port Port | None

can taper from a port instead of defining width1.

None
with_two_ports bool

includes a second port. False for terminator and edge coupler fiber interface.

True
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
port_names tuple[str, str]

input and output port names. Second name only used if with_two_ports.

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

input and output port types. Second type only used if with_two_ports.

('optical', 'optical')
with_bbox bool

box in bbox_layers and bbox_offsets to avoid DRC sharp edges.

True
Source code in gdsfactory/components/tapers/taper.py
 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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def taper(
    length: float = 10.0,
    width1: float = 0.5,
    width2: float | None = None,
    layer: LayerSpec | None = None,
    port: Port | None = None,
    with_two_ports: bool = True,
    cross_section: CrossSectionSpec = "strip",
    port_names: tuple[str, str] = ("o1", "o2"),
    port_types: tuple[str, str] = ("optical", "optical"),
    with_bbox: bool = True,
) -> Component:
    """Linear taper, which tapers only the main cross section section.

    Args:
        length: taper length.
        width1: width of the west/left port.
        width2: width of the east/right port. Defaults to width1.
        layer: layer for the taper.
        port: can taper from a port instead of defining width1.
        with_two_ports: includes a second port.
            False for terminator and edge coupler fiber interface.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
        port_names: input and output port names. Second name only used if with_two_ports.
        port_types: input and output port types. Second type only used if with_two_ports.
        with_bbox: box in bbox_layers and bbox_offsets to avoid DRC sharp edges.
    """
    if len(port_types) != 2:
        raise ValueError("port_types should have two elements")

    x1 = gf.get_cross_section(cross_section, width=width1)
    if width2:
        width2 = gf.snap.snap_to_grid2x(width2)
        x2 = gf.get_cross_section(cross_section, width=width2)
    else:
        x2 = x1

    width1 = x1.width
    width2 = x2.width
    width_max = max([width1, width2])
    if layer:
        x = gf.get_cross_section(cross_section, width=width_max, layer=layer)
    else:
        x = gf.get_cross_section(cross_section, width=width_max)
    layer = layer or x.layer
    assert layer is not None

    if isinstance(port, gf.Port):
        width1 = port.width

    width2 = width2 or width1
    c = gf.Component()
    y1 = width1 / 2
    y2 = width2 / 2

    if length:
        p1 = gf.kdb.DPolygon(
            [
                gf.kdb.DPoint(0, y1),
                gf.kdb.DPoint(length, y2),
                gf.kdb.DPoint(length, -y2),
                gf.kdb.DPoint(0, -y1),
            ]
        )
        c.add_polygon(p1, layer=layer)

        for s1, s2 in zip(x1.sections[1:], x2.sections[1:], strict=False):
            y1 = s1.width / 2
            y2 = s2.width / 2
            offset1 = s1.offset
            offset2 = s2.offset
            p1 = gf.kdb.DPolygon(
                [
                    gf.kdb.DPoint(0, offset1 + y1),
                    gf.kdb.DPoint(length, offset2 + y2),
                    gf.kdb.DPoint(length, offset2 - y2),
                    gf.kdb.DPoint(0, offset1 - y1),
                ]
            )
            c.add_polygon(p1, layer=s1.layer)

    if with_bbox:
        x.add_bbox(c)
    c.add_port(
        name=port_names[0],
        center=(0, 0),
        width=width1,
        orientation=180,
        layer=layer,
        cross_section=x1,
        port_type=port_types[0],
    )
    if with_two_ports:
        c.add_port(
            name=port_names[1],
            center=(length, 0),
            width=width2,
            orientation=0,
            layer=layer,
            cross_section=x2,
            port_type=port_types[1],
        )

    c.info["length"] = length
    c.info["width1"] = float(width1)
    c.info["width2"] = float(width2)
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=port.name)
    return c

taper_nc_sc

taper_nc_sc(
    width1: float = 1,
    width2: float = 0.5,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Taper from nitride to strip.

Parameters:

Name Type Description Default
width1 float

nitride width.

1
width2 float

silicon width.

0.5
length float

taper length.

20
layer_wg LayerSpec

nitride layer.

'WG'
layer_nitride LayerSpec

strip layer.

'WGN'
width_tip_nitride float

tip width for nitride.

0.15
width_tip_silicon float

tip width for strip.

0.15
cross_section CrossSectionSpec

cross_section specification.

'strip'
Source code in gdsfactory/components/tapers/taper.py
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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_nc_sc(
    width1: float = 1,
    width2: float = 0.5,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Taper from nitride to strip.

    Args:
        width1: nitride width.
        width2: silicon width.
        length: taper length.
        layer_wg: nitride layer.
        layer_nitride: strip layer.
        width_tip_nitride: tip width for nitride.
        width_tip_silicon: tip width for strip.
        cross_section: cross_section specification.
    """
    c = gf.Component()
    taper = taper_sc_nc(
        width1=width2,
        width2=width1,
        length=length,
        layer_wg=layer_wg,
        layer_nitride=layer_nitride,
        width_tip_nitride=width_tip_nitride,
        width_tip_silicon=width_tip_silicon,
        cross_section=cross_section,
    )
    c.copy_child_info(taper)
    ref = c << taper
    ref.mirror_x()
    c.add_ports(ref.ports)
    c.auto_rename_ports()
    c.flatten()
    return c

taper_sc_nc

taper_sc_nc(
    width1: float = 0.5,
    width2: float = 1,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Taper from strip to nitride.

Parameters:

Name Type Description Default
width1 float

strip width.

0.5
width2 float

nitride width.

1
length float

taper length.

20
layer_wg LayerSpec

strip layer.

'WG'
layer_nitride LayerSpec

nitride layer.

'WGN'
width_tip_nitride float

tip width for nitride.

0.15
width_tip_silicon float

tip width for strip.

0.15
cross_section CrossSectionSpec

cross_section specification.

'strip'
Source code in gdsfactory/components/tapers/taper.py
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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_sc_nc(
    width1: float = 0.5,
    width2: float = 1,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Taper from strip to nitride.

    Args:
        width1: strip width.
        width2: nitride width.
        length: taper length.
        layer_wg: strip layer.
        layer_nitride: nitride layer.
        width_tip_nitride: tip width for nitride.
        width_tip_silicon: tip width for strip.
        cross_section: cross_section specification.
    """
    return taper_strip_to_ridge(
        layer_wg=layer_wg,
        layer_slab=layer_nitride,
        length=length,
        width1=width1,
        width2=width_tip_silicon,
        w_slab1=width_tip_nitride,
        w_slab2=width2,
        use_slab_port=True,
        cross_section=cross_section,
    )

taper_strip_to_ridge

taper_strip_to_ridge(
    length: float = 10.0,
    width1: float = 0.5,
    width2: float = 0.5,
    w_slab1: float = 0.15,
    w_slab2: float = 6.0,
    layer_wg: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    cross_section: CrossSectionSpec = "strip",
    use_slab_port: bool = False,
    slab_port_layer: LayerSpec | None = None,
) -> Component

Linear taper from strip to rib.

Parameters:

Name Type Description Default
length float

taper length (um).

10.0
width1 float

in um.

0.5
width2 float

in um.

0.5
w_slab1 float

slab width in um.

0.15
w_slab2 float

slab width in um.

6.0
layer_wg LayerSpec

for input waveguide.

'WG'
layer_slab LayerSpec

for output waveguide with slab.

'SLAB90'
cross_section CrossSectionSpec

for input waveguide.

'strip'
use_slab_port bool

if True adds a second port for the slab.

False
slab_port_layer LayerSpec | None

if specified, overrides the layer for the slab port.

None
                  __________________________
                 /           |
         _______/____________|______________
               /             |
   width1     |w_slab1       | w_slab2  width2
         ______\_____________|______________
                \            |
                 \__________________________
Source code in gdsfactory/components/tapers/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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_strip_to_ridge(
    length: float = 10.0,
    width1: float = 0.5,
    width2: float = 0.5,
    w_slab1: float = 0.15,
    w_slab2: float = 6.0,
    layer_wg: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    cross_section: CrossSectionSpec = "strip",
    use_slab_port: bool = False,
    slab_port_layer: LayerSpec | None = None,
) -> Component:
    r"""Linear taper from strip to rib.

    Args:
        length: taper length (um).
        width1: in um.
        width2: in um.
        w_slab1: slab width in um.
        w_slab2: slab width in um.
        layer_wg: for input waveguide.
        layer_slab: for output waveguide with slab.
        cross_section: for input waveguide.
        use_slab_port: if True adds a second port for the slab.
        slab_port_layer: if specified, overrides the layer for the slab port.

    ```text
                      __________________________
                     /           |
             _______/____________|______________
                   /             |
       width1     |w_slab1       | w_slab2  width2
             ______\_____________|______________
                    \            |
                     \__________________________
    ```

    """
    xs = gf.get_cross_section(cross_section)

    taper_wg = taper(
        length=length,
        width1=width1,
        width2=width2,
        cross_section=cross_section,
        layer=layer_wg,
    )
    taper_slab = taper(
        length=length,
        width1=w_slab1,
        width2=w_slab2,
        cross_section=cross_section,
        with_bbox=False,
        layer=layer_slab,
    )

    c = gf.Component()
    taper_ref_wg = c << taper_wg
    taper_ref_slab = c << taper_slab

    c.info["length"] = length
    c.add_port(name="o1", port=taper_ref_wg.ports["o1"])

    if slab_port_layer:
        port = taper_ref_wg.ports["o2"]
        c.add_port(
            name="o2",
            width=port.width,
            orientation=port.orientation,
            layer=slab_port_layer,
            center=port.center,
        )

    if use_slab_port:
        c.add_port(name="o2", port=taper_ref_slab.ports["o2"])
    else:
        c.add_port(name="o2", port=taper_ref_wg.ports["o2"])

    if length:
        xs.add_bbox(c)
    c.flatten()
    return c

taper_strip_to_ridge_trenches

taper_strip_to_ridge_trenches(
    length: float = 10.0,
    width: float = 0.5,
    slab_offset: float = 3.0,
    trench_width: float = 2.0,
    trench_layer: LayerSpec = "DEEP_ETCH",
    layer_wg: LayerSpec = "WG",
    trench_offset: float = 0.1,
) -> gf.Component

Defines taper using trenches to define the etch.

Parameters:

Name Type Description Default
length float

in um.

10.0
width float

in um.

0.5
slab_offset float

in um.

3.0
trench_width float

in um.

2.0
trench_layer LayerSpec

trench layer.

'DEEP_ETCH'
layer_wg LayerSpec

waveguide layer.

'WG'
trench_offset float

after waveguide in um.

0.1
Source code in gdsfactory/components/tapers/taper.py
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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_strip_to_ridge_trenches(
    length: float = 10.0,
    width: float = 0.5,
    slab_offset: float = 3.0,
    trench_width: float = 2.0,
    trench_layer: LayerSpec = "DEEP_ETCH",
    layer_wg: LayerSpec = "WG",
    trench_offset: float = 0.1,
) -> gf.Component:
    """Defines taper using trenches to define the etch.

    Args:
        length: in um.
        width: in um.
        slab_offset: in um.
        trench_width: in um.
        trench_layer: trench layer.
        layer_wg: waveguide layer.
        trench_offset: after waveguide in um.
    """
    c = gf.Component()
    y0 = width / 2 + trench_width - trench_offset
    yL = width / 2 + trench_width - trench_offset + slab_offset

    # straight
    x = [0, length, length, 0]
    yw = [y0, yL, -yL, -y0]
    c.add_polygon(list(zip(x, yw, strict=False)), layer=layer_wg)

    # top trench
    ymin0 = width / 2
    yminL = width / 2
    ymax0 = width / 2 + trench_width
    ymaxL = width / 2 + trench_width + slab_offset
    x = [0, length, length, 0]
    ytt = [ymin0, yminL, ymaxL, ymax0]
    ytb = [-ymin0, -yminL, -ymaxL, -ymax0]
    c.add_polygon(list(zip(x, ytt, strict=False)), layer=trench_layer)
    c.add_polygon(list(zip(x, ytb, strict=False)), layer=trench_layer)

    c.add_port(name="o1", center=(0, 0), width=width, orientation=180, layer=layer_wg)
    c.add_port(
        name="o2", center=(length, 0), width=width, orientation=0, layer=layer_wg
    )
    return c

taper

taper_0p5_to_3_l36 module-attribute

taper_0p5_to_3_l36 = partial(
    taper_from_csv,
    filepath=data / "taper_strip_0p5_3_36.csv",
)

taper_0p5_to_3_l36

taper_adiabatic

neff_TE1550SOI_220nm

neff_TE1550SOI_220nm(w: float) -> float

Returns the effective index of the fundamental TE mode for a 220nm-thick core with 3.45 index, fully clad with 1.44 index.

Parameters:

Name Type Description Default
w float

width in um.

required

Returns:

Type Description
float

effective index.

Source code in gdsfactory/components/tapers/taper_adiabatic.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
42
43
44
45
46
47
def neff_TE1550SOI_220nm(w: float) -> float:
    """Returns the effective index of the fundamental TE mode for a 220nm-thick core with 3.45 index, fully clad with 1.44 index.

    Args:
        w: width in um.

    Returns:
        effective index.
    """
    adiabatic_polyfit_TE1550SOI_220nm = np.array(
        [
            1.02478963e-09,
            -8.65556534e-08,
            3.32415694e-06,
            -7.68408985e-05,
            1.19282177e-03,
            -1.31366332e-02,
            1.05721429e-01,
            -6.31057637e-01,
            2.80689677e00,
            -9.26867694e00,
            2.24535191e01,
            -3.90664800e01,
            4.71899278e01,
            -3.74726005e01,
            1.77381560e01,
            -1.12666286e00,
        ]
    )
    return float(np.poly1d(adiabatic_polyfit_TE1550SOI_220nm)(w).item())

taper_adiabatic

taper_adiabatic(
    width1: float = 0.5,
    width2: float = 5.0,
    length: float = 0,
    neff_w: Callable[[float], float] = neff_TE1550SOI_220nm,
    alpha: float = 1,
    wavelength: float = 1.55,
    npoints: int = 200,
    cross_section: CrossSectionSpec = "strip",
    max_length: float = 200,
) -> gf.Component

Returns a straight adiabatic_taper from an effective index callable.

Parameters:

Name Type Description Default
width1 float

initial width.

0.5
width2 float

final width.

5.0
length float

0 uses the optimized length, and otherwise the optimal shape is compressed/stretched to the specified length.

0
neff_w Callable[[float], float]

a callable that returns the effective index as a function of width - By default, will use a compact model of neff(y) for fundamental 1550 nm TE mode of 220nm-thick core with 3.45 index, fully clad with 1.44 index. Many coefficients are needed to capture the behaviour.

neff_TE1550SOI_220nm
alpha float

parameter that scales the rate of width change. - closer to 0 means longer and more adiabatic; - 1 is the intuitive limit beyond which higher order modes are excited; - [2] reports good performance up to 1.4 for fundamental TE in SOI (for multiple core thicknesses)

1
wavelength float

wavelength in um.

1.55
npoints int

number of points for sampling.

200
cross_section CrossSectionSpec

cross_section specification.

'strip'
max_length float

maximum length for the taper.

200
References

[1] Burns, W. K., et al. "Optical waveguide parabolic coupling horns." Appl. Phys. Lett., vol. 30, no. 1, 1 Jan. 1977, pp. 28-30, doi:10.1063/1.89199. [2] Fu, Yunfei, et al. "Efficient adiabatic silicon-on-insulator waveguide taper." Photonics Res., vol. 2, no. 3, 1 June 2014, pp. A41-A44, doi:10.1364/PRJ.2.000A41. npoints: number of points for sampling

Source code in gdsfactory/components/tapers/taper_adiabatic.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
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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def taper_adiabatic(
    width1: float = 0.5,
    width2: float = 5.0,
    length: float = 0,
    neff_w: Callable[[float], float] = neff_TE1550SOI_220nm,
    alpha: float = 1,
    wavelength: float = 1.55,
    npoints: int = 200,
    cross_section: CrossSectionSpec = "strip",
    max_length: float = 200,
) -> gf.Component:
    """Returns a straight adiabatic_taper from an effective index callable.

    Args:
        width1: initial width.
        width2: final width.
        length: 0 uses the optimized length, and otherwise the optimal shape is compressed/stretched to the specified length.
        neff_w: a callable that returns the effective index as a function of width
                - By default, will use a compact model of neff(y) for fundamental 1550 nm TE mode of 220nm-thick core with 3.45 index, fully clad with 1.44 index. Many coefficients are needed to capture the behaviour.
        alpha: parameter that scales the rate of width change.
                - closer to 0 means longer and more adiabatic;
                - 1 is the intuitive limit beyond which higher order modes are excited;
                - [2] reports good performance up to 1.4 for fundamental TE in SOI (for multiple core thicknesses)
        wavelength: wavelength in um.
        npoints: number of points for sampling.
        cross_section: cross_section specification.
        max_length: maximum length for the taper.

    References:
        [1] Burns, W. K., et al. "Optical waveguide parabolic coupling horns." Appl. Phys. Lett., vol. 30, no. 1, 1 Jan. 1977, pp. 28-30, doi:10.1063/1.89199.
        [2] Fu, Yunfei, et al. "Efficient adiabatic silicon-on-insulator waveguide taper." Photonics Res., vol. 2, no. 3, 1 June 2014, pp. A41-A44, doi:10.1364/PRJ.2.000A41.
        npoints: number of points for sampling
    """
    xs = gf.get_cross_section(cross_section)
    layer = xs.layer
    assert layer is not None

    # Obtain optimal curve
    x_opt, w_opt = transition_adiabatic(
        width1,
        width2,
        neff_w=neff_w,
        wavelength=wavelength,
        alpha=alpha,
        max_length=max_length,
    )

    # Resample the points
    from scipy import interpolate

    w_opt_interp = interpolate.interp1d(x_opt, w_opt)

    if not length:
        length = x_opt[-1]
    x = np.linspace(0, length, npoints)
    w: npt.NDArray[np.floating[Any]] = w_opt_interp(x)

    assert isinstance(w, np.ndarray)

    # Stretch/compress x
    x_array: npt.NDArray[np.float64] = np.linspace(0, length, npoints) * (
        1 + length - x_opt[-1]
    )
    assert isinstance(x_array, np.ndarray)
    y_array = w / 2

    c = gf.Component()
    c.add_polygon(
        [(float(x), float(y)) for x, y in zip(x_array, y_array, strict=False)]
        + [(float(x), float(y)) for x, y in zip(x_array, -y_array, strict=False)][::-1],
        layer=layer,
    )

    # Define ports
    c.add_port(
        name="o1",
        center=(0, 0),
        width=width1,
        orientation=180,
        cross_section=cross_section,
        layer=layer,
    )
    c.add_port(
        name="o2",
        center=(length, 0),
        width=width2,
        orientation=0,
        cross_section=cross_section,
        layer=layer,
    )
    xs.add_bbox(c)
    return c

taper_adiabatic

taper_cross_section

taper_cross_section

taper_cross_section(
    cross_section1: CrossSectionSpec = "strip_rib_tip",
    cross_section2: CrossSectionSpec = "rib2",
    length: float = 10,
    npoints: int = 100,
    linear: bool = False,
    width_type: str = "sine",
    exclude_layers: LayerSpecs | None = None,
) -> Component

Returns taper transition between cross_section1 and cross_section2.

Parameters:

Name Type Description Default
cross_section1 CrossSectionSpec

start cross_section factory.

'strip_rib_tip'
cross_section2 CrossSectionSpec

end cross_section factory.

'rib2'
length float

transition length.

10
npoints int

number of points.

100
linear bool

shape of the transition, sine when False.

False
width_type str

shape of the transition ONLY IF linear is False

'sine'
exclude_layers LayerSpecs | None

layers to exclude from the transition. Sections on these layers will be omitted from the component.

None
                       _____________________
                      /
              _______/______________________
                    /
   cross_section1  |        cross_section2
              ______\_______________________
                     \
                      \_____________________
Source code in gdsfactory/components/tapers/taper_cross_section.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
90
91
92
93
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_cross_section(
    cross_section1: CrossSectionSpec = "strip_rib_tip",
    cross_section2: CrossSectionSpec = "rib2",
    length: float = 10,
    npoints: int = 100,
    linear: bool = False,
    width_type: str = "sine",
    exclude_layers: LayerSpecs | None = None,
) -> Component:
    r"""Returns taper transition between cross_section1 and cross_section2.

    Args:
        cross_section1: start cross_section factory.
        cross_section2: end cross_section factory.
        length: transition length.
        npoints: number of points.
        linear: shape of the transition, sine when False.
        width_type: shape of the transition ONLY IF linear is False
        exclude_layers: layers to exclude from the transition.
            Sections on these layers will be omitted from the component.

    ```text
                           _____________________
                          /
                  _______/______________________
                        /
       cross_section1  |        cross_section2
                  ______\_______________________
                         \
                          \_____________________
    ```


    """
    x1 = gf.get_cross_section(cross_section1)
    x2 = gf.get_cross_section(cross_section2)

    if exclude_layers:
        layers: list[LayerSpec] = (
            list(exclude_layers)
            if isinstance(exclude_layers, (list, tuple))
            and not (len(exclude_layers) == 2 and isinstance(exclude_layers[0], int))
            else [exclude_layers]  # type: ignore[list-item]
        )
        excluded = {gf.get_layer(layer) for layer in layers}

        def _mark_skip(sections: tuple[gf.Section, ...]) -> tuple[gf.Section, ...]:
            return tuple(
                s.model_copy(update={"skip_transition": True})
                if gf.get_layer(s.layer) in excluded
                else s
                for s in sections
            )

        x1 = x1.model_copy(update={"sections": _mark_skip(x1.sections)})
        x2 = x2.model_copy(update={"sections": _mark_skip(x2.sections)})

    if x1 == x2 and not exclude_layers:
        return gf.components.straight(length=length, cross_section=x1)

    transition = gf.path.transition(
        cross_section1=x1,
        cross_section2=x2,
        width_type="linear" if linear else width_type,  # type: ignore
        offset_type="linear" if linear else width_type,  # type: ignore
    )
    taper_path = gf.path.straight(length=length, npoints=npoints)

    c = gf.Component()
    ref = c << gf.path.extrude_transition(taper_path, transition=transition)
    c.add_ports(ref.ports)
    c.add_route_info(cross_section=x1, length=length, taper=True)
    c.flatten()
    return c

taper_cross_section

taper_cross_section_linear module-attribute

taper_cross_section_linear = partial(
    taper_cross_section, linear=True, npoints=2
)

taper_cross_section_linear

taper_cross_section_parabolic module-attribute

taper_cross_section_parabolic = partial(
    taper_cross_section,
    linear=False,
    width_type="parabolic",
    npoints=101,
)

taper_cross_section_parabolic

taper_cross_section_sine module-attribute

taper_cross_section_sine = partial(
    taper_cross_section, linear=False, npoints=101
)

taper_cross_section_sine

taper_electrical module-attribute

taper_electrical = partial(
    taper,
    port_types=("electrical", "electrical"),
    port_names=("e1", "e2"),
    cross_section="metal_routing",
)

taper_electrical

taper_from_csv

Adiabatic tapers from CSV files.

taper_from_csv

taper_from_csv(
    filepath: Path = data / "taper_strip_0p5_3_36.csv",
    cross_section: CrossSectionSpec = "strip",
) -> Component

Returns taper from CSV file.

Parameters:

Name Type Description Default
filepath Path

for CSV file.

data / 'taper_strip_0p5_3_36.csv'
cross_section CrossSectionSpec

specification (CrossSection, string, CrossSectionFactory dict).

'strip'
Source code in gdsfactory/components/tapers/taper_from_csv.py
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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def taper_from_csv(
    filepath: Path = data / "taper_strip_0p5_3_36.csv",
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Returns taper from CSV file.

    Args:
        filepath: for CSV file.
        cross_section: specification (CrossSection, string, CrossSectionFactory dict).
    """
    import pandas as pd

    taper_data = pd.read_csv(filepath)
    xs: list[float] = taper_data["x"].values * 1e6
    ys: npt.NDArray[np.float64] = np.round(taper_data["width"].values * 1e6 / 2.0, 3)

    x = gf.get_cross_section(cross_section)
    layer = x.layer

    c = gf.Component()
    c.add_polygon(
        list(zip(xs, ys, strict=False)) + list(zip(xs, -ys, strict=False))[::-1],
        layer=layer,
    )

    for section in x.sections[1:]:
        ys_trench = ys + section.width
        c.add_polygon(
            [(float(x), float(y)) for x, y in zip(xs, ys_trench, strict=False)]
            + [(float(x), float(y)) for x, y in zip(xs, -ys_trench, strict=False)][
                ::-1
            ],
            layer=section.layer,
        )

    c.add_port(
        name="o1",
        center=(xs[0], 0),
        width=2 * ys[0],
        orientation=180,
        layer=layer,
        cross_section=x,
    )
    c.add_port(
        name="o2",
        center=(xs[-1], 0),
        width=2 * ys[-1],
        orientation=0,
        layer=layer,
        cross_section=x,
    )
    x.add_bbox(c)
    return c

taper_from_csv

taper_hecken

Hecken taper for microstrip impedance matching.

Adapted from PHIDL https://github.com/amccaugh/phidl/ by Adam McCaughan

taper_hecken

taper_hecken(
    length: float = 200,
    B: float = 4.0091,
    dielectric_thickness: float = 0.25,
    eps_r: float = 2,
    Lk_per_sq: float = 2.5e-10,
    Z1: float | None = 50,
    Z2: float | None = 100,
    width1: float | None = None,
    width2: float | None = None,
    num_pts: int = 100,
    layer: LayerSpec = "WG",
) -> Component

Creates a Hecken-tapered microstrip.

Parameters:

Name Type Description Default
length float

Length of the microstrip.

200
B float

Controls the intensity of the taper.

4.0091
dielectric_thickness float

Thickness of the substrate.

0.25
eps_r float

Dielectric constant of the substrate.

2
Lk_per_sq float

Kinetic inductance per square of the microstrip.

2.5e-10
Z1 float | None

Impedance of the left side region of the microstrip.

50
Z2 float | None

Impedance of the right side region of the microstrip.

100
width1 float | None

Width of the left side of the microstrip.

None
width2 float | None

Width of the right side of the microstrip.

None
num_pts int

Number of points comprising the curve of the entire microstrip.

100
layer LayerSpec

Specific layer(s) to put polygon geometry on.

'WG'

Returns:

Type Description
Component

Component containing a Hecken-tapered microstrip.

Source code in gdsfactory/components/tapers/taper_hecken.py
 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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def taper_hecken(
    length: float = 200,
    B: float = 4.0091,
    dielectric_thickness: float = 0.25,
    eps_r: float = 2,
    Lk_per_sq: float = 250e-12,
    Z1: float | None = 50,
    Z2: float | None = 100,
    width1: float | None = None,
    width2: float | None = None,
    num_pts: int = 100,
    layer: LayerSpec = "WG",
) -> Component:
    """Creates a Hecken-tapered microstrip.

    Args:
        length: Length of the microstrip.
        B: Controls the intensity of the taper.
        dielectric_thickness: Thickness of the substrate.
        eps_r: Dielectric constant of the substrate.
        Lk_per_sq: Kinetic inductance per square of the microstrip.
        Z1: Impedance of the left side region of the microstrip.
        Z2: Impedance of the right side region of the microstrip.
        width1: Width of the left side of the microstrip.
        width2: Width of the right side of the microstrip.
        num_pts: Number of points comprising the curve of the entire microstrip.
        layer: Specific layer(s) to put polygon geometry on.

    Returns:
        Component containing a Hecken-tapered microstrip.
    """
    if width1 is not None:
        Z1 = _microstrip_Z_with_Lk(
            width1 * 1e-6, dielectric_thickness * 1e-6, eps_r, Lk_per_sq
        )
    if width2 is not None:
        Z2 = _microstrip_Z_with_Lk(
            width2 * 1e-6, dielectric_thickness * 1e-6, eps_r, Lk_per_sq
        )
    # Normalized length of the wire [-1 to +1]
    xi_list = np.linspace(-1, 1, num_pts)
    if Z1 is None or Z2 is None:
        raise ValueError(
            "Z1 and Z2 must be specified either directly or via width1/width2"
        )
    Z = [np.exp(0.5 * log(Z1 * Z2) + 0.5 * log(Z2 / Z1) * _G(xi, B)) for xi in xi_list]
    widths = np.array(
        [
            _find_microstrip_wire_width(
                z, dielectric_thickness * 1e-6, eps_r, Lk_per_sq
            )
            * 1e6
            for z in Z
        ]
    )
    x = (xi_list / 2) * length

    # Compensate for varying speed of light in the microstrip by shortening
    # and lengthening sections according to the speed of light in that section
    v = np.array(
        [
            _microstrip_v_with_Lk(
                w * 1e-6, dielectric_thickness * 1e-6, eps_r, Lk_per_sq
            )
            for w in widths
        ]
    )
    dx = np.diff(x)
    dx_compensated = dx * v[:-1]
    x_compensated = np.cumsum(dx_compensated)
    x = np.hstack([0, x_compensated]) / max(x_compensated) * length

    # Create blank device and add taper polygon
    c = gf.Component()
    xpts = np.concatenate([x, x[::-1]])
    ypts = np.concatenate([widths / 2, -widths[::-1] / 2])
    points = np.column_stack((xpts, ypts))
    c.add_polygon(points, layer=layer)
    # Snap port widths to even multiples of 0.002 um (2 dbu)
    width1_snapped = round(widths[0] / 0.002) * 0.002
    width2_snapped = round(widths[-1] / 0.002) * 0.002
    c.add_port(
        name="o1", center=(0, 0), width=width1_snapped, orientation=180, layer=layer
    )
    c.add_port(
        name="o2", center=(length, 0), width=width2_snapped, orientation=0, layer=layer
    )

    # Add meta information about the taper
    c.info["num_squares"] = float(np.sum(np.diff(x) / widths[:-1]))
    c.info["width1"] = float(widths[0])
    c.info["width2"] = float(widths[-1])
    c.info["Z1"] = float(Z[0])
    c.info["Z2"] = float(Z[-1])
    # Note there are two values for v/c (and f_cutoff) because the speed of
    # light is different at the beginning and end of the taper

    # c.info["w"] = widths.tolist()
    # c.info["x"] = x.tolist()
    # c.info["Z"] = Z if isinstance(Z, list) else list(Z)
    # c.info["v/c"] = (v / 3e8).tolist()

    time_length = float(np.sum(np.diff(x * 1e-6) / (v[:-1])))
    c.info["time_length"] = time_length
    c.info["f_cutoff"] = 1 / (2 * time_length)
    c.info["length"] = float(length)
    return c

taper_hecken

taper_meander

Meander taper for superconducting nanowires.

Adapted from PHIDL https://github.com/amccaugh/phidl/ by Adam McCaughan

taper_meander

taper_meander(
    x_taper: tuple[float, ...] | None = None,
    w_taper: tuple[float, ...] | None = None,
    meander_length: float = 1000,
    spacing_factor: float = 3,
    min_spacing: float = 0.5,
    layer: LayerSpec = "WG",
) -> Component

Create a meander from arrays of x-positions and widths.

Typically used for creating meandered tapers.

Parameters:

Name Type Description Default
x_taper tuple[float, ...] | None

The x-coordinates of the data points, must be increasing.

None
w_taper tuple[float, ...] | None

The widths at each x-coordinate, same length as x_taper.

None
meander_length float

Length of each section of the meander.

1000
spacing_factor float

Multiplicative spacing factor between adjacent meanders.

3
min_spacing float

Minimum spacing between adjacent meanders.

0.5
layer LayerSpec

Specific layer(s) to put polygon geometry on.

'WG'

Returns:

Type Description
Component

Component containing the meandered taper.

Source code in gdsfactory/components/tapers/taper_meander.py
 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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def taper_meander(
    x_taper: tuple[float, ...] | None = None,
    w_taper: tuple[float, ...] | None = None,
    meander_length: float = 1000,
    spacing_factor: float = 3,
    min_spacing: float = 0.5,
    layer: LayerSpec = "WG",
) -> Component:
    """Create a meander from arrays of x-positions and widths.

    Typically used for creating meandered tapers.

    Args:
        x_taper: The x-coordinates of the data points, must be increasing.
        w_taper: The widths at each x-coordinate, same length as x_taper.
        meander_length: Length of each section of the meander.
        spacing_factor: Multiplicative spacing factor between adjacent meanders.
        min_spacing: Minimum spacing between adjacent meanders.
        layer: Specific layer(s) to put polygon geometry on.

    Returns:
        Component containing the meandered taper.
    """
    # Default taper if none provided
    x_taper = x_taper or (1, 10, 20, 30, 40, 50)
    w_taper = w_taper or (1, 5, 10, 5, 2, 1)

    # Convert to numpy arrays for internal use
    x_taper_arr = np.array(x_taper)
    w_taper_arr = np.array(w_taper)

    def taper_width(x: float) -> float:
        """Interpolate width at a given x position."""
        return float(np.interp(x, x_taper_arr, w_taper_arr))

    @gf.cell(tags=["tapers"])
    def taper_section(
        x_start: float, x_end: float, num_pts: int = 30, layer: LayerSpec = layer
    ) -> Component:
        """Create a single taper section.

        Args:
            x_start: Starting x-coordinate.
            x_end: Ending x-coordinate.
            num_pts: Number of points for the taper.
            layer: Layer for the polygon.

        Returns:
            Component containing the taper section.
        """
        c = gf.Component()
        length = x_end - x_start
        x = np.linspace(0, length, num_pts)
        widths = np.linspace(taper_width(x_start), taper_width(x_end), num_pts)
        xpts = np.concatenate([x, x[::-1]])
        ypts = np.concatenate([widths / 2, -widths[::-1] / 2])
        points = np.column_stack((xpts, ypts))
        c.add_polygon(points, layer=layer)

        # Snap port widths to even multiples of 0.002 um (2 dbu)
        width1_snapped = round(widths[0] / 0.002) * 0.002
        width2_snapped = round(widths[-1] / 0.002) * 0.002
        c.add_port(
            name="o1",
            center=(0, 0),
            width=width1_snapped,
            orientation=180,
            layer=layer,
        )
        c.add_port(
            name="o2",
            center=(length, 0),
            width=width2_snapped,
            orientation=0,
            layer=layer,
        )
        return c

    @gf.cell(tags=["tapers"])
    def arc_tapered(
        radius: float = 10,
        width1: float = 1,
        width2: float = 2,
        theta: float = 45,
        angle_resolution: float = 2.5,
        layer: LayerSpec = layer,
    ) -> Component:
        """Create a tapered arc section.

        Args:
            radius: Radius of the arc.
            width1: Width at the start of the arc.
            width2: Width at the end of the arc.
            theta: Angle of the arc in degrees.
            angle_resolution: Angular resolution in degrees.
            layer: Layer for the polygon.

        Returns:
            Component containing the tapered arc.
        """
        c = gf.Component()
        path1 = gf.path.arc(
            radius=radius,
            angle=theta,
            angular_step=angle_resolution,
        )
        # Snap widths to even multiples of 0.002 um (2 dbu)
        width1_snapped = round(width1 / 0.002) * 0.002
        width2_snapped = round(width2 / 0.002) * 0.002
        # Extrude the path with the first width
        arc_component = gf.path.extrude(path1, width=width1_snapped, layer=layer)
        c.add_ref(arc_component)
        c.add_port(
            name="o1",
            center=(0, 0),
            width=width1_snapped,
            orientation=180,
            layer=layer,
        )
        c.add_port(
            name="o2",
            center=(path1.x, path1.y),
            width=width2_snapped,
            orientation=path1.end_angle + 90,
            layer=layer,
        )
        return c

    c = gf.Component()
    xpos1 = min(x_taper_arr)
    xpos2 = min(x_taper_arr) + meander_length
    t = c.add_ref(taper_section(x_start=xpos1, x_end=xpos2, num_pts=50, layer=layer))
    c.add_port(port=t.ports["o1"], name="o1")
    dir_toggle = -1
    while xpos2 < max(x_taper_arr):
        arc_width1 = taper_width(xpos2)
        arc_radius = max(spacing_factor * arc_width1, min_spacing)
        arc_length = pi * arc_radius
        arc_width2 = taper_width(xpos2 + arc_length)
        A = arc_tapered(
            radius=arc_radius,
            width1=arc_width1,
            width2=arc_width2,
            theta=180 * dir_toggle,
            layer=layer,
        )
        a = c.add_ref(A)
        a.connect("o1", t.ports["o2"])
        dir_toggle = -dir_toggle
        xpos1 = xpos2 + arc_length
        xpos2 = xpos1 + meander_length
        t = c.add_ref(
            taper_section(x_start=xpos1, x_end=xpos2, num_pts=30, layer=layer)
        )
        t.connect("o1", a.ports["o2"])
    c.add_port(port=t.ports["o2"], name="o2")
    return c

taper_meander

taper_nc_sc

taper_nc_sc(
    width1: float = 1,
    width2: float = 0.5,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Taper from nitride to strip.

Parameters:

Name Type Description Default
width1 float

nitride width.

1
width2 float

silicon width.

0.5
length float

taper length.

20
layer_wg LayerSpec

nitride layer.

'WG'
layer_nitride LayerSpec

strip layer.

'WGN'
width_tip_nitride float

tip width for nitride.

0.15
width_tip_silicon float

tip width for strip.

0.15
cross_section CrossSectionSpec

cross_section specification.

'strip'
Source code in gdsfactory/components/tapers/taper.py
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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_nc_sc(
    width1: float = 1,
    width2: float = 0.5,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Taper from nitride to strip.

    Args:
        width1: nitride width.
        width2: silicon width.
        length: taper length.
        layer_wg: nitride layer.
        layer_nitride: strip layer.
        width_tip_nitride: tip width for nitride.
        width_tip_silicon: tip width for strip.
        cross_section: cross_section specification.
    """
    c = gf.Component()
    taper = taper_sc_nc(
        width1=width2,
        width2=width1,
        length=length,
        layer_wg=layer_wg,
        layer_nitride=layer_nitride,
        width_tip_nitride=width_tip_nitride,
        width_tip_silicon=width_tip_silicon,
        cross_section=cross_section,
    )
    c.copy_child_info(taper)
    ref = c << taper
    ref.mirror_x()
    c.add_ports(ref.ports)
    c.auto_rename_ports()
    c.flatten()
    return c

taper_nc_sc

taper_parabolic

taper_parabolic

taper_parabolic(
    length: float = 20,
    width1: float = 0.5,
    width2: float = 5.0,
    exp: float = 0.5,
    npoints: int = 100,
    layer: LayerSpec = "WG",
) -> gf.Component

Returns a parabolic_taper.

Parameters:

Name Type Description Default
length float

in um.

20
width1 float

in um.

0.5
width2 float

in um.

5.0
exp float

exponent.

0.5
npoints int

number of points.

100
layer LayerSpec

layer spec.

'WG'
Source code in gdsfactory/components/tapers/taper_parabolic.py
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
@gf.cell_with_module_name(schematic_function=taper_schematic, tags=["tapers"])
def taper_parabolic(
    length: float = 20,
    width1: float = 0.5,
    width2: float = 5.0,
    exp: float = 0.5,
    npoints: int = 100,
    layer: LayerSpec = "WG",
) -> gf.Component:
    """Returns a parabolic_taper.

    Args:
        length: in um.
        width1: in um.
        width2: in um.
        exp: exponent.
        npoints: number of points.
        layer: layer spec.
    """
    x = np.linspace(0, 1, npoints)
    y = transition_exponential(y1=width1, y2=width2, exp=exp)(x) / 2

    x = length * x
    points1 = np.array([x, y]).T
    points2 = np.flipud(np.array([x, -y]).T)
    points = np.concatenate([points1, points2])

    c = gf.Component()
    c.add_polygon(points, layer=layer)
    c.add_port(name="o1", center=(0, 0), width=width1, orientation=180, layer=layer)
    c.add_port(name="o2", center=(length, 0), width=width2, orientation=0, layer=layer)
    return c

taper_parabolic

taper_sc_nc

taper_sc_nc(
    width1: float = 0.5,
    width2: float = 1,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Taper from strip to nitride.

Parameters:

Name Type Description Default
width1 float

strip width.

0.5
width2 float

nitride width.

1
length float

taper length.

20
layer_wg LayerSpec

strip layer.

'WG'
layer_nitride LayerSpec

nitride layer.

'WGN'
width_tip_nitride float

tip width for nitride.

0.15
width_tip_silicon float

tip width for strip.

0.15
cross_section CrossSectionSpec

cross_section specification.

'strip'
Source code in gdsfactory/components/tapers/taper.py
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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_sc_nc(
    width1: float = 0.5,
    width2: float = 1,
    length: float = 20,
    layer_wg: LayerSpec = "WG",
    layer_nitride: LayerSpec = "WGN",
    width_tip_nitride: float = 0.15,
    width_tip_silicon: float = 0.15,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Taper from strip to nitride.

    Args:
        width1: strip width.
        width2: nitride width.
        length: taper length.
        layer_wg: strip layer.
        layer_nitride: nitride layer.
        width_tip_nitride: tip width for nitride.
        width_tip_silicon: tip width for strip.
        cross_section: cross_section specification.
    """
    return taper_strip_to_ridge(
        layer_wg=layer_wg,
        layer_slab=layer_nitride,
        length=length,
        width1=width1,
        width2=width_tip_silicon,
        w_slab1=width_tip_nitride,
        w_slab2=width2,
        use_slab_port=True,
        cross_section=cross_section,
    )

taper_sc_nc

taper_strip_to_ridge

taper_strip_to_ridge(
    length: float = 10.0,
    width1: float = 0.5,
    width2: float = 0.5,
    w_slab1: float = 0.15,
    w_slab2: float = 6.0,
    layer_wg: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    cross_section: CrossSectionSpec = "strip",
    use_slab_port: bool = False,
    slab_port_layer: LayerSpec | None = None,
) -> Component

Linear taper from strip to rib.

Parameters:

Name Type Description Default
length float

taper length (um).

10.0
width1 float

in um.

0.5
width2 float

in um.

0.5
w_slab1 float

slab width in um.

0.15
w_slab2 float

slab width in um.

6.0
layer_wg LayerSpec

for input waveguide.

'WG'
layer_slab LayerSpec

for output waveguide with slab.

'SLAB90'
cross_section CrossSectionSpec

for input waveguide.

'strip'
use_slab_port bool

if True adds a second port for the slab.

False
slab_port_layer LayerSpec | None

if specified, overrides the layer for the slab port.

None
                  __________________________
                 /           |
         _______/____________|______________
               /             |
   width1     |w_slab1       | w_slab2  width2
         ______\_____________|______________
                \            |
                 \__________________________
Source code in gdsfactory/components/tapers/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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_strip_to_ridge(
    length: float = 10.0,
    width1: float = 0.5,
    width2: float = 0.5,
    w_slab1: float = 0.15,
    w_slab2: float = 6.0,
    layer_wg: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB90",
    cross_section: CrossSectionSpec = "strip",
    use_slab_port: bool = False,
    slab_port_layer: LayerSpec | None = None,
) -> Component:
    r"""Linear taper from strip to rib.

    Args:
        length: taper length (um).
        width1: in um.
        width2: in um.
        w_slab1: slab width in um.
        w_slab2: slab width in um.
        layer_wg: for input waveguide.
        layer_slab: for output waveguide with slab.
        cross_section: for input waveguide.
        use_slab_port: if True adds a second port for the slab.
        slab_port_layer: if specified, overrides the layer for the slab port.

    ```text
                      __________________________
                     /           |
             _______/____________|______________
                   /             |
       width1     |w_slab1       | w_slab2  width2
             ______\_____________|______________
                    \            |
                     \__________________________
    ```

    """
    xs = gf.get_cross_section(cross_section)

    taper_wg = taper(
        length=length,
        width1=width1,
        width2=width2,
        cross_section=cross_section,
        layer=layer_wg,
    )
    taper_slab = taper(
        length=length,
        width1=w_slab1,
        width2=w_slab2,
        cross_section=cross_section,
        with_bbox=False,
        layer=layer_slab,
    )

    c = gf.Component()
    taper_ref_wg = c << taper_wg
    taper_ref_slab = c << taper_slab

    c.info["length"] = length
    c.add_port(name="o1", port=taper_ref_wg.ports["o1"])

    if slab_port_layer:
        port = taper_ref_wg.ports["o2"]
        c.add_port(
            name="o2",
            width=port.width,
            orientation=port.orientation,
            layer=slab_port_layer,
            center=port.center,
        )

    if use_slab_port:
        c.add_port(name="o2", port=taper_ref_slab.ports["o2"])
    else:
        c.add_port(name="o2", port=taper_ref_wg.ports["o2"])

    if length:
        xs.add_bbox(c)
    c.flatten()
    return c

taper_strip_to_ridge

taper_strip_to_ridge_trenches

taper_strip_to_ridge_trenches(
    length: float = 10.0,
    width: float = 0.5,
    slab_offset: float = 3.0,
    trench_width: float = 2.0,
    trench_layer: LayerSpec = "DEEP_ETCH",
    layer_wg: LayerSpec = "WG",
    trench_offset: float = 0.1,
) -> gf.Component

Defines taper using trenches to define the etch.

Parameters:

Name Type Description Default
length float

in um.

10.0
width float

in um.

0.5
slab_offset float

in um.

3.0
trench_width float

in um.

2.0
trench_layer LayerSpec

trench layer.

'DEEP_ETCH'
layer_wg LayerSpec

waveguide layer.

'WG'
trench_offset float

after waveguide in um.

0.1
Source code in gdsfactory/components/tapers/taper.py
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
@gf.cell_with_module_name(schematic_function=transition_schematic, tags=["tapers"])
def taper_strip_to_ridge_trenches(
    length: float = 10.0,
    width: float = 0.5,
    slab_offset: float = 3.0,
    trench_width: float = 2.0,
    trench_layer: LayerSpec = "DEEP_ETCH",
    layer_wg: LayerSpec = "WG",
    trench_offset: float = 0.1,
) -> gf.Component:
    """Defines taper using trenches to define the etch.

    Args:
        length: in um.
        width: in um.
        slab_offset: in um.
        trench_width: in um.
        trench_layer: trench layer.
        layer_wg: waveguide layer.
        trench_offset: after waveguide in um.
    """
    c = gf.Component()
    y0 = width / 2 + trench_width - trench_offset
    yL = width / 2 + trench_width - trench_offset + slab_offset

    # straight
    x = [0, length, length, 0]
    yw = [y0, yL, -yL, -y0]
    c.add_polygon(list(zip(x, yw, strict=False)), layer=layer_wg)

    # top trench
    ymin0 = width / 2
    yminL = width / 2
    ymax0 = width / 2 + trench_width
    ymaxL = width / 2 + trench_width + slab_offset
    x = [0, length, length, 0]
    ytt = [ymin0, yminL, ymaxL, ymax0]
    ytb = [-ymin0, -yminL, -ymaxL, -ymax0]
    c.add_polygon(list(zip(x, ytt, strict=False)), layer=trench_layer)
    c.add_polygon(list(zip(x, ytb, strict=False)), layer=trench_layer)

    c.add_port(name="o1", center=(0, 0), width=width, orientation=180, layer=layer_wg)
    c.add_port(
        name="o2", center=(length, 0), width=width, orientation=0, layer=layer_wg
    )
    return c

taper_strip_to_ridge_trenches

taper_strip_to_slab150 module-attribute

taper_strip_to_slab150 = partial(
    taper_strip_to_ridge, layer_slab="SLAB150"
)

taper_strip_to_slab150

taper_w10_l100 module-attribute

taper_w10_l100 = partial(
    taper_from_csv,
    filepath=data / "taper_strip_0p5_10_100.csv",
)

taper_w10_l100

taper_w10_l150 module-attribute

taper_w10_l150 = partial(
    taper_from_csv,
    filepath=data / "taper_strip_0p5_10_150.csv",
)

taper_w10_l150

taper_w10_l200 module-attribute

taper_w10_l200 = partial(
    taper_from_csv,
    filepath=data / "taper_strip_0p5_10_200.csv",
)

taper_w10_l200

taper_w11_l200 module-attribute

taper_w11_l200 = partial(
    taper_from_csv,
    filepath=data / "taper_strip_0p5_11_200.csv",
)

taper_w11_l200

taper_w12_l200 module-attribute

taper_w12_l200 = partial(
    taper_from_csv,
    filepath=data / "taper_strip_0p5_12_200.csv",
)

taper_w12_l200

texts

pixel_array

pixel_array(
    pixels: str = character_a,
    pixel_size: float = 10.0,
    layer: LayerSpec = "M1",
) -> Component

Returns a pixel component from a string representing the pixels.

Parameters:

Name Type Description Default
pixels str

string representing the pixels

character_a
pixel_size float

width/height for each pixel

10.0
layer LayerSpec

layer for each pixel

'M1'
Source code in gdsfactory/components/texts/text_rectangular_font.py
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
@gf.cell_with_module_name(tags=["texts"])
def pixel_array(
    pixels: str = character_a,
    pixel_size: float = 10.0,
    layer: LayerSpec = "M1",
) -> Component:
    """Returns a pixel component from a string representing the pixels.

    Args:
        pixels: string representing the pixels
        pixel_size: width/height for each pixel
        layer: layer for each pixel
    """
    component = Component()
    lines = [line for line in pixels.split("\n") if len(line) > 0]
    lines.reverse()
    i = 0
    i_max = 0
    a = pixel_size
    for j, line in enumerate(lines):
        i = 0
        for c in line:
            if c in ["X", "1"]:
                pixel = [
                    (i * a, j * a),
                    ((i + 1) * a, j * a),
                    ((i + 1) * a, (j + 1) * a),
                    (i * a, (j + 1) * a),
                ]
                component.add_polygon(pixel, layer=layer)
            i += 1
        i_max = max(i_max, i)
    return component

pixel_array

text

text

text(
    text: str = "abcd",
    size: float = 10.0,
    position: Coordinate = (0, 0),
    justify: str = "left",
    layer: LayerSpec = "WG",
) -> Component

Text shapes.

Parameters:

Name Type Description Default
text str

string.

'abcd'
size float

in um of each character.

10.0
position Coordinate

x, y position.

(0, 0)
justify str

left, right, center.

'left'
layer LayerSpec

for the text.

'WG'
Source code in gdsfactory/components/texts/text.py
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
@gf.cell_with_module_name(tags=["texts"])
def text(
    text: str = "abcd",
    size: float = 10.0,
    position: Coordinate = (0, 0),
    justify: str = "left",
    layer: LayerSpec = "WG",
) -> Component:
    """Text shapes.

    Args:
        text: string.
        size: in um of each character.
        position: x, y position.
        justify: left, right, center.
        layer: for the text.
    """
    scaling = size / 1000
    xoffset = position[0]
    yoffset = position[1]
    t = gf.Component()

    for line in text.split("\n"):
        label = gf.Component()
        for c in line:
            ascii_val = ord(c)
            if c == " ":
                xoffset += 500 * scaling
            elif 33 <= ascii_val <= 126:
                for poly in _glyph[ascii_val]:
                    xpts = np.array(poly)[:, 0] * scaling
                    ypts = np.array(poly)[:, 1] * scaling
                    label.add_polygon(
                        list(zip(xpts + xoffset, ypts + yoffset, strict=False)),
                        layer=layer,
                    )
                xoffset += (_width[ascii_val] + _indent[ascii_val]) * scaling
            else:
                raise ValueError(f"No character with ascii value {ascii_val!r}")
        t.add_ref(label)
        yoffset -= 1500 * scaling
        xoffset = position[0]
    justify = justify.lower()
    for instance in t.insts:
        if justify == "left":
            instance.xmin = position[0]
        elif justify == "right":
            instance.xmax = position[0]
        elif justify == "center":
            xmin = position[0] - instance.xsize / 2
            instance.xmin = xmin
        else:
            raise ValueError(
                f"justify = {justify!r} not in ('center', 'right', 'left')"
            )
    t.flatten()
    return t

text_klayout

text_klayout(
    text: str = "a",
    layer: LayerSpec = "WG",
    layers: LayerSpecs | None = None,
    bbox_layers: LayerSpecs | None = None,
) -> Component

Returns a text component.

Parameters:

Name Type Description Default
text str

string.

'a'
layer LayerSpec

text layer.

'WG'
layers LayerSpecs | None

layers for the text.

None
bbox_layers LayerSpecs | None

layers for the text bounding box.

None
Source code in gdsfactory/components/texts/text.py
 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
@gf.cell_with_module_name(tags=["texts"])
def text_klayout(
    text: str = "a",
    layer: LayerSpec = "WG",
    layers: LayerSpecs | None = None,
    bbox_layers: LayerSpecs | None = None,
) -> Component:
    """Returns a text component.

    Args:
        text: string.
        layer: text layer.
        layers: layers for the text.
        bbox_layers: layers for the text bounding box.
    """
    c = gf.Component()
    gen = kf.kdb.TextGenerator.default_generator()
    reg = gen.text(text, kf.kcl.dbu)

    layers = layers or [layer]

    for text_layer in layers:
        c.shapes(gf.get_layer(text_layer)).insert(reg)

    for bbox_layer in bbox_layers or []:
        c.shapes(gf.get_layer(bbox_layer)).insert(reg.bbox())
    return c

text_lines

text_lines(
    text: tuple[str, ...] = ("Chip", "01"),
    size: float = 0.4,
    layer: LayerSpec = "WG",
) -> Component

Returns a Component from a text lines.

Parameters:

Name Type Description Default
text tuple[str, ...]

list of strings.

('Chip', '01')
size float

text size.

0.4
layer LayerSpec

text layer.

'WG'
Source code in gdsfactory/components/texts/text.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@gf.cell_with_module_name(tags=["texts"])
def text_lines(
    text: tuple[str, ...] = ("Chip", "01"),
    size: float = 0.4,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a Component from a text lines.

    Args:
        text: list of strings.
        size: text size.
        layer: text layer.
    """
    c = gf.Component()

    for i, texti in enumerate(text):
        t = gf.c.text_rectangular(text=texti, size=size, layer=layer)
        tref = c.add_ref(t)
        tref.movey(-6 * size * (i + 1))
    return c

text

text_freetype

text_freetype

text_freetype(
    text: str = "a",
    size: int = 10,
    justify: str = "left",
    font: PathType = PATH.font_ocr,
    layer: LayerSpec = "WG",
    layers: LayerSpecs | None = None,
) -> Component

Returns text Component.

Parameters:

Name Type Description Default
text str

string.

'a'
size int

in um.

10
justify str

left, right, center.

'left'
font PathType

Font face to use. Default DEPLOF does not require additional libraries, otherwise freetype load fonts. You can choose font by name (e.g. "Times New Roman"), or by file OTF or TTF filepath.

font_ocr
layer LayerSpec

list of layers to use for the text.

'WG'
layers LayerSpecs | None

list of layers to use for the text.

None
Source code in gdsfactory/components/texts/text_freetype.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
@gf.cell_with_module_name(tags=["texts"])
def text_freetype(
    text: str = "a",
    size: int = 10,
    justify: str = "left",
    font: PathType = PATH.font_ocr,
    layer: LayerSpec = "WG",
    layers: LayerSpecs | None = None,
) -> Component:
    """Returns text Component.

    Args:
        text: string.
        size: in um.
        justify: left, right, center.
        font: Font face to use. Default DEPLOF does not require additional libraries,
            otherwise freetype load fonts. You can choose font by name
            (e.g. "Times New Roman"), or by file OTF or TTF filepath.
        layer: list of layers to use for the text.
        layers: list of layers to use for the text.

    """
    t = Component()
    yoffset = 0.0
    layers = layers or [layer]

    face = font
    xoffset = 0.0
    if face == "DEPLOF":
        scaling = size / 1000

        for line in text.split("\n"):
            char = Component()
            for c in line:
                ascii_val = ord(c)
                if c == " ":
                    xoffset += 500 * scaling
                elif (33 <= ascii_val <= 126) or (ascii_val == 181):
                    for poly in _glyph[ascii_val]:
                        xpts = np.array(poly)[:, 0] * scaling + xoffset
                        ypts = np.array(poly)[:, 1] * scaling + yoffset
                        points: list[tuple[float, float]] = list(
                            zip(xpts, ypts, strict=False)
                        )
                        char.add_polygon(points, layer=layer)
                    xoffset += (_width[ascii_val] + _indent[ascii_val]) * scaling
                else:
                    valid_chars = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~µ"
                    warnings.warn(
                        f'text(): Warning, some characters ignored, no geometry for character "{chr(ascii_val)}" with ascii value {ascii_val}. Valid characters: {valid_chars}',
                        stacklevel=3,
                    )
            ref = t.add_ref(char)
            yoffset -= 1500 * scaling
            xoffset = 0
    else:
        from gdsfactory.font import (
            _get_font_by_file,
            _get_font_by_name,
            _get_glyph,
        )

        font_path = pathlib.Path(font)
        # Load the font. If we've passed a valid file, try to load that, otherwise search system fonts
        if font_path.is_file() and font_path.suffix in {".otf", ".ttf"}:
            face = _get_font_by_file(str(font))
        else:
            face = _get_font_by_name(str(font))

        # Render each character
        for line in text.split("\n"):
            char = Component()
            xoffset = 0
            for letter in line:
                letter_template, advance_x, ascender = _get_glyph(face, letter)
                scale_factor = size / ascender
                if letter == " ":
                    xoffset += scale_factor * advance_x
                    continue
                letter_dev = Component()
                for polygon_points in letter_template.get_polygons_points(
                    scale=scale_factor
                ).values():
                    for layer in layers:
                        for points_ in polygon_points:
                            letter_dev.add_polygon(points_, layer=layer)
                ref = char.add_ref(letter_dev)
                ref.move((xoffset, 0))
                xoffset += scale_factor * advance_x

            ref = t.add_ref(char)
            ref.move((0, yoffset))
            yoffset -= size

    justify = justify.lower()
    for inst in t.insts:
        if justify == "center":
            inst.move((0, 0))

        elif justify == "right":
            inst.xmax = 0
    t.flatten()
    return t

text_freetype

text_klayout

text_klayout(
    text: str = "a",
    layer: LayerSpec = "WG",
    layers: LayerSpecs | None = None,
    bbox_layers: LayerSpecs | None = None,
) -> Component

Returns a text component.

Parameters:

Name Type Description Default
text str

string.

'a'
layer LayerSpec

text layer.

'WG'
layers LayerSpecs | None

layers for the text.

None
bbox_layers LayerSpecs | None

layers for the text bounding box.

None
Source code in gdsfactory/components/texts/text.py
 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
@gf.cell_with_module_name(tags=["texts"])
def text_klayout(
    text: str = "a",
    layer: LayerSpec = "WG",
    layers: LayerSpecs | None = None,
    bbox_layers: LayerSpecs | None = None,
) -> Component:
    """Returns a text component.

    Args:
        text: string.
        layer: text layer.
        layers: layers for the text.
        bbox_layers: layers for the text bounding box.
    """
    c = gf.Component()
    gen = kf.kdb.TextGenerator.default_generator()
    reg = gen.text(text, kf.kcl.dbu)

    layers = layers or [layer]

    for text_layer in layers:
        c.shapes(gf.get_layer(text_layer)).insert(reg)

    for bbox_layer in bbox_layers or []:
        c.shapes(gf.get_layer(bbox_layer)).insert(reg.bbox())
    return c

text_klayout

text_lines

text_lines(
    text: tuple[str, ...] = ("Chip", "01"),
    size: float = 0.4,
    layer: LayerSpec = "WG",
) -> Component

Returns a Component from a text lines.

Parameters:

Name Type Description Default
text tuple[str, ...]

list of strings.

('Chip', '01')
size float

text size.

0.4
layer LayerSpec

text layer.

'WG'
Source code in gdsfactory/components/texts/text.py
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@gf.cell_with_module_name(tags=["texts"])
def text_lines(
    text: tuple[str, ...] = ("Chip", "01"),
    size: float = 0.4,
    layer: LayerSpec = "WG",
) -> Component:
    """Returns a Component from a text lines.

    Args:
        text: list of strings.
        size: text size.
        layer: text layer.
    """
    c = gf.Component()

    for i, texti in enumerate(text):
        t = gf.c.text_rectangular(text=texti, size=size, layer=layer)
        tref = c.add_ref(t)
        tref.movey(-6 * size * (i + 1))
    return c

text_lines

text_rectangular

text_rectangular

text_rectangular(
    text: str = "abcd",
    size: float = 10.0,
    position: tuple[float, float] = (0.0, 0.0),
    justify: str = "left",
    layer: LayerSpec | None = "WG",
    layers: LayerSpecs | None = None,
    font: Callable[..., dict[str, str]] = rectangular_font,
) -> Component

Pixel based font, guaranteed to be manhattan, without acute angles.

Parameters:

Name Type Description Default
text str

string.

'abcd'
size float

pixel size in um.

10.0
position tuple[float, float]

coordinate.

(0.0, 0.0)
justify str

left, right or center.

'left'
layer LayerSpec | None

for text.

'WG'
layers LayerSpecs | None

optional for duplicating the text.

None
font Callable[..., dict[str, str]]

function that returns dictionary of characters.

rectangular_font
Source code in gdsfactory/components/texts/text_rectangular.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
@gf.cell_with_module_name(tags=["texts"])
def text_rectangular(
    text: str = "abcd",
    size: float = 10.0,
    position: tuple[float, float] = (0.0, 0.0),
    justify: str = "left",
    layer: LayerSpec | None = "WG",
    layers: LayerSpecs | None = None,
    font: Callable[..., dict[str, str]] = rectangular_font,
) -> Component:
    """Pixel based font, guaranteed to be manhattan, without acute angles.

    Args:
        text: string.
        size: pixel size in um.
        position: coordinate.
        justify: left, right or center.
        layer: for text.
        layers: optional for duplicating the text.
        font: function that returns dictionary of characters.
    """
    pixel_size = size
    xoffset = position[0]
    yoffset = position[1]
    component = gf.Component()
    characters = font()

    if layers is None:
        assert layer is not None, "layer is None. Please provide a layer."
        layers = [layer]

    # Extract pixel width count from font definition.
    # Example below is 5, and 7 for FONT_LITHO.
    # A: 1 1 1 1 1
    pixel_width_count = len(characters["A"].split("\n")[0])

    xoffset_factor = pixel_width_count + 1

    for line in text.split("\n"):
        for character in line:
            if character == " ":
                xoffset += pixel_size * xoffset_factor
            elif character.upper() not in characters:
                print(f"skipping character {character!r} not in font")
            else:
                pixels = characters[character.upper()]
                for layer in layers:
                    ref = component.add_ref(
                        pixel_array(pixels=pixels, pixel_size=pixel_size, layer=layer)
                    )
                    ref.move((xoffset, yoffset))
                    component.absorb(ref)
                xoffset += pixel_size * xoffset_factor

        yoffset -= pixel_size * xoffset_factor
        xoffset = position[0]

    c = gf.Component()
    ref = c << component
    justify = justify.lower()
    if justify == "left":
        ref.xmin = position[0]
    elif justify == "right":
        ref.xmax = position[0]
    elif justify == "center":
        ref.x = 0
    else:
        raise ValueError(f"{justify=} not valid (left, center, right)")
    c.flatten()
    return c

text_rectangular_multi_layer

text_rectangular_multi_layer(
    text: str = "abcd",
    layers: LayerSpecs = ("WG", "M1", "M2", "MTOP"),
    text_factory: ComponentSpec = text_rectangular,
    **kwargs: Any
) -> Component

Returns rectangular text in different layers.

Parameters:

Name Type Description Default
text str

string of text.

'abcd'
layers LayerSpecs

list of layers to replicate the text.

('WG', 'M1', 'M2', 'MTOP')
text_factory ComponentSpec

function to create the text Components.

text_rectangular
kwargs Any

keyword arguments for text_factory.

{}

Other Parameters:

Name Type Description
size

pixel size.

position

coordinate.

justify

left, right or center.

font

function that returns dictionary of characters.

Source code in gdsfactory/components/texts/text_rectangular.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@gf.cell_with_module_name(tags=["texts"])
def text_rectangular_multi_layer(
    text: str = "abcd",
    layers: LayerSpecs = ("WG", "M1", "M2", "MTOP"),
    text_factory: ComponentSpec = text_rectangular,
    **kwargs: Any,
) -> Component:
    """Returns rectangular text in different layers.

    Args:
        text: string of text.
        layers: list of layers to replicate the text.
        text_factory: function to create the text Components.
        kwargs: keyword arguments for text_factory.

    Keyword Args:
        size: pixel size.
        position: coordinate.
        justify: left, right or center.
        font: function that returns dictionary of characters.
    """
    return copy_layers(factory=text_factory, text=text, layers=layers, **kwargs)

text_rectangular

text_rectangular_multi_layer

text_rectangular_multi_layer(
    text: str = "abcd",
    layers: LayerSpecs = ("WG", "M1", "M2", "MTOP"),
    text_factory: ComponentSpec = text_rectangular,
    **kwargs: Any
) -> Component

Returns rectangular text in different layers.

Parameters:

Name Type Description Default
text str

string of text.

'abcd'
layers LayerSpecs

list of layers to replicate the text.

('WG', 'M1', 'M2', 'MTOP')
text_factory ComponentSpec

function to create the text Components.

text_rectangular
kwargs Any

keyword arguments for text_factory.

{}

Other Parameters:

Name Type Description
size

pixel size.

position

coordinate.

justify

left, right or center.

font

function that returns dictionary of characters.

Source code in gdsfactory/components/texts/text_rectangular.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
@gf.cell_with_module_name(tags=["texts"])
def text_rectangular_multi_layer(
    text: str = "abcd",
    layers: LayerSpecs = ("WG", "M1", "M2", "MTOP"),
    text_factory: ComponentSpec = text_rectangular,
    **kwargs: Any,
) -> Component:
    """Returns rectangular text in different layers.

    Args:
        text: string of text.
        layers: list of layers to replicate the text.
        text_factory: function to create the text Components.
        kwargs: keyword arguments for text_factory.

    Keyword Args:
        size: pixel size.
        position: coordinate.
        justify: left, right or center.
        font: function that returns dictionary of characters.
    """
    return copy_layers(factory=text_factory, text=text, layers=layers, **kwargs)

text_rectangular_multi_layer

vias

via

via

via(
    size: Size = (0.7, 0.7),
    enclosure: float = 1.0,
    layer: LayerSpec = "VIAC",
    bbox_layers: Sequence[LayerSpec] | None = None,
    bbox_offset: float = 0,
    bbox_offsets: Sequence[float] | None = None,
    pitch: float = 2,
    column_pitch: float | None = None,
    row_pitch: float | None = None,
) -> Component

Rectangular via.

Parameters:

Name Type Description Default
size Size

in x and y direction.

(0.7, 0.7)
enclosure float

inclusion of via.

1.0
layer LayerSpec

via layer.

'VIAC'
bbox_layers Sequence[LayerSpec] | None

layers for the bounding box.

None
bbox_offset float

in um.

0
bbox_offsets Sequence[float] | None

List of offsets for each bbox_layer.

None
pitch float

pitch between vias.

2
column_pitch float | None

Optional pitch between columns of vias. Default is pitch.

None
row_pitch float | None

Optional pitch between rows of vias. Default is pitch.

None
Source code in gdsfactory/components/vias/via.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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
@gf.cell_with_module_name(tags=["vias"])
def via(
    size: Size = (0.7, 0.7),
    enclosure: float = 1.0,
    layer: LayerSpec = "VIAC",
    bbox_layers: Sequence[LayerSpec] | None = None,
    bbox_offset: float = 0,
    bbox_offsets: Sequence[float] | None = None,
    pitch: float = 2,
    column_pitch: float | None = None,
    row_pitch: float | None = None,
) -> Component:
    """Rectangular via.

    Args:
        size: in x and y direction.
        enclosure: inclusion of via.
        layer: via layer.
        bbox_layers: layers for the bounding box.
        bbox_offset: in um.
        bbox_offsets: List of offsets for each bbox_layer.
        pitch: pitch between vias.
        column_pitch: Optional pitch between columns of vias. Default is pitch.
        row_pitch: Optional pitch between rows of vias. Default is pitch.

        enclosure
        _________________________________________
        |<--->                                  |
        |             gap[0]    size[0]         |
        |             <------> <----->          |
        |      ______          ______           |
        |     |      |        |      |          |
        |     |      |        |      |  size[1] |
        |     |______|        |______|          |
        |      <------------->                  |
        |           pitch                       |
        |_______________________________________|
    """
    row_pitch = row_pitch or pitch
    column_pitch = column_pitch or pitch

    c = Component()
    c.info["row_pitch"] = row_pitch
    c.info["column_pitch"] = column_pitch
    c.info["enclosure"] = enclosure
    c.info["xsize"] = size[0]
    c.info["ysize"] = size[1]

    width, height = size
    a = width / 2
    b = height / 2
    c.add_polygon([(-a, -b), (a, -b), (a, b), (-a, b)], layer=layer)

    bbox_layers = bbox_layers or []
    bbox_offsets = bbox_offsets or [bbox_offset] * len(bbox_layers)

    if len(bbox_offsets) != len(bbox_layers):
        raise ValueError(
            f"bbox_offsets {bbox_offsets=} should have the same length as bbox_layers {bbox_layers=}"
        )

    for layer, bbox_offset in zip(bbox_layers, bbox_offsets, strict=False):
        a = (width + 2 * bbox_offset) / 2
        b = (height + 2 * bbox_offset) / 2
        c.add_polygon([(-a, -b), (a, -b), (a, b), (-a, b)], layer=layer)
    return c

via_circular

via_circular(
    radius: float = 0.35,
    enclosure: float = 1.0,
    layer: LayerSpec = "VIAC",
    pitch: float | None = 2,
    column_pitch: float | None = None,
    row_pitch: float | None = None,
    angle_resolution: float = 2.5,
) -> Component

Circular via.

Parameters:

Name Type Description Default
radius float

in um.

0.35
enclosure float

inclusion of via in um for the layer above.

1.0
layer LayerSpec

via layer.

'VIAC'
pitch float | None

pitch between vias.

2
column_pitch float | None

Optional pitch between columns of vias. Default is pitch.

None
row_pitch float | None

Optional pitch between rows of vias. Default is pitch.

None
angle_resolution float

number of degrees per point.

2.5
Source code in gdsfactory/components/vias/via.py
 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
@gf.cell_with_module_name(tags=["vias"])
def via_circular(
    radius: float = 0.35,
    enclosure: float = 1.0,
    layer: LayerSpec = "VIAC",
    pitch: float | None = 2,
    column_pitch: float | None = None,
    row_pitch: float | None = None,
    angle_resolution: float = 2.5,
) -> Component:
    """Circular via.

    Args:
        radius: in um.
        enclosure: inclusion of via in um for the layer above.
        layer: via layer.
        pitch: pitch between vias.
        column_pitch: Optional pitch between columns of vias. Default is pitch.
        row_pitch: Optional pitch between rows of vias. Default is pitch.
        angle_resolution: number of degrees per point.
    """
    if radius <= 0:
        raise ValueError(f"radius={radius} must be > 0")
    c = Component()
    t = np.linspace(0, 360, int(360 / angle_resolution) + 1) * np.pi / 180
    xpts = (radius * np.cos(t)).tolist()
    ypts = (radius * np.sin(t)).tolist()
    xpts = cast("list[float]", xpts)
    ypts = cast("list[float]", ypts)
    c.add_polygon(points=list(zip(xpts, ypts, strict=False)), layer=layer)
    row_pitch = row_pitch or pitch
    column_pitch = column_pitch or pitch

    c.info["row_pitch"] = row_pitch
    c.info["column_pitch"] = column_pitch
    c.info["enclosure"] = enclosure
    c.info["radius"] = radius
    c.info["xsize"] = 2 * radius
    c.info["ysize"] = 2 * radius
    return c

via

via_chain

Via chain.

via_chain

via_chain(
    num_vias: int = 100,
    cols: int = 10,
    via: ComponentSpec = "via1",
    contact: ComponentSpec = "via_stack_m2_m3",
    layers_bot: LayerSpecs = ("M1",),
    layers_top: LayerSpecs = ("M2",),
    offsets_top: tuple[float, ...] = (0,),
    offsets_bot: tuple[float, ...] = (0,),
    via_min_enclosure: float = 1.0,
    min_metal_spacing: float = 1.0,
    contact_offset: float = 0.0,
) -> Component

Via chain to extract via resistance.

Parameters:

Name Type Description Default
num_vias int

number of vias.

100
cols int

number of column pairs.

10
via ComponentSpec

via component.

'via1'
contact ComponentSpec

contact component.

'via_stack_m2_m3'
layers_bot LayerSpecs

list of bottom layers.

('M1',)
layers_top LayerSpecs

list of top layers.

('M2',)
offsets_top tuple[float, ...]

list of top layer offsets.

(0,)
offsets_bot tuple[float, ...]

list of bottom layer offsets.

(0,)
via_min_enclosure float

via_min_enclosure.

1.0
min_metal_spacing float

min_metal_spacing.

1.0
contact_offset float

contact offset.

0.0
side view
                              min_metal_spacing

┌────────────────────────────────────┐ ┌────────────────────────────────────┐ │ layers_top │ │ │ │ │◄───────────► │ │ └─────────────┬─────┬────────────────┘ └───────────────┬─────┬──────────────┘ │ │ via_enclosure │ │ │ │◄───────────────► │ │ │ │ │ │ │ │ │ │ │width│ │ │ ◄─────► │ │ │ │ │ │ ┌─────────────┴─────┴───────────────────────────────────────────────┴─────┴───────────────┐ │ layers_bot │ │ │ └─────────────────────────────────────────────────────────────────────────────────────────┘

◄─────────────────────────────────────────────────────────────────────────────────────────► 2e + w + min_metal_spacing + 2e + w

required
Source code in gdsfactory/components/vias/via_chain.py
 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
 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
@gf.cell_with_module_name(tags=["vias"])
def via_chain(
    num_vias: int = 100,
    cols: int = 10,
    via: ComponentSpec = "via1",
    contact: ComponentSpec = "via_stack_m2_m3",
    layers_bot: LayerSpecs = ("M1",),
    layers_top: LayerSpecs = ("M2",),
    offsets_top: tuple[float, ...] = (0,),
    offsets_bot: tuple[float, ...] = (0,),
    via_min_enclosure: float = 1.0,
    min_metal_spacing: float = 1.0,
    contact_offset: float = 0.0,
) -> Component:
    """Via chain to extract via resistance.

    Args:
        num_vias: number of vias.
        cols: number of column pairs.
        via: via component.
        contact: contact component.
        layers_bot: list of bottom layers.
        layers_top: list of top layers.
        offsets_top: list of top layer offsets.
        offsets_bot: list of bottom layer offsets.
        via_min_enclosure: via_min_enclosure.
        min_metal_spacing: min_metal_spacing.
        contact_offset: contact offset.

        side view:
                                              min_metal_spacing
           ┌────────────────────────────────────┐              ┌────────────────────────────────────┐
           │  layers_top                        │              │                                    │
           │                                    │◄───────────► │                                    │
           └─────────────┬─────┬────────────────┘              └───────────────┬─────┬──────────────┘
                         │     │         via_enclosure                         │     │
                         │     │◄───────────────►                              │     │
                         │     │                                               │     │
                         │     │                                               │     │
                         │width│                                               │     │
                         ◄─────►                                               │     │
                         │     │                                               │     │
           ┌─────────────┴─────┴───────────────────────────────────────────────┴─────┴───────────────┐
           │ layers_bot                                                                              │
           │                                                                                         │
           └─────────────────────────────────────────────────────────────────────────────────────────┘

           ◄─────────────────────────────────────────────────────────────────────────────────────────►
                                         2*e + w + min_metal_spacing + 2*e + w

    """
    if cols % 2 != 0:
        raise ValueError(f"{cols=} must be even")

    c = gf.Component()
    rows = round(num_vias / cols)

    if int(rows) != rows:
        raise ValueError(f"{num_vias=} must be a multiple of {cols=}")

    if rows <= 1:
        raise ValueError(
            f"rows must be at least 2. Got {rows=}. You can increase the number vias {num_vias=}."
        )

    if rows % 2 != 0:
        raise ValueError(
            f"{rows=} must be even. Number of vias needs to be a multiple of {2*cols=}."
        )

    via = gf.get_component(via)
    contact = gf.get_component(contact)
    via_width = via.xsize
    wire_length = 2 * (2 * via_min_enclosure + via_width) + min_metal_spacing
    wire_width = via_width + 2 * via_min_enclosure

    wire_size = (wire_length, wire_width)
    column_pitch = 2 * via_min_enclosure + min_metal_spacing + via_width
    row_pitch = wire_width + min_metal_spacing
    vias = c.add_ref(
        component=via,
        columns=cols,
        rows=rows,
        column_pitch=column_pitch,
        row_pitch=row_pitch,
    )
    top_wire = gf.c.rectangles(size=wire_size, layers=layers_top, offsets=offsets_top)
    top_wires = c.add_ref(
        component=top_wire,
        columns=cols // 2,
        rows=rows,
        column_pitch=wire_length + min_metal_spacing,
        row_pitch=wire_width + min_metal_spacing,
    )
    bot_wire = gf.c.rectangles(size=wire_size, layers=layers_bot, offsets=offsets_bot)
    bot_wires = c.add_ref(
        component=bot_wire,
        columns=cols // 2,
        rows=rows,
        column_pitch=wire_length + min_metal_spacing,
        row_pitch=wire_width + min_metal_spacing,
    )
    top_wires.xmin = -via_min_enclosure
    bot_wires.xmin = top_wires.xmin + wire_length / 2 + min_metal_spacing / 2
    bot_wires.ymin = -via_min_enclosure
    top_wires.ymin = -via_min_enclosure
    vias.xmin = top_wires.xmin + via_min_enclosure + column_pitch
    vias.ymin = top_wires.ymin + via_min_enclosure

    vertical_wire_left = gf.c.rectangle(
        size=(2 * via_min_enclosure + via_width, 2 * wire_width + min_metal_spacing),
        layer=layers_top[0],
    )

    right_wires = c.add_ref(
        component=vertical_wire_left,
        columns=1,
        rows=rows // 2,
        column_pitch=wire_length + min_metal_spacing,
        row_pitch=2 * (wire_width + min_metal_spacing),
    )

    right_wires.xmax = bot_wires.xmax
    right_wires.ymin = bot_wires.ymin

    left_wires = c.add_ref(
        component=vertical_wire_left,
        columns=1,
        rows=rows // 2 - 1,
        column_pitch=wire_length + min_metal_spacing,
        row_pitch=2 * (wire_width + min_metal_spacing),
    )

    left_wires.xmin = top_wires.xmin
    left_wires.ymin = bot_wires.ymin + wire_width + min_metal_spacing

    contact1 = c << contact
    contact2 = c << contact

    contact1.xmax = top_wires.xmin + contact_offset
    contact2.xmax = top_wires.xmin + contact_offset

    contact1.ymax = top_wires.ymin + wire_width + contact_offset
    contact2.ymin = top_wires.ymax - wire_width - contact_offset
    e1 = c.add_port(name="e1", port=contact1.ports["e1"])
    e2 = c.add_port(name="e2", port=contact2.ports["e1"])
    c.create_pin(ports=[e1], name="e1")
    c.create_pin(ports=[e2], name="e2")
    return c

via_chain

via_circular

via_circular(
    radius: float = 0.35,
    enclosure: float = 1.0,
    layer: LayerSpec = "VIAC",
    pitch: float | None = 2,
    column_pitch: float | None = None,
    row_pitch: float | None = None,
    angle_resolution: float = 2.5,
) -> Component

Circular via.

Parameters:

Name Type Description Default
radius float

in um.

0.35
enclosure float

inclusion of via in um for the layer above.

1.0
layer LayerSpec

via layer.

'VIAC'
pitch float | None

pitch between vias.

2
column_pitch float | None

Optional pitch between columns of vias. Default is pitch.

None
row_pitch float | None

Optional pitch between rows of vias. Default is pitch.

None
angle_resolution float

number of degrees per point.

2.5
Source code in gdsfactory/components/vias/via.py
 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
@gf.cell_with_module_name(tags=["vias"])
def via_circular(
    radius: float = 0.35,
    enclosure: float = 1.0,
    layer: LayerSpec = "VIAC",
    pitch: float | None = 2,
    column_pitch: float | None = None,
    row_pitch: float | None = None,
    angle_resolution: float = 2.5,
) -> Component:
    """Circular via.

    Args:
        radius: in um.
        enclosure: inclusion of via in um for the layer above.
        layer: via layer.
        pitch: pitch between vias.
        column_pitch: Optional pitch between columns of vias. Default is pitch.
        row_pitch: Optional pitch between rows of vias. Default is pitch.
        angle_resolution: number of degrees per point.
    """
    if radius <= 0:
        raise ValueError(f"radius={radius} must be > 0")
    c = Component()
    t = np.linspace(0, 360, int(360 / angle_resolution) + 1) * np.pi / 180
    xpts = (radius * np.cos(t)).tolist()
    ypts = (radius * np.sin(t)).tolist()
    xpts = cast("list[float]", xpts)
    ypts = cast("list[float]", ypts)
    c.add_polygon(points=list(zip(xpts, ypts, strict=False)), layer=layer)
    row_pitch = row_pitch or pitch
    column_pitch = column_pitch or pitch

    c.info["row_pitch"] = row_pitch
    c.info["column_pitch"] = column_pitch
    c.info["enclosure"] = enclosure
    c.info["radius"] = radius
    c.info["xsize"] = 2 * radius
    c.info["ysize"] = 2 * radius
    return c

via_circular

via_corner

via_corner

via_corner(
    cross_section: MultiCrossSectionAngleSpec = (
        (metal2, (0, 180)),
        (metal3, (90, 270)),
    ),
    vias: tuple[ComponentSpec] = ("via1",),
    layers_labels: tuple[str, ...] = ("m2", "m3"),
    **kwargs: Any
) -> gf.Component

Returns Corner via.

Use in place of wire_corner to route between two layers.

Parameters:

Name Type Description Default
cross_section MultiCrossSectionAngleSpec

list of cross_section, orientation pairs.

((metal2, (0, 180)), (metal3, (90, 270)))
vias tuple[ComponentSpec]

vias to use to fill the rectangles.

('via1',)
layers_labels tuple[str, ...]

Labels to use for each layer.

('m2', 'm3')
kwargs Any

cross_section settings.

{}
Source code in gdsfactory/components/vias/via_corner.py
 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
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
@gf.cell_with_module_name(tags=["vias"])
def via_corner(
    cross_section: MultiCrossSectionAngleSpec = (
        (metal2, (0, 180)),
        (metal3, (90, 270)),
    ),
    vias: tuple[ComponentSpec] = ("via1",),
    layers_labels: tuple[str, ...] = ("m2", "m3"),
    **kwargs: Any,
) -> gf.Component:
    """Returns Corner via.

    Use in place of wire_corner to route between two layers.

    Args:
        cross_section: list of cross_section, orientation pairs.
        vias: vias to use to fill the rectangles.
        layers_labels: Labels to use for each layer.
        kwargs: cross_section settings.
    """
    cross_sections = [gf.get_cross_section(x[0], **kwargs) for x in cross_section]
    port_orientations = [x[1] for x in cross_section]
    widths = heights = [x.width for x in cross_sections]
    layers = [x.layer for x in cross_sections]
    layers_ports = layers

    max_width = max(widths)
    max_height = max(heights)
    min_height = min(heights)
    min_width = min(widths)

    a = min_width / 2
    b = min_height / 2

    c = gf.Component()
    c.info["size"] = (float(max_width), float(max_height))
    c.info["length"] = max(max_width, max_height)

    port_type = "electrical"
    for i, layer in enumerate(layers):
        assert layer is not None
        ref = c << gf.c.compass(
            size=(widths[i], heights[i]), layer=layer, port_type=port_type
        )
        if layer in layers_ports:
            orientations = port_orientations[i]
            if (90 in orientations) or (270 in orientations):
                orientation = 90
            elif (0 in orientations) or (180 in orientations):
                orientation = 180
            else:
                raise ValueError(f"Port orientation {orientations} not valid.")
            ports = select_ports(ref.ports, orientation=orientation)
            c.add_ports(ports, prefix=f"{layers_labels[i]}_")

    for via in vias:
        via = gf.get_component(via)
        if "xsize" not in via.info:
            raise ValueError(f"Via {via.name!r} does not have xsize in info")

        if "ysize" not in via.info:
            raise ValueError(f"Via {via.name!r} does not have ysize in info")

        if "enclosure" not in via.info:
            raise ValueError(f"Via {via.name!r} does not have enclosure in info")

        if "column_pitch" not in via.info:
            raise ValueError(
                f"Component {via.name!r} does not have a 'column_pitch' key in info"
            )
        if "row_pitch" not in via.info:
            raise ValueError(
                f"Component {via.name!r} does not have a 'row_pitch' key in info"
            )

        w = via.info["xsize"]
        h = via.info["ysize"]
        g = via.info["enclosure"]
        pitch_y = via.info["row_pitch"]
        pitch_x = via.info["column_pitch"]

        nb_vias_x = (min_width - w - 2 * g) / pitch_x + 1
        nb_vias_y = (min_height - h - 2 * g) / pitch_y + 1

        nb_vias_x = int(floor(nb_vias_x)) or 1
        nb_vias_y = int(floor(nb_vias_y)) or 1
        ref = c.add_ref(
            via,
            columns=nb_vias_x,
            rows=nb_vias_y,
            column_pitch=pitch_x,
            row_pitch=pitch_y,
        )

        cw = (min_width - (nb_vias_x - 1) * pitch_x - w) / 2
        ch = (min_height - (nb_vias_y - 1) * pitch_y - h) / 2
        x0 = -a + cw + w / 2
        y0 = -b + ch + h / 2
        ref.move((x0, y0))
    elec = [p for p in c.ports if p.port_type == "electrical"]
    if elec:
        c.create_pin(ports=elec, name="pad")
    return c

via_corner

via_stack

via_stack

via_stack(
    size: Size = (11.0, 11.0),
    layers: LayerSpecs = ("M1", "M2", "MTOP"),
    layer_offsets: (
        Floats
        | tuple[float | tuple[float, float], ...]
        | None
    ) = None,
    vias: Sequence[ComponentSpec | None] = (
        "via1",
        "via2",
        None,
    ),
    layer_to_port_orientations: (
        dict[LayerSpec, list[int]] | None
    ) = None,
    correct_size: bool = False,
    slot_horizontal: bool = False,
    slot_vertical: bool = False,
    port_orientations: Ints | None = (180, 90, 0, -90),
) -> Component

Rectangular via array stack.

You can use it to connect different metal layers or metals to silicon. You can use the naming convention via_stack_layerSource_layerDestination contains 4 ports (e1, e2, e3, e4)

also know as Via array http://www.vlsi-expert.com/2017/12/vias.html

Parameters:

Name Type Description Default
size Size

of the layers.

(11.0, 11.0)
layers LayerSpecs

layers on which to draw rectangles.

('M1', 'M2', 'MTOP')
layer_offsets Floats | tuple[float | tuple[float, float], ...] | None

Optional offsets for each layer with respect to size. positive grows, negative shrinks the size. If a tuple, it is the offset in x and y.

None
vias Sequence[ComponentSpec | None]

vias to use to fill the rectangles.

('via1', 'via2', None)
layer_to_port_orientations dict[LayerSpec, list[int]] | None

dictionary of layer to port_orientations.

None
correct_size bool

if True, if the specified dimensions are too small it increases them to the minimum possible to fit a via.

False
slot_horizontal bool

if True, then vias are horizontal.

False
slot_vertical bool

if True, then vias are vertical.

False
port_orientations Ints | None

list of port_orientations to add. None does not add ports.

(180, 90, 0, -90)
Source code in gdsfactory/components/vias/via_stack.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
258
259
260
261
262
263
264
265
266
@gf.cell_with_module_name(tags=["vias"])
def via_stack(
    size: Size = (11.0, 11.0),
    layers: LayerSpecs = ("M1", "M2", "MTOP"),
    layer_offsets: Floats | tuple[float | tuple[float, float], ...] | None = None,
    vias: Sequence[ComponentSpec | None] = ("via1", "via2", None),
    layer_to_port_orientations: dict[LayerSpec, list[int]] | None = None,
    correct_size: bool = False,
    slot_horizontal: bool = False,
    slot_vertical: bool = False,
    port_orientations: Ints | None = (180, 90, 0, -90),
) -> Component:
    """Rectangular via array stack.

    You can use it to connect different metal layers or metals to silicon.
    You can use the naming convention via_stack_layerSource_layerDestination
    contains 4 ports (e1, e2, e3, e4)

    also know as Via array
    http://www.vlsi-expert.com/2017/12/vias.html

    Args:
        size: of the layers.
        layers: layers on which to draw rectangles.
        layer_offsets: Optional offsets for each layer with respect to size.
            positive grows, negative shrinks the size. If a tuple, it is the offset in x and y.
        vias: vias to use to fill the rectangles.
        layer_to_port_orientations: dictionary of layer to port_orientations.
        correct_size: if True, if the specified dimensions are too small it increases
            them to the minimum possible to fit a via.
        slot_horizontal: if True, then vias are horizontal.
        slot_vertical: if True, then vias are vertical.
        port_orientations: list of port_orientations to add. None does not add ports.
    """
    width_m, height_m = size

    layers = layers or []
    layer_indices = [gf.get_layer(layer) for layer in layers]
    layer_offsets = layer_offsets or [0] * len(layers)
    layer_to_port_orientations_dict = layer_to_port_orientations or {
        layers[-1]: list(port_orientations or [])
    }
    resolved_port_orientations = {
        gf.get_layer(k): v for k, v in layer_to_port_orientations_dict.items()
    }

    elements = {len(layers), len(layer_offsets), len(vias)}
    if len(elements) > 1:
        warnings.warn(
            f"Got {len(layers)} layers, {len(layer_offsets)} layer_offsets, {len(vias)} vias",
            stacklevel=3,
        )

    # Determine required size from all vias BEFORE drawing metal layers
    vias_list = vias or []
    for via, offset in zip(vias_list, layer_offsets, strict=False):
        if via is not None:
            width, height = size
            if isinstance(offset, Iterable):
                offset_x = offset[0]
                offset_y = offset[1]
            else:
                offset_x = offset_y = offset
            width += 2 * offset_x
            height += 2 * offset_y

            _via = gf.get_component(via)
            if "xsize" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'xsize' key in info"
                )
            if "ysize" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'ysize' key in info"
                )
            if "column_pitch" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'column_pitch' key in info"
                )
            if "row_pitch" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'row_pitch' key in info"
                )

            w, h = _via.xsize, _via.ysize
            enclosure = _via.info["enclosure"]

            min_width = w + 2 * enclosure
            min_height = h + 2 * enclosure

            # Check and correct size if needed
            if correct_size and (min_width > width or min_height > height):
                corrected_width = max(min_width, width)
                corrected_height = max(min_height, height)
                warnings.warn(
                    f"Changing size from ({width}, {height}) to ({corrected_width}, {corrected_height}) to fit a via!",
                    stacklevel=3,
                )
                # Update the base size (accounting for offsets)
                width_m = max(width_m, corrected_width - 2 * offset_x)
                height_m = max(height_m, corrected_height - 2 * offset_y)
            elif min_width > width or min_height > height:
                raise ValueError(
                    f"Enclosure cannot be satisfied: size ({width}, {height}) is too small "
                    f"to fit a {(w, h)} um via with enclosure={enclosure}. "
                    f"Minimum required size is ({min_width}, {min_height})."
                )

    c = Component()
    c.info["xsize"], c.info["ysize"] = (width_m, height_m)

    multiple_port_layers = len(resolved_port_orientations) > 1

    # Draw metal layers with corrected size
    for layer_index, offset in zip(layer_indices, layer_offsets, strict=False):
        if isinstance(offset, Iterable):
            offset_x = offset[0]
            offset_y = offset[1]
        else:
            offset_x = offset_y = offset

        size_m = (width_m + 2 * offset_x, height_m + 2 * offset_y)

        if layer_index in resolved_port_orientations:
            ref = c << gf.c.compass(
                size=size_m,
                layer=layer_index,
                port_type="electrical",
                port_orientations=resolved_port_orientations[layer_index],
                auto_rename_ports=False,
            )
            if multiple_port_layers:
                layer_name = (
                    layer_index.name
                    if hasattr(layer_index, "name")
                    else f"{layer_index[0]}_{layer_index[1]}"
                )
                for port in ref.ports:
                    c.add_port(name=f"{port.name}_{layer_name}", port=port)
            else:
                c.add_ports(ref.ports)
        else:
            ref = c << gf.c.compass(
                size=size_m,
                layer=layer_index,
                port_type=None,
                port_orientations=port_orientations,
            )
        # c.absorb(ref)

    # Place vias using the corrected size
    for via, offset in zip(vias_list, layer_offsets, strict=False):
        if via is not None:
            # Use corrected width_m, height_m plus offsets
            if isinstance(offset, Iterable):
                offset_x = offset[0]
                offset_y = offset[1]
            else:
                offset_x = offset_y = offset
            width = width_m + 2 * offset_x
            height = height_m + 2 * offset_y

            _via = gf.get_component(via)
            w, h = _via.xsize, _via.ysize
            enclosure = _via.info["enclosure"]
            pitch_y = _via.info["row_pitch"]
            pitch_x = _via.info["column_pitch"]

            if slot_horizontal:
                # Check that size allows for enclosure in horizontal slot mode
                slot_via_width = width - 2 * enclosure
                if slot_via_width <= 0:
                    raise ValueError(
                        f"Enclosure cannot be satisfied in slot_horizontal mode: "
                        f"width={width}, enclosure={enclosure}. "
                        f"Need width > 2*enclosure, got {width} <= {2 * enclosure}"
                    )
                via = gf.get_component(via, size=(slot_via_width, h))
                nb_vias_x = 1
                nb_vias_y = max(1, (height - 2 * enclosure - h) / pitch_y + 1)
                # Use slot_via_width for via sizing, but keep width for positioning
                w = slot_via_width

            elif slot_vertical:
                # Check that size allows for enclosure in vertical slot mode
                slot_via_height = height - 2 * enclosure
                if slot_via_height <= 0:
                    raise ValueError(
                        f"Enclosure cannot be satisfied in slot_vertical mode: "
                        f"height={height}, enclosure={enclosure}. "
                        f"Need height > 2*enclosure, got {height} <= {2 * enclosure}"
                    )
                via = gf.get_component(via, size=(w, slot_via_height))
                nb_vias_x = max(0, (width - w - 2 * enclosure) / pitch_x + 1)
                nb_vias_y = 1
                # Use slot_via_height for via sizing, but keep height for positioning
                h = slot_via_height
            else:
                via = _via
                nb_vias_x = max(0, (width - w - 2 * enclosure) / pitch_x + 1)
                nb_vias_y = max(0, (height - h - 2 * enclosure) / pitch_y + 1)

            nb_vias_x = int(np.floor(nb_vias_x)) or 1
            nb_vias_y = int(np.floor(nb_vias_y)) or 1
            ref = c.add_ref(
                via,
                columns=nb_vias_x,
                rows=nb_vias_y,
                column_pitch=pitch_x,
                row_pitch=pitch_y,
            )

            a = width / 2
            b = height / 2
            cw = (width - (nb_vias_x - 1) * pitch_x - w) / 2
            ch = (height - (nb_vias_y - 1) * pitch_y - h) / 2

            # Verify that enclosure is respected (with small tolerance for floating point precision)
            tolerance = 1e-9
            if cw < enclosure - tolerance or ch < enclosure - tolerance:
                raise ValueError(
                    f"Enclosure violation: calculated margins (cw={cw:.3f}, ch={ch:.3f}) "
                    f"are less than required enclosure={enclosure}. "
                    f"Size ({width:.3f}, {height:.3f}) is too small for {nb_vias_x}x{nb_vias_y} "
                    f"vias of size ({w}, {h}) with pitch ({pitch_x}, {pitch_y})."
                )

            x0 = -a + cw + w / 2
            y0 = -b + ch + h / 2
            ref.move((x0, y0))
    elec = [p for p in c.ports if p.port_type == "electrical"]
    if elec:
        c.create_pin(ports=elec, name="pad")
    return c

via_stack_corner45

via_stack_corner45(
    width: float = 10,
    layers: Sequence[LayerSpec | None] = (
        "M1",
        "M2",
        "MTOP",
    ),
    layer_offsets: Floats | None = None,
    vias: Sequence[ComponentSpec | None] = (
        "via1",
        "via2",
        None,
    ),
    layer_port: LayerSpec | None = None,
    correct_size: bool = False,
) -> Component

Rectangular via array stack at a 45 degree angle.

Parameters:

Name Type Description Default
width float

of the corner45.

10
layers Sequence[LayerSpec | None]

layers on which to draw rectangles.

('M1', 'M2', 'MTOP')
layer_offsets Floats | None

Optional offsets for each layer with respect to size. positive grows, negative shrinks the size.

None
vias Sequence[ComponentSpec | None]

vias to use to fill the rectangles.

('via1', 'via2', None)
layer_port LayerSpec | None

if None assumes port is on the last layer.

None
correct_size bool

if True, if the specified dimensions are too small it increases them to the minimum possible to fit a via.

False
Source code in gdsfactory/components/vias/via_stack.py
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
@gf.cell_with_module_name(tags=["vias"])
def via_stack_corner45(
    width: float = 10,
    layers: Sequence[LayerSpec | None] = ("M1", "M2", "MTOP"),
    layer_offsets: Floats | None = None,
    vias: Sequence[ComponentSpec | None] = ("via1", "via2", None),
    layer_port: LayerSpec | None = None,
    correct_size: bool = False,
) -> Component:
    """Rectangular via array stack at a 45 degree angle.

    Args:
        width: of the corner45.
        layers: layers on which to draw rectangles.
        layer_offsets: Optional offsets for each layer with respect to size.
            positive grows, negative shrinks the size.
        vias: vias to use to fill the rectangles.
        layer_port: if None assumes port is on the last layer.
        correct_size: if True, if the specified dimensions are too small it increases
            them to the minimum possible to fit a via.
    """
    height = width
    layers_list = layers or []
    layer_offsets_list = layer_offsets or [0] * len(layers_list)

    elements = {len(layers_list), len(layer_offsets_list), len(vias)}
    if len(elements) > 1:
        warnings.warn(
            f"Got {len(layers_list)} layers, {len(layer_offsets_list)} layer_offsets, {len(vias)} vias",
            stacklevel=3,
        )

    if layers_list:
        layer_port = layer_port or layers_list[-1]

    c = Component()
    if layer_port:
        c.info["layer"] = layer_port

    ref: ComponentReference | None = None
    for layer, offset in zip(layers_list, layer_offsets_list, strict=False):
        if layer and layer == layer_port:
            ref = c << gf.c.wire_corner45(
                width=width + 2 * offset, layer=layer, with_corner90_ports=False
            )
            c.add_ports(ref.ports)
        elif layer is not None:
            ref = c << gf.c.wire_corner45(
                width=width + 2 * offset, layer=layer, with_corner90_ports=False
            )
    assert ref is not None

    width_corner = width
    width = ref.xsize
    height = ref.ysize
    xmin = ref.xmin
    ymin = ref.ymin

    vias_list = vias or []
    for via, offset in zip(vias_list, layer_offsets_list, strict=False):
        if via is not None:
            width45 = (
                2 * (width_corner + 2 * offset) * np.cos(np.deg2rad(45))
            )  # Width in the x direction
            _via = gf.get_component(via)
            if "xsize" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'xsize' key in info"
                )
            if "ysize" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'ysize' key in info"
                )

            if "column_pitch" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'column_pitch' key in info"
                )
            if "row_pitch" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'row_pitch' key in info"
                )

            w, h = _via.info["xsize"], _via.info["ysize"]
            enclosure = _via.info["enclosure"]
            pitch_x = _via.info["column_pitch"]
            pitch_y = _via.info["row_pitch"]

            via = _via

            min_width = w + 2 * enclosure
            min_height = h + 2 * enclosure

            if (min_width > width45 and correct_size) or (
                min_width <= width45 and min_height > height and correct_size
            ):
                warnings.warn(
                    f"Changing size from ({width}, {height}) to ({min_width}, {min_height}) to fit a via!",
                    stacklevel=3,
                )
                width45 = max(min_width, width45)
                height = max(min_height, height)
            elif min_width > width45 or min_height > height:
                raise ValueError(
                    f"{min_width=} > {width=} or {min_height=} > {height=}"
                )

            # Keep placing rows until we cover the whole height
            y_covered = enclosure

            while y_covered + enclosure < height:
                y = ymin + y_covered + h / 2  # Position of the via

                # x offset from the edge of the metal to make sure enclosure is fulfilled
                xoff_enc = 2 * enclosure * np.cos(np.deg2rad(45))
                xoff = (y_covered + h) * np.tan(np.deg2rad(45)) + xoff_enc

                xpos0 = xmin + xoff

                # Calculate the number of vias that fit in a given width
                if (y_covered + h) < (height - width45):
                    # The x width is width45
                    xwidth = width45
                else:
                    # The x width is decreasing
                    xwidth = (height - (y_covered + h)) * np.tan(np.deg2rad(45))

                if min_width <= xwidth:
                    vias_per_row = (
                        xwidth - 2 * xoff_enc - 2 * h * np.tan(np.deg2rad(45))
                    ) / (pitch_x) + 1
                    # Place the vias at the given x, y
                    for i in range(int(vias_per_row)):
                        ref = c << via
                        ref.center = (xpos0 + pitch_x * i + w / 2, y)

                y_covered = y_covered + h + pitch_y

    c.flatten()
    return c

via_stack_corner45_extended

via_stack_corner45_extended(
    corner: ComponentSpec = "via_stack_corner45",
    via_stack: ComponentSpec = "via_stack",
    width: float = 3,
    length: float = 10,
) -> Component

Rectangular via array stack at a 45 degree angle.

Parameters:

Name Type Description Default
corner ComponentSpec

corner component.

'via_stack_corner45'
via_stack ComponentSpec

for the via stack.

'via_stack'
width float

of the corner45.

3
length float

of the straight.

10
Source code in gdsfactory/components/vias/via_stack.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
@gf.cell_with_module_name(tags=["vias"])
def via_stack_corner45_extended(
    corner: ComponentSpec = "via_stack_corner45",
    via_stack: ComponentSpec = "via_stack",
    width: float = 3,
    length: float = 10,
) -> Component:
    """Rectangular via array stack at a 45 degree angle.

    Args:
        corner: corner component.
        via_stack: for the via stack.
        width: of the corner45.
        length: of the straight.
    """
    c = gf.Component()
    corner_component = c << gf.get_component(corner, width=width / np.sqrt(2))
    s = gf.get_component(via_stack, size=(length, width))
    sr = c << s
    sl = c << s
    sr.connect("e1", corner_component.ports["e1"])
    sl.connect("e1", corner_component.ports["e2"])
    return c

via_stack

via_stack_corner45

via_stack_corner45(
    width: float = 10,
    layers: Sequence[LayerSpec | None] = (
        "M1",
        "M2",
        "MTOP",
    ),
    layer_offsets: Floats | None = None,
    vias: Sequence[ComponentSpec | None] = (
        "via1",
        "via2",
        None,
    ),
    layer_port: LayerSpec | None = None,
    correct_size: bool = False,
) -> Component

Rectangular via array stack at a 45 degree angle.

Parameters:

Name Type Description Default
width float

of the corner45.

10
layers Sequence[LayerSpec | None]

layers on which to draw rectangles.

('M1', 'M2', 'MTOP')
layer_offsets Floats | None

Optional offsets for each layer with respect to size. positive grows, negative shrinks the size.

None
vias Sequence[ComponentSpec | None]

vias to use to fill the rectangles.

('via1', 'via2', None)
layer_port LayerSpec | None

if None assumes port is on the last layer.

None
correct_size bool

if True, if the specified dimensions are too small it increases them to the minimum possible to fit a via.

False
Source code in gdsfactory/components/vias/via_stack.py
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
@gf.cell_with_module_name(tags=["vias"])
def via_stack_corner45(
    width: float = 10,
    layers: Sequence[LayerSpec | None] = ("M1", "M2", "MTOP"),
    layer_offsets: Floats | None = None,
    vias: Sequence[ComponentSpec | None] = ("via1", "via2", None),
    layer_port: LayerSpec | None = None,
    correct_size: bool = False,
) -> Component:
    """Rectangular via array stack at a 45 degree angle.

    Args:
        width: of the corner45.
        layers: layers on which to draw rectangles.
        layer_offsets: Optional offsets for each layer with respect to size.
            positive grows, negative shrinks the size.
        vias: vias to use to fill the rectangles.
        layer_port: if None assumes port is on the last layer.
        correct_size: if True, if the specified dimensions are too small it increases
            them to the minimum possible to fit a via.
    """
    height = width
    layers_list = layers or []
    layer_offsets_list = layer_offsets or [0] * len(layers_list)

    elements = {len(layers_list), len(layer_offsets_list), len(vias)}
    if len(elements) > 1:
        warnings.warn(
            f"Got {len(layers_list)} layers, {len(layer_offsets_list)} layer_offsets, {len(vias)} vias",
            stacklevel=3,
        )

    if layers_list:
        layer_port = layer_port or layers_list[-1]

    c = Component()
    if layer_port:
        c.info["layer"] = layer_port

    ref: ComponentReference | None = None
    for layer, offset in zip(layers_list, layer_offsets_list, strict=False):
        if layer and layer == layer_port:
            ref = c << gf.c.wire_corner45(
                width=width + 2 * offset, layer=layer, with_corner90_ports=False
            )
            c.add_ports(ref.ports)
        elif layer is not None:
            ref = c << gf.c.wire_corner45(
                width=width + 2 * offset, layer=layer, with_corner90_ports=False
            )
    assert ref is not None

    width_corner = width
    width = ref.xsize
    height = ref.ysize
    xmin = ref.xmin
    ymin = ref.ymin

    vias_list = vias or []
    for via, offset in zip(vias_list, layer_offsets_list, strict=False):
        if via is not None:
            width45 = (
                2 * (width_corner + 2 * offset) * np.cos(np.deg2rad(45))
            )  # Width in the x direction
            _via = gf.get_component(via)
            if "xsize" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'xsize' key in info"
                )
            if "ysize" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'ysize' key in info"
                )

            if "column_pitch" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'column_pitch' key in info"
                )
            if "row_pitch" not in _via.info:
                raise ValueError(
                    f"Component {_via.name!r} does not have a 'row_pitch' key in info"
                )

            w, h = _via.info["xsize"], _via.info["ysize"]
            enclosure = _via.info["enclosure"]
            pitch_x = _via.info["column_pitch"]
            pitch_y = _via.info["row_pitch"]

            via = _via

            min_width = w + 2 * enclosure
            min_height = h + 2 * enclosure

            if (min_width > width45 and correct_size) or (
                min_width <= width45 and min_height > height and correct_size
            ):
                warnings.warn(
                    f"Changing size from ({width}, {height}) to ({min_width}, {min_height}) to fit a via!",
                    stacklevel=3,
                )
                width45 = max(min_width, width45)
                height = max(min_height, height)
            elif min_width > width45 or min_height > height:
                raise ValueError(
                    f"{min_width=} > {width=} or {min_height=} > {height=}"
                )

            # Keep placing rows until we cover the whole height
            y_covered = enclosure

            while y_covered + enclosure < height:
                y = ymin + y_covered + h / 2  # Position of the via

                # x offset from the edge of the metal to make sure enclosure is fulfilled
                xoff_enc = 2 * enclosure * np.cos(np.deg2rad(45))
                xoff = (y_covered + h) * np.tan(np.deg2rad(45)) + xoff_enc

                xpos0 = xmin + xoff

                # Calculate the number of vias that fit in a given width
                if (y_covered + h) < (height - width45):
                    # The x width is width45
                    xwidth = width45
                else:
                    # The x width is decreasing
                    xwidth = (height - (y_covered + h)) * np.tan(np.deg2rad(45))

                if min_width <= xwidth:
                    vias_per_row = (
                        xwidth - 2 * xoff_enc - 2 * h * np.tan(np.deg2rad(45))
                    ) / (pitch_x) + 1
                    # Place the vias at the given x, y
                    for i in range(int(vias_per_row)):
                        ref = c << via
                        ref.center = (xpos0 + pitch_x * i + w / 2, y)

                y_covered = y_covered + h + pitch_y

    c.flatten()
    return c

via_stack_corner45

via_stack_corner45_extended

via_stack_corner45_extended(
    corner: ComponentSpec = "via_stack_corner45",
    via_stack: ComponentSpec = "via_stack",
    width: float = 3,
    length: float = 10,
) -> Component

Rectangular via array stack at a 45 degree angle.

Parameters:

Name Type Description Default
corner ComponentSpec

corner component.

'via_stack_corner45'
via_stack ComponentSpec

for the via stack.

'via_stack'
width float

of the corner45.

3
length float

of the straight.

10
Source code in gdsfactory/components/vias/via_stack.py
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
@gf.cell_with_module_name(tags=["vias"])
def via_stack_corner45_extended(
    corner: ComponentSpec = "via_stack_corner45",
    via_stack: ComponentSpec = "via_stack",
    width: float = 3,
    length: float = 10,
) -> Component:
    """Rectangular via array stack at a 45 degree angle.

    Args:
        corner: corner component.
        via_stack: for the via stack.
        width: of the corner45.
        length: of the straight.
    """
    c = gf.Component()
    corner_component = c << gf.get_component(corner, width=width / np.sqrt(2))
    s = gf.get_component(via_stack, size=(length, width))
    sr = c << s
    sl = c << s
    sr.connect("e1", corner_component.ports["e1"])
    sl.connect("e1", corner_component.ports["e2"])
    return c

via_stack_corner45_extended

via_stack_heater_mtop_mini module-attribute

via_stack_heater_mtop_mini = partial(
    via_stack_heater_mtop, size=(4, 4)
)

via_stack_heater_mtop_mini

via_stack_m1_m3 module-attribute

via_stack_m1_m3 = partial(
    via_stack,
    layers=("M1", "M2", "MTOP"),
    vias=("via1", "via2", None),
)

via_stack_m1_m3

via_stack_m1_mtop module-attribute

via_stack_m1_mtop = partial(
    via_stack,
    layers=("M1", "M2", "MTOP"),
    vias=("via1", "via2", None),
)

via_stack_m1_mtop

via_stack_m2_m3 module-attribute

via_stack_m2_m3 = partial(
    via_stack, layers=("M2", "MTOP"), vias=("via2", None)
)

via_stack_m2_m3

via_stack_npp_m1 module-attribute

via_stack_npp_m1 = partial(
    via_stack,
    layers=("WG", "NPP", "M1"),
    vias=(None, None, "viac"),
)

via_stack_npp_m1

via_stack_slab_m1 module-attribute

via_stack_slab_m1 = partial(
    via_stack,
    layers=("SLAB90", "M1"),
    vias=("viac", "via1"),
)

via_stack_slab_m1

via_stack_slab_m1_horizontal module-attribute

via_stack_slab_m1_horizontal = partial(
    via_stack_slab_m1, slot_horizontal=True
)

via_stack_slab_m1_horizontal

via_stack_slab_m2 module-attribute

via_stack_slab_m2 = partial(
    via_stack,
    layers=("SLAB90", "M1", "M2"),
    vias=("viac", "via1", None),
)

via_stack_slab_m2

via_stack_slab_npp_m3 module-attribute

via_stack_slab_npp_m3 = partial(
    via_stack,
    layers=("SLAB90", "NPP", "M1"),
    vias=(None, None, "viac"),
)

via_stack_slab_npp_m3

via_stack_with_offset

via_stack_with_offset

via_stack_with_offset(
    layers: LayerSpecs = ("PPP", "M1"),
    size: Size | None = (10, 10),
    sizes: Sequence[Size] | None = None,
    layer_offsets: Sequence[float] | None = None,
    vias: Sequence[ComponentSpec | None] = (None, "viac"),
    offsets: Sequence[float] | None = None,
    layer_to_port_orientations: (
        dict[LayerSpec, list[int]] | None
    ) = None,
) -> Component

Rectangular layer transition with offset between layers.

Parameters:

Name Type Description Default
layers LayerSpecs

layer specs between vias.

('PPP', 'M1')
size Size | None

for all vias array.

(10, 10)
sizes Sequence[Size] | None

Optional size for each via array. Overrides size.

None
layer_offsets Sequence[float] | None

Optional offsets for each layer with respect to size. positive grows, negative shrinks the size.

None
vias Sequence[ComponentSpec | None]

via spec for previous layer. None for no via.

(None, 'viac')
offsets Sequence[float] | None

optional offset for each layer relatively to the previous one. By default it only offsets by size[1] if there is a via.

None
layer_to_port_orientations dict[LayerSpec, list[int]] | None

Optional dictionary with layer to port orientations.

None
Source code in gdsfactory/components/vias/via_stack_with_offset.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
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
@gf.cell_with_module_name(tags=["vias"])
def via_stack_with_offset(
    layers: LayerSpecs = ("PPP", "M1"),
    size: Size | None = (10, 10),
    sizes: Sequence[Size] | None = None,
    layer_offsets: Sequence[float] | None = None,
    vias: Sequence[ComponentSpec | None] = (None, "viac"),
    offsets: Sequence[float] | None = None,
    layer_to_port_orientations: dict[LayerSpec, list[int]] | None = None,
) -> Component:
    """Rectangular layer transition with offset between layers.

    Args:
        layers: layer specs between vias.
        size: for all vias array.
        sizes: Optional size for each via array. Overrides size.
        layer_offsets: Optional offsets for each layer with respect to size.
            positive grows, negative shrinks the size.
        vias: via spec for previous layer. None for no via.
        offsets: optional offset for each layer relatively to the previous one.
            By default it only offsets by size[1] if there is a via.
        layer_to_port_orientations: Optional dictionary with layer to port orientations.

        side view

         __________________________
        |                          |
        |                          | layers[2]
        |__________________________|           vias[2] = None
        |                          |
        | layer_offsets[1]+size    | layers[1]
        |__________________________|
            |     |
            vias[1]
         ___|_____|__
        |            |
        |  sizes[0]  |  layers[0]
        |____________|

            vias[0] = None

    """
    c = Component()
    y0 = 0.0

    if sizes and layer_offsets:
        raise ValueError("You need to set either sizes or layer_offsets")

    if size and sizes:
        raise ValueError("You need to set either size or sizes")

    offsets = list(offsets or [0] * len(layers))
    layer_offsets = list(layer_offsets or [0] * len(layers))
    if sizes:
        sizes_list = list(sizes)
    else:
        assert size is not None
        sizes_list = [size] * len(layers)

    elements = {len(layers), len(layer_offsets), len(vias), len(sizes_list)}
    if len(elements) > 1:
        warnings.warn(
            f"Got {len(layers)} layers, {len(layer_offsets)} layer_offsets, {len(vias)} vias, {len(sizes_list)} sizes",
            stacklevel=3,
        )

    port_orientations = (180, 90, 0, -90)
    layer_to_port_orientations_dict = layer_to_port_orientations or {
        layers[-1]: list(port_orientations)
    }

    resolved_layers = [gf.get_layer(la) for la in layers]
    resolved_port_orientations = {
        gf.get_layer(k): v for k, v in layer_to_port_orientations_dict.items()
    }

    previous_layer = layers[0]

    for layer in resolved_port_orientations:
        if layer not in resolved_layers:
            raise ValueError(
                f"layer {layer} in layer_to_port_orientations not in layers {layers}"
            )

    multiple_port_layers = len(resolved_port_orientations) > 1

    for layer, resolved_layer, via, layer_size, size_offset, offset in zip(
        layers, resolved_layers, vias, sizes_list, layer_offsets, offsets, strict=False
    ):
        assert layer_size is not None
        width, height = layer_size
        width += 2 * size_offset
        height += 2 * size_offset
        x0 = -width / 2
        ref_layer = c << gf.c.compass(size=(width, height), layer=layer, port_type=None)
        ref_layer.ymin = y0

        if resolved_layer in resolved_port_orientations:
            ref_layer = c << gf.c.compass(
                size=(width, height),
                layer=layer,
                port_type="electrical",
                port_orientations=resolved_port_orientations[resolved_layer],
                auto_rename_ports=False,
            )
            ref_layer.ymin = int(y0)
            if multiple_port_layers:
                layer_name = (
                    resolved_layer.name
                    if hasattr(resolved_layer, "name")
                    else f"{resolved_layer[0]}_{resolved_layer[1]}"
                )
                for port in ref_layer.ports:
                    c.add_port(name=f"{port.name}_{layer_name}", port=port)
            else:
                c.add_ports(ref_layer.ports)
        else:
            ref_layer = c << gf.c.compass(
                size=(width, height),
                layer=previous_layer,
                port_type=None,
                port_orientations=None,
            )
            ref_layer.ymin = int(y0)

        if via:
            via = gf.get_component(via)
            if "xsize" not in via.info:
                raise ValueError(f"via {via.name!r} is missing xsize info")
            if "ysize" not in via.info:
                raise ValueError(f"via {via.name!r} is missing ysize info")
            if "enclosure" not in via.info:
                raise ValueError(f"via {via.name!r} is missing enclosure info")
            if "column_pitch" not in via.info:
                raise ValueError(
                    f"Component {via.name!r} does not have a 'column_pitch' key in info"
                )
            if "row_pitch" not in via.info:
                raise ValueError(
                    f"Component {via.name!r} does not have a 'row_pitch' key in info"
                )

            w, h = via.info["xsize"], via.info["ysize"]
            enclosure = via.info["enclosure"]
            pitch_x = via.info["column_pitch"]
            pitch_y = via.info["row_pitch"]

            nb_vias_x = (width - w - 2 * enclosure) / pitch_x + 1
            nb_vias_y = (height - h - 2 * enclosure) / pitch_y + 1

            nb_vias_x = int(abs(floor(nb_vias_x))) or 1
            nb_vias_y = int(abs(floor(nb_vias_y))) or 1

            cw = (width - (nb_vias_x - 1) * pitch_x - w) / 2
            ch = (height - (nb_vias_y - 1) * pitch_y - h) / 2

            x00 = x0 + cw + w / 2
            y00 = y0 + ch + h / 2 + offset

            ref = c.add_ref(
                via,
                columns=nb_vias_x,
                rows=nb_vias_y,
                column_pitch=pitch_x,
                row_pitch=pitch_y,
            )
            ref.move((x00, y00))
            y0 += height
            if ref.xsize + enclosure > width or ref.ysize + enclosure > height:
                warnings.warn(
                    f"size = {size} for layer {layer} violates min enclosure"
                    f" {enclosure} for via {via.name!r}",
                    stacklevel=3,
                )

        y0 += offset
        previous_layer = layer

    ref = c << gf.c.compass(
        size=(width, height),
        layer=layers[-2],
        port_type=None,
        port_orientations=None,
    )
    ref.ymin = ref_layer.ymin
    elec = [p for p in c.ports if p.port_type == "electrical"]
    if elec:
        c.create_pin(ports=elec, name="pad")
    return c

via_stack_with_offset

via_stack_with_offset_m1_m3 module-attribute

via_stack_with_offset_m1_m3 = partial(
    via_stack_with_offset,
    layers=("M1", "M2", "MTOP"),
    vias=(None, "via1", "via2"),
)

via_stack_with_offset_m1_m3

via_stack_with_offset_ppp_m1 module-attribute

via_stack_with_offset_ppp_m1 = partial(
    via_stack_with_offset,
    layers=("PPP", "M1"),
    vias=(None, "viac"),
)

via_stack_with_offset_ppp_m1

waveguides

crossing

crossing(arm: ComponentSpec = crossing_arm) -> gf.Component

Waveguide crossing.

Parameters:

Name Type Description Default
arm ComponentSpec

arm spec.

crossing_arm
Source code in gdsfactory/components/waveguides/crossing_waveguide.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
@gf.cell_with_module_name(schematic_function=crossing_schematic, tags=["waveguides"])
def crossing(
    arm: ComponentSpec = crossing_arm,
) -> gf.Component:
    """Waveguide crossing.

    Args:
        arm: arm spec.
    """
    c = gf.Component()
    arm = gf.get_component(arm)
    for rotation in [0, 90, 180, 270]:
        ref = c << arm
        ref.rotate(rotation)
        c.add_port(port=ref["o2"])
    c.auto_rename_ports()
    c.flatten()
    return c

crossing

crossing45

crossing45(
    crossing: ComponentSpec = crossing,
    port_spacing: float = 40.0,
    dx: Delta | None = None,
    alpha: float = 0.08,
    npoints: int = 101,
    cross_section: CrossSectionSpec = "strip",
    cross_section_bends: CrossSectionSpec = "strip",
) -> Component

Returns 45deg crossing with bends.

Parameters:

Name Type Description Default
crossing ComponentSpec

crossing function.

crossing
port_spacing float

target I/O port spacing.

40.0
dx Delta | None

target length.

None
alpha float

optimization parameter. diminish it for tight bends, increase it if raises assertion angle errors

0.08
npoints int

number of points.

101
cross_section CrossSectionSpec

cross_section spec.

'strip'
cross_section_bends CrossSectionSpec

cross_section spec.

'strip'

The 45 Degree crossing CANNOT be kept as an SRef since we only allow for multiples of 90Deg rotations in SRef.

----   ----
    \ /
     X
    / \
---    ----
Source code in gdsfactory/components/waveguides/crossing_waveguide.py
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
@gf.cell(
    check_instances=CheckInstances.IGNORE,
    with_module_name=True,
    schematic_function=crossing_schematic,
    tags=["waveguides"],
)
def crossing45(
    crossing: ComponentSpec = crossing,
    port_spacing: float = 40.0,
    dx: Delta | None = None,
    alpha: float = 0.08,
    npoints: int = 101,
    cross_section: CrossSectionSpec = "strip",
    cross_section_bends: CrossSectionSpec = "strip",
) -> Component:
    r"""Returns 45deg crossing with bends.

    Args:
        crossing: crossing function.
        port_spacing: target I/O port spacing.
        dx: target length.
        alpha: optimization parameter. diminish it for tight bends,
          increase it if raises assertion angle errors
        npoints: number of points.
        cross_section: cross_section spec.
        cross_section_bends: cross_section spec.


    The 45 Degree crossing CANNOT be kept as an SRef since
    we only allow for multiples of 90Deg rotations in SRef.

        ----   ----
            \ /
             X
            / \
        ---    ----

    """
    crossing = gf.get_component(crossing)

    c = Component()
    x = c.add_ref_off_grid(crossing)
    x.rotate(45)

    p_e = x.ports["o3"].center
    dx = dx or port_spacing
    dy = port_spacing / 2

    start_angle = 45
    end_angle = 0
    cpts = find_min_curv_bezier_control_points(
        start_point=p_e,
        end_point=(dx, dy),
        start_angle=start_angle,
        end_angle=end_angle,
        npoints=npoints,
        alpha=alpha,
    )

    bend = bezier(
        control_points=cpts,
        start_angle=start_angle,
        end_angle=end_angle,
        npoints=npoints,
        cross_section=cross_section_bends,
    )

    tol = 1e-2
    assert abs(bend.info["start_angle"] - start_angle) < tol, (
        f"{bend.info['start_angle']} differs from {start_angle}"
    )
    assert abs(bend.info["end_angle"] - end_angle) < tol, bend.info["end_angle"]

    b_tr = c.add_ref_off_grid(bend)
    b_tl = c.add_ref_off_grid(bend)
    b_bl = c.add_ref_off_grid(bend)
    b_br = c.add_ref_off_grid(bend)

    b_tr.connect("o2", x.ports["o3"], mirror=True)
    b_tl.connect("o2", x.ports["o1"], mirror=True)
    b_bl.connect("o2", x.ports["o4"])
    b_br.connect("o2", x.ports["o2"])

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

    c.add_port("o1", port=b_bl.ports["o1"])
    c.add_port("o2", port=b_tl.ports["o1"])
    c.add_port("o3", port=b_tr.ports["o1"])
    c.add_port("o4", port=b_br.ports["o1"])

    xs = gf.get_cross_section(cross_section)
    xs.add_bbox(c)
    return c

crossing45

crossing_etched

crossing_etched(
    width: float = 0.5,
    r1: float = 3.0,
    r2: float = 1.1,
    w: float = 1.2,
    L: float = 3.4,
    layer_wg: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB150",
) -> Component

Waveguide crossing.

Full crossing has to be on WG layer (to start with a 220nm slab). Then we etch the ellipses down to 150nm slabs and we keep linear taper at 220nm.

Parameters:

Name Type Description Default
width float

input waveguides width.

0.5
r1 float

radii.

3.0
r2 float

radii.

1.1
w float

wide width.

1.2
L float

length.

3.4
layer_wg LayerSpec

waveguide layer.

'WG'
layer_slab LayerSpec

shallow etch layer.

'SLAB150'
Source code in gdsfactory/components/waveguides/crossing_waveguide.py
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
@gf.cell_with_module_name(schematic_function=crossing_schematic, tags=["waveguides"])
def crossing_etched(
    width: float = 0.5,
    r1: float = 3.0,
    r2: float = 1.1,
    w: float = 1.2,
    L: float = 3.4,
    layer_wg: LayerSpec = "WG",
    layer_slab: LayerSpec = "SLAB150",
) -> Component:
    """Waveguide crossing.

    Full crossing has to be on WG layer (to start with a 220nm slab).
    Then we etch the ellipses down to 150nm slabs and we keep linear taper at 220nm.

    Args:
        width: input waveguides width.
        r1: radii.
        r2: radii.
        w: wide width.
        L: length.
        layer_wg: waveguide layer.
        layer_slab: shallow etch layer.
    """
    layer_wg = gf.get_layer(layer_wg)
    _ = gf.get_layer(layer_slab)

    # Draw the ellipses
    c = Component()
    _ = c << gf.c.ellipse(radii=(r1, r2), layer=layer_wg)
    _ = c << gf.c.ellipse(radii=(r2, r1), layer=layer_wg)

    a = L + w / 2
    h = width / 2

    taper_cross_pts = [
        (-a, h),
        (-w / 2, w / 2),
        (-h, a),
        (h, a),
        (w / 2, w / 2),
        (a, h),
        (a, -h),
        (w / 2, -w / 2),
        (h, -a),
        (-h, -a),
        (-w / 2, -w / 2),
        (-a, -h),
    ]

    c.add_polygon(taper_cross_pts, layer=layer_wg)

    # tapers_poly = c.add_polygon(taper_cross_pts, layer=layer_wg)
    # b = a - 0.1  # To make sure we get 4 distinct polygons when doing bool ops
    # tmp_polygon = [(-b, b), (b, b), (b, -b), (-b, -b)]
    # polys_etch = gdstk.fast_boolean([tmp_polygon], tapers_poly, "not", layer=layer_slab)
    # c.add(polys_etch)

    positions = [(a, 0), (0, a), (-a, 0), (0, -a)]
    angles = [0, 90, 180, 270]

    for i, (p, angle) in enumerate(zip(positions, angles, strict=False)):
        c.add_port(
            name=str(i),
            center=p,
            orientation=angle,
            width=width,
            layer=layer_wg,
        )
    c.auto_rename_ports()
    c.flatten()
    return c

crossing_etched

crossing_linear_taper

crossing_linear_taper(
    width1: float = 2.5,
    width2: float = 0.5,
    length: float = 3,
    cross_section: CrossSectionSpec = "strip",
    taper: ComponentSpec = "taper",
) -> Component

Returns Crossing based on a taper.

The default is a dummy taper.

Parameters:

Name Type Description Default
width1 float

input width.

2.5
width2 float

output width.

0.5
length float

taper length.

3
cross_section CrossSectionSpec

cross_section spec.

'strip'
taper ComponentSpec

taper spec.

'taper'
Source code in gdsfactory/components/waveguides/crossing_waveguide.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
@gf.cell_with_module_name(schematic_function=crossing_schematic, tags=["waveguides"])
def crossing_linear_taper(
    width1: float = 2.5,
    width2: float = 0.5,
    length: float = 3,
    cross_section: CrossSectionSpec = "strip",
    taper: ComponentSpec = "taper",
) -> Component:
    """Returns Crossing based on a taper.

    The default is a dummy taper.

    Args:
        width1: input width.
        width2: output width.
        length: taper length.
        cross_section: cross_section spec.
        taper: taper spec.
    """
    arm = gf.get_component(
        taper, width1=width1, width2=width2, length=length, cross_section=cross_section
    )
    return crossing(arm=arm)

crossing_linear_taper

straight

Straight waveguide.

straight

straight(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component

Returns a Straight waveguide.

Parameters:

Name Type Description Default
length float

straight length (um).

10.0
npoints int

number of points.

2
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/waveguides/straight.py
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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> Component:
    """Returns a Straight waveguide.

    Args:
        length: straight length (um).
        npoints: number of points.
        cross_section: specification (CrossSection, string or dict).
        width: width of the waveguide. If None, it will use the width of the cross_section.

        o1  ──────────────── o2
                length
    """
    if width is not None:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)
    p = gf.path.straight(length=length, npoints=npoints)
    c = p.extrude(x)
    x.add_bbox(c)

    c.info["length"] = length
    c.info["width"] = x.width if len(x.sections) == 0 else x.sections[0].width
    c.add_route_info(cross_section=x, length=length)
    return c

straight_all_angle

straight_all_angle(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> ComponentAllAngle

Returns a Straight waveguide with offgrid ports.

Parameters:

Name Type Description Default
length float

straight length (um).

10.0
npoints int

number of points.

2
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/waveguides/straight.py
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
@gf.vcell
def straight_all_angle(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> ComponentAllAngle:
    """Returns a Straight waveguide with offgrid ports.

    Args:
        length: straight length (um).
        npoints: number of points.
        cross_section: specification (CrossSection, string or dict).
        width: width of the waveguide. If None, it will use the width of the cross_section.

        o1  ──────────────── o2
                length
    """
    if width is not None:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)
    p = gf.path.straight(length=length, npoints=npoints)
    c = p.extrude(x, all_angle=True)
    x.add_bbox(c)

    c.info["length"] = length
    c.info["width"] = x.width if len(x.sections) == 0 else x.sections[0].width
    c.add_route_info(cross_section=x, length=length)
    return c

straight_array

straight_array(
    n: int = 4,
    spacing: float = 4.0,
    length: float = 10.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Array of straights connected with grating couplers.

useful to align the 4 corners of the chip

Parameters:

Name Type Description Default
n int

number of straights.

4
spacing float

edge to edge straight spacing.

4.0
length float

straight length (um).

10.0
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
Source code in gdsfactory/components/waveguides/straight.py
 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
@gf.cell_with_module_name(tags=["waveguides"])
def straight_array(
    n: int = 4,
    spacing: float = 4.0,
    length: float = 10.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Array of straights connected with grating couplers.

    useful to align the 4 corners of the chip

    Args:
        n: number of straights.
        spacing: edge to edge straight spacing.
        length: straight length (um).
        cross_section: specification (CrossSection, string or dict).
    """
    c = Component()
    wg = straight(cross_section=cross_section, length=length)

    for i in range(n):
        wref = c.add_ref(wg)
        wref.y += i * (spacing + wg.info["width"])
        c.add_ports(wref.ports, prefix=str(i))

    c.auto_rename_ports()
    return c

wire_straight

wire_straight(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "metal_routing",
    width: float | None = None,
) -> Component

Returns a Straight waveguide.

Parameters:

Name Type Description Default
length float

straight length (um).

10.0
npoints int

number of points.

2
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'metal_routing'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/waveguides/straight.py
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
@gf.cell_with_module_name(schematic_function=wire_schematic, tags=["waveguides"])
def wire_straight(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "metal_routing",
    width: float | None = None,
) -> Component:
    """Returns a Straight waveguide.

    Args:
        length: straight length (um).
        npoints: number of points.
        cross_section: specification (CrossSection, string or dict).
        width: width of the waveguide. If None, it will use the width of the cross_section.

        o1  ──────────────── o2
                length
    """
    if width is not None:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)
    p = gf.path.straight(length=length, npoints=npoints)
    c = p.extrude(x)
    x.add_bbox(c)

    c.info["length"] = length
    c.info["width"] = x.width if len(x.sections) == 0 else x.sections[0].width
    c.add_route_info(cross_section=x, length=length)
    return c

straight

straight_all_angle

straight_all_angle(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> ComponentAllAngle

Returns a Straight waveguide with offgrid ports.

Parameters:

Name Type Description Default
length float

straight length (um).

10.0
npoints int

number of points.

2
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/waveguides/straight.py
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
@gf.vcell
def straight_all_angle(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "strip",
    width: float | None = None,
) -> ComponentAllAngle:
    """Returns a Straight waveguide with offgrid ports.

    Args:
        length: straight length (um).
        npoints: number of points.
        cross_section: specification (CrossSection, string or dict).
        width: width of the waveguide. If None, it will use the width of the cross_section.

        o1  ──────────────── o2
                length
    """
    if width is not None:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)
    p = gf.path.straight(length=length, npoints=npoints)
    c = p.extrude(x, all_angle=True)
    x.add_bbox(c)

    c.info["length"] = length
    c.info["width"] = x.width if len(x.sections) == 0 else x.sections[0].width
    c.add_route_info(cross_section=x, length=length)
    return c
import gdsfactory as gf

gf.gpdk.PDK.activate()

c = gf.components.straight_all_angle(length=10, npoints=2, cross_section='strip').copy()
c.draw_ports()
c.plot()

straight_array

straight_array(
    n: int = 4,
    spacing: float = 4.0,
    length: float = 10.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component

Array of straights connected with grating couplers.

useful to align the 4 corners of the chip

Parameters:

Name Type Description Default
n int

number of straights.

4
spacing float

edge to edge straight spacing.

4.0
length float

straight length (um).

10.0
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'strip'
Source code in gdsfactory/components/waveguides/straight.py
 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
@gf.cell_with_module_name(tags=["waveguides"])
def straight_array(
    n: int = 4,
    spacing: float = 4.0,
    length: float = 10.0,
    cross_section: CrossSectionSpec = "strip",
) -> Component:
    """Array of straights connected with grating couplers.

    useful to align the 4 corners of the chip

    Args:
        n: number of straights.
        spacing: edge to edge straight spacing.
        length: straight length (um).
        cross_section: specification (CrossSection, string or dict).
    """
    c = Component()
    wg = straight(cross_section=cross_section, length=length)

    for i in range(n):
        wref = c.add_ref(wg)
        wref.y += i * (spacing + wg.info["width"])
        c.add_ports(wref.ports, prefix=str(i))

    c.auto_rename_ports()
    return c

straight_array

straight_heater_doped_rib

straight_heater_doped_rib(
    length: float = 320.0,
    nsections: int = 3,
    cross_section: CrossSectionSpec = "strip_rib_tip",
    cross_section_heater: CrossSectionSpec = "rib_heater_doped",
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_slab_npp_m3",
    via_stack_metal: (
        ComponentSpec | None
    ) = "via_stack_m1_mtop",
    via_stack_metal_size: Size = (10.0, 10.0),
    via_stack_size: Size = (10.0, 10.0),
    taper: ComponentSpec | None = "taper_cross_section",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    via_stack_gap: float = 0.0,
    width: float = 0.5,
    xoffset_tip1: float = 0.2,
    xoffset_tip2: float = 0.4,
) -> Component

Returns a doped thermal phase shifter.

dimensions from https://doi.org/10.1364/OE.27.010456

Parameters:

Name Type Description Default
length float

of the waveguide in um.

320.0
nsections int

between via_stacks.

3
cross_section CrossSectionSpec

for the input/output ports.

'strip_rib_tip'
cross_section_heater CrossSectionSpec

for the heater.

'rib_heater_doped'
via_stack ComponentSpec | None

optional function to connect the heater strip.

'via_stack_slab_npp_m3'
via_stack_metal ComponentSpec | None

function to connect the metal area.

'via_stack_m1_mtop'
via_stack_metal_size Size

x, y size in um.

(10.0, 10.0)
via_stack_size Size

x, y size in um.

(10.0, 10.0)
taper ComponentSpec | None

optional taper spec.

'taper_cross_section'
heater_width float

in um.

2.0
heater_gap float

in um.

0.8
via_stack_gap float

from edge of via_stack to waveguide.

0.0
width float

waveguide width on the ridge.

0.5
xoffset_tip1 float

distance in um from input taper to via_stack.

0.2
xoffset_tip2 float

distance in um from output taper to via_stack.

              length
0.4
Source code in gdsfactory/components/waveguides/straight_heater_doped.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
 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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_doped_rib(
    length: float = 320.0,
    nsections: int = 3,
    cross_section: CrossSectionSpec = "strip_rib_tip",
    cross_section_heater: CrossSectionSpec = "rib_heater_doped",
    via_stack: ComponentSpec | None = "via_stack_slab_npp_m3",
    via_stack_metal: ComponentSpec | None = "via_stack_m1_mtop",
    via_stack_metal_size: Size = (10.0, 10.0),
    via_stack_size: Size = (10.0, 10.0),
    taper: ComponentSpec | None = "taper_cross_section",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    via_stack_gap: float = 0.0,
    width: float = 0.5,
    xoffset_tip1: float = 0.2,
    xoffset_tip2: float = 0.4,
) -> Component:
    r"""Returns a doped thermal phase shifter.

    dimensions from https://doi.org/10.1364/OE.27.010456

    Args:
        length: of the waveguide in um.
        nsections: between via_stacks.
        cross_section: for the input/output ports.
        cross_section_heater: for the heater.
        via_stack: optional function to connect the heater strip.
        via_stack_metal: function to connect the metal area.
        via_stack_metal_size: x, y size in um.
        via_stack_size: x, y size in um.
        taper: optional taper spec.
        heater_width: in um.
        heater_gap: in um.
        via_stack_gap: from edge of via_stack to waveguide.
        width: waveguide width on the ridge.
        xoffset_tip1: distance in um from input taper to via_stack.
        xoffset_tip2: distance in um from output taper to via_stack.


                              length
        |<--------------------------------------------->|
        |              length_section                   |
        |    <--------------------------->              |
        |  length_via_stack                             |
        |    <------->                             taper|
        |    __________                   _________     |
        |   |          |                  |        |    |
        |   | via_stack|__________________|        |    |
        |   |  size    |  heater width    |        |    |
        |  /|__________|__________________|________|\   |
        | / |             heater_gap               | \  |
        |/  |______________________________________|  \ |
         \  |_______________width__________________|  /
          \ |                                      | /
           \|_____________heater_gap______________ |/
            |        |                    |        |
            |        |____heater_width____|        |
            |        |                    |        |
            |________|                    |________|

        taper         cross_section_heater



                                   |<------width------>|
                                    ____________________ heater_gap             slab_gap
             top_via_stack         |                   |<---------->| bot_via_stack   <-->
         ___ ______________________|                   |___________________________|___
        |   |            |               undoped Si                 |              |   |
        |   |layer_heater|               intrinsic region           |layer_heater  |   |
        |___|____________|__________________________________________|______________|___|
                                                                     <------------>
                                                                      heater_width
        <------------------------------------------------------------------------------>
                                       slab_width
    """
    c = Component()
    cross_section_heater = gf.get_cross_section(
        cross_section_heater,
        heater_width=heater_width,
        heater_gap=heater_gap,
        width=width,
    )
    taper_component: Component | None = None
    if taper:
        taper_component = gf.get_component(
            taper, cross_section1=cross_section, cross_section2=cross_section_heater
        )
        length -= taper_component.xsize * 2

    wg = c << gf.c.straight(
        cross_section=cross_section_heater,
        length=snap_to_grid(length),
    )

    if taper_component:
        taper1 = c << taper_component
        taper1.connect("o2", wg.ports["o1"])
        c.add_port("o1", port=taper1.ports["o1"])
        taper2 = c << taper_component
        taper2.dmirror()
        taper2.connect("o2", wg.ports["o2"])
        c.add_port("o2", port=taper2.ports["o1"])

    else:
        c.add_port("o2", port=wg.ports["o2"])
        c.add_port("o1", port=wg.ports["o1"])

    via_stack_section: Component | None = None
    if via_stack_metal:
        via_stack_section = gf.get_component(via_stack_metal, size=via_stack_metal_size)

    via_stacks: list[ComponentReference] = []
    length_via_stack = snap_to_grid(via_stack_size[1])
    length_section = snap_to_grid((length - length_via_stack) / nsections)
    x0 = via_stack_size[0] / 2 - xoffset_tip1

    via_stack_top: ComponentReference | None = None
    via_stack_bot: ComponentReference | None = None

    for i in range(nsections + 1):
        xi = x0 + length_section * i

        if via_stack_metal and via_stack and via_stack_section:
            via_stack_center = c.add_ref(via_stack_section)
            via_stack_center.x = xi
            via_stack_ref = c << via_stack_section
            via_stack_ref.x = xi
            via_stack_ref.y = (
                +via_stack_metal_size[1] if i % 2 == 0 else -via_stack_metal_size[1]
            )
            via_stacks.append(via_stack_ref)

        if via_stack:
            via_stack_component = gf.get_component(via_stack, size=via_stack_size)
            via_stack_top = c << via_stack_component
            via_stack_top.x = xi
            via_stack_top.ymin = +(heater_gap + width / 2 + via_stack_gap)

            via_stack_bot = c << via_stack_component
            via_stack_bot.x = xi
            via_stack_bot.ymax = -(heater_gap + width / 2 + via_stack_gap)

    if via_stack and via_stack_top and via_stack_bot:
        via_stack_top.movex(xoffset_tip2)
        via_stack_bot.movex(xoffset_tip2)

    if via_stack_metal and via_stack and via_stack_section:
        via_stack_length = length + via_stack_metal_size[0]
        via_stack_top_component = c << gf.get_component(
            via_stack_metal,
            size=(via_stack_length, via_stack_metal_size[0]),
        )
        via_stack_bot_component = c << gf.get_component(
            via_stack_metal,
            size=(via_stack_length, via_stack_metal_size[0]),
        )

        via_stack_bot_component.xmin = via_stacks[0].xmin
        via_stack_top_component.xmin = via_stacks[0].xmin

        via_stack_top_component.ymin = via_stacks[0].ymax
        via_stack_bot_component.ymax = via_stacks[1].ymin

        c.add_ports(via_stack_top_component.ports, prefix="top_")
        c.add_ports(via_stack_bot_component.ports, prefix="bot_")

    top_ports = [p for p in c.ports if p.name and p.name.startswith("top_")]
    bot_ports = [p for p in c.ports if p.name and p.name.startswith("bot_")]
    if top_ports:
        c.create_pin(ports=top_ports, name="top")
    if bot_ports:
        c.create_pin(ports=bot_ports, name="bot")

    c.flatten()
    return c

straight_heater_doped_rib

straight_heater_doped_strip

straight_heater_doped_strip(
    length: float = 320.0,
    nsections: int = 3,
    cross_section: CrossSectionSpec = "strip_heater_doped",
    cross_section_heater: CrossSectionSpec = "rib_heater_doped",
    via_stack: ComponentSpec | None = "via_stack_npp_m1",
    via_stack_metal: (
        ComponentSpec | None
    ) = "via_stack_m1_mtop",
    via_stack_metal_size: Size = (10.0, 10.0),
    via_stack_size: Size = (10.0, 10.0),
    taper: ComponentSpec | None = "taper_cross_section",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    via_stack_gap: float = 0.0,
    width: float = 0.5,
    xoffset_tip1: float = 0.2,
    xoffset_tip2: float = 0.4,
) -> Component

Top view.

Parameters:

Name Type Description Default
length float

of the waveguide in um.

320.0
nsections int

between via_stacks.

3
cross_section CrossSectionSpec

for the input/output ports.

'strip_heater_doped'
cross_section_heater CrossSectionSpec

for the heater.

'rib_heater_doped'
via_stack ComponentSpec | None

optional function to connect the heater strip.

'via_stack_npp_m1'
via_stack_metal ComponentSpec | None

function to connect the metal area.

'via_stack_m1_mtop'
via_stack_metal_size Size

x, y size in um.

(10.0, 10.0)
via_stack_size Size

x, y size in um.

(10.0, 10.0)
taper ComponentSpec | None

optional taper spec.

'taper_cross_section'
heater_width float

in um.

2.0
heater_gap float

in um.

0.8
via_stack_gap float

from edge of via_stack to waveguide.

0.0
width float

waveguide width on the ridge.

0.5
xoffset_tip1 float

distance in um from input taper to via_stack.

0.2
xoffset_tip2 float

distance in um from output taper to via_stack.

              length

<-|--------|---------------------------------> | | length_section |<---------------------------> length_via_stack |<------>| |__|____ /| |__| | / |viastack| |viastack | \ | size |___| | |_|___|_| | | cross_section_heater| | | | | | |____|

0.4

cross_section

                          |<------width------>|
  ____________             ___________________               ______________
 |            |           |     undoped Si    |             |              |
 |layer_heater|           |  intrinsic region |<----------->| layer_heater |
 |____________|           |___________________|             |______________|
                                                             <------------>
                                                heater_gap     heater_width
Source code in gdsfactory/components/waveguides/straight_heater_doped.py
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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_doped_strip(
    length: float = 320.0,
    nsections: int = 3,
    cross_section: CrossSectionSpec = "strip_heater_doped",
    cross_section_heater: CrossSectionSpec = "rib_heater_doped",
    via_stack: ComponentSpec | None = "via_stack_npp_m1",
    via_stack_metal: ComponentSpec | None = "via_stack_m1_mtop",
    via_stack_metal_size: Size = (10.0, 10.0),
    via_stack_size: Size = (10.0, 10.0),
    taper: ComponentSpec | None = "taper_cross_section",
    heater_width: float = 2.0,
    heater_gap: float = 0.8,
    via_stack_gap: float = 0.0,
    width: float = 0.5,
    xoffset_tip1: float = 0.2,
    xoffset_tip2: float = 0.4,
) -> Component:
    r"""Top view.

    Args:
        length: of the waveguide in um.
        nsections: between via_stacks.
        cross_section: for the input/output ports.
        cross_section_heater: for the heater.
        via_stack: optional function to connect the heater strip.
        via_stack_metal: function to connect the metal area.
        via_stack_metal_size: x, y size in um.
        via_stack_size: x, y size in um.
        taper: optional taper spec.
        heater_width: in um.
        heater_gap: in um.
        via_stack_gap: from edge of via_stack to waveguide.
        width: waveguide width on the ridge.
        xoffset_tip1: distance in um from input taper to via_stack.
        xoffset_tip2: distance in um from output taper to via_stack.

                              length
          <-|--------|--------------------------------->
            |        | length_section
            |<--------------------------->
           length_via_stack
            |<------>|
            |________|_______________________________
           /|        |____________________|          |
          / |viastack|                    |via_stack |
          \ | size   |____________________|          |
           \|________|____________________|__________|
                                          |          |
                      cross_section_heater|          |
                                          |          |
                                          |          |
                                          |__________|

    cross_section

                                  |<------width------>|
          ____________             ___________________               ______________
         |            |           |     undoped Si    |             |              |
         |layer_heater|           |  intrinsic region |<----------->| layer_heater |
         |____________|           |___________________|             |______________|
                                                                     <------------>
                                                        heater_gap     heater_width
    """
    return straight_heater_doped_rib(
        length=length,
        nsections=nsections,
        cross_section=cross_section,
        cross_section_heater=cross_section_heater,
        via_stack=via_stack,
        via_stack_metal=via_stack_metal,
        via_stack_metal_size=via_stack_metal_size,
        via_stack_size=via_stack_size,
        taper=taper,
        heater_width=heater_width,
        heater_gap=heater_gap,
        via_stack_gap=via_stack_gap,
        width=width,
        xoffset_tip1=xoffset_tip1,
        xoffset_tip2=xoffset_tip2,
    ).copy()

straight_heater_doped_strip

straight_heater_meander

straight_heater_meander

straight_heater_meander(
    length: float = 300.0,
    spacing: float = 2.0,
    cross_section: CrossSectionSpec = "strip",
    heater_width: float = 2.5,
    extension_length: float = 15.0,
    layer_heater: LayerSpec = "HEATER",
    radius: float | None = None,
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_heater_mtop",
    port_orientation1: float | None = None,
    port_orientation2: float | None = None,
    heater_taper_length: float = 10.0,
    straight_widths: Floats | None = None,
    taper_length: float = 10.0,
    n: int | None = 3,
) -> Component

Returns a meander based heater.

based on SungWon Chung, Makoto Nakai, and Hossein Hashemi, Low-power thermo-optic silicon modulator for large-scale photonic integrated systems Opt. Express 27, 13430-13459 (2019) https://www.osapublishing.org/oe/abstract.cfm?URI=oe-27-9-13430

Parameters:

Name Type Description Default
length float

total length of the optical path.

300.0
spacing float

waveguide spacing (center to center).

2.0
cross_section CrossSectionSpec

for waveguide.

'strip'
heater_width float

for heater.

2.5
extension_length float

of input and output optical ports.

15.0
layer_heater LayerSpec

for top heater, if None, it does not add a heater.

'HEATER'
radius float | None

for the meander bends. Defaults to cross_section radius.

None
via_stack ComponentSpec | None

for the heater to via_stack metal.

'via_stack_heater_mtop'
port_orientation1 float | None

in degrees. None adds all orientations.

None
port_orientation2 float | None

in degrees. None adds all orientations.

None
heater_taper_length float

minimizes current concentrations from heater to via_stack.

10.0
straight_widths Floats | None

widths of the straight sections.

None
taper_length float

from the cross_section.

10.0
n int | None

number of straight sections.

3
Source code in gdsfactory/components/waveguides/straight_heater_meander.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
 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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_meander(
    length: float = 300.0,
    spacing: float = 2.0,
    cross_section: CrossSectionSpec = "strip",
    heater_width: float = 2.5,
    extension_length: float = 15.0,
    layer_heater: LayerSpec = "HEATER",
    radius: float | None = None,
    via_stack: ComponentSpec | None = "via_stack_heater_mtop",
    port_orientation1: float | None = None,
    port_orientation2: float | None = None,
    heater_taper_length: float = 10.0,
    straight_widths: Floats | None = None,
    taper_length: float = 10.0,
    n: int | None = 3,
) -> Component:
    """Returns a meander based heater.

    based on SungWon Chung, Makoto Nakai, and Hossein Hashemi,
    Low-power thermo-optic silicon modulator for large-scale photonic integrated systems
    Opt. Express 27, 13430-13459 (2019)
    https://www.osapublishing.org/oe/abstract.cfm?URI=oe-27-9-13430

    Args:
        length: total length of the optical path.
        spacing: waveguide spacing (center to center).
        cross_section: for waveguide.
        heater_width: for heater.
        extension_length: of input and output optical ports.
        layer_heater: for top heater, if None, it does not add a heater.
        radius: for the meander bends. Defaults to cross_section radius.
        via_stack: for the heater to via_stack metal.
        port_orientation1: in degrees. None adds all orientations.
        port_orientation2: in degrees. None adds all orientations.
        heater_taper_length: minimizes current concentrations from heater to via_stack.
        straight_widths: widths of the straight sections.
        taper_length: from the cross_section.
        n: number of straight sections.
    """
    if n and straight_widths:
        raise ValueError("n and straight_widths are mutually exclusive")

    if n is None and straight_widths is None:
        raise ValueError("Either n or straight_widths should be provided")

    straight_widths = straight_widths or []
    rows = n or len(straight_widths)
    c = gf.Component()
    cross_section2 = cross_section

    straight_length = gf.snap.snap_to_grid(length / rows, grid_factor=2)
    ports: dict[str, Port] = {}

    x = gf.get_cross_section(cross_section)
    radius = radius or x.radius
    n = n or len(straight_widths)

    assert radius is not None
    assert n

    if n and not straight_widths and n % 2 == 0:
        raise ValueError(f"n={n} should be odd")

    ##############
    # Straights
    ##############
    total_length = 0.0

    if straight_widths:
        for row, straight_width in enumerate(straight_widths):
            cross_section1 = gf.get_cross_section(cross_section, width=straight_width)

            _straight = gf.c.straight(
                length=straight_length - 2 * taper_length,
                cross_section=cross_section,
                width=straight_width,
            )
            total_length += straight_length

            taper = gf.c.taper_cross_section_linear(
                cross_section1=cross_section1,
                cross_section2=cross_section2,
                length=taper_length,
            )
            straight_with_tapers = gf.c.extend_ports(
                component=_straight, extension=taper
            )
            straight_ref = c << straight_with_tapers
            straight_ref.y = row * spacing
            ports[f"o1_{row + 1}"] = straight_ref.ports["o1"]
            ports[f"o2_{row + 1}"] = straight_ref.ports["o2"]

    else:
        for row in range(n):
            _straight = gf.c.straight(
                length=straight_length,
                cross_section=cross_section,
            )
            total_length += straight_length
            straight_ref = c << _straight
            straight_ref.y = row * spacing
            ports[f"o1_{row + 1}"] = straight_ref.ports["o1"]
            ports[f"o2_{row + 1}"] = straight_ref.ports["o2"]

    ##############
    # loopbacks
    ##############
    for row in range(1, rows, 2):
        extra_length = 3 * (rows - row - 1) / 2 * radius
        extra_straight1 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight1.connect("o1", ports[f"o1_{row + 1}"])
        extra_straight2 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight2.connect("o1", ports[f"o1_{row + 2}"])

        total_length += 2 * extra_length

        route = gf.routing.route_single(
            c,
            extra_straight2.ports["o2"],
            extra_straight1.ports["o2"],
            radius=radius,
            cross_section=cross_section,
        )
        total_length += route.length * c.kcl.dbu
        total_length += 4 * (np.pi / 2 * radius)

        extra_length = 3 * (row - 1) / 2 * radius
        extra_straight1 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight1.connect("o1", ports[f"o2_{row + 1}"])
        extra_straight2 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight2.connect("o1", ports[f"o2_{row}"])
        total_length += 2 * extra_length

        route = gf.routing.route_single(
            c,
            extra_straight2.ports["o2"],
            extra_straight1.ports["o2"],
            radius=radius,
            cross_section=cross_section,
        )
        total_length += route.length * c.kcl.dbu
        total_length += 4 * (np.pi / 2 * radius)

    straight1 = c << gf.c.straight(length=extension_length, cross_section=cross_section)
    straight2 = c << gf.c.straight(length=extension_length, cross_section=cross_section)
    straight1.connect("o2", ports["o1_1"])
    straight2.connect("o1", ports[f"o2_{rows}"])
    total_length += 2 * extension_length

    c.add_port("o1", port=straight1.ports["o1"])
    c.add_port("o2", port=straight2.ports["o2"])

    heater: ComponentReference | None = None
    heater_cross_section: CrossSectionSpec | None = None

    if layer_heater:
        heater_cross_section = partial(
            gf.cross_section.cross_section, width=heater_width, layer=layer_heater
        )

        heater = c << gf.c.straight(
            length=straight_length,
            cross_section=heater_cross_section,
        )
        heater.movey(spacing * (rows // 2))

    if layer_heater and via_stack and heater:
        via = gf.get_component(via_stack)
        dx = via.xsize / 2 + heater_taper_length or 0
        via_stack_west_center = (heater.dbbox().left - dx, 0)
        via_stack_east_center = (heater.dbbox().right + dx, 0)

        via_stack_west = c << via
        via_stack_east = c << via
        via_stack_west.move(via_stack_west_center)
        via_stack_east.move(via_stack_east_center)

        valid_orientations = {p.orientation for p in via.ports}

        if heater_taper_length and heater_cross_section:
            taper = gf.c.taper(
                cross_section=heater_cross_section,
                width1=via.ports["e1"].width,
                width2=heater_width,
                length=heater_taper_length,
            )
            taper1 = c << taper
            taper2 = c << taper

            taper1.connect("o2", heater.ports["o1"])
            taper2.connect("o2", heater.ports["o2"])

            via_stack_west.connect(
                "e3",
                taper1.ports["o1"],
                allow_width_mismatch=True,
                allow_layer_mismatch=True,
                allow_type_mismatch=True,
            )
            via_stack_east.connect(
                "e1",
                taper2.ports["o1"],
                allow_width_mismatch=True,
                allow_layer_mismatch=True,
                allow_type_mismatch=True,
            )

        if port_orientation1 is not None:
            p1 = list(via_stack_west.ports.filter(orientation=port_orientation1))
        else:
            p1 = list(via_stack_west.ports)

        if port_orientation2 is not None:
            p2 = list(via_stack_east.ports.filter(orientation=port_orientation2))
        else:
            p2 = list(via_stack_east.ports)

        if not p1:
            raise ValueError(
                f"No ports for port_orientation1 {port_orientation1} in {valid_orientations}"
            )
        if not p2:
            raise ValueError(
                f"No ports for port_orientation2 {port_orientation2} in {valid_orientations}"
            )

        c.add_ports(p1, prefix="l_")
        c.add_ports(p2, prefix="r_")

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    c.info["length"] = total_length
    c.flatten()
    return c

straight_heater_meander

straight_heater_meander_doped

Straight heater meander doped.

straight_heater_meander_doped

straight_heater_meander_doped(
    length: float = 300.0,
    spacing: float = 2.0,
    cross_section: CrossSectionSpec = "strip",
    heater_width: float = 1.5,
    extension_length: float = 15.0,
    layers_doping: LayerSpecs = ("P", "PP", "PPP"),
    radius: float = 5.0,
    via_stack: ComponentSpec | None = _via_stack,
    port_orientation1: float | None = None,
    port_orientation2: float | None = None,
    straight_widths: Floats = (0.8, 0.9, 0.8),
    taper_length: float = 10,
) -> Component

Returns a meander based heater.

based on SungWon Chung, Makoto Nakai, and Hossein Hashemi, Low-power thermo-optic silicon modulator for large-scale photonic integrated systems Opt. Express 27, 13430-13459 (2019) https://www.osapublishing.org/oe/abstract.cfm?URI=oe-27-9-13430

Parameters:

Name Type Description Default
length float

total length of the optical path.

300.0
spacing float

waveguide spacing (center to center).

2.0
cross_section CrossSectionSpec

for waveguide.

'strip'
heater_width float

for heater.

1.5
extension_length float

of input and output optical ports.

15.0
layers_doping LayerSpecs

doping layers to be used for heater.

('P', 'PP', 'PPP')
radius float

for the meander bends.

5.0
via_stack ComponentSpec | None

for the heater to via_stack metal.

_via_stack
port_orientation1 float | None

in degrees. None adds all orientations.

None
port_orientation2 float | None

in degrees. None adds all orientations.

None
straight_widths Floats

width of the straight sections.

(0.8, 0.9, 0.8)
taper_length float

from the cross_section.

10
Source code in gdsfactory/components/waveguides/straight_heater_meander_doped.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
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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_meander_doped(
    length: float = 300.0,
    spacing: float = 2.0,
    cross_section: CrossSectionSpec = "strip",
    heater_width: float = 1.5,
    extension_length: float = 15.0,
    layers_doping: LayerSpecs = ("P", "PP", "PPP"),
    radius: float = 5.0,
    via_stack: ComponentSpec | None = _via_stack,
    port_orientation1: float | None = None,
    port_orientation2: float | None = None,
    straight_widths: Floats = (0.8, 0.9, 0.8),
    taper_length: float = 10,
) -> Component:
    """Returns a meander based heater.

    based on SungWon Chung, Makoto Nakai, and Hossein Hashemi,
    Low-power thermo-optic silicon modulator for large-scale photonic integrated systems
    Opt. Express 27, 13430-13459 (2019)
    https://www.osapublishing.org/oe/abstract.cfm?URI=oe-27-9-13430

    Args:
        length: total length of the optical path.
        spacing: waveguide spacing (center to center).
        cross_section: for waveguide.
        heater_width: for heater.
        extension_length: of input and output optical ports.
        layers_doping: doping layers to be used for heater.
        radius: for the meander bends.
        via_stack: for the heater to via_stack metal.
        port_orientation1: in degrees. None adds all orientations.
        port_orientation2: in degrees. None adds all orientations.
        straight_widths: width of the straight sections.
        taper_length: from the cross_section.
    """
    from gdsfactory.pdk import get_layer

    rows = len(straight_widths)
    c = gf.Component()
    x = gf.get_cross_section(cross_section)
    layer = get_layer(x.layer)

    temp_component = Component()
    p1 = temp_component.add_port(
        name="p1",
        center=(0, 0),
        orientation=0,
        cross_section=x,
        layer=layer,
        width=x.width,
    )
    p2 = temp_component.add_port(
        name="p2",
        center=(0, spacing),
        orientation=0,
        cross_section=x,
        layer=layer,
        width=x.width,
    )

    dummy = gf.Component()
    route = gf.routing.route_single(
        dummy, p1, p2, radius=radius, cross_section=cross_section
    )
    cross_section2 = cross_section

    straight_length = gf.snap.snap_to_grid2x(
        (length - (rows - 1) * c.kcl.dbu * route.length) / rows,
    )
    ports: dict[str, Port] = {}

    if straight_length - 2 * taper_length <= 0:
        raise ValueError("straight_length - 2 * taper_length <= 0")

    # Straights
    for row, straight_width in enumerate(straight_widths):
        cross_section1 = gf.get_cross_section(cross_section, width=straight_width)
        straight = gf.c.straight(
            length=straight_length - 2 * taper_length, cross_section=cross_section1
        )

        taper = partial(
            gf.c.taper_cross_section_linear,
            cross_section1=cross_section1,
            cross_section2=cross_section2,
            length=taper_length,
        )

        straight_with_tapers = gf.c.extend_ports(straight, extension=taper)
        straight_ref = c << straight_with_tapers
        if row < len(straight_widths) // 2:
            straight_ref.y = row * spacing
        else:
            straight_ref.y = (row + 1) * spacing
        ports[f"o1_{row + 1}"] = straight_ref["o1"]
        ports[f"o2_{row + 1}"] = straight_ref["o2"]

    # Loopbacks
    for row in range(1, rows, 2):
        extra_length = 3 * (rows - row - 1) / 2 * radius
        extra_straight1 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight1.connect("o1", ports[f"o1_{row + 1}"])
        extra_straight2 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight2.connect("o1", ports[f"o1_{row + 2}"])

        gf.routing.route_single(
            c,
            extra_straight2["o2"],
            extra_straight1["o2"],
            radius=radius,
            cross_section=cross_section,
        )

        extra_length = 3 * (row - 1) / 2 * radius
        extra_straight1 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight1.connect("o1", ports[f"o2_{row + 1}"])
        extra_straight2 = c << gf.c.straight(
            length=extra_length, cross_section=cross_section
        )
        extra_straight2.connect("o1", ports[f"o2_{row}"])

        gf.routing.route_single(
            c,
            extra_straight2.ports["o2"],
            extra_straight1.ports["o2"],
            radius=radius,
            cross_section=cross_section,
        )

    straight1 = c << gf.c.straight(length=extension_length, cross_section=cross_section)
    straight2 = c << gf.c.straight(length=extension_length, cross_section=cross_section)
    straight1.connect("o2", ports["o1_1"])
    straight2.connect("o1", ports[f"o2_{rows}"])

    c.add_port("o1", port=straight1.ports["o1"])
    c.add_port("o2", port=straight2.ports["o2"])

    heater: ComponentReference | None = None

    if layers_doping:
        sections: tuple[Section, ...] = ()
        for doping_layer in layers_doping:
            sections += (Section(layer=doping_layer, width=heater_width, offset=0),)
        heater_cross_section = partial(
            gf.cross_section.cross_section,
            width=heater_width,
            layer="WG",
            sections=sections,
            port_names=("e1", "e2"),
            port_types=("electrical", "electrical"),
        )

        heater = c << gf.c.straight(
            length=straight_length,
            cross_section=heater_cross_section,
        )
        heater.movey(spacing * (rows // 2))

    if layers_doping and via_stack and heater is not None:
        via = via_stacke = via_stackw = gf.get_component(via_stack)
        via_stack_west = c << via_stackw
        via_stack_east = c << via_stacke
        via_stack_west.connect(
            "e3", heater["e1"], allow_layer_mismatch=True, allow_width_mismatch=True
        )
        via_stack_east.connect(
            "e1", heater["e2"], allow_layer_mismatch=True, allow_width_mismatch=True
        )

        valid_orientations = {p.orientation for p in via.ports}
        ports1: Iterable[Port] = []
        ports2: Iterable[Port] = []
        if port_orientation1 is None:
            ports1 = via_stack_west.ports
        else:
            ports1 = via_stack_west.ports.filter(orientation=port_orientation1)

        if port_orientation2 is None:
            ports2 = via_stack_east.ports
        else:
            ports2 = via_stack_east.ports.filter(orientation=port_orientation2)

        if not ports1:
            raise ValueError(
                f"No ports for port_orientation1 {port_orientation1} in {valid_orientations}"
            )
        if not ports2:
            raise ValueError(
                f"No ports for port_orientation2 {port_orientation2} in {valid_orientations}"
            )

        c.add_ports(ports1, prefix="l_")
        c.add_ports(ports2, prefix="r_")

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    # delete any straights with zero length
    for inst in list(c.insts):
        if inst.cell.settings.get("length") == 0.0:
            del c.insts[inst]
    c.flatten()
    return c

straight_heater_meander_doped

straight_heater_metal

straight_heater_metal_simple

straight_heater_metal_simple(
    length: float = 320.0,
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component

Returns a thermal phase shifter that has properly fixed electrical connectivity to extract a suitable electrical netlist and models.

dimensions from https://doi.org/10.1364/OE.27.010456.

Parameters:

Name Type Description Default
length float

of the waveguide.

320.0
length_undercut

length of each undercut section.

required
cross_section_heater CrossSectionSpec

for heated sections. heater metal only.

'heater_metal'
cross_section_waveguide_heater CrossSectionSpec

for heated sections.

'strip_heater_metal'
via_stack ComponentSpec | None

via stack.

'via_stack_heater_mtop'
port_orientation1 int | None

left via stack port orientation. None adds all orientations.

None
port_orientation2 int | None

right via stack port orientation. None adds all orientations.

None
heater_taper_length float

minimizes current concentrations from heater to via_stack.

5.0
ohms_per_square float | None

to calculate resistance.

None
Source code in gdsfactory/components/waveguides/straight_heater_metal.py
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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_metal_simple(
    length: float = 320.0,
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    via_stack: ComponentSpec | None = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component:
    """Returns a thermal phase shifter that has properly fixed electrical connectivity to extract a suitable electrical netlist and models.

    dimensions from https://doi.org/10.1364/OE.27.010456.

    Args:
        length: of the waveguide.
        length_undercut: length of each undercut section.
        cross_section_heater: for heated sections. heater metal only.
        cross_section_waveguide_heater: for heated sections.
        via_stack: via stack.
        port_orientation1: left via stack port orientation. None adds all orientations.
        port_orientation2: right via stack port orientation. None adds all orientations.
        heater_taper_length: minimizes current concentrations from heater to via_stack.
        ohms_per_square: to calculate resistance.
    """
    c = Component()
    straight_heater_section = gf.components.straight(
        cross_section=cross_section_waveguide_heater,
        length=length,
    )

    c.add_ref(straight_heater_section)
    x = gf.get_cross_section(cross_section_heater)
    heater_width = x.width
    c.add_ports(straight_heater_section.ports)

    if via_stack:
        via = via_stackw = via_stacke = gf.get_component(via_stack)
        dx = via_stackw.xsize / 2 + heater_taper_length
        via_stack_west_center = (
            straight_heater_section.xmin - dx,
            straight_heater_section.y,
        )
        via_stack_east_center = (
            straight_heater_section.xmax + dx,
            straight_heater_section.y,
        )

        via_stack_west = c << via_stackw
        via_stack_east = c << via_stacke
        via_stack_west.move(via_stack_west_center)
        via_stack_east.move(via_stack_east_center)

        valid_orientations = {p.orientation for p in via.ports}
        p1 = via_stack_west.ports.filter(orientation=port_orientation1)
        p2 = via_stack_east.ports.filter(orientation=port_orientation2)

        if not p1:
            raise ValueError(
                f"No ports for port_orientation1 {port_orientation1} in {valid_orientations}"
            )
        if not p2:
            raise ValueError(
                f"No ports for port_orientation2 {port_orientation2} in {valid_orientations}"
            )

        c.add_ports(p1, prefix="l_")
        c.add_ports(p2, prefix="r_")
        if heater_taper_length:
            taper = gf.components.taper(
                width1=via_stackw.ports["e1"].width,
                width2=heater_width,
                length=heater_taper_length,
                cross_section=cross_section_heater,
                port_names=("e1", "e2"),
                port_types=("electrical", "electrical"),
            )
            taper1 = c << taper
            taper2 = c << taper
            taper1.connect("e1", via_stack_west.ports["e3"], allow_layer_mismatch=True)
            taper2.connect("e1", via_stack_east.ports["e1"], allow_layer_mismatch=True)

    c.info["resistance"] = (
        ohms_per_square * heater_width * length if ohms_per_square else None
    )
    c.info["length"] = length

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    return c

straight_heater_metal_undercut

straight_heater_metal_undercut(
    length: float = 320.0,
    length_undercut_spacing: float = 6.0,
    length_undercut: float = 30.0,
    length_straight: float = 0.1,
    length_straight_input: float = 15.0,
    cross_section: CrossSectionSpec = "strip",
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    cross_section_heater_undercut: CrossSectionSpec = "strip_heater_metal_undercut",
    with_undercut: bool = True,
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component

Returns a thermal phase shifter.

dimensions from https://doi.org/10.1364/OE.27.010456

Parameters:

Name Type Description Default
length float

of the waveguide.

320.0
length_undercut_spacing float

from undercut regions.

6.0
length_undercut float

length of each undercut section.

30.0
length_straight float

length of the straight waveguide.

0.1
length_straight_input float

from input port to where trenches start.

15.0
cross_section CrossSectionSpec

for waveguide ports.

'strip'
cross_section_heater CrossSectionSpec

for heated sections. heater metal only.

'heater_metal'
cross_section_waveguide_heater CrossSectionSpec

for heated sections.

'strip_heater_metal'
cross_section_heater_undercut CrossSectionSpec

for heated sections with undercut.

'strip_heater_metal_undercut'
with_undercut bool

isolation trenches for higher efficiency.

True
via_stack ComponentSpec | None

via stack.

'via_stack_heater_mtop'
port_orientation1 int | None

left via stack port orientation. None adds all orientations.

None
port_orientation2 int | None

right via stack port orientation. None adds all orientations.

None
heater_taper_length float

minimizes current concentrations from heater to via_stack.

5.0
ohms_per_square float | None

to calculate resistance.

None
Source code in gdsfactory/components/waveguides/straight_heater_metal.py
 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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_metal_undercut(
    length: float = 320.0,
    length_undercut_spacing: float = 6.0,
    length_undercut: float = 30.0,
    length_straight: float = 0.1,
    length_straight_input: float = 15.0,
    cross_section: CrossSectionSpec = "strip",
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    cross_section_heater_undercut: CrossSectionSpec = "strip_heater_metal_undercut",
    with_undercut: bool = True,
    via_stack: ComponentSpec | None = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component:
    """Returns a thermal phase shifter.

    dimensions from https://doi.org/10.1364/OE.27.010456

    Args:
        length: of the waveguide.
        length_undercut_spacing: from undercut regions.
        length_undercut: length of each undercut section.
        length_straight: length of the straight waveguide.
        length_straight_input: from input port to where trenches start.
        cross_section: for waveguide ports.
        cross_section_heater: for heated sections. heater metal only.
        cross_section_waveguide_heater: for heated sections.
        cross_section_heater_undercut: for heated sections with undercut.
        with_undercut: isolation trenches for higher efficiency.
        via_stack: via stack.
        port_orientation1: left via stack port orientation. None adds all orientations.
        port_orientation2: right via stack port orientation. None adds all orientations.
        heater_taper_length: minimizes current concentrations from heater to via_stack.
        ohms_per_square: to calculate resistance.
    """
    period = length_undercut + length_undercut_spacing
    n = int((length - 2 * length_straight_input) // period)

    length_straight_input = (length - n * period) / 2

    if n < 1:
        raise ValueError("length is too short")

    if length_straight > length_straight_input:
        raise ValueError("length_straight_ must be smaller than length_straight_input")

    length_straight_input -= length_straight

    s_ports = gf.components.straight(
        cross_section=cross_section,
        length=length_straight,
    )

    s_si = gf.components.straight(
        cross_section=cross_section_waveguide_heater,
        length=length_straight_input,
    )
    cross_section_undercut = (
        cross_section_heater_undercut
        if with_undercut
        else cross_section_waveguide_heater
    )
    s_uc = gf.components.straight(
        cross_section=cross_section_undercut,
        length=length_undercut,
    )
    s_spacing = gf.components.straight(
        cross_section=cross_section_waveguide_heater,
        length=length_undercut_spacing,
    )
    symbol_to_component = {
        "_": (s_ports, "o1", "o2"),
        "-": (s_si, "o1", "o2"),
        "U": (s_uc, "o1", "o2"),
        "H": (s_spacing, "o1", "o2"),
    }

    # Each character in the sequence represents a component
    sequence = "_-" + n * "UH" + "-_"

    # strip out zero-length straights
    for symbol, (component, _p1, _p2) in symbol_to_component.items():
        if component.settings.get("length") == 0:
            sequence = sequence.replace(symbol, "")

    c = component_sequence(sequence=sequence, symbol_to_component=symbol_to_component)
    x = gf.get_cross_section(cross_section_heater)
    heater_width = x.width

    if via_stack:
        via_stack = gf.get_component(via_stack)

        dx = via_stack.xsize / 2 + heater_taper_length
        dx -= length_straight

        via_stack_west = c << via_stack
        via_stack_east = c << via_stack

        via_stack_west.movex(-dx)
        via_stack_east.movex(+dx + length)

        valid_orientations = {p.orientation for p in via_stack.ports}
        p1 = list(via_stack_west.ports.filter(orientation=port_orientation1))
        p2 = list(via_stack_east.ports.filter(orientation=port_orientation2))

        if not p1:
            raise ValueError(
                f"No ports for port_orientation1 {port_orientation1} in {valid_orientations}"
            )
        if not p2:
            raise ValueError(
                f"No ports for port_orientation2 {port_orientation2} in {valid_orientations}"
            )

        c.add_ports(p1, prefix="l_")
        c.add_ports(p2, prefix="r_")

        if heater_taper_length:
            taper = gf.components.taper(
                width1=via_stack_west["e3"].width,
                width2=heater_width,
                length=heater_taper_length,
                cross_section=cross_section_heater,
                port_names=("e1", "e2"),
                port_types=("electrical", "electrical"),
            )
            taper1 = c << taper
            taper2 = c << taper
            taper1.connect(
                "e1",
                via_stack_west.ports["e3"],
                allow_layer_mismatch=True,
            )
            taper2.connect(
                "e1",
                via_stack_east.ports["e1"],
                allow_layer_mismatch=True,
            )

    c.info["resistance"] = (
        ohms_per_square * heater_width * length if ohms_per_square else 0
    )
    c.info["length"] = length

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    c.flatten()
    return c

straight_heater_metal

straight_heater_metal_90_90 module-attribute

straight_heater_metal_90_90 = partial(
    straight_heater_metal,
    port_orientation1=90,
    port_orientation2=90,
)

straight_heater_metal_90_90

straight_heater_metal_simple

straight_heater_metal_simple(
    length: float = 320.0,
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component

Returns a thermal phase shifter that has properly fixed electrical connectivity to extract a suitable electrical netlist and models.

dimensions from https://doi.org/10.1364/OE.27.010456.

Parameters:

Name Type Description Default
length float

of the waveguide.

320.0
length_undercut

length of each undercut section.

required
cross_section_heater CrossSectionSpec

for heated sections. heater metal only.

'heater_metal'
cross_section_waveguide_heater CrossSectionSpec

for heated sections.

'strip_heater_metal'
via_stack ComponentSpec | None

via stack.

'via_stack_heater_mtop'
port_orientation1 int | None

left via stack port orientation. None adds all orientations.

None
port_orientation2 int | None

right via stack port orientation. None adds all orientations.

None
heater_taper_length float

minimizes current concentrations from heater to via_stack.

5.0
ohms_per_square float | None

to calculate resistance.

None
Source code in gdsfactory/components/waveguides/straight_heater_metal.py
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
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_metal_simple(
    length: float = 320.0,
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    via_stack: ComponentSpec | None = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component:
    """Returns a thermal phase shifter that has properly fixed electrical connectivity to extract a suitable electrical netlist and models.

    dimensions from https://doi.org/10.1364/OE.27.010456.

    Args:
        length: of the waveguide.
        length_undercut: length of each undercut section.
        cross_section_heater: for heated sections. heater metal only.
        cross_section_waveguide_heater: for heated sections.
        via_stack: via stack.
        port_orientation1: left via stack port orientation. None adds all orientations.
        port_orientation2: right via stack port orientation. None adds all orientations.
        heater_taper_length: minimizes current concentrations from heater to via_stack.
        ohms_per_square: to calculate resistance.
    """
    c = Component()
    straight_heater_section = gf.components.straight(
        cross_section=cross_section_waveguide_heater,
        length=length,
    )

    c.add_ref(straight_heater_section)
    x = gf.get_cross_section(cross_section_heater)
    heater_width = x.width
    c.add_ports(straight_heater_section.ports)

    if via_stack:
        via = via_stackw = via_stacke = gf.get_component(via_stack)
        dx = via_stackw.xsize / 2 + heater_taper_length
        via_stack_west_center = (
            straight_heater_section.xmin - dx,
            straight_heater_section.y,
        )
        via_stack_east_center = (
            straight_heater_section.xmax + dx,
            straight_heater_section.y,
        )

        via_stack_west = c << via_stackw
        via_stack_east = c << via_stacke
        via_stack_west.move(via_stack_west_center)
        via_stack_east.move(via_stack_east_center)

        valid_orientations = {p.orientation for p in via.ports}
        p1 = via_stack_west.ports.filter(orientation=port_orientation1)
        p2 = via_stack_east.ports.filter(orientation=port_orientation2)

        if not p1:
            raise ValueError(
                f"No ports for port_orientation1 {port_orientation1} in {valid_orientations}"
            )
        if not p2:
            raise ValueError(
                f"No ports for port_orientation2 {port_orientation2} in {valid_orientations}"
            )

        c.add_ports(p1, prefix="l_")
        c.add_ports(p2, prefix="r_")
        if heater_taper_length:
            taper = gf.components.taper(
                width1=via_stackw.ports["e1"].width,
                width2=heater_width,
                length=heater_taper_length,
                cross_section=cross_section_heater,
                port_names=("e1", "e2"),
                port_types=("electrical", "electrical"),
            )
            taper1 = c << taper
            taper2 = c << taper
            taper1.connect("e1", via_stack_west.ports["e3"], allow_layer_mismatch=True)
            taper2.connect("e1", via_stack_east.ports["e1"], allow_layer_mismatch=True)

    c.info["resistance"] = (
        ohms_per_square * heater_width * length if ohms_per_square else None
    )
    c.info["length"] = length

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    return c

straight_heater_metal_simple

straight_heater_metal_undercut

straight_heater_metal_undercut(
    length: float = 320.0,
    length_undercut_spacing: float = 6.0,
    length_undercut: float = 30.0,
    length_straight: float = 0.1,
    length_straight_input: float = 15.0,
    cross_section: CrossSectionSpec = "strip",
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    cross_section_heater_undercut: CrossSectionSpec = "strip_heater_metal_undercut",
    with_undercut: bool = True,
    via_stack: (
        ComponentSpec | None
    ) = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component

Returns a thermal phase shifter.

dimensions from https://doi.org/10.1364/OE.27.010456

Parameters:

Name Type Description Default
length float

of the waveguide.

320.0
length_undercut_spacing float

from undercut regions.

6.0
length_undercut float

length of each undercut section.

30.0
length_straight float

length of the straight waveguide.

0.1
length_straight_input float

from input port to where trenches start.

15.0
cross_section CrossSectionSpec

for waveguide ports.

'strip'
cross_section_heater CrossSectionSpec

for heated sections. heater metal only.

'heater_metal'
cross_section_waveguide_heater CrossSectionSpec

for heated sections.

'strip_heater_metal'
cross_section_heater_undercut CrossSectionSpec

for heated sections with undercut.

'strip_heater_metal_undercut'
with_undercut bool

isolation trenches for higher efficiency.

True
via_stack ComponentSpec | None

via stack.

'via_stack_heater_mtop'
port_orientation1 int | None

left via stack port orientation. None adds all orientations.

None
port_orientation2 int | None

right via stack port orientation. None adds all orientations.

None
heater_taper_length float

minimizes current concentrations from heater to via_stack.

5.0
ohms_per_square float | None

to calculate resistance.

None
Source code in gdsfactory/components/waveguides/straight_heater_metal.py
 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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
@gf.cell_with_module_name(schematic_function=straight_schematic, tags=["waveguides"])
def straight_heater_metal_undercut(
    length: float = 320.0,
    length_undercut_spacing: float = 6.0,
    length_undercut: float = 30.0,
    length_straight: float = 0.1,
    length_straight_input: float = 15.0,
    cross_section: CrossSectionSpec = "strip",
    cross_section_heater: CrossSectionSpec = "heater_metal",
    cross_section_waveguide_heater: CrossSectionSpec = "strip_heater_metal",
    cross_section_heater_undercut: CrossSectionSpec = "strip_heater_metal_undercut",
    with_undercut: bool = True,
    via_stack: ComponentSpec | None = "via_stack_heater_mtop",
    port_orientation1: int | None = None,
    port_orientation2: int | None = None,
    heater_taper_length: float = 5.0,
    ohms_per_square: float | None = None,
) -> Component:
    """Returns a thermal phase shifter.

    dimensions from https://doi.org/10.1364/OE.27.010456

    Args:
        length: of the waveguide.
        length_undercut_spacing: from undercut regions.
        length_undercut: length of each undercut section.
        length_straight: length of the straight waveguide.
        length_straight_input: from input port to where trenches start.
        cross_section: for waveguide ports.
        cross_section_heater: for heated sections. heater metal only.
        cross_section_waveguide_heater: for heated sections.
        cross_section_heater_undercut: for heated sections with undercut.
        with_undercut: isolation trenches for higher efficiency.
        via_stack: via stack.
        port_orientation1: left via stack port orientation. None adds all orientations.
        port_orientation2: right via stack port orientation. None adds all orientations.
        heater_taper_length: minimizes current concentrations from heater to via_stack.
        ohms_per_square: to calculate resistance.
    """
    period = length_undercut + length_undercut_spacing
    n = int((length - 2 * length_straight_input) // period)

    length_straight_input = (length - n * period) / 2

    if n < 1:
        raise ValueError("length is too short")

    if length_straight > length_straight_input:
        raise ValueError("length_straight_ must be smaller than length_straight_input")

    length_straight_input -= length_straight

    s_ports = gf.components.straight(
        cross_section=cross_section,
        length=length_straight,
    )

    s_si = gf.components.straight(
        cross_section=cross_section_waveguide_heater,
        length=length_straight_input,
    )
    cross_section_undercut = (
        cross_section_heater_undercut
        if with_undercut
        else cross_section_waveguide_heater
    )
    s_uc = gf.components.straight(
        cross_section=cross_section_undercut,
        length=length_undercut,
    )
    s_spacing = gf.components.straight(
        cross_section=cross_section_waveguide_heater,
        length=length_undercut_spacing,
    )
    symbol_to_component = {
        "_": (s_ports, "o1", "o2"),
        "-": (s_si, "o1", "o2"),
        "U": (s_uc, "o1", "o2"),
        "H": (s_spacing, "o1", "o2"),
    }

    # Each character in the sequence represents a component
    sequence = "_-" + n * "UH" + "-_"

    # strip out zero-length straights
    for symbol, (component, _p1, _p2) in symbol_to_component.items():
        if component.settings.get("length") == 0:
            sequence = sequence.replace(symbol, "")

    c = component_sequence(sequence=sequence, symbol_to_component=symbol_to_component)
    x = gf.get_cross_section(cross_section_heater)
    heater_width = x.width

    if via_stack:
        via_stack = gf.get_component(via_stack)

        dx = via_stack.xsize / 2 + heater_taper_length
        dx -= length_straight

        via_stack_west = c << via_stack
        via_stack_east = c << via_stack

        via_stack_west.movex(-dx)
        via_stack_east.movex(+dx + length)

        valid_orientations = {p.orientation for p in via_stack.ports}
        p1 = list(via_stack_west.ports.filter(orientation=port_orientation1))
        p2 = list(via_stack_east.ports.filter(orientation=port_orientation2))

        if not p1:
            raise ValueError(
                f"No ports for port_orientation1 {port_orientation1} in {valid_orientations}"
            )
        if not p2:
            raise ValueError(
                f"No ports for port_orientation2 {port_orientation2} in {valid_orientations}"
            )

        c.add_ports(p1, prefix="l_")
        c.add_ports(p2, prefix="r_")

        if heater_taper_length:
            taper = gf.components.taper(
                width1=via_stack_west["e3"].width,
                width2=heater_width,
                length=heater_taper_length,
                cross_section=cross_section_heater,
                port_names=("e1", "e2"),
                port_types=("electrical", "electrical"),
            )
            taper1 = c << taper
            taper2 = c << taper
            taper1.connect(
                "e1",
                via_stack_west.ports["e3"],
                allow_layer_mismatch=True,
            )
            taper2.connect(
                "e1",
                via_stack_east.ports["e1"],
                allow_layer_mismatch=True,
            )

    c.info["resistance"] = (
        ohms_per_square * heater_width * length if ohms_per_square else 0
    )
    c.info["length"] = length

    l_ports = [p for p in c.ports if p.name and p.name.startswith("l_")]
    r_ports = [p for p in c.ports if p.name and p.name.startswith("r_")]
    if l_ports:
        c.create_pin(ports=l_ports, name="l")
    if r_ports:
        c.create_pin(ports=r_ports, name="r")

    c.flatten()
    return c

straight_heater_metal_undercut

straight_heater_metal_undercut_90_90 module-attribute

straight_heater_metal_undercut_90_90 = partial(
    straight_heater_metal_undercut,
    port_orientation1=90,
    port_orientation2=90,
)

straight_heater_metal_undercut_90_90

straight_piecewise

straight_piecewise

straight_piecewise(
    x: Sequence[float] | Path,
    widths: Sequence[float],
    layer: LayerSpec,
    sections: Sequence[Section] | None = None,
    port_names: tuple[str | None, str | None] = (
        "o1",
        "o2",
    ),
    name: str = "core",
    **kwargs: Any
) -> Component

Create a component with a piecewise-defined straight waveguide.

Parameters:

Name Type Description Default
x Sequence[float] | Path

X coordinates or a custom Path object.

required
widths Sequence[float]

Waveguide widths at each corresponding x.

required
layer LayerSpec

Layer to extrude.

required
sections Sequence[Section] | None

Additional cross-section sections to extrude.

None
port_names tuple[str | None, str | None]

Port names for the waveguide.

('o1', 'o2')
name str

Name for the core (main) Section.

'core'
**kwargs Any

Additional keyword arguments for the Section.

{}
Source code in gdsfactory/components/waveguides/straight_piecewise.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
@gf.cell_with_module_name(tags=["waveguides"])
def straight_piecewise(
    x: Sequence[float] | Path,
    widths: Sequence[float],
    layer: LayerSpec,
    sections: Sequence[Section] | None = None,
    port_names: tuple[str | None, str | None] = ("o1", "o2"),
    name: str = "core",
    **kwargs: Any,
) -> Component:
    """Create a component with a piecewise-defined straight waveguide.

    Args:
        x: X coordinates or a custom Path object.
        widths: Waveguide widths at each corresponding x.
        layer: Layer to extrude.
        sections: Additional cross-section sections to extrude.
        port_names: Port names for the waveguide.
        name: Name for the core (main) Section.
        **kwargs: Additional keyword arguments for the Section.
    """
    if isinstance(x, Sequence) and len(x) != len(widths):
        raise ValueError("x and widths must have the same length.")

    def width_function(_: float) -> npt.NDArray[np.float64]:
        return np.array(widths)

    if isinstance(x, gf.Path):
        p = x
    else:
        p = gf.Path()
        p.points = np.array([(xi, 0.0) for xi in x])

    section_list = list(sections or [])
    section_list.append(
        Section(
            name=name,
            width=0,
            width_function=width_function,
            offset=0,
            layer=layer,
            port_names=port_names,
            **kwargs,
        )
    )
    cross_section = gf.CrossSection(sections=tuple(section_list))

    return gf.path.extrude(p, cross_section=cross_section)
import gdsfactory as gf

gf.gpdk.PDK.activate()

c = gf.components.straight_piecewise(port_names=('o1', 'o2'), name='core').copy()
c.draw_ports()
c.plot()

straight_pin

Straight Doped PIN waveguide.

straight_pin

straight_pin(
    length: float = 500.0,
    cross_section: CrossSectionSpec = pin,
    via_stack: ComponentSpec = "via_stack_slab_m3",
    via_stack_width: float = 10.0,
    via_stack_spacing: float = 2,
    taper: ComponentSpec | None = "taper_strip_to_ridge",
) -> Component

Returns rib waveguide with doping and via_stacks used for PN and PIN modulators.

For PIN: https://doi.org/10.1364/OE.26.029983

500um length for PI phase shift https://ieeexplore.ieee.org/document/8268112

to go beyond 2PI, you will need at least 1mm https://ieeexplore.ieee.org/document/8853396/

For PN: Typical lengths in practice are 2-5mm depending on doping,engineering and application: https://opg.optica.org/oe/fulltext.cfm?uri=oe-21-25-30350&id=275107 https://opg.optica.org/oe/fulltext.cfm?uri=oe-20-11-12014&id=233271

Parameters:

Name Type Description Default
length float

of the waveguide.

500.0
cross_section CrossSectionSpec

for the waveguide.

pin
via_stack ComponentSpec

for the via_stacks.

'via_stack_slab_m3'
via_stack_width float

width of the via_stack.

10.0
via_stack_spacing float

spacing between via_stacks.

2
taper ComponentSpec | None

optional taper.

'taper_strip_to_ridge'
Source code in gdsfactory/components/waveguides/straight_pin.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
@gf.cell_with_module_name(schematic_function=modulator_schematic, tags=["waveguides"])
def straight_pin(
    length: float = 500.0,
    cross_section: CrossSectionSpec = pin,
    via_stack: ComponentSpec = "via_stack_slab_m3",
    via_stack_width: float = 10.0,
    via_stack_spacing: float = 2,
    taper: ComponentSpec | None = "taper_strip_to_ridge",
) -> Component:
    """Returns rib waveguide with doping and via_stacks used for PN and PIN modulators.

    For PIN:
    https://doi.org/10.1364/OE.26.029983

    500um length for PI phase shift
    https://ieeexplore.ieee.org/document/8268112

    to go beyond 2PI, you will need at least 1mm
    https://ieeexplore.ieee.org/document/8853396/

    For PN:
    Typical lengths in practice are 2-5mm depending on doping,engineering and application:
    https://opg.optica.org/oe/fulltext.cfm?uri=oe-21-25-30350&id=275107
    https://opg.optica.org/oe/fulltext.cfm?uri=oe-20-11-12014&id=233271

    Args:
        length: of the waveguide.
        cross_section: for the waveguide.
        via_stack: for the via_stacks.
        via_stack_width: width of the via_stack.
        via_stack_spacing: spacing between via_stacks.
        taper: optional taper.
    """
    c = Component()
    if taper:
        _taper = gf.get_component(taper)
        length -= 2 * _taper.xsize

    wg = c << gf.components.straight(
        cross_section=cross_section,
        length=length,
    )

    if taper:
        t1 = c << _taper
        t2 = c << _taper
        t1.connect("o2", wg.ports["o1"])
        t2.connect("o2", wg.ports["o2"])
        c.add_port("o1", port=t1.ports["o1"])
        c.add_port("o2", port=t2.ports["o1"])

    else:
        c.add_ports(wg.ports)

    via_stack_length = length
    _via_stack = gf.get_component(via_stack, size=(via_stack_length, via_stack_width))
    via_stack_top = c << _via_stack
    via_stack_bot = c << _via_stack
    via_stack_bot.xmin = wg.xmin
    via_stack_top.xmin = wg.xmin

    via_stack_top.ymin = +via_stack_spacing / 2
    via_stack_bot.ymax = -via_stack_spacing / 2

    c.add_ports(via_stack_bot.ports, prefix="bot_")
    c.add_ports(via_stack_top.ports, prefix="top_")

    top_ports = [p for p in c.ports if p.name and p.name.startswith("top_")]
    bot_ports = [p for p in c.ports if p.name and p.name.startswith("bot_")]
    if top_ports:
        c.create_pin(ports=top_ports, name="top")
    if bot_ports:
        c.create_pin(ports=bot_ports, name="bot")

    return c

straight_pin

straight_pin_slot

Straight Doped PIN waveguide.

straight_pin_slot

straight_pin_slot(
    length: float = 500.0,
    cross_section: CrossSectionSpec = "pin",
    via_stack: ComponentSpec | None = "via_stack_m1_mtop",
    via_stack_width: float = 10.0,
    via_stack_slab: (
        ComponentSpec | None
    ) = "via_stack_slab_m1_horizontal",
    via_stack_slab_top: ComponentSpec | None = None,
    via_stack_slab_bot: ComponentSpec | None = None,
    via_stack_slab_width: float | None = None,
    via_stack_spacing: float = 3.0,
    via_stack_slab_spacing: float = 2.0,
    taper: ComponentSpec | None = "taper_strip_to_ridge",
    width: float | None = None,
) -> Component

Returns a PIN straight waveguide with slotted via.

https://doi.org/10.1364/OE.26.029983

500um length for PI phase shift https://ieeexplore.ieee.org/document/8268112

to go beyond 2PI, you will need at least 1mm https://ieeexplore.ieee.org/document/8853396/

Parameters:

Name Type Description Default
length float

of the waveguide.

500.0
cross_section CrossSectionSpec

for the waveguide.

'pin'
via_stack ComponentSpec | None

for via_stacking the metal.

'via_stack_m1_mtop'
via_stack_width float

in um.

10.0
via_stack_slab ComponentSpec | None

function for the component via_stacking the slab.

'via_stack_slab_m1_horizontal'
via_stack_slab_top ComponentSpec | None

Optional, defaults to via_stack_slab.

None
via_stack_slab_bot ComponentSpec | None

Optional, defaults to via_stack_slab.

None
via_stack_slab_width float | None

defaults to via_stack_width.

None
via_stack_spacing float

spacing between via_stacks.

3.0
via_stack_slab_spacing float

spacing between via_stacks slabs.

2.0
taper ComponentSpec | None

optional taper.

'taper_strip_to_ridge'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/waveguides/straight_pin_slot.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
 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
@gf.cell_with_module_name(schematic_function=modulator_schematic, tags=["waveguides"])
def straight_pin_slot(
    length: float = 500.0,
    cross_section: CrossSectionSpec = "pin",
    via_stack: ComponentSpec | None = "via_stack_m1_mtop",
    via_stack_width: float = 10.0,
    via_stack_slab: ComponentSpec | None = "via_stack_slab_m1_horizontal",
    via_stack_slab_top: ComponentSpec | None = None,
    via_stack_slab_bot: ComponentSpec | None = None,
    via_stack_slab_width: float | None = None,
    via_stack_spacing: float = 3.0,
    via_stack_slab_spacing: float = 2.0,
    taper: ComponentSpec | None = "taper_strip_to_ridge",
    width: float | None = None,
) -> Component:
    """Returns a PIN straight waveguide with slotted via.

    https://doi.org/10.1364/OE.26.029983

    500um length for PI phase shift
    https://ieeexplore.ieee.org/document/8268112

    to go beyond 2PI, you will need at least 1mm
    https://ieeexplore.ieee.org/document/8853396/

    Args:
        length: of the waveguide.
        cross_section: for the waveguide.
        via_stack: for via_stacking the metal.
        via_stack_width: in um.
        via_stack_slab: function for the component via_stacking the slab.
        via_stack_slab_top: Optional, defaults to via_stack_slab.
        via_stack_slab_bot: Optional, defaults to via_stack_slab.
        via_stack_slab_width: defaults to via_stack_width.
        via_stack_spacing: spacing between via_stacks.
        via_stack_slab_spacing: spacing between via_stacks slabs.
        taper: optional taper.
        width: width of the waveguide. If None, it will use the width of the cross_section.
    """
    c = Component()
    taper_component: Component | None = None
    if taper:
        taper_component = gf.get_component(taper)
        length -= 2 * taper_component.xsize

    wg = c << gf.components.straight(
        cross_section=cross_section, length=length, width=width
    )

    via_stack_slab_width = via_stack_slab_width or via_stack_width

    if taper_component:
        t1 = c << taper_component
        t2 = c << taper_component
        t1.connect("o2", wg.ports["o1"])
        t2.connect("o2", wg.ports["o2"])
        c.add_port("o1", port=t1.ports["o1"])
        c.add_port("o2", port=t2.ports["o1"])

    else:
        c.add_ports(wg.ports)

    via_stack_length = length

    if via_stack:
        via_stack_top = c << gf.get_component(
            via_stack,
            size=(via_stack_length, via_stack_width),
        )
        via_stack_bot = c << gf.get_component(
            via_stack,
            size=(via_stack_length, via_stack_width),
        )

        via_stack_bot.x = wg.x
        via_stack_top.x = wg.x

        via_stack_top.ymin = +via_stack_spacing / 2
        via_stack_bot.ymax = -via_stack_spacing / 2
        c.add_ports(via_stack_bot.ports, prefix="bot_")
        c.add_ports(via_stack_top.ports, prefix="top_")

    via_stack_slab_top = via_stack_slab_top or via_stack_slab
    via_stack_slab_bot = via_stack_slab_bot or via_stack_slab

    if via_stack_slab_top:
        slot_top = c << gf.get_component(
            via_stack_slab_top,
            size=(via_stack_length, via_stack_slab_width),
        )
        slot_top.x = wg.x
        slot_top.ymin = +via_stack_slab_spacing / 2

    if via_stack_slab_bot:
        slot_bot = c << gf.get_component(
            via_stack_slab_bot,
            size=(via_stack_length, via_stack_slab_width),
        )
        slot_bot.x = wg.x
        slot_bot.ymax = -via_stack_slab_spacing / 2

    top_ports = [p for p in c.ports if p.name and p.name.startswith("top_")]
    bot_ports = [p for p in c.ports if p.name and p.name.startswith("bot_")]
    if top_ports:
        c.create_pin(ports=top_ports, name="top")
    if bot_ports:
        c.create_pin(ports=bot_ports, name="bot")

    return c

straight_pin_slot

straight_pn module-attribute

straight_pn = partial(
    straight_pin, cross_section="pn", length=2000
)

straight_pn

straight_pn_slot module-attribute

straight_pn_slot = partial(
    straight_pin_slot, cross_section="pn"
)

straight_pn_slot

wire_corner

wire_corner(
    cross_section: CrossSectionSpec = "metal_routing",
    port_names: PortNames = port_names_electrical,
    port_types: PortTypes = port_types_electrical,
    width: float | None = None,
    radius: float | None = None,
) -> Component

Returns 45 degrees electrical corner wire.

Parameters:

Name Type Description Default
cross_section CrossSectionSpec

spec.

'metal_routing'
port_names PortNames

port names.

port_names_electrical
port_types PortTypes

port types.

port_types_electrical
width float | None

optional width. Defaults to cross_section width.

None
radius float | None

ignored.

None
Source code in gdsfactory/components/waveguides/wire.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
@gf.cell_with_module_name(tags=["waveguides"])
def wire_corner(
    cross_section: CrossSectionSpec = "metal_routing",
    port_names: PortNames = port_names_electrical,
    port_types: PortTypes = port_types_electrical,
    width: float | None = None,
    radius: float | None = None,
) -> Component:
    """Returns 45 degrees electrical corner wire.

    Args:
        cross_section: spec.
        port_names: port names.
        port_types: port types.
        width: optional width. Defaults to cross_section width.
        radius: ignored.
    """
    if width:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)

    layer = x.layer
    assert layer is not None
    width = x.width

    c = Component()
    a = width / 2
    xpts = [-a, a, a, -a]
    ypts = [-a, -a, a, a]
    c.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)
    c.add_port(
        name=port_names[0],
        center=(-a, 0),
        width=width,
        orientation=180,
        layer=layer,
        port_type=port_types[0],
    )
    c.add_port(
        name=port_names[1],
        center=(0, a),
        width=width,
        orientation=90,
        layer=layer,
        port_type=port_types[1],
    )
    c.info["length"] = width
    c.info["dy"] = width
    x.add_bbox(c)
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=port.name)
    return c

wire_corner

wire_corner45

wire_corner45(
    cross_section: CrossSectionSpec = "metal_routing",
    radius: float = 10,
    width: float | None = None,
    layer: LayerSpec | None = None,
    with_corner90_ports: bool = True,
) -> Component

Returns 90 degrees electrical corner wire.

Parameters:

Name Type Description Default
cross_section CrossSectionSpec

spec.

'metal_routing'
radius float

in um.

10
width float | None

optional width.

None
layer LayerSpec | None

optional layer.

None
with_corner90_ports bool

if True adds ports at 90 degrees.

True
Source code in gdsfactory/components/waveguides/wire.py
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
@gf.cell_with_module_name(tags=["waveguides"])
def wire_corner45(
    cross_section: CrossSectionSpec = "metal_routing",
    radius: float = 10,
    width: float | None = None,
    layer: LayerSpec | None = None,
    with_corner90_ports: bool = True,
) -> Component:
    """Returns 90 degrees electrical corner wire.

    Args:
        cross_section: spec.
        radius: in um.
        width: optional width.
        layer: optional layer.
        with_corner90_ports: if True adds ports at 90 degrees.
    """
    if width:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)
    layer = layer or x.layer
    assert layer is not None
    width = width or x.width
    radius = radius or width

    c = Component()
    a = width / 2
    xpts = [0, radius + a, radius + a, -np.sqrt(2) * width]
    ypts = [-a, radius, radius + np.sqrt(2) * width, -a]
    c.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)

    if with_corner90_ports:
        c.add_port(
            name="e1",
            center=(0, 0),
            width=width,
            orientation=180,
            layer=layer,
            port_type="electrical",
        )
        c.add_port(
            name="e2",
            center=(radius, radius),
            width=width,
            orientation=90,
            layer=layer,
            port_type="electrical",
        )

    else:
        w = float(np.round(width * np.sqrt(2), 3))

        c.add_port(
            name="e1",
            center=(-w / 2, -a),
            width=w,
            orientation=270,
            layer=layer,
            port_type="electrical",
        )
        c.add_port(
            name="e2",
            center=(radius + a, radius + w / 2),
            width=w,
            orientation=0,
            layer=layer,
            port_type="electrical",
        )
    c.info["length"] = float(np.sqrt(2) * radius)
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=port.name)
    return c

wire_corner45

wire_corner45_straight

wire_corner45_straight(
    width: float | None = None,
    radius: float | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
) -> gf.Component

Returns 45 degrees wire straight ends.

Parameters:

Name Type Description Default
width float | None

of the wire.

None
radius float | None

of the corner. Defaults to width.

None
cross_section CrossSectionSpec

metal_routing.

'metal_routing'
Source code in gdsfactory/components/waveguides/wire.py
 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
@gf.cell(tags=["waveguides"])
def wire_corner45_straight(
    width: float | None = None,
    radius: float | None = None,
    cross_section: CrossSectionSpec = "metal_routing",
) -> gf.Component:
    """Returns 45 degrees wire straight ends.

    Args:
        width: of the wire.
        radius: of the corner. Defaults to width.
        cross_section: metal_routing.
    """
    c = gf.Component()
    xs = gf.get_cross_section(cross_section)
    radius = radius or xs.radius or width

    if radius is None:
        raise ValueError("Either radius or width must be specified")

    p = gf.Path(
        [
            (0.0, 0.0),
            (radius / 2.0, 0.0),
            (radius, radius / 2.0),
            (radius, radius),
        ]
    )

    if width:
        xs = gf.get_cross_section(cross_section, width=width)
    else:
        xs = gf.get_cross_section(cross_section)
    c = p.extrude(cross_section=xs)
    for port in c.ports:
        if port.port_type == "electrical":
            c.create_pin(ports=[port], name=port.name)
    return c

wire_corner45_straight

wire_corner_sections

wire_corner_sections(
    cross_section: CrossSectionSpec = "metal_routing",
    port_type: str = "electrical",
    **kwargs: Any
) -> Component

Returns 90 degrees electrical corner wire, where all cross_section sections properly represented.

Works well with symmetric cross_sections, not quite ready for asymmetric.

Parameters:

Name Type Description Default
cross_section CrossSectionSpec

spec.

'metal_routing'
port_type str

"electrical" or "optical".

'electrical'
kwargs Any

cross_section settings, ignored (such as radius, width, layer).

{}
Source code in gdsfactory/components/waveguides/wire.py
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
@gf.cell_with_module_name(tags=["waveguides"])
def wire_corner_sections(
    cross_section: CrossSectionSpec = "metal_routing",
    port_type: str = "electrical",
    **kwargs: Any,
) -> Component:
    """Returns 90 degrees electrical corner wire, where all cross_section sections properly represented.

    Works well with symmetric cross_sections, not quite ready for asymmetric.

    Args:
        cross_section: spec.
        port_type: "electrical" or "optical".
        kwargs: cross_section settings, ignored (such as radius, width, layer).
    """
    x = gf.get_cross_section(cross_section)

    xmin, ymax = x.get_xmin_xmax()

    main_section = x.sections[0]

    all_sections = [main_section]
    all_sections.extend(x.sections)

    c = Component()

    for section in all_sections:
        layer = section.layer
        width = section.width
        offset = section.offset
        b = width / 2

        xpts = [xmin, offset - b, offset - b, offset + b, offset + b, xmin]
        ypts = [
            -offset + b,
            -offset + b,
            ymax,
            ymax,
            -offset - b,
            -offset - b,
        ]

        assert layer is not None

        c.add_polygon(list(zip(xpts, ypts, strict=False)), layer=layer)

    c.add_port(
        name="e1",
        center=(xmin, -(xmin + ymax) / 2),
        orientation=180,
        cross_section=x,
        layer=x.layer,
        port_type=port_type,
    )
    c.add_port(
        name="e2",
        center=((xmin + ymax) / 2, ymax),
        orientation=90,
        cross_section=x,
        layer=x.layer,
        port_type=port_type,
    )
    c.info["length"] = ymax - xmin
    c.info["dy"] = ymax - xmin
    x.add_bbox(c)
    if port_type == "electrical":
        for port in c.ports:
            c.create_pin(ports=[port], name=port.name)
    return c

wire_corner_sections

wire_straight

wire_straight(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "metal_routing",
    width: float | None = None,
) -> Component

Returns a Straight waveguide.

Parameters:

Name Type Description Default
length float

straight length (um).

10.0
npoints int

number of points.

2
cross_section CrossSectionSpec

specification (CrossSection, string or dict).

'metal_routing'
width float | None

width of the waveguide. If None, it will use the width of the cross_section.

None
Source code in gdsfactory/components/waveguides/straight.py
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
@gf.cell_with_module_name(schematic_function=wire_schematic, tags=["waveguides"])
def wire_straight(
    length: float = 10.0,
    npoints: int = 2,
    cross_section: CrossSectionSpec = "metal_routing",
    width: float | None = None,
) -> Component:
    """Returns a Straight waveguide.

    Args:
        length: straight length (um).
        npoints: number of points.
        cross_section: specification (CrossSection, string or dict).
        width: width of the waveguide. If None, it will use the width of the cross_section.

        o1  ──────────────── o2
                length
    """
    if width is not None:
        x = gf.get_cross_section(cross_section, width=width)
    else:
        x = gf.get_cross_section(cross_section)
    p = gf.path.straight(length=length, npoints=npoints)
    c = p.extrude(x)
    x.add_bbox(c)

    c.info["length"] = length
    c.info["width"] = x.width if len(x.sections) == 0 else x.sections[0].width
    c.add_route_info(cross_section=x, length=length)
    return c

wire_straight