Instances and ports#

gdsfactory defines your component once in memory and can add multiple Instances (references) to the same component.

As you build components you can include references to other components. Adding an instance is like having a pointer to a component.

The GDSII specification allows the use of instances, and similarly gdsfactory uses them (with the add_ref() function). what is an instance? Simply put: An instance 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 instantiate the polygon 250 times. Each instance 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()

# Add a polygon
xpts = [0, 0, 5, 6, 9, 12]
ypts = [0, 1, 1, 2, 2, 0]
p.add_polygon(list(zip(xpts, ypts)), layer=(2, 0))

# plot the Component with the polygon in it
p.plot()
../_images/26335f37f496c3d1010c32523749349c80249223078da1b3c832faedd0c8e761.png

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 instantiate our Component p which contains our polygon.

c = gf.Component()  # Create a new blank Component
poly_ref = c.add_ref(p)  # instantiate the Component "p" that has the polygon in it
c.plot()
../_images/26335f37f496c3d1010c32523749349c80249223078da1b3c832faedd0c8e761.png

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)  # instantiate the Component "p" that has the polygon in it
poly_ref3 = c.add_ref(p)  # instantiate the Component "p" that has the polygon in it
c.plot()
../_images/26335f37f496c3d1010c32523749349c80249223078da1b3c832faedd0c8e761.png

Now you have 3x polygons all on top of each other. Again, this would appear useless, except that you can manipulate each instance 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 instances.

poly_ref2.drotate(90)  # Rotate the 2nd instance we made 90 degrees
poly_ref3.drotate(180)  # Rotate the 3rd instance we made 180 degrees
c.plot()
../_images/5ce153b4e91a74da162083c407b708fa778a39979ee86644c20eccb5363f2bf6.png

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 instance#

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(list(zip(xpts, ypts)), layer=(1, 0))
p
../_images/b94d17ac8ba7b344b2de5d8ba88b89ab8f4a4b68cc2d0567bbb0e7387e163907.png

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()
../_images/65ce54fe5f0740bb657975b2afb7185d3b782493341752b93d3c94b62cba00cc.png

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()  # 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
../_images/746cb387ee6a4ad821989513bbd75751eac441a424e7c7c2be9c4091bc34a922.png

As you’ve seen you have two ways to add a reference to our component:

  1. create the reference and add it to the component

c = gf.Component()
wr = c.add_ref(gf.components.straight(width=0.6))
c.plot()
../_images/d2657047dcc0cca7e38e84dd214a945cf4d9310d3d621c13bf99c513a8782c8a.png
  1. or do it in a single line

c = gf.Component()
wr = c << gf.components.straight(width=0.6)
c.plot()
../_images/d2657047dcc0cca7e38e84dd214a945cf4d9310d3d621c13bf99c513a8782c8a.png

in both cases you can move the reference wr after created

c = gf.Component()
wr1 = c << gf.components.straight(width=0.6)
wr2 = c << gf.components.straight(width=0.6)
wr2.movey(10)
c.add_ports(wr1.ports, prefix="bot_")
c.add_ports(wr2.ports, prefix="top_")
c.pprint_ports()
┏━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name    width  orientation  layer     center        port_type ┃
┡━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ bot_o1 │ 0.6   │ 180.0       │ WG (1/0) │ (0.0, 0.0)   │ optical   │
│ bot_o2 │ 0.6   │ 0.0         │ WG (1/0) │ (10.0, 0.0)  │ optical   │
│ top_o1 │ 0.6   │ 180.0       │ WG (1/0) │ (0.0, 10.0)  │ optical   │
│ top_o2 │ 0.6   │ 0.0         │ WG (1/0) │ (10.0, 10.0) │ 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.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  orientation  layer     center        port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.6   │ 180.0       │ WG (1/0) │ (0.0, 0.0)   │ optical   │
│ o4   │ 0.6   │ 0.0         │ WG (1/0) │ (10.0, 0.0)  │ optical   │
│ o2   │ 0.6   │ 180.0       │ WG (1/0) │ (0.0, 10.0)  │ optical   │
│ o3   │ 0.6   │ 0.0         │ WG (1/0) │ (10.0, 10.0) │ optical   │
└──────┴───────┴─────────────┴──────────┴──────────────┴───────────┘
c.plot()
../_images/1f2848c9c4657d79831f1188a5cc7b533661d3b1ce6c9d75598e92a6bc50b15b.png

