Skip to content

Download notebook (.ipynb)

Instances

A cell instance is a pointer to an existing cell placed inside another cell. The instance itself contains no geometry — it only records which cell to show, and where (position, rotation, mirror).

Key benefits:

  • Memory efficiency — one cell definition, many placements.
  • Consistency — modify the original cell and every instance updates automatically.
  • Hierarchy — build circuits from subcircuits without flattening.
import kfactory as kf


class LAYER(kf.LayerInfos):
    WG: kf.kdb.LayerInfo = kf.kdb.LayerInfo(1, 0)
    WGEX: kf.kdb.LayerInfo = kf.kdb.LayerInfo(2, 0)
    FLOORPLAN: kf.kdb.LayerInfo = kf.kdb.LayerInfo(10, 0)


L = LAYER()
kf.kcl.infos = L

Creating an instance

Use cell.create_inst(child) or the << shorthand to place a child cell inside a parent. Both forms return an Instance object that you can move, rotate, or mirror after creation.

# Build a simple polygon cell to reuse
poly = kf.KCell(name="polygon_source")
xpts = [0, 0, 5, 6, 9, 12]
ypts = [0, 1, 1, 2, 2, 0]
poly.shapes(kf.kcl.find_layer(L.WGEX)).insert(
    kf.kdb.DPolygon([kf.kdb.DPoint(x, y) for x, y in zip(xpts, ypts, strict=False)])
)
poly


png

# Long form: create_inst
parent = kf.KCell(name="three_instances")
inst1 = parent.create_inst(poly)

# Short form: << operator (exactly equivalent)
inst2 = parent << poly
inst3 = parent << poly

parent


png

All three instances overlap because they share the same default position. We can move them independently without touching the original cell.

inst2.transform(kf.kdb.DCplxTrans(1, 15, False, 0, 0))  # rotate 15°
inst3.transform(kf.kdb.DCplxTrans(1, 30, False, 0, 0))  # rotate 30°
parent


png

Modifying the original propagates everywhere

Instances are live pointers. Adding geometry to poly is immediately visible in every instance — without touching parent.

poly.shapes(kf.kcl.find_layer(L.WG)).insert(
    kf.kdb.DPolygon(
        [
            kf.kdb.DPoint(x, y)
            for x, y in zip([14, 14, 16, 16], [0, 2, 2, 0], strict=False)
        ]
    )
)
parent  # all three instances now show the extra rectangle


png

Transforming instances

An Instance wraps a KLayout CellInstArray. You can apply any KLayout transformation to reposition it:

Transform type Preserves angles? Floating-point coords?
Trans Yes (orthogonal) No (DBU integers)
DTrans Yes (orthogonal) Yes (µm)
DCplxTrans No (arbitrary °) Yes (µm)
c = kf.KCell(name="transform_demo")
s = kf.cells.straight.straight(length=10, width=0.5, layer=L.WG)

inst_a = c << s
inst_b = c << s
inst_c = c << s

inst_b.transform(kf.kdb.DTrans(0.0, 5.0))  # shift 5 µm north
inst_c.transform(kf.kdb.DCplxTrans(1, 45, False, 0, 10))  # shift + rotate 45°

c


png

Connecting instances by port

instance.connect("port_name", target_port) moves and rotates the instance so that the named port aligns face-to-face with target_port. This is the standard way to assemble photonic circuits.

circuit = kf.KCell(name="chained_straights")

seg1 = circuit << kf.cells.straight.straight(length=10, width=0.5, layer=L.WG)
seg2 = circuit << kf.cells.straight.straight(length=15, width=0.5, layer=L.WG)
seg3 = circuit << kf.cells.straight.straight(length=10, width=0.5, layer=L.WG)

seg2.connect("o1", seg1.ports["o2"])
seg3.connect("o1", seg2.ports["o2"])

circuit.add_ports(seg1.ports, prefix="in_")
circuit.add_ports(seg3.ports, prefix="out_")
circuit.draw_ports()
circuit


png

Mirrored connections

Pass mirror=True to reflect the instance before alignment. This is useful when two ports face the same direction (e.g. building a U-bend from two 90° bends).

bend = kf.cells.euler.bend_euler(radius=5, width=0.5, layer=L.WG, angle=90)

u_bend = kf.KCell(name="u_bend")
b1 = u_bend << bend
b2 = u_bend << bend
b2.connect("o1", b1.ports["o2"], mirror=True)

u_bend.add_ports(b1.ports, prefix="left_")
u_bend.add_ports(b2.ports, prefix="right_")
u_bend.draw_ports()
u_bend


png

Arrays of instances

create_inst(cell, na=N, nb=M, a=vec_a, b=vec_b) creates a regular NxM array using a single GDS AREF record — very compact in the output file.

Note: Array elements cannot have individual ports; use individual instances when you need per-element port access.

tile = kf.cells.straight.straight(length=10, width=0.5, layer=L.WG)

grid = kf.KCell(name="instance_array")
arr = grid.create_inst(
    tile,
    na=3,
    nb=2,  # 3 columns, 2 rows
    a=(20_000, 0),  # 20 µm step in x  (DBU = nm)
    b=(0, 10_000),  # 10 µm step in y
)
grid.draw_ports()
grid


png

Ports of array elements are accessed via inst[port_name, col, row]:

print("Port at column 0, row 1:", arr["o1", 0, 1])
print("Port at column 2, row 0:", arr["o2", 2, 0])
Port at column 0, row 1: Port(self.name='o1', self.width=500, trans=r180 *1 0,10, layer=WG (1/0), port_type=optical)
Port at column 2, row 0: Port(self.name='o2', self.width=500, trans=r0 *1 50,0, layer=WG (1/0), port_type=optical)

Hierarchical nesting

Instances can point to cells that themselves contain instances. There is no practical depth limit — kfactory and KLayout handle deep hierarchy natively.

top = kf.KCell(name="top_level")
r1 = top << u_bend
r2 = top << u_bend
r3 = top << u_bend

r1.transform(kf.kdb.DTrans(0.0, 0.0))
r2.transform(kf.kdb.DTrans(30.0, 0.0))
r3.transform(kf.kdb.DTrans(60.0, 0.0))

top


png

Flattening non-Manhattan connections

When you connect at a non-90° angle the sub-cell boundary and the parent boundary can produce sub-nanometre gaps at the interface. Calling instance.flatten() merges the instance geometry into the parent, eliminating those gaps.

angled = kf.KCell(name="angled_connect")
b30a = angled << kf.cells.euler.bend_euler(radius=5, width=1, layer=L.WG, angle=30)
b30b = angled << kf.cells.euler.bend_euler(radius=5, width=1, layer=L.WG, angle=30)
b30b.connect("o1", b30a.ports["o2"])
b30b.flatten()  # merges geometry into parent to close sub-nm gaps
angled


png

Summary

Task API
Place a child cell parent.create_inst(child) or inst = parent << child
Reposition inst.transform(kf.kdb.DTrans(x, y))
Connect face-to-face inst.connect("port", other_port)
Connect with mirror inst.connect("port", other_port, mirror=True)
Regular array parent.create_inst(child, na=N, nb=M, a=vec_a, b=vec_b)
Access array port arr_inst["port_name", col, row]
Expose child ports parent.add_ports(inst.ports, prefix="…")
Flatten into parent inst.flatten()

See Also

Topic Where
Port system used by connect() Core Concepts: Ports
Routing between instance ports Routing: Overview
Assembling multi-cell components Components: Overview
Grid and tiling arrays of instances Utilities: Grid Layout