The qINTERCONNECT solver is a Python module that can be called from within the INTERCONNECT scripting environment or from an external Python environment (e.g. a Jupyter notebook). Since qINTERCONNECT requires a GUI license to run, two licenses are required to run qINTERCONNECT directly within the INTERCONNECT scripting environment. Users with only one available license must run qINTERCONNECT from an external Python environment. First, verify that the version of Python being used is the packaged version. Next, add the necessary directories to path and import the qINTERCONNECT solver as shown below.

import sys

from qinterconnect.solver import QSolver

Additionally, Matplotlib and NumPy Python modules must be loaded

from matplotlib import pyplot as plt

import numpy as np

# QSolver

A qINTERCONNECT simulation is configured by creating a QSolver object with a set of parameters. For more information on the qINTERCONNECT solver physics, see qINTERCONNECT Solver Introduction.

The QSolver object contains several methods to further configure and run a simulation. These methods are described in the following table.

Syntax | Description |
---|---|

simulation=QSolver(**params) | Creates a simulation object with parameters given by the params dictionary (described below) |

simulation.add_loss_ports() |
Replaces lossy waveguides with waveguide couplers whose reflection coefficients equal the waveguide loss Takes the optional input ‘lossless=True’, in which case the simulation is performed without adding loss channels but instead uses lossless waveguides. |

Result=simulation.run() | Runs the simulation and returns the probability of success and, if an expected output was specified, the fidelity. |

Result=simulation.run_sweep() | Runs a simulation sweep and stores the probability of success and fidelity for each iteration of the sweep. The parameter being swept over must be specified as shown below. |

## Parameters

The properties of each simulation are set within the params argument of the QSolver object. The params argument is a dictionary defining the simulation parameters with the following fields:

Field | Type | Description |
---|---|---|

mode_per_channel | integer | Number of modes per channel |

nchannel | integer | Number of channels in the photonic circuit |

interconnect_file | string | Name of the INTERCONNECT file containing the photonic circuit to be simulated |

circuit | string | Name of the compound element containing the photonic circuit |

input_ports | list of strings | List of the names of the input ports as per the INTERCONNECT file |

output_ports | list of strings | List of the names of the output ports as per the INTERCONNECT file |

nmax | integer | Maximum number of photons up to which the simulation will include |

input_dims | list of integers | Independently specifies the maximum number of photons to be simulated per channel. For example, \( \left[ 3,2,3 \right] \) means the Hilbert space of the first and third channels will be truncated at 3 photons, and the Hilbert space of the second channel will be truncated at 2 photons |