Arrays of Instances#

In GDS, there’s a type of structure called a “Instance” which takes a cell and repeats it NxM times on a fixed grid spacing. For convenience, Component includes this functionality with the add_ref() function.

Let’s make a new Component and put a big array of our Component c in it:

c3 = gf.Component()  # Create a new blank Component
c = gf.components.rectangle(size=(1,1))
aref = c3.add_ref(c , columns=2, rows=3, column_pitch=10, row_pitch=20)  
c3.add_ports(aref.ports)
# Reference the Component "c" 3 references in it with a 3 rows, 6 columns array
c3.pprint_ports()
c3.draw_ports()
c3
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ name  width  orientation  layer     center        port_type  ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ e1   │ 1.0   │ 180.0       │ WG (1/0) │ (0.0, 0.5)   │ electrical │
│ e2   │ 1.0   │ 90.0        │ WG (1/0) │ (0.5, 1.0)   │ electrical │
│ e3   │ 1.0   │ 0.0         │ WG (1/0) │ (1.0, 0.5)   │ electrical │
│ e4   │ 1.0   │ 270.0       │ WG (1/0) │ (0.5, 0.0)   │ electrical │
│ e1   │ 1.0   │ 180.0       │ WG (1/0) │ (0.0, 20.5)  │ electrical │
│ e2   │ 1.0   │ 90.0        │ WG (1/0) │ (0.5, 21.0)  │ electrical │
│ e3   │ 1.0   │ 0.0         │ WG (1/0) │ (1.0, 20.5)  │ electrical │
│ e4   │ 1.0   │ 270.0       │ WG (1/0) │ (0.5, 20.0)  │ electrical │
│ e1   │ 1.0   │ 180.0       │ WG (1/0) │ (0.0, 40.5)  │ electrical │
│ e2   │ 1.0   │ 90.0        │ WG (1/0) │ (0.5, 41.0)  │ electrical │
│ e3   │ 1.0   │ 0.0         │ WG (1/0) │ (1.0, 40.5)  │ electrical │
│ e4   │ 1.0   │ 270.0       │ WG (1/0) │ (0.5, 40.0)  │ electrical │
│ e1   │ 1.0   │ 180.0       │ WG (1/0) │ (10.0, 0.5)  │ electrical │
│ e2   │ 1.0   │ 90.0        │ WG (1/0) │ (10.5, 1.0)  │ electrical │
│ e3   │ 1.0   │ 0.0         │ WG (1/0) │ (11.0, 0.5)  │ electrical │
│ e4   │ 1.0   │ 270.0       │ WG (1/0) │ (10.5, 0.0)  │ electrical │
│ e1   │ 1.0   │ 180.0       │ WG (1/0) │ (10.0, 20.5) │ electrical │
│ e2   │ 1.0   │ 90.0        │ WG (1/0) │ (10.5, 21.0) │ electrical │
│ e3   │ 1.0   │ 0.0         │ WG (1/0) │ (11.0, 20.5) │ electrical │
│ e4   │ 1.0   │ 270.0       │ WG (1/0) │ (10.5, 20.0) │ electrical │
│ e1   │ 1.0   │ 180.0       │ WG (1/0) │ (10.0, 40.5) │ electrical │
│ e2   │ 1.0   │ 90.0        │ WG (1/0) │ (10.5, 41.0) │ electrical │
│ e3   │ 1.0   │ 0.0         │ WG (1/0) │ (11.0, 40.5) │ electrical │
│ e4   │ 1.0   │ 270.0       │ WG (1/0) │ (10.5, 40.0) │ electrical │
└──────┴───────┴─────────────┴──────────┴──────────────┴────────────┘
../_images/754b50b13c2e03a23112018dfcd06155b4c80be91235fb43916cc91afda88e31.png
len(aref.ports)
24

gdsfactory provides you with similar functionality in gf.components.array. Notice that the port naming is different!

