Examples

Spiral Delay Line

Two interleaved Archimedean spirals of a 2 µm multimode waveguide, joined by an Euler S-bend.

Compiling…
DelayLine from laylight/delay_line.lyl, compiled in your browserOpen in playground

An optical delay line is a long waveguide folded into a small area. A double spiral is the compact way to do it: the light runs inward along one spiral, turns around in an S-bend at the center and runs outward along a second spiral interleaved with the first, so input and output end up on opposite sides. Hong et al. made such spirals with ultralow loss by using a 2 µm wide multimode waveguide, in which the fundamental mode barely touches the sidewalls, and Euler bends in the S-bend, which do not excite the higher-order modes.

In the example, the light enters through a 450 nm waveguide and a bent taper, a quarter circle whose width grows to 2 µm, runs inward along an Archimedean spiral, turns in an S-bend of two Euler bends, and leaves along the interleaved outward spiral and a second bent taper. The waveguides are 1.5 µm apart, so each spiral advances by 7 µm per turn. The outward half is the inward half turned by 180°. With the default of 3 turns per spiral the waveguide is 1.5 mm long; with 30 turns it is 4.9 cm, with an outer radius of 235 µm, close to the paper's 5 cm spiral.

Two simplifications: the paper's S-bend uses a modified Euler curve whose curvature ranges from 1/25 µm⁻¹ to 1/10 µm⁻¹, and narrows to 0.6 µm at the junction. The example uses standard Euler bends with the same minimum radius of 10 µm and keeps the full width, so its S-bend is 42.6 µm tall instead of 32 µm.

References

  • S. Hong et al., "Ultralow-loss compact silicon photonic waveguide spirals and delay lines," Photonics Research 10(1), 1–7 (2022). doi:10.1364/PRJ.437726

Parameters

