Component

A Component is like an empty canvas, where you can add polygons, references to other Components and ports (to connect to other components)

image0

based on phidl tutorial

Lets add a polygon

[1]:
import gdsfactory as gf

gf.asserts.version_equal_or_greater_than("3.2.3")

# Create a blank component (essentially an empty GDS cell with some special features)
c = gf.Component("myComponent")

# Create and add a polygon from separate lists of x points and y points
# (Can also be added like [(x1,y1), (x2,y2), (x3,y3), ... ]
poly1 = c.add_polygon(
    [(-8, 6, 7, 9), (-6, 8, 17, 5)], layer=1
)  # GDS layers are tuples of ints (but if we use only one number it assumes the other number is 0)

print(c)
c.plot()  # quickplot it!
c.show()  # show it in klayout (you need to have klayout open and install gdsfactory from the git repo with make install)
2021-09-25 16:18:31.603 | INFO     | gdsfactory.config:<module>:51 - 3.2.9
myComponent: uid 1, ports [], aliases [], 1 polygons, 0 references
../_images/notebooks_00_geometry_2_2.png

Exercise :

Make a component similar to the one above that has a second polygon in layer (1, 1)

[2]:
c = gf.Component("myComponent2")
# Create some new geometry from the functions available in the geometry library
t = gf.components.text("Hello!")
r = gf.components.rectangle(size=[5, 10], layer=(2, 0))

# Add references to the new geometry to c, our blank component
text1 = c.add_ref(t)  # Add the text we created as a reference
# Using the << operator (identical to add_ref()), add the same geometry a second time
text2 = c << t
r = c << r  # Add the rectangle we created

# Now that the geometry has been added to "c", we can move everything around:
text1.movey(25)
text2.move([5, 30])
text2.rotate(45)
r.movex(-15)
r.movex(-15)

print(c)
c.plot()  # plot it!
c.show()  # show it in klayout
myComponent2: uid 3, ports [], aliases [], 0 polygons, 3 references
../_images/notebooks_00_geometry_4_1.png

Connect ports

Any Component can have “Port”s in it which allow you to snap geometry together like legos.

Below is an example where we write a simple function to make a rectangular straight, assign ports to the ends of the rectangle, and then snap those rectangles together

[3]:
@gf.cell
def straight(length=10, width=1, layer=(1, 0)):
    WG = gf.Component(f"straight_{length}_{width}_{layer}")
    WG.add_polygon([(0, 0), (length, 0), (length, width), (0, width)], layer=layer)
    WG.add_port(name="o1", midpoint=[0, width / 2], width=width, orientation=180)
    WG.add_port(name="o2", midpoint=[length, width / 2], width=width, orientation=0)
    return WG


c = gf.Component("straights")

wg1 = c << straight(length=6, width=2.5, layer=1)
wg2 = c << straight(length=11, width=2.5, layer=2)
wg3 = c << straight(length=15, width=2.5, layer=3)
wg2.movey(10).rotate(10)
wg3.movey(20).rotate(15)

c
../_images/notebooks_00_geometry_6_0.png
[3]:
straights: uid 8, ports [], aliases [], 0 polygons, 3 references
[4]:
# Now we can connect everything together using the ports:

# Each straight has two ports: 'W0' and 'E0'.  These are arbitrary
# names defined in our straight() function above

# Let's keep wg1 in place on the bottom, and connect the other straights to it.
# To do that, on wg2 we'll grab the "W0" port and connect it to the "E0" on wg1:
wg2.connect("o1", wg1.ports["o2"])
# Next, on wg3 let's grab the "W0" port and connect it to the "E0" on wg2:
wg3.connect("o1", wg2.ports["o2"])

c
../_images/notebooks_00_geometry_7_0.png
[4]:
straights: uid 8, ports [], aliases [], 0 polygons, 3 references

Move and rotate references

There are several actions we can take to move and rotate the geometry.

These actions include movement, rotation, and reflection.

[5]:
c = gf.Component("straights")

wg1 = c << straight(length=1, layer=(1, 0))
wg2 = c << straight(length=2, layer=(2, 0))
wg3 = c << straight(length=3, layer=(3, 0))

# Create and add a polygon from separate lists of x points and y points
# e.g. [(x1, x2, x3, ...), (y1, y2, y3, ...)]
poly1 = c.add_polygon([(8, 6, 7, 9), (6, 8, 9, 5)])