c4 = gf.Component() # Create a new blank Component
c = gf.components.rectangle(size=(1,1))
aref = c4 << gf.components.array(component=c, columns=2, rows=3, row_pitch=20, column_pitch=10)
c4.add_ports(aref.ports)
#c4.draw_ports()
c4.pprint_ports()
c4.show()
┏━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ name    width  orientation  layer     center        port_type  ┃
┡━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ e1_1_1 │ 1.0   │ 180.0       │ WG (1/0) │ (0.0, 0.5)   │ electrical │
│ e2_1_1 │ 1.0   │ 90.0        │ WG (1/0) │ (0.5, 1.0)   │ electrical │
│ e3_1_1 │ 1.0   │ 0.0         │ WG (1/0) │ (1.0, 0.5)   │ electrical │
│ e4_1_1 │ 1.0   │ 270.0       │ WG (1/0) │ (0.5, 0.0)   │ electrical │
│ e1_2_1 │ 1.0   │ 180.0       │ WG (1/0) │ (0.0, 20.5)  │ electrical │
│ e2_2_1 │ 1.0   │ 90.0        │ WG (1/0) │ (0.5, 21.0)  │ electrical │
│ e3_2_1 │ 1.0   │ 0.0         │ WG (1/0) │ (1.0, 20.5)  │ electrical │
│ e4_2_1 │ 1.0   │ 270.0       │ WG (1/0) │ (0.5, 20.0)  │ electrical │
│ e1_3_1 │ 1.0   │ 180.0       │ WG (1/0) │ (0.0, 40.5)  │ electrical │
│ e2_3_1 │ 1.0   │ 90.0        │ WG (1/0) │ (0.5, 41.0)  │ electrical │
│ e3_3_1 │ 1.0   │ 0.0         │ WG (1/0) │ (1.0, 40.5)  │ electrical │
│ e4_3_1 │ 1.0   │ 270.0       │ WG (1/0) │ (0.5, 40.0)  │ electrical │
│ e1_1_2 │ 1.0   │ 180.0       │ WG (1/0) │ (10.0, 0.5)  │ electrical │
│ e2_1_2 │ 1.0   │ 90.0        │ WG (1/0) │ (10.5, 1.0)  │ electrical │
│ e3_1_2 │ 1.0   │ 0.0         │ WG (1/0) │ (11.0, 0.5)  │ electrical │
│ e4_1_2 │ 1.0   │ 270.0       │ WG (1/0) │ (10.5, 0.0)  │ electrical │
│ e1_2_2 │ 1.0   │ 180.0       │ WG (1/0) │ (10.0, 20.5) │ electrical │
│ e2_2_2 │ 1.0   │ 90.0        │ WG (1/0) │ (10.5, 21.0) │ electrical │
│ e3_2_2 │ 1.0   │ 0.0         │ WG (1/0) │ (11.0, 20.5) │ electrical │
│ e4_2_2 │ 1.0   │ 270.0       │ WG (1/0) │ (10.5, 20.0) │ electrical │
│ e1_3_2 │ 1.0   │ 180.0       │ WG (1/0) │ (10.0, 40.5) │ electrical │
│ e2_3_2 │ 1.0   │ 90.0        │ WG (1/0) │ (10.5, 41.0) │ electrical │
│ e3_3_2 │ 1.0   │ 0.0         │ WG (1/0) │ (11.0, 40.5) │ electrical │
│ e4_3_2 │ 1.0   │ 270.0       │ WG (1/0) │ (10.5, 40.0) │ electrical │
└────────┴───────┴─────────────┴──────────┴──────────────┴────────────┘
help(gf.components.array)
Help on function array in module gdsfactory.components.containers.array_component:

array(component: 'ComponentSpec' = 'pad', spacing: 'Spacing | None' = None, columns: 'int' = 6, rows: 'int' = 1, column_pitch: 'float' = 150, row_pitch: 'float' = 150, add_ports: 'bool' = True, size: 'Float2 | None' = None, centered: 'bool' = False, post_process: 'Iterable[AnyComponentPostProcess] | None' = None, auto_rename_ports: 'bool' = False) -> 'Component'
    Returns an array of components.
    
    Args:
        component: to replicate.
        spacing: x, y spacing (deprecated).
        columns: in x.
        rows: in y.
        column_pitch: pitch between columns.
        row_pitch: pitch between rows.
        auto_rename_ports: True to auto rename ports.
        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.
        post_process: function to apply to the array after creation.
    
    Raises:
        ValueError: If columns > 1 and spacing[0] = 0.
        ValueError: If rows > 1 and spacing[1] = 0.
    
    .. code::
    
        2 rows x 4 columns
    
          column_pitch
          <---------->
         ___        ___       ___        ___
        |   |      |   |     |   |      |   |
        |___|      |___|     |___|      |___|
    
         ___        ___       ___        ___
        |   |      |   |     |   |      |   |
        |___|      |___|     |___|      |___|

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", other=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
../_images/b39af7287c7b02b4c1dc67799cab060d63c0a6de617eff9a46795b213e16e8c2.png
dbr = gf.Component()
dbr.add_ref(period, columns=n, rows=1, column_pitch=l1 + l2)
dbr
../_images/70c5eff2a34b7467731414a6cfebb875596b898dff0494309c1d4aaa30dc0701.png

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.dcenter = ((l1 + l2) * n, 0)
dbr.draw_ports()
dbr
../_images/c03954f5cfb046dbda10ad4ae28add96f7d742f9a6036bb9cb3ee007d4304486.png

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
../_images/417d6ce110bd5ac6f1e9efc5c12bab014aa9cf48becc286fd1c33d34f3b532f5.png
c = gf.Component()

mmi = c << gf.components.mmi1x2()
b = c << gf.components.bend_circular()
b.connect("o1", other=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()
../_images/d36feac3121c58efffd92ef5027fcf0101c29d6d0215ec1a82886ea1220604df.png

You can also access the ports as reference[port_name] instead of reference.ports[port_name]

c = gf.Component()

mmi = c << gf.components.mmi1x2()
b = c << gf.components.bend_circular()
b.connect("o1", other=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()
../_images/d36feac3121c58efffd92ef5027fcf0101c29d6d0215ec1a82886ea1220604df.png

Notice that connect mates two ports together and does not imply that ports will remain connected.

Accessing parent cell from instance#

You can access the cell from the instance

print(b.cell.name)
bend_circular_RNone_A90_35590aa7

Accessing components from layout#

You can also access the Component from the layout by the Component name. You can access the cells from the KCLayout:

bend = c.kcl[b.cell.name]
print(type(bend))
bend
<class 'gdsfactory.component.Component'>
../_images/417d6ce110bd5ac6f1e9efc5c12bab014aa9cf48becc286fd1c33d34f3b532f5.png

Port#

You can assign custom names to ports and later rename them using gf.port.auto_rename_ports(prefix='o') or component.auto_rename_ports() which will rename them in place.

By default, ports are numbered clockwise starting from the bottom-left corner. The naming conventions are:

  • Optical ports use the prefix o.

  • Electrical ports use the prefix e.

Here is the default one we use (clockwise starting from bottom left corner)

             3   4
             |___|_
         2 -|      |- 5
            |      |
         1 -|______|- 6
             |   |
             8   7

Port names are defined in the gdsfactory.cross_section. For example:

  • gdsfactory.cross_section.strip assigns o1 for the input and o2 for the output.

  • gdsfactory.cross_section.metal1 assigns e1 for the input and e2 for the output.

Here are the most commonly used port types:

Port Type

Description

optical

Optical ports

electrical

Electrical ports

placement

Placement ports (excluded in netlist extraction)

vertical_te

For grating couplers with TE polarization

vertical_tm

For grating couplers with TM polarization

electrical_rf

Electrical ports for RF (high frequency)

pad

For pads

pad_rf

For RF pads

bump

For bumps

edge_coupler

For edge couplers

If you want to define another type that is not included you can do it like this:

gf.CONF.port_types += ["my_own_port_type"]
size = 4
c = gf.components.nxn(west=2, south=2, north=2, east=2, xsize=size, ysize=size)
c.plot()
../_images/d8bcd5450ac58e52a26cbe924a6fb2c61348fed7c335c08891f5e8818a8d4a42.png
c.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  orientation  layer     center       port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 1.25) │ optical   │
│ o2   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 2.75) │ optical   │
│ o6   │ 0.5   │ 0.0         │ WG (1/0) │ (4.0, 1.25) │ optical   │
│ o5   │ 0.5   │ 0.0         │ WG (1/0) │ (4.0, 2.75) │ optical   │
│ o3   │ 0.5   │ 90.0        │ WG (1/0) │ (1.25, 4.0) │ optical   │
│ o4   │ 0.5   │ 90.0        │ WG (1/0) │ (2.75, 4.0) │ optical   │
│ o8   │ 0.5   │ 270.0       │ WG (1/0) │ (1.25, 0.0) │ optical   │
│ o7   │ 0.5   │ 270.0       │ WG (1/0) │ (2.75, 0.0) │ optical   │
└──────┴───────┴─────────────┴──────────┴─────────────┴───────────┘