ParameterValueSource
Waveguide width2 µmHong 2022
Gap between waveguides1.5 µm (7 µm pitch per spiral)Hong 2022 (pitch derived)
Turns per spiral3assumed (30 for the paper's 5 cm)
Radius where the spirals meet the S-bend25 µmHong 2022 (its R_max)
S-bendtwo Euler bends, minimum radius 10 µmHong 2022 (standard Euler bends, constant width)
Input and output450 nm, bent tapers of 25 µm radiusassumed

How it is written

The spiral is a path through points computed in a loop from the spiral's equation; the outward spiral is a clone placed with rotate 180deg. The two Euler bends of the S-bend are placed by their end on a frame at the center whose direction is computed from where the spiral ends. The bent tapers are where Laylight is at a disadvantage: it has no bent taper or path of varying width, so the taper is a polygon whose two edges are computed in two loops. gdstk draws it with a single call, an arc whose width changes along it, and chains the taper, the spiral and the S-bend into one path, which makes its version the shortest here.

Where a sampled path meets an exact primitive such as euler, the path's end face is perpendicular to its last rounded segment rather than to the curve, which leaves notches of a few nanometers; the spiral uses ends = "round" to close them.

Implementations

ImplementationLines of codeTokensLayout
Laylight40424reference
gdsfactory32 (0.80×)351 (0.83×)same within 2 nm
gdstk27 (0.68×)314 (0.74×)same within 2 nm
KLayout36 (0.90×)462 (1.09×)same within 2 nm
laylight/delay_line.lyl
import "tech.lyl"

# A spiral delay line after Hong et al., Photonics Res. 2022: two interleaved
# Archimedean spirals of a multimode waveguide, joined at the centre by an S-bend
# of two Euler bends, with bent tapers to the single-mode input and output.
cell DelayLine(
    width = 2um,
    gap = 1.5um,  # between neighbouring waveguides
    turns number = 3,  # of each spiral
    inner_radius = 25um,  # where the spirals meet the S-bend
    p = 0.74,  # Euler fraction of the S-bend: min radius 10 um
    io_width = 450nm,
    taper_radius = 25um,
) {
    layer si {
        # The inward spiral starts at the polar angle where it heads straight up
        # (an Archimedean spiral crosses its circles at the angle atan(b / r));
        # the outward one is it turned by 180deg.
        define b = (width + gap) / pi    # growth of the radius per radian
        define outer = inner_radius + 2 * (width + gap) * turns
        define start = 180deg + atan(b / outer)
        define n = round(720 * turns)    # 0.5deg steps
        define spiral: [point] = []
        for i in 0..n + 1 {
            define r = outer - (outer - inner_radius) * i / n
            define a = start - turns * 360deg * i / n
            spiral.append((r * cos(a), r * sin(a)))
        }
        shape path with spiral, width, ends = "round" as inward    # closes the joints
        place inward at self.origin
        clone inward as outward at self.origin rotate 180deg

        # Each half of the S-bend turns by 180deg - 2 skew to meet the spiral.
        define skew = atan(b / inner_radius)
        define radius = inner_radius / (2 * cos(skew))    # the chord is inner_radius
        shape euler with radius, width, 2 * skew - 180deg, p = p as first
        clone first as second
        define heading = start - turns * 360deg + 90deg + skew    # at the centre
        place first.end at (0, 0, heading)
        place second.end at (0, 0, heading + 180deg)

        # The bent tapers: quarter circles whose width grows linearly to width.
        define taper: [point] = []
        for i in 0..181 {
            define r = taper_radius + (io_width + (width - io_width) * i / 180) / 2
            taper.append((r * sin(i * 0.5deg), -r * cos(i * 0.5deg)))
        }
        for i in 0..181 {
            define r = taper_radius - (width - (width - io_width) * i / 180) / 2
            taper.append((r * cos(i * 0.5deg), -r * sin(i * 0.5deg)))
        }
        shape polygon with taper as input
        place input at (inward.start.x - taper_radius, inward.start.y)
        clone input as output
        place output at (taper_radius - inward.start.x, -inward.start.y) rotate 180deg
    }
}
gdsfactory/delay_line.py
"""A spiral delay line, as laylight/delay_line.lyl."""

import gdsfactory as gf
import numpy as np

from tech import SI


@gf.cell
def delay_line(
    width=2,
    gap=1.5,
    turns=3,
    inner_radius=25,
    p=0.74,
    io_width=0.45,
    taper_radius=25,
):
    """Two interleaved Archimedean spirals of a multimode waveguide, joined at the
    centre by an S-bend of two Euler bends, with bent tapers to the single-mode
    input and output."""
    c = gf.Component()
    # The inward spiral starts at the polar angle where it heads straight up (an
    # Archimedean spiral crosses its circles at the angle atan(b / r)); the
    # outward one is it turned by 180 degrees.
    b = (width + gap) / np.pi  # growth of the radius per radian
    outer = inner_radius + 2 * (width + gap) * turns
    t = np.linspace(0, 1, round(720 * turns) + 1)  # 0.5 degree steps
    a = np.pi + np.arctan(b / outer) - 2 * np.pi * turns * t
    r = outer - (outer - inner_radius) * t
    skew = np.arctan(b / inner_radius)
    points = np.column_stack([r * np.cos(a), r * np.sin(a)])
    end_angle = np.degrees(a[-1] - skew) - 90
    spiral = gf.Path(points, start_angle=90, end_angle=end_angle)  # exact tangents
    # Each half of the S-bend turns by 180 degrees - 2 skew to meet the spiral.
    radius = inner_radius / (2 * np.cos(skew))  # the chord is inner_radius
    bend = gf.path.euler(radius, 2 * np.degrees(skew) - 180, p, use_eff=True)
    half = (spiral + bend).extrude(width=width, layer=SI)
    # The bent taper: a quarter circle whose width grows linearly to width.
    xs = gf.cross_section.strip
    taper = gf.path.arc(taper_radius, npoints=181).extrude_transition(
        gf.path.transition(xs(io_width, SI), xs(width, SI), width_type="linear")
    )
    for angle in [0, 180]:
        (c << half).drotate(angle)
        (c << taper).dmove(points[0] - taper_radius).drotate(angle)
    return c


if __name__ == "__main__":
    import sys

    delay_line().write_gds(sys.argv[1])
gdstk/delay_line.py
"""A spiral delay line, as laylight/delay_line.lyl."""

import gdstk
import numpy as np

from tech import SI
from waveguides import euler


def delay_line(
    width=2,
    gap=1.5,
    turns=3,
    inner_radius=25,
    p=0.74,
    io_width=0.45,
    taper_radius=25,
):
    """Two interleaved Archimedean spirals of a multimode waveguide, joined at the
    centre by an S-bend of two Euler bends, with bent tapers to the single-mode
    input and output."""
    cell = gdstk.Cell("DelayLine")
    # The inward spiral starts at the polar angle where it heads straight up (an
    # Archimedean spiral crosses its circles at the angle atan(b / r)); the
    # outward one is it turned by 180 degrees.
    b = (width + gap) / np.pi  # growth of the radius per radian
    outer = inner_radius + 2 * (width + gap) * turns
    t = np.linspace(0, 1, round(720 * turns) + 1)  # 0.5 degree steps
    a = np.pi + np.arctan(b / outer) - 2 * np.pi * turns * t
    spiral = (outer - (outer - inner_radius) * t) * np.exp(1j * a)
    # Each half of the S-bend turns by 180 degrees - 2 skew to meet the spiral.
    skew = np.arctan(b / inner_radius)
    bend = euler(1, 2 * np.degrees(skew) - 180, p) @ [1, 1j]  # as x + iy
    bend *= inner_radius / abs(bend[-1]) * np.exp(1j * (a[-1] - np.pi / 2 - skew))
    # The bent taper from the input, the spiral and the S-bend, as one path.
    start = spiral[0] - taper_radius * (1 + 1j)
    half = gdstk.FlexPath(start, io_width, tolerance=1e-3, **SI)
    half.arc(taper_radius, -np.pi / 2, 0, width=width)
    half.segment(np.concatenate([spiral[1:], spiral[-1] + bend[1:]]))
    cell.add(half, half.copy().rotate(np.pi))
    return cell


if __name__ == "__main__":
    import sys

    top = delay_line()
    lib = gdstk.Library()
    lib.add(top, *top.dependencies(True))
    lib.write_gds(sys.argv[1])
klayout/delay_line.py
"""A spiral delay line, as laylight/delay_line.lyl."""

import klayout.db as kdb
import numpy as np

from tech import SI, layout
from waveguides import euler


def delay_line(
    width=2,
    gap=1.5,
    turns=3,
    inner_radius=25,
    p=0.74,
    io_width=0.45,
    taper_radius=25,
):
    """Two interleaved Archimedean spirals of a multimode waveguide, joined at the
    centre by an S-bend of two Euler bends, with bent tapers to the single-mode
    input and output."""
    cell = layout.create_cell("DelayLine")
    si = cell.shapes(layout.layer(SI))
    # The inward spiral starts at the polar angle where it heads straight up (an
    # Archimedean spiral crosses its circles at the angle atan(b / r)); the
    # outward one is it turned by 180 degrees.
    b = (width + gap) / np.pi  # growth of the radius per radian
    outer = inner_radius + 2 * (width + gap) * turns
    t = np.linspace(0, 1, round(720 * turns) + 1)  # 0.5 degree steps
    a = np.pi + np.arctan(b / outer) - 2 * np.pi * turns * t
    spiral = (outer - (outer - inner_radius) * t) * np.exp(1j * a)
    # Each half of the S-bend turns by 180 degrees - 2 skew to meet the spiral.
    skew = np.arctan(b / inner_radius)
    bend = euler(1, 2 * np.degrees(skew) - 180, p) @ [1, 1j]  # as x + iy
    bend *= inner_radius / abs(bend[-1]) * np.exp(1j * (a[-1] - np.pi / 2 - skew))
    inward = np.concatenate([spiral, spiral[-1] + bend[1:]])
    # Round ends close the joint to the taper. A polygon, since GDS paths hold at
    # most 8191 points (KLayout splits long polygons when writing).
    points = [kdb.DPoint(z.real, z.imag) for z in inward]
    half = kdb.DPath(points, width, width / 2, width / 2, True).polygon()
    # The bent taper: a quarter circle whose width grows linearly to width.
    phi = np.linspace(0, np.pi / 2, 181)  # 0.5 degree steps
    w = io_width + (width - io_width) * phi / (np.pi / 2)
    arc = np.exp(1j * (phi - np.pi / 2))
    edge = np.concatenate(
        [(taper_radius + w / 2) * arc, ((taper_radius - w / 2) * arc)[::-1]]
    )
    edge += spiral[0] - taper_radius
    taper = kdb.DPolygon([kdb.DPoint(z.real, z.imag) for z in edge])
    for trans in [kdb.DCplxTrans.R0, kdb.DCplxTrans.R180]:
        si.insert(half.transformed(trans))
        si.insert(taper.transformed(trans))
    return cell


if __name__ == "__main__":
    import sys

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

On this page