diff --git a/qiskit/compiler/scheduler.py b/qiskit/compiler/scheduler.py index b714b8d46923..23c04dd92724 100644 --- a/qiskit/compiler/scheduler.py +++ b/qiskit/compiler/scheduler.py @@ -22,8 +22,9 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.pulse import InstructionScheduleMap, Schedule -from qiskit.providers.backend import Backend -from qiskit.scheduler import ScheduleConfig +from qiskit.providers.backend import Backend, BackendV1, BackendV2 +from qiskit.providers.backend_compat import convert_to_target +from qiskit.transpiler import Target from qiskit.scheduler.schedule_circuit import schedule_circuit from qiskit.tools.parallel import parallel_map @@ -42,6 +43,7 @@ def schedule( meas_map: Optional[List[List[int]]] = None, dt: Optional[float] = None, method: Optional[Union[str, List[str]]] = None, + target: Optional[Target] = None, ) -> Union[Schedule, List[Schedule]]: """ Schedule a circuit to a pulse ``Schedule``, using the backend, according to any specified @@ -58,6 +60,9 @@ def schedule( which contain time information, dt is required. If not provided, it will be obtained from the backend configuration method: Optionally specify a particular scheduling method + target: The optional :class:`~.Target` representing the target backend. If ``None``, + defaults to the ``backend``\'s ``target``, constructed from convert_to_target, + or prepared from ``meas_map`` and ``inst_map`` Returns: A pulse ``Schedule`` that implements the input circuit @@ -67,36 +72,36 @@ def schedule( """ arg_circuits_list = isinstance(circuits, list) start_time = time() - if backend and getattr(backend, "version", 0) > 1: - if inst_map is None: - inst_map = backend.instruction_schedule_map - if meas_map is None: - meas_map = backend.meas_map - if dt is None: - dt = backend.dt - else: - if inst_map is None: - if backend is None: - raise QiskitError( - "Must supply either a backend or InstructionScheduleMap for scheduling passes." - ) - defaults = backend.defaults() + if target is None: + if isinstance(backend, BackendV2): + target = backend.target + if inst_map: + target.update_from_instruction_schedule_map(inst_map=inst_map) + elif isinstance(backend, BackendV1): + defaults = backend.defaults() if hasattr(backend, "defaults") else None if defaults is None: raise QiskitError( "The backend defaults are unavailable. The backend may not support pulse." ) - inst_map = defaults.instruction_schedule_map - if meas_map is None: - if backend is None: + if backend.configuration() is not None: + target = convert_to_target( + configuration=backend.configuration(), + properties=backend.properties(), + defaults=defaults, + ) + if inst_map: + target.update_from_instruction_schedule_map(inst_map=inst_map) + else: + raise QiskitError("Must specify backend that has a configuration.") + else: + if meas_map and inst_map: + target = Target(concurrent_measurements=meas_map, dt=dt) + target.update_from_instruction_schedule_map(inst_map=inst_map) + else: raise QiskitError( - "Must supply either a backend or a meas_map for scheduling passes." + "Must specify either target, backend, " + "or both meas_map and inst_map for scheduling passes." ) - meas_map = backend.configuration().meas_map - if dt is None: - if backend is not None: - dt = backend.configuration().dt - - schedule_config = ScheduleConfig(inst_map=inst_map, meas_map=meas_map, dt=dt) circuits = circuits if isinstance(circuits, list) else [circuits] schedules = parallel_map(schedule_circuit, circuits, (schedule_config, method, backend)) end_time = time() diff --git a/qiskit/compiler/sequencer.py b/qiskit/compiler/sequencer.py index 541f78c9f762..917de3bde8d9 100644 --- a/qiskit/compiler/sequencer.py +++ b/qiskit/compiler/sequencer.py @@ -17,9 +17,10 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError -from qiskit.providers.backend import Backend +from qiskit.providers.backend import Backend, BackendV1, BackendV2 +from qiskit.providers.backend_compat import convert_to_target from qiskit.pulse import InstructionScheduleMap, Schedule -from qiskit.scheduler import ScheduleConfig +from qiskit.transpiler import Target from qiskit.scheduler.sequence import sequence as _sequence @@ -29,6 +30,7 @@ def sequence( inst_map: Optional[InstructionScheduleMap] = None, meas_map: Optional[List[List[int]]] = None, dt: Optional[float] = None, + target: Optional[Target] = None, ) -> Union[Schedule, List[Schedule]]: """ Schedule a scheduled circuit to a pulse ``Schedule``, using the backend. @@ -43,6 +45,10 @@ def sequence( dt: The output sample rate of backend control electronics. For scheduled circuits which contain time information, dt is required. If not provided, it will be obtained from the backend configuration + target: The optional :class:`~.Target` representing the target backend. If ``None``, + defaults to the ``backend``\'s ``target``, constructed from convert_to_target, + or prepared from ``meas_map`` and ``inst_map`` + Returns: A pulse ``Schedule`` that implements the input circuit @@ -50,20 +56,31 @@ def sequence( Raises: QiskitError: If ``inst_map`` and ``meas_map`` are not passed and ``backend`` is not passed """ - if inst_map is None: - if backend is None: - raise QiskitError("Must supply either a backend or inst_map for sequencing.") - inst_map = backend.defaults().instruction_schedule_map - if meas_map is None: - if backend is None: - raise QiskitError("Must supply either a backend or a meas_map for sequencing.") - meas_map = backend.configuration().meas_map - if dt is None: - if backend is None: - raise QiskitError("Must supply either a backend or a dt for sequencing.") - dt = backend.configuration().dt - - schedule_config = ScheduleConfig(inst_map=inst_map, meas_map=meas_map, dt=dt) + if target is None: + if isinstance(backend, BackendV2): + target = backend.target + if inst_map: + target.update_from_instruction_schedule_map(inst_map=inst_map) + elif isinstance(backend, BackendV1): + if backend.configuration() is not None: + target = convert_to_target( + configuration=backend.configuration(), + properties=backend.properties(), + defaults=backend.defaults() if hasattr(backend, "defaults") else None, + ) + if inst_map: + target.update_from_instruction_schedule_map(inst_map=inst_map) + else: + raise QiskitError("Must specify backend that has a configuration.") + else: + if meas_map and inst_map: + target = Target(concurrent_measurements=meas_map, dt=dt) + target.update_from_instruction_schedule_map(inst_map=inst_map) + else: + raise QiskitError( + "Must specify either target, backend, " + "or both meas_map and inst_map for scheduling passes." + ) circuits = scheduled_circuits if isinstance(scheduled_circuits, list) else [scheduled_circuits] - schedules = [_sequence(circuit, schedule_config) for circuit in circuits] + schedules = [_sequence(circuit, target=target) for circuit in circuits] return schedules[0] if len(schedules) == 1 else schedules diff --git a/qiskit/providers/fake_provider/fake_openpulse_2q.py b/qiskit/providers/fake_provider/fake_openpulse_2q.py index 6b9ea13bdbc7..8ce8b1315d28 100644 --- a/qiskit/providers/fake_provider/fake_openpulse_2q.py +++ b/qiskit/providers/fake_provider/fake_openpulse_2q.py @@ -301,6 +301,30 @@ def __init__(self): Nduv(date=mock_time, name="gate_length", unit="ns", value=0.0), ], ), + Gate( + gate="u1", + qubits=[1], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=0.0), + ], + ), + Gate( + gate="u2", + qubits=[0], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=2 * dt), + ], + ), + Gate( + gate="u2", + qubits=[1], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=2 * dt), + ], + ), Gate( gate="u3", qubits=[0], @@ -314,7 +338,7 @@ def __init__(self): qubits=[1], parameters=[ Nduv(date=mock_time, name="gate_error", unit="", value=0.06), - Nduv(date=mock_time, name="gate_length", unit="ns", value=4 * dt), + Nduv(date=mock_time, name="gate_length", unit="ns", value=2 * dt), ], ), Gate( diff --git a/qiskit/providers/fake_provider/fake_openpulse_3q.py b/qiskit/providers/fake_provider/fake_openpulse_3q.py index 8d2529a68a2a..56ea40d316b2 100644 --- a/qiskit/providers/fake_provider/fake_openpulse_3q.py +++ b/qiskit/providers/fake_provider/fake_openpulse_3q.py @@ -13,6 +13,7 @@ """ Fake backend supporting OpenPulse. """ +import datetime from qiskit.providers.models import ( GateConfig, @@ -21,6 +22,7 @@ Command, UchannelLO, ) +from qiskit.providers.models.backendproperties import Nduv, Gate, BackendProperties from qiskit.qobj import PulseQobjInstruction from .fake_backend import FakeBackend @@ -326,7 +328,105 @@ def __init__(self): ], } ) + mock_time = datetime.datetime.now() + dt = 1.3333 + self._properties = BackendProperties( + backend_name="fake_openpulse_3q", + backend_version="0.0.0", + last_update_date=mock_time, + qubits=[ + [ + Nduv(date=mock_time, name="T1", unit="µs", value=71.9500421005539), + Nduv(date=mock_time, name="T2", unit="µs", value=69.4240447362455), + Nduv(date=mock_time, name="frequency", unit="MHz", value=4919.96800692), + Nduv(date=mock_time, name="readout_error", unit="", value=0.02), + ], + [ + Nduv(date=mock_time, name="T1", unit="µs", value=81.9500421005539), + Nduv(date=mock_time, name="T2", unit="µs", value=75.5598482446578), + Nduv(date=mock_time, name="frequency", unit="GHz", value=5.01996800692), + Nduv(date=mock_time, name="readout_error", unit="", value=0.02), + ], + [ + Nduv(date=mock_time, name="T1", unit="µs", value=81.9500421005539), + Nduv(date=mock_time, name="T2", unit="µs", value=75.5598482446578), + Nduv(date=mock_time, name="frequency", unit="GHz", value=5.01996800692), + Nduv(date=mock_time, name="readout_error", unit="", value=0.02), + ], + ], + gates=[ + Gate( + gate="u1", + qubits=[0], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=0.0), + ], + ), + Gate( + gate="u1", + qubits=[1], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=0.0), + ], + ), + Gate( + gate="u1", + qubits=[2], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=0.0), + ], + ), + Gate( + gate="u3", + qubits=[0], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=2 * dt), + ], + ), + Gate( + gate="u3", + qubits=[1], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=2 * dt), + ], + ), + Gate( + gate="u3", + qubits=[2], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=0.06), + Nduv(date=mock_time, name="gate_length", unit="ns", value=2 * dt), + ], + ), + Gate( + gate="cx", + qubits=[0, 1], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=1.0), + Nduv(date=mock_time, name="gate_length", unit="ns", value=22 * dt), + ], + ), + Gate( + gate="cx", + qubits=[1, 2], + parameters=[ + Nduv(date=mock_time, name="gate_error", unit="", value=1.0), + Nduv(date=mock_time, name="gate_length", unit="ns", value=22 * dt), + ], + ), + ], + general=[], + ) super().__init__(configuration) def defaults(self): # pylint: disable=missing-function-docstring return self._defaults + + def properties(self): + """Return the measured characteristics of the backend.""" + return self._properties diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 913384a56f3c..3c3ce02e121b 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -103,7 +103,7 @@ d2 = pulse.DriveChannel(2) with pulse.build(backend) as bell_prep: - pulse.u2(0, math.pi, 0) + pulse.u3(1.57, 0, math.pi, 0) pulse.cx(0, 1) with pulse.build(backend) as decoupled_bell_prep_and_measure: diff --git a/qiskit/pulse/macros.py b/qiskit/pulse/macros.py index f45d94bbeb98..a1ac2d11cbe9 100644 --- a/qiskit/pulse/macros.py +++ b/qiskit/pulse/macros.py @@ -28,6 +28,7 @@ def measure( qubits: List[int], backend=None, + target: Optional[Target] = None, inst_map: Optional[InstructionScheduleMap] = None, meas_map: Optional[Union[List[List[int]], Dict[int, List[int]]]] = None, qubit_mem_slots: Optional[Dict[int, int]] = None, @@ -51,6 +52,7 @@ def measure( qubits: List of qubits to be measured. backend (Union[Backend, BaseBackend]): A backend instance, which contains hardware-specific data required for scheduling. + target: The :class:`~.Target` representing the target backend. inst_map: Mapping of circuit operations to pulse schedules. If None, defaults to the ``instruction_schedule_map`` of ``backend``. meas_map: List of sets of qubits that must be measured together. If None, defaults to @@ -63,12 +65,13 @@ def measure( """ # backend is V2. - if isinstance(backend, BackendV2): + if isinstance(backend, BackendV2) or target: + meas_map = meas_map or getattr(backend, "meas_map", None) return _measure_v2( qubits=qubits, - target=backend.target, - meas_map=meas_map or backend.meas_map, + target=target or backend.target, + meas_map=meas_map or target.concurrent_measurements, qubit_mem_slots=qubit_mem_slots or dict(zip(qubits, range(len(qubits)))), measure_name=measure_name, ) diff --git a/qiskit/scheduler/config.py b/qiskit/scheduler/config.py index b8e2d5ac2425..c39c28217ee2 100644 --- a/qiskit/scheduler/config.py +++ b/qiskit/scheduler/config.py @@ -15,7 +15,6 @@ from typing import List from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap -from qiskit.pulse.utils import format_meas_map class ScheduleConfig: @@ -31,5 +30,5 @@ def __init__(self, inst_map: InstructionScheduleMap, meas_map: List[List[int]], dt: Sample duration. """ self.inst_map = inst_map - self.meas_map = format_meas_map(meas_map) + self.meas_map = meas_map self.dt = dt diff --git a/qiskit/scheduler/lowering.py b/qiskit/scheduler/lowering.py index fa622b205d3b..2c138c72042c 100644 --- a/qiskit/scheduler/lowering.py +++ b/qiskit/scheduler/lowering.py @@ -27,6 +27,7 @@ from qiskit.pulse.channels import AcquireChannel, MemorySlot, DriveChannel from qiskit.pulse.exceptions import PulseError from qiskit.pulse.macros import measure +from qiskit.transpiler import Target from qiskit.scheduler.config import ScheduleConfig from qiskit.providers import BackendV1, BackendV2 @@ -64,13 +65,16 @@ def lower_gates( """ from qiskit.pulse.transforms.base_transforms import target_qobj_transform + if schedule_config is None and target is None: + raise QiskitError("Only one of schedule_config or target must be specified.") + meas_map = target.concurrent_measurements + dt = target.dt circ_pulse_defs = [] - inst_map = schedule_config.inst_map qubit_mem_slots = {} # Map measured qubit index to classical bit index # convert the unit of durations from SI to dt before lowering - circuit = convert_durations_to_dt(circuit, dt_in_sec=schedule_config.dt, inplace=False) + circuit = convert_durations_to_dt(circuit, dt_in_sec=dt, inplace=False) def get_measure_schedule(qubit_mem_slots: Dict[int, int]) -> CircuitPulseDef: """Create a schedule to measure the qubits queued for measuring.""" @@ -169,8 +173,8 @@ def get_measure_schedule(qubit_mem_slots: Dict[int, int]) -> CircuitPulseDef: pass # Calibration not defined for this operation try: - schedule = inst_map.get( - instruction.operation, inst_qubits, *instruction.operation.params + schedule = target.get_calibration( + instruction.operation.name, tuple(inst_qubits), *instruction.operation.params ) schedule = target_qobj_transform(schedule) circ_pulse_defs.append(CircuitPulseDef(schedule=schedule, qubits=inst_qubits)) diff --git a/qiskit/scheduler/methods/basic.py b/qiskit/scheduler/methods/basic.py index 60b2f20056b3..87b59e1e225e 100644 --- a/qiskit/scheduler/methods/basic.py +++ b/qiskit/scheduler/methods/basic.py @@ -20,6 +20,7 @@ from qiskit.circuit.barrier import Barrier from qiskit.pulse.schedule import Schedule +from qiskit.transpiler import Target from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.lowering import lower_gates from qiskit.providers import BackendV1, BackendV2 @@ -35,9 +36,9 @@ def as_soon_as_possible( (asap) scheduling policy. Circuit instructions are first each mapped to equivalent pulse - Schedules according to the command definition given by the schedule_config. Then, this circuit - instruction-equivalent Schedule is appended at the earliest time at which all qubits involved - in the instruction are available. + Schedules according to the command definition given by the schedule_config or target. + Then, this circuit instruction-equivalent Schedule is appended at the earliest time + at which all qubits involved in the instruction are available. Args: circuit: The quantum circuit to translate. @@ -88,9 +89,10 @@ def as_late_as_possible( (alap) scheduling policy. Circuit instructions are first each mapped to equivalent pulse - Schedules according to the command definition given by the schedule_config. Then, this circuit - instruction-equivalent Schedule is appended at the latest time that it can be without allowing - unnecessary time between instructions or allowing instructions with common qubits to overlap. + Schedules according to the command definition given by the schedule_config or target. + Then, this circuit instruction-equivalent Schedule is appended at the latest time that + it can be without allowing unnecessary time between instructions or allowing instructions + with common qubits to overlap. This method should improves the outcome fidelity over ASAP scheduling, because we may maximize the time that the qubit remains in the ground state. diff --git a/qiskit/scheduler/schedule_circuit.py b/qiskit/scheduler/schedule_circuit.py index 52fd8bf72b76..c61c32c109b7 100644 --- a/qiskit/scheduler/schedule_circuit.py +++ b/qiskit/scheduler/schedule_circuit.py @@ -17,6 +17,7 @@ from qiskit.exceptions import QiskitError from qiskit.pulse.schedule import Schedule +from qiskit.transpiler import Target from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.methods import as_soon_as_possible, as_late_as_possible from qiskit.providers import BackendV1, BackendV2 @@ -43,6 +44,7 @@ def schedule_circuit( Args: circuit: The quantum circuit to translate. schedule_config: Backend specific parameters used for building the Schedule. + target: Target built from some Backend parameters. method: The scheduling pass method to use. backend: A backend used to build the Schedule, the backend could be BackendV1 or BackendV2. diff --git a/qiskit/scheduler/sequence.py b/qiskit/scheduler/sequence.py index d73a5cdf93df..962b05c03c8f 100644 --- a/qiskit/scheduler/sequence.py +++ b/qiskit/scheduler/sequence.py @@ -22,6 +22,7 @@ from qiskit.exceptions import QiskitError from qiskit.pulse.schedule import Schedule from qiskit.pulse.transforms import pad +from qiskit.transpiler import Target from qiskit.scheduler.config import ScheduleConfig from qiskit.scheduler.lowering import lower_gates from qiskit.providers import BackendV1, BackendV2 @@ -36,7 +37,7 @@ def sequence( Return the pulse Schedule which implements the input scheduled circuit. Assume all measurements are done at once at the last of the circuit. - Schedules according to the command definition given by the schedule_config. + Schedules according to the command definition given by the schedule_config or target. Args: scheduled_circuit: The scheduled quantum circuit to translate. diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index bc42a9c11e99..c63ce5fb3ae7 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -460,7 +460,9 @@ def update_instruction_properties(self, instruction, qargs, properties): self._instruction_durations = None self._instruction_schedule_map = None - def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, error_dict=None): + def update_from_instruction_schedule_map( + self, inst_map, inst_name_map=None, error_dict=None, ignore_instruction=None + ): """Update the target from an instruction schedule map. If the input instruction schedule map contains new instructions not in @@ -485,15 +487,23 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err a when updating the ``Target`` the error value will be pulled from this dictionary. If one is not found in ``error_dict`` then ``None`` will be used. + ignore_instruction (list): An optionional list filterring out gates + that are ignored when updating the ``Target`` from the instruction + schedule map. """ + # ['u1', 'u2', 'u3'] is set as the default because the + # current IBM providers don't support them as basis gates. + if ignore_instruction is None: + ignore_instruction = ["u1", "u2", "u3"] get_calibration = getattr(inst_map, "_get_calibration_entry") # Expand name mapping with custom gate name provided by user. qiskit_inst_name_map = get_standard_gate_name_mapping() if inst_name_map is not None: qiskit_inst_name_map.update(inst_name_map) - for inst_name in inst_map.instructions: + if inst_name in ignore_instruction: + continue # Prepare dictionary of instruction properties out_props = {} for qargs in inst_map.qubits_with_instruction(inst_name): @@ -508,8 +518,6 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err entry = get_calibration(inst_name, qargs) if entry.user_provided and getattr(props, "_calibration", None) != entry: - # It only copies user-provided calibration from the inst map. - # Backend defined entry must already exist in Target. if self.dt is not None: try: duration = entry.get_schedule().duration * self.dt @@ -524,9 +532,11 @@ def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, err ) else: if props is None: - # Edge case. Calibration is backend defined, but this is not - # registered in the backend target. Ignore this entry. - continue + duration = None + props = InstructionProperties( + duration=duration, + calibration=entry, + ) try: # Update gate error if provided. props.error = error_dict[inst_name][qargs] diff --git a/test/python/compiler/test_scheduler.py b/test/python/compiler/test_scheduler.py index bb3379d490bc..aec167ccf352 100644 --- a/test/python/compiler/test_scheduler.py +++ b/test/python/compiler/test_scheduler.py @@ -45,7 +45,7 @@ def test_instruction_map_and_backend_not_supplied(self): """Test instruction map and backend not supplied.""" with self.assertRaisesRegex( QiskitError, - r"Must supply either a backend or InstructionScheduleMap for scheduling passes.", + r"Must specify either target, backend, or both meas_map and inst_map for scheduling passes.", ): schedule(self.circ) @@ -61,7 +61,7 @@ def test_measurement_map_and_backend_not_supplied(self): """Test measurement map and backend not supplied.""" with self.assertRaisesRegex( QiskitError, - r"Must supply either a backend or a meas_map for scheduling passes.", + r"Must specify either target, backend, or both meas_map and inst_map for scheduling passes.", ): schedule(self.circ, inst_map=InstructionScheduleMap()) diff --git a/test/python/pulse/test_builder.py b/test/python/pulse/test_builder.py index d02bafa889d7..20232a6b0504 100644 --- a/test/python/pulse/test_builder.py +++ b/test/python/pulse/test_builder.py @@ -901,7 +901,6 @@ def test_complex_build(self): pulse.u2(0, pi / 2, 1) pulse.u2(0, pi / 2, 0) pulse.measure(0) - # prepare and schedule circuits that will be used. single_u2_qc = circuit.QuantumCircuit(2) single_u2_qc.append(circuit.library.U2Gate(0, pi / 2), [1]) diff --git a/test/python/pulse/test_builder_v2.py b/test/python/pulse/test_builder_v2.py index 1f194a9d3c29..9ab2ca025e23 100644 --- a/test/python/pulse/test_builder_v2.py +++ b/test/python/pulse/test_builder_v2.py @@ -12,15 +12,16 @@ """Test pulse builder with backendV2 context utilities.""" + import numpy as np -from qiskit import circuit, pulse +from qiskit import circuit, compiler, pulse from qiskit.pulse import builder, macros +from qiskit.pulse import library, instructions from qiskit.pulse.instructions import directives from qiskit.pulse.transforms import target_qobj_transform from qiskit.providers.fake_provider import FakeMumbaiV2 -from qiskit.pulse import instructions from qiskit.test import QiskitTestCase @@ -351,3 +352,254 @@ def test_delay_qubits(self): reference += instructions.Delay(10, u8) self.assertScheduleEqual(schedule, reference) + + +class TestGatesV2(TestBuilderV2): + """Test builder gates.""" + + def test_cx(self): + """Test cx gate.""" + with pulse.build(self.backend) as schedule: + pulse.cx(0, 1) + + reference_qc = circuit.QuantumCircuit(2) + reference_qc.cx(0, 1) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + def test_x(self): + """Test x gate.""" + with pulse.build(self.backend) as schedule: + pulse.x(0) + + reference_qc = circuit.QuantumCircuit(1) + reference_qc.x(0) + reference_qc = compiler.transpile(reference_qc, self.backend) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + def test_u1(self): + """Test u1 gate.""" + with pulse.build(self.backend) as schedule: + with pulse.transpiler_settings(layout_method="trivial"): + pulse.u1(np.pi / 2, 0) + + reference_qc = circuit.QuantumCircuit(1) + reference_qc.append(circuit.library.U1Gate(np.pi / 2), [0]) + reference_qc = compiler.transpile(reference_qc, self.backend) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + def test_u2(self): + """Test u2 gate.""" + with pulse.build(self.backend) as schedule: + pulse.u2(np.pi / 2, 0, 0) + + reference_qc = circuit.QuantumCircuit(1) + reference_qc.append(circuit.library.U2Gate(np.pi / 2, 0), [0]) + reference_qc = compiler.transpile(reference_qc, self.backend) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + def test_u3(self): + """Test u3 gate.""" + with pulse.build(self.backend) as schedule: + pulse.u3(np.pi / 8, np.pi / 16, np.pi / 4, 0) + + reference_qc = circuit.QuantumCircuit(1) + reference_qc.append(circuit.library.U3Gate(np.pi / 8, np.pi / 16, np.pi / 4), [0]) + reference_qc = compiler.transpile(reference_qc, self.backend) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + def test_lazy_evaluation_with_transpiler(self): + """Test that the two cx gates are optimizied away by the transpiler.""" + with pulse.build(self.backend) as schedule: + pulse.cx(0, 1) + pulse.cx(0, 1) + + reference_qc = circuit.QuantumCircuit(2) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + def test_measure(self): + """Test pulse measurement macro against circuit measurement and + ensure agreement.""" + with pulse.build(self.backend) as schedule: + with pulse.align_sequential(): + pulse.x(0) + pulse.measure(0) + + reference_qc = circuit.QuantumCircuit(1, 1) + reference_qc.x(0) + reference_qc.measure(0, 0) + reference_qc = compiler.transpile(reference_qc, self.backend) + reference = compiler.schedule(reference_qc, self.backend) + + self.assertScheduleEqual(schedule, reference) + + +class TestBuilderCompositionV2(TestBuilderV2): + """Test more sophisticated composite builder examples.""" + + def test_complex_build(self): + """Test a general program build with nested contexts, + circuits and macros.""" + d0 = pulse.DriveChannel(0) + d1 = pulse.DriveChannel(1) + d2 = pulse.DriveChannel(2) + delay_dur = 300 + short_dur = 200 + long_dur = 810 + + with pulse.build(self.backend) as schedule: + with pulse.align_sequential(): + pulse.delay(delay_dur, d0) + pulse.u2(0, np.pi / 2, 1) + with pulse.align_right(): + pulse.play(library.Constant(short_dur, 0.1), d1) + pulse.play(library.Constant(long_dur, 0.1), d2) + pulse.u2(0, np.pi / 2, 1) + with pulse.align_left(): + pulse.u2(0, np.pi / 2, 0) + pulse.u2(0, np.pi / 2, 1) + pulse.u2(0, np.pi / 2, 0) + pulse.measure(0) + # prepare and schedule circuits that will be used. + single_u2_qc = circuit.QuantumCircuit(2) + single_u2_qc.append(circuit.library.U2Gate(0, np.pi / 2), [1]) + single_u2_qc = compiler.transpile(single_u2_qc, self.backend) + single_u2_sched = compiler.schedule(single_u2_qc, self.backend) + + # sequential context + sequential_reference = pulse.Schedule() + sequential_reference += instructions.Delay(delay_dur, d0) + sequential_reference.insert(delay_dur, single_u2_sched, inplace=True) + + # align right + align_right_reference = pulse.Schedule() + align_right_reference += pulse.Play(library.Constant(long_dur, 0.1), d2) + align_right_reference.insert( + long_dur - single_u2_sched.duration, single_u2_sched, inplace=True + ) + align_right_reference.insert( + long_dur - single_u2_sched.duration - short_dur, + pulse.Play(library.Constant(short_dur, 0.1), d1), + inplace=True, + ) + + # align left + triple_u2_qc = circuit.QuantumCircuit(2) + triple_u2_qc.append(circuit.library.U2Gate(0, np.pi / 2), [0]) + triple_u2_qc.append(circuit.library.U2Gate(0, np.pi / 2), [1]) + triple_u2_qc.append(circuit.library.U2Gate(0, np.pi / 2), [0]) + triple_u2_qc = compiler.transpile(triple_u2_qc, self.backend) + align_left_reference = compiler.schedule(triple_u2_qc, self.backend, method="alap") + + # measurement + measure_reference = macros.measure( + qubits=[0], backend=self.backend, meas_map=self.backend.meas_map + ) + reference = pulse.Schedule() + reference += sequential_reference + # Insert so that the long pulse on d2 occurs as early as possible + # without an overval on d1. + insert_time = reference.ch_stop_time(d1) - align_right_reference.ch_start_time(d1) + reference.insert(insert_time, align_right_reference, inplace=True) + reference.insert(reference.ch_stop_time(d0, d1), align_left_reference, inplace=True) + reference += measure_reference + + self.assertScheduleEqual(schedule, reference) + + +class TestSubroutineCallV2(TestBuilderV2): + """Test for calling subroutine.""" + + def test_call_circuit(self): + """Test calling circuit instruction.""" + inst_map = self.backend.target.instruction_schedule_map() + reference = inst_map.get("x", (0,)) + + ref_sched = pulse.Schedule() + ref_sched += pulse.instructions.Call(reference) + + u1_qc = circuit.QuantumCircuit(2) + u1_qc.append(circuit.library.XGate(), [0]) + + transpiler_settings = {"optimization_level": 0} + + with pulse.build(self.backend, default_transpiler_settings=transpiler_settings) as schedule: + with pulse.align_right(): + builder.call(u1_qc) + + self.assertScheduleEqual(schedule, ref_sched) + + def test_call_circuit_with_cregs(self): + """Test calling of circuit wiht classical registers.""" + + qc = circuit.QuantumCircuit(2, 2) + qc.h(0) + qc.cx(0, 1) + qc.measure([0, 1], [0, 1]) + + with pulse.build(self.backend) as schedule: + pulse.call(qc) + + reference_qc = compiler.transpile(qc, self.backend) + reference = compiler.schedule(reference_qc, self.backend) + + ref_sched = pulse.Schedule() + ref_sched += pulse.instructions.Call(reference) + + self.assertScheduleEqual(schedule, ref_sched) + + def test_call_gate_and_circuit(self): + """Test calling circuit with gates.""" + h_control = circuit.QuantumCircuit(2) + h_control.h(0) + + with pulse.build(self.backend) as schedule: + with pulse.align_sequential(): + # this is circuit, a subroutine stored as Call instruction + pulse.call(h_control) + # this is instruction, not subroutine + pulse.cx(0, 1) + # this is macro, not subroutine + pulse.measure([0, 1]) + + # subroutine + h_reference = compiler.schedule(compiler.transpile(h_control, self.backend), self.backend) + + # gate + cx_circ = circuit.QuantumCircuit(2) + cx_circ.cx(0, 1) + cx_reference = compiler.schedule(compiler.transpile(cx_circ, self.backend), self.backend) + + # measurement + measure_reference = macros.measure(qubits=[0, 1], backend=self.backend) + + reference = pulse.Schedule() + reference += pulse.instructions.Call(h_reference) + reference += cx_reference + reference += measure_reference << reference.duration + + self.assertScheduleEqual(schedule, reference) + + def test_subroutine_not_transpiled(self): + """Test called circuit is frozen as a subroutine.""" + subprogram = circuit.QuantumCircuit(1) + subprogram.x(0) + + transpiler_settings = {"optimization_level": 2} + + with pulse.build(self.backend, default_transpiler_settings=transpiler_settings) as schedule: + pulse.call(subprogram) + pulse.call(subprogram) + + self.assertNotEqual(len(target_qobj_transform(schedule).instructions), 0) diff --git a/test/python/scheduler/test_basic_scheduler.py b/test/python/scheduler/test_basic_scheduler.py index 83782243d15f..f3c4b31ca81f 100644 --- a/test/python/scheduler/test_basic_scheduler.py +++ b/test/python/scheduler/test_basic_scheduler.py @@ -239,32 +239,32 @@ def test_measure_combined(self): def test_3q_schedule(self): """Test a schedule that was recommended by David McKay :D""" - # ┌─────────────────┐ - # q0_0: ─────────■─────────┤ U3(3.14,1.57,0) ├──────────────────────── - # ┌─┴─┐ └┬───────────────┬┘ - # q0_1: ───────┤ X ├────────┤ U2(3.14,1.57) ├───■───────────────────── - # ┌──────┴───┴──────┐ └───────────────┘ ┌─┴─┐┌─────────────────┐ - # q0_2: ┤ U2(0.778,0.122) ├───────────────────┤ X ├┤ U2(0.778,0.122) ├ - # └─────────────────┘ └───┘└─────────────────┘ + # ┌─────────────────┐ + # q0_0: ─────────■───────────┤ U3(3.14,1.57,0) ├──────────────────────────── + # ┌─┴─┐ └┬────────────────┘┐ + # q0_1: ───────┤ X ├──────────┤ U3(0,3.14,1.57) ├───■─────────────────────── + # ┌──────┴───┴────────┐ └─────────────────┘ ┌─┴─┐┌───────────────────┐ + # q0_2: ┤ U3(0,0.778,0.122) ├─────────────────────┤ X ├┤ U3(0,0.778,0.122) ├ + # └───────────────────┘ └───┘└───────────────────┘ backend = FakeOpenPulse3Q() inst_map = backend.defaults().instruction_schedule_map q = QuantumRegister(3) c = ClassicalRegister(3) qc = QuantumCircuit(q, c) qc.cx(q[0], q[1]) - qc.append(U2Gate(0.778, 0.122), [q[2]]) + qc.append(U3Gate(1.57, 0.778, 0.122), [q[2]]) qc.append(U3Gate(3.14, 1.57, 0), [q[0]]) - qc.append(U2Gate(3.14, 1.57), [q[1]]) + qc.append(U3Gate(1.57, 3.14, 1.57), [q[1]]) qc.cx(q[1], q[2]) - qc.append(U2Gate(0.778, 0.122), [q[2]]) + qc.append(U3Gate(1.57, 0.778, 0.122), [q[2]]) sched = schedule(qc, backend) expected = Schedule( inst_map.get("cx", [0, 1]), - (22, inst_map.get("u2", [1], 3.14, 1.57)), - (22, inst_map.get("u2", [2], 0.778, 0.122)), - (24, inst_map.get("cx", [1, 2])), - (44, inst_map.get("u3", [0], 3.14, 1.57, 0)), - (46, inst_map.get("u2", [2], 0.778, 0.122)), + (22, inst_map.get("u3", [2], 1.57, 0.778, 0.122)), + (22, inst_map.get("u3", [1], 1.57, 3.14, 1.57)), + (26, inst_map.get("cx", [1, 2])), + (48, inst_map.get("u3", [0], 3.14, 1.57, 0)), + (48, inst_map.get("u3", [2], 1.57, 0.778, 0.122)), ) for actual, expected in zip(sched.instructions, expected.instructions): self.assertEqual(actual[0], expected[0])