Skip to content

Commit 660448c

Browse files
ikkohamt-imamichi
andauthored
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 <[email protected]>
1 parent d0effae commit 660448c

File tree

4 files changed

+69
-23
lines changed

4 files changed

+69
-23
lines changed

qiskit/primitives/backend_estimator.py

+19-7
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222

2323
import numpy as np
2424

25-
from qiskit.circuit import QuantumCircuit
25+
from qiskit.circuit import ClassicalRegister, QuantumCircuit, QuantumRegister
2626
from qiskit.compiler import transpile
27+
from qiskit.exceptions import QiskitError
2728
from qiskit.providers import BackendV1, BackendV2, Options
2829
from qiskit.quantum_info import Pauli, PauliList
2930
from qiskit.quantum_info.operators.base_operator import BaseOperator
@@ -216,10 +217,18 @@ def _transpile(self):
216217
transpiled_circuits = []
217218
for diff_circuit in diff_circuits:
218219
transpiled_circuit_copy = transpiled_circuit.copy()
219-
for creg in diff_circuit.cregs:
220-
if creg not in transpiled_circuit_copy.cregs:
221-
transpiled_circuit_copy.add_register(creg)
222-
transpiled_circuit_copy.compose(diff_circuit, inplace=True)
220+
# diff_circuit is supposed to have a classical register whose name is different from
221+
# those of the transpiled_circuit
222+
clbits = diff_circuit.cregs[0]
223+
for creg in transpiled_circuit_copy.cregs:
224+
if clbits.name == creg.name:
225+
raise QiskitError(
226+
"Classical register for measurements conflict with those of the input "
227+
f"circuit: {clbits}. "
228+
"Recommended to avoid register names starting with '__'."
229+
)
230+
transpiled_circuit_copy.add_register(clbits)
231+
transpiled_circuit_copy.compose(diff_circuit, clbits=clbits, inplace=True)
223232
transpiled_circuit_copy.metadata = diff_circuit.metadata
224233
transpiled_circuits.append(transpiled_circuit_copy)
225234
self._transpiled_circuits += transpiled_circuits
@@ -298,7 +307,9 @@ def _measurement_circuit(num_qubits: int, pauli: Pauli):
298307
qubit_indices = np.arange(pauli.num_qubits)[pauli.z | pauli.x]
299308
if not np.any(qubit_indices):
300309
qubit_indices = [0]
301-
meas_circuit = QuantumCircuit(num_qubits, len(qubit_indices))
310+
meas_circuit = QuantumCircuit(
311+
QuantumRegister(num_qubits, "q"), ClassicalRegister(len(qubit_indices), f"__c_{pauli}")
312+
)
302313
for clbit, i in enumerate(qubit_indices):
303314
if pauli.x[i]:
304315
if pauli.z[i]:
@@ -432,7 +443,8 @@ def _pauli_expval_with_variance(counts: Counts, paulis: PauliList) -> tuple[np.n
432443
expvals = np.zeros(size, dtype=float)
433444
denom = 0 # Total shots for counts dict
434445
for bin_outcome, freq in counts.items():
435-
outcome = int(bin_outcome, 2)
446+
split_outcome = bin_outcome.split(" ", 1)[0] if " " in bin_outcome else bin_outcome
447+
outcome = int(split_outcome, 2)
436448
denom += freq
437449
for k in range(size):
438450
coeff = (-1) ** _parity(diag_inds[k] & outcome)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
fixes:
3+
Fix the bug :class:`.BackendEstimator` fails if circuits have classical registers.

test/python/primitives/test_backend_estimator.py

+47-14
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,12 @@ def test_estimator_run_no_params(self, backend):
113113
self.assertIsInstance(result, EstimatorResult)
114114
np.testing.assert_allclose(result.values, [-1.284366511861733], rtol=0.05)
115115

116-
@combine(backend=BACKENDS)
117-
def test_run_1qubit(self, backend):
116+
@combine(backend=BACKENDS, creg=[True, False])
117+
def test_run_1qubit(self, backend, creg):
118118
"""Test for 1-qubit cases"""
119119
backend.set_options(seed_simulator=123)
120-
qc = QuantumCircuit(1)
121-
qc2 = QuantumCircuit(1)
120+
qc = QuantumCircuit(1, 1) if creg else QuantumCircuit(1)
121+
qc2 = QuantumCircuit(1, 1) if creg else QuantumCircuit(1)
122122
qc2.x(0)
123123

124124
op = SparsePauliOp.from_list([("I", 1)])
@@ -141,12 +141,12 @@ def test_run_1qubit(self, backend):
141141
self.assertIsInstance(result, EstimatorResult)
142142
np.testing.assert_allclose(result.values, [-1], rtol=0.1)
143143

144-
@combine(backend=BACKENDS)
145-
def test_run_2qubits(self, backend):
144+
@combine(backend=BACKENDS, creg=[True, False])
145+
def test_run_2qubits(self, backend, creg):
146146
"""Test for 2-qubit cases (to check endian)"""
147147
backend.set_options(seed_simulator=123)
148-
qc = QuantumCircuit(2)
149-
qc2 = QuantumCircuit(2)
148+
qc = QuantumCircuit(2, 1) if creg else QuantumCircuit(2)
149+
qc2 = QuantumCircuit(2, 1) if creg else QuantumCircuit(2, 1)
150150
qc2.x(0)
151151

152152
op = SparsePauliOp.from_list([("II", 1)])
@@ -191,8 +191,6 @@ def test_run_errors(self, backend):
191191
est = BackendEstimator(backend=backend)
192192
with self.assertRaises(ValueError):
193193
est.run([qc], [op2], [[]]).result()
194-
with self.assertRaises(ValueError):
195-
est.run([qc2], [op], [[]]).result()
196194
with self.assertRaises(ValueError):
197195
est.run([qc], [op], [[1e4]]).result()
198196
with self.assertRaises(ValueError):
@@ -270,10 +268,6 @@ def max_circuits(self):
270268

271269
backend = FakeNairobiLimitedCircuits()
272270
backend.set_options(seed_simulator=123)
273-
qc = QuantumCircuit(1)
274-
qc2 = QuantumCircuit(1)
275-
qc2.x(0)
276-
backend.set_options(seed_simulator=123)
277271
qc = RealAmplitudes(num_qubits=2, reps=2)
278272
op = SparsePauliOp.from_list([("IZ", 1), ("XI", 2), ("ZY", -1)])
279273
k = 5
@@ -385,6 +379,45 @@ def test_layout(self, backend):
385379
else:
386380
self.assertEqual(value, -1)
387381

382+
@unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test")
383+
def test_circuit_with_measurement(self):
384+
"""Test estimator with a dynamic circuit"""
385+
from qiskit_aer import AerSimulator
386+
387+
bell = QuantumCircuit(2)
388+
bell.h(0)
389+
bell.cx(0, 1)
390+
bell.measure_all()
391+
observable = SparsePauliOp("ZZ")
392+
393+
backend = AerSimulator()
394+
backend.set_options(seed_simulator=15)
395+
estimator = BackendEstimator(backend, skip_transpilation=True)
396+
estimator.set_transpile_options(seed_transpiler=15)
397+
result = estimator.run(bell, observable).result()
398+
self.assertAlmostEqual(result.values[0], 1, places=1)
399+
400+
@unittest.skipUnless(optionals.HAS_AER, "qiskit-aer is required to run this test")
401+
def test_dynamic_circuit(self):
402+
"""Test estimator with a dynamic circuit"""
403+
from qiskit_aer import AerSimulator
404+
405+
qc = QuantumCircuit(2, 1)
406+
with qc.for_loop(range(5)):
407+
qc.h(0)
408+
qc.cx(0, 1)
409+
qc.measure(1, 0)
410+
qc.break_loop().c_if(0, True)
411+
412+
observable = SparsePauliOp("IZ")
413+
414+
backend = AerSimulator()
415+
backend.set_options(seed_simulator=15)
416+
estimator = BackendEstimator(backend, skip_transpilation=True)
417+
estimator.set_transpile_options(seed_transpiler=15)
418+
result = estimator.run(qc, observable).result()
419+
self.assertAlmostEqual(result.values[0], 0, places=1)
420+
388421

389422
if __name__ == "__main__":
390423
unittest.main()

test/python/primitives/test_estimator.py

-2
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,6 @@ def test_run_errors(self):
225225
est = Estimator()
226226
with self.assertRaises(ValueError):
227227
est.run([qc], [op2], [[]]).result()
228-
with self.assertRaises(ValueError):
229-
est.run([qc2], [op], [[]]).result()
230228
with self.assertRaises(ValueError):
231229
est.run([qc], [op], [[1e4]]).result()
232230
with self.assertRaises(ValueError):

0 commit comments

Comments
 (0)