Parametrized circuits

This tutorial shows you can submit parametrized quantum circuits to the POVMSampler.

[1]:
%load_ext autoreload
%autoreload 2

Parametrized Circuit

Let us look at a 2-qubit quantum circuit with 5 parameters.

[2]:
from qiskit.circuit.library import RealAmplitudes

# Prepare inputs.
num_qubits = 2
qc = RealAmplitudes(num_qubits=num_qubits, reps=2)
qc.draw()
[2]:
     ┌────────────────────────────────────────────────┐
q_0: ┤0                                               ├
     │  RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5]) │
q_1: ┤1                                               ├
     └────────────────────────────────────────────────┘
[3]:
import numpy as np

# Assume you want to run the circuit with two different sets of parameter values
theta = np.array(
    [
        [0, 1, 1, 2, 3, 5],  # first set of parameter values
        [0, 1, 1, 2, 2, 5],  # second set of parameter values
    ]
)
# Shape of the resulting `BindingsArray` is (2,) with `num_param` equal to 6

Measurement

We now look at the implementation of Classical Shadows measurement

[4]:
from povm_toolbox.library import ClassicalShadows

# By default, the Classical Shadows (CS) measurement uses X,Y,Z measurements with equal probability.
cs_implementation = ClassicalShadows(num_qubits=num_qubits, seed_rng=342)
# Define the default shot budget.
shots = 4096
[5]:
pub = (qc, theta, shots, cs_implementation)
[6]:
from povm_toolbox.sampler import POVMSampler
from qiskit.primitives import StatevectorSampler as Sampler

# Internal `BaseSampler`
sampler = Sampler(seed=432)

# Actual `POVMSampler` instance
povm_sampler = POVMSampler(sampler=sampler)

# Submit the job by specifying the list of PUBs to run, here we have a single PUB.
job = povm_sampler.run([pub])

Results

We submitted a single PUB, hence the PrimitiveResult will contain only one POVMPubResult.

[7]:
pub_result = job.result()[0]
print(pub_result)

# Note that the pub result will contain a `BitArray` that has the same shape
# as the submitted `BindingsArray`, which is (2,) in this example.
print(pub_result.get_counts().shape)
POVMPubResult(data=DataBin<2>(povm_measurement_creg=BitArray(<shape=(2,), num_shots=4096, num_bits=2>)), metadata=RPMMetadata(povm_implementation=ClassicalShadows(num_qubits=2), composed_circuit=<qiskit.circuit.library.n_local.real_amplitudes.RealAmplitudes object at 0x177cbbd40>, pvm_keys=np.ndarray<2,4096,2>))
(2,)

We now define our POVM post-processor, which will use the result object to estimate expectation values of some observables.

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

post_processor = POVMPostProcessor(pub_result)

Since the POVM implementation that we used is informationally complete, we can define our observables of interest after the sampling process.

[9]:
from qiskit.quantum_info import SparsePauliOp

H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)])
H2 = SparsePauliOp.from_list([("II", 1), ("XX", 1), ("YY", -1), ("ZZ", 1)])

The post-processor will return two expectation values corresponding to the two different sets of parameter values that were submitted.

[10]:
exp_value, std = post_processor.get_expectation_value(H1)
print(exp_value)
[1.98364258 4.71411133]

If we are interested to evaluate an observable only for one set of parameter values, this set can be specified through the loc argument.

[11]:
exp_value, std = post_processor.get_expectation_value(H2, loc=1)
print(exp_value)
0.05297851562500033

Further example

Let us look at a BindingsArray instance with a more complex shape.

[12]:
bindings_array_shape = (3, 4)
num_param = 6

theta = np.arange(72).reshape((*bindings_array_shape, num_param))
[13]:
job = povm_sampler.run([(qc, theta, shots, cs_implementation)])
pub_result = job.result()[0]
print(pub_result.get_counts().shape)
(3, 4)
[14]:
post_processor = POVMPostProcessor(pub_result)
exp_values, std = post_processor.get_expectation_value(H1)
print(exp_values)
print(std)
[[-2.65698242 -1.44775391  4.69360352  3.39575195]
 [-4.09399414 -2.57861328 -1.41918945  5.98339844]
 [ 4.74121094  5.98266602 -0.06347656 -0.11474609]]
[[0.09684276 0.07191009 0.05729555 0.07203544]
 [0.0796096  0.09563027 0.10687004 0.07980559]
 [0.0973881  0.07996116 0.08056718 0.07930243]]