You can get the optical ports by layer

c.get_ports_list(layer=(1, 0))
[Port(name: o1, dwidth: 0.5, trans: r180 *1 0,1.25, layer: WG (1/0), port_type: optical),
 Port(name: o2, dwidth: 0.5, trans: r180 *1 0,2.75, layer: WG (1/0), port_type: optical),
 Port(name: o6, dwidth: 0.5, trans: r0 *1 4,1.25, layer: WG (1/0), port_type: optical),
 Port(name: o5, dwidth: 0.5, trans: r0 *1 4,2.75, layer: WG (1/0), port_type: optical),
 Port(name: o3, dwidth: 0.5, trans: r90 *1 1.25,4, layer: WG (1/0), port_type: optical),
 Port(name: o4, dwidth: 0.5, trans: r90 *1 2.75,4, layer: WG (1/0), port_type: optical),
 Port(name: o8, dwidth: 0.5, trans: r270 *1 1.25,0, layer: WG (1/0), port_type: optical),
 Port(name: o7, dwidth: 0.5, trans: r270 *1 2.75,0, layer: WG (1/0), port_type: optical)]
c.pprint_ports(layer=(1, 0))
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  orientation  layer     center       port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 1.25) │ optical   │
│ o2   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 2.75) │ optical   │
│ o6   │ 0.5   │ 0.0         │ WG (1/0) │ (4.0, 1.25) │ optical   │
│ o5   │ 0.5   │ 0.0         │ WG (1/0) │ (4.0, 2.75) │ optical   │
│ o3   │ 0.5   │ 90.0        │ WG (1/0) │ (1.25, 4.0) │ optical   │
│ o4   │ 0.5   │ 90.0        │ WG (1/0) │ (2.75, 4.0) │ optical   │
│ o8   │ 0.5   │ 270.0       │ WG (1/0) │ (1.25, 0.0) │ optical   │
│ o7   │ 0.5   │ 270.0       │ WG (1/0) │ (2.75, 0.0) │ optical   │
└──────┴───────┴─────────────┴──────────┴─────────────┴───────────┘

or by width

c.get_ports_list(width=500)
[Port(name: o1, dwidth: 0.5, trans: r180 *1 0,1.25, layer: WG (1/0), port_type: optical),
 Port(name: o2, dwidth: 0.5, trans: r180 *1 0,2.75, layer: WG (1/0), port_type: optical),
 Port(name: o6, dwidth: 0.5, trans: r0 *1 4,1.25, layer: WG (1/0), port_type: optical),
 Port(name: o5, dwidth: 0.5, trans: r0 *1 4,2.75, layer: WG (1/0), port_type: optical),
 Port(name: o3, dwidth: 0.5, trans: r90 *1 1.25,4, layer: WG (1/0), port_type: optical),
 Port(name: o4, dwidth: 0.5, trans: r90 *1 2.75,4, layer: WG (1/0), port_type: optical),
 Port(name: o8, dwidth: 0.5, trans: r270 *1 1.25,0, layer: WG (1/0), port_type: optical),
 Port(name: o7, dwidth: 0.5, trans: r270 *1 2.75,0, layer: WG (1/0), port_type: optical)]
