Skip to main content

Layout scripting for photonic and electronic chip design

Project description

Lumicron API

Layout scripting for photonic and electronic chip design.

Define cells, place components, route waveguides, and export to GDS/OASIS — all in Python.

Install

pip install lumicron_api

Requires Python 3.12+.

Documentation

The User Guide and PDK Authoring Guide PDFs are bundled inside the wheel — no internet required after install.

lumicron-docs                  # opens the User Guide
lumicron-docs --pdk            # opens the PDK Authoring Guide
lumicron-docs --where          # prints both filesystem paths

From Python:

import lumicron_api as lm

lm.open_user_guide()           # opens the User Guide in your default PDF viewer
lm.user_guide_path()           # → Path to the bundled PDF

Quick Start

import lumicron_api as lm
import lumicron_api.pdks.elyon_demo.all as pdk

@lm.pcell
def MZI(arm_length: float = 300,
        delay_y: float = 50):
    c = lm.CELL("MZI")

    # Components
    splitter = pdk.YSplitter(layer=pdk.LAYER.SILC)
    combiner = pdk.YSplitter(layer=pdk.LAYER.SILC)

    # Add and place
    s = c.add(splitter)
    k = c.add(combiner)
    c.Place(s).at((0, 0))
    c.Place(k).using("i1").at((arm_length + 50, 0)).rotate_by(180)

    # Route
    c.Route(s.port["o1"], k.port["o2"], radius=10)
    c.Route(s.port["o2"], k.port["o1"], radius=10).jog("S", by=delay_y)

    return c


if __name__ == "__main__":
    MZI().to_layout().to_gds("mzi.gds")

Features

  • Hierarchical Cells — build complex designs from reusable parametric components
  • Fluid Chain-based Syntax - chain verbs and descriptors to your place and route commands using intuitive commands to "speak" your layout into existence. c.Place(mmi).using("in1").relative_to(heater.port["in"]).move_by(dx=-50, dy=-50).rotate_by(90)
  • Intuitive Placement System - anchor-based positioning with directional helpers (.above(), .below(), .right_of(), .left_of()) and .move_by() offset helper
  • Smart Routing — auto-router that automatically selects route style based on port position, overridable by style="manhattan", "sbend", or "direct". Automatically validates bend radius and automatically tapers routes with flexible positional placement
  • Robust routing — routing supported for ports at arbitrary angles. Contains intuitive route management via .go() commands: c.Route(coupler.port["out"], mod_array.port["m0_in"], bend_radius=25).go("N", to=mod_array.S, dy=-100).go("W", to=mod_array.port["m0_in"]). Allows custom bend and taper PCell to be auto-placed into routes
  • Route shaping.jog(direction, by) inserts a U-shaped manhattan detour at the route's bend radius for delay lines, MZI arms, and pad fanout
  • Bend types — circular (default), euler (clothoid), and custom bend PCells with adjustable p parameter
  • Bundle routingc.RouteBundle(ports_a, ports_b, spacing=10) routes multiple port pairs with a single call. Chainable: .go("E", by=150).go("N", by=80).go("E", by=150) steers the whole bundle as a shared spine with pitch preserved through every 90° turn. Pitch-matching fanout (e.g. fiber-array 127 µm in, 2 µm bus through the middle) is auto-inserted — .go() distances describe total port-to-port spans, not spine-only travel. fanout="sbend" swaps the right-angle U-detour for a smooth diagonal bridge, useful when the per-channel stagger is tighter than one bend radius. .match_lengths() equalizes per-channel path lengths via a closed-form rectangular-detour algorithm — each channel solves its own leg lengths analytically so total paths match across the bundle. Variable forward distances per channel are supported. Phase-matched arrays via delta=[...], absolute length via target=L, length_match=True as a one-liner shortcut, mirror=True/mirror=[…] to flip the bulge direction (whole bundle or per-channel)
  • Layout-aware routingc.Route(a, b, clearance=5) auto-detours routes around placed cell bounding boxes, bend-radius-aware with path simplification
  • Waveguide — standalone path primitive for spirals, delay lines, and tapered structures with custom bend PCell support
  • Arrays — 1D and 2D arrays with per-element rotation
  • Port promotion — access deeply nested ports from any level in the hierarchy
  • Shapes — Rectangle, Circle, Ring, Oval, Arc, Triangle, Parallelogram, Trapezoid
  • PDK support — ships with Elyon Demo PDK (17 layers, 11 components, 3 route profiles)
  • Export — GDSII, OASIS with auto-filename from cell name

Placement

# Absolute
c.Place(shape).at((100, 200))

# Relative
c.Place(pad).right_of(coupler)
c.Place(stalk).below(segment).move_by(dy=5)

# Anchor + rotate
c.Place(splitter).using("i1").at(frame.NW).rotate_by(90)

Routing

# Auto-route with circular bends
c.Route(port_a, port_b, radius=10)

# Euler bends (smooth curvature transition)
c.Route(port_a, port_b, radius=10, bend="euler")
c.Route(port_a, port_b, radius=10, bend="euler", p=0.3)  # adjust euler fraction

# Off-angle ports — auto-detected
c.Route(angled_port_a, angled_port_b, radius=25)

