# How to learn parameters in a quantum circuit, given an interference pattern?

Using cirq, I have the following quantum circuit, with three parameters: phi, alpha and beta:

q0 = cirq.GridQubit(0,0)
q1 = cirq.GridQubit(0,1)

phi = sp.Symbol('phi')
alpha = sp.Symbol('alpha')
beta = sp.Symbol('beta')
circuit = cirq.Circuit([
cirq.H(q0),
cirq.CNOT(q0,q1),
cirq.XPowGate(exponent=phi)(q0),
cirq.H(q1),
cirq.X(q1) ** (alpha*phi),
cirq.XX(q0,q1) ** (beta*phi),
cirq.measure(q0, q1, key='m')
])
SVGCircuit(circuit)

This circuit is just some randomness I was playing with, so please don't take it too seriously...

If I fix the values of alpha=2 and beta=-3 and perform a scan over phi from $$[0,2\pi]$$ I can generate a distribution of the number of times the system is measured in state $$|00\rangle$$ (actually not 100% sure I am right here)...

Example scan with phi=0.25

>>> resolver = cirq.ParamResolver({'phi':0.25,'alpha':alpha,'beta':beta})
>>> trials = cirq.Simulator().run(circuit, resolver, repetitions=1000)
>>> trials.histogram(key='m')
Counter({0: 726, 2: 124, 3: 122, 1: 28})

Plotting trials.histogram(key='m')[0] as a function of phi, I get:

## Finally, my question...

What would be the best way to set this up as a (hybrid-)ML problem, in order to solve what the values of alpha and beta, given a prior distribution? I.e. using this above distribution, I could turn this into a regression problem with a little NN to try and learn alpha and beta

I can think of possible ways to do it in a manual way by setting up a function to just return the distribution given parameters alpha and beta, before calculating the loss etc. I was hoping there was a better/cleaner way to do this, perhaps via tfq?

Any suggestions? Or suggestions to move to qiskit/pennylane?

• I don't know about the machine learning side, but what does the fourier transform of your data look like? Nov 12 '20 at 13:10
• @DaftWullie I could potentially plot that - why are you interested in seeing it? This is just an arbitrary problem, im not particular interested in actually finding an actual solution - if that makes sense. cheers Nov 12 '20 at 13:54
• I suppose, for me, I want to know what I can understand about a system first, before I go to some relatively black-box method that just spits out an answer but might not help my understanding. I'm guessing here that the values you get out from the Fourier transform would tell you a lot about the different angles used in the system, even if it doesn't immediately help you identify which is which. Nov 12 '20 at 14:24
• Fair enough, and thanks for thinking about it! Yeah, thought that might be why you asked. I attempted to make a FT but it came out crazy - a problem on my side - I think it'd be very possible to do it to infer some information. My question was more of a technical one, inspired by this: tensorflow.org/quantum/tutorials/… but instead of having three parameters acting on one qubit, i'm trying to learn how three parameters act of 2 entangled qubits to give an interference - which I could get from the expectation values Nov 12 '20 at 14:57

I think there might be a lot to unpack here. Just making sure my understanding of the problem is correct:

I hand you a quantum circuit with some free parameters and then I hand you some samples from that quantum circuit at specific parameter values, but I don't tell you what the parameter values are and then your goal is to try and determine what the parameter values are via some optimization or ML type algorithm that takes the samples I gave you as input. I'm not entirely sure this is what you're after, but I can offer you a simple piece of code that does some of the major steps here in TFQ, which can hopefully serve as a good starting point to play around with things:

my_symbols = ...
my_circuit = cirq.Circuit(...) # created containing my_symbols

true_parameters = [1,2,3]
guess_parameters = [4,5,6]

# guess_distribution is a ragged tensor of shape [n_circuits, n_samples, n_qubits]
guess_distribution = tfq.layers.Sample(my_circuit, symbol_names=my_symbols, symbol_values = guess_parameters, repetitions=10000)

# true_distribution is a ragged tensor of shape [n_circuits, n_samples, n_qubits]
true_distribution = tfq.layers.Sample(my_circuit, symbol_names=my_symbols, symbol_values = true_parameters, repetitions=10000)

# Compute the histograms. [n_circuits, 2^n_qubits + 1]
guess_probs = tf.math.bincount(tf.cast(guess_distribution.to_tensor(), tf.dtypes.int32)) / 10000
true_probs = tf.math.bincount(tf.cast(true_distribution.to_tensor(), tf.dtypes.int32)) / 10000

# Measure the distribution overlap between your parameter guess and the true parameters. scalar tensor.
kl_overlap = tf.keras.losses.KLDivergence()(guess_probs, true_probs)

You could go a lot of different ways with this snippet. For example:

1. You could try different ways to characterize distribution closeness. KL divergence is a good way, but it is by no means the only way. Like you mention in your question there might be some ways of computing a few expectation values from your circuit that work as a good proxy quantity for distribution closeness.

2. You could remain focused on the single circuit case and attempt to optimize the values of guess_parameters using an optimizer of your choice (gradient free ones like this one could work well). If you would like to use gradient based optimizers like the ones found in TensorFlow, you might need to investigate defining a custom gradient for your specific problem of "measuring distribution closeness" (since sampling on it's own is not differentiable) using the @tf.custom_gradient tag. This would then be compatible with your standard GradientTape workflow:

x = tf.constant(my_guess)
g.watch(x)

# Can now optimize x values using this tensor here.
# Along with any of tf.optimizers.
1. Lastly you could frame the problem slightly differently: I hand you a quantum circuit with some free parameters and then I hand you some samples from that quantum circuit where each sample has a corresponding label associated with it. This label indicates a unique values of parameters that this sample came from. Again I don't tell you what any of these parameters are, but now the problem has become slightly harder in your goal is to try and determine which parameter values correspond to each specific label (not just one set of values for alpha and beta this time like in #1) using an algorithm that incorporates just the samples and labels.

This is a far more complex problem and could function very similarly to the example you linked in your question, where the neural network must learn to map from labels to parameter values where the loss gets backprop'd through the samples you take via your closeness measure. At a high level this would require incorporating the above snippet decorated with a @tf.custom_gradient into that tutorial plus some structural modifications to the tf.keras.Model, but is in principle very doable. There are some more complex TFQ examples here that we also walk through here.

As a final note I will say TensorFlow is tricky and when you throw things like TensorFlow Quantum (or pennylane) on top of that, things can get really tricky. If you are more interested in 1 and 2 from the list, you might be better off sticking with you favorite between cirq/qiskit and doing everything manually, but if you are more interested in 3 I'd say tackling the learning curve of TF + TFQ would definitely help in the long run.

Here's a paper that might also interest you. They tackle a slightly different problem. Here the authors tried to use a neural network to emulate sampling from a quantum circuit. Turns out it's a little tricky :).

• Thanks a lot for writing such a useful reply. I'm still digesting! 2) was particularly useful for understanding tf.gradients that im not familiar with. The links and paper are also really interesting! Nov 16 '20 at 10:30

As I know, the quantum neural network(QNN) does have a category that relates to what you want, that is, learning an unknown oracle. As the operation of your circuit can be considered as known, QNN can generate a unitary that has the same operation as your circuit.

But a critical problem arises, is the correct answer always possible?

For example, in a two-dimensional rotation case, we know that R($$\alpha$$)R($$\beta$$)=R($$\alpha+\beta$$), so consider the R($$\alpha+\beta$$) as the oracle, it is impossible to learn the value of $$\alpha$$ and $$\beta$$ (but $$\alpha+\beta$$ is possible). This example is rather coarse, maybe you can extend it to a more general case.

And there is intentionally another difficulty, that is, how can we reduce the oracle from the QNN?

E.g., the oracle is a two-qubit Hadamard gate ($$H\otimes H$$) and the QNN architecture is [2,3,2], then what we get is a network of unitaries or a large unitary with dimension $$2^7\times2^7$$, then is it an easy job to reduce the two-qubit Hadamard gate ($$2^2\times2^2$$)?

The second concern comes to me recently and I have to work on it later (so now it may be just a guess), and for the details of QNN, I think this paper is great.

I am new to QNN and looking for more discussion.

• Thanks for the answer, going to read the paper! I get your point (I hope :) ), I think this is less of a QNN problem and more of training a classical NN to be able to "control" quantum data. I was inspired by this: tensorflow.org/quantum/tutorials/… In my example I would be assuming I knew the "circuit", so that it's not a black box. Nov 12 '20 at 15:02

Here's an update for those interested (& before I had read Michael's comment)

I changed my problem around a bit, after discovering TFQ/cirq cannot (yet) handle serialising complex power-raised gates. Also, it was getting too complex using a ControlledPQC to have non-trainable values (phi) and trainable values alpha and beta .. I think it's doable but it really does get tricky.

