Examples

Waveguides and Bends

Rib and strip waveguides, a rib-to-strip converter, and circular, Euler and S-bends.

Compiling…
Waveguides from laylight/waveguides.lyl, compiled in your browserOpen in playground

Silicon photonic circuits use two kinds of waveguides. A strip waveguide is etched through the full 220 nm silicon layer: it confines light strongly and can bend tightly, but its rough sidewalls scatter more. A rib waveguide is etched only partway, leaving a thin slab on either side: it loses less light in straight sections but needs large bends. The hybrid waveguide of Bogaerts and Selvaraja combines them: straight sections are rib waveguides, and before every bend a converter turns the rib into a strip waveguide.

The top half of the example is such a hybrid bend. A 700 nm rib with a 4.7 µm slab runs into a 15 µm rib-to-strip converter, a double linear taper in which the rib narrows to the 460 nm strip width while the slab narrows away. A compact strip bend turns by 90°, and a second converter, placed the other way round, returns to a rib waveguide. The paper rounds the bend with spline sections of 20° at either end of a circular arc of 5 µm radius; the example models them as a partial Euler bend (curvature growing linearly along the bend), with the same minimum radius and a fraction p = 2 · 20° / 90° of the turn in the transitions. Below it are the three strip bends of the Laylight primitive set for comparison: a circular arc, the same partial euler, and a Bézier sbend.

References

  • W. Bogaerts and S. K. Selvaraja, "Compact single-mode silicon hybrid rib/strip waveguide with adiabatic bends," IEEE Photonics Journal 3(3), 422–432 (2011). doi:10.1109/JPHOT.2011.2142931
  • F. Vogelbacher et al., "Analysis of silicon nitride partial Euler waveguide bends," Optics Express 27(22), 31394 (2019). doi:10.1364/OE.27.031394

Parameters

ParameterValueSource
Strip waveguide width460 nmBogaerts 2011, Sec. 2.4
Rib waveguide width700 nm (70 nm etch, 150 nm slab)Bogaerts 2011, Sec. 3.1
Slab width4.7 µm (2 µm on either side of the rib)assumed
Converterdouble linear taper, 15 µm, 700 → 460 nmBogaerts 2011, Sec. 3.1, Fig. 8
Bend90°, minimum radius 5 µm, 20° transitions (p = 0.44)Bogaerts 2011, Sec. 3.3; Vogelbacher 2019
Rib sections20 µmassumed

How it is written

The converter is a cell with two ports, input and output, at the ends of its core taper. The hybrid bend places it twice: once at the end of the first rib section, and once with its output on the end of the bend and rotate 180deg, so that it runs backwards. The second rib section is placed by its right_center on the converter's input; since the converter is turned, the rib section turns with it, and so does the slab placed on the rib's center. The bend itself is the euler primitive given by its minimum radius; its start and end anchors point along the waveguide, so placing start on a port lines it up.

gdsfactory has the same building blocks (Euler bends, cross-sections with a slab section, ports with connect), which makes its version the closest to Laylight's. gdstk and KLayout have no Euler bend: their versions compute the bend's centerline with numpy in a function that the other examples import, and place the rotated converter by coordinates.

Implementations

ImplementationLines of codeTokensLayout
Laylight28283reference
gdsfactory37 (1.32×)362 (1.28×)same within 2 nm
gdstk46 (1.64×)698 (2.47×)same within 5 nm
KLayout51 (1.82×)809 (2.86×)same within 3 nm
laylight/waveguides.lyl
import "tech.lyl"

# Strip and rib waveguides on 220 nm SOI (strip 460 nm, rib 700 nm with a
# partially etched slab), after Bogaerts & Selvaraja, IEEE Photonics J. 2011.

define strip_width = 460nm
define rib_width = 700nm
define slab_width = 4.7um   # the rib and 2 um of slab on either side

