# Quantum gate synthesis¶

*Author: Josh Izaac*

This notebook works through the process used to produce the gate synthesis results presented in “Machine learning method for state preparation and gate synthesis on photonic quantum computers”.

We use the continuous-variable (CV) quantum optical circuit package Strawberry Fields, and in particular its TensorFlow backend, to perform quantum circuit optimization. By leveraging Tensorflow, we have access to a number of additional funtionalities, including GPU integration, automatic gradient computation, built-in optimization algorithms, and other machine learning tools.

## Variational quantum circuits¶

A key element of machine learning is optimization. We can use Tensorflow’s automatic differentiation tools to optimize the parameters of variational quantum circuits constructed using Strawberry Fields. In this approach, we fix a circuit architecture where the states, gates, and/or measurements may have learnable parameters \(\vec{\theta}\) associated with them. We then define a loss function based on the output state of this circuit. In this case, we define a loss function such that the action of the variational quantum circuit is close to some specified target unitary. For more details on the TensorFlow backend in Strawberry Fields, please see the Strawberry Fields documentation.

For arbitrary gate synthesis using optimization, we need to make use of a quantum circuit with a layer structure that is **universal** - that is, by ‘stacking’ the layers, we can guarantee that we can produce *any* CV state with at-most polynomial overhead. Therefore, the architecture we choose must consist of layers with each layer containing parameterized Gaussian *and* non-Gaussian gates. **The non-Gaussian gates provide both the nonlinearity and the universality of the model.** To this end,
we employ the CV quantum neural network architecture described below:

Here,

\(\mathcal{U}_i(\theta_i,\phi_i)\) is an N-mode linear optical interferometer composed of two-mode beamsplitters \(BS(\theta,\phi)\) and single-mode rotation gates \(R(\phi)=e^{i\phi\hat{n}}\),

\(\mathcal{D}(\alpha_i)\) are single mode displacements in the phase space by complex value \(\alpha_i\),

\(\mathcal{S}(r_i, \phi_i)\) are single mode squeezing operations of magnitude \(r_i\) and phase \(\phi_i\), and

\(\Phi(\lambda_i)\) is a single mode non-Gaussian operation, in this case chosen to be the Kerr interaction \(\mathcal{K}(\kappa_i)=e^{i\kappa_i\hat{n}^2}\) of strength \(\kappa_i\).

Reference

Killoran, N., Bromley, T. R., Arrazola, J. M., Schuld, M., Quesada, N., & Lloyd, S. (2018). “Continuous-variable quantum neural networks.” arXiv:1806.06871.

## Hyperparameters¶

First, we must define the **hyperparameters** of our layer structure:

`cutoff`

: the simulation Fock space truncation we will use in the optimization. The TensorFlow backend will perform numerical operations in this truncated Fock space when performing the optimization.`depth`

: The number of layer ansatz in our variational quantum circuit. As a general rule, increasing the number of layers (and thus, the number of parameters we are optimizing over) increases the optimizers chance of finding a reasonable local minimum in the optimization landscape.`reps`

: the number of steps in the optimization routine performing gradient descent

Some other optional hyperparameters include:

The standard deviation of initial parameters. Note that we make a distinction between the standard deviation of

*passive*parameters (those that preserve photon number when changed, such as phase parameters), and*active*parameters (those that introduce or remove energy from the system when changed).

```
[1]:
```

```
# Cutoff dimension
cutoff = 10
# gate cutoff
gate_cutoff = 4
# Number of layers
depth = 25
# Number of steps in optimization routine performing gradient descent
reps = 1000
# Standard deviation of initial parameters
passive_sd = 0.1
active_sd = 0.001
```

Note that, unlike in state learning, we must also specify a *gate cutoff* \(d\). This restricts the target unitary to its action on a \(d\)-dimensional subspace of the truncated Fock space, where \(d\leq D\), where \(D\) is the overall simulation Fock basis cutoff. As a result, we restrict the gate synthesis optimization to only \(d\) input-output relations.

## The layer parameters \(\vec{\theta}\)¶

We use TensorFlow to create the variables corresponding to the gate parameters. Note that each variable has shape `[depth]`

, with each individual element representing the gate parameter in layer \(i\).

```
[2]:
```

```
import tensorflow as tf
```

```
[3]:
```

```
# squeeze gate
sq_r = tf.Variable(tf.random_normal(shape=[depth], stddev=active_sd))
sq_phi = tf.Variable(tf.random_normal(shape=[depth], stddev=passive_sd))
# displacement gate
d_r = tf.Variable(tf.random_normal(shape=[depth], stddev=active_sd))
d_phi = tf.Variable(tf.random_normal(shape=[depth], stddev=passive_sd))
# rotation gates
r1 = tf.Variable(tf.random_normal(shape=[depth], stddev=passive_sd))
r2 = tf.Variable(tf.random_normal(shape=[depth], stddev=passive_sd))
# kerr gate
kappa = tf.Variable(tf.random_normal(shape=[depth], stddev=active_sd))
```

