networks¶
The Network is the core of Photontorch.
This is where everything comes together. The Network is a special kind of torch.nn.Module, where all subcomponents are automatically initialized and connected in the right way.
reduction of S-matrix¶
Each component can be described in terms of it’s S matrix. For such a component, we have that the output fields \(\bf x_{\rm out}\) are connected to the input fields \(x_{\rm in}\) through a scattering matrix:
For a network of components, the field vectors \(x\) will just be stacked on top of each other, and the S-matrix will just be the block-diagonal matrix of the S-matrices of the individual components. However, to connect the output fields to each other, we need a connection matrix, which connects the output fields of the individual components to input fields of other components in the fields vector \(x\):
a simulation (without delays) can thus simply be described by:
However, when delays come in the picture, the situation is a bit more complex. We then split the fields vector \(x\) in a memory_containing part (mc) and a memory-less part (ml):
Usually, we are only interested in the memory-containing nodes, as memory-less nodes should be connected together and act all at once. After some matrix algebra we arrive at
Which defines the reduced connection matrix used in the simulations.
complex matrix inverse¶
PyTorch still does not allow complex valued Tensors. Therefore, the above equation was completely rewritten with matrices containing the real and imaginary parts. This would be fairly straightforward if it were not for the matrix inverse in the reduced connection matrix:
unfortunately for complex matrices \(P^{-1} \neq {\rm real}(P)^{-1} + i{\rm imag}(P)^{-1}\), the actual case is a bit more complicated.
It is however, pretty clear from the equations that the \({\rm real}(P)^{-1}\) will always exist, and thus we can write for the real and imaginary part of \(P^{-1}\):
This equation is valid, even if \({\rm imag}(P)^{-1}\) does not exist.
network¶
Photontorch network
The Network is the core of Photontorch: it is where everything comes together.
A network is created by subclassing it and linking its components together, like so:
class Circuit(Network):
def __init__(self):
self.src = pt.Source()
self.det = pt.Detector()
self.wg = pt.Waveguide()
self.link("src:0", "0:wg:1", "0:det")
circuit = Circuit()
-
class
photontorch.networks.network.
Network
(components=None, connections=None, name=None)¶ Bases:
photontorch.components.component.Component
a Network (circuit) of Components
The Network is the core of Photontorch. This is where everything comes together. The Network is a special kind of torch.nn.Module, where all subcomponents are automatically initialized and connected in the right way.
-
__init__
(components=None, connections=None, name=None)¶ Network initialization
- Parameters
components (dict) – a dictionary containing the components of the network. keys: str: new names for the components (will override the component name) values: Component: the component
connections (list) – a list containing the connections of the network. the connection string can have two formats: 1. “comp1:port1:comp2:port2”: signifying a connection between ports (always reflexive) 2. “comp:port:output_port”: signifying a connection to an output port index
name (optional, str) – name of the network
Note
Although it’s possible to initialize the network with a list of components and a list of connections. It’s a lot easier to create a network with subclassing. For example:
class Circuit(Network): def __init__(self): self.src = pt.Source() self.det = pt.Detector() self.wg = pt.Waveguide() self.link("src:0", "0:wg:1", "0:det") circuit = Circuit()
Note
If quick creation of a network is desired, a network can also be created with a with-block:
with pt.Network() as circuit: circuit.src = pt.Source() circuit.det = pt.Detector() circuit.wg = pt.Waveguide() circuit.link("src:0", "0:wg:1", "0:det")
However, creating networks by subclassing is often preferred as multiple instances of the network can easily be created after the network class is defined.
-
action
(t, x_in, x_out)¶ Perform the action of an active components in the network
-
add_component
(name, comp)¶ Add a component to the network
Pytorch requires submodules to be registered as attributes of a module. This method register a component as a torch module in the _modules dictionary.
- Parameters
name (optional, str) – name of the component to add to the network
Note
the following two lines are equivalent:
nw.wg = pt.Waveguide() nw.add_component("wg", pt.Waveguide())
The first one is often preferred for simple networks, whereas the latter can be useful when components are created in a loop.
-
components
= None¶ dictionary containing all the components of the network
-
connections
= None¶ list containing all the connection of the network
-
forward
(source=0.0, power=True, detector=None)¶ calculate the network’s response to an applied source.
- Parameters
source (Tensor) – The source tensor to calculate the response for.
power (bool) – Return detected power, otherwise return complex signal.
detector (callable) – Custom detector function to use to detect the signal.
- Returns
- The detected tensor with shape (t, w, s, b) or with
shape (2, t, w, s, b) in the case of power=False (in that case, dimension 0 contains the stacked real and imaginary part of the result)
- Return type
Tensor
Note
- The source tensor should have shape (t, w, s, b), with
t: the number of timesteps in the simulation environment.
w: the number of wavelengths in the simulation environment.
s: the number of sources in the network.
b: the number of unrelated input waveforms (the batch size).
Alternatively, two of such tensors can be stacked together in dimension 0 to represent the real and imaginary part of a complex tensor, resulting in a tensor of shape (2, t, w, s, b).
Any lower dimensional tensor should have named dimensions to remove any ambiguity in the broadcasting rules. Dimensions of a tensor can be named with the
.rename
method of the PyTorch Tensor class. accepted dimension names are ‘c’, ‘t’, ‘w’, ‘s’, ‘b’.
-
graph
(draw=True)¶ create a graph visualization of the network
- Parameters
draw (bool) – draw the graph with matplotlib
-
initialize
()¶ Initializer of the network.
The goal of this initialization is to split the network into memory-containing nodes (nodes that introduce delay, sources, detectors, active components…) and memory-less nodes. A matrix reduction method is then applied to remove the memory-less nodes from the S-matrix and C-matrix. It is with these reduced matrices that the simulation will then be performed.
The resulting reduced matrices will also be reordered, such that source nodes will be the first nodes of the network and detector nodes will be the last nodes. This is done for performance reasons during simulation, but has the added benefit of easier access to the results afterwards.
Note
during initialization, the reduced connection matrix is calculated for all batches and for all wavelengths. This is a quite verbose calculation, because the matrices need to be (batch) multiplied in the right way. Below, you can find the equation for the reduced connection matrix we’re trying to calculate (will saved to network attribute
._C
.\[C^{\rm red} = \left(1-C^{\rm mlml}S^{\rm mlml}\right)^{-1} C^{\rm mlmc}\]Note
Before any initialization can happen, all the ports of the comonents of the network need to be interconnected: the network needs to be fully connected (terminated). If the network is not fully connected, the initialization will silently fail. This is to speed up the initialization of nested networks, where only the top level needs to be fully initialized.
Note
Usually, calling
initialize
directly is not necessary.Network.forward
callsinitialize
automatically. It could however be useful to callinitialize
if you want access to the reduced matrices without needing the response of the network to an input signal.
-
link
(*ports)¶ link components together
- Parameters
*ports[str] – the components and ports to link together. The following format is expected: “idx1:component:idx2”. This format specifies the input port (idx1) and the output port (idx2) of a component to use in the chain. The chain can be preceded and followed by an integer, specifying the port indices of the resulting network.
Example
>>> with pt.Network() as allpass: >>> allpass.dc = pt.DirectionalCoupler() >>> allpass.wg = pt.Waveguide() >>> allpass.link(0, '0:dc:2', '0:wg:1', '3:dc:1', 1)
-
property
num_ports
¶ number of ports in the network
-
plot
(detected, **kwargs)¶ Plot detected power versus time or wavelength
- Parameters
detected (np.ndarray|Tensor) – detected power. Allowed shapes: * (#timesteps,) * (#timesteps, #detectors) * (#timesteps, #detectors, #batches) * (#timesteps, #wavelengths) * (#timesteps, #wavelengths, #detectors) * (#timesteps, #wavelengths, #detectors, #batches) * (#wavelengths,) * (#wavelengths, #detectors) * (#wavelengths, #detectors, #batches) the plot function should be smart enough to figure out what to plot.
**kwargs – keyword arguments given to plt.plot
Note
if #timesteps = #wavelengths, the plotting function will choose #timesteps as the first dimension
-
set_C
(C)¶ set the combined connection matrix of all the components in the network
- Returns
binary tensor with only 1’s and 0’s.
Note
To create the connection matrix, the connection strings are parsed.
-
set_S
(S)¶ get the combined S-matrix of all the components in the network
-
set_actions_at
(actions_at)¶ set the locations of the functions in the network
-
set_delays
(delays)¶ set all the delays in the network
-
set_detectors_at
(detectors_at)¶ set the locations of the detectors in the network
-
set_port_order
(port_order)¶ set the reordering indices for the ports of the network
-
set_sources_at
(sources_at)¶ set the locations of the sources in the network
-
step
(t, srcvalue, buffer)¶ Single step forward pass through the network
- Parameters
t (float) – the time of the simulation
srcvalue (Tensor) – The source value at the next timestep
buffer (Tensor) – The internal state of the network
- Returns
The detected fields buffer (Tensor): The internal state of the network after the step
- Return type
detected (Tensor)
-
terminate
(term=None, name=None)¶ Terminate open conections with the Term of your choice
- Parameters
term – (Term|list|dict): Which term to use. Defaults to Term. If a dictionary or list is specified, then one needs to specify as many terms as there are open ports.
- Returns
terminated network.
Note
the original (unterminated) network is always available with the
.base
attribute of the terminated network.
-
training
¶
-
unterminate
()¶ remove termination of network
- Returns
unterminated network.
-
-
photontorch.networks.network.
current_network
()¶ get the current network being defined
-
photontorch.networks.network.
link
(*ports)¶ link components together
- Parameters
*ports – the ports to link together. The first and last port can be an integer to specify the ordering of the network ports.
Note
if more than two ports are specified, then the intermediate ports should be of the ‘double port’ type (i.e.
idx1:comp_name:idx2
). The first index will connect to the port before; the second index will connect to the port after.Example
>>> with pt.Network() as nw: >>> nw.dc = pt.DirectionalCoupler() >>> nw.wg = pt.Waveguide() >>> nw.link(0, '0:dc:2','0:wg:1','3:dc:1', 1)
clements¶
unitary matrix network based on the Clements network
-
class
photontorch.networks.clements.
ClementsNxN
(N=2, capacity=None, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)¶ Bases:
photontorch.networks.network.Network
A unitary matrix network based on the Clements architecture.
Network:
<--- capacity ---> 0__ ______ ______[]__0 \/ \/ 1__/\__ __/\__ __[]__1 \/ \/ 2__ __/\__ __/\__[]__2 \/ \/ 3__/\______/\______[]__3 with: 0__[]__1 = phase shift 3__ __2 \/ = MZI 0__/\__1
-
__init__
(N=2, capacity=None, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)¶ - Parameters
N (int) – number of input / output ports (the network represents an NxN matrix)
capacity (int) – number of consecutive MZI layers (to span the full unitary space one needs capacity >=N).
wg_factory (callable) – function without arguments which creates the waveguides.
mzi_factory (callable) – function without arguments which creates the MZIs or any other general 4-port component with ports defined anti-clockwise.
name (optional, str) – the name of the network (default: lowercase classname)
-
terminate
(term=None)¶ Terminate open conections with the term of your choice
- Parameters
term – (Term|list|dict): Which term to use. Defaults to Term. If a dictionary or list is specified, then one needs to specify as many terms as there are open connections.
- Returns
terminated network with sources on the left and detectors on the right.
-
training
¶
-
reck¶
wg_factory=wg_factory,
The reck module implements a unitary matrix network based on the Reck network
-
class
photontorch.networks.reck.
ReckNxN
(N=2, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)¶ Bases:
photontorch.networks.network.Network
A unitary matrix network based on the Reck Network.
Network:
.--- : 9 | | 2 .---3 1-- : 8 | 0 | | 2 2 .---3 1--3 1-- : 7 N | 0 0 | | | 2 2 2 .---3 1--3 1--3 1-- : 6 | 0 0 0 | | | | 2 2 2 2 .---3 1--3 1--3 1--3 1-- : 5 | 0 0 0 0 | | | | | .. .. .. .. .. 4 3 2 1 0 N
-
__init__
(N=2, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)¶ - Parameters
N (int) – number of input ports (the network represents an MxN matrix)
wg_factory (callable) – function without arguments which creates the waveguides.
mzi_factory (callable) – function without arguments which creates the MZIs or any other general 4-port component with ports defined anti-clockwise.
name (optional, str) – name of the component
Note
ReckMxN
expectsM >= N
. If M < N is desired, consider terminating with thetransposed=True
flag:reck7x3 = Reck(3, 7).terminate(transposed=True)
-
terminate
(term=None)¶ Terminate open conections with the term of your choice
- Parameters
term – (Term|list|dict): Which term to use. Defaults to Term. If a dictionary or list is specified, then one needs to specify as many terms as there are open connections.
- Returns
terminated network with sources on the bottom and detectors on the right.
-
training
¶
-
rings¶
A collection of ring networks
-
class
photontorch.networks.rings.
RingNetwork
(N=2, capacity=2, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)¶ Bases:
photontorch.networks.network.Network
A ring network
By changing the orientation of some of the MZIs in the Clements Network, a ring network can be obtained.
Network:
<--- capacity ---> 0__ ______ ______[]__0 \/ \/ 1__/\__ __/\__ __[]__1 \/ \/ 2__ __/\__ __/\__[]__2 \/ \/ 3__/\______/\______[]__3 with: __[]__ = phase shift __ __ \/ = MZI __/\__
-
__init__
(N=2, capacity=2, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)¶ - Parameters
N (int) – number of input waveguides (= number of output waveguides)
capacity (int) – number of consecutive MZI layers (1 more than the consequtive number of ring layers)
wg_factory (callable) – function without arguments which creates the waveguides.
mzi_factory (callable) – function without arguments which creates the MZIs or any other general 4-port component with ports defined anti-clockwise.
name (optional, str) – name of the component
-
terminate
(term=None)¶ Terminate open conections with the term of your choice
- Parameters
term – (Term|list|dict): Which term to use. Defaults to Term. If a dictionary or list is specified, then one needs to specify as many terms as there are open connections.
- Returns
terminated network with sources on the left and detectors on the right.
-
training
¶
-