# Rib-to-strip converter: a double linear taper in which the rib narrows to
# the strip width while its slab narrows away.
cell RibToStrip(length = 15um) {
    marker {
        input  { at core.start }
        output { at core.end }
    }

    layer si {
        shape taper with length, rib_width, strip_width as core.start at self.origin
    }

    layer slab {
        shape taper with length, slab_width, strip_width as slab.start at self.origin
    }
}

# A hybrid bend: a rib waveguide converted to strip for a compact partial
# Euler bend and back; below it, strip bends: circular, Euler and S-shaped.
# `radius` is the smallest radius of the bends, `p = 0.44` puts 20 deg spline
# sections at the ends of a 90 deg bend, and the rib sections are `length` long.
cell Waveguides(radius = 5um, p = 0.44, length = 20um) {
    layer si {
        shape rect with length, rib_width as rib_in.left_center at self.origin
        clone rib_in as rib_out.right_center at to_rib.marker.input[0]
        shape euler with {
            min_radius = radius, width = strip_width, angle = 90deg, p = p
        } as bend.start at to_strip.marker.output[0]
        shape arc with radius, strip_width, 90deg as circular.start at (0, -3 * radius)
        clone bend as euler.start at circular.start offset (2 * radius, 0)
        shape sbend with 4 * radius, radius, strip_width as s
        place s.start at euler.start offset (2 * radius, 0)
    }

    layer slab {
        shape rect with length, slab_width as slab_in.center at rib_in.center
        clone slab_in as slab_out.center at rib_out.center
    }

    place RibToStrip at rib_in.right_center as to_strip
    place RibToStrip.marker.output[0] at bend.end rotate 180deg as to_rib
}
gdsfactory/waveguides.py
"""Strip and rib waveguides and bends, as laylight/waveguides.lyl."""

import gdsfactory as gf

from tech import SI, SLAB

STRIP_WIDTH = 0.46
RIB_WIDTH = 0.7
SLAB_WIDTH = 4.7  # the rib and 2 um of slab on either side


@gf.cell
def rib_to_strip(length=15):
    """Rib-to-strip converter: a double linear taper in which the rib narrows to
    the strip width while its slab narrows away."""
    c = gf.Component()
    core = c << gf.components.taper(length, RIB_WIDTH, STRIP_WIDTH, layer=SI)
    c << gf.components.taper(length, SLAB_WIDTH, STRIP_WIDTH, layer=SLAB)
    c.add_ports(core.ports)
    return c


@gf.cell
def waveguides(radius=5, p=0.44, length=20):
    """A hybrid bend: a rib waveguide converted to strip for a compact partial
    Euler bend and back; below it, strip bends: circular, Euler and S-shaped."""
    c = gf.Component()
    strip = gf.cross_section.cross_section(
        width=STRIP_WIDTH, layer=SI, radius_min=radius
    )
    rib = gf.cross_section.cross_section(
        width=RIB_WIDTH, layer=SI, sections=[gf.Section(width=SLAB_WIDTH, layer=SLAB)]
    )
    euler = gf.components.bend_euler(
        radius, p=p, with_arc_floorplan=False, cross_section=strip
    )
    rib_in = c << gf.components.straight(length, cross_section=rib)
    to_strip = c << rib_to_strip()
    to_strip.connect("o1", rib_in.ports["o2"])
    bend = c << euler
    bend.connect("o1", to_strip.ports["o2"])
    to_rib = c << rib_to_strip()
    to_rib.connect("o2", bend.ports["o2"])
    rib_out = c << gf.components.straight(length, cross_section=rib)
    rib_out.connect("o1", to_rib.ports["o1"])
    bends = [
        gf.components.bend_circular(radius, cross_section=strip),
        euler,
        gf.components.bend_s((4 * radius, radius), cross_section=strip),
    ]
    for i, bend in enumerate(bends):
        (c << bend).dmove((2 * radius * i, -3 * radius))
    return c


if __name__ == "__main__":
    import sys

    waveguides().write_gds(sys.argv[1])
gdstk/waveguides.py
"""Strip and rib waveguides and bends, as laylight/waveguides.lyl."""