For convenience, we store the TensorFlow variables representing the parameters in a list:

```
[4]:
```

```
params = [r1, sq_r, sq_phi, r2, d_r, d_phi, kappa]
```

Now, we can create a function to define the \(i\)th layer, acting on qumode `q`

. This allows us to simply call this function in a loop later on when we build our circuit.

```
[5]:
```

```
# layer architecture
def layer(i, q):
Rgate(r1[i]) | q
Sgate(sq_r[i], sq_phi[i]) | q
Rgate(r2[i]) | q
Dgate(d_r[i], d_phi[i]) | q
Kgate(kappa[i]) | q
return q
```

## Constructing the circuit¶

Now that we have defined our gate parameters and our layer structure, we can import Strawberry Fields and construct our variational quantum circuit. Note that, to ensure the TensorFlow backend computes the circuit symbolically, we specify `eval=False`

.

```
[6]:
```

```
import strawberryfields as sf
from strawberryfields.ops import *
```

We must also specify the input states to the variational quantum circuit - these are the Fock state \(\ket{i}\), \(i=0,\dots,d\), allowing us to optimize the circuit parameters to learn the target unitary acting on all input Fock states within the \(d\)-dimensional subspace.

```
[7]:
```

```
import numpy as np
in_ket = np.zeros([gate_cutoff, cutoff])
np.fill_diagonal(in_ket, 1)
```

```
[8]:
```

```
# Create program and engine
prog = sf.Program(1)
eng = sf.Engine('tf', backend_options={"cutoff_dim": cutoff, "batch_size": gate_cutoff})
# Apply circuit of layers with corresponding depth
with prog.context as q:
Ket(in_ket) | q
for k in range(depth):
layer(k, q[0])
# Run engine
state = eng.run(prog, run_options={"eval": False}).state
ket = state.ket()
```

Here, we use the `batch_size`

argument to perform the optimization in parallel - each batch calculates the variational quantum circuit acting on a different input Fock state: \(U(\vec{\theta}) | n\rangle\).

Note that the output state vector is an unevaluated tensor:

```
[9]:
```

```
ket
```

```
[9]:
```

```
<tf.Tensor 'ket:0' shape=(4, 10) dtype=complex64>
```

## Performing the optimization¶