# Alternatively, create and add a polygon from a list of points
# e.g. [(x1,y1), (x2,y2), (x3,y3), ...] using the same function
poly2 = c.add_polygon([(0, 0), (1, 1), (1, 3), (-3, 3)])


# Shift the first straight we created over by dx = 10, dy = 5
wg1.move([10, 5])
# Shift the second straight over by dx = 10, dy = 0
wg2.move(origin=[0, 0], destination=[10, 0])
# Shift the third straight over by dx = 4, dy = 4
wg3.move([1, 1], [5, 5], axis="y")

poly1.movey(4)  # Same as specifying axis='y' in the move() command
poly2.movex(4)  # Same as specifying axis='x'' in the move() command
wg3.movex(0, 10)
# Moves "from" x=0 "to" x=10 (dx=10)

# wg1.rotate(45) # Rotate the first straight by 45 degrees around (0,0)
# wg2.rotate(30, center = [1,1]) # Rotate the second straight by 30 degrees around (1,1)
# wg1.reflect(p1 = [1,1], p2 = [1,3]) # Reflects wg3 across the line formed by p1 and p2
c
../_images/notebooks_00_geometry_9_0.png
[5]:
straights: uid 12, ports [], aliases [], 2 polygons, 3 references

Ports

Although our straights wg1/wg2/wg3 have ports, they’re only references of the component c we’re working in, and c itself does not – it only draws the subports (ports of wg1, wg2, wg3) as a convenience. We need to add ports that we specifically want in our new component c. add_port() can take a port argument which allows you to pass it an underlying reference port to copy. You can also rename the port if you desire:

You can access the ports of a Component or ComponentReference

[6]:
wg2.ports
[6]:
{'o1': Port (name o1, midpoint [10.   0.5], width 1.0, orientation 180, layer (1, 0), port_type optical),
 'o2': Port (name o2, midpoint [12.   0.5], width 1.0, orientation 0, layer (1, 0), port_type optical)}

References

Now that we have our component c which is a multi-straight component, we can add references to that component in a new blank canvas we’ll call c2. We’ll add two copies of c to c2, and shift one so we can see them both

[7]:
c2 = gf.Component("MultiMultiWaveguide")
wg1 = straight()
wg2 = straight(layer=(2, 0))
mwg1_ref = c2.add_ref(wg1)
mwg2_ref = c2.add_ref(wg2)
mwg2_ref.move(destination=[10, 10])
c2
../_images/notebooks_00_geometry_14_0.png
[7]:
MultiMultiWaveguide: uid 16, ports [], aliases [], 0 polygons, 2 references
[8]:
# Like before, let's connect mwg1 and mwg2 together
mwg1_ref.connect(port="o2", destination=mwg2_ref.ports["o1"])
c2
../_images/notebooks_00_geometry_15_0.png
[8]:
MultiMultiWaveguide: uid 16, ports [], aliases [], 0 polygons, 2 references

Labels

We can also label (annotate) our devices, in order to record information directly into the final GDS file without putting any extra geometry onto any layer This label will display in a GDS viewer, but will not be rendered or printed like the polygons created by gf.components.text(). You can use for example gf.show() to see the labels in Klayout

[9]:
c2.add_label(text="First label", position=mwg1_ref.center)
c2.add_label("Second label", mwg2_ref.center)

# It's very useful for recording information about the devices or layout
c2.add_label(
    text=f"The x size of this\nlayout is {c2.xsize}",
    position=(c2.xmax, c2.ymax),
    layer=1,
)

# Again, note we have to write the GDS for it to be visible (view in KLayout)
c2.write_gds("MultiMultiWaveguideWithLabels.gds")
c2
../_images/notebooks_00_geometry_17_0.png
[9]:
MultiMultiWaveguide: uid 16, ports [], aliases [], 0 polygons, 2 references

Boolean shapes

If you want to subtract one shape from another, merge two shapes, or perform an XOR on them, you can do that with the gf.boolean() function.

The operation argument should be {not, and, or, xor, ‘A-B’, ‘B-A’, ‘A+B’}. Note that ‘A+B’ is equivalent to ‘or’, ‘A-B’ is equivalent to ‘not’, and ‘B-A’ is equivalent to ‘not’ with the operands switched

