Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a workaround to #422, Sampler failing when no measurements #426

Merged
merged 9 commits into from
Oct 10, 2023
11 changes: 9 additions & 2 deletions circuit_knitting/cutting/cutting_experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,21 @@ def _append_measurement_circuit(
if not inplace:
qc = qc.copy()

# If the circuit has no measurements, the Sampler will fail. So, we
# measure one qubit as a temporary workaround to
# https://github.com/Qiskit-Extensions/circuit-knitting-toolbox/issues/422
pauli_indices = cog.pauli_indices
if not pauli_indices:
Copy link
Collaborator

@caleb-johnson caleb-johnson Oct 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What can pauli_indices be here? Should we just explicitly state what we expect? (e.g. if pauli_indices == [])

Or we could just assert pauli_indices == [] inside the if? (assuming an empty list is what is returned in I observable case)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What can pauli_indices be here? Should we just explicitly state what we expect? (e.g. if pauli_indices == [])

It's a sequence of ints. It contains the qubit indices that need to be measured.

The choice of style here is influenced by PEP 8:

For sequences, (strings, lists, tuples), use the fact that empty sequences are false:

# Correct:
if not seq:
if seq:
# Wrong:
if len(seq):
if not len(seq):

The "nice" thing about doing it this way is it doesn't matter what type the sequence is. For instance, if it were to be a tuple of indices, then it will still behave correctly with the current code, but it would not if we were to compare with [] because () != [].

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, thanks for explanation

Copy link
Collaborator

@ibrahim-shehzad ibrahim-shehzad Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, this is more profound than I thought. Nice!

pauli_indices = [0]

# Append the appropriate measurements to qc
obs_creg = ClassicalRegister(len(cog.pauli_indices), name="observable_measurements")
obs_creg = ClassicalRegister(len(pauli_indices), name="observable_measurements")
qc.add_register(obs_creg)
# Implement the necessary basis rotations and measurements, as
# in BackendEstimator._measurement_circuit().
genobs_x = cog.general_observable.x
genobs_z = cog.general_observable.z
for clbit, subqubit in enumerate(cog.pauli_indices):
for clbit, subqubit in enumerate(pauli_indices):
# subqubit is the index of the qubit in the subsystem.
# actual_qubit is its index in the system of interest (if different).
actual_qubit = qubit_locations[subqubit]
Expand Down
28 changes: 28 additions & 0 deletions test/cutting/test_cutting_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from qiskit.extensions import UnitaryGate
from qiskit.quantum_info import PauliList, random_unitary
from qiskit.primitives import Estimator
from qiskit_aer.primitives import Sampler

from circuit_knitting.utils.simulation import ExactSampler
from circuit_knitting.cutting import (
Expand Down Expand Up @@ -179,3 +180,30 @@ def test_cutting_exact_reconstruction(example_circuit):
logger.info("Max error: %f", np.max(np.abs(exact_expvals - simulated_expvals)))

assert np.allclose(exact_expvals, simulated_expvals, atol=1e-8)


def test_sampler_with_identity_subobservable(example_circuit):
"""This test ensures that the sampler does not throw an error if you pass it a subcircuit with no observable measurements.

Tests temporary workaround to Issue #422.

This test passes if no exceptions are raised.

"""

qc = example_circuit
observable_to_test = PauliList(
["IIZ"]
) # Without the workaround to Issue #422, this observable causes a Sampler error.
subcircuits, bases, subobservables = partition_problem(
qc, "AAB", observables=observable_to_test
)
subexperiments, coefficients = generate_cutting_experiments(
subcircuits, subobservables, num_samples=np.inf
)
samplers = {label: Sampler() for label in subexperiments.keys()}
results = {
label: sampler.run(subexperiments[label]).result()
for label, sampler in samplers.items()
}
_ = results