Data encoding on forest.qvm

Continuing the discussion from AmplitudeEmbedding:

Hi @josh is there any alternative for the above problem. I am planning to handle lots of data on a quantum circuit and the default.qubit device is too slow in doing the operations I require. So, I planned to use forest.qvm as you mentioned in your other post that it works fast.

Any suggestions in this regard are highly appreciated.

Thanks in advance.

Regards,
Avinash.

Hi @avinash_ch,

What is the issue you are currently experiencing with the forest plugin and AmplitudeEmbedding?

If you could post a small code snippet here, and the corresponding traceback, that would be great!

Hi @josh,
I am trying to use the following device:
dev = qml.device("forest.qvm", device="{}q-pyqvm".format(11))
to encode data as a quantum state using AmplitudeEmbedding method and I am getting the following error:

DeviceError(“Gate {} not supported on device {}”.format(o.name, self.short_name))
pennylane._device.DeviceError: Gate QubitStateVector not supported on device forest.qvm

Can I ask what version of PennyLane and PennyLane-Forest you are using? It might be best to upgrade to the latest version of both:

  • PennyLane v0.8:

    pip install pennylane --upgrade
    
  • PennyLane-Forest:

    pip install git+https://github.com/rigetti/pennylane-forest.git#egg=pennylane-forest
    

Note that we are still in the process of uploading a new PennyLane-Forest plugin, so currently it needs to be installed on GitHub :slight_smile:

Hi @josh

Below are the details and also a sample code snippet.
Please take a look. If upgrading fixes the error please let me know.

Many thanks,
Avinash

PennyLane Version: 0.6.0
PennyLane-Forest Version: 0.6.0

$ qvm -S and $ quilc -S commands executed successfully in two terminals.

Sample Code:

import pennylane as qml
from pennylane.templates.embeddings import AmplitudeEmbedding
from pennylane import numpy as np

dev = qml.device("forest.qvm", device="{}q-pyqvm".format(11))

vals = np.random.randint(10, size=(1,2048))
f = vals.tolist()

@qml.qnode(dev)
def circuit(features=None):
    AmplitudeEmbedding(features, wires = [0,1,2,3,4,5,6,7,8,9,10], pad=True, normalize=True)
    return qml.expval(qml.PauliZ(0))

result = circuit(features=f)
print(result)

Error Trace:

/usr/local/lib/python3.6/dist-packages/pyquil/numpy_simulator.py:29: FutureWarning: The code in pyquil.numpy_simulator has been moved to pyquil.simulation, please update your import statements.
  FutureWarning,
Traceback (most recent call last):
  File "sample_forest_sdk.py", line 42, in <module>
    result = circuit(features=f)
  File "/home/avinash/.local/lib/python3.6/site-packages/pennylane/decorator.py", line 64, in wrapper
    return qnode(*args, **kwargs)
  File "/home/avinash/.local/lib/python3.6/site-packages/pennylane/qnode.py", line 510, in __call__
    return self.evaluate(args, **kwargs)  # args as one tuple
  File "/home/avinash/.local/lib/python3.6/site-packages/autograd/tracer.py", line 48, in f_wrapped
    return f_raw(*args, **kwargs)
  File "/home/avinash/.local/lib/python3.6/site-packages/pennylane/qnode.py", line 583, in evaluate
    ret = self.device.execute(self.circuit.operations, self.circuit.observables, self.variable_deps)
  File "/home/avinash/.local/lib/python3.6/site-packages/pennylane/_device.py", line 155, in execute
    self.check_validity(queue, observables)
  File "/home/avinash/.local/lib/python3.6/site-packages/pennylane/_device.py", line 328, in check_validity
    raise DeviceError("Gate {} not supported on device {}".format(o.name, self.short_name))
pennylane._device.DeviceError: Gate QubitStateVector not supported on device forest.qvm

Hi @avinash_ch, the issue you are seeing is due to the older version of the PennyLane-Forest plugin not supporting Amplitude Embedding. However, it now supports it on the latest version :slight_smile:

Can I get you to upgrade both PennyLane and PennyLane-Forest by running the following commands:

pip uninstall pennylane pennylane-forest
pip install pennylane --upgrade
pip install git+https://github.com/rigetti/pennylane-forest.git#egg=pennylane-forest

Thank you @josh. I will do that.

Hi @josh,

Upgrading the plugin worked and I was able to encode on pyqvm device using the AmplitudeEmbedding method.

I have few queries on the following code snippet which tests the speed of the two devices:

Code:

import pennylane as qml
from pennylane import numpy as np
from pennylane.templates.embeddings import AmplitudeEmbedding
import time

def RY_layer(w):
    for idx, element in enumerate(w):
        qml.RY(element, wires=idx)

def entangling_layer(nqubits):
    for i in range(0, nqubits - 1, 2):  
        qml.CNOT(wires=[i, i + 1])
    for i in range(1, nqubits - 1, 2):  
        qml.CNOT(wires=[i, i + 1])

