## Simple qubit rotation with Pennylane and TFQ

In this jupyter file we define a variational quantum circuit $V(\theta)$ that rotates an initial state $|0000\rangle$ into a target state with equal superposition $\frac{1}{\sqrt{|\Sigma|}}\sum_{\sigma_i} | \sigma_i \rangle$. The aim is that  $\frac{1}{\sqrt{|\Sigma|}}\sum_{\sigma_i} \langle \sigma_i | V(\theta) | 0000\rangle = 1$.


```python
import pennylane as qml
from pennylane import numpy as np
from tqdm.notebook import tqdm
```

## Pennylane version

Define the device `default.qubit` and a circuit where one layer contains a general rotation $R(\phi, \theta, \omega) = R_z(\omega)R_x(\theta)R_z(\phi)$ on each qubit, followed by entangling gates. We apply 2 layers. The $R(\phi, \theta, \omega)$ gate is a native in pennylane `qml.Rot()`. We use 4 qubits.


```python
dev1 = qml.device("default.qubit", wires=4)
```


```python
target_state = np.ones(2**4)/np.sqrt(2**4)
density = np.outer(target_state, target_state)

@qml.qnode(dev1)
def circuit(params):
    for j in range(2): # 2 layers
        for i in range(4): # 4 qubits
            qml.Rot(*params[j][i], wires=i)
        qml.CNOT(wires=[0,1])
        qml.CNOT(wires=[2,3])
        qml.CNOT(wires=[1,2])
    return qml.expval(qml.Hermitian(density, wires=[0,1,2,3]))
```

Define a cost function. In our case we want the overlap of the circuit output to be maximal with the targe_state. Therefore we minimize $1-\frac{1}{\sqrt{|\Sigma|}}\sum_{\sigma_i}\langle \sigma_i | V(\theta) | 0000\rangle$


```python
def cost(var):
    return 1-circuit(var)
```

Initialize the parameters randomly. The shape of the parametrs is $(layers, number of qubits, 3)$ because for each layer and qubit we have 3 paramters.


```python
init_params = np.random.rand(2, 4, 3) # 2 layers, 4 qubits, 3 parameters per rotation
print(cost(init_params))
```

    0.9791545794006542


### Training

For the training we define a gradient descent optimizer and continuously update the parameters 


