Support dynamic circuit in BackendEstimator (#9700)

* Support dynamic circuit in Estimator

* fix lint

* revert the validation condition

* fix test

* add reno

* separate measurements

* lint

* add test and bug fix reno

* fix reno

* revise tests and fix typo

---------

Co-authored-by: Takashi Imamichi <imamichi@jp.ibm.com>
This commit is contained in:
Ikko Hamamura 2023-10-06 14:22:44 +09:00 committed by GitHub
parent d0effae290
commit 660448c02e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 23 deletions

View File

@ -22,8 +22,9 @@ from itertools import accumulate
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.circuit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit.compiler import transpile
from qiskit.exceptions import QiskitError
from qiskit.providers import BackendV1, BackendV2, Options
from qiskit.quantum_info import Pauli, PauliList
from qiskit.quantum_info.operators.base_operator import BaseOperator
@ -216,10 +217,18 @@ class BackendEstimator(BaseEstimator[PrimitiveJob[EstimatorResult]]):
transpiled_circuits = []
for diff_circuit in diff_circuits:
transpiled_circuit_copy = transpiled_circuit.copy()
for creg in diff_circuit.cregs:
if creg not in transpiled_circuit_copy.cregs:
transpiled_circuit_copy.add_register(creg)
transpiled_circuit_copy.compose(diff_circuit, inplace=True)
# diff_circuit is supposed to have a classical register whose name is different from
# those of the transpiled_circuit
clbits = diff_circuit.cregs[0]
for creg in transpiled_circuit_copy.cregs:
if clbits.name == creg.name:
raise QiskitError(
"Classical register for measurements conflict with those of the input "
f"circuit: {clbits}. "
"Recommended to avoid register names starting with '__'."
)
transpiled_circuit_copy.add_register(clbits)
transpiled_circuit_copy.compose(diff_circuit, clbits=clbits, inplace=True)
transpiled_circuit_copy.metadata = diff_circuit.metadata
transpiled_circuits.append(transpiled_circuit_copy)
self._transpiled_circuits += transpiled_circuits
@ -298,7 +307,9 @@ class BackendEstimator(BaseEstimator[PrimitiveJob[EstimatorResult]]):
qubit_indices = np.arange(pauli.num_qubits)[pauli.z | pauli.x]
if not np.any(qubit_indices):
qubit_indices = [0]
meas_circuit = QuantumCircuit(num_qubits, len(qubit_indices))
meas_circuit = QuantumCircuit(
QuantumRegister(num_qubits, "q"), ClassicalRegister(len(qubit_indices), f"__c_{pauli}")
)
for clbit, i in enumerate(qubit_indices):
if pauli.x[i]:
if pauli.z[i]:
@ -432,7 +443,8 @@ def _pauli_expval_with_variance(counts: Counts, paulis: PauliList) -> tuple[np.n
expvals = np.zeros(size, dtype=float)
denom = 0 # Total shots for counts dict
for bin_outcome, freq in counts.items():
outcome = int(bin_outcome, 2)
split_outcome = bin_outcome.split(" ", 1)[0] if " " in bin_outcome else bin_outcome
outcome = int(split_outcome, 2)
denom += freq
for k in range(size):
coeff = (-1) ** _parity(diag_inds[k] & outcome)

View File

@ -0,0 +1,3 @@
---
fixes:
Fix the bug :class:`.BackendEstimator` fails if circuits have classical registers.

View File

@ -113,12 +113,12 @@ class TestBackendEstimator(QiskitTestCase):
self.assertIsInstance(result, EstimatorResult)
np.testing.assert_allclose(result.values, [-1.284366511861733], rtol=0.05)
@combine(backend=BACKENDS)
def test_run_1qubit(self, backend):
@combine(backend=BACKENDS, creg=[True, False])
def test_run_1qubit(self, backend, creg):
"""Test for 1-qubit cases"""
backend.set_options(seed_simulator=123)
qc = QuantumCircuit(1)
qc2 = QuantumCircuit(1)
qc = QuantumCircuit(1, 1) if creg else QuantumCircuit(1)
qc2 = QuantumCircuit(1, 1) if creg else QuantumCircuit(1)
qc2.x(0)
op = SparsePauliOp.from_list([("I", 1)])
@ -141,12 +141,12 @@ class TestBackendEstimator(QiskitTestCase):
self.assertIsInstance(result, EstimatorResult)
np.testing.assert_allclose(result.values, [-1], rtol=0.1)
@combine(backend=BACKENDS)
def test_run_2qubits(self, backend):
@combine(backend=BACKENDS, creg=[True, False])
def test_run_2qubits(self, backend, creg):
"""Test for 2-qubit cases (to check endian)"""
backend.set_options(seed_simulator=123)
qc = QuantumCircuit(2)
qc2 = QuantumCircuit(2)
qc = QuantumCircuit(2, 1) if creg else QuantumCircuit(2)
qc2 = QuantumCircuit(2, 1) if creg else QuantumCircuit(2, 1)
qc2.x(0)
op = SparsePauliOp.from_list([("II", 1)])
@ -191,8 +191,6 @@ class TestBackendEstimator(QiskitTestCase):
est = BackendEstimator(backend=backend)
with self.assertRaises(ValueError):
est.run([qc], [op2], [[]]).result()
with self.assertRaises(ValueError):
est.run([qc2], [op], [[]]).result()
with self.assertRaises(ValueError):
est.run([qc], [op], [[1e4]]).result()
with self.assertRaises(ValueError):
@ -270,10 +268,6 @@ class TestBackendEstimator(QiskitTestCase):
backend = FakeNairobiLimitedCircuits()
backend.set_options(seed_simulator=123)
qc = QuantumCircuit(1)
qc2 = QuantumCircuit(1)
qc2.x(0)
backend.set_options(seed_simulator=123)
qc = RealAmplitudes(num_qubits=2, reps=2)
op = SparsePauliOp.from_list([("IZ", 1), ("XI", 2), ("ZY", -1)])
k = 5
@ -385,6 +379,45 @@ class TestBackendEstimator(QiskitTestCase):
else:
self.assertEqual(value, -1)
@unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test")
def test_circuit_with_measurement(self):
"""Test estimator with a dynamic circuit"""
from qiskit_aer import AerSimulator
bell = QuantumCircuit(2)
bell.h(0)
bell.cx(0, 1)
bell.measure_all()
observable = SparsePauliOp("ZZ")
backend = AerSimulator()
backend.set_options(seed_simulator=15)
estimator = BackendEstimator(backend, skip_transpilation=True)
estimator.set_transpile_options(seed_transpiler=15)
result = estimator.run(bell, observable).result()
self.assertAlmostEqual(result.values[0], 1, places=1)
@unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test")
def test_dynamic_circuit(self):
"""Test estimator with a dynamic circuit"""
from qiskit_aer import AerSimulator
qc = QuantumCircuit(2, 1)
with qc.for_loop(range(5)):
qc.h(0)
qc.cx(0, 1)
qc.measure(1, 0)
qc.break_loop().c_if(0, True)
observable = SparsePauliOp("IZ")
backend = AerSimulator()
backend.set_options(seed_simulator=15)
estimator = BackendEstimator(backend, skip_transpilation=True)
estimator.set_transpile_options(seed_transpiler=15)
result = estimator.run(qc, observable).result()
self.assertAlmostEqual(result.values[0], 0, places=1)
if __name__ == "__main__":
unittest.main()

View File

@ -225,8 +225,6 @@ class TestEstimator(QiskitTestCase):
est = Estimator()
with self.assertRaises(ValueError):
est.run([qc], [op2], [[]]).result()
with self.assertRaises(ValueError):
est.run([qc2], [op], [[]]).result()
with self.assertRaises(ValueError):
est.run([qc], [op], [[1e4]]).result()
with self.assertRaises(ValueError):