\(\newcommand{ket}[1]{\left|#1\right\rangle}\) With the Strawberry Fields TensorFlow backend calculating the resulting state of the circuit symbolically, we can use TensorFlow to optimize the gate parameters to minimize the cost function we specify. With gate synthesis, we minimize the overlaps in the Fock basis between the target and learnt unitaries via the following cost function:

where \(V\) is the target unitary, \(U(\vec{\theta})\) is the learnt unitary, and \(d\) is the gate cutoff. Note that this is a generalization of state preparation to more than one input-output relation.

For our target unitary, lets use Strawberry Fields to generate a 4x4 random unitary:

```
[10]:
```

```
from strawberryfields.utils import random_interferometer
target_unitary = np.identity(cutoff, dtype=np.complex128)
target_unitary[:gate_cutoff, :gate_cutoff] = random_interferometer(4)
```

This matches the gate cutoff of \(d=4\) that we chose above when defining our hyperparameters.

Using this target state, we calculate the cost function we would like to minimize. We must use TensorFlow functions to manipulate this data, as were are working with symbolic variables!

```
[11]:
```

```
in_state = np.arange(gate_cutoff)
# extract action of the target unitary acting on
# the allowed input fock states.
target_kets = np.array([target_unitary[:, i] for i in in_state])
target_kets = tf.constant(target_kets, dtype=tf.complex64)
# overlaps
overlaps = tf.real(tf.einsum('bi,bi->b', tf.conj(target_kets), ket))
mean_overlap = tf.reduce_mean(overlaps)
# cost
cost = tf.reduce_sum(tf.abs(overlaps - 1))
```

Now that the cost function is defined, we can define and run the optimization. Below, we choose the Adam optimizer that is built into TensorFlow.

```
[12]:
```

```
# Using Adam algorithm for optimization
optimiser = tf.train.AdamOptimizer()
min_cost = optimiser.minimize(cost)
# Begin Tensorflow session
session = tf.Session()
session.run(tf.global_variables_initializer())
```

We then loop over all repetitions, storing the best predicted fidelity value.

```
[13]:
```

```
overlap_progress = []
cost_progress = []
# Run optimization
for i in range(reps):
# one repitition of the optimization
_, cost_val, overlaps_val, ket_val, params_val = session.run(
[min_cost, cost, overlaps, ket, params])
# calculate the mean overlap
# This gives us an idea of how the optimization is progressing
mean_overlap_val = np.mean(overlaps_val)
# store cost at each step
cost_progress.append(cost_val)
overlap_progress.append(overlaps_val)
# Prints progress at every 100 reps
if i % 100 == 0:
# print progress
print("Rep: {} Cost: {:.4f} Mean overlap: {:.4f}".format(i, cost_val, mean_overlap_val))
```

```
Rep: 0 Cost: 3.2086 Mean overlap: 0.1978
Rep: 100 Cost: 0.9056 Mean overlap: 0.7736
Rep: 200 Cost: 0.6388 Mean overlap: 0.8403
Rep: 300 Cost: 0.5518 Mean overlap: 0.8621
Rep: 400 Cost: 0.4826 Mean overlap: 0.8794
Rep: 500 Cost: 0.3582 Mean overlap: 0.9105
Rep: 600 Cost: 0.2373 Mean overlap: 0.9407
Rep: 700 Cost: 0.1362 Mean overlap: 0.9659
Rep: 800 Cost: 0.0603 Mean overlap: 0.9849
Rep: 900 Cost: 0.0423 Mean overlap: 0.9894
```

## Results and visualisation¶

Plotting the cost vs. optimization step:

```
[14]:
```

```
from matplotlib import pyplot as plt
%matplotlib inline
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.sans-serif'] = ['Computer Modern Roman']
plt.style.use('default')
```

```
[15]:
```

```
plt.plot(cost_progress)
plt.ylabel('Cost')
plt.xlabel('Step');
```

We can use matrix plots to plot the real and imaginary components of the target and learnt unitary.

```
[16]:
```

```
learnt_unitary = ket_val.T[:gate_cutoff, :gate_cutoff]
target_unitary = target_unitary[:gate_cutoff, :gate_cutoff]
```

```
[17]:
```

```
fig, ax = plt.subplots(1, 4, figsize=(7, 4))
ax[0].matshow(target_unitary.real, cmap=plt.get_cmap('Reds'))
ax[1].matshow(target_unitary.imag, cmap=plt.get_cmap('Greens'))
ax[2].matshow(learnt_unitary.real, cmap=plt.get_cmap('Reds'))
ax[3].matshow(learnt_unitary.imag, cmap=plt.get_cmap('Greens'))
ax[0].set_xlabel(r'$\mathrm{Re}(V)$')
ax[1].set_xlabel(r'$\mathrm{Im}(V)$')
ax[2].set_xlabel(r'$\mathrm{Re}(U)$')
ax[3].set_xlabel(r'$\mathrm{Im}(U)$');
```

## Process fidelity¶

The process fidelity between the two unitaries is defined by

where:

\(\left|\Psi(V)\right\rangle\) is the action of \(V\) on one half of a maximally entangled state \(\left|\phi\right\rangle\):

\(V\) is the target unitary,

\(U\) the learnt unitary.

```
[18]:
```

```
I = np.identity(gate_cutoff)
phi = I.flatten()/np.sqrt(gate_cutoff)
psiV = np.kron(I, target_unitary) @ phi
psiU = np.kron(I, learnt_unitary) @ phi
```

```
[19]:
```

```
np.abs(np.vdot(psiV, psiU))**2
```

```
[19]:
```

```
0.985476461019703
```

Therefore, after 1000 repetitions, the learnt unitary synthesized via a variational quantum circuit has a process fidelity of 95.98% to the target unitary.

## Circuit parameters¶

We can also query the optimal variational circuit parameters \(\vec{\theta}\) that resulted in the learnt unitary. For example, to determine the maximum squeezing magnitude in the variational quantum circuit:

```
[20]:
```

```
np.max(np.abs(params_val[1]))
```

```
[20]:
```

```
0.22109538
```

## References¶

[1] Juan Miguel Arrazola, Thomas R. Bromley, Josh Izaac, Casey R. Myers, Kamil Brádler, and Nathan Killoran. Machine learning method for state preparation and gate synthesis on photonic quantum computers. Quantum Science and Technology, 4 024004, (2019).

[2] Killoran, N., Bromley, T. R., Arrazola, J. M., Schuld, M., Quesada, N., & Lloyd, S. “Continuous-variable quantum neural networks.” arXiv, 2018. arXiv:1806.06871

Note

`Click here`

to download this gallery page as an interactive Jupyter notebook.

## Contents

## Downloads