c0 = gf.components.straight_heater_metal()
c0.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ name  width  orientation  layer        center                      port_type  ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ o1   │ 0.5   │ 180.0       │ WG (1/0)    │ (0.0, 0.0)                 │ optical    │
│ o2   │ 0.5   │ 0.0         │ WG (1/0)    │ (320.0, 0.0)               │ optical    │
│ l_e1 │ 11.0  │ 180.0       │ MTOP (49/0) │ (-15.9, 0.0)               │ electrical │
│ l_e2 │ 11.0  │ 90.0        │ MTOP (49/0) │ (-10.4, 5.5)               │ electrical │
│ l_e3 │ 11.0  │ 0.0         │ MTOP (49/0) │ (-4.9, 0.0)                │ electrical │
│ l_e4 │ 11.0  │ 270.0       │ MTOP (49/0) │ (-10.4, -5.5)              │ electrical │
│ r_e1 │ 11.0  │ 180.0       │ MTOP (49/0) │ (324.90000000000003, 0.0)  │ electrical │
│ r_e2 │ 11.0  │ 90.0        │ MTOP (49/0) │ (330.40000000000003, 5.5)  │ electrical │
│ r_e3 │ 11.0  │ 0.0         │ MTOP (49/0) │ (335.90000000000003, 0.0)  │ electrical │
│ r_e4 │ 11.0  │ 270.0       │ MTOP (49/0) │ (330.40000000000003, -5.5) │ electrical │
└──────┴───────┴─────────────┴─────────────┴────────────────────────────┴────────────┘
c2 = c0.dup()
c2.auto_rename_ports()
c2.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ name  width  orientation  layer        center                      port_type  ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ o1   │ 0.5   │ 180.0       │ WG (1/0)    │ (0.0, 0.0)                 │ optical    │
│ o2   │ 0.5   │ 0.0         │ WG (1/0)    │ (320.0, 0.0)               │ optical    │
│ e1   │ 11.0  │ 180.0       │ MTOP (49/0) │ (-15.9, 0.0)               │ electrical │
│ e3   │ 11.0  │ 90.0        │ MTOP (49/0) │ (-10.4, 5.5)               │ electrical │
│ e6   │ 11.0  │ 0.0         │ MTOP (49/0) │ (-4.9, 0.0)                │ electrical │
│ e8   │ 11.0  │ 270.0       │ MTOP (49/0) │ (-10.4, -5.5)              │ electrical │
│ e2   │ 11.0  │ 180.0       │ MTOP (49/0) │ (324.90000000000003, 0.0)  │ electrical │
│ e4   │ 11.0  │ 90.0        │ MTOP (49/0) │ (330.40000000000003, 5.5)  │ electrical │
│ e5   │ 11.0  │ 0.0         │ MTOP (49/0) │ (335.90000000000003, 0.0)  │ electrical │
│ e7   │ 11.0  │ 270.0       │ MTOP (49/0) │ (330.40000000000003, -5.5) │ electrical │
└──────┴───────┴─────────────┴─────────────┴────────────────────────────┴────────────┘

You can also rename them with a different port naming convention

  • prefix: add e for electrical o for optical

  • clockwise

  • 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()
../_images/d8bcd5450ac58e52a26cbe924a6fb2c61348fed7c335c08891f5e8818a8d4a42.png
gf.port.pprint_ports(ref.ports)
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  orientation  layer     center       port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 1.25) │ optical   │
│ o2   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 2.75) │ optical   │
│ o6   │ 0.5   │ 0.0         │ WG (1/0) │ (4.0, 1.25) │ optical   │
│ o5   │ 0.5   │ 0.0         │ WG (1/0) │ (4.0, 2.75) │ optical   │
│ o3   │ 0.5   │ 90.0        │ WG (1/0) │ (1.25, 4.0) │ optical   │
│ o4   │ 0.5   │ 90.0        │ WG (1/0) │ (2.75, 4.0) │ optical   │
│ o8   │ 0.5   │ 270.0       │ WG (1/0) │ (1.25, 0.0) │ optical   │
│ o7   │ 0.5   │ 270.0       │ WG (1/0) │ (2.75, 0.0) │ optical   │
└──────┴───────┴─────────────┴──────────┴─────────────┴───────────┘

You can also get the ports counter-clockwise

             4   3
             |___|_
         5 -|      |- 2
            |      |
         6 -|______|- 1
             |   |
             7   8