def circuit(features, weights):
    AmplitudeEmbedding(features, wires = list(range(0,n_qubits)), pad=None, normalize=True)
    for k in range(depth):
        entangling_layer(n_qubits)
        RY_layer(weights[k])

    exp_vals = [qml.expval(qml.PauliZ(position)) for position in range(n_qubits)]
    return tuple(exp_vals)

n_qubits = 11
depth = 50
features = np.random.randint(10, size=(2048,))
weights = (0.001 * np.random.randint(100, size=(depth,n_qubits)))

devices = [qml.device("default.qubit", wires=n_qubits),
           qml.device("forest.qvm", device="{}q-pyqvm".format(n_qubits))]

for dev in devices:
    print("\nDevice: {}".format(dev.name))
    qnode = qml.QNode(circuit, dev)
    start_point = time.perf_counter()
    qnode(features,weights)
    end_point = time.perf_counter()
    print("\n Time in seconds: ", end_point - start_point)

Output:

Device: Default qubit PennyLane plugin
Time in seconds: 36.0572343878448

Device: Forest QVM Device
Time in seconds: 1006.6512296050787

  • I have found from the above result that default.qubit device is performing extremely faster than pyqvm. Is this usual or unusual(ref: benchmarking) ?

  • I also want to experiment optimization of gate parameters in a similar way you did for the Quantum Transfer Learning experiment. What would be the best device to work on?

It would be of great help if you could suggest on this and share any relevant links to further study upon.

Thank you,

Sincerely,
Avinash.

Hi @avinash_ch,

I’m not too surprised at the speed difference, for two reasons:

  1. Since the device benchmarking posts were made on this forum, we’ve put in a lot of effort into making default.qubit faster for small-medium sized circuits :slight_smile:

  2. At the same time, I don’t believe the pyQVM is no longer maintained by Rigetti, and has actually slowed down significantly in the last couple of version of pyQuil.

For basic prototyping, default.qubit is probably best for now.

For the quantum transfer learning case, have a look at the following resources:

:slightly_smiling_face:Thanks @josh ! I will go through the links.

Hi @josh,
Thanks for the PennyLane Version 0.8.0.
I highly appreciate your amazing efforts and congratulations to your team.

I was trying to understand the operation of qml.probs() measurement

Here is the code I tried from your release notes:

import pennylane as qml
from pennylane import numpy as np

dev = qml.device("default.qubit", wires = 2)

@qml.qnode(dev)
def circuit(x):
    qml.Hadamard(wires=0)
    qml.RY(x, wires=0)
    qml.RX(x, wires=1)
    qml.CNOT(wires=[0, 1])
    return [qml.probs(wires=[0]), qml.probs(wires=[1])]

print(np.real(dev._state))
prob = circuit(0.2)
print(np.real(dev._state))
print(prob)

Output:

[1. 0. 0. 0.]
[0.62981904 0.         0.         0.77029947]
[[0.40066533 0.59933467]
 [0.40264541 0.59735459]]

I have the following questions on the above code and results.

  • What is the math behind the calculation of marginal probability? As the states are entangled the measurement on first wire effects the state of the second qubit, how did you handle this while displaying the probability?

  • How do you calculate probabilities on each wire after encoding the input using AmplitudeEmbedding() as the state is entangled after encoding? Some circuit like:

    0: ──╭QubitStateVector(M0)──┤ ObservableReturnTypes.Probability[I]
    1: ──├QubitStateVector(M0)──┤ ObservableReturnTypes.Probability[I]
    2: ──╰QubitStateVector(M0)──┤ ObservableReturnTypes.Probability[I]
    M0 = [x1, x2, x3, x4, x5, x6, x7, x8]

Please explain or provide some links to understand the same.

Many thanks in advance.

Sincerely,
Avinash.

What is the math behind the calculation of marginal probability?

Here, we are simply summing over the wires not specified to determine the marginal probabilities.

As the states are entangled the measurement on first wire effects the state of the second qubit, how did you handle this while displaying the probability?

In PennyLane’s case, we always compute the full system joint probabilities directly from the quantum device. Then, if the marginal probabilities are requested, we marginalize the joint probabilities using classical post-processing. This is how we are able to return this information from a single device run :slightly_smiling_face:

Thanks for your time @josh! You are the best in giving prompt responses :slight_smile:

Below is my understanding (please correct me if I am wrong), followed by a question:

Consider, I encode eight values into three qubits using AmplitudeEmbedding doing no other quantum operation further.

Then, if I measure each qubits using qml.probs() I get corresponding probabilities of the states that are the absolute squares of the amplitudes. If I measure each wire, I would get individual probabilities of each wire.

Another operation is if I measure each wire using qml.expval.PauliZ(0), qml.expval.PauliZ(1),and qml.expval.PauliZ(2) I would get three random values between [-1, 1] for each wire.

These expectation values (or the probabilities for qml.probs()) depend on the state of the individual wires. Initially, each wire is at |0> state. After AmplitudeEmbedding operation, each qubit is turned into some quantum state with alpha, beta as amplitudes based on the eight input values.

Now, my question is:
How are the eight input values embedded onto these three wires as alpha, beta on each wire? Can you please explain.

Thanks for patience. :slight_smile:

Sincerely,
Avinash.