# Force a routing style
c.Route(port_a, port_b, radius=10, style="manhattan")
c.Route(port_a, port_b, radius=10, style="sbend")
c.Route(port_a, port_b, radius=10, style="direct")

# Custom bend PCell (user-defined 90° bend geometry)
c.Route(port_a, port_b, bend=my_euler_bend_cell)

# Manual with GPS-style directions
c.Route(port_a, port_b, radius=15) \
    .go("E", by=50) \
    .go("N", to=port_b, dy=-20)

# Route jog — U-shaped detour for delay lines, MZI arms, pad fanout
c.Route(port_a, port_b, radius=10).jog("S", by=50)

# Positional taper — place taper 50µm from port A
c.Route(port_a, port_b, radius=10).start_taper(length=10, at=50)

# With route profile
c.Route(port_a, port_b, profile=pdk.RP.siln_strip)

# Layout-aware routing — detour around placed cells
c.Route(port_a, port_b, radius=10, clearance=5)

# Control distance from port to first/last bend (direct paths)
c.Route(port_a, port_b, radius=10, start_straight=20, end_straight=15)

# Bundle routing — route multiple port pairs at once
c.RouteBundle(
    ports_a=[ref_a.port["o1"], ref_a.port["o2"]],
    ports_b=[ref_b.port["i1"], ref_b.port["i2"]],
    spacing=10, radius=10,
)

# Manhattan bundle — steer the whole bus with .go(), pitch preserved
# through each corner. Pitch-matching fanout is auto-inserted.
(c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20)
   .go("E", by=150).go("N", by=80).go("E", by=150))

# S-bend fanout for sub-radius per-channel stagger (e.g. 25 µm port pitch
# into 20 µm bundle pitch): swaps the U-detour for a smooth diagonal bridge.
(c.RouteBundle(ports_a=pa, ports_b=pb, spacing=20, radius=20, fanout="sbend")
   .go("E", by=300))

# Length-matched bundle — equalize per-channel path lengths.
# .match_lengths() rebuilds every channel as an independent trombone
# detour so each route's centerline is the same length. delta=[...]
# stages a per-channel offset (phased-array / delay-line bundles).
# length_match=True on the constructor is a one-liner shortcut.
(c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20)
   .go("E", by=200).go("N", by=100).go("E", by=300)
   .match_lengths())

(c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20)
   .go("E", by=500)
   .match_lengths(delta=[0, 40, 80, 120]))

c.RouteBundle(ports_a=pa, ports_b=pb, spacing=2, radius=20,
              length_match=True).go("E", by=500)

Waveguide

# Standalone path from points — auto-generates i1/o1 ports
wg = lm.Waveguide(
    points=[(0, 0), (50, 0), (50, 100), (100, 100)],
    layer=pdk.LAYER.SILC, width=0.5, radius=10,
)

# Tapered waveguide (width interpolates linearly)
wg = lm.Waveguide(
    points=spiral_points,
    layer=pdk.LAYER.SILC, width=0.2, width_end=1.0, radius=10,
)

# Custom bend PCell in waveguide
wg = lm.Waveguide(
    points=[(0, 0), (50, 0), (50, 50)],
    layer=pdk.LAYER.SILC, width=0.5, bend=my_bend_cell,
)

ref = c.add(wg)
c.Route(some_port, ref.port["o1"], radius=10)

Arrays

couplers = lm.ARRAY(coupler, count=8, pitch=(0, 127), rotation=90)
arr = c.add(couplers)
c.Place(arr).at((0, 0))

# Access individual element ports
arr[0].port["o1"]
arr[3].C

Parametric Cells

@lm.pcell
def Ring(radius: float = 50, width: float = 5):
    c = lm.CELL("Ring")
    c.add(lm.Ring(outer_radius=radius, width=width, layer=pdk.LAYER.SILC))
    return c

Ring()              # cell name: "Ring"
Ring(radius=100)    # cell name: "Ring_radius=100"

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lumicron_api-0.5.0.tar.gz (1.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lumicron_api-0.5.0-py3-none-any.whl (1.6 MB view details)

Uploaded Python 3

File details

Details for the file lumicron_api-0.5.0.tar.gz.

File metadata

  • Download URL: lumicron_api-0.5.0.tar.gz
  • Upload date:
  • Size: 1.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for lumicron_api-0.5.0.tar.gz
Algorithm Hash digest
SHA256 0fae350ef181b3670863602d105913cae9b9aa857bea1dd49a98fab2de51aa89
MD5 a8cfca3782baef60b99f87b978d25660
BLAKE2b-256 46c9f64da0ef49ca90098a54fe86afa6a2503e31f2ce257ab5a8b60f752822be

See more details on using hashes here.

File details

Details for the file lumicron_api-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: lumicron_api-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.10

File hashes

Hashes for lumicron_api-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4ba60fe88c1036c2db13ef1548f534007f176ec8e82f520e324ec0f4079fde9a
MD5 cca5c373b76410f32c303972a3cd5e2c
BLAKE2b-256 14cdd294418802bef166bd56c17b696c5c3ac61c7b0d5ce66a72c6c23a4a848c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page