Anyway, here is the modified problem and solution I found adapting TensorFlow Quantum's examples ....

I now have this circuit:

# define two qubits
q0 = cirq.GridQubit(0,0)
q1 = cirq.GridQubit(0,1)

# make a PauliSum gate that will return [1] if the state is a bell state b00 (|00>)
z0 = cirq.Z(q0)
z1 = cirq.Z(q1)
z00 = (((1+z0)/2)*((1+z1)/2) )

# define three parameters, x will be varied - a,b are fixed
phi = sp.Symbol('phi')
alpha = sp.Symbol('alpha')
beta = sp.Symbol('beta')

circuit = cirq.Circuit([
cirq.rx(phi)(q0),
cirq.ry(-2*phi)(q1),
cirq.H(q0),
cirq.CNOT(q0,q1),
cirq.rx(alpha)(q0),
cirq.ry(beta)(q1),
])

which looks like:

I can calculate the z00 expectation which is going to give me the same distribution as would running measurements on the state $$|00\rangle$$ (obviously)

#fix two values of alpha and beta
a = 1.8
b = -3

#loop over phi from -6 to 6
x_values = np.arange(-6,6,0.05)
y_expectations = []

for j in x_values:
#resolve the circuit for different values of phi
resolver = cirq.ParamResolver({'alpha':a,'beta':b,'phi':j})

#calcuclate the expectation value
result = cirq.Simulator().simulate(cirq.resolve_parameters(circuit,resolver))
e = z00.expectation_from_state_vector(result.final_state_vector,{q0:0,q1:1})
y_expectations.append(e)

plt.scatter(x_values,y_expectations,label=f'Expected, $$\\alpha$$={a}, $$\\beta$$={b}')
plt.xlabel('$$\phi$$')
plt.ylabel('Expectation')
plt.legend()
plt.show();

Gives the following:

I can split the circuit into two parts:

1. Input qubits with rotations by the angle \phi
input_circuit = cirq.Circuit([
cirq.rx(phi)(q0),
cirq.ry(-2*phi)(q1)
])
>>> input_circuit
(0, 0): ───Rx(phi)──────

(0, 1): ───Ry(-2*phi)───
1. The circuit that is going to be my model - aka given some inputs (rotated qubits) these will be entangled and rotated by fixed values that I want to learn.
model_circuit = cirq.Circuit([
cirq.H(q0),
cirq.CNOT(q0,q1),
cirq.rx(alpha)(q0),
cirq.ry(beta)(q1),
])
>>> model_circuit

(0, 0): ───H───@───Rx(alpha)───
│
(0, 1): ───────X───Ry(beta)────

Using tfq I can make a really simple model based on the model_circuit:

#building a keras model
#- the input is the input_circuit
#- the PQC outputs the expectation to be in state |00> from the model circuit, given the input circuit
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(), dtype=tf.string),
tfq.layers.PQC(model_circuit, z00),
])

Also, need to convert the input circuits (one circuit per value of phi) into tensors:

#generate a series of input circuits, with different x values (values of phi)
input_circuits =  tfq.convert_to_tensor([
cirq.resolve_parameters(input_circuit, cirq.ParamResolver({'phi':x}))
] for x in x_values)

I can test the model (randomly initialised values of alpha and beta given my input circuits:

#test the model
y_test = model([input_circuits]).numpy()
#plot how a randomly initiated model compares with the expected
plt.plot(x_values,y_expectations,lw=4,label=f'Expected, $\\alpha$={a}, $\\beta$={b}')
plt.plot(x_values,y_test,'ro',label='Pre-trained NN output')
plt.xlabel('$\phi$')
plt.ylabel('Expectation')
plt.legend()
plt.show();

Next is to compile the model, using a basic optimiser and loss, e.g.:

loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)
model.summary()

Then the model can be fitted given now the y_expected from the original expectations (this could easily be the original 'measurements' distribution instead)

y_expected = np.array(y_expectations)
history = model.fit(
input_circuits, y_expected,
epochs=20,
verbose=0)

After the model has been trained, I can make the predictions again and see how it's easily learnt what the parameters were:

I hope this helps someone - it's not quite the answer to my problem and I would like to not have to split up the circuit into an input and model. Ultimately this was just me having a bit of fun and I don't think is very realistic anyway!