diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py index b2f0cb3ce56d..fe582540a647 100644 --- a/qiskit/providers/backend_compat.py +++ b/qiskit/providers/backend_compat.py @@ -70,7 +70,11 @@ def convert_to_target( if filter_faulty: faulty_qubits = set(properties.faulty_qubits()) qubit_properties = qubit_props_list_from_props(properties=properties) - target = Target(num_qubits=configuration.n_qubits, qubit_properties=qubit_properties) + target = Target( + num_qubits=configuration.n_qubits, + qubit_properties=qubit_properties, + concurrent_measurements=getattr(configuration, "meas_map", None), + ) # Parse instructions gates: Dict[str, Any] = {} for gate in properties.gates: @@ -122,7 +126,10 @@ def convert_to_target( target.add_instruction(Measure(), measure_props) # Parse from configuration because properties doesn't exist else: - target = Target(num_qubits=configuration.n_qubits) + target = Target( + num_qubits=configuration.n_qubits, + concurrent_measurements=getattr(configuration, "meas_map", None), + ) for gate in configuration.gates: name = gate.name gate_props = ( diff --git a/qiskit/providers/fake_provider/utils/backend_converter.py b/qiskit/providers/fake_provider/utils/backend_converter.py index 25d9aebba153..9e038c39df38 100644 --- a/qiskit/providers/fake_provider/utils/backend_converter.py +++ b/qiskit/providers/fake_provider/utils/backend_converter.py @@ -43,7 +43,7 @@ def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict qubit_props = None if props_dict: qubit_props = qubit_props_from_props(props_dict) - target = Target(qubit_properties=qubit_props) + target = Target(qubit_properties=qubit_props, concurrent_measurements=conf_dict.get("meas_map")) # Parse from properties if it exsits if props_dict is not None: # Parse instructions diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 08c5dfdfe38d..05c207db59a4 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -21,7 +21,7 @@ import itertools -from typing import Any +from typing import Optional, List, Any from collections.abc import Mapping from collections import defaultdict import datetime @@ -239,6 +239,7 @@ class Target(Mapping): "_non_global_strict_basis", "qubit_properties", "_global_operations", + "concurrent_measurements", ) @deprecate_arg("aquire_alignment", new_alias="acquire_alignment", since="0.23.0") @@ -252,6 +253,7 @@ def __init__( pulse_alignment=1, acquire_alignment=1, qubit_properties=None, + concurrent_measurements=None, ): """ Create a new Target object @@ -287,7 +289,9 @@ def __init__( matches the qubit number the properties are defined for. If some qubits don't have properties available you can set that entry to ``None`` - Raises: + concurrent_measurements(list): A list of sets of qubits that must be + measured together. This must be provided + as a nested list like [[0, 1], [2, 3, 4]]. ValueError: If both ``num_qubits`` and ``qubit_properties`` are both defined and the value of ``num_qubits`` differs from the length of ``qubit_properties``. @@ -322,6 +326,7 @@ def __init__( "length of the input qubit_properties list" ) self.qubit_properties = qubit_properties + self.concurrent_measurements = concurrent_measurements def add_instruction(self, instruction, properties=None, name=None): """Add a new instruction to the :class:`~qiskit.transpiler.Target` @@ -1215,6 +1220,7 @@ def from_configuration( inst_map: InstructionScheduleMap | None = None, backend_properties: BackendProperties | None = None, instruction_durations: InstructionDurations | None = None, + concurrent_measurements: Optional[List[List[int]]] = None, dt: float | None = None, timing_constraints: TimingConstraints | None = None, custom_name_mapping: dict[str, Any] | None = None, @@ -1263,6 +1269,9 @@ def from_configuration( instruction_durations: Optional instruction durations for instructions. If specified it will take priority for setting the ``duration`` field in the :class:`~InstructionProperties` objects for the instructions in the target. + concurrent_measurements(list): A list of sets of qubits that must be + measured together. This must be provided + as a nested list like [[0, 1], [2, 3, 4]]. dt: The system time resolution of input signals in seconds timing_constraints: Optional timing constraints to include in the :class:`~.Target` @@ -1306,6 +1315,7 @@ def from_configuration( pulse_alignment=pulse_alignment, acquire_alignment=acquire_alignment, qubit_properties=qubit_properties, + concurrent_measurements=concurrent_measurements, ) name_mapping = get_standard_gate_name_mapping() if custom_name_mapping is not None: diff --git a/releasenotes/notes/enable_target_aware_meas_map-0d8542402a74e9d8.yaml b/releasenotes/notes/enable_target_aware_meas_map-0d8542402a74e9d8.yaml new file mode 100644 index 000000000000..b0bb3df33f94 --- /dev/null +++ b/releasenotes/notes/enable_target_aware_meas_map-0d8542402a74e9d8.yaml @@ -0,0 +1,16 @@ +--- +features: + - | + Added :attr:`~.Target.concurrent_measurements` which represents a hardware constraint of qubits + measured concurrently. This constraint is provided in the nested list form, + in which each element represents qubit group to be measured together. + In an example below, + + .. code-block:: python + + [[0, 1], [2, 3, 4]] + + qubits 0 and 1, and 2, 3 and 4 are measured together on the device. + This constraint doesn't block measuring an individual qubit, + however, Qiskit scheduler must consider the alignment of + measure operations for those qubits. \ No newline at end of file diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index d634741cc1bc..d352846c5dae 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1903,6 +1903,15 @@ def test_inst_map(self): self.assertEqual(target.pulse_alignment, constraints.pulse_alignment) self.assertEqual(target.acquire_alignment, constraints.acquire_alignment) + def test_concurrent_measurements(self): + fake_backend = FakeVigo() + config = fake_backend.configuration() + target = Target.from_configuration( + basis_gates=config.basis_gates, + concurrent_measurements=config.meas_map, + ) + self.assertEqual(target.concurrent_measurements, config.meas_map) + def test_custom_basis_gates(self): basis_gates = ["my_x", "cx"] custom_name_mapping = {"my_x": XGate()}