mirror of https://github.com/Qiskit/qiskit.git
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:
parent
d0effae290
commit
660448c02e
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
fixes:
|
||||
Fix the bug :class:`.BackendEstimator` fails if circuits have classical registers.
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue