POVM Sampler Pub

This tutorial shows you how to input multiple circuits and parameter values to the POVMSampler.run method via primitive unified blocs (PUBs).

[1]:
%load_ext autoreload
%autoreload 2

Interface of the POVMSampler.run method

The interface follows the style of the Qiskit V2 Primitives, which relies on PUBs. Each PUB is a tuple that contains a circuit and the data broadcasted to it. This greatly simplifies your ability to send complex data to a circuit.

the run() method takes an array of PUBs. Each PUB is in the format

(<single circuit>, <optional one or more parameter values>, <optional shots>, <optional POVM implementation>)

First example

Let us look at a first simple example.

Initialize QuantumCircuit and POVMSampler

The first step is to create the qiskit.circuit.QuantumCircuits from which you want to obtain the probability distribution with the povm_toolbox.sampler.POVMSampler.

[2]:
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter

qc1 = QuantumCircuit(2)
qc1.h(0)
qc1.cx(0, 1)
qc1.draw("mpl", style="iqp")
[2]:
../_images/tutorials_povm_sampler_pub_6_0.png

Initialize measurement procedure

Next, create a povm_toolbox.library.POVMImplementation instance.

[3]:
from povm_toolbox.library import ClassicalShadows

shots1 = 128
measurement1 = ClassicalShadows(num_qubits=2, seed=43)
measurement1.measurement_circuit.draw("mpl")
[3]:
../_images/tutorials_povm_sampler_pub_8_0.png

Create the PUB

Finally, create the pub-like object, which is simply a tuple (<single circuit>, <optional one or more parameter values>, <optional shots>, <optional POVM implementation>)

[4]:
pub1 = (qc1, None, shots1, measurement1)

Second example

Let us look at an example where the full PUB is not specified.

Initialize Quantum Circuits

The first step is to create the qiskit.circuit.QuantumCircuits from which you want to obtain the probability distribution with the povm_toolbox.sampler.POVMSampler.

[5]:
qc2 = QuantumCircuit(2)
qc2.x(0)
qc2.h(0)
qc2.cx(0, 1)
qc2.draw("mpl", style="iqp")
[5]:
../_images/tutorials_povm_sampler_pub_13_0.png
[6]:
qc3 = QuantumCircuit(2)
qc3.h(0)
qc3.x(1)
qc3.cx(0, 1)
qc3.draw("mpl", style="iqp")
[6]:
../_images/tutorials_povm_sampler_pub_14_0.png
[7]:
qc4 = QuantumCircuit(2)
qc4.x(0)
qc4.x(1)
qc4.h(0)
qc4.cx(0, 1)
qc4.draw("mpl", style="iqp")
[7]:
../_images/tutorials_povm_sampler_pub_15_0.png

Create the PUBs

Finally, create the pub-like objects, which is simply a tuple (<single circuit>, <optional one or more parameter values>, <optional shots>, <optional POVM implementation>). Here we will use the fact that the last three items are optional because : - The circuits are not parametrized so there is no need to specify parameter values. - We assume a default number of shot will be supplied with the POVMSampler.run method. - We assume a default POVM implementation will be supplied with the POVMSampler.run method.

[8]:
# All of the following syntax are equivalent:

pub2 = (qc2, None)  # or (qc2, None, None) or (qc2, None, None, None)
pub3 = (qc3,)
pub4 = qc4

Set a default measurement procedure

We specify a povm_toolbox.library.POVMImplementation instance that will be the default one if no measurement is specified by a PUB.

[9]:
shots_default = 2048
measurement_default = ClassicalShadows(num_qubits=2, seed=78)

Third example

Let’s look at an example with a parametrized circuit.

Initialize QuantumCircuit and POVMSampler

The first step is to create the qiskit.circuit.QuantumCircuits from which you want to obtain the probability distribution with the povm_toolbox.sampler.POVMSampler.

[10]:
import numpy as np

qc5 = QuantumCircuit(3)

theta = Parameter("theta")
qc5.ry(theta, 0)
qc5.cx(0, 1)
qc5.cx(1, 2)
qc5.draw("mpl", style="iqp")
[10]:
../_images/tutorials_povm_sampler_pub_22_0.png

Set the parameter values to bound to the QuantumCircuit

In this example the quantum circuit has a single parameter (theta). We can set different values for theta.

[11]:
# Set 4 different values for theta
parameter_values = np.array([0.0, 0.4, 0.5 * np.pi, np.pi])

Initialize measurement procedure

