Randomized measurements

This tutorial shows the different randomized measurements of the povm_toolbox.

[1]:
%load_ext autoreload
%autoreload 2

Circuit and Observables

We first look at a random the 2-qubit circuit, with depth 3.

[2]:
from qiskit.circuit import ClassicalRegister, QuantumCircuit
from qiskit.circuit.library import XXMinusYYGate

num_qubits = 2

# circuit originally generated by
# `random_circuit(num_qubits=num_qubits, depth=3, measure=False, seed=0)`

qc_random = QuantumCircuit(2)
qc_random.append(XXMinusYYGate(1.7, 0.257), [0, 1])
qc_random.rx(5.74, qubit=0)
qc_random.id(qubit=1)
qc_random.ch(control_qubit=1, target_qubit=0)

creg = ClassicalRegister(1)
qc_random.add_register(creg)
qc_random.draw("mpl", style="iqp")
[2]:
../_images/tutorials_randomized_measurements_3_0.png

We also draw 3 random observables.

[3]:
from qiskit.quantum_info import SparsePauliOp

set_observables = [
    SparsePauliOp(["II", "XX", "YY", "ZY"], coeffs=[1, 1, -4, 1]),
    SparsePauliOp(["XI", "ZX", "YI"], coeffs=[1, -1, 2.5]),
    SparsePauliOp(["II", "XI", "ZI", "IX", "IY", "YX"], coeffs=[0.4, 4, -1, 1, -0.5, 1.8]),
]

For reference, we compute the true final state and the exact expectation values for the different observables.

[4]:
import numpy as np
from qiskit.quantum_info import Statevector

exact_state = Statevector(qc_random)
exp_val = np.real_if_close(
    np.array([exact_state.expectation_value(obs) for obs in set_observables])
)

Classical Shadows

We now look at the implementation of Classical Shadows measurement.

[5]:
from povm_toolbox.library import ClassicalShadows

# By default, the Classical Shadows (CS) measurement uses Z-,X-,Y-measurements with equal probability.
cs_implementation = ClassicalShadows(num_qubits=num_qubits, seed=23)
# Define the default shot budget.
cs_shots = 4096
# Construct the `POVMSamplerPub`.
cs_pub = (qc_random, None, cs_shots, cs_implementation)

Locally-Biased Classical Shadows

We now look at Classical Shadows that can be locally-biased.

[6]:
from povm_toolbox.library import LocallyBiasedClassicalShadows

# Set the distributions of the shots among the Z-,X-,Y-measurements (in this order).
bias = np.array(
    [
        [
            0.0000000028,  # bias for Z-measurements on first qubit
            0.4408677642,  # bias for X-measurements on first qubit
            0.5591322330,  # bias for Y-measurements on first qubit
        ],
        [
            0.1989721524,  # bias for Z-measurements on second qubit
            0.3190601952,  # bias for X-measurements on second qubit
            0.4819676524,  # bias for Y-measurements on second qubit
        ],
    ]
)

# The Locally-Biased Classical Shadows (LBCS) measurement uses Z-,X-,Y-measurements with different probabilities.
lbcs_implementation = LocallyBiasedClassicalShadows(num_qubits=num_qubits, bias=bias, seed=24)
# Define the default shot budget.
lbcs_shots = 4096
# Construct the `POVMSamplerPub`.
lbcs_pub = (qc_random, None, lbcs_shots, lbcs_implementation)

Mutually-Unbiased-Bases POVM

We now look at POVMs that are rotated locally-biased Classical Shadows.

[7]:
from povm_toolbox.library import MutuallyUnbiasedBasesMeasurements

# Define the Euler angles to rotate the measurement.
angles = np.array(
    [
        [0.0000027222, 0.0000000910, 0.3],
        [0.0000022917, 0.0000002655, 0.0],
    ]
)

# Set the distributions of the shots among the PMs.
bias = np.array(
    [
        [0.0000372719, 0.4377084332, 0.5622542949],
        [0.2002136793, 0.3192036469, 0.4805826738],
    ]
)

# Define the PM-simulable POVM.
mub_implementation = MutuallyUnbiasedBasesMeasurements(
    num_qubits=num_qubits, bias=bias, angles=angles, seed=12
)
# Define the default shot budget.
mub_shots = 4100
# Construct the `POVMSamplerPub`.
mub_pub = (qc_random, None, mub_shots, mub_implementation)

PM-simulable POVM

We now look at POVMs that are simulable (through randomization) with only single-qubit projective measurements (PMs).

[8]:
from povm_toolbox.library import RandomizedProjectiveMeasurements

# Define our projective measurements acting on each qubit.
angles = np.array(
    [
        [0.0000027222, 0.0000000910, 1.5707688831, 0.0000235665, 1.5707519773, 1.5707694998],
        [0.0000022917, 0.0000002655, 1.5707961328, 0.0000069500, 1.5707958682, 1.5708006931],
    ]
)

# Set the distributions of the shots among the PMs.
bias = np.array(
    [
        [0.0000372719, 0.4377084332, 0.5622542949],
        [0.2002136793, 0.3192036469, 0.4805826738],
    ]
)

# Define the PM-simulable POVM.
pmsim_implementation = RandomizedProjectiveMeasurements(
    num_qubits=num_qubits, bias=bias, angles=angles, seed=25
)
# Define the default shot budget.
pmsim_shots = 4100
# Construct the `POVMSamplerPub`.
pmsim_pub = (qc_random, None, pmsim_shots, pmsim_implementation)