```python
# initialise the optimizer
opt = qml.GradientDescentOptimizer(stepsize=0.4) # stepsize is the learning rate

# set the number of steps
steps = 100
# set the initial parameter values
params = init_params

for i in tqdm(range(steps)):
    # update the circuit parameters
    params = opt.step(cost, params)

    if (i + 1) % 10 == 0:
        print("Cost after step {:5d}: {: .7f}".format(i + 1, cost(params)))

print("Optimized rotation angles: {}".format(params))
```

    /home/apozas/anaconda3/lib/python3.7/site-packages/ipykernel_launcher.py:9: TqdmDeprecationWarning: This function will be removed in tqdm==5.0.0
    Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`
      if __name__ == '__main__':



    HBox(children=(FloatProgress(value=0.0), HTML(value='')))


    Cost after step    10:  0.7926067
    Cost after step    20:  0.1511231
    Cost after step    30:  0.0142782
    Cost after step    40:  0.0049359
    Cost after step    50:  0.0024388
    Cost after step    60:  0.0013136
    Cost after step    70:  0.0007371
    Cost after step    80:  0.0004231
    Cost after step    90:  0.0002461
    Cost after step   100:  0.0001442
    
    Optimized rotation angles: [[[ 4.37680865e-01  5.83405123e-09  3.20036600e-01]
      [ 5.87685497e-01 -5.85645201e-08 -2.78093370e-02]
      [ 9.77501871e-02 -4.70612985e-02  5.59492498e-01]
      [ 3.37501386e-01  1.05926966e+00 -7.16822962e-02]]
    
     [[ 9.64584837e-01  1.57079632e+00  3.82648191e-09]
      [ 6.43859415e-01  1.57079627e+00 -2.28028241e-08]
      [ 1.10680485e+00  1.56630413e+00  4.68466476e-02]
      [ 2.36741061e-01  5.17397955e-01 -1.43807908e-01]]]


If we check the final state we see that appart from a global pahse we find the target state.


```python
circuit(params)
dev1.state
```




    array([-0.21085093-0.13604769j, -0.20637046-0.13970451j,
           -0.20998474-0.13732926j, -0.20530324-0.14079939j,
           -0.20998475-0.13732924j, -0.20530324-0.14079937j,
           -0.21085091-0.13604769j, -0.20637045-0.1397045j ,
           -0.21085091-0.13604769j, -0.20637044-0.1397045j ,
           -0.20998474-0.13732924j, -0.20530324-0.14079937j,
           -0.20998474-0.13732926j, -0.20530323-0.14079939j,
           -0.21085093-0.13604769j, -0.20637046-0.1397045j ])



## TFQ version


```python
import tensorflow as tf
import tensorflow_quantum as tfq
import tensorflow.keras as keras

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
```

### Generate symbols

I did not figure out, how I can do the gradients in TFQ without using symbols, this seems to be mandatory for tfq. I don't reallay see the advantage so far. Especially the evaluation with the resolver function seems a bit odd and unnecessary.

The definition of the circuit is almost the same as in Pennylane.

There is no option to define a projections operator to calculate the overlap with a target state because they are not unitary. This gives a bit less room to play with TFQ. I assume the idea was, that these gates are not really feasible on a real quantum device.

% Instead of defining a hermitian matrix that gives the overlap with the target state, we can simply measure the operator $M = 1/4*(X_1 + X_2 + X_3 + X_4)$ and minimize the loss $1-\langle M \rangle$.


```python
def generate_circuit(nr_of_qubits, layers):
    qubits = cirq.GridQubit.rect(1, nr_of_qubits) # Define qubit grid. In this case 
    nr_parameters = 3*nr_of_qubits*layers # 3 params for each qubit and layer

    symb = sympy.symbols('theta0:'+str(nr_parameters))
    symbols = np.array(symb)
    symbols = symbols.reshape(layers, nr_of_qubits, 3)
    circuit = cirq.Circuit()

    for l in range(layers):
        # Add a series of single qubit rotations.
        for i, qubit in enumerate(qubits):
            circuit += cirq.rz(symbols[l][i][0])(qubit)
            circuit += cirq.rx(symbols[l][i][1])(qubit)
            circuit += cirq.rz(symbols[l][i][2])(qubit)

        circuit += cirq.CZ(qubits[0], qubits[1])
        circuit += cirq.CZ(qubits[2], qubits[3])
        circuit += cirq.CZ(qubits[1], qubits[2])

    op = 1/4*(cirq.X(qubits[0]) + cirq.X(qubits[1]) + cirq.X(qubits[2]) + cirq.X(qubits[3]))         
    return circuit, op, list(symb)

nr_of_qubits = 4
layers = 2
tf_circuit, op, (symbols) = generate_circuit(nr_of_qubits, layers)
SVGCircuit(tf_circuit) 
```




    

    



### Training

One can leverage all the Tensorflow machinery for training quantum circuits. We will now insert the previous circuit in a ``tf.keras`` model in order to train it.

First of all, the circuit must be converted into a layer so it can be inserted in a model. The most direct choice is the ``PQC`` (which stands for Parameterized Quantum Circuit) layer. This layer requires as additional specifications the operator we are going to measure in the end, the number of evaluations, and the way the gradients are going to be computed.


```python
outputs = tfq.layers.PQC(tf_circuit,         # Circuit to be transformed into tf layer
                         1-op,               # Output of the layer
                         repetitions=1024,
                         differentiator=tfq.differentiators.ParameterShift())
