Routing and Connections¶
PhotonForge makes it easy to place and connect components to build complex layouts. Components are placed through references (see our getting started page for the basic concepts used in PhotonForge) and those are connected by ports.
To demonstrate routing and connections, let us first create a few components using the basic technology as our default technology.
[1]:
import photonforge as pf
tech = pf.basic_technology()
pf.config.default_technology = tech
We use the parametric library of components to create a coupler and a spiral:
[2]:
coupler = pf.parametric.s_bend_coupler(
port_spec="Strip",
s_bend_length=6,
s_bend_offset=1.5,
coupling_distance=0.6,
euler_fraction=0.5,
)
coupler
[2]:
The spiral and routing functions we will use later use a radius
argument that can take a default value from config.default_radius if we set it first.
[3]:
pf.config.default_radius = 5
spiral = pf.parametric.rectangular_spiral(port_spec="Strip", turns=8)
spiral
[3]:
We create our main component and start by adding a reference to the coupler.
[4]:
component = pf.Component("MAIN")
coupler_ref1 = component.add_reference(coupler)
component
[4]:
We also want to add a reference to the spiral, but we want it connected to the coupler we just added. For that we can use the reference’s connect method, which takes care of any translation, rotations, or reflection required to connect the desired ports.
In this case, we connect port P0 from the spiral reference to port P3 of the coupler reference. Note that we’re using the reference’s ports, not the original component’s, because we have to take into account any transformations the references already have.
[5]:
spiral_ref = component.add_reference(spiral)
spiral_ref.connect("P0", coupler_ref1["P3"])
component
[5]:
We can see that the spiral reference has been translated to connect to the coupler at the appropriate port. The reference origin can be inspected to reveal the translation:
[6]:
spiral_ref.origin
[6]:
array([12. , 3.6])
We want the second coupler to be aligned to the first, so we won’t the placing it by connecting to the second port of the spiral reference. Instead, we use the reference alignment coordinates to position it:
[7]:
coupler_ref2 = component.add_reference(coupler)
coupler_ref2.x_min = spiral_ref.x_max + 5
coupler_ref2.y_mid = coupler_ref1.y_mid
component
[7]:
The connection between the spiral and the second coupler references can be built with the help of the route parametric component. It generates a waveguide connection between 2 ports and it accepts a number of parameters for customization. Internally, it uses other parametric components to build the path, namely, straight, bend, and s_bend sections. It also includes a predefined Circuit model, so it can be easily simulated as part of the full device.
As with any component, we add it as a reference. In this case, however, there’s no need to manually specify the connections because the route is already created with the required translation (rotation, etc.) in place.
[8]:
route1 = pf.parametric.route(port1=spiral_ref["P1"], port2=coupler_ref2["P1"])
component.add_reference(route1)
component
[8]:
Note that the spiral parametric component does include an argument to provide a similar geometry (align_ports="y"
), but using that would defeat the purpose of demonstrating the route generation.
The remaining connection between the 2 coupler references could be made in a similar fashion. For the sake of demonstrating the flexibility of route
, let’s imagine that there’s a region we need to avoid between the ports we want to connect. We will add a rectangle here to indicate the obstacle that prevents a direct route:
[9]:
component.add(
"SLAB", pf.Rectangle(center=(spiral_ref.x_mid + 2.5, -7.5), size=(10, 15))
)
[9]:
Using the waypoints
argument in route
we can specify a list of points that the route must cross (we can also specify the route direction at each point, if we so desire). We use that feature here to go around the avoided region:
[10]:
route2 = pf.parametric.route(
port1=coupler_ref1["P2"],
port2=coupler_ref2["P0"],
waypoints=[(coupler_ref1.x_max + 10, -20), (coupler_ref2.x_min - 10, -20)],
)
component.add(route2)
[10]:
We can get the path length from routes using the route_length function. Note that it also works for the rectangular spiral, because it is also built as a single continuous path. Similarly, it can be used with straight, transition, bend, s_bend, combinations of those, or any component built with connected paths.
[11]:
print(f"Route 1 length: {pf.route_length(route1):.2f} μm")
print(f"Route 2 length: {pf.route_length(route2):.2f} μm")
print(f"Rectangular spiral length: {pf.route_length(spiral):.2f} μm")
Route 1 length: 41.02 μm
Route 2 length: 64.41 μm
Rectangular spiral length: 308.14 μm
Besides directly connecting and building routes to connect ports, PhotonForge also offers the possibility of defining virtual connections, which can help setting up system-level simulations before the final layout and routing is in place. More information about virtual connections can be found in the Circuit Model guide.