Device Abstraction
Understand what a device is in laylight and how it relates to cells, ports, and placement.
A device is the core reusable unit in laylight. It is more than a KLayout cell. A device combines the logic that generates geometry, the connection points that other devices attach to, and the layout context needed to build both.
This abstraction keeps layout assembly explicit. You construct devices, inspect their ports, connect them into larger chains, and only then place the resulting cells into a top-level design.
A device defines a small contract
Every reusable structure in laylight is built around AbstractDevice. That contract is intentionally small:
layoutidentifies the KLayoutLayoutthat owns the device's cells and geometry.techprovides the process and layer definitions used during generation.cell()returns the KLayout cell for the device.input()andoutput()return transforms that describe how the device connects to other devices.
That means callers do not need to know how a device draws itself internally. They only need a cell to place and a set of transforms to connect.
Why not work with raw cells directly?
A raw KLayout cell can hold geometry, but it does not describe how that geometry should be reused. By itself, a cell does not answer questions such as:
- Where is the input face of this structure?
- Where should the next structure attach?
- Which technology layers should be used when building it?
- Has its geometry already been materialized?
The device abstraction packages those concerns together. It gives laylight a common way to treat a straight waveguide, a coupler, an MMI, or a composite assembly as the same kind of object.
Geometry is generated lazily
AbstractDevice does not require geometry to be built when the Python object is created. Instead, cell() calls top() on first use, caches the returned cell, and reuses that cached cell on later calls.
This lazy model is useful for composition. A device can be passed around as a design object before any geometry is emitted. The geometry appears only when something needs the backing cell, such as placement into a top cell or export to GDS or PNG.
Ports are represented as transforms
In laylight, ports are geometric transforms, returned as laylight.Trans values from input() and output().
This is a deliberate choice. A transform captures the position, orientation, and mirroring needed to align one device to another. The core composition logic can therefore stay purely geometric:
parent_output = parent.output()[0]
child_input = child.input()[0]
placement = parent_output * child_input.inverted()This is the basis for device chaining. A caller does not need a special-case connector for each structure type. If two devices expose compatible transforms, they can be aligned mechanically.
Multiple ports are handled the same way. A device can return several output transforms or several input transforms, and higher-level composition code can choose which one to use.
Composition happens above the device
Devices describe themselves. Placement logic composes them.
Placer is the main utility that turns device-level contracts into assembled layouts. When you call then(...), it reads the parent's output transform, reads the child's input transform, computes the relative placement, and carries that transform forward through the chain.
That separation is important:
- a device is responsible for its own geometry and ports
- the placer is responsible for how devices are arranged in a larger assembly
This keeps device classes focused. A ring, taper, or coupler can describe its own geometry without needing to know where it will sit in the final layout.
Composite blocks can become devices again
After several devices have been chained together, the result can be wrapped back into a device using Placer.as_dev(...).
This produces an AdhocDevice, which is a bridge between a concrete cell and the normal device interface. It is useful when a composite block should behave like a reusable structure: something that has inputs, outputs, and a cell, even if it was assembled from smaller parts rather than implemented as a dedicated class.
This keeps the abstraction closed under composition. Individual devices can form a composite block, and that composite block can still be treated as a device by the rest of the system.
When to think in devices
Define a device when a piece of geometry needs to be:
- parameterized
- reused in multiple places
- connected to downstream structures through ports
- composed into a larger assembly
Work directly with raw cells or top-level shape insertion when the geometry is purely local scaffolding and no other part of the design needs to reason about its ports or reuse it as a block.
In practice, the boundary is simple: if other code needs to connect to it, place it predictably, or treat it as a reusable building block, it should probably be a device.
What this page does not cover
This page explains the role of the device abstraction. It does not document every concrete structure class or every placement helper.
For the minimal workflow, start with Overview. For the broader layout assembly model, see Core Concepts.