Next, create a povm_toolbox.library.POVMImplementation instance.

[12]:
shots5 = 4096
measurement5 = ClassicalShadows(num_qubits=3, seed=62)
measurement5.measurement_circuit.draw("mpl")
[12]:
../_images/tutorials_povm_sampler_pub_26_0.png

Create the PUB

Finally, create the pub-like object, which is simply a tuple (<single circuit>, <optional one or more parameter values>, <optional shots>, <optional POVM implementation>)

[13]:
pub5 = (qc5, parameter_values, shots5, measurement5)

Initialize the POVMSampler

[14]:
from povm_toolbox.sampler import POVMSampler
from qiskit.primitives import StatevectorSampler as Sampler

sampler = Sampler(seed=91)
povm_sampler = POVMSampler(sampler=sampler)

Run and get results

Now that you have defined your povm_sampler, run it by calling the povm_toolbox.sampler.POVMSampler.run method, which returns an instance of povm_toolbox.sampler.POVMSamplerJob. You can get the results from the job (as a qiskit.primitives.PrimitiveResult object) with the povm_toolbox.sampler.POVMSamplerJob.result method.

[15]:
job = povm_sampler.run(
    [pub1, pub2, pub3, pub4, pub5], shots=shots_default, povm=measurement_default
)
result = job.result()
print(result)
PrimitiveResult([POVMPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(), num_shots=128, num_bits=2>)), metadata=RPMMetadata(povm_implementation=ClassicalShadows(num_qubits=2), composed_circuit=<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x00000176C8F10B50>, pvm_keys=np.ndarray<128,2>)), POVMPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(), num_shots=2048, num_bits=2>)), metadata=RPMMetadata(povm_implementation=ClassicalShadows(num_qubits=2), composed_circuit=<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x00000176C8F108B0>, pvm_keys=np.ndarray<2048,2>)), POVMPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(), num_shots=2048, num_bits=2>)), metadata=RPMMetadata(povm_implementation=ClassicalShadows(num_qubits=2), composed_circuit=<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x00000176C8F118A0>, pvm_keys=np.ndarray<2048,2>)), POVMPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(), num_shots=2048, num_bits=2>)), metadata=RPMMetadata(povm_implementation=ClassicalShadows(num_qubits=2), composed_circuit=<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x00000176C8F11E10>, pvm_keys=np.ndarray<2048,2>)), POVMPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(4,), num_shots=4096, num_bits=3>)), metadata=RPMMetadata(povm_implementation=ClassicalShadows(num_qubits=3), composed_circuit=<qiskit.circuit.quantumcircuit.QuantumCircuit object at 0x00000176C8F10A60>, pvm_keys=np.ndarray<4,4096,3>))], metadata={'raw_results': PrimitiveResult([SamplerPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(128,), num_shots=1, num_bits=2>), shape=(128,)), metadata={'shots': 1}), SamplerPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(2048,), num_shots=1, num_bits=2>), shape=(2048,)), metadata={'shots': 1}), SamplerPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(2048,), num_shots=1, num_bits=2>), shape=(2048,)), metadata={'shots': 1}), SamplerPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(2048,), num_shots=1, num_bits=2>), shape=(2048,)), metadata={'shots': 1}), SamplerPubResult(data=DataBin(povm_measurement_creg=BitArray(<shape=(4, 4096), num_shots=1, num_bits=3>), shape=(4, 4096)), metadata={'shots': 1})], metadata={})})
[16]:
for pub_result in result:
    print(pub_result.get_counts())