c.get_ports_list(clockwise=False)
['Port(name: o1, dwidth: 0.5, trans: r180 *1 0,1.25, layer: WG (1/0), port_type: optical)', 'Port(name: o2, dwidth: 0.5, trans: r180 *1 0,2.75, layer: WG (1/0), port_type: optical)', 'Port(name: o6, dwidth: 0.5, trans: r0 *1 4,1.25, layer: WG (1/0), port_type: optical)', 'Port(name: o5, dwidth: 0.5, trans: r0 *1 4,2.75, layer: WG (1/0), port_type: optical)', 'Port(name: o3, dwidth: 0.5, trans: r90 *1 1.25,4, layer: WG (1/0), port_type: optical)', 'Port(name: o4, dwidth: 0.5, trans: r90 *1 2.75,4, layer: WG (1/0), port_type: optical)', 'Port(name: o8, dwidth: 0.5, trans: r270 *1 1.25,0, layer: WG (1/0), port_type: optical)', 'Port(name: o7, dwidth: 0.5, trans: r270 *1 2.75,0, layer: WG (1/0), port_type: optical)']

Lets extend the East facing ports (orientation = 0 deg)

import gdsfactory as gf

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()
../_images/751662a28b7ccf85a10faab18651d27e8b12680af5e35c328dd67ac5833f38d7.png
c.pprint_ports()
┏━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ name  width  orientation  layer     center       port_type ┃
┡━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ o1   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 1.25) │ optical   │
│ o2   │ 0.5   │ 180.0       │ WG (1/0) │ (0.0, 2.75) │ optical   │
│ o6   │ 0.5   │ 0.0         │ WG (1/0) │ (9.0, 1.25) │ optical   │
│ o5   │ 0.5   │ 0.0         │ WG (1/0) │ (9.0, 2.75) │ optical   │
│ o3   │ 0.5   │ 90.0        │ WG (1/0) │ (1.25, 4.0) │ optical   │
│ o4   │ 0.5   │ 90.0        │ WG (1/0) │ (2.75, 4.0) │ optical   │
│ o8   │ 0.5   │ 270.0       │ WG (1/0) │ (1.25, 0.0) │ optical   │
│ o7   │ 0.5   │ 270.0       │ WG (1/0) │ (2.75, 0.0) │ optical   │
└──────┴───────┴─────────────┴──────────┴─────────────┴───────────┘

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).

c = gf.components.mmi1x2()
c.plot()
../_images/e12e6e03659a9db609afdafc29766d1101657e2fef0dde529ce35836372b83e1.png
c = gf.components.mmi1x2()
c_with_pins = gf.add_pins.add_pins_container(c)
c_with_pins.plot()
../_images/6dd49aa27736cf4f0360b372e607ff4bafe06544716299057bc571b0cd09d114.png
c = gf.components.mmi1x2()
c.draw_ports()
c.plot()
../_images/6bac51e0e305263ce780100d1bbdb2b6e1aec5b57cd3a508e4b83222633ffdb3.png

Component_sequence#

When you have repetitive connections you can describe the connectivity as an ASCII map

import gdsfactory as gf

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.plot()
../_images/ab2bcec9af0382f8e41080318da14de204ecdd8422b65fd550ce315e08f7554e.png

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 Instance as well as Port, Polygon, Instance, Label, and Group

import gdsfactory as gf

# Start with a blank Component
c = gf.Component()

# 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 instances
text = c << T
ellipse = c << E
rect1 = c << R
rect2 = c << R

c.plot()
../_images/447329ccdc7fcbf4817af916ad1a9a1683c1db104c4432558676329927076e88.png
c = gf.Component()
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()
../_images/6fed5dd9af8d3a8a8f4f626a5ac642c60076917fc14c902bb0202e626cd86b1a.png
c = gf.Component()
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()
../_images/c01c54e0138871d29490ce2e7b10c5ccff9937cee759083ca0634735542acdb7.png
# Now you can practice move and rotate the objects.

