Quantum Artificial Intelligence

#ArtificialIntelligence   #technology   #MachineLearning In this article we want to develop a neural network that can measure a simulated qubit and return the correct value between 0 or 1.

Initially, the qubit will generate errors, our goal is to train a neural network that creates the parameters for the qubit to fix these errors.

First, we need to define our quantum circuit using the using cirq library and install or import tensorflow.

!pip install tensorflow==2.1.0
!pip3 install -U tensorflow-quantum
%matplotlib inline
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow_quantum as tfq
import cirq
from cirq.contrib.svg import SVGCircuit
import sympy
import numpy as np
a, b = sympy.symbols('a b')
q0, q1 = cirq.GridQubit.rect(1, 2)
circuit = cirq.Circuit(
cirq.rx(a).on(q0),
cirq.ry(b).on(q1), cirq.CNOT(control=q0, target=q1))
cirq_simulator = cirq.Simulator()
z0 = cirq.Z(q0)

So we have told python to allocate space in memory for our circuit, now we want to develop our neural network that will control it. We will not be too extravagant with our neural network, we will just have the input, a hidden layer of size 10 and an output of size 3.

The 3 outputs the network corresponds to the three parameters we already set.

After creating a controller and a quantum circuit and we need to connect them, to do this we will create inputs for both the controller and the circuit, starting with the circuit:

circuits_input = tf.keras.Input(shape=(),
dtype=tf.string,
name='circuits_input')

Then we need to create inputs for our commands:

commands_input = tf.keras.Input(shape=(1,),
dtype=tf.dtypes.float32,
name='commands_input')

Now we want to connect the two models, we will go about this by creating layers for both the neural network and the quantum circuit.

dense_2 = controller(commands_input)
expectation_layer = tfq.layers.ControlledPQC(model_circuit,
operators = cirq.Z(qubit))
expectation = expectation_layer([circuits_input, dense_2])
model = tf.keras.Model(inputs=[circuits_input, commands_input],
outputs=expectation)
tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)

We connect the two models by creating a layer for the controller, another layer for the quantum circuit input and connecting them using a perimeter quantum circuit, which trains our quantum circuit.

Now that we have our model time to create our data as well as calibrate our circuit input.

To do that we will create two data points to map, our input values for our output.

commands = np.array([, ], dtype=np.float32)
expected_outputs = np.array([, [-1]], dtype=np.float32)

To calibrate our circuit inputs we need to set our theater values to a random number, between 0 and 2*pi.

We also want to make sure we have two copies of the circuit so that we can create data values for both inputs.

random_rotations = np.random.uniform(0, 2 * np.pi, 3)
noisy_preparation = cirq.Circuit(cirq.rx(random_rotations)(qubit), cirq.ry(random_rotations)(qubit), cirq.rz(random_rotations)(qubit))
datapoint_circuits = tfq.convert_to_tensor([noisy_preparation] * 2)

And now we can train our model! You can see from the plot that our error is pretty low by the end — try changing the number of epochs to see what the minimum number of epochs is to still get good results.

loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)
history = model.fit(x=[datapoint_circuits, commands],
y=expected_outputs,
epochs=100,
verbose=0)
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

When you run the command on your notebook, you will discover the error rate is low. This is a good thing but we still do not know for sure if the qubit is in the right state. So we want to check!

def check_error(command_values, desired_values):
"""Based on the value in `command_value` see how well you could prepare
the full circuit to have `desired_value` when taking expectation w.r.t. Z."""
params_to_prepare_output = controller(command_values).numpy()
full_circuit = noisy_preparation + model_circuit
for index in [0, 1]:
state = cirq_simulator.simulate(
full_circuit,
{s:v for (s,v) in zip(control_params,
params_to_prepare_output[index])}
).final_state
expectation = z0.expectation_from_wavefunction(state, {qubit:
0}).real
print(f'For a desired output (expectation) of
{desired_values[index]} with'
f' noisy preparation, the controller\nnetwork found the
following '
f'values for theta: {params_to_prepare_output[index]}\nWhich
gives an'
f' actual expectation of: {expectation}\n')
check_error(commands, expected_outputs)

What if you wanted to add more data between 1 and 0? As we know qubits can be 1,0 both or in between the two.

commands = np.array([, , , ], dtype=np.float32)
# The desired Z expectation value at output of quantum circuit.
expected_outputs = np.array([, [-1], [.5], [-0.5]], dtype=np.float32)
random_rotations = np.random.uniform(0, 2 * np.pi, 3)
noisy_preparation = cirq.Circuit(
cirq.rx(random_rotations)(qubit),
cirq.ry(random_rotations)(qubit),
cirq.rz(random_rotations)(qubit))
datapoint_circuits = tfq.convert_to_tensor([noisy_preparation] * 4)  # Make four copied of this circuit
loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)
history = model.fit(x=[datapoint_circuits, commands],
y=expected_outputs,
epochs=300,
verbose=0)
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()
def check_error(command_values, desired_values):
params_to_prepare_output = controller(command_values).numpy()
full_circuit = noisy_preparation + model_circuit
for index in range(4):
state = cirq_simulator.simulate(
full_circuit,
{s:v for (s,v) in zip(control_params, params_to_prepare_output[index])}
).final_state
expectation = z0.expectation_from_wavefunction(state, {qubit: 0}).real
print(f'For a desired output (expectation) of {desired_values[index]} with'
f' noisy preparation, the controller\nnetwork found the following '
f'values for theta: {params_to_prepare_output[index]}\nWhich gives an'
f' actual expectation of: {expectation}\n')
check_error(commands, expected_outputs)