References and ports#
gdsfactory defines your component once in memory and can add multiple References (Instances) to the same component.
As you build components you can include references to other components. Adding a reference is like having a pointer to a component.
The GDSII specification allows the use of references, and similarly gdsfactory uses them (with the add_ref()
function).
what is a reference? Simply put: A reference does not contain any geometry. It only points to an existing geometry.
Say you have a ridiculously large polygon with 100 billion vertices that you call BigPolygon. It’s huge, and you need to use it in your design 250 times. Well, a single copy of BigPolygon takes up 1MB of memory, so you don’t want to make 250 copies of it You can instead references the polygon 250 times. Each reference only uses a few bytes of memory – it only needs to know the memory address of BigPolygon, position, rotation and mirror. This way, you can keep one copy of BigPolygon and use it again and again.
You can start by making a blank Component
and add a single polygon to it.
import gdsfactory as gf
# Create a blank Component
p = gf.Component("component_with_polygon")
# Add a polygon
xpts = [0, 0, 5, 6, 9, 12]
ypts = [0, 1, 1, 2, 2, 0]
p.add_polygon([xpts, ypts], layer=(2, 0))
# plot the Component with the polygon in it
p.plot()
Now, you want to reuse this polygon repeatedly without creating multiple copies of it.
To do so, you need to make a second blank Component
, this time called c
.
In this new Component you reference our Component p
which contains our polygon.
c = gf.Component("Component_with_references") # Create a new blank Component
poly_ref = c.add_ref(p) # Reference the Component "p" that has the polygon in it
c.plot()
you just made a copy of your polygon – but remember, you didn’t actually
make a second polygon, you just made a reference (aka pointer) to the original
polygon. Let’s add two more references to c
:
poly_ref2 = c.add_ref(p) # Reference the Component "p" that has the polygon in it
poly_ref3 = c.add_ref(p) # Reference the Component "p" that has the polygon in it
c.plot()
Now you have 3x polygons all on top of each other. Again, this would appear
useless, except that you can manipulate each reference independently. Notice that
when you called c.add_ref(p)
above, we saved the result to a new variable each
time (poly_ref
, poly_ref2
, and poly_ref3
)? You can use those variables to
reposition the references.
poly_ref2.rotate(90) # Rotate the 2nd reference we made 90 degrees
poly_ref3.rotate(180) # Rotate the 3rd reference we made 180 degrees
c.plot()
Now you’re getting somewhere! You’ve only had to make the polygon once, but you’re able to reuse it as many times as you want.
Modifying the referenced geometry#
What happens when you change the original geometry that the reference points to? In your case, your references in
c
all point to the Component p
that with the original polygon. Let’s try
adding a second polygon to p
.
First you add the second polygon and make sure P
looks like you expect:
# Add a 2nd polygon to "p"
xpts = [14, 14, 16, 16]
ypts = [0, 2, 2, 0]
p.add_polygon([xpts, ypts], layer=(1, 0))
p
2024-10-31 18:38:06.736 | WARNING | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
component_with_polygon: uid eaae961a, ports [], references [], 2 polygons
That looks good. Now let’s find out what happened to c
that contains the
three references. Keep in mind that you have not modified c
or executed any
functions/operations on c
– all you have done is modify p
.
c.plot()
When you modify the original geometry, all of the references automatically reflect the modifications. This is very powerful, because you can use this to make very complicated designs from relatively simple elements in a computation- and memory-efficient way.
Let’s try making references a level deeper by referencing c
. Note here we use
the <<
operator to add the references – this is just shorthand, and is
exactly equivalent to using add_ref()
c2 = gf.Component("array_sample") # Create a new blank Component
d_ref1 = c2.add_ref(c) # Reference the Component "c" that 3 references in it
d_ref2 = c2 << c # Use the "<<" operator to create a 2nd reference to c.plot()
d_ref3 = c2 << c # Use the "<<" operator to create a 3rd reference to c.plot()
d_ref1.move([20, 0])
d_ref2.move([40, 0])
c2
array_sample: uid 5c4454ae, ports [], references ['Component_with_references_1', 'Component_with_references_2', 'Component_with_references_3'], 0 polygons
As you’ve seen you have two ways to add a reference to our component:
create the reference and add it to the component
c = gf.Component("reference_sample")
w = gf.components.straight(width=0.6)
wr = w.ref()
c.add(wr)
c.plot()
or do it in a single line
c = gf.Component("reference_sample_shorter_syntax")
wr = c << gf.components.straight(width=0.6)
c.plot()
in both cases you can move the reference wr
after created
c = gf.Component("two_references")
wr1 = c << gf.components.straight(width=0.6)
wr2 = c << gf.components.straight(width=0.6)
wr2.movey(10)
c.add_ports(wr1.get_ports_list(), prefix="bot_")
c.add_ports(wr2.get_ports_list(), prefix="top_")
c.ports
{'bot_o1': {'name': 'bot_o1', 'width': 0.6, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
'bot_o2': {'name': 'bot_o2', 'width': 0.6, 'center': [10.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
'top_o1': {'name': 'top_o1', 'width': 0.6, 'center': [0.0, 10.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
'top_o2': {'name': 'top_o2', 'width': 0.6, 'center': [10.0, 10.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}
You can also auto_rename ports using gdsfactory default convention, where ports are numbered clockwise starting from the bottom left
c.auto_rename_ports()
c.ports
{'o1': {'name': 'o1', 'width': 0.6, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
'o4': {'name': 'o4', 'width': 0.6, 'center': [10.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
'o2': {'name': 'o2', 'width': 0.6, 'center': [0.0, 10.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
'o3': {'name': 'o3', 'width': 0.6, 'center': [10.0, 10.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}
c.plot()
Arrays of references#
In GDS, there’s a type of structure called a “ComponentReference” which takes a cell and repeats it NxM times on a fixed grid spacing. For convenience, Component
includes this functionality with the add_array() function.
Note that CellArrays are not compatible with ports (since there is no way to access/modify individual elements in a GDS cellarray)
gdsfactory also provides with more flexible arrangement options if desired, see for example grid()
and packer()
.
As well as gf.components.array
Let’s make a new Component and put a big array of our Component c
in it:
c3 = gf.Component("array_of_references") # Create a new blank Component
aref = c3.add_array(
c, columns=6, rows=3, spacing=[20, 15]
) # Reference the Component "c" 3 references in it with a 3 rows, 6 columns array
c3
array_of_references: uid 848cf3f1, ports [], references ['two_references_1'], 0 polygons
CellArrays don’t have ports and there is no way to access/modify individual elements in a GDS cellarray.
gdsfactory provides you with similar functions in gf.components.array
and gf.components.array_2d
c4 = gf.Component("demo_array") # Create a new blank Component
aref = c4 << gf.components.array(component=c, columns=3, rows=2)
c4.add_ports(aref.get_ports_list())
c4
demo_array: uid 5bef20b5, ports ['o1_1_1', 'o1_1_2', 'o1_1_3', 'o2_1_1', 'o2_1_2', 'o2_1_3', 'o1_2_1', 'o1_2_2', 'o1_2_3', 'o2_2_1', 'o2_2_2', 'o2_2_3', 'o3_2_1', 'o3_2_2', 'o3_2_3', 'o4_2_1', 'o4_2_2', 'o4_2_3', 'o3_1_1', 'o3_1_2', 'o3_1_3', 'o4_1_1', 'o4_1_2', 'o4_1_3'], references ['array_1'], 0 polygons
help(gf.components.array)
Help on function array in module gdsfactory.components.array_component:
array(component: 'ComponentSpec' = 'pad', spacing: 'tuple[float, float]' = (150.0, 150.0), columns: 'int' = 6, rows: 'int' = 1, add_ports: 'bool' = True, size: 'Float2 | None' = None, centered: 'bool' = False) -> gdsfactory.component.Component
Returns an array of components.
Args:
component: to replicate.
spacing: x, y spacing.
columns: in x.
rows: in y.
add_ports: add ports from component into the array.
size: Optional x, y size. Overrides columns and rows.
centered: center the array around the origin.
Raises:
ValueError: If columns > 1 and spacing[0] = 0.
ValueError: If rows > 1 and spacing[1] = 0.
.. code::
2 rows x 4 columns
___ ___ ___ ___
| | | | | | | |
|___| |___| |___| |___|
___ ___ ___ ___
| | | | | | | |
|___| |___| |___| |___|
You can also create an array of references for periodic structures. Let’s create a Distributed Bragg Reflector
@gf.cell
def dbr_period(w1=0.5, w2=0.6, l1=0.2, l2=0.4, straight=gf.components.straight):
"""Return one DBR period."""
c = gf.Component()
r1 = c << straight(length=l1, width=w1)
r2 = c << straight(length=l2, width=w2)
r2.connect(port="o1", destination=r1.ports["o2"], allow_width_mismatch=True)
c.add_port("o1", port=r1.ports["o1"])
c.add_port("o2", port=r2.ports["o2"])
return c
l1 = 0.2
l2 = 0.4
n = 3
period = dbr_period(l1=l1, l2=l2)
period
dbr_period: uid 11113ce7, ports ['o1', 'o2'], references ['straight_1', 'straight_2'], 0 polygons
dbr = gf.Component("DBR")
dbr.add_array(period, columns=n, rows=1, spacing=(l1 + l2, 100))
dbr
DBR: uid 91f55f80, ports [], references ['dbr_period_1'], 0 polygons
Finally we need to add ports to the new component
p0 = dbr.add_port("o1", port=period.ports["o1"])
p1 = dbr.add_port("o2", port=period.ports["o2"])
p1.center = [(l1 + l2) * n, 0]
dbr
DBR: uid 91f55f80, ports ['o1', 'o2'], references ['dbr_period_1'], 0 polygons
Connect references#
We have seen that once you create a reference you can manipulate the reference to move it to a location. Here we are going to connect that reference to a port. Remember that we follow that a certain reference source
connects to a destination
port
bend = gf.components.bend_circular()
bend
bend_circular: uid 0e68e781, ports ['o1', 'o2'], references [], 1 polygons
c = gf.Component("sample_reference_connect")
mmi = c << gf.components.mmi1x2()
b = c << gf.components.bend_circular()
b.connect("o1", destination=mmi.ports["o2"])
c.add_port("o1", port=mmi.ports["o1"])
c.add_port("o2", port=b.ports["o2"])
c.add_port("o3", port=mmi.ports["o3"])
c.plot()
You can also access the ports as reference[port_name]
instead of reference.ports[port_name]
c = gf.Component("sample_reference_connect_simpler")
mmi = c << gf.components.mmi1x2()
b = c << gf.components.bend_circular()
b.connect("o1", destination=mmi["o2"])
c.add_port("o1", port=mmi["o1"])
c.add_port("o2", port=b["o2"])
c.add_port("o3", port=mmi["o3"])
c.plot()
c = gf.Component("sample_reference_connect_simpler")
mmi = c << gf.components.mmi1x2()
b = c.add_ref(gf.components.bend_circular()).connect("o1", destination=mmi["o2"])
c.add_port("o1", port=mmi["o1"])
c.add_port("o2", port=b["o2"])
c.add_port("o3", port=mmi["o3"])
c.plot()
c = gf.Component("sample_reference_connect_simpler_with_mirror")
mmi = c << gf.components.mmi1x2()
b = (
c.add_ref(gf.components.bend_circular())
.mirror()
.connect("o1", destination=mmi["o2"])
)
c.add_port("o1", port=mmi["o1"])
c.add_port("o2", port=b["o2"])
c.add_port("o3", port=mmi["o3"])
c.plot()
Notice that connect
mates two ports together and does not imply that ports will remain connected.
Port#
You can name the ports as you want and use gf.port.auto_rename_ports(prefix='o')
to rename them later on.
Here is the default naming convention.
Ports are numbered clock-wise starting from the bottom left corner.
Optical ports have o
prefix and Electrical ports e
prefix.
The port naming comes in most cases from the gdsfactory.cross_section
. For example:
gdsfactory.cross_section.strip
has portso1
for input ando2
for output.gdsfactory.cross_section.metal1
has portse1
for input ande2
for output.
size = 4
c = gf.components.nxn(west=2, south=2, north=2, east=2, xsize=size, ysize=size)
c.plot()
c = gf.components.straight_heater_metal(length=90)
c.plot()
c.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ name ┃ width ┃ center ┃ orientation ┃ layer ┃ port_type ┃ ┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩ │ l_e1 │ 11.0 │ [-15.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ l_e2 │ 11.0 │ [-10.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ l_e3 │ 11.0 │ [-4.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ l_e4 │ 11.0 │ [-10.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ │ o1 │ 0.5 │ [0.0, 0.0] │ 180.0 │ [1, 0] │ optical │ │ o2 │ 0.5 │ [90.0, 0.0] │ 0.0 │ [1, 0] │ optical │ │ r_e1 │ 11.0 │ [94.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ r_e2 │ 11.0 │ [100.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ r_e3 │ 11.0 │ [105.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ r_e4 │ 11.0 │ [100.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ └──────┴───────┴───────────────┴─────────────┴─────────┴────────────┘
You can get the optical ports by layer
c.get_ports_dict(layer=(1, 0))
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
'o2': {'name': 'o2', 'width': 0.5, 'center': [90.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}
or by width
c.get_ports_dict(width=0.5)
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 0.0], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
'o2': {'name': 'o2', 'width': 0.5, 'center': [90.0, 0.0], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'}}
c0 = gf.components.straight_heater_metal()
c0.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ name ┃ width ┃ center ┃ orientation ┃ layer ┃ port_type ┃ ┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩ │ l_e1 │ 11.0 │ [-15.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ l_e2 │ 11.0 │ [-10.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ l_e3 │ 11.0 │ [-4.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ l_e4 │ 11.0 │ [-10.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ │ o1 │ 0.5 │ [0.0, 0.0] │ 180.0 │ [1, 0] │ optical │ │ o2 │ 0.5 │ [320.0, 0.0] │ 0.0 │ [1, 0] │ optical │ │ r_e1 │ 11.0 │ [324.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ r_e2 │ 11.0 │ [330.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ r_e3 │ 11.0 │ [335.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ r_e4 │ 11.0 │ [330.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ └──────┴───────┴───────────────┴─────────────┴─────────┴────────────┘
c1 = c0.copy()
c1.auto_rename_ports_layer_orientation()
c1.pprint_ports()
┏━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ name ┃ width ┃ center ┃ orientation ┃ layer ┃ port_type ┃ ┡━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩ │ 49_0_E0 │ 11.0 │ [-4.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ 49_0_E1 │ 0.5 │ [320.0, 0.0] │ 0.0 │ [1, 0] │ optical │ │ 49_0_E2 │ 11.0 │ [335.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ 49_0_N0 │ 11.0 │ [-10.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ 49_0_N1 │ 11.0 │ [330.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ 49_0_S0 │ 11.0 │ [-10.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ │ 49_0_S1 │ 11.0 │ [330.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ │ 49_0_W0 │ 11.0 │ [-15.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ 49_0_W1 │ 0.5 │ [0.0, 0.0] │ 180.0 │ [1, 0] │ optical │ │ 49_0_W2 │ 11.0 │ [324.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ └─────────┴───────┴───────────────┴─────────────┴─────────┴────────────┘
c2 = c0.copy()
c2.auto_rename_ports()
c2.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━┓ ┃ name ┃ width ┃ center ┃ orientation ┃ layer ┃ port_type ┃ ┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━┩ │ e1 │ 11.0 │ [-15.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ e2 │ 11.0 │ [324.9, 0.0] │ 180.0 │ [49, 0] │ electrical │ │ e3 │ 11.0 │ [-10.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ e4 │ 11.0 │ [330.4, 5.5] │ 90.0 │ [49, 0] │ electrical │ │ e5 │ 11.0 │ [-4.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ e6 │ 11.0 │ [335.9, 0.0] │ 0.0 │ [49, 0] │ electrical │ │ e7 │ 11.0 │ [330.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ │ e8 │ 11.0 │ [-10.4, -5.5] │ 270.0 │ [49, 0] │ electrical │ │ o1 │ 0.5 │ [0.0, 0.0] │ 180.0 │ [1, 0] │ optical │ │ o2 │ 0.5 │ [320.0, 0.0] │ 0.0 │ [1, 0] │ optical │ └──────┴───────┴───────────────┴─────────────┴─────────┴────────────┘
You can also rename them with a different port naming convention
prefix: add
e
for electricalo
for opticalclockwise
counter-clockwise
orientation
E
East,W
West,N
North,S
South
Here is the default one we use (clockwise starting from bottom left west facing port)
3 4
|___|_
2 -| |- 5
| |
1 -|______|- 6
| |
8 7
c = gf.Component("demo_ports")
nxn = gf.components.nxn(west=2, north=2, east=2, south=2, xsize=4, ysize=4)
ref = c.add_ref(nxn)
c.add_ports(ref.ports)
c.plot()
ref.get_ports_list() # by default returns ports clockwise starting from bottom left west facing port
[{'name': 'o1', 'width': 0.5, 'center': [0.0, 1.25], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o2', 'width': 0.5, 'center': [0.0, 2.75], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o3', 'width': 0.5, 'center': [1.25, 4.0], 'orientation': 90.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o4', 'width': 0.5, 'center': [2.75, 4.0], 'orientation': 90.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o5', 'width': 0.5, 'center': [4.0, 2.75], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o6', 'width': 0.5, 'center': [4.0, 1.25], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o7', 'width': 0.5, 'center': [2.75, 0.0], 'orientation': 270.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o8', 'width': 0.5, 'center': [1.25, 0.0], 'orientation': 270.0, 'layer': [1, 0], 'port_type': 'optical'}]
c.auto_rename_ports()
c.plot()
You can also get the ports counter-clockwise
4 3
|___|_
5 -| |- 2
| |
6 -|______|- 1
| |
7 8
c.auto_rename_ports_counter_clockwise()
c.plot()
c.get_ports_list(clockwise=False)
[{'name': 'o1', 'width': 0.5, 'center': [4.0, 1.25], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o2', 'width': 0.5, 'center': [4.0, 2.75], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o3', 'width': 0.5, 'center': [2.75, 4.0], 'orientation': 90.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o4', 'width': 0.5, 'center': [1.25, 4.0], 'orientation': 90.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o5', 'width': 0.5, 'center': [0.0, 2.75], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o6', 'width': 0.5, 'center': [0.0, 1.25], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o7', 'width': 0.5, 'center': [1.25, 0.0], 'orientation': 270.0, 'layer': [1, 0], 'port_type': 'optical'},
{'name': 'o8', 'width': 0.5, 'center': [2.75, 0.0], 'orientation': 270.0, 'layer': [1, 0], 'port_type': 'optical'}]
c.ports_layer
{'1_0_W0': 'o6',
'1_0_W1': 'o5',
'o1': 'o1',
'o2': 'o2',
'1_0_N0': 'o4',
'1_0_N1': 'o3',
'1_0_S0': 'o7',
'1_0_S1': 'o8'}
c.port_by_orientation_cw("W0")
{'name': 'o6', 'width': 0.5, 'center': [0.0, 1.25], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'}
c.port_by_orientation_ccw("W1")
{'name': 'o6', 'width': 0.5, 'center': [0.0, 1.25], 'orientation': 180.0, 'layer': [1, 0], 'port_type': 'optical'}
Lets extend the East facing ports (orientation = 0 deg)
cross_section = gf.cross_section.strip()
nxn = gf.components.nxn(
west=2, north=2, east=2, south=2, xsize=4, ysize=4, cross_section=cross_section
)
c = gf.components.extension.extend_ports(component=nxn, orientation=0)
c.plot()
c.ports
{'o1': {'name': 'o1', 'width': 0.5, 'center': [0.0, 1.25], 'orientation': 180, 'layer': [1, 0], 'port_type': 'optical'},
'o2': {'name': 'o2', 'width': 0.5, 'center': [0.0, 2.75], 'orientation': 180, 'layer': [1, 0], 'port_type': 'optical'},
'o3': {'name': 'o3', 'width': 0.5, 'center': [1.25, 4.0], 'orientation': 90, 'layer': [1, 0], 'port_type': 'optical'},
'o4': {'name': 'o4', 'width': 0.5, 'center': [2.75, 4.0], 'orientation': 90, 'layer': [1, 0], 'port_type': 'optical'},
'o5': {'name': 'o5', 'width': 0.5, 'center': [9.0, 2.75], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
'o6': {'name': 'o6', 'width': 0.5, 'center': [9.0, 1.25], 'orientation': 0.0, 'layer': [1, 0], 'port_type': 'optical'},
'o7': {'name': 'o7', 'width': 0.5, 'center': [2.75, 0.0], 'orientation': 270, 'layer': [1, 0], 'port_type': 'optical'},
'o8': {'name': 'o8', 'width': 0.5, 'center': [1.25, 0.0], 'orientation': 270, 'layer': [1, 0], 'port_type': 'optical'}}
df = c.get_ports_pandas()
df
name | width | center | orientation | layer | port_type | shear_angle | |
---|---|---|---|---|---|---|---|
0 | o1 | 0.5 | [0.0, 1.25] | 180.0 | [1, 0] | optical | None |
1 | o2 | 0.5 | [0.0, 2.75] | 180.0 | [1, 0] | optical | None |
2 | o3 | 0.5 | [1.25, 4.0] | 90.0 | [1, 0] | optical | None |
3 | o4 | 0.5 | [2.75, 4.0] | 90.0 | [1, 0] | optical | None |
4 | o5 | 0.5 | [9.0, 2.75] | 0.0 | [1, 0] | optical | None |
5 | o6 | 0.5 | [9.0, 1.25] | 0.0 | [1, 0] | optical | None |
6 | o7 | 0.5 | [2.75, 0.0] | 270.0 | [1, 0] | optical | None |
7 | o8 | 0.5 | [1.25, 0.0] | 270.0 | [1, 0] | optical | None |
df[df.port_type == "optical"]
name | width | center | orientation | layer | port_type | shear_angle | |
---|---|---|---|---|---|---|---|
0 | o1 | 0.5 | [0.0, 1.25] | 180.0 | [1, 0] | optical | None |
1 | o2 | 0.5 | [0.0, 2.75] | 180.0 | [1, 0] | optical | None |
2 | o3 | 0.5 | [1.25, 4.0] | 90.0 | [1, 0] | optical | None |
3 | o4 | 0.5 | [2.75, 4.0] | 90.0 | [1, 0] | optical | None |
4 | o5 | 0.5 | [9.0, 2.75] | 0.0 | [1, 0] | optical | None |
5 | o6 | 0.5 | [9.0, 1.25] | 0.0 | [1, 0] | optical | None |
6 | o7 | 0.5 | [2.75, 0.0] | 270.0 | [1, 0] | optical | None |
7 | o8 | 0.5 | [1.25, 0.0] | 270.0 | [1, 0] | optical | None |
Port markers (Pins)#
You can add pins (port markers) to each port. Different foundries do this differently, so gdsfactory supports all of them.
square with port inside the component.
square centered (half inside, half outside component).
triangular pointing towards the outside of the port.
path (SiEPIC).
by default Component.show() will add triangular pins, so you can see the direction of the port in Klayout.
gf.components.mmi1x2()
2024-10-31 18:38:10.607 | WARNING | gdsfactory.klive:show:49 - UserWarning: Could not connect to klive server. Is klayout open and klive plugin installed?
mmi1x2: uid 1a93cd73, ports ['o1', 'o2', 'o3'], references [], 4 polygons
mmi = gf.components.mmi1x2()
mmi_with_pins = gf.add_pins.add_pins_container(mmi)
mmi_with_pins.plot()
Component_sequence#
When you have repetitive connections you can describe the connectivity as an ASCII map
bend180 = gf.components.bend_circular180()
wg_pin = gf.components.straight_pin(length=40)
wg = gf.components.straight()
# Define a map between symbols and (component, input port, output port)
symbol_to_component = {
"D": (bend180, "o1", "o2"),
"C": (bend180, "o2", "o1"),
"P": (wg_pin, "o1", "o2"),
"-": (wg, "o1", "o2"),
}
# Generate a sequence
# This is simply a chain of characters. Each of them represents a component
# with a given input and and a given output
sequence = "DC-P-P-P-P-CD"
component = gf.components.component_sequence(
sequence=sequence, symbol_to_component=symbol_to_component
)
component.name = "component_sequence"
component.plot()
As the sequence is defined as a string you can use the string operations to easily build complex sequences
Movement#
You can move, rotate and mirror ComponentReference as well as Port
, Polygon
, ComponentReference
, Label
, and Group
import gdsfactory as gf
# Start with a blank Component
c = gf.Component("demo_movement")
# Create some more Components with shapes
T = gf.components.text("hello", size=10, layer=(1, 0))
E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
R = gf.components.rectangle(size=(10, 3), layer=(3, 0))
# Add the shapes to c as references
text = c << T
ellipse = c << E
rect1 = c << R
rect2 = c << R
c.plot()
c = gf.Component("move_one_ellipse")
e1 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e2 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1.movex(10)
c.plot()
c = gf.Component("move_one_ellipse_xmin")
e1 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e2 = c << gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e2.xmin = e1.xmax
c.plot()
# Now you can practice move and rotate the objects.
c = gf.Component("two_ellipses_on_top_of_each_other")
E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << E
e2 = c << E
c.plot()
c = gf.Component("ellipse_moved")
e = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << e
e2 = c << e
e2.move(origin=[5, 5], destination=[10, 10]) # Translate by dx = 5, dy = 5
c.plot()
c = gf.Component("ellipse_moved_v2")
e = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << e
e2 = c << e
e2.move([5, 5]) # Translate by dx = 5, dy = 5
c.plot()
c = gf.Component("rectangles")
r = gf.components.rectangle(size=(10, 5), layer=(2, 0))
rect1 = c << r
rect2 = c << r
rect1.rotate(45) # Rotate the first straight by 45 degrees around (0,0)
rect2.rotate(
-30, center=[1, 1]
) # Rotate the second straight by -30 degrees around (1,1)
c.plot()
2024-10-31 18:38:12.075 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component rectangles has invalid transformations. Try component.flatten_offgrid_references() first.
c = gf.Component("mirror_demo")
text = c << gf.components.text("hello")
text.mirror(p1=[1, 1], p2=[1, 3]) # Reflects across the line formed by p1 and p2
c.plot()
c = gf.Component("hello")
text = c << gf.components.text("hello")
c.plot()
Each Component and ComponentReference object has several properties which can be used to learn information about the object (for instance where it’s center coordinate is). Several of these properties can actually be used to move the geometry by assigning them new values.
Available properties are:
xmin
/xmax
: minimum and maximum x-values of all points within the objectymin
/ymax
: minimum and maximum y-values of all points within the objectx
: centerpoint between minimum and maximum x-values of all points within the objecty
: centerpoint between minimum and maximum y-values of all points within the objectbbox
: bounding box (see note below) in format ((xmin,ymin),(xmax,ymax))center
: center of bounding box
print("bounding box:")
print(
text.bbox
) # Will print the bounding box of text in terms of [(xmin, ymin), (xmax, ymax)]
print("xsize and ysize:")
print(text.xsize) # Will print the width of text in the x dimension
print(text.ysize) # Will print the height of text in the y dimension
print("center:")
print(text.center) # Gives you the center coordinate of its bounding box
print("xmax")
print(text.xmax) # Gives you the rightmost (+x) edge of the text bounding box
# Let's use these properties to manipulate our shapes to arrange them a little
# better
bounding box:
[[ 0. 0.]
[33. 11.]]
xsize and ysize:
33.0
11.0
center:
[16.5 5.5]
xmax
33.0
c = gf.Component("canvas")
text = c << gf.components.text("hello")
E = gf.components.ellipse(radii=(10, 5), layer=(3, 0))
R = gf.components.rectangle(size=(10, 5), layer=(2, 0))
rect1 = c << R
rect2 = c << R
ellipse = c << E
c.plot()
ellipse.center = [
0,
0,
] # Move the ellipse such that the bounding box center is at (0,0)
# Next, let's move the text to the left edge of the ellipse
text.y = (
ellipse.y
) # Move the text so that its y-center is equal to the y-center of the ellipse
text.xmax = ellipse.xmin # Moves the ellipse so its xmax == the ellipse's xmin
# Align the right edge of the rectangles with the x=0 axis
rect1.xmax = 0
rect2.xmax = 0
# Move the rectangles above and below the ellipse
rect1.ymin = ellipse.ymax + 5
rect2.ymax = ellipse.ymin - 5
c.plot()
# In addition to working with the properties of the references inside the
# Component,
# we can also manipulate the whole Component if we want. Let's try mirroring the
# whole Component `c`:
print(c.xmax) # Prints out '10.0'
c2 = c.mirror((0, 1)) # Mirror across line made by (0,0) and (0,1)
c2.plot()
10.0
# A bounding box is the smallest enclosing box which contains all points of the geometry.
c = gf.Component("hi_bbox")
text = c << gf.components.text("hi")
bbox = text.bbox
c << gf.components.bbox(bbox=bbox, layer=(2, 0))
c.plot()
c = gf.Component("sample_padding")
text = c << gf.components.text("bye")
device_bbox = text.bbox
c.add_polygon(gf.get_padding_points(text, default=1), layer=(2, 0))
c.plot()
# When we query the properties of c, they will be calculated with respect to this bounding-rectangle. For instance:
print("Center of Component c:")
print(c.center)
print("X-max of Component c:")
print(c.xmax)
Center of Component c:
[11. 3.5]
X-max of Component c:
23.0
c = gf.Component("rect")
R = gf.components.rectangle(size=(10, 3), layer=(2, 0))
rect1 = c << R
c.plot()
# You can chain many of the movement/manipulation functions because they all return the object they manipulate.
# For instance you can combine two expressions:
rect1.rotate(angle=37)
rect1.move([10, 20])
c.plot()
2024-10-31 18:38:13.596 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component rect has invalid transformations. Try component.flatten_offgrid_references() first.
# ...into this single-line expression
c = gf.Component("single_expression")
R = gf.components.rectangle(size=(10, 3), layer=(2, 0))
rect1 = c << R
rect1.rotate(angle=37).move([10, 20])
c.plot()
2024-10-31 18:38:13.762 | WARNING | gdsfactory.component:_write_library:1934 - UserWarning: Component single_expression has invalid transformations. Try component.flatten_offgrid_references() first.