diff --git a/qiskit_experiments/calibration_management/base_calibration_experiment.py b/qiskit_experiments/calibration_management/base_calibration_experiment.py index 985f39a060..d453f0b31b 100644 --- a/qiskit_experiments/calibration_management/base_calibration_experiment.py +++ b/qiskit_experiments/calibration_management/base_calibration_experiment.py @@ -316,10 +316,14 @@ def _map_to_physical_qubits(self, circuit: QuantumCircuit) -> QuantumCircuit: """ initial_layout = Layout.from_intlist(list(self.physical_qubits), *circuit.qregs) + coupling_map = self._backend_data.coupling_map + if coupling_map is not None: + coupling_map = CouplingMap(self._backend_data.coupling_map) + layout = PassManager( [ SetLayout(initial_layout), - FullAncillaAllocation(CouplingMap(self._backend_data.coupling_map)), + FullAncillaAllocation(coupling_map), EnlargeWithAncilla(), ApplyLayout(), ] diff --git a/releasenotes/notes/cals-no-coupling-map-5114ae9faa2f9e69.yaml b/releasenotes/notes/cals-no-coupling-map-5114ae9faa2f9e69.yaml new file mode 100644 index 0000000000..c996c31f5d --- /dev/null +++ b/releasenotes/notes/cals-no-coupling-map-5114ae9faa2f9e69.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed error generating circuits for :class:`.BaseCalibrationExperiment` + subclasses when the backend instance had no coupling map. Fixed `#1116 + `_. diff --git a/test/calibration/test_base_calibration_experiment.py b/test/calibration/test_base_calibration_experiment.py index ba5bd23160..bbbea31598 100644 --- a/test/calibration/test_base_calibration_experiment.py +++ b/test/calibration/test_base_calibration_experiment.py @@ -14,16 +14,18 @@ from test.base import QiskitExperimentsTestCase +from qiskit import QuantumCircuit from qiskit.circuit import Parameter from qiskit.pulse import Play, Constant, DriveChannel, ScheduleBlock -from qiskit_experiments.library import QubitSpectroscopy from qiskit_experiments.calibration_management.base_calibration_experiment import ( BaseCalibrationExperiment, Calibrations, ) from qiskit_experiments.framework.composite import ParallelExperiment, BatchExperiment +from qiskit_experiments.library import QubitSpectroscopy from qiskit_experiments.test.fake_backend import FakeBackend + from .utils import MockCalExperiment, DoNothingAnalysis @@ -324,3 +326,36 @@ def test_update_calibration_parallel(self): new_schedule2 = cals.get_schedule("test2", (1,)) ref_schedule2 = schedule2.assign_parameters({param2: ref_new_value2}, inplace=False) self.assertEqual(new_schedule2, ref_schedule2) + + def test_transpiled_circuits_no_coupling_map(self): + """Test transpilation of calibration experiment with no coupling map""" + # This test was added to catch errors found when running calibration + # experiments against DynamicsBackend from qiskit-dynamics for which + # the coupling map could be None. Previously, this led to + # BaseCalibrationExperiment's custom pass manager failing. + backend = FakeBackend(num_qubits=2) + # If the following fails, it should be reassessed if this test is still + # useful + self.assertTrue(backend.coupling_map is None) + + cals = Calibrations() + + # Build a circuit to be passed through transpilation pipeline + qc = QuantumCircuit(1, 1) + qc.x(0) + qc.measure(0, 0) + + exp = MockCalExperiment( + physical_qubits=(1,), + calibrations=cals, + new_value=0.2, + param_name="amp", + sched_name="x", + backend=backend, + circuits=[qc], + ) + transpiled = exp._transpiled_circuits()[0] + # Make sure circuit was expanded with the ancilla on qubit 0 + self.assertEqual(len(transpiled.qubits), 2) + # Make sure instructions were unchanged + self.assertDictEqual(transpiled.count_ops(), qc.count_ops()) diff --git a/test/calibration/utils.py b/test/calibration/utils.py index 6e7dfd2e58..2f9dd7b96b 100644 --- a/test/calibration/utils.py +++ b/test/calibration/utils.py @@ -13,7 +13,10 @@ """Utility to test calibration module.""" import datetime -from typing import Sequence +from typing import Optional, Sequence + +from qiskit import QuantumCircuit +from qiskit.providers import Backend from qiskit_experiments.calibration_management import ( BaseCalibrationExperiment, @@ -46,12 +49,25 @@ def _run_analysis( class DoNothingExperiment(BaseExperiment): """Experiment doesn't provide any circuit to run.""" - def __init__(self, physical_qubits: Sequence[int], return_value: float): - super().__init__(physical_qubits=physical_qubits, analysis=DoNothingAnalysis()) + def __init__( + self, + physical_qubits: Sequence[int], + return_value: float, + circuits: Optional[Sequence[QuantumCircuit]] = None, + backend: Optional[Backend] = None, + ): + super().__init__( + physical_qubits=physical_qubits, analysis=DoNothingAnalysis(), backend=backend + ) self.analysis.set_options(return_value=return_value) + if circuits is not None: + self._circuits = circuits + else: + self._circuits = [] + def circuits(self): - return [] + return self._circuits class MockCalExperiment(BaseCalibrationExperiment, DoNothingExperiment): @@ -69,6 +85,8 @@ def __init__( new_value: float, param_name: str, sched_name: str, + circuits: Optional[Sequence[QuantumCircuit]] = None, + backend: Optional[Backend] = None, ): """Create mock calibration experiment. @@ -78,11 +96,15 @@ def __init__( new_value: New parameter value obtained by the calibration experiment. param_name: Name of parameter to update. sched_name: Name of schedule to update. + circuits: List of QuantumCircuits for the circuits() method + backend: Backend to set on experiment """ super().__init__( physical_qubits=physical_qubits, calibrations=calibrations, return_value=new_value, + circuits=circuits, + backend=backend, ) self.to_update = { "param": param_name,