[Counter({(5, 0): 19, (3, 4): 19, (5, 2): 15, (3, 0): 15, (2, 2): 14, (0, 0): 14, (1, 2): 14, (5, 4): 10, (1, 4): 8})]
[Counter({(5, 2): 243, (3, 2): 240, (4, 4): 233, (1, 4): 232, (3, 0): 227, (0, 0): 224, (5, 0): 223, (1, 2): 214, (3, 4): 212})]
[Counter({(5, 0): 246, (5, 2): 245, (4, 4): 232, (1, 4): 232, (1, 0): 231, (1, 2): 220, (2, 2): 219, (3, 4): 215, (3, 0): 208})]
[Counter({(3, 2): 244, (1, 0): 242, (5, 2): 241, (3, 0): 240, (5, 4): 237, (1, 4): 213, (3, 4): 213, (5, 0): 210, (1, 2): 208})]
[Counter({(4, 5, 4): 179, (2, 5, 2): 170, (4, 5, 2): 168, (0, 5, 4): 164, (5, 4, 0): 163, (3, 2, 0): 161, (5, 2, 0): 158, (0, 3, 2): 158, (0, 4, 0): 157, (0, 3, 4): 155, (2, 3, 2): 155, (0, 0, 2): 155, (0, 5, 2): 152, (2, 5, 4): 150, (4, 3, 2): 149, (0, 0, 4): 149, (3, 0, 2): 149, (4, 0, 0): 146, (0, 2, 0): 146, (2, 3, 4): 146, (4, 3, 4): 143, (5, 0, 4): 140, (0, 0, 0): 139, (3, 0, 4): 137, (5, 0, 2): 137, (3, 4, 0): 135, (2, 0, 0): 135})
 Counter({(4, 0, 0): 179, (2, 3, 2): 167, (2, 0, 0): 164, (4, 3, 4): 164, (2, 3, 4): 164, (3, 0, 2): 163, (2, 5, 2): 162, (0, 0, 4): 160, (5, 0, 2): 159, (4, 5, 2): 157, (0, 5, 2): 156, (4, 5, 4): 155, (0, 3, 2): 155, (3, 0, 4): 154, (0, 0, 2): 150, (5, 2, 0): 149, (0, 5, 4): 148, (4, 3, 2): 145, (3, 2, 0): 144, (0, 0, 0): 143, (0, 2, 0): 141, (3, 4, 0): 140, (2, 5, 4): 139, (5, 4, 0): 137, (0, 4, 0): 135, (0, 3, 4): 133, (5, 0, 4): 133})
 Counter({(1, 1, 2): 182, (4, 5, 4): 174, (4, 3, 2): 169, (2, 5, 2): 163, (0, 5, 4): 163, (0, 3, 4): 163, (0, 3, 2): 162, (4, 1, 4): 159, (3, 0, 0): 157, (0, 3, 0): 154, (2, 3, 4): 154, (2, 3, 0): 153, (1, 1, 4): 153, (2, 5, 0): 151, (4, 3, 0): 150, (0, 5, 0): 149, (2, 1, 2): 149, (2, 1, 4): 147, (4, 3, 4): 144, (0, 5, 2): 144, (4, 5, 2): 143, (4, 1, 2): 142, (4, 5, 0): 140, (2, 5, 4): 136, (0, 0, 0): 132, (5, 0, 0): 132, (3, 3, 2): 131})
 Counter({(3, 1, 4): 180, (4, 3, 2): 179, (1, 5, 4): 170, (2, 5, 4): 168, (2, 3, 4): 167, (4, 1, 1): 166, (1, 5, 2): 165, (5, 1, 4): 161, (2, 1, 1): 160, (1, 3, 4): 158, (1, 1, 4): 151, (5, 4, 1): 149, (3, 1, 2): 148, (3, 2, 1): 147, (1, 1, 2): 146, (4, 5, 4): 144, (4, 5, 2): 144, (1, 3, 2): 143, (1, 2, 1): 143, (1, 1, 1): 143, (1, 4, 1): 143, (3, 4, 1): 143, (5, 2, 1): 141, (2, 3, 2): 138, (5, 1, 2): 136, (2, 5, 2): 133, (4, 3, 4): 130})]
[17]:
from povm_toolbox.post_processor import POVMPostProcessor
from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp(["II", "XX", "YY", "ZZ"], coeffs=[1, 1, -1, 1])

post_processor1 = POVMPostProcessor(result[0])
exp_value, std = post_processor1.get_expectation_value(observable=observable)
print(exp_value)

for pub_result in result[1:4]:
    post_processor = POVMPostProcessor(pub_result)
    exp_value, std = post_processor.get_expectation_value(observable=observable)
    print(exp_value)
3.671874999999999
-0.09423828125000021
-0.07226562499999953
-0.09423828124999951
[18]:
post_processor5 = POVMPostProcessor(result[4])
observable = SparsePauliOp(
    ["IIZ", "IXX", "YIY", "ZZI", "XXX", "XYZ"], coeffs=[1, 1, -1, 1, 0.5, -2]
)
exp_value, std = post_processor5.get_expectation_value(observable=observable)
print(exp_value)
[ 2.00793457  2.0892334   2.35656738 -3.83862305]
[19]:
exp_value, std = post_processor5.get_expectation_value(observable=observable, loc=(1))
print(exp_value, "\u00b1", std)
2.089233398437499 ± 0.20321114673076907