import gdstk
import numpy as np

from tech import SI, SLAB

STRIP_WIDTH = 0.46
RIB_WIDTH = 0.7
SLAB_WIDTH = 4.7  # the rib and 2 um of slab on either side


def euler(radius, angle, p=0.5, n=1000):
    """Centerline of an Euler bend from (0, 0) towards +x: its curvature grows
    linearly to 1 / radius, stays there and falls again; `p` is the fraction of
    the turn in the two clothoids. Positive angles turn left."""
    a = np.radians(abs(angle))
    s1 = np.sqrt(p * a)  # the unit curve's clothoids end at s1 and s2
    s2 = s1 + (1 - p) * a / s1
    s = np.linspace(0, s1 + s2, n)
    theta = np.select(
        [s < s1, s < s2], [s**2 / 2, s1 * (s - s1 / 2)], a - (s1 + s2 - s) ** 2 / 2
    )
    t = np.exp(1j * theta)
    z = np.concatenate([[0], np.cumsum(t[1:] + t[:-1]) * (s[1] - s[0]) / 2])
    z *= radius * s1
    return np.column_stack([z.real, np.sign(angle) * z.imag])


def taper(length, w1, w2, layer):
    """A linear taper from width w1 at (0, 0) to w2 at (length, 0)."""
    points = [(0, -w1 / 2), (length, -w2 / 2), (length, w2 / 2), (0, w1 / 2)]
    return gdstk.Polygon(points, **layer)


def rib_to_strip(length=15):
    """Rib-to-strip converter: a double linear taper in which the rib narrows to
    the strip width while its slab narrows away."""
    cell = gdstk.Cell(f"RibToStrip_{length}")
    cell.add(taper(length, RIB_WIDTH, STRIP_WIDTH, SI))
    cell.add(taper(length, SLAB_WIDTH, STRIP_WIDTH, SLAB))
    return cell


def rib(x0, y0, x1, y1):
    """A rib waveguide from (x0, y0) to (x1, y1), along x or y."""
    return [
        gdstk.FlexPath([(x0, y0), (x1, y1)], RIB_WIDTH, **SI),
        gdstk.FlexPath([(x0, y0), (x1, y1)], SLAB_WIDTH, **SLAB),
    ]


def waveguides(radius=5, p=0.44, length=20):
    """A hybrid bend: a rib waveguide converted to strip for a compact partial
    Euler bend and back; below it, strip bends: circular, Euler and S-shaped."""
    cell = gdstk.Cell("Waveguides")
    converter, t = rib_to_strip(), 15
    bend = euler(radius, 90, p)
    x, y = bend[-1] + (length + t, 0)
    cell.add(*rib(0, 0, length, 0), *rib(x, y + t, x, y + t + length))
    cell.add(gdstk.Reference(converter, (length, 0)))
    cell.add(gdstk.FlexPath(bend + (length + t, 0), STRIP_WIDTH, tolerance=1e-3, **SI))
    cell.add(gdstk.Reference(converter, (x, y + t), rotation=-np.pi / 2))
    y = -3 * radius
    arc = gdstk.FlexPath((0, y), STRIP_WIDTH, tolerance=1e-3, **SI)
    cell.add(arc.arc(radius, -np.pi / 2, 0))
    cell.add(gdstk.FlexPath(bend + (2 * radius, y), STRIP_WIDTH, tolerance=1e-3, **SI))
    s = gdstk.FlexPath((4 * radius, y), STRIP_WIDTH, tolerance=1e-3, **SI)
    control = [(2 * radius, 0), (2 * radius, radius), (4 * radius, radius)]
    cell.add(s.bezier(control, relative=True))
    return cell


if __name__ == "__main__":
    import sys

    top = waveguides()
    lib = gdstk.Library()
    lib.add(top, *top.dependencies(True))
    lib.write_gds(sys.argv[1])
klayout/waveguides.py
"""Strip and rib waveguides and bends, as laylight/waveguides.lyl."""