[10]:
import gdsfactory as gf

c = gf.Component()
e1 = c.add_ref(gf.components.ellipse(layer=(0, 0)))
e2 = c.add_ref(gf.components.ellipse(radii=(10, 6), layer=(0, 0))).movex(2)
e3 = c.add_ref(gf.components.ellipse(radii=(10, 4), layer=(0, 0))).movex(5)
gf.plot(c)
../_images/notebooks_00_geometry_19_0.png
[11]:
c = gf.boolean(A=[e1, e3], B=e2, operation="A-B")
c.plot()
c.show()
../_images/notebooks_00_geometry_20_0.png
[12]:
import gdsfactory as gf

c = gf.Component("rectangle_with_label")
r = c << gf.components.rectangle(size=(1, 1))
r.x = 0
r.y = 0
c.add_label(
    text=f"Demo label",
    position=(0, 0),
    layer=(1, 0),
)
c.plot()
c.show()
../_images/notebooks_00_geometry_21_0.png

Move Reference by port

[13]:
c = gf.Component("ref_port_sample")
w1ref = c << gf.components.straight(width=1)
w2 = gf.components.straight(width=2)
[14]:
w2.ref?
[15]:
w2ref = w2.ref(w1ref.ports["o2"], port_id="o1")
[16]:
c.add(w2ref)
../_images/notebooks_00_geometry_26_0.png
[16]:
ref_port_sample: uid 29, ports [], aliases [], 0 polygons, 2 references
[17]:
c = gf.Component()
w2ref = w2.ref((10, 10), port_id="o1")
c.add(w2ref)
c
../_images/notebooks_00_geometry_27_0.png
[17]:
Unnamed_847a60f3: uid 32, ports [], aliases [], 0 polygons, 1 references
[18]:
c = gf.Component()
w2ref = w2.ref((50, 20), port_id="o2")
c.add(w2ref)
c
../_images/notebooks_00_geometry_28_0.png
[18]:
Unnamed_c26e6738: uid 33, ports [], aliases [], 0 polygons, 1 references

Mirror reference

By default the mirror works along the x=0 axis.

[19]:
c = gf.Component("mirror_mmi")
mmi = gf.components.mmi1x2()
mmi_ref = c << mmi
c
../_images/notebooks_00_geometry_30_0.png
[19]:
mirror_mmi: uid 34, ports [], aliases [], 0 polygons, 1 references
[20]:
mmi_ref.mirror()
c
../_images/notebooks_00_geometry_31_0.png
[20]:
mirror_mmi: uid 34, ports [], aliases [], 0 polygons, 1 references

You can also specify any other axis

[21]:
c = gf.Component("mirror_bend")
b = gf.components.bend_circular()
b_ref = c << b
c
../_images/notebooks_00_geometry_33_0.png
[21]:
mirror_bend: uid 38, ports [], aliases [], 0 polygons, 1 references
[22]:
b_ref.mirror?
[23]:
b_ref.mirror(p1=(0, 0), p2=(1, 0))
c
../_images/notebooks_00_geometry_35_0.png
[23]:
mirror_bend: uid 38, ports [], aliases [], 0 polygons, 1 references
[24]:
b_ref.ports
[24]:
{'o1': Port (name o1, midpoint [6.123234e-16 0.000000e+00], width 0.5, orientation 180.0, layer (1, 0), port_type optical),
 'o2': Port (name o2, midpoint [ 10. -10.], width 0.5, orientation 270.0, layer (1, 0), port_type optical)}

Write GDS

GDSII is the Standard format for exchanging CMOS and Photonic circuits.

Lets export your component to GDS.

[25]:
c = gf.components.straight(layer=gf.LAYER.SLAB90)
c
../_images/notebooks_00_geometry_38_0.png
[25]:
straight_layer3_0: uid 40, ports ['o1', 'o2'], aliases [], 1 polygons, 0 references
[26]:
c.write_gds("straight.gds")
[26]:
PosixPath('straight.gds')

You can see the GDS file in Klayout viewer.

Sometimes you also want to save the GDS together with metadata (settings, port names, widths, locations …)

[27]:
c.write_gds_with_metadata("straight.gds")
[27]:
PosixPath('straight.gds')
[ ]: