Loading...

Postulate is the best way to take and share notes for classes, research, and other learning.

More info

How to find the bloch vectors of a two-qubit circuit using QNodeCollections in Pennylane

Profile picture of Laura GaoLaura Gao
Apr 9, 2021Last updated Mar 14, 20245 min read

When building a quantum GAN to generate a two-qubit state, I wanted to see the bloch vectors of my generated two-qubit state as opposed to the real one. To do that, I used this code:

obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0), qml.PauliX(1), qml.PauliY(1), qml.PauliZ(1)] # the observation matrix

bloch_vector_real = qml.map(real_data, obs, dev, interface="tf") # Creates a QNode collection.
bloch_vector_generator = qml.map(generator, obs, dev, interface="tf")

bloch_vector_realz = bloch_vector_real(phi) # here: you pass phi through the QNode collection
bloch_vector_generatorz = bloch_vector_generator(generator_weights) # calling it with a z at the end because adding _2 at the  end is boring. for a lack of better name.

print("Real Bloch vector: {}".format(bloch_vector_realz))
print('')
print("Generator Bloch vector: {}".format(bloch_vector_generatorz))

Out:

Real Bloch vector: [-6.38713226e-01  6.77924961e-01 -5.96046448e-08 -6.72066212e-03
 -3.17868590e-01 -8.75472177e-01]

Generator Bloch vector: [-0.04750997 -0.63140127 -0.22388268  0.20524305 -0.74125844 -0.11634603]

Let's break down what this code means.

The Observation Matrix

obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0), qml.PauliX(1), qml.PauliY(1), qml.PauliZ(1)] # the observation matrix

This matrix, when added to the end of a circuit, takes the expectation value of both qubits 0 and 1 in the PauliX, PauliY, and PauliZ bases.

Why this works: Our goal is to find the bloch vector coordinates of a two-qubit state. The bloch vector coordinates of a qubit is equal to the expectation value in each of the 3 bases. Ex. the x-coordinate of the bloch vector of qubit 0 is equal to the expectation value of qubit 0 in the PauliX basis.



qml.map()

According to the documentation, the function qml.map() "map[s] a quantum template over a list of observables to create a QNodeCollection."

In English: it creates a QNodeCollection from its input arguments.

If you don't know: A QNodeCollection object is a collection of QNodes, and functionally, they can be executed the same way as QNodes (an oversimplified explanation).

If you don't know: A QNode is a circuit + device:



[Image source: Pennylane]

In this code:

bloch_vector_real = qml.map(real_data, obs, dev, interface="tf")

We pass the arguments real_data, obs, dev, and interface='tf' through the qml.map() function.

This means we create a QNodeCollection (functionally a QNode) whose architecture contains the real_data function, the obs matrix, and the device dev. interface="tf" is something you'll also put in regular QNode definitions - the purpose of which is to allow you to run Tensorflow functions and pass Tensorflow-type-objects through said QNode. (We need to pass Tensorflow-type-objects through the QNode because we are using a Tensorflow optimizer to perform gradient descent, and Tensorflow optimizers need their parameters to be Tensorflow-type objects.)

In the above code, real_data is a piece of circuit architecture that is not a QNode. (It is just a regular quantum function with gates inside):

def real_data(phi, **kwargs):
		qml.Hadamard(wires=0)
		qml.CNOT(wires=[0,1])
		qml.RX(phi[0], wires=0)
		qml.RY(phi[1], wires=0)
		qml.RZ(phi[2], wires=0)
		qml.RX(phi[3], wires=0)
		qml.RY(phi[4], wires=0)
		qml.RZ(phi[5], wires=0)
		qml.CNOT(wires=[0, 1])
		qml.RX(phi[6], wires=1)
		qml.RY(phi[7], wires=1)
		qml.RZ(phi[8], wires=1)
		qml.RX(phi[9], wires=1)
		qml.RY(phi[10], wires=1)
		qml.RZ(phi[11], wires=1)

Our goal at the end of the QGAN is to evaluate real_data and see what bloch vectors does it produce. However, the problem is that we cannot execute real_data because it is not a QNode currently.

What do we do?

Remember:

Functionally, [QNodeCollections] can be executed the same way as QNodes.

So, we shall turn our real_data into a QNodeCollection. And then we will be able to run it, to see what bloch vectors it produces.

And that is exactly what these lines of code do:

bloch_vector_real = qml.map(real_data, obs, dev, interface="tf")
bloch_vector_generator = qml.map(generator, obs, dev, interface="tf")

The variable bloch_vector_real variable is of QNodeCollection class. Therefore, we can treat the variable the same way we treat a variable that represents a QNode.

Actually, QNode are not variables. They are functions. So: bloch_vector_real is not actually a variable (although it looks like one from the line of code above). It is a function.

We can call this function the same way we treat other functions:

bloch_vector_real(phi)

Calling the above line of code outputs the bloch vector values of real_data.

bloch_vector_realz = bloch_vector_real(phi) # here: you pass phi through the QNode collection
print("Real Bloch vector: {}".format(bloch_vector_realz))

Out:

Real Bloch vector: [-6.38713226e-01  6.77924961e-01 -5.96046448e-08 -6.72066212e-03	 -3.17868590e-01 -8.75472177e-01]

We have successfully retrieved the bloch vectors of the two-qubit state.



Notes:

  • More precisely, a QNodeCollection is simply not the same thing as a QNode. As the name suggests, it is "a sequence of independent QNodes" according to the docs. The utility of QNodeCollections is that "when the collection is evaluated, all QNodes are simultaneously evaluated with the same parameters."

  • These bloch vector outputs are Numpy arrays.


Comments (loading...)

Sign in to comment

Quantum computing

Working notes from learning QC