c = gf.Component()
E = gf.components.ellipse(radii=(10, 5), layer=(2, 0))
e1 = c << E
e2 = c << E
c.plot()
../_images/16050742affb290a3a2a9cf1329f2d53fdecef3590f46d459928f22da426fb23.png
c = gf.Component()
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()
../_images/b0c3b623ca9002a6b89b24cabb3285f9248b6446312d6faeba3693894c9177ab.png
c = gf.Component()
r = gf.components.rectangle(size=(10, 5), layer=(2, 0))
rect1 = c << r
rect2 = c << r

rect1.drotate(45)  # Rotate the first straight by 45 degrees around (0,0)
rect2.drotate(-30)  # Rotate the second straight by -30 degrees around (1,1)
c.plot()
../_images/fe533222e3c34be7f073829b0ef7b44c39d1ed54b7fb7a2518b3162c2f98983f.png
import gdsfactory as gf

c = gf.Component()
text = c << gf.components.text("hello")
text.dmirror(p1=gf.kdb.DPoint(1, 1), p2=gf.kdb.DPoint(1, 3))  
# Reflects across the line formed by p1 and p2
c.plot()
../_images/ea71aca421cb71cd54299d6dcffb4cfa52909c21e032023501356b0de5b476cd.png
c = gf.Component()
text = c << gf.components.text("hello")
c.plot()
../_images/1bf3174d5cee02d98d7d20ba6e8862c49e83ad52e92d098c33a8cd456822e6d5.png

Each Component and Instance 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 object

  • ymin / ymax: minimum and maximum y-values of all points within the object

  • x: centerpoint between minimum and maximum x-values of all points within the object

  • y: centerpoint between minimum and maximum y-values of all points within the object

  • bbox: bounding box (see note below) in format ((xmin,ymin),(xmax,ymax))

  • center: center of bounding box

print(
    "printing the bounding box of text in terms of [(xmin, ymin), (xmax, ymax)] in um"
)
print(text.dbbox())  # in Decimal um (float)
print("xsize and ysize:")
print(text.xsize)  # Will print the width of text in the x dimension in um
print(text.ysize)  # Will print the height of text in the y dimension in um

print("center:")
print(text.dcenter)  # Gives you the center coordinate of its bounding box in DBU
print("xmax")
print(text.xmax)  # Gives you the rightmost (+x) edge of the text bounding box
printing the bounding box of text in terms of [(xmin, ymin), (xmax, ymax)] in um
(1,0;34,11)
xsize and ysize:
33.0
11.0
center:
17.5,5.5
xmax
34.0
c = gf.Component()
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()
../_images/7e5a0611c62484b960a6da884fc803429012b91c454656310ad99e66bf7f133f.png
ellipse.dcenter = (0, 0)  # Move the ellipse to the center of the bounding box

# 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()
../_images/132b3ec0cccbd941239d7954da8e4cbf6989afe59366a58b7f4d4b4f9e0e8209.png
import gdsfactory as gf
# A bounding box is the smallest enclosing box which contains all points of the geometry.

c = gf.Component()
text = c << gf.components.text("hi")
c << gf.components.bbox(text, layer=(2, 0))
fig = c.plot()
../_images/884d7bf6b5fecfca6e50a905682a41ffa0b989318bb4e20eeff6f762043c1173.png
c = gf.Component()
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()
../_images/9cedcafe0de5e8a985f06e818d45bb8c2b9832423d175e42aa3f083575d77eca.png
# 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.dcenter)

print("X-max of Component c:")
print(c.xmax)
Center of Component c:
12,3.5
X-max of Component c:
24.0
c = gf.Component()
R = gf.components.rectangle(size=(10, 3), layer=(2, 0))
rect1 = c << R
f = c.plot()
../_images/6689d8725ba7c31e18056f7cbe49ae7bc4f2c07655edb2db8e8fcac82eaaed6f.png
# 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.drotate(angle=37)
rect1.move((10, 20))
f = c.plot()
../_images/fff5cbdc1683eb24f28488714f5261220445f46209485ef23ab901c234091a3d.png
# ...into this single-line expression

c = gf.Component()
R = gf.components.rectangle(size=(10, 3), layer=(2, 0))
rect1 = c << R
rect1.drotate(angle=37).move((10, 20))
f = c.plot()
../_images/fff5cbdc1683eb24f28488714f5261220445f46209485ef23ab901c234091a3d.png