diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 1edc89013572..fc521dc1f5f1 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -41,6 +41,7 @@ from qiskit.circuit.parameterexpression import ParameterValueType from qiskit.circuit.gate import Gate from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping +from qiskit.circuit.controlflow import ForLoopOp, IfElseOp, SwitchCaseOp, WhileLoopOp from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.pulse.calibration_entries import CalibrationEntry, ScheduleDef from qiskit.pulse.schedule import Schedule, ScheduleBlock @@ -49,7 +50,7 @@ from qiskit.transpiler.instruction_durations import InstructionDurations from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.providers.exceptions import BackendPropertyError -from qiskit.pulse.exceptions import PulseError, UnassignedDurationError +from qiskit.pulse.exceptions import UnassignedDurationError from qiskit.exceptions import QiskitError # import QubitProperties here to provide convenience alias for building a @@ -992,23 +993,32 @@ def from_configuration( the :class:`~.Target`. This function provides a simple interface to convert those separate objects to a :class:`~.Target`. - This constructor will use the input from ``basis_gates``, ``num_qubits``, - and ``coupling_map`` to build a base model of the backend and the - ``instruction_durations``, ``backend_properties``, and ``inst_map`` inputs - are then queried (in that order) based on that model to look up the properties - of each instruction and qubit. If there is an inconsistency between the inputs - any extra or conflicting information present in ``instruction_durations``, - ``backend_properties``, or ``inst_map`` will be ignored. + Given the ``basis_gates`` this constructor will try to collect as much information + as possible to build the target from any of the objects ``InstructionDurations``, + ``BackendProperties``, ``InstructionScheduleMap``, ``CouplingMap``. + + ``instruction_durations``, ``backend_properties``, and ``inst_map`` inputs are then + queried (in that order) to extract the duration of instructions. + + ``BackendProperties`` is mandatory to get the error of instructions and qubits' + properties. + Args: basis_gates: The list of basis gate names for the backend. For the target to be created these names must either be in the output from :func:`~.get_standard_gate_name_mapping` or present in the specified ``custom_name_mapping`` argument. - num_qubits: The number of qubits supported on the backend. + + num_qubits: The number of qubits supported on the backend. In case not + provided, constructor will try to set it with information present + in any ``InstructionDurations``, ``BackendProperties``, + ``InstructionScheduleMap``, or ``CouplingMap``. + coupling_map: The coupling map representing connectivity constraints on the backend. If specified all gates from ``basis_gates`` will be supported on all qubits (or pairs of qubits). + inst_map: DEPRECATED. The instruction schedule map representing the pulse :class:`~.Schedule` definitions for each instruction. If this is specified ``coupling_map`` must be specified. The @@ -1016,24 +1026,31 @@ def from_configuration( and if ``inst_map`` is used the schedule is looked up based on the instructions from the pair of ``basis_gates`` and ``coupling_map``. If you want to define a custom gate for - a particular qubit or qubit pair, you can manually build :class:`.Target`. + a particular qubit or qubit pair, you can manually build :class:`.Target` + backend_properties: The :class:`~.BackendProperties` object which is - used for instruction properties and qubit properties. - If specified and instruction properties are intended to be used - then the ``coupling_map`` argument must be specified. This is - only used to lookup error rates and durations (unless - ``instruction_durations`` is specified which would take - precedence) for instructions specified via ``coupling_map`` and - ``basis_gates``. + used for Qubit Properties and error and durations of instruction properties. + In case of durations, ``instruction_durations`` argument if specified, would + take precedence for instructions specified in ``basis_gates``. + 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` + In case ``dt`` is not specified, ``dt`` extracted from this argument will be set + as the ``dt`` of the ``Target``. + + concurrent_measurements: A list of lists of qubits that must be measured together. + This must be provided as a nested list like ``[[0, 1], [2, 3, 4]]``. + If this argument is not provided then ``InstructionScheduleMap`` would be used + to extract information to set this argument. + + dt: The system time resolution of input signals in seconds. + If not provided then ``InstructionDurations`` is used to extract the information + to set this argument. In case this argument is unequal to the ``dt`` provided in + ``InstructionDurations`` the latter will take precedence. + + timing_constraints: Optional timing constraints to include in the :class:`~.Target`. + custom_name_mapping: An optional dictionary that maps custom gate/operation names in ``basis_gates`` to an :class:`~.Operation` object representing that gate/operation. By default, most standard gates names are mapped to the @@ -1045,10 +1062,9 @@ def from_configuration( Target: the target built from the input configuration Raises: - TranspilerError: If the input basis gates contain > 2 qubits and ``coupling_map`` is - specified. - KeyError: If no mapping is available for a specified ``basis_gate``. + TranspilerError: If no mapping is available for a specified ``basis_gate``. """ + granularity = 1 min_length = 1 pulse_alignment = 1 @@ -1059,151 +1075,329 @@ def from_configuration( pulse_alignment = timing_constraints.pulse_alignment acquire_alignment = timing_constraints.acquire_alignment - qubit_properties = None - if backend_properties is not None: - # pylint: disable=cyclic-import - from qiskit.providers.backend_compat import qubit_props_list_from_props - - qubit_properties = qubit_props_list_from_props(properties=backend_properties) - - target = cls( - num_qubits=num_qubits, - dt=dt, - granularity=granularity, - min_length=min_length, - pulse_alignment=pulse_alignment, - acquire_alignment=acquire_alignment, - qubit_properties=qubit_properties, - concurrent_measurements=concurrent_measurements, - ) - name_mapping = get_standard_gate_name_mapping() + required_operations = {"measure", "delay"} + all_instructions = set.union(required_operations, set(basis_gates)) + faulty_qubits = set() + faulty_ops = set() + qiskit_inst_mapping = get_standard_gate_name_mapping() if custom_name_mapping is not None: - name_mapping.update(custom_name_mapping) - - # While BackendProperties can also contain coupling information we - # rely solely on CouplingMap to determine connectivity. This is because - # in legacy transpiler usage (and implicitly in the BackendV1 data model) - # the coupling map is used to define connectivity constraints and - # the properties is only used for error rate and duration population. - # If coupling map is not specified we ignore the backend_properties - if coupling_map is None: - for gate in basis_gates: - if gate not in name_mapping: - raise KeyError( - f"The specified basis gate: {gate} is not present in the standard gate " - "names or a provided custom_name_mapping" - ) - target.add_instruction(name_mapping[gate], name=gate) - else: - one_qubit_gates = [] - two_qubit_gates = [] - global_ideal_variable_width_gates = [] # pylint: disable=invalid-name - if num_qubits is None: - num_qubits = len(coupling_map.graph) - for gate in basis_gates: - if gate not in name_mapping: - raise KeyError( - f"The specified basis gate: {gate} is not present in the standard gate " - "names or a provided custom_name_mapping" - ) - gate_obj = name_mapping[gate] - if gate_obj.num_qubits == 1: - one_qubit_gates.append(gate) - elif gate_obj.num_qubits == 2: - two_qubit_gates.append(gate) - elif inspect.isclass(gate_obj): - global_ideal_variable_width_gates.append(gate) - else: - raise TranspilerError( - f"The specified basis gate: {gate} has {gate_obj.num_qubits} " - "qubits. This constructor method only supports fixed width operations " - "with <= 2 qubits (because connectivity is defined on a CouplingMap)." + qiskit_inst_mapping.update(custom_name_mapping) + + qiskit_control_flow_mapping = { + "if_else": IfElseOp, + "while_loop": WhileLoopOp, + "for_loop": ForLoopOp, + "switch_case": SwitchCaseOp, + } + + inst_name_map = {} # type: Dict[str, Instruction] + in_data_target = { + "num_qubits": num_qubits, + "granularity": granularity, + "min_length": min_length, + "pulse_alignment": pulse_alignment, + "acquire_alignment": acquire_alignment, + "dt": dt, + "concurrent_measurements": concurrent_measurements, + } + + for name in all_instructions: + if name in qiskit_control_flow_mapping: + continue + if name in qiskit_inst_mapping: + inst_name_map[name] = qiskit_inst_mapping[name] + else: + raise TranspilerError( + f"The specified basis gate: {name} is neither present in the standard gate " + "names nor in the provided `custom_name_mapping`" + ) + + # Create inst properties placeholder + # Without any assignment, properties value is None, + # which defines a global instruction that can be applied to any qubit(s). + # The None value behaves differently from an empty dictionary. + # See API doc of Target.add_instruction for details. + prop_name_map = dict.fromkeys(all_instructions) + + # This covers the case when backend_properties is None and InstructionDuration exists + # The InstructionDurations will be used to set the instructions' duration in + # InstructionProperties. + if backend_properties is None and instruction_durations is not None: + if in_data_target["dt"] is None: + in_data_target["dt"] = instruction_durations.dt + elif in_data_target["dt"] != instruction_durations.dt: + warnings.warn( + "`dt` from argument is different from `dt` reported by `InstructionDurations`, so, " + "`dt` from `InstructionDurations` overrides `dt` from argument.", + RuntimeWarning, + ) + in_data_target["dt"] = instruction_durations.dt + + for name_qubits, _ in instruction_durations.duration_by_name_qubits.items(): + name, qubits = name_qubits + duration = instruction_durations.get( + inst=name, qubits=list(qubits), unit="s", parameters=None + ) + + if prop_name_map[name] is None: + prop_name_map[name] = {} + + # Since, BackendProperties is None, instructions' error cannot be obtained. + prop_name_map[name][qubits] = InstructionProperties(error=None, duration=duration) + if isinstance(prop_name_map[name], dict) and any( + v is None for v in prop_name_map[name].values() + ): + # Properties provides gate properties only for subset of qubits + logger.info( + "Gate properties of instruction %s are not provided for every qubits. " + "This gate is ideal for some qubits and the rest is with finite error. " + "Created backend target may confuse error-aware circuit optimization.", + name, ) - for gate in one_qubit_gates: - gate_properties: dict[tuple, InstructionProperties] = {} - for qubit in range(num_qubits): - error = None - duration = None - calibration = None - if backend_properties is not None: - if duration is None: + + # Populate InstructionProperties + if backend_properties is not None: + faulty_qubits = set(backend_properties.faulty_qubits()) + + for name in all_instructions: + + # BackendProperties doesn't have gate_property for 'Measure'. + if name in required_operations: + continue + + try: + for qubits, params in backend_properties.gate_property(name).items(): + if set.intersection( + faulty_qubits, qubits + ) or not backend_properties.is_gate_operational(name, qubits): + faulty_ops.add((name, qubits)) + continue + + error = params["gate_error"][0] if "gate_error" in params else None + if instruction_durations is not None: try: - duration = backend_properties.gate_length(gate, qubit) - except BackendPropertyError: - duration = None - try: - error = backend_properties.gate_error(gate, qubit) - except BackendPropertyError: - error = None - if inst_map is not None: - try: - calibration = inst_map._get_calibration_entry(gate, qubit) - # If we have dt defined and there is a custom calibration which is user - # generate use that custom pulse schedule for the duration. If it is - # not user generated than we assume it's the same duration as what is - # defined in the backend properties - if dt and calibration.user_provided: - duration = calibration.get_schedule().duration * dt - except PulseError: - calibration = None - # Durations if specified manually should override model objects - if instruction_durations is not None: - try: - duration = instruction_durations.get(gate, qubit, unit="s") - except TranspilerError: - duration = None + # setting unit to 's' becasue 'gate_length' from + # BackendProperties is also reported in 's'. + duration = instruction_durations.get( + inst=name, qubits=list(qubits), unit="s", parameters=None + ) + except TranspilerError: + warnings.warn( + f"Duration of instruction {name} on qubits {qubits} is " + "not found in `InstructionDurations` passed, " + "falling back to `BackendProperties` to fetch duration " + "for this particular gate,qubits combination", + RuntimeWarning, + ) + duration = ( + params["gate_length"][0] if "gate_length" in params else None + ) + else: + duration = params["gate_length"][0] if "gate_length" in params else None + + if prop_name_map[name] is None: + prop_name_map[name] = {} + + prop_name_map[name][qubits] = InstructionProperties( + error=error, duration=duration + ) - if error is None and duration is None and calibration is None: - gate_properties[(qubit,)] = None - else: - gate_properties[(qubit,)] = InstructionProperties( - duration=duration, error=error, calibration=calibration + if isinstance(prop_name_map[name], dict) and any( + v is None for v in prop_name_map[name].values() + ): + # Properties provides gate properties only for subset of qubits + logger.info( + "Gate properties of instruction %s are not provided for every qubits. " + "This gate is ideal for some qubits and the rest is with finite error. " + "Created backend target may confuse error-aware circuit optimization.", + name, + ) + except BackendPropertyError: + logger.info("backend does not report any property for gate: %s", name) + continue + + # If num_qubits is not set, + # Setting num_qubits from BackendProperteis.qubits + if in_data_target["num_qubits"] is None: + in_data_target["num_qubits"] = len(backend_properties.qubits) + + # Measure instruction property is stored in qubit property + prop_name_map["measure"] = {} + for qubit_idx in range(in_data_target["num_qubits"]): + qubit_prop = backend_properties.qubit_property(qubit_idx) + duration = ( + qubit_prop["readout_length"][0] if "readout_length" in qubit_prop else None + ) + error = qubit_prop["readout_error"][0] if "readout_error" in qubit_prop else None + prop_name_map["measure"][(qubit_idx,)] = InstructionProperties( + duration=duration, error=error + ) + + # Populate QubitProperties + qubit_properties = [] + for qubit_idx in range(in_data_target["num_qubits"]): + try: + qubit_prop = backend_properties.qubit_property(qubit_idx) + qubit_properties.append( + QubitProperties( + t1=qubit_prop["T1"][0], + t2=qubit_prop["T2"][0], + frequency=qubit_prop["frequency"][0], ) - target.add_instruction(name_mapping[gate], properties=gate_properties, name=gate) - edges = list(coupling_map.get_edges()) - for gate in two_qubit_gates: - gate_properties = {} - for edge in edges: - error = None - duration = None - calibration = None - if backend_properties is not None: - if duration is None: - try: - duration = backend_properties.gate_length(gate, edge) - except BackendPropertyError: - duration = None - try: - error = backend_properties.gate_error(gate, edge) - except BackendPropertyError: - error = None - if inst_map is not None: - try: - calibration = inst_map._get_calibration_entry(gate, edge) - # If we have dt defined and there is a custom calibration which is user - # generate use that custom pulse schedule for the duration. If it is - # not user generated than we assume it's the same duration as what is - # defined in the backend properties - if dt and calibration.user_provided: - duration = calibration.get_schedule().duration * dt - except PulseError: - calibration = None - # Durations if specified manually should override model objects - if instruction_durations is not None: - try: - duration = instruction_durations.get(gate, edge, unit="s") - except TranspilerError: - duration = None + ) + except BackendPropertyError: + logger.info("backend does not report qubit property for index %s", qubit_idx) + + in_data_target["qubit_properties"] = qubit_properties + + # If only InstructionScheduleMap is present, 'dt' is required to get the duration of + # InstructionProperties in 's'. + if ( + instruction_durations is None + and backend_properties is None + and in_data_target["dt"] is None + and inst_map is not None + ): + warnings.warn( + "please set the `dt` argument to set the duration for the instructions", + RuntimeWarning, + ) - if error is None and duration is None and calibration is None: - gate_properties[edge] = None - else: - gate_properties[edge] = InstructionProperties( - duration=duration, error=error, calibration=calibration + if inst_map is not None: + for name in inst_map.instructions: + + # Need to ignore some Instruction still reported in + # Backend's pulse_defaults (for example 'u'). + if name not in all_instructions: + continue + + for qubits in inst_map.qubits_with_instruction(name): + if not isinstance(qubits, tuple): + qubits = (qubits,) + + if backend_properties is None and instruction_durations is None: + + if name == "measure": + # More than 1 qubit's index, suggests `concurrent_measurements`. + if len(qubits) > 1: + if in_data_target["concurrent_measurements"] is None: + in_data_target["concurrent_measurements"] = [] + in_data_target["concurrent_measurements"].append(list(qubits)) + continue + + # Creating just a place holder for InstructionProperties + # duration will be set by duration provided by InstructionScheduleMap + # down the line. + if prop_name_map[name] is None: + prop_name_map[name] = {} + prop_name_map[name][qubits] = InstructionProperties( + error=None, duration=None ) - target.add_instruction(name_mapping[gate], properties=gate_properties, name=gate) - for gate in global_ideal_variable_width_gates: - target.add_instruction(name_mapping[gate], name=gate) + + elif name not in prop_name_map or qubits not in prop_name_map[name]: + logger.info( + "Gate calibration for instruction %s on qubits %s is found " + "in the PulseDefaults payload. However, this entry is not defined in " + "the gate mapping of Target. This calibration is ignored.", + name, + qubits, + ) + continue + + if (name, qubits) in faulty_ops: + continue + + entry = inst_map._get_calibration_entry(name, qubits) + + try: + prop_name_map[name][qubits].calibration = entry + except AttributeError: + logger.info( + "The PulseDefaults payload received contains an instruction %s on " + "qubits %s which is not present in the configuration or properties payload.", + name, + qubits, + ) + + # If duration of instruction is still None, that means + # InstructionDurations, BackendProperties is None + # So, setting the duration from InstructionScheduleMap + # But, 'dt' is required here to get the durations in 's'. + if ( + prop_name_map[name][qubits].duration is None + and in_data_target["dt"] is not None + ): + prop_name_map[name][qubits].duration = ( + entry.get_schedule().duration * in_data_target["dt"] + ) + + if ( + coupling_map is not None + and backend_properties is None + and instruction_durations is None + and inst_map is None + ): + + warnings.warn( + "`InstructionDurations`, `BackendProperties`, and `InstructionScheduleMap`" + "is not found so setting `duration`, `error`, and `calibration` of " + "`InstructionProperties to be None`", + RuntimeWarning, + ) + + for name in all_instructions: + if prop_name_map[name] is None: + prop_name_map[name] = {} + + if inst_name_map[name].num_qubits == 1: + for qubit in coupling_map.physical_qubits: + # Not setting None, just to keep a place holder for duration, error, and + # calibration. None will indicate that gate is 'ideal'. + prop_name_map[name][(qubit,)] = InstructionProperties() + elif inst_name_map[name].num_qubits == 2: + for edge in coupling_map.get_edges(): + # Not setting None, just to keep a place holder for duration, error, and + # calibration. None will indicate that gate is 'ideal'. + prop_name_map[name][edge] = InstructionProperties() + elif inst_name_map[name].num_qubits > 2: + prop_name_map[name] = None + + # Setting the num_qubits in case it is None + if in_data_target["num_qubits"] is None and ( + instruction_durations is not None or inst_map is not None or coupling_map is not None + ): + for gate_name in basis_gates: + if inst_name_map[gate_name].num_qubits == 1: + # This line is a bit fragile, if a single-qubit gate not defined for a qubit + # then this logic breaks!. ( Provided that gate sneaks in this if block ) + in_data_target["num_qubits"] = len(prop_name_map.get(gate_name)) + break + + # Adding 'Delay' instruction properties + if in_data_target["num_qubits"] is not None: + if prop_name_map["delay"] is None: + prop_name_map["delay"] = { + (q,): None + for q in range(in_data_target["num_qubits"]) + if q not in faulty_qubits # filtering faulty_qubits only works if + } # BackendProperties is present. + + target = Target(**in_data_target) + + for inst_name in all_instructions: + if inst_name in qiskit_control_flow_mapping: + # Control flow operator doesn't have gate property. + target.add_instruction( + instruction=qiskit_control_flow_mapping[inst_name], + name=inst_name, + ) + else: + target.add_instruction( + instruction=inst_name_map[inst_name], + properties=prop_name_map.get(inst_name, None), + name=inst_name, + ) + return target diff --git a/releasenotes/notes/fix_no_measure_in_Target_from_configuration-5b64dda6a35a8600.yaml b/releasenotes/notes/fix_no_measure_in_Target_from_configuration-5b64dda6a35a8600.yaml new file mode 100644 index 000000000000..dfb91375b9a8 --- /dev/null +++ b/releasenotes/notes/fix_no_measure_in_Target_from_configuration-5b64dda6a35a8600.yaml @@ -0,0 +1,14 @@ +--- +upgrade: + - | + Added :class:`.QubitProperties` in :class:`.Target`. + One can access :class:`.QubitProperties` for a particular :class:`.Target` object by calling it's + method :meth:`.Target.qubit_properties`. + + If no mapping is available for a specified :code:`basis_gate`, this raises :class:`.TranspilerError`. + +fixes: + - | + Fixed an issue with :meth:`.Target.form_configuration` method in :class:`.Target` when attempting to get + the :code:`Target` object, the measure instruction was missing. + Refer to `#10168 ` for more information. diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 980924224d15..4ed0c441ddaa 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -47,7 +47,6 @@ from qiskit.transpiler import InstructionProperties from qiskit.providers.fake_provider import ( GenericBackendV2, - Fake5QV1, Fake7QPulseV1, ) from test import QiskitTestCase # pylint: disable=wrong-import-order @@ -1967,34 +1966,70 @@ def test_empty_repr(self): class TestTargetFromConfiguration(QiskitTestCase): """Test the from_configuration() constructor.""" + def setUp(self): + super().setUp() + + self.backend = Fake7QPulseV1() + self.backend_config = self.backend.configuration() + self.backend_props = self.backend.properties() + self.pulse_defaults = self.backend.defaults() + + self.backend_dt = self.backend_config.dt + self.backend_num_qubits = self.backend_config.num_qubits + self.backend_basis_gates = self.backend_config.basis_gates + self.backend_inst_sched_map = self.pulse_defaults.instruction_schedule_map + self.backend_cp_mp = CouplingMap(couplinglist=self.backend_config.coupling_map) + self.backend_timing_constraints = TimingConstraints( + **self.backend_config.timing_constraints + ) + self.backend_meas_map = self.backend_config.meas_map + self.backend_inst_durs = InstructionDurations.from_backend(self.backend) + def test_basis_gates_qubits_only(self): """Test construction with only basis gates.""" target = Target.from_configuration(["u", "cx"], 3) - self.assertEqual(target.operation_names, {"u", "cx"}) + + # delay and measure are added automatically. + self.assertEqual(target.operation_names, {"u", "cx", "measure", "delay"}) def test_basis_gates_no_qubits(self): target = Target.from_configuration(["u", "cx"]) - self.assertEqual(target.operation_names, {"u", "cx"}) + + # delay and measure are added automatically + self.assertEqual(target.operation_names, {"u", "cx", "measure", "delay"}) def test_basis_gates_coupling_map(self): """Test construction with only basis gates.""" - target = Target.from_configuration( - ["u", "cx"], 3, CouplingMap.from_ring(3, bidirectional=False) - ) - self.assertEqual(target.operation_names, {"u", "cx"}) + with self.assertWarnsRegex(RuntimeWarning, ".is not found so setting."): + target = Target.from_configuration( + ["u", "cx"], 3, CouplingMap.from_ring(3, bidirectional=False) + ) + # Measure and Delay are added automatically in operation_names + self.assertEqual(target.operation_names, {"u", "cx", "measure", "delay"}) self.assertEqual({(0,), (1,), (2,)}, target["u"].keys()) self.assertEqual({(0, 1), (1, 2), (2, 0)}, target["cx"].keys()) + def test_target_has_measure(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + num_qubits=self.backend_num_qubits, + coupling_map=self.backend_cp_mp, + inst_map=self.backend_inst_sched_map, + backend_properties=self.backend_props, + timing_constraints=self.backend_timing_constraints, + ) + self.assertTrue(target.instruction_schedule_map().has("measure", 0)) + def test_properties(self): with self.assertWarns(DeprecationWarning): fake_backend = Fake5QV1() config = fake_backend.configuration() properties = fake_backend.properties() target = Target.from_configuration( - basis_gates=config.basis_gates, - num_qubits=config.num_qubits, - coupling_map=CouplingMap(config.coupling_map), - backend_properties=properties, + basis_gates=self.backend_basis_gates, + num_qubits=self.backend_num_qubits, + coupling_map=self.backend_cp_mp, + backend_properties=self.backend_props, ) self.assertEqual(0, target["rz"][(0,)].error) self.assertEqual(0, target["rz"][(0,)].duration) @@ -2006,16 +2041,34 @@ def test_properties_with_durations(self): properties = fake_backend.properties() durations = InstructionDurations([("rz", 0, 0.5)], dt=1.0) target = Target.from_configuration( - basis_gates=config.basis_gates, - num_qubits=config.num_qubits, - coupling_map=CouplingMap(config.coupling_map), - backend_properties=properties, + basis_gates=self.backend_basis_gates, + num_qubits=self.backend_num_qubits, + coupling_map=self.backend_cp_mp, + backend_properties=self.backend_props, instruction_durations=durations, - dt=config.dt, + dt=self.backend_dt, ) self.assertEqual(0.5, target["rz"][(0,)].duration) def test_inst_map(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + num_qubits=self.backend_num_qubits, + coupling_map=self.backend_cp_mp, + backend_properties=self.backend_props, + dt=self.backend_dt, + inst_map=self.backend_inst_sched_map, + timing_constraints=self.backend_timing_constraints, + ) + self.assertIsNotNone(target["sx"][(0,)].calibration) + self.assertEqual(target.granularity, self.backend_timing_constraints.granularity) + self.assertEqual(target.min_length, self.backend_timing_constraints.min_length) + self.assertEqual(target.pulse_alignment, self.backend_timing_constraints.pulse_alignment) + self.assertEqual( + target.acquire_alignment, self.backend_timing_constraints.acquire_alignment + ) + + def test_concurrent_measurements(self): with self.assertWarns(DeprecationWarning): fake_backend = Fake7QPulseV1() config = fake_backend.configuration() @@ -2043,10 +2096,10 @@ def test_concurrent_measurements(self): fake_backend = Fake5QV1() config = fake_backend.configuration() target = Target.from_configuration( - basis_gates=config.basis_gates, - concurrent_measurements=config.meas_map, + basis_gates=self.backend_basis_gates, + concurrent_measurements=self.backend_meas_map, ) - self.assertEqual(target.concurrent_measurements, config.meas_map) + self.assertEqual(target.concurrent_measurements, self.backend_meas_map) def test_custom_basis_gates(self): basis_gates = ["my_x", "cx"] @@ -2054,26 +2107,158 @@ def test_custom_basis_gates(self): target = Target.from_configuration( basis_gates=basis_gates, num_qubits=2, custom_name_mapping=custom_name_mapping ) - self.assertEqual(target.operation_names, {"my_x", "cx"}) - - def test_missing_custom_basis_no_coupling(self): - basis_gates = ["my_X", "cx"] - with self.assertRaisesRegex(KeyError, "is not present in the standard gate names"): - Target.from_configuration(basis_gates, num_qubits=4) - - def test_missing_custom_basis_with_coupling(self): - basis_gates = ["my_X", "cx"] - cmap = CouplingMap.from_line(3) - with self.assertRaisesRegex(KeyError, "is not present in the standard gate names"): - Target.from_configuration(basis_gates, 3, cmap) + # delay and measure are added automatically + self.assertEqual(target.operation_names, {"my_x", "cx", "delay", "measure"}) def test_over_two_qubit_gate_without_coupling(self): basis_gates = ["ccx", "cx", "swap", "u"] target = Target.from_configuration(basis_gates, 15) - self.assertEqual(target.operation_names, {"ccx", "cx", "swap", "u"}) - def test_over_two_qubits_with_coupling(self): - basis_gates = ["ccx", "cx", "swap", "u"] - cmap = CouplingMap.from_line(15) - with self.assertRaisesRegex(TranspilerError, "This constructor method only supports"): - Target.from_configuration(basis_gates, 15, cmap) + # delay and measure are added automatically + self.assertEqual(target.operation_names, {"ccx", "cx", "swap", "u", "delay", "measure"}) + + def test_durations_only_with_InstructionDurations_passed(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, instruction_durations=self.backend_inst_durs + ) + self.assertEqual( + target["x"][(0,)].duration, self.backend_props.gate_length(gate="x", qubits=(0,)) + ) + + def test_warns_no_inst_dur_props_dt_with_inst_map(self): + with self.assertWarnsRegex(RuntimeWarning, ".set the `dt` argument to set."): + Target.from_configuration( + basis_gates=self.backend_basis_gates, + inst_map=self.backend_inst_sched_map, + ) + + def test_duration_with_inst_map_dt(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + inst_map=self.backend_inst_sched_map, + dt=self.backend_dt, + ) + self.assertEqual( + target["x"][(0,)].duration, self.backend_props.gate_length(gate="x", qubits=(0,)) + ) + + def test_measure_from_inst_map_with_dt(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + inst_map=self.backend_inst_sched_map, + dt=0.222e-09, + ) + self.assertTrue("measure" in target) + + def test_concurrent_measurement_set_by_inst_map(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + inst_map=self.backend_inst_sched_map, + dt=0.222e-09, + ) + self.assertEqual(target.concurrent_measurements, self.backend_meas_map) + + def test_warns_if_duration_not_in_inst_dur(self): + with self.assertWarnsRegex(RuntimeWarning, ".passed, falling back to."): + Target.from_configuration( + basis_gates=self.backend_basis_gates, + backend_properties=self.backend_props, + instruction_durations=InstructionDurations([("x", 0, 1.0)], dt=1.0), + ) + + def test_inst_dur_overrides_backend_prop_dur(self): + inst_dur = InstructionDurations.from_backend(self.backend) + inst_dur.update([("x", [0], 1.0, "s")]) + + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + backend_properties=self.backend_props, + instruction_durations=inst_dur, + ) + self.assertEqual(target["x"][(0,)].duration, 1.0) + + def test_dt_set_by_inst_dur(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + instruction_durations=self.backend_inst_durs, + ) + self.assertEqual(target.dt, self.backend_dt) + + def test_set_num_qubits_from_props(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, backend_properties=self.backend_props + ) + self.assertEqual(target.num_qubits, self.backend_num_qubits) + + def test_set_num_qubits_from_inst_dur(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + instruction_durations=self.backend_inst_durs, + ) + self.assertEqual(target.num_qubits, self.backend_num_qubits) + + def test_set_num_qubits_from_cp_mp(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + coupling_map=self.backend_cp_mp, + ) + self.assertEqual(target.num_qubits, self.backend_num_qubits) + + def test_set_num_qubits_from_inst_sched(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + inst_map=self.backend_inst_sched_map, + dt=0.22e-09, + ) + self.assertEqual(target.num_qubits, self.backend_num_qubits) + + def test_backend_props_sets_qubit_props(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + backend_properties=self.backend_props, + ) + self.assertEqual( + target.qubit_properties[0].t1, self.backend_props.qubit_property(qubit=0)["T1"][0] + ) + + def test_inst_dur_overrides_inst_sched_map(self): + inst_durs = InstructionDurations.from_backend(self.backend) + inst_durs.update([("x", [0], 1.0, "s")]) + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + instruction_durations=inst_durs, + inst_map=self.backend_inst_sched_map, + ) + self.assertEqual(target["x"][(0,)].duration, 1.0) + + def test_measure_exists_for_inst_durs(self): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + instruction_durations=self.backend_inst_durs, + ) + self.assertTrue("measure" in target) + + def test_inst_durs_dt_overrides_backend_dt_unequal_warns(self): + with self.assertWarnsRegex(RuntimeWarning, ".from argument is different from."): + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + instruction_durations=self.backend_inst_durs, + dt=1.0, + ) + self.assertEqual(target.dt, self.backend_inst_durs.dt) + + def test_duration_all_props_inst_durs_inst_sched_map(self): + inst_durs = InstructionDurations.from_backend(self.backend) + inst_durs.update([("x", [0], 1.0, "s")]) + + target = Target.from_configuration( + basis_gates=self.backend_basis_gates, + backend_properties=self.backend_props, + instruction_durations=inst_durs, + inst_map=self.backend_inst_sched_map, + ) + self.assertEqual(target["x"][(0,)].duration, 1.0) + + def test_raise_TranspilerError_on_undefined_gates(self): + with self.assertRaisesRegex(TranspilerError, ".is neither present in the standard gate."): + Target.from_configuration(basis_gates=["undefined_gate"])