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

$x_{\rm out} = S \cdot x_{\rm in}$

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$$:

$x_{\rm in} = C \cdot x_{\rm out}$

a simulation (without delays) can thus simply be described by:

$x(t+1) = C\cdot S\cdot x(t)$

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

$\begin{split}\begin{pmatrix}x^{\rm mc} \\x^{\rm ml} \end{pmatrix}(t+1) = \begin{pmatrix} C^{\rm mcmc} & C^{\rm mcml} \\ C^{\rm mlmc} & C^{\rm mlml} \end{pmatrix} \begin{pmatrix} S^{\rm mcmc} & S^{\rm mcml} \\ S^{\rm mlmc} & S^{\rm mlml} \end{pmatrix} \cdot\begin{pmatrix}x^{\rm mc} \\x^{\rm ml} \end{pmatrix}(t)\end{split}$

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

\begin{split}\begin{align} x^{\rm mc}(t+1) &= \left( C^{\rm mcmc} + C^{\rm mcml}\cdot S^{\rm mlml}\cdot \left(1-C^{\rm mlml}S^{\rm mlml}\right)^{-1} C^{\rm mlmc}\right)S^{\rm mcmc} x^{\rm mc}(t) \\ &= C^{\rm red} x^{\rm mc}(t), \end{align}\end{split}

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:

\begin{align} P^{-1} = \left(1-C^{\rm mlml}S^{\rm mlml}\right)^{-1} \end{align}

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}$$:

\begin{split}\begin{align} {\rm real}(P^{-1}) &= \left({\rm real}(P) + {\rm imag}(P)\cdot {\rm real}(P)^{-1} \cdot {\rm imag}(P)\right)^{-1}\\ {\rm real}(P^{-1}) &= -{\rm real}(P^{-1})\cdot {\rm imag}(P) \cdot {\rm real}(P)^{-1} \end{align}\end{split}

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()
circuit = Circuit()

class photontorch.networks.network.Network(components=None, connections=None, name=None)

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


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


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 calls initialize automatically. It could however be useful to call initialize if you want access to the reduced matrices without needing the response of the network to an input signal.

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

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

Reference:

https://www.osapublishing.org/optica/fulltext.cfm?uri=optica-3-12-1460&id=355743

class photontorch.networks.clements.ClementsNxN(N=2, capacity=None, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)

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

Reference:

https://www.osapublishing.org/optica/abstract.cfm?uri=optica-3-12-1460

__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

Reference:

https://journals.aps.org/prl/abstract/10.1103/NhysRevLett.73.58

class photontorch.networks.reck.ReckNxN(N=2, wg_factory=<function _wg_factory>, mzi_factory=<function _mzi_factory>, name=None)

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

Reference:

https://journals.aps.org/prl/abstract/10.1103/NhysRevLett.73.58

__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 expects M >= N. If M < N is desired, consider terminating with the transposed=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)

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