```

Next, we can instantiate a model, taking an arbitrary input and outputting the result of the measurement of $M$


```python
model = tf.keras.Sequential([
    keras.layers.Input(shape=(), dtype=tf.string),
    outputs
])
```

In order to compile and fit the model, one needs to define a loss function (the quantity to optimize) and an optimizer. We want to optimize the expectation value of $M$, this is, the output of the model. Tensorflow needs this, however, as a function ``f(real_values,predictions)``


```python
loss = lambda real, pred: pred    # Forget about any "real" values, just consider as loss the model's output

model.compile(loss=loss,
              optimizer=keras.optimizers.SGD(learning_rate=0.4)) # Same optimizer as the Pennylane case
```

Finally, the ``fit`` function is designed for classification problems, and thus it needs of some inputs and corresponding "true" outputs. Our variational quantum circuit has none of these, so we just insert empty attributes: the input will be an empty quantum circuit, which corresponds to setting the initial state to $|0000\rangle$; the output will be an empty array (you can choose anything you want, since the loss function will discard whatever you set)


```python
dummy_input  = tfq.convert_to_tensor([cirq.Circuit()])
dummy_output = np.array([[]])
```

An it is finally time to train!


```python
model.fit(dummy_input, dummy_output, epochs=steps)
```

    WARNING:tensorflow:AutoGraph could not transform <bound method ParameterShift.differentiate_sampled of <tensorflow.python.eager.function.TfMethodTarget object at 0x7ff2e854d450>> and will run it as-is.
    Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output.
    Cause: unexpected indent (<unknown>, line 77)


    Train on 1 samples
    Epoch 1/100
    WARNING: AutoGraph could not transform <bound method ParameterShift.differentiate_sampled of <tensorflow.python.eager.function.TfMethodTarget object at 0x7ff2e854d450>> and will run it as-is.
    Please report this to the TensorFlow team. When filing the bug, set the verbosity to 10 (on Linux, `export AUTOGRAPH_VERBOSITY=10`) and attach the full output.
    Cause: unexpected indent (<unknown>, line 77)
    1/1 [==============================] - 7s 7s/sample - loss: 1.2593
    Epoch 2/100
    1/1 [==============================] - 7s 7s/sample - loss: 1.1460
    Epoch 3/100
    1/1 [==============================] - 8s 8s/sample - loss: 1.0508
    Epoch 4/100
    1/1 [==============================] - 7s 7s/sample - loss: 0.9326
    Epoch 5/100
    1/1 [==============================] - 7s 7s/sample - loss: 0.8359
    Epoch 6/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.7749
    Epoch 7/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.7402
    Epoch 8/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.7217
    Epoch 9/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.7085
    Epoch 10/100
    1/1 [==============================] - 7s 7s/sample - loss: 0.7012
    Epoch 11/100
    1/1 [==============================] - 7s 7s/sample - loss: 0.6929
    Epoch 12/100
    1/1 [==============================] - 7s 7s/sample - loss: 0.6855
    Epoch 13/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.6724
    Epoch 14/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6670
    Epoch 15/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6606
    Epoch 16/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6538
    Epoch 17/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6460
    Epoch 18/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6372
    Epoch 19/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6265
    Epoch 20/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.6167
    Epoch 21/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.6050
    Epoch 22/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.5884
    Epoch 23/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.5806
    Epoch 24/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.5713
    Epoch 25/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.5571
    Epoch 26/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.5430
    Epoch 27/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.5332
    Epoch 28/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.5229
    Epoch 29/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.5059
    Epoch 30/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.4883
    Epoch 31/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.4717
    Epoch 32/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.4580
    Epoch 33/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.4399
    Epoch 34/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.4233
    Epoch 35/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.4072
    Epoch 36/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.3921
    Epoch 37/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.3726
    Epoch 38/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.3511
    Epoch 39/100
    1/1 [==============================] - 7s 7s/sample - loss: 0.3335
    Epoch 40/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.3086
    Epoch 41/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.2837
    Epoch 42/100
    1/1 [==============================] - 11s 11s/sample - loss: 0.2695
    Epoch 43/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.2505
    Epoch 44/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.2305
    Epoch 45/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.2085
    Epoch 46/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1870
    Epoch 47/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1733
    Epoch 48/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1528
    Epoch 49/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1382
    Epoch 50/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1270
    Epoch 51/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1167
    Epoch 52/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.1011
    Epoch 53/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0903
    Epoch 54/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0767
    Epoch 55/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0640
    Epoch 56/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0532
    Epoch 57/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0449
    Epoch 58/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0386
    Epoch 59/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0327
    Epoch 60/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0273
    Epoch 61/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0225
    Epoch 62/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0181
    Epoch 63/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0132
    Epoch 64/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0107
    Epoch 65/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0098
    Epoch 66/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0078
    Epoch 67/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0063
    Epoch 68/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0049
    Epoch 69/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0044
    Epoch 70/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0044
    Epoch 71/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0029
    Epoch 72/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0020
    Epoch 73/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0020
    Epoch 74/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0020
    Epoch 75/100
    1/1 [==============================] - 6s 6s/sample - loss: 9.7656e-04
    Epoch 76/100
    1/1 [==============================] - 5s 5s/sample - loss: 9.7656e-04
    Epoch 77/100
    1/1 [==============================] - 6s 6s/sample - loss: 9.7656e-04
    Epoch 78/100
    1/1 [==============================] - 5s 5s/sample - loss: 4.8828e-04
    Epoch 79/100
    1/1 [==============================] - 6s 6s/sample - loss: 4.8828e-04
    Epoch 80/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0000e+00
    Epoch 81/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0000e+00
    Epoch 82/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0000e+00
    Epoch 83/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0000e+00
    Epoch 84/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0000e+00
    Epoch 85/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 86/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 87/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 88/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 89/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 90/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 91/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 92/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 93/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 94/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 95/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 96/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 97/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 98/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 99/100
    1/1 [==============================] - 5s 5s/sample - loss: 0.0000e+00
    Epoch 100/100
    1/1 [==============================] - 6s 6s/sample - loss: 0.0000e+00





    <tensorflow.python.keras.callbacks.History at 0x7ff2cd65ab10>



Like in the Hello World example we can extract the wave function we see that we get the equal superposition state with some global phase.


```python
simulator = cirq.Simulator()
dictionary = {symb: model.trainable_variables[0].numpy()[i] for i, symb in enumerate(symbols)}
resolver = cirq.ParamResolver(dictionary)
resolved_circuit = cirq.resolve_parameters(tf_circuit, resolver)
output_state_vector = simulator.simulate(tf_circuit, resolver).final_state
output_state_vector
```




    array([-0.00286266+0.01872098j, -0.00046927+0.2587476j ,
           -0.0138061 +0.10716106j,  0.0847271 -0.19069251j,
            0.05543072-0.0915546j ,  0.00039238-0.29273975j,
           -0.02523318+0.16849318j,  0.265944  -0.46552533j,
            0.08174167+0.0482669j ,  0.17641312+0.06576671j,
            0.11405597+0.04105367j, -0.3545978 -0.23970872j,
            0.03512595-0.02338716j,  0.09215477-0.29585007j,
           -0.06562621+0.1250625j ,  0.2509636 -0.20192285j], dtype=complex64)




```python

```

b7a550638091747f702af01bd3ec6f52cb3bd6e6
ipynb
Jupyter Notebook
Simple_qubit_rotation_Pennylan_TFQ.ipynb
apozas/Pennaylane_and_TFQ
4d27e1ba15ce82b0299570f5a70b42f3ceb68466
Apache-2.0
Simple_qubit_rotation_Pennylan_TFQ.ipynb
apozas/Pennaylane_and_TFQ
4d27e1ba15ce82b0299570f5a70b42f3ceb68466
Apache-2.0
Simple_qubit_rotation_Pennylan_TFQ.ipynb
apozas/Pennaylane_and_TFQ
4d27e1ba15ce82b0299570f5a70b42f3ceb68466
Apache-2.0
Qwen/Qwen-72B
1. YES
2. YES