ket_in | dictionary |
Dictionary with keys corresponding to tuples of input states in the Fock basis and values with the corresponding probability amplitude in the polar coordinate basis. For example, { (0,1) : [0.5,0], Corresponds to an input state with a probability amplitude of 0.5 of zero photons in the first channel and one photon in the second channel, and a probability amplitude of 0.866 of one photon in the first channel and zero photons in the second channel $$ \vert \psi_{in} \rangle = 0.5 \vert 01 \rangle + 0.866 \vert 10 \rangle $$ |

measurement_mask | list of integers |
Integers in the list correspond to the type of projective measurement performed on each mode at the output of every channel. A value of 100 means the mode is not directly measured, but its state is included at the end of the simulation. A value of 200 means the mode is not directly measured, but the information in the mode is discarded (traced over). Otherwise, the integer value corresponds to how many photons we are expecting to measure for that mode, discarding all other cases. For example, a measurement mask of \( \left[100,1,0\right] \) means we only keep the states where the second mode is populated by a single photon, and the third mode is populated by zero photons, and do not measure the first mode, but we want it to be included in the final density matrix. For more information on measurement, see Solver Physics |

frequency | float |
Defines the frequency at which the INTERCONNECT simulation is performed. Defaults to 193.41449 THz (1550 nm). |

## Other options of the params dictionary

The fidelity of a quantum circuit can be calculated if the expected output state is specified.

ket_out | dictionary |
Dictionary with keys corresponding to tuples of expected output states in the Fock basis and values with the corresponding probability amplitude in the polar coordinate basis, allowing fidelity to be calculated. Expected output states are only specified for modes where the measurement mask was set to ‘100’. |

If manually including loss channels (not with the .add_loss_ports() method), information about the loss channels must also be specified:

loss_inputs | list of strings | Names of the loss input channels |

loss_outputs | list of strings |
Names of the loss output channels |

loss_dims | list of integers |
Specifies the maximum number of photons to be simulated in each loss channel |

Sweeps over parameter values can be performed with the “parameters” argument of params:

parameters | dictionary |
The key of each entry in the dictionary is the name of the parameter to sweep through, which must correspond to the name of a property of the compound element in the interconnect file. The corresponding value is a list that determines the range of the sweep in the format \( \left[start value, end value, step size\right] \). If multiple parameters are specified, the sweep is performed over the grid of parameter combinations. |

Optionally, a custom S-matrix can be used to run a simulation in place of using an S-matrix generated from INTERCONNECT.

S-matrix | array |
Takes a numpy array representing the S-parameters of a circuit. For example, the S-matrix for a 50:50 waveguide coupler can be written as S=np.array(([1,1j],[1j,1]))/np.sqrt(2) |

The following parameters are used for multi-frequency simulations.

frequency_start | float |
The starting frequency for the INTERCONNECT s-parameter simulation. Units are in Hz. |

frequency_stop |
float |
The final frequency for the INTERCONNECT s-parameter simulation. Units are in Hz. |

frequency_points |
list of floats |
Specifies the number of frequency points in the sweep. |

## Results

After the simulation has run, we can use the following commands to access various useful properties of the QSolver object.

simulation = QSolver(**params)

simulation.run()

# simulation has the following results:

Syntax | Description |
---|---|

simulation.results | Returns the probability of success of the simulation based on the values provided in the measurement mask, as well as the fidelity, if an expected output state is provided. |

simulation.rho_final[:] | The final density matrix after measurement |

simulation.rho_expected[:] | The expected density matrix based on ‘ket_out’ |

simulation.all_prob_success | Probability of success based on the measurement mask |

simulation.all_fidelity | Fidelity, if an expected output state is provided. |

simulation.input_density_matrix[:] | Density matrix of the input state based on ‘ket_in’ |

simulation.mapping_table | Returns the order of the Fock basis states that the input density matrix is based on |

simulation.qs | Returns the quantum S-matrix in the basis of Fock states |

simulation.S | Returns the S-matrix in the basis of modes |

simulation.reduced_mapping_table | Returns the order of the Fock basis states that the output density matrix, after projective measurement has been performed, is based on |

We can also access any input parameter to our simulation by calling, for example, simulation.ket_in

After a sweep is performed, we can access the fidelity and probability of success over the range of the sweep through the following commands

simulation = QSolver(**params)

Results = simulation.run_sweep()

# Results has the following results:

Syntax | Description |
---|---|

Results["Fidelity"] | Returns the fidelity as a list if an expected density matrix was provided |

Results["Probability_of_Success"] | Returns the probability of success as a list |

Results["paramVals"] | Returns the parameter being swept over as a list |

## Example_1

The following script shows the syntax for configuring the params argument and running a sweep over a circuit parameter “theta_1” in an INTERCONNECT project file named ‘NLS_ideal.icp’. The fidelity is plotted as a function of the swept parameter and the final density matrix for the last simulation in the sweep is shown.

nlsparams_sweep = {

"mode_per_channel": 1,

"nchannel": 3,

"interconnect_file": "NLS_ideal.icp",

"circuit": "NLS",

"input_ports": ["signal_in", "ancilla1_in", "ancilla0_in"],

"output_ports": ["signal_out", "ancilla1_out", "ancilla0_out"],

"parameters":{

"theta_1":[1.00,1.3,0.025]

},

"nmax":3,

"input_dims": [3,3,3],

"ket_in":{

(0, 1, 0): [0.4,0],

(1, 1, 0): [0.1,0],

(2, 1, 0): [0.91104,0]

},

"ket_out":{

(0,): [0.4,0],

(1,): [0.1,0],

(2,): [-0.91104,0]

},

"measurement_mask":[100,1,0]

}

nls_sweep = QSolver(**nlsparams_sweep)

results = nls_sweep.run_sweep()

data = np.array(results["Fidelity"])

theta_1 = np.array(results["paramVals"])

plt.plot(theta_1[0], data)

plt.xlabel(r"$\theta$$_{1}$",size=20)

plt.ylabel("Fidelity",size=15)

print('Final density matrix for the last simulation in the sweep is: \n',nls_sweep.rho_final[:])

## UnitaryCircuit

The UnitaryCircuit method can be used to automatically generate a circuit in INTERCONNECT with an S-matrix corresponding to a user-defined unitary matrix. The circuit is constructed using Clements decomposition, resulting in a mesh of a beamsplitter elements which consist of 50:50 directional couplers and phase shifters [1]. The method must first be imported from the qINTERCONNECT solver as shown below.

from qinterconnect.solver import UnitaryCircuit

The syntax for calling the UnitaryCircuit method is given in the following table:

Syntax | Description |
---|---|

myU=UnitaryCircuit(U) | Creates a simulation object from a unitary matrix input U. |

myU.construct_circuit("Clements","myfilename.icp") |
Performs the creation of the unitary circuit using Clements decomposition and saves it in an INTERCONNECT project file |

## Example_2

For example, the circuit for a randomly generated 6x6 unitary matrix can be created as follows:

from qinterconnect.solver import UnitaryCircuit from scipy.stats import unitary_group

unitary_matrix=unitary_group.rvs(6) myU=UnitaryCircuit(unitary_matrix) myU.construct_circuit("Clements","unitary_6x6.icp")

For a more detailed example see Unitary Circuit Generation and Simulation – Ansys Optics.

## Related Publications

- W. R. Clements, P. C. Humphreys, B. J. Metcalf, W. S. Kolthammer and I. A. Walmsley, "Optimal design for universal multiport interferometers", Optica Vol.
**3**, No. 12, p. 1460-1465, 2016.