How are the eight input values embedded onto these three wires as alpha, beta on each wire? Can you please explain.

Hi @avinash_ch — the features correspond directly to the values alpha, beta, etc :slightly_smiling_face:

So if you have the features [c_0, c_1, \dots, c_7] that you want to embed, AmplitudeEmbedding is simply preparing the state

|\psi\rangle = \sum_i c_i |i\rangle,

where |i\rangle are the computational basis states.

Arbitrary state preparation is either supported directly by the plugin (for example, the Qiskit plugin supports direct state preparation), while for others (such as Forest), PennyLane decomposes the state preparation into a set of gates using the Mottonen decomposition.

Hi @josh!

Please look at the following code:

dev = qml.device("default.qubit", wires = 2)
f = np.array([1,2,3,4])

@qml.qnode(dev)
def probcal2(features=None):
    AmplitudeEmbedding(features, wires=[0,1], pad=None, normalize=True)
    return [qml.probs(wires=[0]), qml.probs(wires=[1])]

print("Initial state : ", np.real(dev._state))
prob = probcal2(features=f)
print("Final state : ", np.real(dev._state))
print("Probabilites on wire 0: ", prob[0])
print("Probabilities on wire 1: ", prob[1])
alpha0 = np.sqrt(prob[0,0])
beta0 = np.sqrt(prob[0,1])
alpha1 = np.sqrt(prob[1,0])
beta1 = np.sqrt(prob[1,1])
print("Amplitudes on wire 0: ", alpha0, beta0)
print("Amplitudes on wire 1: ", alpha1, beta1)
qubit1 = np.array([alpha0, beta0])
qubit1.resize(2,)
qubit2 = np.array([alpha1, beta1])
qubit2.resize(2,)
input_reconst = np.kron(qubit1,qubit2)
print("Reconstructed input from amplitudes: ", input_reconst)

Output:

Initial state :  [1. 0. 0. 0.]
Final state :  [0.18257419 0.36514837 0.54772256 0.73029674]
Probabilites on wire 0:  [0.16666667 0.83333333]
Probabilities on wire 1:  [0.33333333 0.66666667]
Amplitudes on wire 0:  0.40824829046386296 0.9128709291752768
Amplitudes on wire 1:  0.5773502691896257 0.8164965809277259
Reconstructed input from amplitudes:  [0.23570226 0.33333333 0.52704628 0.74535599]
  • Are the amplitudes on the wire 0 and wire 1 in the above output is a result of MottonenStatePreparation(state_vector, wires) ? and Is that the reason behind the values for the operation input_reconst = np.kron(qubit1,qubit2) differs from the input?

Please comment on the above program if there is any invalid operation.

Thank you for your time and patience. :slight_smile:

Sincerely,
Avinash.

Hi @avinash_ch! We can’t reconstruct the quantum state from the marginal probability distributions. However, we can go in the reverse order:

  • From the quantum state, we can compute the joint probability distribution of all qubits.

  • From the joint probability distribution, we can compute the marginal probabilities.

For example, consider the following program:

import pennylane as qml
import numpy as np

dev = qml.device("default.qubit", wires = 2)
f = np.array([1,2,3,4])

@qml.qnode(dev)
def marginal_probs(features=None):
    qml.templates.AmplitudeEmbedding(features, wires=[0,1], pad=None, normalize=True)
    return [qml.probs(wires=[0]), qml.probs(wires=[1])]

@qml.qnode(dev)
def joint_probs(features=None):
    qml.templates.AmplitudeEmbedding(features, wires=[0,1], pad=None, normalize=True)
    return qml.probs(wires=[0, 1])

marginals = marginal_probs(features=f)
joint = joint_probs(features=f)

Now, generating the probabilities from the quantum state:

>>> print("Initial state : ", dev.state)
>>> print("Final state : ", dev.state)
>>> print("Joint probabilities: ", joint)
>>> print("State absolute squared: ", np.abs(dev.state)**2)
Initial state :  [1.+0.j 0.+0.j 0.+0.j 0.+0.j]
Final state :  [0.18257419+0.j 0.36514837+0.j 0.54772256+0.j 0.73029674+0.j]
Joint probabilities:  [0.03333333 0.13333333 0.3        0.53333333]
State absolute squared:  [0.03333333 0.13333333 0.3        0.53333333]

Then, getting the marginal probabilities:

>>> print("Marginal probabilites on wire 0: ", marginals[0])
>>> print("Marginal prob 0 from joint: ", np.sum(joint.reshape([2, 2]), axis=1))
Marginal probabilites on wire 0:  [0.16666667 0.83333333]
Marginal prob 0 from joint:  [0.16666667 0.83333333]
>>> print("Marginal probabilites on wire 1: ", marginals[1])
>>> print("Marginal prob 1 from joint: ", np.sum(joint.reshape([2, 2]), axis=0))
Marginal probabilites on wire 1:  [0.33333333 0.66666667]
Marginal prob 1 from joint:  [0.33333333 0.66666667]

That’s great! :slight_smile: qml.probs() is a very useful feature.
Many thanks for the explanation @josh!

1 Like