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:
- 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)───
- 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.:
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)
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!