POVM Sampler

We now instantiate the POVMSampler that will use the different POVM measurements.

[9]:
from numpy.random import default_rng
from povm_toolbox.sampler.povm_sampler import POVMSampler
from qiskit.primitives import StatevectorSampler

# First define a standard sampler (that will be used under the hood).
sampler = StatevectorSampler(seed=default_rng(0))
# Then define the POVM sampler, which takes BaseSampler as an argument.
povm_sampler = POVMSampler(sampler)
# Submit the job by specifying which POVM to use, which circuit(s) to measure and the shot budget.
job = povm_sampler.run([cs_pub, lbcs_pub, mub_pub, pmsim_pub])

Results

We retrieve the result object, which contains the POVM used and from which we can query the counts of each outcome.

[10]:
from povm_toolbox.post_processor.povm_post_processor import POVMPostProcessor

# Retrieve the `PrimitiveResult` object, which contains 3 `POVMPubResult` objects.
result = job.result()
print(f"\n{'-':-<85}-\n")

# Loop through the different PUB results
for k, pub_result in enumerate(result):
    # Instantiate the post-processor from the PUB result.
    post_processor = POVMPostProcessor(pub_result)
    print(f"PUB number : {k}")
    # If one wants to explicitly retrieve the POVM used for a specific PUB,
    # it can be accessed through the metadata of the PUB result.
    print(f"POVM class: {pub_result.metadata.povm_implementation}")
    print(f"Number of shots : {sum(post_processor.counts[0].values())}\n")

    print("   Exact         Estimate       Error      Estimated std     z-score")
    for i, obs in enumerate(set_observables):
        cs_est_exp_val, std = post_processor.get_expectation_value(obs)
        print(
            f"   {np.real(exp_val[i]):>10.3e}   {cs_est_exp_val:>12.3e}   {abs(cs_est_exp_val - np.real(exp_val[i])) / abs(np.real(exp_val[i])):>8.1%}",
            end=" ",
        )
        print(f"        {std:> 8.5f} {(cs_est_exp_val - np.real(exp_val[i])) / std:> 11.2f}")
    print(f"\n{'-':-<85}-\n")
--------------------------------------------------------------------------------------

PUB number : 0
POVM class: ClassicalShadows(num_qubits=2)
Number of shots : 4096

   Exact         Estimate       Error      Estimated std     z-score
    1.470e+00      1.347e+00       8.4%          0.19243       -0.64
   -1.420e+00     -1.294e+00       8.8%          0.08414        1.49
    2.947e+00      3.072e+00       4.2%          0.12670        0.98

--------------------------------------------------------------------------------------

PUB number : 1
POVM class: LocallyBiasedClassicalShadows(num_qubits=2, bias=array([[2.80000000e-09, 4.40867764e-01, 5.59132233e-01],
       [1.98972152e-01, 3.19060195e-01, 4.81967652e-01]]))
Number of shots : 4096

   Exact         Estimate       Error      Estimated std     z-score
    1.470e+00      1.533e+00       4.3%          0.13764        0.46
   -1.420e+00     -1.532e+00       7.9%          0.07798       -1.44
    2.947e+00      2.920e+00       0.9%          0.11659       -0.24

--------------------------------------------------------------------------------------

PUB number : 2
POVM class: MutuallyUnbiasedBasesMeasurements(num_qubits=2, bias=array([[3.72719000e-05, 4.37708433e-01, 5.62254295e-01],
       [2.00213679e-01, 3.19203647e-01, 4.80582674e-01]]), angles=array([[2.7222e-06, 9.1000e-08, 3.0000e-01],
       [2.2917e-06, 2.6550e-07, 0.0000e+00]]))
Number of shots : 4100

   Exact         Estimate       Error      Estimated std     z-score
    1.470e+00      1.495e+00       1.7%          0.13698        0.18
   -1.420e+00     -1.497e+00       5.5%          0.07997       -0.97
    2.947e+00      2.826e+00       4.1%          0.11678       -1.04

--------------------------------------------------------------------------------------

PUB number : 3
POVM class: RandomizedProjectiveMeasurements(num_qubits=2, bias=array([[3.72719000e-05, 4.37708433e-01, 5.62254295e-01],
       [2.00213679e-01, 3.19203647e-01, 4.80582674e-01]]), angles=array([[[2.72220000e-06, 9.10000000e-08],
        [1.57076888e+00, 2.35665000e-05],
        [1.57075198e+00, 1.57076950e+00]],

       [[2.29170000e-06, 2.65500000e-07],
        [1.57079613e+00, 6.95000000e-06],
        [1.57079587e+00, 1.57080069e+00]]]))
Number of shots : 4100

   Exact         Estimate       Error      Estimated std     z-score
    1.470e+00      1.521e+00       3.4%          0.13332        0.38
   -1.420e+00     -1.495e+00       5.3%          0.07798       -0.97
    2.947e+00      3.054e+00       3.6%          0.11699        0.91

--------------------------------------------------------------------------------------

[11]:
# Inspect one of the circuit sent to the internal `BaseSamplerV2` sampler:

lbcs_composed_circuit = result[1].metadata.composed_circuit
lbcs_composed_circuit.draw("mpl", style="iqp")
[11]:
../_images/tutorials_randomized_measurements_20_0.png