Basic usage of ports and nodes¶
All components in Finesse 3 that can be connected together have various ports
associated with them, which in turn contain
Node
objects representing some optical, electrical, or mechanical state. Each
state has a directionality associated with it: input, output, or bidirectional. Input
nodes can only be connected to another output node, whereas bidirectional do not care.
Every optical port has an input and output node, usually called i and o. Electrical
ports usually just have a single input or output node.
Mechanical ports have bidirectional nodes, but you cannot directly connect multiple mechanical components together as of Finesse 3 alpha. Additional mechanical features may be added in a later version.
Accessing ports¶
Ports of a component can be accessed via the component through the names that they were
assigned during construction. For example, a Mirror
has two ports (p1 and
p2) which can be grabbed directly:
from finesse.components import Mirror
M1 = Mirror("M1")
print(M1.p1)
print(M1.p2)
<Port M1.p1 Type=NodeType.OPTICAL @ 0x7f9e6026a850>
<Port M1.p2 Type=NodeType.OPTICAL @ 0x7f9e6026a2e0>
You can also get a read-only tuple of all the ports available:
print(M1.ports)
print(M1.optical_nodes)
print(M1.electrical_nodes)
print(M1.mechanical_nodes)
(<Port M1.p1 Type=NodeType.OPTICAL @ 0x7f9e6026a850>, <Port M1.p2 Type=NodeType.OPTICAL @ 0x7f9e6026a2e0>, <Port M1.mech Type=NodeType.MECHANICAL @ 0x7f9e602656a0>)
(<OpticalNode M1.p1.i @ 0x7f9e6026aac0>, <OpticalNode M1.p1.o @ 0x7f9e6026aa00>, <OpticalNode M1.p2.i @ 0x7f9e6026a7f0>, <OpticalNode M1.p2.o @ 0x7f9e6026a4c0>)
()
(<MechanicalNode M1.mech.z @ 0x7f9e62aeb0a0>, <MechanicalNode M1.mech.yaw @ 0x7f9dff5fcfa0>, <MechanicalNode M1.mech.pitch @ 0x7f9dff5fcca0>, <MechanicalNode M1.mech.F_z @ 0x7f9dff5fcfd0>, <MechanicalNode M1.mech.F_yaw @ 0x7f9dff4ccd90>, <MechanicalNode M1.mech.F_pitch @ 0x7f9dff608070>)
What does a port contain?¶
In the example code below we show the main properties of a Port
- namely its
type, the nodes that it holds and the component that it is attached to (which is always
a Space
between different optical connections):
from finesse import Model
from finesse.components import Mirror, Space
M1 = Mirror("M1")
M2 = Mirror("M2")
model = Model()
# connect M1 <-> M2 in a model via a Space of length 1m
model.chain(M1, Space("M1_M2", L=10), M2)
print(f"Port M1.p2 name = {M1.p2.name}")
print(f"Port M1.p2 type = {M1.p2.type}")
print(f"Port M1.p2 owning component = {M1.p2.component}")
print(f"Port M1.p2 attached component = {M1.p2.attached_to}")
print(f"Port M1.p2 nodes = {M1.p2.nodes}")
Port M1.p2 name = p2
Port M1.p2 type = NodeType.OPTICAL
Port M1.p2 owning component = Mirror('M1', misaligned=False, ybeta=0.0, xbeta=0.0, Rcy=inf, Rcx=inf, phi=0.0, L=0.0, T=0.5, R=0.5)
Port M1.p2 attached component = Space('M1_M2', gouy_y=0.0, gouy_x=0.0, nr=1.0, L=10.0)
Port M1.p2 nodes = (<OpticalNode M1.p2.i @ 0x7fe1645e9c10>, <OpticalNode M1.p2.o @ 0x7fe1645e96d0>)
Accessing nodes — via a component¶
Nodes can be accessed directly through components or via the Model
instance
that their owning component is associated with (see next section for details). To access
all the nodes of a component:
print(f"All nodes of M1 = {M1.nodes}")
All nodes of M1 = OrderedDict([('M1.p1.i', <OpticalNode M1.p1.i @ 0x7fe144188c10>), ('M1.p1.o', <OpticalNode M1.p1.o @ 0x7fe144188be0>), ('M1.p2.i', <OpticalNode M1.p2.i @ 0x7fe1645e9c10>), ('M1.p2.o', <OpticalNode M1.p2.o @ 0x7fe1645e96d0>), ('M1.mech.z', <MechanicalNode M1.mech.z @ 0x7fe1001aa730>), ('M1.mech.yaw', <MechanicalNode M1.mech.yaw @ 0x7fe1001aad00>), ('M1.mech.pitch', <MechanicalNode M1.mech.pitch @ 0x7fe1001aaca0>), ('M1.mech.F_z', <MechanicalNode M1.mech.F_z @ 0x7fe1001aa6a0>), ('M1.mech.F_yaw', <MechanicalNode M1.mech.F_yaw @ 0x7fe1001aabb0>), ('M1.mech.F_pitch', <MechanicalNode M1.mech.F_pitch @ 0x7fe1001aaa00>)])
Or all the optical nodes:
print(f"Optical nodes of M1 = {M1.optical_nodes}")
Optical nodes of M1 = (<OpticalNode M1.p1.i @ 0x7fe144188c10>, <OpticalNode M1.p1.o @ 0x7fe144188be0>, <OpticalNode M1.p2.i @ 0x7fe1645e9c10>, <OpticalNode M1.p2.o @ 0x7fe1645e96d0>)
Get a single optical node of a component by its direction:
print(f"Input node of port M1.n1 = {M1.p1.i}")
print(f"Output node of port M1.n1 = {M1.p1.o}")
Input node of port M1.n1 = <OpticalNode M1.p1.i @ 0x7fe144188c10>
Output node of port M1.n1 = <OpticalNode M1.p1.o @ 0x7fe144188be0>
Accessing nodes — via the model¶
Nodes play an important role in the Model
class as the Node.full_name
property forms the node_type of the underlying directed graph object (stored in
Model.network
). Thus, we use Node instances to report on graph data as well as
perform operations on this graph - see the networkx DiGraph documentation for
details on these methods and attributes.
You can also access all the nodes of a given NodeType
in a Model instance with
(e.g. for optical nodes):
print(f"All optical nodes in model: {model.optical_nodes}")
All optical nodes in model: [<OpticalNode M1.p1.i @ 0x7fe144188c10>, <OpticalNode M1.p1.o @ 0x7fe144188be0>, <OpticalNode M1.p2.i @ 0x7fe1645e9c10>, <OpticalNode M1.p2.o @ 0x7fe1645e96d0>, <OpticalNode M2.p1.i @ 0x7fe1645e96a0>, <OpticalNode M2.p1.o @ 0x7fe1001aa9a0>, <OpticalNode M2.p2.i @ 0x7fe1001aaa90>, <OpticalNode M2.p2.o @ 0x7fe1001aae80>]
Node names are used as keys in the network of a Model to get data on the node itself and edges connected to the node:
print(model.network.nodes[model.M2.p1.i.full_name])
print(model.network[model.M2.p1.i.full_name][model.M2.p1.o.full_name])
{'weakref': <weakref at 0x7fe1001ceea0; to 'OpticalNode' at 0x7fe1645e96a0>, 'owner': <weakref at 0x7fe1001be040; to 'Mirror' at 0x7fe1645e94c0>, 'optical': True}
{'name': 'P1i_P1o', 'in_ref': <weakref at 0x7fe1001ceea0; to 'OpticalNode' at 0x7fe1645e96a0>, 'out_ref': <weakref at 0x7fe1001d02c0; to 'OpticalNode' at 0x7fe1001aa9a0>, 'owner': <weakref at 0x7fe1001be040; to 'Mirror' at 0x7fe1645e94c0>, 'length': 1, 'coupling_type': <CouplingType.OPTICAL_TO_OPTICAL: 0>}