Specifying a measurement layout¶
This document shows you how to deal with circuit layout in different cases.
[1]:
%load_ext autoreload
%autoreload 2
1. Transpiled Quantum Circuit¶
Example of a quantum circuit without ancilla qubits before transpilation.
Define quantum circuit¶
[2]:
from qiskit import QuantumCircuit
qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.draw("mpl", style="iqp")
[2]:
Transpile circuit¶
[3]:
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
backend.set_options(seed_simulator=25)
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
# Transpile the circuit to an "Instruction Set Architecture" (ISA) circuit.
# Note: the transpiler automatically adds "ancilla" qubits to make the transpiled
# circuit match the size of the FakeManilaV2 backend.
qc_isa = pm.run(qc)
qc_isa.draw("mpl", style="iqp")
[3]:
Define measurement procedure¶
No need to set a particular measurement_layout
, because the POVMSampler
will analyze the transpiled circuit and apply the TranspileLayout
to the measurement circuit.
[4]:
from povm_toolbox.library import ClassicalShadows
measurement = ClassicalShadows(2, measurement_layout=None, seed=13)
More precisely, the measurement layout that will be automatically applied by the POVMSampler
is extracted as :
transpile_layout = transpiled_circuit.layout
measurement_layout = transpile_layout.final_index_layout(filter_ancillas=True)
For the above circuit, we have:
[5]:
print(qc_isa.layout.final_index_layout(filter_ancillas=True))
[4, 3]
Run the job¶
Initialize Sampler
and POVMSampler
. Then run the job.
[6]:
from povm_toolbox.sampler import POVMSampler
from qiskit_ibm_runtime import SamplerV2 as RuntimeSampler
# First define a standard sampler (that will be used under the hood).
runtime_sampler = RuntimeSampler(backend=backend)
# Then define the POVM sampler, which takes BaseSampler as an argument.
povm_sampler = POVMSampler(runtime_sampler)
# Submit the job by specifying which POVM to use, which circuit(s) to measure and the shot budget.
job = povm_sampler.run([qc_isa], shots=4096, povm=measurement)
Look at the final composed circuit to check that the measurement was performed on the correct qubits.
[7]:
pub_result = job.result()[0]
pub_result.metadata.composed_circuit.draw("mpl", style="iqp")
[7]:
Define observable¶
[8]:
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["XI", "XX", "YY", "ZX"], coeffs=[1, 1, -1, 1])
Get the expected value¶
The observable has to be specified in terms of virtual qubits. Therefore, there is no need to apply the layout that was used by the POVM on the physical qubits.
[9]:
from povm_toolbox.post_processor import POVMPostProcessor
post_processor = POVMPostProcessor(pub_result)
exp_value, std = post_processor.get_expectation_value(observable)
print(exp_value)
1.8845214843749996
For reference, we can compare our estimated expectation value to the exact value.
[10]:
import numpy as np
from qiskit.quantum_info import Statevector
isa_observable = observable.apply_layout(layout=qc_isa.layout, num_qubits=qc_isa.num_qubits)
exact_expectation_value = np.real_if_close(Statevector(qc_isa).expectation_value(isa_observable))
print(f"Exact value: {exact_expectation_value}")
print(f"Estimated value: {exp_value}")
Exact value: 1.9999999999999996
Estimated value: 1.8845214843749996
2. Quantum Circuit with ancilla qubits¶
Example of an un-transpiled quantum circuit with ancilla qubits
Define quantum circuit¶
The circuit can contain some ancilla qubits that you might not want to measure.
[11]:
qc_with_ancilla = QuantumCircuit(4)
# Qubits (1,3):
qc_with_ancilla.h(1)
qc_with_ancilla.cx(1, 3)
# Ancilla qubits (0,2):
qc_with_ancilla.z(0)
qc_with_ancilla.z(2)
qc_with_ancilla.draw("mpl", style="iqp")
[11]:
Define measurement procedure¶
The measurement_layout
argument specifies which qubits to measure. It can be seen as a map from virtual to physical qubits indices. In this example, we only want to measure qubits 1 and 3.
[12]:
# The first local POVM will act on physical qubit 1 and the second local POVM will act on physical qubit 3.
measurement = ClassicalShadows(num_qubits=2, measurement_layout=[1, 3], seed=14)
Run the job¶
Initialize Sampler
and POVMSampler
. Then run the job.
[13]:
from numpy.random import default_rng
from qiskit.primitives import StatevectorSampler
rng = default_rng(26)
sampler = StatevectorSampler(seed=rng)
povm_sampler = POVMSampler(sampler=sampler)
job = povm_sampler.run([qc_with_ancilla], shots=4096, povm=measurement)
pub_result = job.result()[0]
Look at the final composed circuit to check that the measurement was performed on the correct qubits.
[14]:
pub_result.metadata.composed_circuit.draw("mpl")
[14]:
Get the expected value¶
The observable has to be specified in terms of virtual qubits. Therefore, there is no need to apply the layout that was used by the POVM on the full register of qubits.
[15]:
post_processor = POVMPostProcessor(pub_result)
print(f"Exact value: {exact_expectation_value}")
exp_value, std = post_processor.get_expectation_value(observable)
print(f"Estimated value: {exp_value}")
Exact value: 1.9999999999999996
Estimated value: 2.0104980468749996
3. Transpiled Quantum Circuit with ancilla qubits¶
Example where the quantum circuit had ancilla qubits before transpilation.
Transpile circuit with ancilla qubits¶
[16]:
# Transpile the circuit to an "Instruction Set Architecture" (ISA) circuit
qc_with_ancilla_isa = pm.run(qc_with_ancilla)
qc_with_ancilla_isa.draw("mpl", style="iqp")
[16]:
Define measurement procedure¶
The measurement_layout
argument specifies which qubits to measure. This argument overrides the automatic detection of a possible TranspileLayout
attribute of the supplied circuit. I.e., it specifies directly the (final) physical qubits on which the POVM will act. In this example, we want to measure physical qubits 4 and 3.
If you want to specify the virtual qubits instead (here virtual qubits 1 and 3), you have to do the composition with the TranspileLayout
manually as follows:
[17]:
# Measuring virtual qubits 1 and 3
virtual_msmt_layout = [1, 3]
# Get the transpilation layout
transpile_layout = qc_with_ancilla_isa.layout.final_index_layout()
# Compute the final measurement layout
final_msmt_layout = [transpile_layout[idx] for idx in virtual_msmt_layout]
print("Final measurement layout:", final_msmt_layout)
measurement = ClassicalShadows(2, measurement_layout=final_msmt_layout, seed=15)
Final measurement layout: [4, 3]
Run the job¶
Initialize Sampler
and POVMSampler
. Then run the job.
[18]:
# First define a standard sampler (that will be used under the hood).
runtime_sampler = RuntimeSampler(backend=backend)
# Then define the POVM sampler, which takes BaseSampler as an argument.
povm_sampler = POVMSampler(runtime_sampler)
# Submit the job by specifying which POVM to use, which circuit(s) to measure and the shot budget.
job = povm_sampler.run([qc_with_ancilla_isa], shots=4096, povm=measurement)
pub_result = job.result()[0]
You can check that measurement was performed on the correct physical qubits by looking at the final composed circuit.
[19]:
pub_result.metadata.composed_circuit.draw("mpl", style="iqp")
[19]:
Get the expected value¶
The observable has to be specified in terms of virtual qubits. Therefore, there is no need to apply the layout that was used by the POVM on the physical qubits.
[20]:
post_processor = POVMPostProcessor(pub_result)
print(f"Exact value: {exact_expectation_value}")
exp_value, std = post_processor.get_expectation_value(observable)
print(f"Estimated value: {exp_value}")
Exact value: 1.9999999999999996
Estimated value: 1.8706054687499996