import klayout.db as kdb
import numpy as np

from tech import SI, SLAB, layout

STRIP_WIDTH = 0.46
RIB_WIDTH = 0.7
SLAB_WIDTH = 4.7  # the rib and 2 um of slab on either side


def euler(radius, angle, p=0.5, n=1000):
    """Centerline of an Euler bend from (0, 0) towards +x: its curvature grows
    linearly to 1 / radius, stays there and falls again; `p` is the fraction of
    the turn in the two clothoids. Positive angles turn left."""
    a = np.radians(abs(angle))
    s1 = np.sqrt(p * a)  # the unit curve's clothoids end at s1 and s2
    s2 = s1 + (1 - p) * a / s1
    s = np.linspace(0, s1 + s2, n)
    theta = np.select(
        [s < s1, s < s2], [s**2 / 2, s1 * (s - s1 / 2)], a - (s1 + s2 - s) ** 2 / 2
    )
    t = np.exp(1j * theta)
    z = np.concatenate([[0], np.cumsum(t[1:] + t[:-1]) * (s[1] - s[0]) / 2])
    z *= radius * s1
    return np.column_stack([z.real, np.sign(angle) * z.imag])


def path(points, width):
    """A waveguide of `width` along `points` (rows of x, y)."""
    return kdb.DPath([kdb.DPoint(x, y) for x, y in points], width)


def polygon(points):
    return kdb.DPolygon([kdb.DPoint(x, y) for x, y in points])


def taper(length, w1, w2):
    """A linear taper from width w1 at (0, 0) to w2 at (length, 0)."""
    return polygon([(0, -w1 / 2), (length, -w2 / 2), (length, w2 / 2), (0, w1 / 2)])


def rib_to_strip(length=15):
    """Rib-to-strip converter: a double linear taper in which the rib narrows to
    the strip width while its slab narrows away."""
    cell = layout.create_cell("RibToStrip")
    cell.shapes(layout.layer(SI)).insert(taper(length, RIB_WIDTH, STRIP_WIDTH))
    cell.shapes(layout.layer(SLAB)).insert(taper(length, SLAB_WIDTH, STRIP_WIDTH))
    return cell


def waveguides(radius=5, p=0.44, length=20):
    """A hybrid bend: a rib waveguide converted to strip for a compact partial
    Euler bend and back; below it, strip bends: circular, Euler and S-shaped."""
    cell = layout.create_cell("Waveguides")
    si = cell.shapes(layout.layer(SI))
    slab = cell.shapes(layout.layer(SLAB))
    converter, t = rib_to_strip(), 15
    bend = euler(radius, 90, p)
    x, y = bend[-1] + (length + t, 0)
    for rib in [[(0, 0), (length, 0)], [(x, y + t), (x, y + t + length)]]:
        si.insert(path(rib, RIB_WIDTH))
        slab.insert(path(rib, SLAB_WIDTH))
    for trans in [kdb.DCplxTrans(length, 0), kdb.DCplxTrans(1, 270, False, x, y + t)]:
        cell.insert(kdb.DCellInstArray(converter.cell_index(), trans))
    si.insert(path(bend + (length + t, 0), STRIP_WIDTH))
    y = -3 * radius
    a = np.linspace(0, np.pi / 2, 500)
    arc = np.column_stack([np.sin(a), 1 - np.cos(a)]) * radius
    si.insert(path(arc + (0, y), STRIP_WIDTH))
    si.insert(path(bend + (2 * radius, y), STRIP_WIDTH))
    u = np.linspace(0, 1, 500)[:, None]
    control = np.array([(0, 0), (2, 0), (2, 1), (4, 1)]) * radius
    weights = np.hstack([(1 - u) ** 3, 3 * u * (1 - u) ** 2, 3 * u**2 * (1 - u), u**3])
    si.insert(path(weights @ control + (4 * radius, y), STRIP_WIDTH))
    return cell


if __name__ == "__main__":
    import sys

    waveguides()
    layout.write(sys.argv[1])

On this page