From 04e2c792f0ce9cc4d2d9fdacaf31c1593eef7e05 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 9 Feb 2025 12:41:50 +0200 Subject: [PATCH 01/18] Handle ScheduleBlock and pulse gates loading --- qiskit/qpy/binary_io/circuits.py | 32 +- qiskit/qpy/binary_io/schedules.py | 202 ++------ qiskit/qpy/interface.py | 20 +- qiskit/qpy/type_keys.py | 2 +- test/python/qpy/test_block_load_from_qpy.py | 521 -------------------- 5 files changed, 73 insertions(+), 704 deletions(-) delete mode 100644 test/python/qpy/test_block_load_from_qpy.py diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 174acceb59e4..8fda99e56bb3 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -639,8 +639,7 @@ def _read_custom_operations(file_obj, version, vectors): def _read_calibrations(file_obj, version, vectors, metadata_deserializer): - calibrations = {} - + # TODO: document the purpose of this function header = formats.CALIBRATION._make( struct.unpack(formats.CALIBRATION_PACK, file_obj.read(formats.CALIBRATION_SIZE)) ) @@ -648,22 +647,21 @@ def _read_calibrations(file_obj, version, vectors, metadata_deserializer): defheader = formats.CALIBRATION_DEF._make( struct.unpack(formats.CALIBRATION_DEF_PACK, file_obj.read(formats.CALIBRATION_DEF_SIZE)) ) - name = file_obj.read(defheader.name_size).decode(common.ENCODE) - qubits = tuple( - struct.unpack("!q", file_obj.read(struct.calcsize("!q")))[0] - for _ in range(defheader.num_qubits) - ) - params = tuple( - value.read_value(file_obj, version, vectors) for _ in range(defheader.num_params) + name = file_obj.read(defheader.name_size).decode(common.ENCODE) # TODO: this is where the name of the gate comes from. Emit a warning here + warnings.warn( + category=exceptions.QPYLoadingDeprecatedFeatureWarning, + message="Support for loading dulse gates has been removed in Qiskit 2.0. " + f"If `{name}` is in the circuit, it will be left as a custom instruction without definition." + ) - schedule = schedules.read_schedule_block(file_obj, version, metadata_deserializer) - if name not in calibrations: - calibrations[name] = {(qubits, params): schedule} - else: - calibrations[name][(qubits, params)] = schedule + for _ in range(defheader.num_qubits): # qubits info + struct.unpack("!q", file_obj.read(struct.calcsize("!q")))[0] + + for _ in range(defheader.num_params): # read params info + value.read_value(file_obj, version, vectors) - return calibrations + schedules.read_schedule_block(file_obj, version, metadata_deserializer) def _dumps_register(register, index_map): @@ -1451,9 +1449,9 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa standalone_var_indices, ) - # Read calibrations + # Read calibrations, but don't use them since pulse gates are not supported as of Qiskit 2.0 if version >= 5: - circ._calibrations_prop = _read_calibrations( + _read_calibrations( file_obj, version, vectors, metadata_deserializer ) diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index 4afddb29992a..cad8eb8ddb75 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -32,15 +32,13 @@ def _read_channel(file_obj, version): - type_key = common.read_type_key(file_obj) - index = value.read_value(file_obj, version, {}) - - channel_cls = type_keys.ScheduleChannel.retrieve(type_key) - - return channel_cls(index) + # TODO: document purpose + common.read_type_key(file_obj) # read type_key + value.read_value(file_obj, version, {}) # read index def _read_waveform(file_obj, version): + # TODO: document purpose header = formats.WAVEFORM._make( struct.unpack( formats.WAVEFORM_PACK, @@ -48,15 +46,8 @@ def _read_waveform(file_obj, version): ) ) samples_raw = file_obj.read(header.data_size) - samples = common.data_from_binary(samples_raw, np.load) - name = value.read_value(file_obj, version, {}) - - return library.Waveform( - samples=samples, - name=name, - epsilon=header.epsilon, - limit_amplitude=header.amp_limited, - ) + common.data_from_binary(samples_raw, np.load) # read samples + value.read_value(file_obj, version, {}) # read name def _loads_obj(type_key, binary_data, version, vectors): @@ -78,25 +69,26 @@ def _loads_obj(type_key, binary_data, version, vectors): def _read_kernel(file_obj, version): + # TODO: document params = common.read_mapping( file_obj=file_obj, deserializer=_loads_obj, version=version, vectors={}, ) - name = value.read_value(file_obj, version, {}) - return Kernel(name=name, **params) + value.read_value(file_obj, version, {}) # read name def _read_discriminator(file_obj, version): - params = common.read_mapping( + # TODO: docucment + # read params + common.read_mapping( file_obj=file_obj, deserializer=_loads_obj, version=version, vectors={}, ) - name = value.read_value(file_obj, version, {}) - return Discriminator(name=name, **params) + value.read_value(file_obj, version, {}) # read name def _loads_symbolic_expr(expr_bytes, use_symengine=False): @@ -114,6 +106,7 @@ def _loads_symbolic_expr(expr_bytes, use_symengine=False): def _read_symbolic_pulse(file_obj, version): + # TODO: document purpose make = formats.SYMBOLIC_PULSE._make pack = formats.SYMBOLIC_PULSE_PACK size = formats.SYMBOLIC_PULSE_SIZE @@ -125,10 +118,11 @@ def _read_symbolic_pulse(file_obj, version): ) ) pulse_type = file_obj.read(header.type_size).decode(common.ENCODE) - envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size)) - constraints = _loads_symbolic_expr(file_obj.read(header.constraints_size)) - valid_amp_conditions = _loads_symbolic_expr(file_obj.read(header.valid_amp_conditions_size)) - parameters = common.read_mapping( + _loads_symbolic_expr(file_obj.read(header.envelope_size)) # read envelope + _loads_symbolic_expr(file_obj.read(header.constraints_size)) # read constraints + _loads_symbolic_expr(file_obj.read(header.valid_amp_conditions_size)) # read valid amp conditions + # read parameters + common.read_mapping( file_obj, deserializer=value.loads_value, version=version, @@ -146,50 +140,19 @@ def _read_symbolic_pulse(file_obj, version): class_name = "SymbolicPulse" # Default class name, if not in the library if pulse_type in legacy_library_pulses: - parameters["angle"] = np.angle(parameters["amp"]) - parameters["amp"] = np.abs(parameters["amp"]) - _amp, _angle = sym.symbols("amp, angle") - envelope = envelope.subs(_amp, _amp * sym.exp(sym.I * _angle)) - - warnings.warn( - f"Library pulses with complex amp are no longer supported. " - f"{pulse_type} with complex amp was converted to (amp,angle) representation.", - UserWarning, - ) class_name = "ScalableSymbolicPulse" - duration = value.read_value(file_obj, version, {}) - name = value.read_value(file_obj, version, {}) - - if class_name == "SymbolicPulse": - return library.SymbolicPulse( - pulse_type=pulse_type, - duration=duration, - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) - elif class_name == "ScalableSymbolicPulse": - return library.ScalableSymbolicPulse( - pulse_type=pulse_type, - duration=duration, - amp=parameters["amp"], - angle=parameters["angle"], - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) + value.read_value(file_obj, version, {}) # read duration + value.read_value(file_obj, version, {}) # read name + + if class_name == "SymbolicPulse" or class_name == "ScalableSymbolicPulse": + return None else: raise NotImplementedError(f"Unknown class '{class_name}'") def _read_symbolic_pulse_v6(file_obj, version, use_symengine): + # TODO: document purpose make = formats.SYMBOLIC_PULSE_V2._make pack = formats.SYMBOLIC_PULSE_PACK_V2 size = formats.SYMBOLIC_PULSE_SIZE_V2 @@ -201,83 +164,43 @@ def _read_symbolic_pulse_v6(file_obj, version, use_symengine): ) ) class_name = file_obj.read(header.class_name_size).decode(common.ENCODE) - pulse_type = file_obj.read(header.type_size).decode(common.ENCODE) - envelope = _loads_symbolic_expr(file_obj.read(header.envelope_size), use_symengine) - constraints = _loads_symbolic_expr(file_obj.read(header.constraints_size), use_symengine) - valid_amp_conditions = _loads_symbolic_expr( + file_obj.read(header.type_size).decode(common.ENCODE) # read pulse type + _loads_symbolic_expr(file_obj.read(header.envelope_size), use_symengine) # read envelope + _loads_symbolic_expr(file_obj.read(header.constraints_size), use_symengine) # read constraints + _loads_symbolic_expr( file_obj.read(header.valid_amp_conditions_size), use_symengine - ) - parameters = common.read_mapping( + ) # read valid_amp_conditions + # read parameters + common.read_mapping( file_obj, deserializer=value.loads_value, version=version, vectors={}, ) - duration = value.read_value(file_obj, version, {}) - name = value.read_value(file_obj, version, {}) - - if class_name == "SymbolicPulse": - return library.SymbolicPulse( - pulse_type=pulse_type, - duration=duration, - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) - elif class_name == "ScalableSymbolicPulse": - # Between Qiskit 0.40 and 0.46, the (amp, angle) representation was present, - # but complex amp was still allowed. In Qiskit 1.0 and beyond complex amp - # is no longer supported and so the amp needs to be checked and converted. - # Once QPY version is bumped, a new reader function can be introduced without - # this check. - if isinstance(parameters["amp"], complex): - parameters["angle"] = np.angle(parameters["amp"]) - parameters["amp"] = np.abs(parameters["amp"]) - warnings.warn( - f"ScalableSymbolicPulse with complex amp are no longer supported. " - f"{pulse_type} with complex amp was converted to (amp,angle) representation.", - UserWarning, - ) + value.read_value(file_obj, version, {}) # read duration + value.read_value(file_obj, version, {}) # read name - return library.ScalableSymbolicPulse( - pulse_type=pulse_type, - duration=duration, - amp=parameters["amp"], - angle=parameters["angle"], - parameters=parameters, - name=name, - limit_amplitude=header.amp_limited, - envelope=envelope, - constraints=constraints, - valid_amp_conditions=valid_amp_conditions, - ) + if class_name == "SymbolicPulse" or class_name == "ScalableSymbolicPulse": + return None else: raise NotImplementedError(f"Unknown class '{class_name}'") def _read_alignment_context(file_obj, version): - type_key = common.read_type_key(file_obj) + # TODO: document purpose + common.read_type_key(file_obj) - context_params = common.read_sequence( + common.read_sequence( file_obj, deserializer=value.loads_value, version=version, vectors={}, ) - context_cls = type_keys.ScheduleAlignment.retrieve(type_key) - instance = object.__new__(context_cls) - instance._context_params = tuple(context_params) - return instance - - -# pylint: disable=too-many-return-statements def _loads_operand(type_key, data_bytes, version, use_symengine): + # TODO: document purpose ADD NONE TO ALL THE DUMMY READERS if type_key == type_keys.ScheduleOperand.WAVEFORM: return common.data_from_binary(data_bytes, _read_waveform, version=version) if type_key == type_keys.ScheduleOperand.SYMBOLIC_PULSE: @@ -308,22 +231,18 @@ def _loads_operand(type_key, data_bytes, version, use_symengine): def _read_element(file_obj, version, metadata_deserializer, use_symengine): + # TODO: document purpose of the function type_key = common.read_type_key(file_obj) if type_key == type_keys.Program.SCHEDULE_BLOCK: - return read_schedule_block(file_obj, version, metadata_deserializer, use_symengine) + read_schedule_block(file_obj, version, metadata_deserializer, use_symengine) - operands = common.read_sequence( + # read operands + common.read_sequence( file_obj, deserializer=_loads_operand, version=version, use_symengine=use_symengine ) - name = value.read_value(file_obj, version, {}) - - instance = object.__new__(type_keys.ScheduleInstruction.retrieve(type_key)) - instance._operands = tuple(operands) - instance._name = name - instance._hash = None - - return instance + # read name + value.read_value(file_obj, version, {}) def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version): @@ -332,7 +251,7 @@ def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version): if type_key == type_keys.Program.SCHEDULE_BLOCK: return common.data_from_binary( data_bytes, - deserializer=read_schedule_block, + deserializer=read_schedule_block, # TODO: where is this function used? version=version, metadata_deserializer=metadata_deserializer, ) @@ -344,6 +263,7 @@ def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version): ) +# TODO: all the _write and dump functions below should be removed def _write_channel(file_obj, data, version): type_key = type_keys.ScheduleChannel.assign(data) common.write_type_key(file_obj, type_key) @@ -513,6 +433,7 @@ def _dumps_reference_item(schedule, metadata_serializer, version): @ignore_pulse_deprecation_warnings def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symengine=False): + # TODO: document the purpose of this function """Read a single ScheduleBlock from the file like object. Args: @@ -530,7 +451,7 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen platforms. Please check that your target platform is supported by the symengine library before setting this option, as it will be required by qpy to deserialize the payload. Returns: - ScheduleBlock: The schedule block object from the file. + ScheduleBlock: The schedule block object from the file. #TODO: NONE Raises: TypeError: If any of the instructions is invalid data format. @@ -545,37 +466,22 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen file_obj.read(formats.SCHEDULE_BLOCK_HEADER_SIZE), ) ) - name = file_obj.read(data.name_size).decode(common.ENCODE) + file_obj.read(data.name_size).decode(common.ENCODE) # read name metadata_raw = file_obj.read(data.metadata_size) - metadata = json.loads(metadata_raw, cls=metadata_deserializer) - context = _read_alignment_context(file_obj, version) + json.loads(metadata_raw, cls=metadata_deserializer) # read metadata + _read_alignment_context(file_obj, version) - block = ScheduleBlock( - name=name, - metadata=metadata, - alignment_context=context, - ) for _ in range(data.num_elements): - block_elm = _read_element(file_obj, version, metadata_deserializer, use_symengine) - block.append(block_elm, inplace=True) + _read_element(file_obj, version, metadata_deserializer, use_symengine) # Load references if version >= 7: - flat_key_refdict = common.read_mapping( + common.read_mapping( file_obj=file_obj, deserializer=_loads_reference_item, version=version, metadata_deserializer=metadata_deserializer, ) - ref_dict = {} - for key_str, schedule in flat_key_refdict.items(): - if schedule is not None: - composite_key = tuple(key_str.split(instructions.Reference.key_delimiter)) - ref_dict[composite_key] = schedule - if ref_dict: - block.assign_references(ref_dict, inplace=True) - - return block def write_schedule_block( diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index cab90eb9407f..e8f32564f2e9 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -22,16 +22,14 @@ import re from qiskit.circuit import QuantumCircuit -from qiskit.pulse import ScheduleBlock from qiskit.exceptions import QiskitError from qiskit.qpy import formats, common, binary_io, type_keys from qiskit.qpy.exceptions import QPYLoadingDeprecatedFeatureWarning, QpyError from qiskit.version import __version__ -from qiskit.utils.deprecate_pulse import deprecate_pulse_arg # pylint: disable=invalid-name -QPY_SUPPORTED_TYPES = Union[QuantumCircuit, ScheduleBlock] +QPY_SUPPORTED_TYPES = Union[QuantumCircuit] # This version pattern is taken from the pypa packaging project: # https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254 @@ -74,11 +72,6 @@ VERSION_PATTERN_REGEX = re.compile(VERSION_PATTERN, re.VERBOSE | re.IGNORECASE) -@deprecate_pulse_arg( - "programs", - deprecation_description="Passing `ScheduleBlock` to `programs`", - predicate=lambda p: isinstance(p, ScheduleBlock), -) def dump( programs: Union[List[QPY_SUPPORTED_TYPES], QPY_SUPPORTED_TYPES], file_obj: BinaryIO, @@ -350,15 +343,8 @@ def load( if type_key == type_keys.Program.CIRCUIT: loader = binary_io.read_circuit elif type_key == type_keys.Program.SCHEDULE_BLOCK: - loader = binary_io.read_schedule_block - warnings.warn( - category=QPYLoadingDeprecatedFeatureWarning, - message="Pulse gates deserialization is deprecated as of Qiskit 1.3 and " - "will be removed in Qiskit 2.0. This is part of the deprecation plan for " - "the entire Qiskit Pulse package. Once Pulse is removed, `ScheduleBlock` " - "sections will be ignored when loading QPY files with pulse data.", - ) - + raise QPYLoadingDeprecatedFeatureWarning("Payloads of type `ScheduleBlock` cannot be loaded as of Qiskit 2.0. " + "Use an earlier version if Qiskit if needed.") else: raise TypeError(f"Invalid payload format data kind '{type_key}'.") diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py index 60262440d033..01226c73526f 100644 --- a/qiskit/qpy/type_keys.py +++ b/qiskit/qpy/type_keys.py @@ -437,7 +437,7 @@ class Program(TypeKeyBase): def assign(cls, obj): if isinstance(obj, QuantumCircuit): return cls.CIRCUIT - if isinstance(obj, ScheduleBlock): + if isinstance(obj, ScheduleBlock): # TODO: remove this path return cls.SCHEDULE_BLOCK raise exceptions.QpyError( diff --git a/test/python/qpy/test_block_load_from_qpy.py b/test/python/qpy/test_block_load_from_qpy.py deleted file mode 100644 index 6085618e8362..000000000000 --- a/test/python/qpy/test_block_load_from_qpy.py +++ /dev/null @@ -1,521 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test cases for the schedule block qpy loading and saving.""" - -import io -import unittest -import warnings -from ddt import ddt, data, unpack -import numpy as np -import symengine as sym - -from qiskit.pulse import builder, Schedule -from qiskit.pulse.library import ( - SymbolicPulse, - Gaussian, - GaussianSquare, - Drag, - Constant, - Waveform, -) -from qiskit.pulse.channels import ( - DriveChannel, - ControlChannel, - MeasureChannel, - AcquireChannel, - MemorySlot, - RegisterSlot, -) -from qiskit.pulse.instructions import Play, TimeBlockade -from qiskit.circuit import Parameter, QuantumCircuit, Gate -from qiskit.qpy import dump, load -from qiskit.qpy.exceptions import QPYLoadingDeprecatedFeatureWarning -from qiskit.utils import optionals as _optional -from qiskit.pulse.configuration import Kernel, Discriminator -from test import QiskitTestCase # pylint: disable=wrong-import-order - - -class QpyScheduleTestCase(QiskitTestCase): - """QPY schedule testing platform.""" - - def assert_roundtrip_equal(self, block, use_symengine=False): - """QPY roundtrip equal test.""" - qpy_file = io.BytesIO() - with self.assertWarns(DeprecationWarning): - dump(block, qpy_file, use_symengine=use_symengine) - qpy_file.seek(0) - new_block = load(qpy_file)[0] - - self.assertEqual(block, new_block) - - -@ddt -class TestLoadFromQPY(QpyScheduleTestCase): - """Test loading and saving schedule block to qpy file.""" - - @data( - (Gaussian, DriveChannel, 160, 0.1, 40), - (GaussianSquare, DriveChannel, 800, 0.1, 64, 544), - (Drag, DriveChannel, 160, 0.1, 40, 0.5), - (Constant, DriveChannel, 800, 0.1), - (Constant, ControlChannel, 800, 0.1), - (Constant, MeasureChannel, 800, 0.1), - ) - @unpack - def test_library_pulse_play(self, envelope, channel, *params): - """Test playing standard pulses.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.play( - envelope(*params), - channel(0), - ) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_playing_custom_symbolic_pulse(self): - """Test playing a custom user pulse.""" - # pylint: disable=invalid-name - t, amp, freq = sym.symbols("t, amp, freq") - sym_envelope = 2 * amp * (freq * t - sym.floor(1 / 2 + freq * t)) - - with self.assertWarns(DeprecationWarning): - my_pulse = SymbolicPulse( - pulse_type="Sawtooth", - duration=100, - parameters={"amp": 0.1, "freq": 0.05}, - envelope=sym_envelope, - name="pulse1", - ) - with builder.build() as test_sched: - builder.play(my_pulse, DriveChannel(0)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_symbolic_amplitude_limit(self): - """Test applying amplitude limit to symbolic pulse.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.play( - Gaussian(160, 20, 40, limit_amplitude=False), - DriveChannel(0), - ) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_waveform_amplitude_limit(self): - """Test applying amplitude limit to waveform.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.play( - Waveform([1, 2, 3, 4, 5], limit_amplitude=False), - DriveChannel(0), - ) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_playing_waveform(self): - """Test playing waveform.""" - # pylint: disable=invalid-name - t = np.linspace(0, 1, 100) - waveform = 0.1 * np.sin(2 * np.pi * t) - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.play(waveform, DriveChannel(0)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_phases(self): - """Test phase.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.shift_phase(0.1, DriveChannel(0)) - builder.set_phase(0.4, DriveChannel(1)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_frequencies(self): - """Test frequency.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.shift_frequency(10e6, DriveChannel(0)) - builder.set_frequency(5e9, DriveChannel(1)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_delay(self): - """Test delay.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.delay(100, DriveChannel(0)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_barrier(self): - """Test barrier.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.barrier(DriveChannel(0), DriveChannel(1), ControlChannel(2)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_time_blockade(self): - """Test time blockade.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.append_instruction(TimeBlockade(10, DriveChannel(0))) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_measure(self): - """Test measurement.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.acquire(100, AcquireChannel(0), MemorySlot(0)) - builder.acquire(100, AcquireChannel(1), RegisterSlot(1)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - @data( - (0, Parameter("dur"), 0.1, 40), - (Parameter("ch1"), 160, 0.1, 40), - (Parameter("ch1"), Parameter("dur"), Parameter("amp"), Parameter("sigma")), - (0, 160, Parameter("amp") * np.exp(1j * Parameter("phase")), 40), - ) - @unpack - def test_parameterized(self, channel, *params): - """Test playing parameterized pulse.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.play(Gaussian(*params), DriveChannel(channel)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_nested_blocks(self): - """Test nested blocks with different alignment contexts.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - with builder.align_equispaced(duration=1200): - with builder.align_left(): - builder.delay(100, DriveChannel(0)) - builder.delay(200, DriveChannel(1)) - with builder.align_right(): - builder.delay(100, DriveChannel(0)) - builder.delay(200, DriveChannel(1)) - with builder.align_sequential(): - builder.delay(100, DriveChannel(0)) - builder.delay(200, DriveChannel(1)) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_called_schedule(self): - """Test referenced pulse Schedule object. - - Referenced object is naively converted into ScheduleBlock with TimeBlockade instructions. - Thus referenced Schedule is still QPY compatible. - """ - with self.assertWarns(DeprecationWarning): - refsched = Schedule() - refsched.insert(20, Play(Constant(100, 0.1), DriveChannel(0))) - refsched.insert(50, Play(Constant(100, 0.1), DriveChannel(1))) - - with builder.build() as test_sched: - builder.call(refsched, name="test_ref") - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_unassigned_reference(self): - """Test schedule with unassigned reference.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.reference("custom1", "q0") - builder.reference("custom1", "q1") - - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_partly_assigned_reference(self): - """Test schedule with partly assigned reference.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.reference("custom1", "q0") - builder.reference("custom1", "q1") - - with builder.build() as sub_q0: - builder.delay(Parameter("duration"), DriveChannel(0)) - - test_sched.assign_references( - {("custom1", "q0"): sub_q0}, - inplace=True, - ) - - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_nested_assigned_reference(self): - """Test schedule with assigned reference for nested schedule.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - with builder.align_left(): - builder.reference("custom1", "q0") - builder.reference("custom1", "q1") - - with builder.build() as sub_q0: - builder.delay(Parameter("duration"), DriveChannel(0)) - - with builder.build() as sub_q1: - builder.delay(Parameter("duration"), DriveChannel(1)) - - test_sched.assign_references( - {("custom1", "q0"): sub_q0, ("custom1", "q1"): sub_q1}, - inplace=True, - ) - - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_bell_schedule(self): - """Test complex schedule to create a Bell state.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - with builder.align_sequential(): - # H - builder.shift_phase(-1.57, DriveChannel(0)) - builder.play(Drag(160, 0.05, 40, 1.3), DriveChannel(0)) - builder.shift_phase(-1.57, DriveChannel(0)) - # ECR - with builder.align_left(): - builder.play(GaussianSquare(800, 0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, 0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - with builder.align_left(): - builder.play(GaussianSquare(800, -0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, -0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - # Measure - with builder.align_left(): - builder.play(GaussianSquare(8000, 0.2, 64, 7744), MeasureChannel(0)) - builder.acquire(8000, AcquireChannel(0), MemorySlot(0)) - - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - @unittest.skipUnless(_optional.HAS_SYMENGINE, "Symengine required for this test") - def test_bell_schedule_use_symengine(self): - """Test complex schedule to create a Bell state.""" - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - with builder.align_sequential(): - # H - builder.shift_phase(-1.57, DriveChannel(0)) - builder.play(Drag(160, 0.05, 40, 1.3), DriveChannel(0)) - builder.shift_phase(-1.57, DriveChannel(0)) - # ECR - with builder.align_left(): - builder.play(GaussianSquare(800, 0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, 0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - with builder.align_left(): - builder.play(GaussianSquare(800, -0.05, 64, 544), DriveChannel(1)) - builder.play(GaussianSquare(800, -0.22, 64, 544, 2), ControlChannel(0)) - builder.play(Drag(160, 0.1, 40, 1.5), DriveChannel(0)) - # Measure - with builder.align_left(): - builder.play(GaussianSquare(8000, 0.2, 64, 7744), MeasureChannel(0)) - builder.acquire(8000, AcquireChannel(0), MemorySlot(0)) - - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched, True) - - def test_with_acquire_instruction_with_kernel(self): - """Test a schedblk with acquire instruction with kernel.""" - kernel = Kernel( - name="my_kernel", kernel={"real": np.ones(10), "imag": np.zeros(10)}, bias=[0, 0] - ) - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.acquire(100, AcquireChannel(0), MemorySlot(0), kernel=kernel) - - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - def test_with_acquire_instruction_with_discriminator(self): - """Test a schedblk with acquire instruction with a discriminator.""" - discriminator = Discriminator( - name="my_discriminator", discriminator_type="linear", params=[1, 0] - ) - with self.assertWarns(DeprecationWarning): - with builder.build() as test_sched: - builder.acquire(100, AcquireChannel(0), MemorySlot(0), discriminator=discriminator) - - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - self.assert_roundtrip_equal(test_sched) - - -class TestPulseGate(QpyScheduleTestCase): - """Test loading and saving pulse gate attached circuit to qpy file.""" - - def test_1q_gate(self): - """Test for single qubit pulse gate.""" - mygate = Gate("mygate", 1, []) - - with self.assertWarns(DeprecationWarning): - with builder.build() as caldef: - builder.play(Constant(100, 0.1), DriveChannel(0)) - - qc = QuantumCircuit(2) - qc.append(mygate, [0]) - with self.assertWarns(DeprecationWarning): - qc.add_calibration(mygate, (0,), caldef) - - self.assert_roundtrip_equal(qc) - - def test_2q_gate(self): - """Test for two qubit pulse gate.""" - mygate = Gate("mygate", 2, []) - - with self.assertWarns(DeprecationWarning): - with builder.build() as caldef: - builder.play(Constant(100, 0.1), ControlChannel(0)) - - qc = QuantumCircuit(2) - qc.append(mygate, [0, 1]) - with self.assertWarns(DeprecationWarning): - qc.add_calibration(mygate, (0, 1), caldef) - - self.assert_roundtrip_equal(qc) - - def test_parameterized_gate(self): - """Test for parameterized pulse gate.""" - amp = Parameter("amp") - angle = Parameter("angle") - mygate = Gate("mygate", 2, [amp, angle]) - - with self.assertWarns(DeprecationWarning): - with builder.build() as caldef: - builder.play(Constant(100, amp * np.exp(1j * angle)), ControlChannel(0)) - - qc = QuantumCircuit(2) - qc.append(mygate, [0, 1]) - with self.assertWarns(DeprecationWarning): - qc.add_calibration(mygate, (0, 1), caldef) - - self.assert_roundtrip_equal(qc) - - def test_override(self): - """Test for overriding standard gate with pulse gate.""" - amp = Parameter("amp") - - with self.assertWarns(DeprecationWarning): - with builder.build() as caldef: - builder.play(Constant(100, amp), ControlChannel(0)) - - qc = QuantumCircuit(2) - qc.rx(amp, 0) - with self.assertWarns(DeprecationWarning): - qc.add_calibration("rx", (0,), caldef, [amp]) - - self.assert_roundtrip_equal(qc) - - def test_multiple_calibrations(self): - """Test for circuit with multiple pulse gates.""" - amp1 = Parameter("amp1") - amp2 = Parameter("amp2") - mygate = Gate("mygate", 1, [amp2]) - - with self.assertWarns(DeprecationWarning): - with builder.build() as caldef1: - builder.play(Constant(100, amp1), DriveChannel(0)) - - with builder.build() as caldef2: - builder.play(Constant(100, amp2), DriveChannel(1)) - - qc = QuantumCircuit(2) - qc.rx(amp1, 0) - qc.append(mygate, [1]) - with self.assertWarns(DeprecationWarning): - qc.add_calibration("rx", (0,), caldef1, [amp1]) - qc.add_calibration(mygate, (1,), caldef2) - - self.assert_roundtrip_equal(qc) - - def test_with_acquire_instruction_with_kernel(self): - """Test a pulse gate with acquire instruction with kernel.""" - kernel = Kernel( - name="my_kernel", kernel={"real": np.zeros(10), "imag": np.zeros(10)}, bias=[0, 0] - ) - - with self.assertWarns(DeprecationWarning): - with builder.build() as sched: - builder.acquire(10, AcquireChannel(0), MemorySlot(0), kernel=kernel) - - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.add_calibration("measure", (0,), sched) - - self.assert_roundtrip_equal(qc) - - def test_with_acquire_instruction_with_discriminator(self): - """Test a pulse gate with acquire instruction with discriminator.""" - discriminator = Discriminator("my_discriminator") - - with self.assertWarns(DeprecationWarning): - with builder.build() as sched: - builder.acquire(10, AcquireChannel(0), MemorySlot(0), discriminator=discriminator) - - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.add_calibration("measure", (0,), sched) - - self.assert_roundtrip_equal(qc) - - -class TestSymengineLoadFromQPY(QiskitTestCase): - """Test use of symengine in qpy set of methods.""" - - def setUp(self): - super().setUp() - - # pylint: disable=invalid-name - t, amp, freq = sym.symbols("t, amp, freq") - sym_envelope = 2 * amp * (freq * t - sym.floor(1 / 2 + freq * t)) - - with self.assertWarns(DeprecationWarning): - my_pulse = SymbolicPulse( - pulse_type="Sawtooth", - duration=100, - parameters={"amp": 0.1, "freq": 0.05}, - envelope=sym_envelope, - name="pulse1", - ) - with builder.build() as test_sched: - builder.play(my_pulse, DriveChannel(0)) - - self.test_sched = test_sched - - @unittest.skipIf(not _optional.HAS_SYMENGINE, "Install symengine to run this test.") - def test_symengine_full_path(self): - """Test use_symengine option for circuit with parameter expressions.""" - qpy_file = io.BytesIO() - with self.assertWarns(DeprecationWarning): - dump(self.test_sched, qpy_file, use_symengine=True) - qpy_file.seek(0) - with self.assertWarns(QPYLoadingDeprecatedFeatureWarning): - new_sched = load(qpy_file)[0] - self.assertEqual(self.test_sched, new_sched) From 39d12b4f8c6b50f8285aee28158efb9fe2401ae3 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 9 Feb 2025 17:08:43 +0200 Subject: [PATCH 02/18] Add documentation and remove redundant code --- qiskit/qpy/__init__.py | 44 ++- qiskit/qpy/binary_io/__init__.py | 1 - qiskit/qpy/binary_io/circuits.py | 62 +--- qiskit/qpy/binary_io/schedules.py | 325 +++--------------- qiskit/qpy/interface.py | 37 +- qiskit/qpy/type_keys.py | 235 +------------ .../remove-pulse-qpy-07a96673c8f10e38.yaml | 11 + .../circuit/test_circuit_load_from_qpy.py | 40 +-- test/python/qpy/test_circuit_load_from_qpy.py | 4 +- 9 files changed, 127 insertions(+), 632 deletions(-) create mode 100644 releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 60922f3d3ec2..2b3716136bb2 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -18,12 +18,11 @@ .. currentmodule:: qiskit.qpy QPY is a binary serialization format for :class:`~.QuantumCircuit` and -:class:`~.ScheduleBlock` objects that is designed to be cross-platform, -Python version agnostic, and backwards compatible moving forward. QPY should -be used if you need a mechanism to save or copy between systems a -:class:`~.QuantumCircuit` or :class:`~.ScheduleBlock` that preserves the full -Qiskit object structure (except for custom attributes defined outside of -Qiskit code). This differs from other serialization formats like +objects that is designed to be cross-platform, Python version agnostic, +and backwards compatible moving forward. QPY should be used if you need +a mechanism to save or copy between systems a :class:`~.QuantumCircuit` +that preserves the full Qiskit object structure (except for custom attributes +defined outside of Qiskit code). This differs from other serialization formats like `OpenQASM `__ (2.0 or 3.0) which has a different abstraction model and can result in a loss of information contained in the original circuit (or is unable to represent some aspects of the @@ -170,6 +169,12 @@ def open(*args): it to QPY setting ``use_symengine=False``. The resulting file can then be loaded by any later version of Qiskit. + With the removal of Pulse in Qiskit 2.0, QPY does not support loading ``ScheduleBlock` programs + or pulse gates. If such payloads are being loaded, QPY will issue a warning and + return partial circuits. In the case of a ``ScheduleBlock`` payload, a circuit with only a name + and metadata will be loaded. It the case of pulse gates, the circuit will contain custom + instructions without calibration data attached, hence leaving them undefined. + QPY format version history -------------------------- @@ -902,7 +907,7 @@ def open(*args): --------- Version 7 adds support for :class:`.~Reference` instruction and serialization of -a :class:`.~ScheduleBlock` program while keeping its reference to subroutines:: +a ``ScheduleBlock`` program while keeping its reference to subroutines:: from qiskit import pulse from qiskit import qpy @@ -974,12 +979,12 @@ def open(*args): Version 5 --------- -Version 5 changes from :ref:`qpy_version_4` by adding support for :class:`.~ScheduleBlock` +Version 5 changes from :ref:`qpy_version_4` by adding support for ``ScheduleBlock`` and changing two payloads the INSTRUCTION metadata payload and the CUSTOM_INSTRUCTION block. These now have new fields to better account for :class:`~.ControlledGate` objects in a circuit. In addition, new payload MAP_ITEM is defined to implement the :ref:`qpy_mapping` block. -With the support of :class:`.~ScheduleBlock`, now :class:`~.QuantumCircuit` can be +With the support of ``ScheduleBlock``, now :class:`~.QuantumCircuit` can be serialized together with :attr:`~.QuantumCircuit.calibrations`, or `Pulse Gates `_. In QPY version 5 and above, :ref:`qpy_circuit_calibrations` payload is @@ -996,7 +1001,7 @@ def open(*args): immediately follows the file header block to represent the program type stored in the file. - When ``type==c``, :class:`~.QuantumCircuit` payload follows -- When ``type==s``, :class:`~.ScheduleBlock` payload follows +- When ``type==s``, ``ScheduleBlock`` payload follows .. note:: @@ -1009,12 +1014,10 @@ def open(*args): SCHEDULE_BLOCK ~~~~~~~~~~~~~~ -:class:`~.ScheduleBlock` is first supported in QPY Version 5. This allows +``ScheduleBlock`` is first supported in QPY Version 5. This allows users to save pulse programs in the QPY binary format as follows: -.. plot:: - :include-source: - :nofigs: +.. code-block:: python from qiskit import pulse, qpy @@ -1027,13 +1030,6 @@ def open(*args): with open('schedule.qpy', 'rb') as fd: new_schedule = qpy.load(fd)[0] -.. plot:: - :nofigs: - - # This block is hidden from readers. It's cleanup code. - from pathlib import Path - Path("schedule.qpy").unlink() - Note that circuit and schedule block are serialized and deserialized through the same QPY interface. Input data type is implicitly analyzed and no extra option is required to save the schedule block. @@ -1043,7 +1039,7 @@ def open(*args): SCHEDULE_BLOCK_HEADER ~~~~~~~~~~~~~~~~~~~~~ -:class:`~.ScheduleBlock` block starts with the following header: +``ScheduleBlock`` block starts with the following header: .. code-block:: c @@ -1243,8 +1239,8 @@ def open(*args): and ``num_params`` length of INSTRUCTION_PARAM payload for parameters associated to the custom instruction. The ``type`` indicates the class of pulse program which is either, in principle, -:class:`~.ScheduleBlock` or :class:`~.Schedule`. As of QPY Version 5, -only :class:`~.ScheduleBlock` payload is supported. +``ScheduleBlock`` or :class:`~.Schedule`. As of QPY Version 5, +only ``ScheduleBlock`` payload is supported. Finally, :ref:`qpy_schedule_block` payload is packed for each CALIBRATION_DEF entry. .. _qpy_instruction_v5: diff --git a/qiskit/qpy/binary_io/__init__.py b/qiskit/qpy/binary_io/__init__.py index a5948b7d3f1b..46f0bd0473b7 100644 --- a/qiskit/qpy/binary_io/__init__.py +++ b/qiskit/qpy/binary_io/__init__.py @@ -31,6 +31,5 @@ _read_instruction, ) from .schedules import ( - write_schedule_block, read_schedule_block, ) diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 8fda99e56bb3..92e6e7d55c14 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -647,18 +647,19 @@ def _read_calibrations(file_obj, version, vectors, metadata_deserializer): defheader = formats.CALIBRATION_DEF._make( struct.unpack(formats.CALIBRATION_DEF_PACK, file_obj.read(formats.CALIBRATION_DEF_SIZE)) ) - name = file_obj.read(defheader.name_size).decode(common.ENCODE) # TODO: this is where the name of the gate comes from. Emit a warning here - warnings.warn( - category=exceptions.QPYLoadingDeprecatedFeatureWarning, - message="Support for loading dulse gates has been removed in Qiskit 2.0. " - f"If `{name}` is in the circuit, it will be left as a custom instruction without definition." - - ) + name = file_obj.read(defheader.name_size).decode(common.ENCODE) + if name: + warnings.warn( + category=exceptions.QPYLoadingDeprecatedFeatureWarning, + message="Support for loading dulse gates has been removed in Qiskit 2.0. " + f"If `{name}` is in the circuit, it will be left as a custom instruction" + " without definition.", + ) - for _ in range(defheader.num_qubits): # qubits info - struct.unpack("!q", file_obj.read(struct.calcsize("!q")))[0] + for _ in range(defheader.num_qubits): # read qubits info + file_obj.read(struct.calcsize("!q")) - for _ in range(defheader.num_params): # read params info + for _ in range(defheader.num_params): # read params info value.read_value(file_obj, version, vectors) schedules.read_schedule_block(file_obj, version, metadata_deserializer) @@ -992,34 +993,6 @@ def _write_custom_operation( return new_custom_instruction -def _write_calibrations(file_obj, calibrations, metadata_serializer, version): - flatten_dict = {} - for gate, caldef in calibrations.items(): - for (qubits, params), schedule in caldef.items(): - key = (gate, qubits, params) - flatten_dict[key] = schedule - header = struct.pack(formats.CALIBRATION_PACK, len(flatten_dict)) - file_obj.write(header) - for (name, qubits, params), schedule in flatten_dict.items(): - # In principle ScheduleBlock and Schedule can be supported. - # As of version 5 only ScheduleBlock is supported. - name_bytes = name.encode(common.ENCODE) - defheader = struct.pack( - formats.CALIBRATION_DEF_PACK, - len(name_bytes), - len(qubits), - len(params), - type_keys.Program.assign(schedule), - ) - file_obj.write(defheader) - file_obj.write(name_bytes) - for qubit in qubits: - file_obj.write(struct.pack("!q", qubit)) - for param in params: - value.write_value(file_obj, param, version=version) - schedules.write_schedule_block(file_obj, schedule, metadata_serializer, version=version) - - def _write_registers(file_obj, in_circ_regs, full_bits): bitmap = {bit: index for index, bit in enumerate(full_bits)} @@ -1320,8 +1293,11 @@ def write_circuit( file_obj.write(instruction_buffer.getvalue()) instruction_buffer.close() - # Write calibrations - _write_calibrations(file_obj, circuit._calibrations_prop, metadata_serializer, version=version) + # Pulse has been removed in Qiskit 2.0. As long as we keep QPY at version 13, + # we need to write an empty calibrations header since read_circuit expects it + header = struct.pack(formats.CALIBRATION_PACK, 0) + file_obj.write(header) + _write_layout(file_obj, circuit) @@ -1449,11 +1425,9 @@ def read_circuit(file_obj, version, metadata_deserializer=None, use_symengine=Fa standalone_var_indices, ) - # Read calibrations, but don't use them since pulse gates are not supported as of Qiskit 2.0 + # Consume calibrations, but don't use them since pulse gates are not supported as of Qiskit 2.0 if version >= 5: - _read_calibrations( - file_obj, version, vectors, metadata_deserializer - ) + _read_calibrations(file_obj, version, vectors, metadata_deserializer) for vec_name, (vector, initialized_params) in vectors.items(): if len(initialized_params) != len(vector): diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index cad8eb8ddb75..f9f43e45139e 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -10,35 +10,35 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Read and write schedule and schedule instructions.""" +"""Read schedule and schedule instructions. + +This module is kep post pulse-removal to allow reading legacy +payloads containing pulse gates without breaking the load flow. +The purpose of the `_read` and `_load` methods below is just to advance +the file handle while consuming pulse data.""" +from curses import meta import json import struct import zlib -import warnings from io import BytesIO import numpy as np import symengine as sym +from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.exceptions import QiskitError -from qiskit.pulse import library, channels, instructions -from qiskit.pulse.schedule import ScheduleBlock from qiskit.qpy import formats, common, type_keys from qiskit.qpy.binary_io import value from qiskit.qpy.exceptions import QpyError -from qiskit.pulse.configuration import Kernel, Discriminator -from qiskit.utils.deprecate_pulse import ignore_pulse_deprecation_warnings -def _read_channel(file_obj, version): - # TODO: document purpose - common.read_type_key(file_obj) # read type_key - value.read_value(file_obj, version, {}) # read index +def _read_channel(file_obj, version) -> None: + common.read_type_key(file_obj) # read type_key + value.read_value(file_obj, version, {}) # read index -def _read_waveform(file_obj, version): - # TODO: document purpose +def _read_waveform(file_obj, version) -> None: header = formats.WAVEFORM._make( struct.unpack( formats.WAVEFORM_PACK, @@ -46,8 +46,8 @@ def _read_waveform(file_obj, version): ) ) samples_raw = file_obj.read(header.data_size) - common.data_from_binary(samples_raw, np.load) # read samples - value.read_value(file_obj, version, {}) # read name + common.data_from_binary(samples_raw, np.load) # read samples + value.read_value(file_obj, version, {}) # read name def _loads_obj(type_key, binary_data, version, vectors): @@ -68,19 +68,17 @@ def _loads_obj(type_key, binary_data, version, vectors): return value.loads_value(type_key, binary_data, version, vectors) -def _read_kernel(file_obj, version): - # TODO: document - params = common.read_mapping( +def _read_kernel(file_obj, version) -> None: + common.read_mapping( file_obj=file_obj, deserializer=_loads_obj, version=version, vectors={}, ) - value.read_value(file_obj, version, {}) # read name + value.read_value(file_obj, version, {}) # read name -def _read_discriminator(file_obj, version): - # TODO: docucment +def _read_discriminator(file_obj, version) -> None: # read params common.read_mapping( file_obj=file_obj, @@ -88,7 +86,7 @@ def _read_discriminator(file_obj, version): version=version, vectors={}, ) - value.read_value(file_obj, version, {}) # read name + value.read_value(file_obj, version, {}) # read name def _loads_symbolic_expr(expr_bytes, use_symengine=False): @@ -105,8 +103,7 @@ def _loads_symbolic_expr(expr_bytes, use_symengine=False): return sym.sympify(expr) -def _read_symbolic_pulse(file_obj, version): - # TODO: document purpose +def _read_symbolic_pulse(file_obj, version) -> None: make = formats.SYMBOLIC_PULSE._make pack = formats.SYMBOLIC_PULSE_PACK size = formats.SYMBOLIC_PULSE_SIZE @@ -118,9 +115,11 @@ def _read_symbolic_pulse(file_obj, version): ) ) pulse_type = file_obj.read(header.type_size).decode(common.ENCODE) - _loads_symbolic_expr(file_obj.read(header.envelope_size)) # read envelope - _loads_symbolic_expr(file_obj.read(header.constraints_size)) # read constraints - _loads_symbolic_expr(file_obj.read(header.valid_amp_conditions_size)) # read valid amp conditions + _loads_symbolic_expr(file_obj.read(header.envelope_size)) # read envelope + _loads_symbolic_expr(file_obj.read(header.constraints_size)) # read constraints + _loads_symbolic_expr( + file_obj.read(header.valid_amp_conditions_size) + ) # read valid amp conditions # read parameters common.read_mapping( file_obj, @@ -142,16 +141,14 @@ def _read_symbolic_pulse(file_obj, version): if pulse_type in legacy_library_pulses: class_name = "ScalableSymbolicPulse" - value.read_value(file_obj, version, {}) # read duration - value.read_value(file_obj, version, {}) # read name + value.read_value(file_obj, version, {}) # read duration + value.read_value(file_obj, version, {}) # read name - if class_name == "SymbolicPulse" or class_name == "ScalableSymbolicPulse": - return None - else: + if class_name not in ("SymbolicPulse", "ScalableSymbolicPulse"): raise NotImplementedError(f"Unknown class '{class_name}'") -def _read_symbolic_pulse_v6(file_obj, version, use_symengine): +def _read_symbolic_pulse_v6(file_obj, version, use_symengine) -> None: # TODO: document purpose make = formats.SYMBOLIC_PULSE_V2._make pack = formats.SYMBOLIC_PULSE_PACK_V2 @@ -164,12 +161,12 @@ def _read_symbolic_pulse_v6(file_obj, version, use_symengine): ) ) class_name = file_obj.read(header.class_name_size).decode(common.ENCODE) - file_obj.read(header.type_size).decode(common.ENCODE) # read pulse type - _loads_symbolic_expr(file_obj.read(header.envelope_size), use_symengine) # read envelope - _loads_symbolic_expr(file_obj.read(header.constraints_size), use_symengine) # read constraints + file_obj.read(header.type_size).decode(common.ENCODE) # read pulse type + _loads_symbolic_expr(file_obj.read(header.envelope_size), use_symengine) # read envelope + _loads_symbolic_expr(file_obj.read(header.constraints_size), use_symengine) # read constraints _loads_symbolic_expr( file_obj.read(header.valid_amp_conditions_size), use_symengine - ) # read valid_amp_conditions + ) # read valid_amp_conditions # read parameters common.read_mapping( file_obj, @@ -178,17 +175,14 @@ def _read_symbolic_pulse_v6(file_obj, version, use_symengine): vectors={}, ) - value.read_value(file_obj, version, {}) # read duration - value.read_value(file_obj, version, {}) # read name + value.read_value(file_obj, version, {}) # read duration + value.read_value(file_obj, version, {}) # read name - if class_name == "SymbolicPulse" or class_name == "ScalableSymbolicPulse": - return None - else: + if class_name not in ("SymbolicPulse", "ScalableSymbolicPulse"): raise NotImplementedError(f"Unknown class '{class_name}'") -def _read_alignment_context(file_obj, version): - # TODO: document purpose +def _read_alignment_context(file_obj, version) -> None: common.read_type_key(file_obj) common.read_sequence( @@ -199,6 +193,7 @@ def _read_alignment_context(file_obj, version): ) +# pylint: disable=too-many-return-statements def _loads_operand(type_key, data_bytes, version, use_symengine): # TODO: document purpose ADD NONE TO ALL THE DUMMY READERS if type_key == type_keys.ScheduleOperand.WAVEFORM: @@ -230,8 +225,7 @@ def _loads_operand(type_key, data_bytes, version, use_symengine): return value.loads_value(type_key, data_bytes, version, {}) -def _read_element(file_obj, version, metadata_deserializer, use_symengine): - # TODO: document purpose of the function +def _read_element(file_obj, version, metadata_deserializer, use_symengine) -> None: type_key = common.read_type_key(file_obj) if type_key == type_keys.Program.SCHEDULE_BLOCK: @@ -245,13 +239,13 @@ def _read_element(file_obj, version, metadata_deserializer, use_symengine): value.read_value(file_obj, version, {}) -def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version): +def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version) -> None: if type_key == type_keys.Value.NULL: return None if type_key == type_keys.Program.SCHEDULE_BLOCK: return common.data_from_binary( data_bytes, - deserializer=read_schedule_block, # TODO: where is this function used? + deserializer=read_schedule_block, version=version, metadata_deserializer=metadata_deserializer, ) @@ -263,178 +257,8 @@ def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version): ) -# TODO: all the _write and dump functions below should be removed -def _write_channel(file_obj, data, version): - type_key = type_keys.ScheduleChannel.assign(data) - common.write_type_key(file_obj, type_key) - value.write_value(file_obj, data.index, version=version) - - -def _write_waveform(file_obj, data, version): - samples_bytes = common.data_to_binary(data.samples, np.save) - - header = struct.pack( - formats.WAVEFORM_PACK, - data.epsilon, - len(samples_bytes), - data._limit_amplitude, - ) - file_obj.write(header) - file_obj.write(samples_bytes) - value.write_value(file_obj, data.name, version=version) - - -def _dumps_obj(obj, version): - """Wraps `value.dumps_value` to serialize dictionary and list objects - which are not supported by `value.dumps_value`. - """ - if isinstance(obj, dict): - with BytesIO() as container: - common.write_mapping( - file_obj=container, mapping=obj, serializer=_dumps_obj, version=version - ) - binary_data = container.getvalue() - return b"D", binary_data - elif isinstance(obj, list): - with BytesIO() as container: - common.write_sequence( - file_obj=container, sequence=obj, serializer=_dumps_obj, version=version - ) - binary_data = container.getvalue() - return b"l", binary_data - else: - return value.dumps_value(obj, version=version) - - -def _write_kernel(file_obj, data, version): - name = data.name - params = data.params - common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj, version=version) - value.write_value(file_obj, name, version=version) - - -def _write_discriminator(file_obj, data, version): - name = data.name - params = data.params - common.write_mapping(file_obj=file_obj, mapping=params, serializer=_dumps_obj, version=version) - value.write_value(file_obj, name, version=version) - - -def _dumps_symbolic_expr(expr, use_symengine): - if expr is None: - return b"" - if use_symengine: - expr_bytes = expr.__reduce__()[1][0] - else: - from sympy import srepr, sympify - - expr_bytes = srepr(sympify(expr)).encode(common.ENCODE) - return zlib.compress(expr_bytes) - - -def _write_symbolic_pulse(file_obj, data, use_symengine, version): - class_name_bytes = data.__class__.__name__.encode(common.ENCODE) - pulse_type_bytes = data.pulse_type.encode(common.ENCODE) - envelope_bytes = _dumps_symbolic_expr(data.envelope, use_symengine) - constraints_bytes = _dumps_symbolic_expr(data.constraints, use_symengine) - valid_amp_conditions_bytes = _dumps_symbolic_expr(data.valid_amp_conditions, use_symengine) - - header_bytes = struct.pack( - formats.SYMBOLIC_PULSE_PACK_V2, - len(class_name_bytes), - len(pulse_type_bytes), - len(envelope_bytes), - len(constraints_bytes), - len(valid_amp_conditions_bytes), - data._limit_amplitude, - ) - file_obj.write(header_bytes) - file_obj.write(class_name_bytes) - file_obj.write(pulse_type_bytes) - file_obj.write(envelope_bytes) - file_obj.write(constraints_bytes) - file_obj.write(valid_amp_conditions_bytes) - common.write_mapping( - file_obj, - mapping=data._params, - serializer=value.dumps_value, - version=version, - ) - value.write_value(file_obj, data.duration, version=version) - value.write_value(file_obj, data.name, version=version) - - -def _write_alignment_context(file_obj, context, version): - type_key = type_keys.ScheduleAlignment.assign(context) - common.write_type_key(file_obj, type_key) - common.write_sequence( - file_obj, sequence=context._context_params, serializer=value.dumps_value, version=version - ) - - -def _dumps_operand(operand, use_symengine, version): - if isinstance(operand, library.Waveform): - type_key = type_keys.ScheduleOperand.WAVEFORM - data_bytes = common.data_to_binary(operand, _write_waveform, version=version) - elif isinstance(operand, library.SymbolicPulse): - type_key = type_keys.ScheduleOperand.SYMBOLIC_PULSE - data_bytes = common.data_to_binary( - operand, _write_symbolic_pulse, use_symengine=use_symengine, version=version - ) - elif isinstance(operand, channels.Channel): - type_key = type_keys.ScheduleOperand.CHANNEL - data_bytes = common.data_to_binary(operand, _write_channel, version=version) - elif isinstance(operand, str): - type_key = type_keys.ScheduleOperand.OPERAND_STR - data_bytes = operand.encode(common.ENCODE) - elif isinstance(operand, Kernel): - type_key = type_keys.ScheduleOperand.KERNEL - data_bytes = common.data_to_binary(operand, _write_kernel, version=version) - elif isinstance(operand, Discriminator): - type_key = type_keys.ScheduleOperand.DISCRIMINATOR - data_bytes = common.data_to_binary(operand, _write_discriminator, version=version) - else: - type_key, data_bytes = value.dumps_value(operand, version=version) - - return type_key, data_bytes - - -def _write_element(file_obj, element, metadata_serializer, use_symengine, version): - if isinstance(element, ScheduleBlock): - common.write_type_key(file_obj, type_keys.Program.SCHEDULE_BLOCK) - write_schedule_block(file_obj, element, metadata_serializer, use_symengine, version=version) - else: - type_key = type_keys.ScheduleInstruction.assign(element) - common.write_type_key(file_obj, type_key) - common.write_sequence( - file_obj, - sequence=element.operands, - serializer=_dumps_operand, - use_symengine=use_symengine, - version=version, - ) - value.write_value(file_obj, element.name, version=version) - - -def _dumps_reference_item(schedule, metadata_serializer, version): - if schedule is None: - type_key = type_keys.Value.NULL - data_bytes = b"" - else: - type_key = type_keys.Program.SCHEDULE_BLOCK - data_bytes = common.data_to_binary( - obj=schedule, - serializer=write_schedule_block, - metadata_serializer=metadata_serializer, - version=version, - ) - return type_key, data_bytes - - -@ignore_pulse_deprecation_warnings def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symengine=False): - # TODO: document the purpose of this function - """Read a single ScheduleBlock from the file like object. + """Consume a single ScheduleBlock from the file like object. Args: file_obj (File): A file like object that contains the QPY binary data. @@ -451,7 +275,9 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen platforms. Please check that your target platform is supported by the symengine library before setting this option, as it will be required by qpy to deserialize the payload. Returns: - ScheduleBlock: The schedule block object from the file. #TODO: NONE + QuantumCircuit: Returns a dummy QuantumCircuit object, containing just name and metadata. + This function exists just to allow reading legacy payloads containing pulse information + without breaking the entire load flow. Raises: TypeError: If any of the instructions is invalid data format. @@ -466,9 +292,9 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen file_obj.read(formats.SCHEDULE_BLOCK_HEADER_SIZE), ) ) - file_obj.read(data.name_size).decode(common.ENCODE) # read name + name = file_obj.read(data.name_size).decode(common.ENCODE) metadata_raw = file_obj.read(data.metadata_size) - json.loads(metadata_raw, cls=metadata_deserializer) # read metadata + metadata = json.loads(metadata_raw, cls=metadata_deserializer) # read metadata _read_alignment_context(file_obj, version) for _ in range(data.num_elements): @@ -483,59 +309,4 @@ def read_schedule_block(file_obj, version, metadata_deserializer=None, use_symen metadata_deserializer=metadata_deserializer, ) - -def write_schedule_block( - file_obj, block, metadata_serializer=None, use_symengine=False, version=common.QPY_VERSION -): - """Write a single ScheduleBlock object in the file like object. - - Args: - file_obj (File): The file like object to write the circuit data in. - block (ScheduleBlock): A schedule block data to write. - metadata_serializer (JSONEncoder): An optional JSONEncoder class that - will be passed the :attr:`.ScheduleBlock.metadata` dictionary for - ``block`` and will be used as the ``cls`` kwarg - on the ``json.dump()`` call to JSON serialize that dictionary. - use_symengine (bool): If True, symbolic objects will be serialized using symengine's - native mechanism. This is a faster serialization alternative, but not supported in all - platforms. Please check that your target platform is supported by the symengine library - before setting this option, as it will be required by qpy to deserialize the payload. - version (int): The QPY format version to use for serializing this circuit block - Raises: - TypeError: If any of the instructions is invalid data format. - """ - metadata = json.dumps(block.metadata, separators=(",", ":"), cls=metadata_serializer).encode( - common.ENCODE - ) - block_name = block.name.encode(common.ENCODE) - - # Write schedule block header - header_raw = formats.SCHEDULE_BLOCK_HEADER( - name_size=len(block_name), - metadata_size=len(metadata), - num_elements=len(block), - ) - header = struct.pack(formats.SCHEDULE_BLOCK_HEADER_PACK, *header_raw) - file_obj.write(header) - file_obj.write(block_name) - file_obj.write(metadata) - - _write_alignment_context(file_obj, block.alignment_context, version=version) - for block_elm in block._blocks: - # Do not call block.blocks. This implicitly assigns references to instruction. - # This breaks original reference structure. - _write_element(file_obj, block_elm, metadata_serializer, use_symengine, version=version) - - # Write references - flat_key_refdict = {} - for ref_keys, schedule in block._reference_manager.items(): - # Do not call block.reference. This returns the reference of most outer program by design. - key_str = instructions.Reference.key_delimiter.join(ref_keys) - flat_key_refdict[key_str] = schedule - common.write_mapping( - file_obj=file_obj, - mapping=flat_key_refdict, - serializer=_dumps_reference_item, - metadata_serializer=metadata_serializer, - version=version, - ) + return QuantumCircuit(name=name, metadata=metadata) diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index e8f32564f2e9..0958fdd8cad9 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -120,9 +120,7 @@ def dump( Args: programs: QPY supported object(s) to store in the specified file like object. - QPY supports :class:`.QuantumCircuit` and :class:`.ScheduleBlock`. - Different data types must be separately serialized. - Support for :class:`.ScheduleBlock` is deprecated since Qiskit 1.3.0. + QPY supports :class:`.QuantumCircuit`. file_obj: The file like object to write the QPY data too metadata_serializer: An optional JSONEncoder class that will be passed the ``.metadata`` attribute for each program in ``programs`` and will be @@ -149,7 +147,7 @@ def dump( .. note:: - If serializing a :class:`.QuantumCircuit` or :class:`.ScheduleBlock` that contain + If serializing a :class:`.QuantumCircuit` that contains :class:`.ParameterExpression` objects with ``version`` set low with the intent to load the payload using a historical release of Qiskit, it is safest to set the ``use_symengine`` flag to ``False``. Versions of Qiskit prior to 1.2.4 cannot load @@ -180,9 +178,6 @@ def dump( if issubclass(program_type, QuantumCircuit): type_key = type_keys.Program.CIRCUIT writer = binary_io.write_circuit - elif program_type is ScheduleBlock: - type_key = type_keys.Program.SCHEDULE_BLOCK - writer = binary_io.write_schedule_block else: raise TypeError(f"'{program_type}' is not supported data type.") @@ -211,10 +206,7 @@ def dump( file_obj.write(header) common.write_type_key(file_obj, type_key) - pulse_gates = False for program in programs: - if type_key == type_keys.Program.CIRCUIT and program._calibrations_prop: - pulse_gates = True writer( file_obj, program, @@ -223,13 +215,6 @@ def dump( version=version, ) - if pulse_gates: - warnings.warn( - category=DeprecationWarning, - message="Pulse gates serialization is deprecated as of Qiskit 1.3. " - "It will be removed in Qiskit 2.0.", - ) - def load( file_obj: BinaryIO, @@ -238,8 +223,7 @@ def load( """Load a QPY binary file This function is used to load a serialized QPY Qiskit program file and create - :class:`~qiskit.circuit.QuantumCircuit` objects or - :class:`~qiskit.pulse.schedule.ScheduleBlock` objects from its contents. + :class:`~qiskit.circuit.QuantumCircuit` objects from its contents. For example: .. code-block:: python @@ -260,12 +244,11 @@ def load( circuits = qpy.load(fd) which will read the contents of the qpy and return a list of - :class:`~qiskit.circuit.QuantumCircuit` objects or - :class:`~qiskit.pulse.schedule.ScheduleBlock` objects from the file. + :class:`~qiskit.circuit.QuantumCircuit` objects from the file. Args: file_obj: A file like object that contains the QPY binary - data for a circuit or pulse schedule. + data for a circuit. metadata_deserializer: An optional JSONDecoder class that will be used for the ``cls`` kwarg on the internal ``json.load`` call used to deserialize the JSON payload used for @@ -343,8 +326,14 @@ def load( if type_key == type_keys.Program.CIRCUIT: loader = binary_io.read_circuit elif type_key == type_keys.Program.SCHEDULE_BLOCK: - raise QPYLoadingDeprecatedFeatureWarning("Payloads of type `ScheduleBlock` cannot be loaded as of Qiskit 2.0. " - "Use an earlier version if Qiskit if needed.") + loader = binary_io.read_schedule_block + warnings.warn( + category=QPYLoadingDeprecatedFeatureWarning, + message="Payloads of type `ScheduleBlock` cannot be loaded as of Qiskit 2.0. " + "An empty circuit (possibly with serialized metadata) will be loaded. " + "Use an earlier version of Qiskit if you want to load a `ScheduleBlock`" + " payload.", + ) else: raise TypeError(f"Invalid payload format data kind '{type_key}'.") diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py index 01226c73526f..b4dc6c4cd465 100644 --- a/qiskit/qpy/type_keys.py +++ b/qiskit/qpy/type_keys.py @@ -37,36 +37,6 @@ from qiskit.circuit.parameter import Parameter from qiskit.circuit.parameterexpression import ParameterExpression from qiskit.circuit.parametervector import ParameterVectorElement -from qiskit.pulse.channels import ( - Channel, - DriveChannel, - MeasureChannel, - ControlChannel, - AcquireChannel, - MemorySlot, - RegisterSlot, -) -from qiskit.pulse.configuration import Discriminator, Kernel -from qiskit.pulse.instructions import ( - Acquire, - Play, - Delay, - SetFrequency, - ShiftFrequency, - SetPhase, - ShiftPhase, - RelativeBarrier, - TimeBlockade, - Reference, -) -from qiskit.pulse.library import Waveform, SymbolicPulse -from qiskit.pulse.schedule import ScheduleBlock -from qiskit.pulse.transforms.alignments import ( - AlignLeft, - AlignRight, - AlignSequential, - AlignEquispaced, -) from qiskit.qpy import exceptions @@ -168,7 +138,7 @@ class Condition(IntEnum): class Container(TypeKeyBase): - """Typle key enum for container-like object.""" + """Type key enum for container-like object.""" RANGE = b"r" TUPLE = b"t" @@ -220,125 +190,13 @@ def retrieve(cls, type_key): raise NotImplementedError -class ScheduleAlignment(TypeKeyBase): - """Type key enum for schedule block alignment context object.""" - - LEFT = b"l" - RIGHT = b"r" - SEQUENTIAL = b"s" - EQUISPACED = b"e" - - # AlignFunc is not serializable due to the callable in context parameter - - @classmethod - def assign(cls, obj): - if isinstance(obj, AlignLeft): - return cls.LEFT - if isinstance(obj, AlignRight): - return cls.RIGHT - if isinstance(obj, AlignSequential): - return cls.SEQUENTIAL - if isinstance(obj, AlignEquispaced): - return cls.EQUISPACED - - raise exceptions.QpyError( - f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." - ) - - @classmethod - def retrieve(cls, type_key): - if type_key == cls.LEFT: - return AlignLeft - if type_key == cls.RIGHT: - return AlignRight - if type_key == cls.SEQUENTIAL: - return AlignSequential - if type_key == cls.EQUISPACED: - return AlignEquispaced - - raise exceptions.QpyError( - f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace." - ) - - -class ScheduleInstruction(TypeKeyBase): - """Type key enum for schedule instruction object.""" - - ACQUIRE = b"a" - PLAY = b"p" - DELAY = b"d" - SET_FREQUENCY = b"f" - SHIFT_FREQUENCY = b"g" - SET_PHASE = b"q" - SHIFT_PHASE = b"r" - BARRIER = b"b" - TIME_BLOCKADE = b"t" - REFERENCE = b"y" - - # 's' is reserved by ScheduleBlock, i.e. block can be nested as an element. - # Call instruction is not supported by QPY. - # This instruction has been excluded from ScheduleBlock instructions with - # qiskit-terra/#8005 and new instruction Reference will be added instead. - # Call is only applied to Schedule which is not supported by QPY. - # Also snapshot is not suppored because of its limited usecase. - - @classmethod - def assign(cls, obj): - if isinstance(obj, Acquire): - return cls.ACQUIRE - if isinstance(obj, Play): - return cls.PLAY - if isinstance(obj, Delay): - return cls.DELAY - if isinstance(obj, SetFrequency): - return cls.SET_FREQUENCY - if isinstance(obj, ShiftFrequency): - return cls.SHIFT_FREQUENCY - if isinstance(obj, SetPhase): - return cls.SET_PHASE - if isinstance(obj, ShiftPhase): - return cls.SHIFT_PHASE - if isinstance(obj, RelativeBarrier): - return cls.BARRIER - if isinstance(obj, TimeBlockade): - return cls.TIME_BLOCKADE - if isinstance(obj, Reference): - return cls.REFERENCE - - raise exceptions.QpyError( - f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." - ) - - @classmethod - def retrieve(cls, type_key): - if type_key == cls.ACQUIRE: - return Acquire - if type_key == cls.PLAY: - return Play - if type_key == cls.DELAY: - return Delay - if type_key == cls.SET_FREQUENCY: - return SetFrequency - if type_key == cls.SHIFT_FREQUENCY: - return ShiftFrequency - if type_key == cls.SET_PHASE: - return SetPhase - if type_key == cls.SHIFT_PHASE: - return ShiftPhase - if type_key == cls.BARRIER: - return RelativeBarrier - if type_key == cls.TIME_BLOCKADE: - return TimeBlockade - if type_key == cls.REFERENCE: - return Reference - - raise exceptions.QpyError( - f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace." - ) - - class ScheduleOperand(TypeKeyBase): - """Type key enum for schedule instruction operand object.""" + """Type key enum for schedule instruction operand object. + + Note: This class is kept post pulse-removal to allow reading of + legacy payloads containing pulse gates without breaking the entire + load flow. + """ WAVEFORM = b"w" SYMBOLIC_PULSE = b"s" @@ -353,92 +211,27 @@ class ScheduleOperand(TypeKeyBase): OPERAND_STR = b"o" @classmethod - def assign(cls, obj): - if isinstance(obj, Waveform): - return cls.WAVEFORM - if isinstance(obj, SymbolicPulse): - return cls.SYMBOLIC_PULSE - if isinstance(obj, Channel): - return cls.CHANNEL - if isinstance(obj, str): - return cls.OPERAND_STR - if isinstance(obj, Kernel): - return cls.KERNEL - if isinstance(obj, Discriminator): - return cls.DISCRIMINATOR - - raise exceptions.QpyError( - f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." - ) - - @classmethod - def retrieve(cls, type_key): + def assign(cls, _): raise NotImplementedError - -class ScheduleChannel(TypeKeyBase): - """Type key enum for schedule channel object.""" - - DRIVE = b"d" - CONTROL = b"c" - MEASURE = b"m" - ACQURE = b"a" - MEM_SLOT = b"e" - REG_SLOT = b"r" - - # SnapShot channel is not defined because of its limited usecase. - @classmethod - def assign(cls, obj): - if isinstance(obj, DriveChannel): - return cls.DRIVE - if isinstance(obj, ControlChannel): - return cls.CONTROL - if isinstance(obj, MeasureChannel): - return cls.MEASURE - if isinstance(obj, AcquireChannel): - return cls.ACQURE - if isinstance(obj, MemorySlot): - return cls.MEM_SLOT - if isinstance(obj, RegisterSlot): - return cls.REG_SLOT - - raise exceptions.QpyError( - f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." - ) - - @classmethod - def retrieve(cls, type_key): - if type_key == cls.DRIVE: - return DriveChannel - if type_key == cls.CONTROL: - return ControlChannel - if type_key == cls.MEASURE: - return MeasureChannel - if type_key == cls.ACQURE: - return AcquireChannel - if type_key == cls.MEM_SLOT: - return MemorySlot - if type_key == cls.REG_SLOT: - return RegisterSlot - - raise exceptions.QpyError( - f"A class corresponding to type key '{type_key}' is not found in {cls.__name__} namespace." - ) + def retrieve(cls, _): + raise NotImplementedError class Program(TypeKeyBase): - """Typle key enum for program that QPY supports.""" + """Type key enum for program that QPY supports.""" CIRCUIT = b"q" + # This is left for backward compatibility, for identifying payloads of type `ScheduleBlock` + # and raising accordingly. `ScheduleBlock` support has been removed in Qiskit 2.0 as part + # of the pulse package removal in that version. SCHEDULE_BLOCK = b"s" @classmethod def assign(cls, obj): if isinstance(obj, QuantumCircuit): return cls.CIRCUIT - if isinstance(obj, ScheduleBlock): # TODO: remove this path - return cls.SCHEDULE_BLOCK raise exceptions.QpyError( f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." diff --git a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml new file mode 100644 index 000000000000..499ab1b21a0c --- /dev/null +++ b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml @@ -0,0 +1,11 @@ +--- +upgrade_qpy: + - | + With the removal of Pulse in Qiskit 2.0, support for serializing ``ScheduleBlock` programs + via the :func:`qiskit.qpy.dump` function has been removed. Furthermore, in order to keep + backward compatibility, users can still load payloads containing pulse data (i.e. either + `ScheduleBlock`s or containing pulse gates) using the :func:`qiskit.qpy.load` function. + However, pulse data is ignore, resulting with potentially partially specified circuits. + In particular, loading a ``ScheduleBlock`` payload will result with a circuit having only + a name and metadata. Loading a :class:`~QuantumCircuit` payload with pulse gates will + result with a circuit containing undefined custom instructions. diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 962dd22ac79b..926fbf06d01b 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -21,7 +21,7 @@ import ddt import numpy as np -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, pulse +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.circuit import CASE_DEFAULT, IfElseOp, WhileLoopOp, SwitchCaseOp from qiskit.circuit.classical import expr, types from qiskit.circuit.classicalregister import Clbit @@ -331,44 +331,6 @@ def test_bound_parameter(self): self.assertEqual(qc, new_circ) self.assertDeprecatedBitProperties(qc, new_circ) - def test_bound_calibration_parameter(self): - """Test a circuit with a bound calibration parameter is correctly serialized. - - In particular, this test ensures that parameters on a circuit - instruction are consistent with the circuit's calibrations dictionary - after serialization. - """ - amp = Parameter("amp") - - with self.assertWarns(DeprecationWarning): - with pulse.builder.build() as sched: - pulse.builder.play(pulse.Constant(100, amp), pulse.DriveChannel(0)) - - gate = Gate("custom", 1, [amp]) - - qc = QuantumCircuit(1) - qc.append(gate, (0,)) - with self.assertWarns(DeprecationWarning): - qc.add_calibration(gate, (0,), sched) - qc.assign_parameters({amp: 1 / 3}, inplace=True) - - qpy_file = io.BytesIO() - with self.assertWarns(DeprecationWarning): - # qpy.dump warns for deprecations of pulse gate serialization - dump(qc, qpy_file) - qpy_file.seek(0) - new_circ = load(qpy_file)[0] - self.assertEqual(qc, new_circ) - instruction = new_circ.data[0] - cal_key = ( - tuple(new_circ.find_bit(q).index for q in instruction.qubits), - tuple(instruction.operation.params), - ) - # Make sure that looking for a calibration based on the instruction's - # parameters succeeds - with self.assertWarns(DeprecationWarning): - self.assertIn(cal_key, new_circ.calibrations[gate.name]) - def test_parameter_expression(self): """Test a circuit with a parameter expression.""" theta = Parameter("theta") diff --git a/test/python/qpy/test_circuit_load_from_qpy.py b/test/python/qpy/test_circuit_load_from_qpy.py index c95e54857759..a2d83d755f85 100644 --- a/test/python/qpy/test_circuit_load_from_qpy.py +++ b/test/python/qpy/test_circuit_load_from_qpy.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Test cases for the schedule block qpy loading and saving.""" +"""Test cases for circuit qpy loading and saving.""" import io import struct @@ -30,7 +30,7 @@ class QpyCircuitTestCase(QiskitTestCase): - """QPY schedule testing platform.""" + """QPY circuit testing platform.""" def assert_roundtrip_equal(self, circuit, version=None, use_symengine=None): """QPY roundtrip equal test.""" From ddb4f5683a26b5a9c7cd5009b263bff4f8a0f7d3 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 9 Feb 2025 18:10:34 +0200 Subject: [PATCH 03/18] Limit QPY version when generating circuits for compatibility test Fix some doc issues --- qiskit/qpy/__init__.py | 2 +- qiskit/qpy/binary_io/schedules.py | 1 - releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml | 6 +++--- test/qpy_compat/test_qpy.py | 7 ++++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index 2b3716136bb2..c5fa51191dc3 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -169,7 +169,7 @@ def open(*args): it to QPY setting ``use_symengine=False``. The resulting file can then be loaded by any later version of Qiskit. - With the removal of Pulse in Qiskit 2.0, QPY does not support loading ``ScheduleBlock` programs + With the removal of Pulse in Qiskit 2.0, QPY does not support loading ``ScheduleBlock`` programs or pulse gates. If such payloads are being loaded, QPY will issue a warning and return partial circuits. In the case of a ``ScheduleBlock`` payload, a circuit with only a name and metadata will be loaded. It the case of pulse gates, the circuit will contain custom diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index f9f43e45139e..ddfdbad42b03 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -16,7 +16,6 @@ payloads containing pulse gates without breaking the load flow. The purpose of the `_read` and `_load` methods below is just to advance the file handle while consuming pulse data.""" -from curses import meta import json import struct import zlib diff --git a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml index 499ab1b21a0c..09e6c651420e 100644 --- a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml +++ b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml @@ -1,11 +1,11 @@ --- upgrade_qpy: - | - With the removal of Pulse in Qiskit 2.0, support for serializing ``ScheduleBlock` programs + With the removal of Pulse in Qiskit 2.0, support for serializing ``ScheduleBlock`` programs via the :func:`qiskit.qpy.dump` function has been removed. Furthermore, in order to keep backward compatibility, users can still load payloads containing pulse data (i.e. either - `ScheduleBlock`s or containing pulse gates) using the :func:`qiskit.qpy.load` function. + ``ScheduleBlock`` s or containing pulse gates) using the :func:`qiskit.qpy.load` function. However, pulse data is ignore, resulting with potentially partially specified circuits. In particular, loading a ``ScheduleBlock`` payload will result with a circuit having only - a name and metadata. Loading a :class:`~QuantumCircuit` payload with pulse gates will + a name and metadata. Loading a :class:`~.QuantumCircuit` payload with pulse gates will result with a circuit containing undefined custom instructions. diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index cc70cccf7d4d..221bb4a7e6af 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -848,18 +848,19 @@ def generate_circuits(version_parts): ] if version_parts >= (0, 19, 2): output_circuits["control_flow.qpy"] = generate_control_flow_circuits() - if version_parts >= (0, 21, 0): + if version_parts >= (0, 21, 0) and version_parts < (2, 0, 0): output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks() output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits() - if version_parts >= (0, 24, 0): + if version_parts >= (0, 24, 0) and version_parts < (2, 0, 0): output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule() + if version_parts >= (0, 24, 0): output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits() if version_parts >= (0, 24, 1): output_circuits["open_controlled_gates.qpy"] = generate_open_controlled_gates() output_circuits["controlled_gates.qpy"] = generate_controlled_gates() if version_parts >= (0, 24, 2): output_circuits["layout.qpy"] = generate_layout_circuits() - if version_parts >= (0, 25, 0): + if version_parts >= (0, 25, 0) and version_parts < (2, 0, 0): output_circuits["acquire_inst_with_kernel_and_disc.qpy"] = ( generate_acquire_instruction_with_kernel_and_discriminator() ) From 06a855193392fd61aa5216d673937c81af5c0ab8 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Tue, 11 Feb 2025 11:50:18 +0200 Subject: [PATCH 04/18] Handle QPY compatibility testing. Misc other fixes --- qiskit/qpy/binary_io/circuits.py | 2 +- qiskit/qpy/binary_io/schedules.py | 10 +++++----- .../remove-pulse-qpy-07a96673c8f10e38.yaml | 2 +- test/qpy_compat/test_qpy.py | 18 +++++++++++++++--- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index 92e6e7d55c14..ee284233a41d 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -639,7 +639,7 @@ def _read_custom_operations(file_obj, version, vectors): def _read_calibrations(file_obj, version, vectors, metadata_deserializer): - # TODO: document the purpose of this function + """Consume calibrations data, make the file handle point to the next section""" header = formats.CALIBRATION._make( struct.unpack(formats.CALIBRATION_PACK, file_obj.read(formats.CALIBRATION_SIZE)) ) diff --git a/qiskit/qpy/binary_io/schedules.py b/qiskit/qpy/binary_io/schedules.py index ddfdbad42b03..811197ad16a1 100644 --- a/qiskit/qpy/binary_io/schedules.py +++ b/qiskit/qpy/binary_io/schedules.py @@ -143,12 +143,11 @@ def _read_symbolic_pulse(file_obj, version) -> None: value.read_value(file_obj, version, {}) # read duration value.read_value(file_obj, version, {}) # read name - if class_name not in ("SymbolicPulse", "ScalableSymbolicPulse"): + if class_name not in {"SymbolicPulse", "ScalableSymbolicPulse"}: raise NotImplementedError(f"Unknown class '{class_name}'") def _read_symbolic_pulse_v6(file_obj, version, use_symengine) -> None: - # TODO: document purpose make = formats.SYMBOLIC_PULSE_V2._make pack = formats.SYMBOLIC_PULSE_PACK_V2 size = formats.SYMBOLIC_PULSE_SIZE_V2 @@ -177,7 +176,7 @@ def _read_symbolic_pulse_v6(file_obj, version, use_symengine) -> None: value.read_value(file_obj, version, {}) # read duration value.read_value(file_obj, version, {}) # read name - if class_name not in ("SymbolicPulse", "ScalableSymbolicPulse"): + if class_name not in {"SymbolicPulse", "ScalableSymbolicPulse"}: raise NotImplementedError(f"Unknown class '{class_name}'") @@ -194,7 +193,6 @@ def _read_alignment_context(file_obj, version) -> None: # pylint: disable=too-many-return-statements def _loads_operand(type_key, data_bytes, version, use_symengine): - # TODO: document purpose ADD NONE TO ALL THE DUMMY READERS if type_key == type_keys.ScheduleOperand.WAVEFORM: return common.data_from_binary(data_bytes, _read_waveform, version=version) if type_key == type_keys.ScheduleOperand.SYMBOLIC_PULSE: @@ -228,7 +226,7 @@ def _read_element(file_obj, version, metadata_deserializer, use_symengine) -> No type_key = common.read_type_key(file_obj) if type_key == type_keys.Program.SCHEDULE_BLOCK: - read_schedule_block(file_obj, version, metadata_deserializer, use_symengine) + return read_schedule_block(file_obj, version, metadata_deserializer, use_symengine) # read operands common.read_sequence( @@ -237,6 +235,8 @@ def _read_element(file_obj, version, metadata_deserializer, use_symengine) -> No # read name value.read_value(file_obj, version, {}) + return None + def _loads_reference_item(type_key, data_bytes, metadata_deserializer, version) -> None: if type_key == type_keys.Value.NULL: diff --git a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml index 09e6c651420e..f393e7ccfd20 100644 --- a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml +++ b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml @@ -5,7 +5,7 @@ upgrade_qpy: via the :func:`qiskit.qpy.dump` function has been removed. Furthermore, in order to keep backward compatibility, users can still load payloads containing pulse data (i.e. either ``ScheduleBlock`` s or containing pulse gates) using the :func:`qiskit.qpy.load` function. - However, pulse data is ignore, resulting with potentially partially specified circuits. + However, pulse data is ignored, resulting with potentially partially specified circuits. In particular, loading a ``ScheduleBlock`` payload will result with a circuit having only a name and metadata. Loading a :class:`~.QuantumCircuit` payload with pulse gates will result with a circuit containing undefined custom instructions. diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 221bb4a7e6af..0ba8e22d7690 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -848,10 +848,10 @@ def generate_circuits(version_parts): ] if version_parts >= (0, 19, 2): output_circuits["control_flow.qpy"] = generate_control_flow_circuits() - if version_parts >= (0, 21, 0) and version_parts < (2, 0, 0): + if version_parts >= (0, 21, 0) and version_parts < (2, 0): output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks() output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits() - if version_parts >= (0, 24, 0) and version_parts < (2, 0, 0): + if version_parts >= (0, 24, 0) and version_parts < (2, 0): output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule() if version_parts >= (0, 24, 0): output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits() @@ -860,7 +860,7 @@ def generate_circuits(version_parts): output_circuits["controlled_gates.qpy"] = generate_controlled_gates() if version_parts >= (0, 24, 2): output_circuits["layout.qpy"] = generate_layout_circuits() - if version_parts >= (0, 25, 0) and version_parts < (2, 0, 0): + if version_parts >= (0, 25, 0) and version_parts < (2, 0): output_circuits["acquire_inst_with_kernel_and_disc.qpy"] = ( generate_acquire_instruction_with_kernel_and_discriminator() ) @@ -951,11 +951,23 @@ def generate_qpy(qpy_files): def load_qpy(qpy_files, version_parts): """Load qpy circuits from files and compare to reference circuits.""" + pulse_files = { + "schedule_blocks.qpy", + "pulse_gates.qpy", + "referenced_schedule_blocks.qpy", + "acquire_inst_with_kernel_and_disc.qpy", + } for path, circuits in qpy_files.items(): print(f"Loading qpy file: {path}") with open(path, "rb") as fd: qpy_circuits = load(fd) equivalent = path in {"open_controlled_gates.qpy", "controlled_gates.qpy"} + if path in pulse_files: + # Qiskit Pulse was removed in version 2.0. We want to be able to load + # pulse-based payloads, however these will be partially specified hence + # we should not compare them to the cached circuits. + # See https://github.com/Qiskit/qiskit/pull/13814 + continue for i, circuit in enumerate(circuits): bind = None if path == "parameterized.qpy": From 9ea2fd3ae84fd3ce5a05d8f936d64aed3fa73044 Mon Sep 17 00:00:00 2001 From: Eli Arbel <46826214+eliarbel@users.noreply.github.com> Date: Wed, 12 Feb 2025 10:48:17 +0200 Subject: [PATCH 05/18] Update qiskit/qpy/binary_io/circuits.py Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> --- qiskit/qpy/binary_io/circuits.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index ee284233a41d..ed1b58d64db7 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -651,7 +651,7 @@ def _read_calibrations(file_obj, version, vectors, metadata_deserializer): if name: warnings.warn( category=exceptions.QPYLoadingDeprecatedFeatureWarning, - message="Support for loading dulse gates has been removed in Qiskit 2.0. " + message="Support for loading pulse gates has been removed in Qiskit 2.0. " f"If `{name}` is in the circuit, it will be left as a custom instruction" " without definition.", ) From 27ffb5191b2caf80dd63c86c2fa0cac4296b3fb9 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Wed, 12 Feb 2025 12:06:31 +0200 Subject: [PATCH 06/18] Remove pulse from GenericBackendV2 This commit removes pulse-related functionality from GenericBackendV2, as part of Pulse removal in Qiskit 2.0. This includes the ability to initialize the backend with custom calibrations and query it for channel information. Also, various clean ups where made to accommodate for the updated API of GenericBackendV2. --- .../fake_provider/generic_backend_v2.py | 708 +----------------- qiskit/pulse/builder.py | 78 +- ...se-generic-backendv2-738ad9f7ab64b8fd.yaml | 6 + test/python/circuit/test_scheduled_circuit.py | 22 +- test/python/compiler/test_transpiler.py | 117 +-- .../python/primitives/test_backend_sampler.py | 26 +- .../primitives/test_backend_sampler_v2.py | 5 +- .../fake_provider/test_generic_backend_v2.py | 43 +- test/python/providers/test_backend_v2.py | 49 +- test/python/providers/test_pulse_defaults.py | 73 -- test/python/pulse/test_builder_v2.py | 324 -------- test/python/pulse/test_macros.py | 256 ------- .../transpiler/test_instruction_durations.py | 3 +- .../transpiler/test_passmanager_config.py | 14 +- test/python/transpiler/test_sabre_swap.py | 18 +- .../python/transpiler/test_stochastic_swap.py | 9 +- test/python/transpiler/test_target.py | 285 ------- test/python/transpiler/test_vf2_layout.py | 3 +- 18 files changed, 94 insertions(+), 1945 deletions(-) create mode 100644 releasenotes/notes/remove-pulse-generic-backendv2-738ad9f7ab64b8fd.yaml delete mode 100644 test/python/providers/test_pulse_defaults.py delete mode 100644 test/python/pulse/test_builder_v2.py delete mode 100644 test/python/pulse/test_macros.py diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index afb2c6b2bf82..0891302f1528 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -15,12 +15,8 @@ from __future__ import annotations import warnings -from collections.abc import Iterable -from typing import List, Dict, Any, Union import numpy as np -from qiskit import pulse -from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap from qiskit.circuit import QuantumCircuit, Instruction from qiskit.circuit.controlflow import ( IfElseOp, @@ -37,12 +33,6 @@ from qiskit.providers.basic_provider import BasicSimulator from qiskit.providers.backend import BackendV2 from qiskit.utils import optionals as _optionals -from qiskit.providers.models.pulsedefaults import Command -from qiskit.qobj.converters.pulse_instruction import QobjToInstructionConverter -from qiskit.pulse.calibration_entries import PulseQobjDef -from qiskit.providers.models.pulsedefaults import MeasurementKernel, Discriminator -from qiskit.qobj.pulse_qobj import QobjMeasurementOption -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency, deprecate_pulse_arg # Noise default values/ranges for duration and error of supported # instructions. There are two possible formats: @@ -77,440 +67,13 @@ } -class PulseDefaults: - """Internal - Description of default settings for Pulse systems. These are instructions - or settings that - may be good starting points for the Pulse user. The user may modify these defaults for custom - scheduling. - """ - - # Copy from the deprecated from qiskit.providers.models.pulsedefaults.PulseDefaults - - _data = {} - - def __init__( - self, - qubit_freq_est: List[float], - meas_freq_est: List[float], - buffer: int, - pulse_library: List[PulseLibraryItem], - cmd_def: List[Command], - meas_kernel: MeasurementKernel = None, - discriminator: Discriminator = None, - **kwargs: Dict[str, Any], - ): - """ - Validate and reformat transport layer inputs to initialize. - Args: - qubit_freq_est: Estimated qubit frequencies in GHz. - meas_freq_est: Estimated measurement cavity frequencies in GHz. - buffer: Default buffer time (in units of dt) between pulses. - pulse_library: Pulse name and sample definitions. - cmd_def: Operation name and definition in terms of Commands. - meas_kernel: The measurement kernels - discriminator: The discriminators - **kwargs: Other attributes for the super class. - """ - self._data = {} - self.buffer = buffer - self.qubit_freq_est = [freq * 1e9 for freq in qubit_freq_est] - """Qubit frequencies in Hertz.""" - self.meas_freq_est = [freq * 1e9 for freq in meas_freq_est] - """Measurement frequencies in Hertz.""" - self.pulse_library = pulse_library - self.cmd_def = cmd_def - self.instruction_schedule_map = InstructionScheduleMap() - self.converter = QobjToInstructionConverter(pulse_library) - - for inst in cmd_def: - entry = PulseQobjDef(converter=self.converter, name=inst.name) - entry.define(inst.sequence, user_provided=False) - self.instruction_schedule_map._add( - instruction_name=inst.name, - qubits=tuple(inst.qubits), - entry=entry, - ) - - if meas_kernel is not None: - self.meas_kernel = meas_kernel - if discriminator is not None: - self.discriminator = discriminator - - self._data.update(kwargs) - - def __getattr__(self, name): - try: - return self._data[name] - except KeyError as ex: - raise AttributeError(f"Attribute {name} is not defined") from ex - - def to_dict(self): - """Return a dictionary format representation of the PulseDefaults. - Returns: - dict: The dictionary form of the PulseDefaults. - """ - out_dict = { - "qubit_freq_est": self.qubit_freq_est, - "meas_freq_est": self.qubit_freq_est, - "buffer": self.buffer, - "pulse_library": [x.to_dict() for x in self.pulse_library], - "cmd_def": [x.to_dict() for x in self.cmd_def], - } - if hasattr(self, "meas_kernel"): - out_dict["meas_kernel"] = self.meas_kernel.to_dict() - if hasattr(self, "discriminator"): - out_dict["discriminator"] = self.discriminator.to_dict() - for key, value in self.__dict__.items(): - if key not in [ - "qubit_freq_est", - "meas_freq_est", - "buffer", - "pulse_library", - "cmd_def", - "meas_kernel", - "discriminator", - "converter", - "instruction_schedule_map", - ]: - out_dict[key] = value - out_dict.update(self._data) - - out_dict["qubit_freq_est"] = [freq * 1e-9 for freq in self.qubit_freq_est] - out_dict["meas_freq_est"] = [freq * 1e-9 for freq in self.meas_freq_est] - return out_dict - - @classmethod - def from_dict(cls, data): - """Create a new PulseDefaults object from a dictionary. - - Args: - data (dict): A dictionary representing the PulseDefaults - to create. It will be in the same format as output by - :meth:`to_dict`. - Returns: - PulseDefaults: The PulseDefaults from the input dictionary. - """ - schema = { - "pulse_library": PulseLibraryItem, # The class PulseLibraryItem is deprecated - "cmd_def": Command, - "meas_kernel": MeasurementKernel, - "discriminator": Discriminator, - } - - # Pulse defaults data is nested dictionary. - # To avoid deepcopy and avoid mutating the source object, create new dict here. - in_data = {} - for key, value in data.items(): - if key in schema: - with warnings.catch_warnings(): - # The class PulseLibraryItem is deprecated - warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit") - if isinstance(value, list): - in_data[key] = list(map(schema[key].from_dict, value)) - else: - in_data[key] = schema[key].from_dict(value) - else: - in_data[key] = value - - return cls(**in_data) - - def __str__(self): - qubit_freqs = [freq / 1e9 for freq in self.qubit_freq_est] - meas_freqs = [freq / 1e9 for freq in self.meas_freq_est] - qfreq = f"Qubit Frequencies [GHz]\n{qubit_freqs}" - mfreq = f"Measurement Frequencies [GHz]\n{meas_freqs} " - return f"<{self.__class__.__name__}({str(self.instruction_schedule_map)}{qfreq}\n{mfreq})>" - - -def _to_complex(value: Union[List[float], complex]) -> complex: - """Convert the input value to type ``complex``. - Args: - value: Value to be converted. - Returns: - Input value in ``complex``. - Raises: - TypeError: If the input value is not in the expected format. - """ - if isinstance(value, list) and len(value) == 2: - return complex(value[0], value[1]) - elif isinstance(value, complex): - return value - - raise TypeError(f"{value} is not in a valid complex number format.") - - -class PulseLibraryItem: - """INTERNAL - An item in a pulse library.""" - - # Copy from the deprecated from qiskit.qobj.PulseLibraryItem - def __init__(self, name, samples): - """Instantiate a pulse library item. - - Args: - name (str): A name for the pulse. - samples (list[complex]): A list of complex values defining pulse - shape. - """ - self.name = name - if isinstance(samples[0], list): - self.samples = np.array([complex(sample[0], sample[1]) for sample in samples]) - else: - self.samples = samples - - def to_dict(self): - """Return a dictionary format representation of the pulse library item. - - Returns: - dict: The dictionary form of the PulseLibraryItem. - """ - return {"name": self.name, "samples": self.samples} - - @classmethod - def from_dict(cls, data): - """Create a new PulseLibraryItem object from a dictionary. - - Args: - data (dict): A dictionary for the experiment config - - Returns: - PulseLibraryItem: The object from the input dictionary. - """ - return cls(**data) - - def __repr__(self): - return f"PulseLibraryItem({self.name}, {repr(self.samples)})" - - def __str__(self): - return f"Pulse Library Item:\n\tname: {self.name}\n\tsamples: {self.samples}" - - def __eq__(self, other): - if isinstance(other, PulseLibraryItem): - if self.to_dict() == other.to_dict(): - return True - return False - - -class PulseQobjInstruction: - """Internal - A class representing a single instruction in a PulseQobj Experiment.""" - - # Copy from the deprecated from qiskit.qobj.PulseQobjInstruction - - _COMMON_ATTRS = [ - "ch", - "conditional", - "val", - "phase", - "frequency", - "duration", - "qubits", - "memory_slot", - "register_slot", - "label", - "type", - "pulse_shape", - "parameters", - ] - - def __init__( - self, - name, - t0, - ch=None, - conditional=None, - val=None, - phase=None, - duration=None, - qubits=None, - memory_slot=None, - register_slot=None, - kernels=None, - discriminators=None, - label=None, - type=None, # pylint: disable=invalid-name,redefined-builtin - pulse_shape=None, - parameters=None, - frequency=None, - ): - """Instantiate a new PulseQobjInstruction object. - - Args: - name (str): The name of the instruction - t0 (int): Pulse start time in integer **dt** units. - ch (str): The channel to apply the pulse instruction. - conditional (int): The register to use for a conditional for this - instruction - val (complex): Complex value to apply, bounded by an absolute value - of 1. - phase (float): if a ``fc`` instruction, the frame change phase in - radians. - frequency (float): if a ``sf`` instruction, the frequency in Hz. - duration (int): The duration of the pulse in **dt** units. - qubits (list): A list of ``int`` representing the qubits the - instruction operates on - memory_slot (list): If a ``measure`` instruction this is a list - of ``int`` containing the list of memory slots to store the - measurement results in (must be the same length as qubits). - If a ``bfunc`` instruction this is a single ``int`` of the - memory slot to store the boolean function result in. - register_slot (list): If a ``measure`` instruction this is a list - of ``int`` containing the list of register slots in which to - store the measurement results (must be the same length as - qubits). If a ``bfunc`` instruction this is a single ``int`` - of the register slot in which to store the result. - kernels (list): List of :class:`QobjMeasurementOption` objects - defining the measurement kernels and set of parameters if the - measurement level is 1 or 2. Only used for ``acquire`` - instructions. - discriminators (list): A list of :class:`QobjMeasurementOption` - used to set the discriminators to be used if the measurement - level is 2. Only used for ``acquire`` instructions. - label (str): Label of instruction - type (str): Type of instruction - pulse_shape (str): The shape of the parametric pulse - parameters (dict): The parameters for a parametric pulse - """ - self.name = name - self.t0 = t0 - if ch is not None: - self.ch = ch - if conditional is not None: - self.conditional = conditional - if val is not None: - self.val = val - if phase is not None: - self.phase = phase - if frequency is not None: - self.frequency = frequency - if duration is not None: - self.duration = duration - if qubits is not None: - self.qubits = qubits - if memory_slot is not None: - self.memory_slot = memory_slot - if register_slot is not None: - self.register_slot = register_slot - if kernels is not None: - self.kernels = kernels - if discriminators is not None: - self.discriminators = discriminators - if label is not None: - self.label = label - if type is not None: - self.type = type - if pulse_shape is not None: - self.pulse_shape = pulse_shape - if parameters is not None: - self.parameters = parameters - - def to_dict(self): - """Return a dictionary format representation of the Instruction. - - Returns: - dict: The dictionary form of the PulseQobjInstruction. - """ - out_dict = {"name": self.name, "t0": self.t0} - for attr in self._COMMON_ATTRS: - if hasattr(self, attr): - out_dict[attr] = getattr(self, attr) - if hasattr(self, "kernels"): - out_dict["kernels"] = [x.to_dict() for x in self.kernels] - if hasattr(self, "discriminators"): - out_dict["discriminators"] = [x.to_dict() for x in self.discriminators] - return out_dict - - def __repr__(self): - out = f'PulseQobjInstruction(name="{self.name}", t0={self.t0}' - for attr in self._COMMON_ATTRS: - attr_val = getattr(self, attr, None) - if attr_val is not None: - if isinstance(attr_val, str): - out += f', {attr}="{attr_val}"' - else: - out += f", {attr}={attr_val}" - out += ")" - return out - - def __str__(self): - out = f"Instruction: {self.name}\n" - out += f"\t\tt0: {self.t0}\n" - for attr in self._COMMON_ATTRS: - if hasattr(self, attr): - out += f"\t\t{attr}: {getattr(self, attr)}\n" - return out - - @classmethod - def from_dict(cls, data): - """Create a new PulseQobjExperimentConfig object from a dictionary. - - Args: - data (dict): A dictionary for the experiment config - - Returns: - PulseQobjInstruction: The object from the input dictionary. - """ - schema = { - "discriminators": QobjMeasurementOption, - "kernels": QobjMeasurementOption, - } - skip = ["t0", "name"] - - # Pulse instruction data is nested dictionary. - # To avoid deepcopy and avoid mutating the source object, create new dict here. - in_data = {} - for key, value in data.items(): - if key in skip: - continue - if key == "parameters": - # This is flat dictionary of parametric pulse parameters - formatted_value = value.copy() - if "amp" in formatted_value: - formatted_value["amp"] = _to_complex(formatted_value["amp"]) - in_data[key] = formatted_value - continue - if key in schema: - if isinstance(value, list): - in_data[key] = list(map(schema[key].from_dict, value)) - else: - in_data[key] = schema[key].from_dict(value) - else: - in_data[key] = value - - return cls(data["name"], data["t0"], **in_data) - - def __eq__(self, other): - if isinstance(other, PulseQobjInstruction): - if self.to_dict() == other.to_dict(): - return True - return False - - -def _pulse_library(): - # The number of samples determines the pulse durations of the corresponding - # instructions. This default defines pulses with durations in multiples of - # 16 dt for consistency with the pulse granularity of real IBM devices, but - # keeps the number smaller than what would be realistic for - # manageability. If needed, more realistic durations could be added in the - # future (order of 160dt for 1q gates, 1760dt for 2q gates and measure). - return [ - PulseLibraryItem( - name="pulse_1", samples=np.linspace(0, 1.0, 16, dtype=np.complex128) - ), # 16dt - PulseLibraryItem( - name="pulse_2", samples=np.linspace(0, 1.0, 32, dtype=np.complex128) - ), # 32dt - PulseLibraryItem( - name="pulse_3", samples=np.linspace(0, 1.0, 64, dtype=np.complex128) - ), # 64dt - ] - - class GenericBackendV2(BackendV2): """Generic :class:`~.BackendV2` implementation with a configurable constructor. This class will return a :class:`~.BackendV2` instance that runs on a local simulator (in the spirit of fake backends) and contains all the necessary information to test backend-interfacing components, such as the transpiler. A :class:`.GenericBackendV2` instance can be constructed from as little as a specified ``num_qubits``, but users can additionally configure the basis gates, coupling map, - ability to run dynamic circuits (control flow instructions), instruction calibrations and dtm. + ability to run dynamic circuits (control flow instructions) and dtm. The remainder of the backend properties are generated by randomly sampling from default ranges extracted from historical IBM backend data. The seed for this random generation can be fixed to ensure the reproducibility of the backend output. @@ -519,8 +82,6 @@ class GenericBackendV2(BackendV2): transpilation. """ - @deprecate_pulse_arg("pulse_channels") - @deprecate_pulse_arg("calibrate_instructions") def __init__( self, num_qubits: int, @@ -528,10 +89,8 @@ def __init__( *, coupling_map: list[list[int]] | CouplingMap | None = None, control_flow: bool = False, - calibrate_instructions: bool | InstructionScheduleMap | None = None, dtm: float | None = None, seed: int | None = None, - pulse_channels: bool = True, noise_info: bool = True, ): """ @@ -563,26 +122,11 @@ def __init__( control_flow: Flag to enable control flow directives on the target (defaults to False). - calibrate_instructions: DEPRECATED. Instruction calibration settings, this argument - supports both boolean and :class:`.InstructionScheduleMap` as - input types, and is ``None`` by default: - - #. If ``calibrate_instructions==None``, no calibrations will be added to the target. - #. If ``calibrate_instructions==True``, all gates will be calibrated for all - qubits using the default pulse schedules generated internally. - #. If ``calibrate_instructions==False``, all gates will be "calibrated" for - all qubits with an empty pulse schedule. - #. If an :class:`.InstructionScheduleMap` instance is given, the calibrations - in this instruction schedule map will be appended to the target - instead of the default pulse schedules (this allows for custom calibrations). - dtm: System time resolution of output signals in nanoseconds. None by default. seed: Optional seed for generation of default values. - pulse_channels: DEPRECATED. If true, sets default pulse channel information on the backend. - noise_info: If true, associates gates and qubits with default noise information. """ @@ -598,13 +142,9 @@ def __init__( self._dtm = dtm self._num_qubits = num_qubits self._control_flow = control_flow - self._calibrate_instructions = calibrate_instructions self._supported_gates = get_standard_gate_name_mapping() self._noise_info = noise_info - if calibrate_instructions and not noise_info: - raise QiskitError("Must set parameter noise_info when calibrating instructions.") - if coupling_map is None: self._coupling_map = CouplingMap().from_full(num_qubits) else: @@ -627,10 +167,6 @@ def __init__( self._basis_gates.append(name) self._build_generic_target() - if pulse_channels: - self._build_default_channels() - else: - self.channels_map = {} @property def target(self): @@ -650,20 +186,6 @@ def dtm(self) -> float: def meas_map(self) -> list[list[int]]: return self._target.concurrent_measurements - def _build_default_channels(self) -> None: - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # Prevent pulse deprecation warnings from being emitted - channels_map = { - "acquire": {(i,): [pulse.AcquireChannel(i)] for i in range(self.num_qubits)}, - "drive": {(i,): [pulse.DriveChannel(i)] for i in range(self.num_qubits)}, - "measure": {(i,): [pulse.MeasureChannel(i)] for i in range(self.num_qubits)}, - "control": { - (edge): [pulse.ControlChannel(i)] for i, edge in enumerate(self._coupling_map) - }, - } - setattr(self, "channels_map", channels_map) - def _get_noise_defaults(self, name: str, num_qubits: int) -> tuple: """Return noise default values/ranges for duration and error of supported instructions. There are two possible formats: @@ -677,110 +199,9 @@ def _get_noise_defaults(self, name: str, num_qubits: int) -> tuple: return _NOISE_DEFAULTS_FALLBACK["1-q"] return _NOISE_DEFAULTS_FALLBACK["multi-q"] - def _get_calibration_sequence( - self, inst: str, num_qubits: int, qargs: tuple[int] - ) -> list[PulseQobjInstruction]: - """Return calibration pulse sequence for given instruction (defined by name and num_qubits) - acting on qargs. - """ - - pulse_library = _pulse_library() - # Note that the calibration pulses are different for - # 1q gates vs 2q gates vs measurement instructions. - if inst == "measure": - with warnings.catch_warnings(): - # The class PulseQobjInstruction is deprecated - warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit") - sequence = [ - PulseQobjInstruction( - name="acquire", - duration=1792, - t0=0, - qubits=qargs, - memory_slot=qargs, - ) - ] + [ - PulseQobjInstruction(name=pulse_library[1].name, ch=f"m{i}", t0=0) - for i in qargs - ] - return sequence - with warnings.catch_warnings(): - # The class PulseQobjInstruction is deprecated - warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit") - if num_qubits == 1: - return [ - PulseQobjInstruction(name="fc", ch=f"u{qargs[0]}", t0=0, phase="-P0"), - PulseQobjInstruction(name=pulse_library[0].name, ch=f"d{qargs[0]}", t0=0), - ] - return [ - PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[0]}", t0=0), - PulseQobjInstruction(name=pulse_library[2].name, ch=f"u{qargs[0]}", t0=0), - PulseQobjInstruction(name=pulse_library[1].name, ch=f"d{qargs[1]}", t0=0), - PulseQobjInstruction(name="fc", ch=f"d{qargs[1]}", t0=0, phase=2.1), - ] - - def _generate_calibration_defaults(self) -> PulseDefaults: - """Generate pulse calibration defaults as specified with `self._calibrate_instructions`. - If `self._calibrate_instructions` is True, the pulse schedules will be generated from - a series of default calibration sequences. If `self._calibrate_instructions` is False, - the pulse schedules will contain empty calibration sequences, but still be generated and - added to the target. - """ - - # If self._calibrate_instructions==True, this method - # will generate default pulse schedules for all gates in self._basis_gates, - # except for `delay` and `reset`. - calibration_buffer = self._basis_gates.copy() - for inst in ["delay", "reset"]: - calibration_buffer.remove(inst) - - # List of calibration commands (generated from sequences of PulseQobjInstructions) - # corresponding to each calibrated instruction. Note that the calibration pulses - # are different for 1q gates vs 2q gates vs measurement instructions. - cmd_def = [] - for inst in calibration_buffer: - num_qubits = self._supported_gates[inst].num_qubits - qarg_set = self._coupling_map if num_qubits > 1 else list(range(self.num_qubits)) - if inst == "measure": - cmd_def.append( - Command( - name=inst, - qubits=qarg_set, - sequence=( - self._get_calibration_sequence(inst, num_qubits, qarg_set) - if self._calibrate_instructions - else [] - ), - ) - ) - else: - for qarg in qarg_set: - qubits = [qarg] if num_qubits == 1 else qarg - cmd_def.append( - Command( - name=inst, - qubits=qubits, - sequence=( - self._get_calibration_sequence(inst, num_qubits, qubits) - if self._calibrate_instructions - else [] - ), - ) - ) - - qubit_freq_est = np.random.normal(4.8, scale=0.01, size=self.num_qubits).tolist() - meas_freq_est = np.linspace(6.4, 6.6, self.num_qubits).tolist() - return PulseDefaults( - qubit_freq_est=qubit_freq_est, - meas_freq_est=meas_freq_est, - buffer=0, - pulse_library=_pulse_library(), - cmd_def=cmd_def, - ) - def _build_generic_target(self): """This method generates a :class:`~.Target` instance with - default qubit, instruction and calibration properties. + default qubit and instruction properties. """ # the qubit properties are sampled from default ranges properties = _QUBIT_PROPERTIES @@ -810,17 +231,8 @@ def _build_generic_target(self): concurrent_measurements=[list(range(self._num_qubits))], ) - # Generate instruction schedule map with calibrations to add to target. - calibration_inst_map = None - if self._calibrate_instructions is not None: - if isinstance(self._calibrate_instructions, InstructionScheduleMap): - calibration_inst_map = self._calibrate_instructions - else: - defaults = self._generate_calibration_defaults() - calibration_inst_map = defaults.instruction_schedule_map - # Iterate over gates, generate noise params from defaults, - # and add instructions, noise and calibrations to target. + # and add instructions and noise information to the target. for name in self._basis_gates: if name not in self._supported_gates: raise QiskitError( @@ -835,7 +247,7 @@ def _build_generic_target(self): ) if self._noise_info: noise_params = self._get_noise_defaults(name, gate.num_qubits) - self._add_noisy_instruction_to_target(gate, noise_params, calibration_inst_map) + self._add_noisy_instruction_to_target(gate, noise_params) else: qarg_set = self._coupling_map if gate.num_qubits > 1 else range(self.num_qubits) props = {(qarg,) if isinstance(qarg, int) else qarg: None for qarg in qarg_set} @@ -853,7 +265,6 @@ def _add_noisy_instruction_to_target( self, instruction: Instruction, noise_params: tuple[float, ...] | None, - calibration_inst_map: InstructionScheduleMap | None, ) -> None: """Add instruction properties to target for specified instruction. @@ -861,7 +272,6 @@ def _add_noisy_instruction_to_target( instruction: Instance of instruction to be added to the target noise_params: Error and duration noise values/ranges to include in instruction properties. - calibration_inst_map: Instruction schedule map with calibration defaults """ qarg_set = self._coupling_map if instruction.num_qubits > 1 else range(self.num_qubits) props = {} @@ -878,46 +288,16 @@ def _add_noisy_instruction_to_target( self._rng.uniform(*noise_params[2:]), ) ) - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # Prevent pulse deprecations from being emitted - if ( - calibration_inst_map is not None - and instruction.name not in ["reset", "delay"] - and qarg in calibration_inst_map.qubits_with_instruction(instruction.name) - ): - # Do NOT call .get method. This parses Qobj immediately. - # This operation is computationally expensive and should be bypassed. - calibration_entry = calibration_inst_map._get_calibration_entry( - instruction.name, qargs - ) - else: - calibration_entry = None - if duration is not None and len(noise_params) > 2: - # Ensure exact conversion of duration from seconds to dt - dt = _QUBIT_PROPERTIES["dt"] - rounded_duration = round(duration / dt) * dt - # Clamp rounded duration to be between min and max values - duration = max(noise_params[0], min(rounded_duration, noise_params[1])) - props.update({qargs: InstructionProperties(duration, error, calibration_entry)}) - self._target.add_instruction(instruction, props) - # The "measure" instruction calibrations need to be added qubit by qubit, once the - # instruction has been added to the target. - if calibration_inst_map is not None and instruction.name == "measure": - for qarg in calibration_inst_map.qubits_with_instruction(instruction.name): - try: - qargs = tuple(qarg) - except TypeError: - qargs = (qarg,) - # Do NOT call .get method. This parses Qobj immediately. - # This operation is computationally expensive and should be bypassed. - calibration_entry = calibration_inst_map._get_calibration_entry( - instruction.name, qargs - ) - for qubit in qargs: - if qubit < self.num_qubits: - self._target[instruction.name][(qubit,)].calibration = calibration_entry + if duration is not None and len(noise_params) > 2: + # Ensure exact conversion of duration from seconds to dt + dt = _QUBIT_PROPERTIES["dt"] + rounded_duration = round(duration / dt) * dt + # Clamp rounded duration to be between min and max values + duration = max(noise_params[0], min(rounded_duration, noise_params[1])) + props.update({qargs: InstructionProperties(duration, error, None)}) + + self._target.add_instruction(instruction, props) def run(self, run_input, **options): """Run on the backend using a simulator. @@ -934,11 +314,9 @@ def run(self, run_input, **options): Noisy simulations of pulse jobs are not yet supported in :class:`~.GenericBackendV2`. Args: - run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An - individual or a list of - :class:`~qiskit.circuit.QuantumCircuit`, - :class:`~qiskit.pulse.ScheduleBlock`, or - :class:`~qiskit.pulse.Schedule` objects to run on the backend. + run_input (QuantumCircuit or list): An + individual or a list of :class:`~qiskit.circuit.QuantumCircuit` + objects to run on the backend. options: Any kwarg options to pass to the backend for running the config. If a key is also present in the options attribute/object, then the expectation is that the value @@ -952,25 +330,15 @@ def run(self, run_input, **options): QiskitError: If a pulse job is supplied and qiskit_aer is not installed. """ circuits = run_input - pulse_job = None - if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): - pulse_job = True - elif isinstance(circuits, QuantumCircuit): - pulse_job = False - elif isinstance(circuits, list): - if circuits: - if all(isinstance(x, (pulse.Schedule, pulse.ScheduleBlock)) for x in circuits): - pulse_job = True - elif all(isinstance(x, QuantumCircuit) for x in circuits): - pulse_job = False - if pulse_job is None: # submitted job is invalid + if not isinstance(circuits, QuantumCircuit) and ( + not isinstance(circuits, list) + or not all(isinstance(x, QuantumCircuit) for x in circuits) + ): raise QiskitError( f"Invalid input object {circuits}, must be either a " - "QuantumCircuit, Schedule, or a list of either" + "QuantumCircuit or a list of QuantumCircuit objects" ) - if pulse_job: # pulse job - raise QiskitError("Pulse simulation is currently not supported for V2 backends.") - # circuit job + if not _optionals.HAS_AER: warnings.warn("Aer not found using BasicSimulator and no noise", RuntimeWarning) if self._sim is None: @@ -1001,35 +369,3 @@ def _default_options(cls) -> Options: return AerSimulator._default_options() else: return BasicSimulator._default_options() - - @deprecate_pulse_dependency - def drive_channel(self, qubit: int): - drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) - qubits = (qubit,) - if qubits in drive_channels_map: - return drive_channels_map[qubits][0] - return None - - @deprecate_pulse_dependency - def measure_channel(self, qubit: int): - measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) - qubits = (qubit,) - if qubits in measure_channels_map: - return measure_channels_map[qubits][0] - return None - - @deprecate_pulse_dependency - def acquire_channel(self, qubit: int): - acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) - qubits = (qubit,) - if qubits in acquire_channels_map: - return acquire_channels_map[qubits][0] - return None - - @deprecate_pulse_dependency - def control_channel(self, qubits: Iterable[int]): - control_channels_map = getattr(self, "channels_map", {}).get("control", {}) - qubits = tuple(qubits) - if qubits in control_channels_map: - return control_channels_map[qubits] - return [] diff --git a/qiskit/pulse/builder.py b/qiskit/pulse/builder.py index 70ecc9d11dfa..5d6c5fbd2d41 100644 --- a/qiskit/pulse/builder.py +++ b/qiskit/pulse/builder.py @@ -104,14 +104,12 @@ Methods to return the correct channels for the respective qubit indices. -.. plot:: - :include-source: - :nofigs: +.. code-block:: python from qiskit import pulse from qiskit.providers.fake_provider import GenericBackendV2 - backend = GenericBackendV2(num_qubits=2, calibrate_instructions=True) + backend = GenericBackendV2(num_qubits=2) with pulse.build(backend) as drive_sched: d0 = pulse.drive_channel(0) @@ -132,14 +130,12 @@ Pulse instructions are available within the builder interface. Here's an example: -.. plot:: - :alt: Output from the previous code. - :include-source: +.. code-block:: python from qiskit import pulse from qiskit.providers.fake_provider import GenericBackendV2 - backend = GenericBackendV2(num_qubits=2, calibrate_instructions=True) + backend = GenericBackendV2(num_qubits=2) with pulse.build(backend) as drive_sched: d0 = pulse.drive_channel(0) @@ -181,9 +177,7 @@ example an alignment context like :func:`align_right` may be used to align all pulses as late as possible in a pulse program. -.. plot:: - :alt: Output from the previous code. - :include-source: +.. code-block:: python from qiskit import pulse @@ -213,14 +207,12 @@ Macros help you add more complex functionality to your pulse program. -.. plot:: - :include-source: - :nofigs: +.. code-block:: python from qiskit import pulse from qiskit.providers.fake_provider import GenericBackendV2 - backend = GenericBackendV2(num_qubits=2, calibrate_instructions=True) + backend = GenericBackendV2(num_qubits=2) with pulse.build(backend) as measure_sched: mem_slot = pulse.measure(0) @@ -241,15 +233,13 @@ The utility functions can be used to gather attributes about the backend and modify how the program is built. -.. plot:: - :include-source: - :nofigs: +.. code-block:: python from qiskit import pulse from qiskit.providers.fake_provider import GenericBackendV2 - backend = GenericBackendV2(num_qubits=2, calibrate_instructions=True) + backend = GenericBackendV2(num_qubits=2) with pulse.build(backend) as u3_sched: print('Number of qubits in backend: {}'.format(pulse.num_qubits())) @@ -651,10 +641,7 @@ def build( To enter a building context and starting building a pulse program: - .. plot:: - :include-source: - :nofigs: - :context: reset + .. code-block:: python from qiskit import transpile, pulse from qiskit.providers.fake_provider import FakeOpenPulse2Q @@ -750,9 +737,7 @@ def append_instruction(instruction: instructions.Instruction): Examples: - .. plot:: - :include-source: - :nofigs: + .. code-block:: python from qiskit import pulse @@ -1663,15 +1648,12 @@ def call( 1. Calling a schedule block (recommended) - .. plot:: - :include-source: - :nofigs: - :context: reset + .. code-block:: python from qiskit import circuit, pulse from qiskit.providers.fake_provider import GenericBackendV2 - backend = GenericBackendV2(num_qubits=5, calibrate_instructions=True) + backend = GenericBackendV2(num_qubits=5) with pulse.build() as x_sched: pulse.play(pulse.Gaussian(160, 0.1, 40), pulse.DriveChannel(0)) @@ -1698,10 +1680,7 @@ def call( The actual program is stored in the reference table attached to the schedule. - .. plot:: - :include-source: - :nofigs: - :context: + .. code-block:: python print(pulse_prog.references) @@ -1712,10 +1691,7 @@ def call( In addition, you can call a parameterized target program with parameter assignment. - .. plot:: - :include-source: - :nofigs: - :context: + .. code-block:: python amp = circuit.Parameter("amp") @@ -1754,10 +1730,7 @@ def call( If there is a name collision between parameters, you can distinguish them by specifying each parameter object in a python dictionary. For example, - .. plot:: - :include-source: - :nofigs: - :context: + .. code-block:: python amp1 = circuit.Parameter('amp') amp2 = circuit.Parameter('amp') @@ -1786,10 +1759,7 @@ def call( 2. Calling a schedule - .. plot:: - :include-source: - :nofigs: - :context: + .. code-block:: python x_sched = backend.instruction_schedule_map.get("x", (0,)) @@ -1927,9 +1897,7 @@ def barrier(*channels_or_qubits: chans.Channel | int, name: str | None = None): in the case where we are calling an outside circuit or schedule and want to align a pulse at the end of one call: - .. plot:: - :include-source: - :nofigs: + .. code-block:: python import math from qiskit import pulse @@ -2040,10 +2008,7 @@ def measure( To use the measurement it is as simple as specifying the qubit you wish to measure: - .. plot:: - :include-source: - :nofigs: - :context: reset + .. code-block:: python from qiskit import pulse from qiskit.providers.fake_provider import FakeOpenPulse2Q @@ -2063,10 +2028,7 @@ def measure( future we will support using this handle to a result register to build up ones program. It is also possible to supply this register: - .. plot:: - :include-source: - :nofigs: - :context: + .. code-block:: python with pulse.build(backend) as pulse_prog: pulse.play(pulse.Constant(100, 1.0), qubit_drive_chan) diff --git a/releasenotes/notes/remove-pulse-generic-backendv2-738ad9f7ab64b8fd.yaml b/releasenotes/notes/remove-pulse-generic-backendv2-738ad9f7ab64b8fd.yaml new file mode 100644 index 000000000000..147b785c48b8 --- /dev/null +++ b/releasenotes/notes/remove-pulse-generic-backendv2-738ad9f7ab64b8fd.yaml @@ -0,0 +1,6 @@ +--- +upgrade_providers: + - | + As part of Pulse removal in Qiskit 2.0, pulse support has been removed from + :class:`.GenericBackendV2`. This includes the ability to initialize the backend + with custom calibrations and pulse channels information. diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 45238dc79e74..f72b6d2adb23 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -57,8 +57,7 @@ def test_schedule_circuit_when_backend_tells_dt(self): qc.h(0) # 195[dt] qc.h(1) # 210[dt] - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2(2, calibrate_instructions=True, seed=42) + backend = GenericBackendV2(2, seed=42) sc = transpile(qc, backend, scheduling_method="alap", layout_method="trivial") self.assertEqual(sc.duration, 451095) @@ -382,17 +381,12 @@ def test_per_qubit_durations_loose_constrain(self): def test_per_qubit_durations(self): """Test target with custom instruction_durations""" - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="argument ``calibrate_instructions`` is deprecated", - ): - target = GenericBackendV2( - 3, - calibrate_instructions=True, - coupling_map=[[0, 1], [1, 2]], - basis_gates=["cx", "h"], - seed=42, - ).target + target = GenericBackendV2( + 3, + coupling_map=[[0, 1], [1, 2]], + basis_gates=["cx", "h"], + seed=42, + ).target target.update_instruction_properties("cx", (0, 1), InstructionProperties(0.00001)) target.update_instruction_properties("cx", (1, 2), InstructionProperties(0.00001)) target.update_instruction_properties("h", (0,), InstructionProperties(0.000002)) @@ -435,8 +429,8 @@ def test_convert_duration_to_dt(self): """Test that circuit duration unit conversion is applied only when necessary. Tests fix for bug reported in PR #11782.""" + backend = GenericBackendV2(num_qubits=3, seed=42) with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2(num_qubits=3, calibrate_instructions=True, seed=42) schedule_config = ScheduleConfig( inst_map=backend.target.instruction_schedule_map(), meas_map=backend.meas_map, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index 5c707c00d884..f1f691da5a89 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -19,7 +19,6 @@ import sys from logging import StreamHandler, getLogger from unittest.mock import patch -import warnings import numpy as np import rustworkx as rx from ddt import data, ddt, unpack @@ -82,7 +81,7 @@ from qiskit.pulse import InstructionScheduleMap from qiskit.quantum_info import Operator, random_unitary from qiskit.utils import parallel -from qiskit.transpiler import CouplingMap, Layout, PassManager, TransformationPass +from qiskit.transpiler import CouplingMap, Layout, PassManager from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget from qiskit.transpiler.passes import BarrierBeforeFinalMeasurements, GateDirection, VF2PostLayout @@ -1461,57 +1460,6 @@ def test_inst_durations_from_calibrations(self): with self.assertWarns(DeprecationWarning): self.assertEqual(out.duration, cal.duration) - @data(0, 1, 2, 3) - def test_multiqubit_gates_calibrations(self, opt_level): - """Test multiqubit gate > 2q with calibrations works - - Adapted from issue description in https://github.com/Qiskit/qiskit-terra/issues/6572 - """ - circ = QuantumCircuit(5) - custom_gate = Gate("my_custom_gate", 5, []) - circ.append(custom_gate, [0, 1, 2, 3, 4]) - circ.measure_all() - backend = GenericBackendV2(num_qubits=6) - - with self.assertWarns(DeprecationWarning): - with pulse.build(backend=backend, name="custom") as my_schedule: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(0) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(1) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(2) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(3) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.drive_channel(4) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(1) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(2) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(3) - ) - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.ControlChannel(4) - ) - circ.add_calibration("my_custom_gate", [0, 1, 2, 3, 4], my_schedule, []) - trans_circ = transpile( - circ, - backend=backend, - optimization_level=opt_level, - layout_method="trivial", - seed_transpiler=42, - ) - self.assertEqual({"measure": 5, "my_custom_gate": 1, "barrier": 1}, trans_circ.count_ops()) - @data(0, 1, 2, 3) def test_circuit_with_delay(self, optimization_level): """Verify a circuit with delay can transpile to a scheduled circuit.""" @@ -1609,17 +1557,12 @@ def test_scheduling_instruction_constraints_backend(self): def test_scheduling_instruction_constraints(self): """Test that scheduling-related loose transpile constraints work with target.""" - with self.assertWarnsRegex( - DeprecationWarning, - expected_regex="argument ``calibrate_instructions`` is deprecated", - ): - target = GenericBackendV2( - 2, - calibrate_instructions=True, - coupling_map=[[0, 1]], - basis_gates=["cx", "h"], - seed=42, - ).target + target = GenericBackendV2( + 2, + coupling_map=[[0, 1]], + basis_gates=["cx", "h"], + seed=42, + ).target qc = QuantumCircuit(2) qc.h(0) qc.delay(0.000001, 1, "s") @@ -2790,52 +2733,6 @@ def test_parallel_dispatch(self, opt_level): self.assertTrue(math.isclose(count["00000"], 500, rel_tol=0.1)) self.assertTrue(math.isclose(count["01111"], 500, rel_tol=0.1)) - def test_parallel_dispatch_lazy_cal_loading(self): - """Test adding calibration by lazy loading in parallel environment.""" - - class TestAddCalibration(TransformationPass): - """A fake pass to test lazy pulse qobj loading in parallel environment.""" - - def __init__(self, target): - """Instantiate with target.""" - super().__init__() - self.target = target - - def run(self, dag): - """Run test pass that adds calibration of SX gate of qubit 0.""" - with warnings.catch_warnings(): - warnings.simplefilter("ignore", category=DeprecationWarning) - # DAGCircuit.add_calibration() is deprecated but we can't use self.assertWarns() here - dag.add_calibration( - "sx", - qubits=(0,), - schedule=self.target["sx"][(0,)].calibration, # PulseQobj is parsed here - ) - return dag - - # Create backend with empty calibrations (PulseQobjEntries) - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - num_qubits=4, - calibrate_instructions=False, - ) - - # This target has PulseQobj entries that provide a serialized schedule data - pass_ = TestAddCalibration(backend.target) - pm = PassManager(passes=[pass_]) - self.assertIsNone(backend.target["sx"][(0,)]._calibration._definition) - - qc = QuantumCircuit(1) - qc.sx(0) - qc_copied = [qc for _ in range(10)] - - qcs_cal_added = pm.run(qc_copied) - with self.assertWarns(DeprecationWarning): - ref_cal = backend.target["sx"][(0,)].calibration - for qc_test in qcs_cal_added: - added_cal = qc_test.calibrations["sx"][((0,), ())] - self.assertEqual(added_cal, ref_cal) - @data(0, 1, 2, 3) def test_parallel_singleton_conditional_gate(self, opt_level): """Test that singleton mutable instance doesn't lose state in parallel.""" diff --git a/test/python/primitives/test_backend_sampler.py b/test/python/primitives/test_backend_sampler.py index dc77a3c47ce6..6acfa6443530 100644 --- a/test/python/primitives/test_backend_sampler.py +++ b/test/python/primitives/test_backend_sampler.py @@ -368,10 +368,7 @@ def max_circuits(self): def test_primitive_job_size_limit_backend_v1(self): """Test primitive respects backend's job size limit.""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + backend = GenericBackendV2(7, basis_gates=["cx", "u1", "u2", "u3"], seed=42) qc = QuantumCircuit(1) qc.measure_all() qc2 = QuantumCircuit(1) @@ -410,10 +407,7 @@ def test_circuit_with_dynamic_circuit(self): def test_sequential_run(self): """Test sequential run.""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + backend = GenericBackendV2(7, basis_gates=["cx", "u1", "u2", "u3"], seed=42) qc = QuantumCircuit(1) qc.measure_all() qc2 = QuantumCircuit(1) @@ -461,10 +455,8 @@ def callback(msg): bound_counter = CallbackPass("bound_pass_manager", callback) bound_pass = PassManager(bound_counter) + backend = GenericBackendV2(7, basis_gates=["cx", "u1", "u2", "u3"], seed=42) with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - 7, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) sampler = BackendSampler(backend=backend, bound_pass_manager=bound_pass) _ = sampler.run([self._circuit[0]]).result() expected = [ @@ -485,13 +477,11 @@ def callback(msg): # pylint: disable=function-redefined bound_counter = CallbackPass("bound_pass_manager", callback) bound_pass = PassManager(bound_counter) - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - 7, - calibrate_instructions=True, - basis_gates=["cx", "u1", "u2", "u3"], - seed=42, - ) + backend = GenericBackendV2( + 7, + basis_gates=["cx", "u1", "u2", "u3"], + seed=42, + ) with self.assertWarns(DeprecationWarning): sampler = BackendSampler(backend=backend, bound_pass_manager=bound_pass) _ = sampler.run([self._circuit[0], self._circuit[0]]).result() diff --git a/test/python/primitives/test_backend_sampler_v2.py b/test/python/primitives/test_backend_sampler_v2.py index 77830d5c9224..dc5c9ee82e9d 100644 --- a/test/python/primitives/test_backend_sampler_v2.py +++ b/test/python/primitives/test_backend_sampler_v2.py @@ -1484,10 +1484,7 @@ def max_circuits(self): def test_job_size_limit_backend_v1(self): """Test BackendSamplerV2 respects backend's job size limit.""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - 2, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) + backend = GenericBackendV2(2, basis_gates=["cx", "u1", "u2", "u3"], seed=42) qc = QuantumCircuit(1) qc.measure_all() qc2 = QuantumCircuit(1) diff --git a/test/python/providers/fake_provider/test_generic_backend_v2.py b/test/python/providers/fake_provider/test_generic_backend_v2.py index d42a6dbf7e07..1cd02fb91097 100644 --- a/test/python/providers/fake_provider/test_generic_backend_v2.py +++ b/test/python/providers/fake_provider/test_generic_backend_v2.py @@ -47,18 +47,6 @@ def test_ccx_2Q(self): with self.assertRaises(QiskitError): GenericBackendV2(num_qubits=2, basis_gates=["ccx", "id"], seed=42) - def test_calibration_no_noise_info(self): - """Test failing with a backend with calibration and no noise info""" - with self.assertRaises(QiskitError): - with self.assertWarns(DeprecationWarning): - GenericBackendV2( - num_qubits=2, - basis_gates=["ccx", "id"], - calibrate_instructions=True, - noise_info=False, - seed=42, - ) - def test_no_noise(self): """Test no noise info when parameter is false""" backend = GenericBackendV2( @@ -91,14 +79,12 @@ def test_no_noise_fully_connected(self): def test_no_info(self): """Test no noise info when parameter is false""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - num_qubits=5, - coupling_map=CouplingMap.from_line(5), - noise_info=False, - pulse_channels=False, - seed=42, - ) + backend = GenericBackendV2( + num_qubits=5, + coupling_map=CouplingMap.from_line(5), + noise_info=False, + seed=42, + ) qc = QuantumCircuit(5) qc.h(0) qc.cx(0, 1) @@ -110,23 +96,6 @@ def test_no_info(self): self.assertTrue(Operator.from_circuit(qc_res).equiv(qc)) self.assertEqual(backend.target.qubit_properties, None) - def test_no_pulse_channels(self): - """Test no/empty pulse channels when parameter is false""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - num_qubits=5, coupling_map=CouplingMap.from_line(5), pulse_channels=False, seed=42 - ) - qc = QuantumCircuit(5) - qc.h(0) - qc.cx(0, 1) - qc.cx(0, 2) - qc.cx(1, 4) - qc.cx(3, 0) - qc.cx(2, 4) - qc_res = generate_preset_pass_manager(optimization_level=2, backend=backend).run(qc) - self.assertTrue(Operator.from_circuit(qc_res).equiv(qc)) - self.assertTrue(len(backend.channels_map) == 0) - def test_operation_names(self): """Test that target basis gates include "delay", "measure" and "reset" even if not provided by user.""" diff --git a/test/python/providers/test_backend_v2.py b/test/python/providers/test_backend_v2.py index b3c5d3531ff1..6f9f68a21a52 100644 --- a/test/python/providers/test_backend_v2.py +++ b/test/python/providers/test_backend_v2.py @@ -28,12 +28,11 @@ from qiskit.compiler import transpile from qiskit.providers.basic_provider import BasicSimulator from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit.pulse import channels from qiskit.quantum_info import Operator from qiskit.transpiler import InstructionProperties from test import QiskitTestCase # pylint: disable=wrong-import-order -from ..legacy_cmaps import BOGOTA_CMAP, TENERIFE_CMAP +from ..legacy_cmaps import TENERIFE_CMAP from .fake_mumbai_v2 import FakeMumbaiFractionalCX @@ -203,49 +202,3 @@ def test_transpile_mumbai_target(self): expected.measure(qr[0], cr[0]) expected.measure(qr[1], cr[1]) self.assertEqual(expected, tqc) - - @data(0, 1, 2, 3, 4) - def test_drive_channel(self, qubit): - """Test getting drive channel with qubit index.""" - backend = GenericBackendV2(num_qubits=5, seed=42) - with self.assertWarns(DeprecationWarning): - chan = backend.drive_channel(qubit) - ref = channels.DriveChannel(qubit) - self.assertEqual(chan, ref) - - @data(0, 1, 2, 3, 4) - def test_measure_channel(self, qubit): - """Test getting measure channel with qubit index.""" - backend = GenericBackendV2(num_qubits=5, seed=42) - with self.assertWarns(DeprecationWarning): - chan = backend.measure_channel(qubit) - ref = channels.MeasureChannel(qubit) - self.assertEqual(chan, ref) - - @data(0, 1, 2, 3, 4) - def test_acquire_channel(self, qubit): - """Test getting acquire channel with qubit index.""" - backend = GenericBackendV2(num_qubits=5, seed=42) - with self.assertWarns(DeprecationWarning): - chan = backend.acquire_channel(qubit) - ref = channels.AcquireChannel(qubit) - self.assertEqual(chan, ref) - - @data((4, 3), (3, 4), (3, 2), (2, 3), (1, 2), (2, 1), (1, 0), (0, 1)) - def test_control_channel(self, qubits): - """Test getting acquire channel with qubit index.""" - bogota_cr_channels_map = { - (4, 3): 7, - (3, 4): 6, - (3, 2): 5, - (2, 3): 4, - (1, 2): 2, - (2, 1): 3, - (1, 0): 1, - (0, 1): 0, - } - backend = GenericBackendV2(num_qubits=5, coupling_map=BOGOTA_CMAP, seed=42) - with self.assertWarns(DeprecationWarning): - chan = backend.control_channel(qubits)[0] - ref = channels.ControlChannel(bogota_cr_channels_map[qubits]) - self.assertEqual(chan, ref) diff --git a/test/python/providers/test_pulse_defaults.py b/test/python/providers/test_pulse_defaults.py deleted file mode 100644 index 2d8fe5b7bf11..000000000000 --- a/test/python/providers/test_pulse_defaults.py +++ /dev/null @@ -1,73 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - - -"""Test the PulseDefaults part of the backend.""" -import copy -import warnings - -import numpy as np - -from qiskit.providers.fake_provider import FakeOpenPulse2Q, GenericBackendV2 -from test import QiskitTestCase # pylint: disable=wrong-import-order - - -class TestPulseDefaults(QiskitTestCase): - """Test the PulseDefaults creation and method usage.""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - # BackendV2 does not have defaults - self.defs = FakeOpenPulse2Q().defaults() - backend = GenericBackendV2( - 2, calibrate_instructions=True, basis_gates=["cx", "u1", "u2", "u3"], seed=42 - ) - self.inst_map = backend.instruction_schedule_map - - def test_buffer(self): - """Test getting the buffer value.""" - self.assertEqual(self.defs.buffer, 10) - - def test_freq_est(self): - """Test extracting qubit frequencies.""" - warnings.simplefilter("ignore") - self.assertEqual(self.defs.qubit_freq_est[1], 5.0 * 1e9) - self.assertEqual(self.defs.meas_freq_est[0], 6.5 * 1e9) - warnings.simplefilter("default") - - def test_default_building(self): - """Test building of ops definition is properly built from backend.""" - self.assertTrue(self.inst_map.has("u1", (0,))) - self.assertTrue(self.inst_map.has("u3", (0,))) - self.assertTrue(self.inst_map.has("u3", 1)) - self.assertTrue(self.inst_map.has("cx", (0, 1))) - self.assertEqual(self.inst_map.get_parameters("u1", 0), ("P0",)) - u1_minus_pi = self.inst_map.get("u1", 0, P0=np.pi) - fc_cmd = u1_minus_pi.instructions[0][-1] - self.assertAlmostEqual(fc_cmd.phase, -np.pi) - - def test_str(self): - """Test that __str__ method works.""" - self.assertEqual( - "" in str(self.defs)[100:] - ) - - def test_deepcopy(self): - """Test that deepcopy creates an identical object.""" - copy_defs = copy.deepcopy(self.defs) - self.assertEqual(list(copy_defs.to_dict().keys()), list(self.defs.to_dict().keys())) diff --git a/test/python/pulse/test_builder_v2.py b/test/python/pulse/test_builder_v2.py deleted file mode 100644 index 29248f6179ca..000000000000 --- a/test/python/pulse/test_builder_v2.py +++ /dev/null @@ -1,324 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2024. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test pulse builder with backendV2 context utilities.""" - -import numpy as np - -from qiskit import pulse -from qiskit.pulse import macros - -from qiskit.pulse.instructions import directives -from qiskit.pulse.transforms import target_qobj_transform -from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit.pulse import instructions -from test import QiskitTestCase # pylint: disable=wrong-import-order -from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings - -from ..legacy_cmaps import MUMBAI_CMAP - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestBuilderV2(QiskitTestCase): - """Test the pulse builder context with backendV2.""" - - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.backend = GenericBackendV2( - num_qubits=27, coupling_map=MUMBAI_CMAP, calibrate_instructions=True, seed=42 - ) - - def assertScheduleEqual(self, program, target): - """Assert an error when two pulse programs are not equal. - - .. note:: Two programs are converted into standard execution format then compared. - """ - self.assertEqual(target_qobj_transform(program), target_qobj_transform(target)) - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestContextsV2(TestBuilderV2): - """Test builder contexts.""" - - def test_phase_compensated_frequency_offset(self): - """Test that the phase offset context properly compensates for phase - accumulation with backendV2.""" - d0 = pulse.DriveChannel(0) - with pulse.build(self.backend) as schedule: - with pulse.frequency_offset(1e9, d0, compensate_phase=True): - pulse.delay(10, d0) - - reference = pulse.Schedule() - reference += instructions.ShiftFrequency(1e9, d0) - reference += instructions.Delay(10, d0) - reference += instructions.ShiftPhase( - -2 * np.pi * ((1e9 * 10 * self.backend.target.dt) % 1), d0 - ) - reference += instructions.ShiftFrequency(-1e9, d0) - self.assertScheduleEqual(schedule, reference) - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestChannelsV2(TestBuilderV2): - """Test builder channels.""" - - def test_drive_channel(self): - """Text context builder drive channel.""" - with pulse.build(self.backend): - with self.assertWarns(DeprecationWarning): - self.assertEqual(pulse.drive_channel(0), pulse.DriveChannel(0)) - - def test_measure_channel(self): - """Text context builder measure channel.""" - with pulse.build(self.backend): - with self.assertWarns(DeprecationWarning): - self.assertEqual(pulse.measure_channel(0), pulse.MeasureChannel(0)) - - def test_acquire_channel(self): - """Text context builder acquire channel.""" - with self.assertWarns(DeprecationWarning): - with pulse.build(self.backend): - self.assertEqual(pulse.acquire_channel(0), pulse.AcquireChannel(0)) - - def test_control_channel(self): - """Text context builder control channel.""" - with pulse.build(self.backend): - with self.assertWarns(DeprecationWarning): - self.assertEqual(pulse.control_channels(0, 1)[0], pulse.ControlChannel(0)) - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestDirectivesV2(TestBuilderV2): - """Test builder directives.""" - - def test_barrier_on_qubits(self): - """Test barrier directive on qubits with backendV2. - A part of qubits map of Mumbai - 0 -- 1 -- 4 -- - | - | - 2 - """ - with pulse.build(self.backend) as schedule: - with self.assertWarns(DeprecationWarning): - pulse.barrier(0, 1) - reference = pulse.ScheduleBlock() - reference += directives.RelativeBarrier( - pulse.DriveChannel(0), - pulse.DriveChannel(1), - pulse.MeasureChannel(0), - pulse.MeasureChannel(1), - pulse.ControlChannel(0), - pulse.ControlChannel(1), - pulse.ControlChannel(2), - pulse.ControlChannel(3), - pulse.ControlChannel(4), - pulse.ControlChannel(8), - pulse.AcquireChannel(0), - pulse.AcquireChannel(1), - ) - self.assertEqual(schedule, reference) - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestUtilitiesV2(TestBuilderV2): - """Test builder utilities.""" - - def test_active_backend(self): - """Test getting active builder backend.""" - with pulse.build(self.backend): - self.assertEqual(pulse.active_backend(), self.backend) - - def test_qubit_channels(self): - """Test getting the qubit channels of the active builder's backend.""" - with pulse.build(self.backend): - with self.assertWarns(DeprecationWarning): - qubit_channels = pulse.qubit_channels(0) - - self.assertEqual( - qubit_channels, - { - pulse.DriveChannel(0), - pulse.MeasureChannel(0), - pulse.AcquireChannel(0), - pulse.ControlChannel(0), - pulse.ControlChannel(1), - }, - ) - - def test_num_qubits(self): - """Test builder utility to get number of qubits with backendV2.""" - with pulse.build(self.backend): - self.assertEqual(pulse.num_qubits(), 27) - - def test_samples_to_seconds(self): - """Test samples to time with backendV2""" - target = self.backend.target - target.dt = 0.1 - with pulse.build(self.backend): - time = pulse.samples_to_seconds(100) - self.assertTrue(isinstance(time, float)) - self.assertEqual(pulse.samples_to_seconds(100), 10) - - def test_samples_to_seconds_array(self): - """Test samples to time (array format) with backendV2.""" - target = self.backend.target - target.dt = 0.1 - with pulse.build(self.backend): - samples = np.array([100, 200, 300]) - times = pulse.samples_to_seconds(samples) - self.assertTrue(np.issubdtype(times.dtype, np.floating)) - np.testing.assert_allclose(times, np.array([10, 20, 30])) - - def test_seconds_to_samples(self): - """Test time to samples with backendV2""" - target = self.backend.target - target.dt = 0.1 - with pulse.build(self.backend): - samples = pulse.seconds_to_samples(10) - self.assertTrue(isinstance(samples, int)) - self.assertEqual(pulse.seconds_to_samples(10), 100) - - def test_seconds_to_samples_array(self): - """Test time to samples (array format) with backendV2.""" - target = self.backend.target - target.dt = 0.1 - with pulse.build(self.backend): - times = np.array([10, 20, 30]) - samples = pulse.seconds_to_samples(times) - self.assertTrue(np.issubdtype(samples.dtype, np.integer)) - np.testing.assert_allclose(pulse.seconds_to_samples(times), np.array([100, 200, 300])) - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestMacrosV2(TestBuilderV2): - """Test builder macros with backendV2.""" - - def test_macro(self): - """Test builder macro decorator.""" - - @pulse.macro - def nested(a): - with self.assertWarns(DeprecationWarning): - pulse.play(pulse.Gaussian(100, a, 20), pulse.drive_channel(0)) - return a * 2 - - @pulse.macro - def test(): - with self.assertWarns(DeprecationWarning): - pulse.play(pulse.Constant(100, 1.0), pulse.drive_channel(0)) - output = nested(0.5) - return output - - with pulse.build(self.backend) as schedule: - output = test() - self.assertEqual(output, 0.5 * 2) - - reference = pulse.Schedule() - reference += pulse.Play(pulse.Constant(100, 1.0), pulse.DriveChannel(0)) - reference += pulse.Play(pulse.Gaussian(100, 0.5, 20), pulse.DriveChannel(0)) - - self.assertScheduleEqual(schedule, reference) - - def test_measure(self): - """Test utility function - measure with backendV2.""" - with pulse.build(self.backend) as schedule: - with self.assertWarns(DeprecationWarning): - reg = pulse.measure(0) - - self.assertEqual(reg, pulse.MemorySlot(0)) - - reference = macros.measure(qubits=[0], backend=self.backend, meas_map=self.backend.meas_map) - - self.assertScheduleEqual(schedule, reference) - - def test_measure_multi_qubits(self): - """Test utility function - measure with multi qubits with backendV2.""" - with pulse.build(self.backend) as schedule: - with self.assertWarns(DeprecationWarning): - regs = pulse.measure([0, 1]) - - self.assertListEqual(regs, [pulse.MemorySlot(0), pulse.MemorySlot(1)]) - - reference = macros.measure( - qubits=[0, 1], backend=self.backend, meas_map=self.backend.meas_map - ) - - self.assertScheduleEqual(schedule, reference) - - def test_measure_all(self): - """Test utility function - measure with backendV2..""" - with pulse.build(self.backend) as schedule: - with self.assertWarns(DeprecationWarning): - regs = pulse.measure_all() - - self.assertEqual(regs, [pulse.MemorySlot(i) for i in range(self.backend.num_qubits)]) - reference = macros.measure_all(self.backend) - - self.assertScheduleEqual(schedule, reference) - - def test_delay_qubit(self): - """Test delaying on a qubit macro.""" - with pulse.build(self.backend) as schedule: - with self.assertWarns(DeprecationWarning): - pulse.delay_qubits(10, 0) - - d0 = pulse.DriveChannel(0) - m0 = pulse.MeasureChannel(0) - a0 = pulse.AcquireChannel(0) - u0 = pulse.ControlChannel(0) - u1 = pulse.ControlChannel(1) - - reference = pulse.Schedule() - reference += instructions.Delay(10, d0) - reference += instructions.Delay(10, m0) - reference += instructions.Delay(10, a0) - reference += instructions.Delay(10, u0) - reference += instructions.Delay(10, u1) - - self.assertScheduleEqual(schedule, reference) - - def test_delay_qubits(self): - """Test delaying on multiple qubits with backendV2 to make sure we don't insert delays twice.""" - with pulse.build(self.backend) as schedule: - with self.assertWarns(DeprecationWarning): - pulse.delay_qubits(10, 0, 1) - - d0 = pulse.DriveChannel(0) - d1 = pulse.DriveChannel(1) - m0 = pulse.MeasureChannel(0) - m1 = pulse.MeasureChannel(1) - a0 = pulse.AcquireChannel(0) - a1 = pulse.AcquireChannel(1) - u0 = pulse.ControlChannel(0) - u1 = pulse.ControlChannel(1) - u2 = pulse.ControlChannel(2) - u3 = pulse.ControlChannel(3) - u4 = pulse.ControlChannel(4) - u8 = pulse.ControlChannel(8) - - reference = pulse.Schedule() - reference += instructions.Delay(10, d0) - reference += instructions.Delay(10, d1) - reference += instructions.Delay(10, m0) - reference += instructions.Delay(10, m1) - reference += instructions.Delay(10, a0) - reference += instructions.Delay(10, a1) - reference += instructions.Delay(10, u0) - reference += instructions.Delay(10, u1) - reference += instructions.Delay(10, u2) - reference += instructions.Delay(10, u3) - reference += instructions.Delay(10, u4) - reference += instructions.Delay(10, u8) - - self.assertScheduleEqual(schedule, reference) diff --git a/test/python/pulse/test_macros.py b/test/python/pulse/test_macros.py deleted file mode 100644 index c1f0b93339ab..000000000000 --- a/test/python/pulse/test_macros.py +++ /dev/null @@ -1,256 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019, 2024. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test cases for Pulse Macro functions.""" - -from qiskit.pulse import ( - Schedule, - AcquireChannel, - Acquire, - InstructionScheduleMap, - MeasureChannel, - MemorySlot, - GaussianSquare, - Play, -) -from qiskit.pulse import macros -from qiskit.pulse.exceptions import PulseError -from qiskit.providers.fake_provider import FakeOpenPulse2Q, Fake27QPulseV1, GenericBackendV2 -from test import QiskitTestCase # pylint: disable=wrong-import-order -from qiskit.utils.deprecate_pulse import decorate_test_methods, ignore_pulse_deprecation_warnings - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestMeasure(QiskitTestCase): - """Pulse measure macro.""" - - @ignore_pulse_deprecation_warnings - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.backend = FakeOpenPulse2Q() - self.backend_v1 = Fake27QPulseV1() - - self.inst_map = self.backend.defaults().instruction_schedule_map - with self.assertWarns(DeprecationWarning): - self.backend_v2 = GenericBackendV2( - num_qubits=27, - calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, - seed=42, - ) - - def test_measure(self): - """Test macro - measure.""" - sched = macros.measure(qubits=[0], backend=self.backend) - expected = Schedule( - self.inst_map.get("measure", [0, 1]).filter(channels=[MeasureChannel(0)]), - Acquire(10, AcquireChannel(0), MemorySlot(0)), - ) - self.assertEqual(sched.instructions, expected.instructions) - - def test_measure_sched_with_qubit_mem_slots(self): - """Test measure with custom qubit_mem_slots.""" - sched = macros.measure(qubits=[0], backend=self.backend, qubit_mem_slots={0: 1}) - expected = Schedule( - self.inst_map.get("measure", [0, 1]).filter(channels=[MeasureChannel(0)]), - Acquire(10, AcquireChannel(0), MemorySlot(1)), - ) - self.assertEqual(sched.instructions, expected.instructions) - - def test_measure_sched_with_meas_map(self): - """Test measure with custom meas_map as list and dict.""" - sched_with_meas_map_list = macros.measure( - qubits=[0], backend=self.backend, meas_map=[[0, 1]] - ) - sched_with_meas_map_dict = macros.measure( - qubits=[0], backend=self.backend, meas_map={0: [0, 1], 1: [0, 1]} - ) - expected = Schedule( - self.inst_map.get("measure", [0, 1]).filter(channels=[MeasureChannel(0)]), - Acquire(10, AcquireChannel(0), MemorySlot(0)), - ) - self.assertEqual(sched_with_meas_map_list.instructions, expected.instructions) - self.assertEqual(sched_with_meas_map_dict.instructions, expected.instructions) - - def test_measure_with_custom_inst_map(self): - """Test measure with custom inst_map, meas_map with measure_name.""" - q0_sched = Play(GaussianSquare(1200, 1, 0.4, 1150), MeasureChannel(0)) - q0_sched += Acquire(1200, AcquireChannel(0), MemorySlot(0)) - inst_map = InstructionScheduleMap() - inst_map.add("my_sched", 0, q0_sched) - sched = macros.measure( - qubits=[0], measure_name="my_sched", inst_map=inst_map, meas_map=[[0]] - ) - self.assertEqual(sched.instructions, q0_sched.instructions) - - with self.assertRaises(PulseError): - macros.measure(qubits=[0], measure_name="name", inst_map=inst_map, meas_map=[[0]]) - - def test_fail_measure(self): - """Test failing measure.""" - with self.assertRaises(PulseError): - macros.measure(qubits=[0], meas_map=self.backend.configuration().meas_map) - with self.assertRaises(PulseError): - macros.measure(qubits=[0], inst_map=self.inst_map) - - def test_measure_v2(self): - """Test macro - measure with backendV2.""" - sched = macros.measure(qubits=[0], backend=self.backend_v2) - with self.assertWarns(DeprecationWarning): - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[MeasureChannel(0), AcquireChannel(0)] - ) - self.assertEqual(sched.instructions, expected.instructions) - - def test_measure_v2_sched_with_qubit_mem_slots(self): - """Test measure with backendV2 and custom qubit_mem_slots.""" - sched = macros.measure(qubits=[0], backend=self.backend_v2, qubit_mem_slots={0: 2}) - with self.assertWarns(DeprecationWarning): - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[ - MeasureChannel(0), - ] - ) - measure_duration = expected.filter(instruction_types=[Play]).duration - expected += Acquire(measure_duration, AcquireChannel(0), MemorySlot(2)) - self.assertEqual(sched.instructions, expected.instructions) - - def test_measure_v2_sched_with_meas_map(self): - """Test measure with backendV2 custom meas_map as list and dict.""" - sched_with_meas_map_list = macros.measure( - qubits=[0], backend=self.backend_v2, meas_map=[[0, 1]] - ) - sched_with_meas_map_dict = macros.measure( - qubits=[0], backend=self.backend_v2, meas_map={0: [0, 1], 1: [0, 1]} - ) - with self.assertWarns(DeprecationWarning): - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[ - MeasureChannel(0), - ] - ) - measure_duration = expected.filter(instruction_types=[Play]).duration - expected += Acquire(measure_duration, AcquireChannel(0), MemorySlot(0)) - self.assertEqual(sched_with_meas_map_list.instructions, expected.instructions) - self.assertEqual(sched_with_meas_map_dict.instructions, expected.instructions) - - def test_multiple_measure_v2(self): - """Test macro - multiple qubit measure with backendV2.""" - sched = macros.measure(qubits=[0, 1], backend=self.backend_v2) - with self.assertWarns(DeprecationWarning): - expected = self.backend_v2.target.get_calibration("measure", (0,)).filter( - channels=[ - MeasureChannel(0), - ] - ) - expected += self.backend_v2.target.get_calibration("measure", (1,)).filter( - channels=[ - MeasureChannel(1), - ] - ) - measure_duration = expected.filter(instruction_types=[Play]).duration - expected += Acquire(measure_duration, AcquireChannel(0), MemorySlot(0)) - expected += Acquire(measure_duration, AcquireChannel(1), MemorySlot(1)) - self.assertEqual(sched.instructions, expected.instructions) - - def test_output_with_measure_v1_and_measure_v2(self): - """Test make outputs of measure_v1 and measure_v2 consistent.""" - sched_measure_v1 = macros.measure(qubits=[0, 1], backend=self.backend_v1) - sched_measure_v2 = macros.measure(qubits=[0, 1], backend=self.backend_v2) - - self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) - - def test_output_with_measure_v1_and_measure_v2_sched_with_qubit_mem_slots(self): - """Test make outputs of measure_v1 and measure_v2 with custom qubit_mem_slots consistent.""" - sched_measure_v1 = macros.measure( - qubits=[0], backend=self.backend_v1, qubit_mem_slots={0: 2} - ) - sched_measure_v2 = macros.measure( - qubits=[0], backend=self.backend_v2, qubit_mem_slots={0: 2} - ) - self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) - - def test_output_with_measure_v1_and_measure_v2_sched_with_meas_map(self): - """Test make outputs of measure_v1 and measure_v2 - with custom meas_map as list and dict consistent.""" - with self.assertWarns(DeprecationWarning): - backend = Fake27QPulseV1() - num_qubits_list_measure_v1 = list(range(backend.configuration().num_qubits)) - num_qubits_list_measure_v2 = list(range(self.backend_v2.num_qubits)) - sched_with_meas_map_list_v1 = macros.measure( - qubits=[0], backend=self.backend_v1, meas_map=[num_qubits_list_measure_v1] - ) - sched_with_meas_map_dict_v1 = macros.measure( - qubits=[0], - backend=self.backend_v1, - meas_map={0: num_qubits_list_measure_v1, 1: num_qubits_list_measure_v1}, - ) - sched_with_meas_map_list_v2 = macros.measure( - qubits=[0], backend=self.backend_v2, meas_map=[num_qubits_list_measure_v2] - ) - sched_with_meas_map_dict_v2 = macros.measure( - qubits=[0], - backend=self.backend_v2, - meas_map={0: num_qubits_list_measure_v2, 1: num_qubits_list_measure_v2}, - ) - self.assertEqual( - sched_with_meas_map_list_v1.instructions, - sched_with_meas_map_list_v2.instructions, - ) - self.assertEqual( - sched_with_meas_map_dict_v1.instructions, - sched_with_meas_map_dict_v2.instructions, - ) - - def test_output_with_multiple_measure_v1_and_measure_v2(self): - """Test macro - consistent output of multiple qubit measure with backendV1 and backendV2.""" - sched_measure_v1 = macros.measure(qubits=[0, 1], backend=self.backend_v1) - sched_measure_v2 = macros.measure(qubits=[0, 1], backend=self.backend_v2) - self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) - - -@decorate_test_methods(ignore_pulse_deprecation_warnings) -class TestMeasureAll(QiskitTestCase): - """Pulse measure all macro.""" - - @ignore_pulse_deprecation_warnings - def setUp(self): - super().setUp() - with self.assertWarns(DeprecationWarning): - self.backend_v1 = FakeOpenPulse2Q() - self.inst_map = self.backend_v1.defaults().instruction_schedule_map - with self.assertWarns(DeprecationWarning): - self.backend_v2 = GenericBackendV2( - num_qubits=2, - calibrate_instructions=self.backend_v1.defaults().instruction_schedule_map, - seed=42, - ) - - def test_measure_all(self): - """Test measure_all function.""" - sched = macros.measure_all(self.backend_v1) - expected = Schedule(self.inst_map.get("measure", [0, 1])) - self.assertEqual(sched.instructions, expected.instructions) - - def test_measure_all_v2(self): - """Test measure_all function with backendV2.""" - sched = macros.measure_all(self.backend_v1) - expected = Schedule( - self.inst_map.get("measure", list(range(self.backend_v1.configuration().num_qubits))) - ) - self.assertEqual(sched.instructions, expected.instructions) - - def test_output_of_measure_all_with_backend_v1_and_v2(self): - """Test make outputs of measure_all with backendV1 and backendV2 consistent.""" - sched_measure_v1 = macros.measure_all(backend=self.backend_v1) - sched_measure_v2 = macros.measure_all(backend=self.backend_v2) - self.assertEqual(sched_measure_v1.instructions, sched_measure_v2.instructions) diff --git a/test/python/transpiler/test_instruction_durations.py b/test/python/transpiler/test_instruction_durations.py index d9a3ef2b1773..9813dc4e7303 100644 --- a/test/python/transpiler/test_instruction_durations.py +++ b/test/python/transpiler/test_instruction_durations.py @@ -96,8 +96,7 @@ def test_fail_if_get_unbounded_duration_with_unit_conversion_when_dt_is_not_prov def test_from_backend_with_backendv2(self): """Test if `from_backend()` method allows using BackendV2""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2(num_qubits=4, calibrate_instructions=True, seed=42) + backend = GenericBackendV2(num_qubits=4, seed=42) inst_durations = InstructionDurations.from_backend(backend) self.assertEqual(inst_durations, backend.target.durations()) self.assertIsInstance(inst_durations, InstructionDurations) diff --git a/test/python/transpiler/test_passmanager_config.py b/test/python/transpiler/test_passmanager_config.py index 85cbb7909aef..fccd68f0a261 100644 --- a/test/python/transpiler/test_passmanager_config.py +++ b/test/python/transpiler/test_passmanager_config.py @@ -87,14 +87,12 @@ def test_from_backend_and_user(self): qr = QuantumRegister(4, "qr") initial_layout = [None, qr[0], qr[1], qr[2], None, qr[3]] - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2( - num_qubits=20, - coupling_map=ALMADEN_CMAP, - basis_gates=["id", "u1", "u2", "u3", "cx"], - calibrate_instructions=None, - seed=42, - ) + backend = GenericBackendV2( + num_qubits=20, + coupling_map=ALMADEN_CMAP, + basis_gates=["id", "u1", "u2", "u3", "cx"], + seed=42, + ) config = PassManagerConfig.from_backend( backend, basis_gates=["user_gate"], initial_layout=initial_layout ) diff --git a/test/python/transpiler/test_sabre_swap.py b/test/python/transpiler/test_sabre_swap.py index 109053ee8324..23a54433ed62 100644 --- a/test/python/transpiler/test_sabre_swap.py +++ b/test/python/transpiler/test_sabre_swap.py @@ -13,7 +13,6 @@ """Test the Sabre Swap pass""" import unittest -import warnings import itertools import ddt @@ -1380,17 +1379,12 @@ class TestSabreSwapRandomCircuitValidOutput(QiskitTestCase): @classmethod def setUpClass(cls): super().setUpClass() - with warnings.catch_warnings(): - # Catch warnings since self.assertWarns cannot be used here. - # The `calibrate_instructions` argument is deprecated in Qiksit 1.3 - warnings.simplefilter("ignore", category=DeprecationWarning) - cls.backend = GenericBackendV2( - num_qubits=27, - calibrate_instructions=True, - control_flow=True, - coupling_map=MUMBAI_CMAP, - seed=42, - ) + cls.backend = GenericBackendV2( + num_qubits=27, + control_flow=True, + coupling_map=MUMBAI_CMAP, + seed=42, + ) cls.coupling_edge_set = {tuple(x) for x in cls.backend.coupling_map} cls.basis_gates = set(cls.backend.operation_names) diff --git a/test/python/transpiler/test_stochastic_swap.py b/test/python/transpiler/test_stochastic_swap.py index 7e4dfe63de3c..bae6328a9b84 100644 --- a/test/python/transpiler/test_stochastic_swap.py +++ b/test/python/transpiler/test_stochastic_swap.py @@ -13,7 +13,6 @@ """Test the Stochastic Swap pass""" import unittest -import warnings import numpy.random @@ -1528,13 +1527,7 @@ class TestStochasticSwapRandomCircuitValidOutput(QiskitTestCase): @classmethod def setUpClass(cls): super().setUpClass() - with warnings.catch_warnings(): - # Catch warnings since self.assertWarns cannot be used here. - # The `calibrate_instructions` argument is deprecated in Qiksit 1.3 - warnings.simplefilter("ignore", category=DeprecationWarning) - cls.backend = GenericBackendV2( - num_qubits=27, calibrate_instructions=True, control_flow=True, seed=42 - ) + cls.backend = GenericBackendV2(num_qubits=27, control_flow=True, seed=42) cls.coupling_edge_set = {tuple(x) for x in cls.backend.coupling_map} cls.basis_gates = set(cls.backend.operation_names) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index f7aae24eff54..694ef38dc4fa 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -36,9 +36,6 @@ from qiskit.circuit import IfElseOp, ForLoopOp, WhileLoopOp, SwitchCaseOp from qiskit.circuit.measure import Measure from qiskit.circuit.parameter import Parameter -from qiskit import pulse -from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap -from qiskit.pulse.calibration_entries import CalibrationPublisher, ScheduleDef from qiskit.transpiler.coupling import CouplingMap from qiskit.transpiler.instruction_durations import InstructionDurations from qiskit.transpiler.timing_constraints import TimingConstraints @@ -1209,288 +1206,6 @@ def test_target_no_num_qubits_qubit_properties(self): self.assertEqual(target.num_qubits, len(qubit_properties)) -class TestPulseTarget(QiskitTestCase): - def setUp(self): - super().setUp() - self.pulse_target = Target( - dt=3e-7, granularity=2, min_length=4, pulse_alignment=8, acquire_alignment=8 - ) - with self.assertWarns(DeprecationWarning): - with pulse.build(name="sx_q0") as self.custom_sx_q0: - pulse.play(pulse.Constant(100, 0.1), pulse.DriveChannel(0)) - with pulse.build(name="sx_q1") as self.custom_sx_q1: - pulse.play(pulse.Constant(100, 0.2), pulse.DriveChannel(1)) - sx_props = { - (0,): InstructionProperties( - duration=35.5e-9, error=0.000413, calibration=self.custom_sx_q0 - ), - (1,): InstructionProperties( - duration=35.5e-9, error=0.000502, calibration=self.custom_sx_q1 - ), - } - self.pulse_target.add_instruction(SXGate(), sx_props) - - def test_instruction_schedule_map(self): - with self.assertWarns(DeprecationWarning): - inst_map = self.pulse_target.instruction_schedule_map() - self.assertIn("sx", inst_map.instructions) - self.assertEqual(inst_map.qubits_with_instruction("sx"), [0, 1]) - self.assertTrue("sx" in inst_map.qubit_instructions(0)) - - def test_instruction_schedule_map_ideal_sim_backend(self): - ideal_sim_target = Target(num_qubits=3) - theta = Parameter("theta") - phi = Parameter("phi") - lam = Parameter("lambda") - for inst in [ - UGate(theta, phi, lam), - RXGate(theta), - RYGate(theta), - RZGate(theta), - CXGate(), - ECRGate(), - CCXGate(), - Measure(), - ]: - ideal_sim_target.add_instruction(inst, {None: None}) - with self.assertWarns(DeprecationWarning): - inst_map = ideal_sim_target.instruction_schedule_map() - self.assertEqual(InstructionScheduleMap(), inst_map) - - def test_str(self): - expected = """Target -Number of qubits: 2 -Instructions: - sx - (0,): - Duration: 3.55e-08 sec. - Error Rate: 0.000413 - With pulse schedule calibration - (1,): - Duration: 3.55e-08 sec. - Error Rate: 0.000502 - With pulse schedule calibration -""" - self.assertEqual(expected, str(self.pulse_target)) - - def test_update_from_instruction_schedule_map_add_instruction(self): - target = Target() - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - inst_map.add("sx", 0, self.custom_sx_q0) - inst_map.add("sx", 1, self.custom_sx_q1) - with self.assertWarns(DeprecationWarning): - target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, target.instruction_schedule_map()) - - def test_update_from_instruction_schedule_map_with_schedule_parameter(self): - self.pulse_target.dt = None - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - duration = Parameter("duration") - - with self.assertWarns(DeprecationWarning): - with pulse.build(name="sx_q0") as custom_sx: - pulse.play(pulse.Constant(duration, 0.2), pulse.DriveChannel(0)) - - inst_map.add("sx", 0, custom_sx, ["duration"]) - - target = Target(dt=3e-7) - with self.assertWarns(DeprecationWarning): - target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, target.instruction_schedule_map()) - - def test_update_from_instruction_schedule_map_update_schedule(self): - self.pulse_target.dt = None - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - with pulse.build(name="sx_q1") as custom_sx: - pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) - - inst_map.add("sx", 0, self.custom_sx_q0) - inst_map.add("sx", 1, custom_sx) - with self.assertWarns(DeprecationWarning): - self.pulse_target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, self.pulse_target.instruction_schedule_map()) - # Calibration doesn't change for q0 - self.assertEqual(self.pulse_target["sx"][(0,)].duration, 35.5e-9) - self.assertEqual(self.pulse_target["sx"][(0,)].error, 0.000413) - # Calibration is updated for q1 without error dict and gate time - self.assertIsNone(self.pulse_target["sx"][(1,)].duration) - self.assertIsNone(self.pulse_target["sx"][(1,)].error) - - def test_update_from_instruction_schedule_map_new_instruction_no_name_map(self): - target = Target() - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - inst_map.add("sx", 0, self.custom_sx_q0) - inst_map.add("sx", 1, self.custom_sx_q1) - with self.assertWarns(DeprecationWarning): - target.update_from_instruction_schedule_map(inst_map) - self.assertEqual(target["sx"][(0,)].calibration, self.custom_sx_q0) - self.assertEqual(target["sx"][(1,)].calibration, self.custom_sx_q1) - - def test_update_from_instruction_schedule_map_new_qarg_raises(self): - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - inst_map.add("sx", 0, self.custom_sx_q0) - inst_map.add("sx", 1, self.custom_sx_q1) - inst_map.add("sx", 2, self.custom_sx_q1) - with self.assertWarns(DeprecationWarning): - self.pulse_target.update_from_instruction_schedule_map(inst_map) - self.assertFalse(self.pulse_target.instruction_supported("sx", (2,))) - - def test_update_from_instruction_schedule_map_with_dt_set(self): - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - with pulse.build(name="sx_q1") as custom_sx: - pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) - - inst_map.add("sx", 0, self.custom_sx_q0) - inst_map.add("sx", 1, custom_sx) - self.pulse_target.dt = 1.0 - with self.assertWarns(DeprecationWarning): - self.pulse_target.update_from_instruction_schedule_map(inst_map, {"sx": SXGate()}) - self.assertEqual(inst_map, self.pulse_target.instruction_schedule_map()) - self.assertEqual(self.pulse_target["sx"][(1,)].duration, 1000.0) - self.assertIsNone(self.pulse_target["sx"][(1,)].error) - # This is an edge case. - # System dt is read-only property and changing it will break all underlying calibrations. - # duration of sx0 returns previous value since calibration doesn't change. - self.assertEqual(self.pulse_target["sx"][(0,)].duration, 35.5e-9) - self.assertEqual(self.pulse_target["sx"][(0,)].error, 0.000413) - - def test_update_from_instruction_schedule_map_with_error_dict(self): - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - with pulse.build(name="sx_q1") as custom_sx: - pulse.play(pulse.Constant(1000, 0.2), pulse.DriveChannel(1)) - - inst_map.add("sx", 0, self.custom_sx_q0) - inst_map.add("sx", 1, custom_sx) - self.pulse_target.dt = 1.0 - error_dict = {"sx": {(1,): 1.0}} - - with self.assertWarns(DeprecationWarning): - self.pulse_target.update_from_instruction_schedule_map( - inst_map, {"sx": SXGate()}, error_dict=error_dict - ) - self.assertEqual(self.pulse_target["sx"][(1,)].error, 1.0) - self.assertEqual(self.pulse_target["sx"][(0,)].error, 0.000413) - - def test_timing_constraints(self): - generated_constraints = self.pulse_target.timing_constraints() - expected_constraints = TimingConstraints(2, 4, 8, 8) - for i in ["granularity", "min_length", "pulse_alignment", "acquire_alignment"]: - self.assertEqual( - getattr(generated_constraints, i), - getattr(expected_constraints, i), - f"Generated constraints differs from expected for attribute {i}" - f"{getattr(generated_constraints, i)}!={getattr(expected_constraints, i)}", - ) - - def test_default_instmap_has_no_custom_gate(self): - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2(num_qubits=27, calibrate_instructions=True) - target = backend.target - - # This copies .calibration of InstructionProperties of each instruction - # This must not convert PulseQobj to Schedule during this. - # See qiskit-terra/#9595 - with self.assertWarns(DeprecationWarning): - inst_map = target.instruction_schedule_map() - self.assertFalse(inst_map.has_custom_gate()) - - # Get pulse schedule. This generates Schedule provided by backend. - with self.assertWarns(DeprecationWarning): - sched = inst_map.get("sx", (0,)) - self.assertEqual(sched.metadata["publisher"], CalibrationPublisher.BACKEND_PROVIDER) - self.assertFalse(inst_map.has_custom_gate()) - - # Update target with custom instruction. This is user provided schedule. - with self.assertWarns(DeprecationWarning): - new_prop = InstructionProperties( - duration=self.custom_sx_q0.duration, - error=None, - calibration=self.custom_sx_q0, - ) - target.update_instruction_properties(instruction="sx", qargs=(0,), properties=new_prop) - with self.assertWarns(DeprecationWarning): - inst_map = target.instruction_schedule_map() - self.assertTrue(inst_map.has_custom_gate()) - - empty = InstructionProperties() - target.update_instruction_properties(instruction="sx", qargs=(0,), properties=empty) - with self.assertWarns(DeprecationWarning): - inst_map = target.instruction_schedule_map() - self.assertFalse(inst_map.has_custom_gate()) - - def test_get_empty_target_calibration(self): - target = Target() - properties = {(0,): InstructionProperties(duration=100, error=0.1)} - target.add_instruction(XGate(), properties) - - with self.assertWarns(DeprecationWarning): - self.assertIsNone(target["x"][(0,)].calibration) - - def test_has_calibration(self): - target = Target() - properties = { - (0,): InstructionProperties(duration=100, error=0.1), - (1,): None, - } - target.add_instruction(XGate(), properties) - - with self.assertWarns(DeprecationWarning): - # Test false for properties with no calibration - self.assertFalse(target.has_calibration("x", (0,))) - # Test false for no properties - self.assertFalse(target.has_calibration("x", (1,))) - - with self.assertWarns(DeprecationWarning): - properties = { - (0,): InstructionProperties( - duration=self.custom_sx_q0.duration, - error=None, - calibration=self.custom_sx_q0, - ) - } - target.add_instruction(SXGate(), properties) - - # Test true for properties with calibration - with self.assertWarns(DeprecationWarning): - self.assertTrue(target.has_calibration("sx", (0,))) - - def test_loading_legacy_ugate_instmap(self): - # This is typical IBM backend situation. - # IBM provider used to have u1, u2, u3 in the basis gates and - # these have been replaced with sx and rz. - # However, IBM provider still provides calibration of these u gates, - # and the inst map loads them as backend calibrations. - # Target is implicitly updated with inst map when it is set in transpile. - # If u gates are not excluded, they may appear in the transpiled circuit. - # These gates are no longer supported by hardware. - with self.assertWarns(DeprecationWarning): - entry = ScheduleDef() - entry.define(pulse.Schedule(name="fake_u3"), user_provided=False) # backend provided - instmap = InstructionScheduleMap() - instmap._add("u3", (0,), entry) - - # Today's standard IBM backend target with sx, rz basis - target = Target() - target.add_instruction(SXGate(), {(0,): InstructionProperties()}) - target.add_instruction(RZGate(Parameter("θ")), {(0,): InstructionProperties()}) - target.add_instruction(Measure(), {(0,): InstructionProperties()}) - names_before = set(target.operation_names) - - with self.assertWarns(DeprecationWarning): - target.update_from_instruction_schedule_map(instmap) - names_after = set(target.operation_names) - - # Otherwise u3 and sx-rz basis conflict in 1q decomposition. - self.assertSetEqual(names_before, names_after) - - class TestGlobalVariableWidthOperations(QiskitTestCase): def setUp(self): super().setUp() diff --git a/test/python/transpiler/test_vf2_layout.py b/test/python/transpiler/test_vf2_layout.py index 5257a9d1cee2..9fbc9ac45480 100644 --- a/test/python/transpiler/test_vf2_layout.py +++ b/test/python/transpiler/test_vf2_layout.py @@ -713,8 +713,7 @@ def test_reasonable_limits_for_simple_layouts_v1(self): def test_reasonable_limits_for_simple_layouts(self): """Test that the default trials is set to a reasonable number.""" - with self.assertWarns(DeprecationWarning): - backend = GenericBackendV2(27, calibrate_instructions=True, seed=42) + backend = GenericBackendV2(27, seed=42) qc = QuantumCircuit(5) qc.cx(2, 3) qc.cx(0, 1) From 082a26f309576235da787869cc8096c23437585a Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 14 Feb 2025 11:32:31 +0200 Subject: [PATCH 07/18] First pass --- .../src/basis/basis_translator/mod.rs | 39 +- crates/accelerate/src/check_map.rs | 6 +- .../src/euler_one_qubit_decomposer.rs | 12 - crates/accelerate/src/gate_direction.rs | 33 +- .../instruction_properties.rs | 1 - crates/circuit/src/converters.rs | 5 - crates/circuit/src/dag_circuit.rs | 256 ------------ qiskit/circuit/quantumcircuit.py | 201 +--------- qiskit/compiler/transpiler.py | 9 - qiskit/converters/circuit_to_dagdependency.py | 2 - .../converters/circuit_to_dagdependency_v2.py | 1 - qiskit/converters/dag_to_circuit.py | 1 - qiskit/converters/dag_to_dagdependency.py | 1 - qiskit/converters/dag_to_dagdependency_v2.py | 1 - qiskit/converters/dagdependency_to_circuit.py | 6 - qiskit/converters/dagdependency_to_dag.py | 5 - qiskit/dagcircuit/dagdependency.py | 34 +- qiskit/dagcircuit/dagdependency_v2.py | 76 ---- qiskit/providers/backend_compat.py | 95 +---- .../basic_provider/basic_simulator.py | 2 +- .../fake_provider/generic_backend_v2.py | 2 +- .../fake_provider/utils/backend_converter.py | 4 +- qiskit/scheduler/config.py | 37 -- qiskit/scheduler/lowering.py | 187 --------- qiskit/scheduler/methods/__init__.py | 15 - qiskit/scheduler/methods/basic.py | 140 ------- qiskit/scheduler/schedule_circuit.py | 69 ---- qiskit/scheduler/sequence.py | 104 ----- qiskit/transpiler/basepasses.py | 5 +- .../passes/basis/unroll_3q_or_more.py | 2 - .../passes/basis/unroll_custom_definitions.py | 2 +- .../passes/calibration/base_builder.py | 79 ---- .../passes/calibration/exceptions.py | 22 -- .../passes/optimization/normalize_rx_angle.py | 1 + .../optimization/optimize_1q_decomposition.py | 18 +- qiskit/transpiler/passes/scheduling/alap.py | 1 - .../scheduling/alignments/check_durations.py | 7 - qiskit/transpiler/passes/scheduling/asap.py | 1 - .../passes/scheduling/base_scheduler.py | 9 +- .../passes/scheduling/dynamical_decoupling.py | 29 +- .../passes/scheduling/padding/base_padding.py | 1 - .../padding/dynamical_decoupling.py | 56 +-- .../scheduling/scheduling/base_scheduler.py | 18 +- .../passes/scheduling/time_unit_conversion.py | 29 +- .../passes/synthesis/high_level_synthesis.py | 3 +- qiskit/transpiler/passmanager_config.py | 14 - .../preset_passmanagers/builtin_plugins.py | 34 +- .../transpiler/preset_passmanagers/common.py | 2 +- .../generate_preset_pass_manager.py | 126 ++---- qiskit/transpiler/target.py | 363 +----------------- qiskit/visualization/circuit/matplotlib.py | 19 +- test/python/circuit/test_calibrations.py | 61 --- .../python/circuit/test_circuit_operations.py | 5 - .../python/circuit/test_circuit_properties.py | 97 ----- test/python/circuit/test_compose.py | 32 -- test/python/circuit/test_parameters.py | 159 -------- test/python/circuit/test_scheduled_circuit.py | 1 - test/python/compiler/test_transpiler.py | 148 ------- test/python/converters/test_circuit_to_dag.py | 15 - .../test_circuit_to_dagdependency.py | 15 - .../test_circuit_to_dagdependency_v2.py | 14 - test/python/dagcircuit/test_compose.py | 17 - test/python/primitives/test_primitive.py | 39 +- test/python/pulse/test_block.py | 19 +- .../transpiler/test_dynamical_decoupling.py | 185 --------- test/python/transpiler/test_gate_direction.py | 44 --- .../transpiler/test_normalize_rx_angle.py | 1 + .../transpiler/test_passmanager_config.py | 17 - .../transpiler/test_preset_passmanagers.py | 1 - .../test_scheduling_padding_pass.py | 28 -- test/python/transpiler/test_target.py | 24 +- test/python/transpiler/test_vf2_layout.py | 5 +- .../python/transpiler/test_vf2_post_layout.py | 16 +- .../mpl/circuit/references/calibrations.png | Bin 7213 -> 0 bytes .../calibrations_with_control_gates.png | Bin 8984 -> 0 bytes .../calibrations_with_rzz_and_rxx.png | Bin 12305 -> 0 bytes .../calibrations_with_swap_and_reset.png | Bin 10213 -> 0 bytes .../circuit/test_circuit_matplotlib_drawer.py | 143 ------- 78 files changed, 130 insertions(+), 3141 deletions(-) delete mode 100644 qiskit/scheduler/config.py delete mode 100644 qiskit/scheduler/lowering.py delete mode 100644 qiskit/scheduler/methods/__init__.py delete mode 100644 qiskit/scheduler/methods/basic.py delete mode 100644 qiskit/scheduler/schedule_circuit.py delete mode 100644 qiskit/scheduler/sequence.py delete mode 100644 qiskit/transpiler/passes/calibration/base_builder.py delete mode 100644 qiskit/transpiler/passes/calibration/exceptions.py delete mode 100644 test/python/circuit/test_calibrations.py delete mode 100644 test/visual/mpl/circuit/references/calibrations.png delete mode 100644 test/visual/mpl/circuit/references/calibrations_with_control_gates.png delete mode 100644 test/visual/mpl/circuit/references/calibrations_with_rzz_and_rxx.png delete mode 100644 test/visual/mpl/circuit/references/calibrations_with_swap_and_reset.png diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index 996ba6412c16..ee9471505d3c 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -213,8 +213,7 @@ fn extract_basis( min_qubits: usize, ) -> PyResult<()> { for (node, operation) in circuit.op_nodes(true) { - if !circuit.has_calibration_for_index(py, node)? - && circuit.get_qargs(operation.qubits).len() >= min_qubits + if circuit.get_qargs(operation.qubits).len() >= min_qubits { basis.insert((operation.op.name().to_string(), operation.op.num_qubits())); } @@ -244,10 +243,7 @@ fn extract_basis( .borrow(); for (index, inst) in circuit_data.iter().enumerate() { let instruction_object = circuit.get_item(index)?; - let has_calibration = circuit - .call_method1(intern!(py, "_has_calibration_for"), (&instruction_object,))?; - if !has_calibration.is_truthy()? - && circuit_data.get_qargs(inst.qubits).len() >= min_qubits + if circuit_data.get_qargs(inst.qubits).len() >= min_qubits { basis.insert((inst.op.name().to_string(), inst.op.num_qubits())); } @@ -280,7 +276,7 @@ fn extract_basis_target( ) -> PyResult<()> { for (node, node_obj) in dag.op_nodes(true) { let qargs: &[Qubit] = dag.get_qargs(node_obj.qubits); - if dag.has_calibration_for_index(py, node)? || qargs.len() < min_qubits { + if qargs.len() < min_qubits { continue; } // Treat the instruction as on an incomplete basis if the qargs are in the @@ -343,6 +339,7 @@ fn extract_basis_target( /// This needs to use a Python instance of `QuantumCircuit` due to it needing /// to access `has_calibration_for()` which is unavailable through rust. However, /// this API will be removed with the deprecation of `Pulse`. +// TODO: remove this fn extract_basis_target_circ( circuit: &Bound, source_basis: &mut HashSet, @@ -355,10 +352,7 @@ fn extract_basis_target_circ( let circ_data = circ_data_bound.borrow(); for (index, node_obj) in circ_data.iter().enumerate() { let qargs = circ_data.get_qargs(node_obj.qubits); - if circuit - .call_method1("_has_calibration_for", (circuit.get_item(index)?,))? - .is_truthy()? - || qargs.len() < min_qubits + if qargs.len() < min_qubits { continue; } @@ -535,29 +529,6 @@ fn apply_translation( continue; } - if dag.has_calibration_for_index(py, node)? { - out_dag.apply_operation_back( - py, - node_obj.op.clone(), - node_qarg, - node_carg, - if node_obj.params_view().is_empty() { - None - } else { - Some( - node_obj - .params_view() - .iter() - .map(|param| param.clone_ref(py)) - .collect(), - ) - }, - node_obj.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - None, - )?; - continue; - } let unique_qargs: Option = if qubit_set.is_empty() { None } else { diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 68281d0ca198..5f8240fbb213 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -36,7 +36,7 @@ fn recurse<'py>( None => edge_set.contains(&[qubits[0].into(), qubits[1].into()]), } }; - for (node, inst) in dag.op_nodes(false) { + for (_node, inst) in dag.op_nodes(false) { let qubits = dag.get_qargs(inst.qubits); if inst.op.control_flow() { if let OperationRef::Instruction(py_inst) = inst.op.view() { @@ -65,9 +65,7 @@ fn recurse<'py>( } } } - } else if qubits.len() == 2 - && (dag.calibrations_empty() || !dag.has_calibration_for_index(py, node)?) - && !check_qubits(qubits) + } else if qubits.len() == 2 && !check_qubits(qubits) { return Ok(Some(( inst.op.name().to_string(), diff --git a/crates/accelerate/src/euler_one_qubit_decomposer.rs b/crates/accelerate/src/euler_one_qubit_decomposer.rs index bc4da40e57d6..30aec7922c22 100644 --- a/crates/accelerate/src/euler_one_qubit_decomposer.rs +++ b/crates/accelerate/src/euler_one_qubit_decomposer.rs @@ -1094,18 +1094,6 @@ pub(crate) fn optimize_1q_gates_decomposition( } else { unreachable!("nodes in runs will always be op nodes") }; - if !dag.calibrations_empty() { - let mut has_calibration = false; - for node in &raw_run { - if dag.has_calibration_for_index(py, *node)? { - has_calibration = true; - break; - } - } - if has_calibration { - continue; - } - } if basis_gates_per_qubit[qubit.index()].is_none() { let basis_gates = match target { Some(target) => Some( diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 711779a3b47a..5a1871189b17 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -292,7 +292,7 @@ where } } - if op_args.len() != 2 || dag.has_calibration_for_index(py, node)? { + if op_args.len() != 2 { continue; }; @@ -336,7 +336,6 @@ where } // No matching replacement found if gate_complies(packed_inst, &[op_args1, op_args0]) - || has_calibration_for_op_node(py, dag, packed_inst, &[op_args1, op_args0])? { return Err(TranspilerError::new_err(format!("{} would be supported on {:?} if the direction was swapped, but no rules are known to do that. {:?} can be automatically flipped.", packed_inst.op.name(), op_args, vec!["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]))); // NOTE: Make sure to update the list of the supported gates if adding more replacements @@ -376,36 +375,6 @@ where Ok(dag) } -// Check whether the dag as calibration for a DAGOpNode -fn has_calibration_for_op_node( - py: Python, - dag: &DAGCircuit, - packed_inst: &PackedInstruction, - qargs: &[Qubit], -) -> PyResult { - let py_args = PyTuple::new(py, dag.qubits().map_indices(qargs))?; - - let dag_op_node = Py::new( - py, - ( - DAGOpNode { - instruction: CircuitInstruction { - operation: packed_inst.op.clone(), - qubits: py_args.unbind(), - clbits: PyTuple::empty(py).unbind(), - params: packed_inst.params_view().iter().cloned().collect(), - extra_attrs: packed_inst.extra_attrs.clone(), - #[cfg(feature = "cache_pygates")] - py_op: packed_inst.py_op.clone(), - }, - }, - DAGNode { node: None }, - ), - )?; - - dag.has_calibration_for(py, dag_op_node.borrow(py)) -} - // Return a replacement DAG for the given standard gate in the supported list // TODO: optimize it by caching the DAGs of the non-parametric gates and caching and // mutating upon request the DAGs of the parametric gates diff --git a/crates/accelerate/src/target_transpiler/instruction_properties.rs b/crates/accelerate/src/target_transpiler/instruction_properties.rs index a7a31c87924c..5de5b656913e 100644 --- a/crates/accelerate/src/target_transpiler/instruction_properties.rs +++ b/crates/accelerate/src/target_transpiler/instruction_properties.rs @@ -37,7 +37,6 @@ impl InstructionProperties { /// specified set of qubits /// error (Option): The average error rate for the instruction on the specified /// set of qubits. - /// calibration (Option): The pulse representation of the instruction. #[new] #[pyo3(signature = (duration=None, error=None))] pub fn new(_py: Python<'_>, duration: Option, error: Option) -> Self { diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index b9b7b433eec8..b49cfb1ed6cd 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -30,7 +30,6 @@ use crate::packed_instruction::PackedInstruction; pub struct QuantumCircuitData<'py> { pub data: CircuitData, pub name: Option>, - pub calibrations: Option>>, pub metadata: Option>, pub qregs: Option>, pub cregs: Option>, @@ -47,10 +46,6 @@ impl<'py> FromPyObject<'py> for QuantumCircuitData<'py> { Ok(QuantumCircuitData { data: data_borrowed, name: ob.getattr(intern!(py, "name")).ok(), - calibrations: ob - .getattr(intern!(py, "_calibrations_prop"))? - .extract() - .ok(), metadata: ob.getattr(intern!(py, "metadata")).ok(), qregs: ob .getattr(intern!(py, "qregs")) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 0dbf6aff8979..31b31beff86f 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -188,8 +188,6 @@ pub struct DAGCircuit { #[pyo3(get, set)] metadata: Option, - calibrations: HashMap>, - dag: StableDiGraph, #[pyo3(get)] @@ -371,7 +369,6 @@ impl DAGCircuit { Ok(DAGCircuit { name: None, metadata: Some(PyDict::new(py).unbind().into()), - calibrations: HashMap::new(), dag: StableDiGraph::default(), qregs: PyDict::new(py).unbind(), cregs: PyDict::new(py).unbind(), @@ -520,7 +517,6 @@ impl DAGCircuit { let out_dict = PyDict::new(py); out_dict.set_item("name", self.name.as_ref().map(|x| x.clone_ref(py)))?; out_dict.set_item("metadata", self.metadata.as_ref().map(|x| x.clone_ref(py)))?; - out_dict.set_item("_calibrations_prop", self.calibrations.clone())?; out_dict.set_item("qregs", self.qregs.clone_ref(py))?; out_dict.set_item("cregs", self.cregs.clone_ref(py))?; out_dict.set_item("global_phase", self.global_phase.clone())?; @@ -606,10 +602,6 @@ impl DAGCircuit { let dict_state = state.downcast_bound::(py)?; self.name = dict_state.get_item("name")?.unwrap().extract()?; self.metadata = dict_state.get_item("metadata")?.unwrap().extract()?; - self.calibrations = dict_state - .get_item("_calibrations_prop")? - .unwrap() - .extract()?; self.qregs = dict_state.get_item("qregs")?.unwrap().extract()?; self.cregs = dict_state.get_item("cregs")?.unwrap().extract()?; self.global_phase = dict_state.get_item("global_phase")?.unwrap().extract()?; @@ -827,182 +819,6 @@ impl DAGCircuit { Ok(()) } - /// Return calibration dictionary. - /// - /// The custom pulse definition of a given gate is of the form - /// {'gate_name': {(qubits, params): schedule}} - /// - /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 - #[getter] - fn get_calibrations(&self, py: Python) -> HashMap> { - emit_pulse_dependency_deprecation( - py, - "property ``qiskit.dagcircuit.dagcircuit.DAGCircuit.calibrations``", - ); - - self.calibrations.clone() - } - - /// Set the circuit calibration data from a dictionary of calibration definition. - /// - /// Args: - /// calibrations (dict): A dictionary of input in the format - /// {'gate_name': {(qubits, gate_params): schedule}} - /// - /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 - #[setter] - fn set_calibrations(&mut self, py: Python, calibrations: HashMap>) { - emit_pulse_dependency_deprecation( - py, - "property ``qiskit.dagcircuit.dagcircuit.DAGCircuit.calibrations``", - ); - - self.calibrations = calibrations; - } - - // This is an alternative and Python-private path to 'get_calibration' to avoid - // deprecation warnings - #[getter(_calibrations_prop)] - fn get_calibrations_prop(&self) -> HashMap> { - self.calibrations.clone() - } - - // This is an alternative and Python-private path to 'set_calibration' to avoid - // deprecation warnings - #[setter(_calibrations_prop)] - fn set_calibrations_prop(&mut self, calibrations: HashMap>) { - self.calibrations = calibrations; - } - - /// Register a low-level, custom pulse definition for the given gate. - /// - /// Args: - /// gate (Union[Gate, str]): Gate information. - /// qubits (Union[int, Tuple[int]]): List of qubits to be measured. - /// schedule (Schedule): Schedule information. - /// params (Optional[List[Union[float, Parameter]]]): A list of parameters. - /// - /// Raises: - /// Exception: if the gate is of type string and params is None. - /// - /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 - #[pyo3(signature=(gate, qubits, schedule, params=None))] - fn add_calibration<'py>( - &mut self, - py: Python<'py>, - mut gate: Bound<'py, PyAny>, - qubits: Bound<'py, PyAny>, - schedule: Py, - mut params: Option>, - ) -> PyResult<()> { - emit_pulse_dependency_deprecation( - py, - "method ``qiskit.dagcircuit.dagcircuit.DAGCircuit.add_calibration``", - ); - - if gate.is_instance(imports::GATE.get_bound(py))? { - params = Some(gate.getattr(intern!(py, "params"))?); - gate = gate.getattr(intern!(py, "name"))?; - } - - let params_tuple = if let Some(operands) = params { - let add_calibration = PyModule::from_code( - py, - std::ffi::CString::new( - r#" -import numpy as np - -def _format(operand): - try: - # Using float/complex value as a dict key is not good idea. - # This makes the mapping quite sensitive to the rounding error. - # However, the mechanism is already tied to the execution model (i.e. pulse gate) - # and we cannot easily update this rule. - # The same logic exists in QuantumCircuit.add_calibration. - evaluated = complex(operand) - if np.isreal(evaluated): - evaluated = float(evaluated.real) - if evaluated.is_integer(): - evaluated = int(evaluated) - return evaluated - except TypeError: - # Unassigned parameter - return operand - "#, - )? - .as_c_str(), - std::ffi::CString::new("add_calibration.py")?.as_c_str(), - std::ffi::CString::new("add_calibration")?.as_c_str(), - )?; - - let format = add_calibration.getattr("_format")?; - let mapped: PyResult> = - operands.try_iter()?.map(|p| format.call1((p?,))).collect(); - PyTuple::new(py, mapped?)?.into_any() - } else { - PyTuple::empty(py).into_any() - }; - - let calibrations = self - .calibrations - .entry(gate.extract()?) - .or_insert_with(|| PyDict::new(py).unbind()) - .bind(py); - - let qubits = if let Ok(qubits) = qubits.downcast::() { - qubits.to_tuple()?.into_any() - } else { - PyTuple::new(py, [qubits])?.into_any() - }; - let key = PyTuple::new(py, &[qubits.unbind(), params_tuple.into_any().unbind()])?; - calibrations.set_item(key, schedule)?; - Ok(()) - } - - /// Return True if the dag has a calibration defined for the node operation. In this - /// case, the operation does not need to be translated to the device basis. - /// - /// DEPRECATED since Qiskit 1.3.0 and will be removed in Qiskit 2.0.0 - pub fn has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { - emit_pulse_dependency_deprecation( - py, - "method ``qiskit.dagcircuit.dagcircuit.DAGCircuit.has_calibration_for``", - ); - - self._has_calibration_for(py, node) - } - - fn _has_calibration_for(&self, py: Python, node: PyRef) -> PyResult { - if !self - .calibrations - .contains_key(node.instruction.operation.name()) - { - return Ok(false); - } - let mut params = Vec::new(); - for p in &node.instruction.params { - if let Param::ParameterExpression(exp) = p { - let exp = exp.bind(py); - if !exp.getattr(intern!(py, "parameters"))?.is_truthy()? { - let as_py_float = exp.call_method0(intern!(py, "__float__"))?; - params.push(as_py_float.unbind()); - continue; - } - } - params.push(p.into_py_any(py)?); - } - let qubits: Vec = self - .qubits - .map_bits(node.instruction.qubits.bind(py).iter())? - .map(|bit| bit.0) - .collect(); - let qubits = PyTuple::new(py, qubits)?; - let params = PyTuple::new(py, params)?; - self.calibrations[node.instruction.operation.name()] - .bind(py) - .contains((qubits, params)) - } - /// Remove all operation nodes with the given name. fn remove_all_ops_named(&mut self, opname: &str) { let mut to_remove = Vec::new(); @@ -1987,18 +1803,6 @@ def _format(operand): dag.global_phase = add_global_phase(py, &dag.global_phase, &other.global_phase)?; - for (gate, cals) in other.calibrations.iter() { - let calibrations = match dag.calibrations.get(gate) { - Some(calibrations) => calibrations, - None => { - dag.calibrations - .insert(gate.clone(), PyDict::new(py).unbind()); - &dag.calibrations[gate] - } - }; - calibrations.bind(py).update(cals.bind(py).as_mapping())?; - } - // This is all the handling we need for realtime variables, if there's no remapping. They: // // * get added to the DAG and then operations involving them get appended on normally. @@ -2434,22 +2238,6 @@ def _format(operand): if !phase_eq { return Ok(false); } - if self.calibrations.len() != other.calibrations.len() { - return Ok(false); - } - - for (k, v1) in &self.calibrations { - match other.calibrations.get(k) { - Some(v2) => { - if !v1.bind(py).eq(v2.bind(py))? { - return Ok(false); - } - } - None => { - return Ok(false); - } - } - } // We don't do any semantic equivalence between Var nodes, as things stand; DAGs can only be // equal in our mind if they use the exact same UUID vars. @@ -6256,7 +6044,6 @@ impl DAGCircuit { Ok(Self { name: None, metadata: Some(PyDict::new(py).unbind().into()), - calibrations: HashMap::default(), dag: StableDiGraph::with_capacity(num_nodes, num_edges), qregs: PyDict::new(py).unbind(), cregs: PyDict::new(py).unbind(), @@ -6439,44 +6226,6 @@ impl DAGCircuit { Ok(()) } - pub fn calibrations_empty(&self) -> bool { - self.calibrations.is_empty() - } - - pub fn has_calibration_for_index(&self, py: Python, node_index: NodeIndex) -> PyResult { - let node = &self.dag[node_index]; - if let NodeType::Operation(instruction) = node { - if !self.calibrations.contains_key(instruction.op.name()) { - return Ok(false); - } - let params = match &instruction.params { - Some(params) => { - let mut out_params = Vec::new(); - for p in params.iter() { - if let Param::ParameterExpression(exp) = p { - let exp = exp.bind(py); - if !exp.getattr(intern!(py, "parameters"))?.is_truthy()? { - let as_py_float = exp.call_method0(intern!(py, "__float__"))?; - out_params.push(as_py_float.unbind()); - continue; - } - } - out_params.push(p.into_pyobject(py)?.into_any().unbind()); - } - PyTuple::new(py, out_params) - } - None => Ok(PyTuple::empty(py)), - }?; - let qargs = self.qargs_interner.get(instruction.qubits); - let qubits = PyTuple::new(py, qargs.iter().map(|x| x.0))?; - self.calibrations[instruction.op.name()] - .bind(py) - .contains((qubits, params).into_py_any(py)?) - } else { - Err(DAGCircuitError::new_err("Specified node is not an op node")) - } - } - /// Return the op name counts in the circuit /// /// Args: @@ -6734,10 +6483,6 @@ impl DAGCircuit { _ => unreachable!("Incorrect parameter assigned for global phase"), }; - if let Some(calibrations) = qc.calibrations { - new_dag.calibrations = calibrations; - } - new_dag.metadata = qc.metadata.map(|meta| meta.unbind()); // Add the qubits depending on order, and produce the qargs map. @@ -6862,7 +6607,6 @@ impl DAGCircuit { let circ = QuantumCircuitData { data: circuit_data, name: None, - calibrations: None, metadata: None, qregs: None, cregs: None, diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 7ab1bae5d8cd..e813cbe6f59f 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -150,7 +150,6 @@ class QuantumCircuit: Immutable data attribute Summary ========================= ====================================================================== :attr:`ancillas` List of :class:`AncillaQubit`\\ s tracked by the circuit. - :attr:`calibrations` Custom user-supplied pulse calibrations for individual instructions. :attr:`cregs` List of :class:`ClassicalRegister`\\ s tracked by the circuit. :attr:`clbits` List of :class:`Clbit`\\ s tracked by the circuit. @@ -229,12 +228,6 @@ class QuantumCircuit: .. autoattribute:: parameters - The storage of any :ref:`manual pulse-level calibrations ` for individual - instructions on the circuit is in :attr:`calibrations`. This presents as a :class:`dict`, but - should not be mutated directly; use the methods discussed in :ref:`circuit-calibrations`. - - .. autoattribute:: calibrations - If you have transpiled your circuit, so you have a physical circuit, you can inspect the :attr:`layout` attribute for information stored by the transpiler about how the virtual qubits of the source circuit map to the hardware qubits of your physical circuit, both at the start and @@ -813,19 +806,8 @@ class QuantumCircuit: .. automethod:: clear .. automethod:: remove_final_measurements - .. _circuit-calibrations: - - Manual calibration of instructions - ---------------------------------- - - :class:`QuantumCircuit` can store :attr:`calibrations` of instructions that define the pulses - used to run them on one particular hardware backend. You can - - .. automethod:: add_calibration - .. automethod:: has_calibration_for - - Circuit properties + Circuit properties ================== Simple circuit metrics @@ -1115,7 +1097,6 @@ def __init__( self._data: CircuitData = CircuitData() self._ancillas: list[AncillaQubit] = [] - self._calibrations: DefaultDict[str, dict[tuple, Any]] = defaultdict(dict) self.add_register(*regs) self._layout = None @@ -1330,67 +1311,6 @@ def op_start_times(self) -> list[int]: ) return self._op_start_times - @property - @deprecate_pulse_dependency(is_property=True) - def calibrations(self) -> dict: - """Return calibration dictionary. - - The custom pulse definition of a given gate is of the form - ``{'gate_name': {(qubits, params): schedule}}`` - """ - return self._calibrations_prop - - @calibrations.setter - @deprecate_pulse_dependency(is_property=True) - def calibrations(self, calibrations: dict): - """Set the circuit calibration data from a dictionary of calibration definition. - - Args: - calibrations (dict): A dictionary of input in the format - ``{'gate_name': {(qubits, gate_params): schedule}}`` - """ - self._calibrations_prop = calibrations - - @property - def _calibrations_prop(self) -> dict: - """An alternative private path to the `calibrations` property for - avoiding deprecation warnings.""" - return dict(self._calibrations) - - @_calibrations_prop.setter - def _calibrations_prop(self, calibrations: dict): - """An alternative private path to the `calibrations` property for - avoiding deprecation warnings.""" - self._calibrations = defaultdict(dict, calibrations) - - @deprecate_pulse_dependency - def has_calibration_for(self, instruction: CircuitInstruction | tuple): - """Return True if the circuit has a calibration defined for the instruction context. In this - case, the operation does not need to be translated to the device basis. - """ - - return self._has_calibration_for(instruction) - - def _has_calibration_for(self, instruction: CircuitInstruction | tuple): - """An alternative private path to the `has_calibration_for` method for - avoiding deprecation warnings.""" - if isinstance(instruction, CircuitInstruction): - operation = instruction.operation - qubits = instruction.qubits - else: - operation, qubits, _ = instruction - if not self._calibrations_prop or operation.name not in self._calibrations_prop: - return False - qubits = tuple(self.qubits.index(qubit) for qubit in qubits) - params = [] - for p in operation.params: - if isinstance(p, ParameterExpression) and not p.parameters: - params.append(float(p)) - else: - params.append(p) - params = tuple(params) - return (qubits, params) in self._calibrations_prop[operation.name] - @property def metadata(self) -> dict: """The user provided metadata associated with the circuit. @@ -2030,9 +1950,6 @@ def replace_var(var: expr.Var, cache: Mapping[expr.Var, expr.Var]) -> expr.Var: ) edge_map.update(zip(other.clbits, dest._cbit_argument_conversion(clbits))) - for gate, cals in other._calibrations_prop.items(): - dest._calibrations[gate].update(cals) - dest.duration = None dest.unit = "dt" dest.global_phase += other.global_phase @@ -3712,7 +3629,7 @@ def copy_empty_like( That structure includes: - * name, calibrations and other metadata + * name and other metadata * global phase * all the qubits and clbits, including the registers * the realtime variables defined in the circuit, handled according to the ``vars`` keyword @@ -3766,7 +3683,7 @@ def copy_empty_like( def clear(self) -> None: """Clear all instructions in self. - Clearing the circuits will keep the metadata and calibrations. + Clearing the circuits will keep the metadata. .. seealso:: :meth:`copy_empty_like` @@ -4381,70 +4298,10 @@ def assign_parameters( # pylint: disable=missing-raises-doc " the circuit." ) - def create_mapping_view(): - return raw_mapping - target._data.assign_parameters_mapping(raw_mapping) else: - # This should be a cache retrieval, since we warmed the cache. We need to keep hold of - # what the parameters were before anything is assigned, because we assign parameters in - # the calibrations (which aren't tracked in the internal parameter table) after, which - # would change what we create. We don't make the full Python-space mapping object of - # parameters to values eagerly because 99.9% of the time we don't need it, and it's - # relatively expensive to do for large numbers of parameters. - initial_parameters = target._data.parameters - - def create_mapping_view(): - return dict(zip(initial_parameters, parameters)) - target._data.assign_parameters_iterable(parameters) - # Finally, assign the parameters inside any of the calibrations. We don't track these in - # the `ParameterTable`, so we manually reconstruct things. We lazily construct the mapping - # `{parameter: bound_value}` the first time we encounter a binding (we have to scan for - # this, because calibrations don't use a parameter-table lookup), rather than always paying - # the cost - most circuits don't have parametric calibrations, and it's expensive. - mapping_view = None - - def map_calibration(qubits, parameters, schedule): - # All calls to this function should share the same `{Parameter: bound_value}` mapping, - # which we only want to lazily construct a single time. - nonlocal mapping_view - if mapping_view is None: - mapping_view = create_mapping_view() - - modified = False - new_parameters = list(parameters) - for i, parameter in enumerate(new_parameters): - if not isinstance(parameter, ParameterExpression): - continue - if not (contained := parameter.parameters & mapping_view.keys()): - continue - for to_bind in contained: - parameter = parameter.assign(to_bind, mapping_view[to_bind]) - if not parameter.parameters: - parameter = parameter.numeric() - if isinstance(parameter, complex): - raise TypeError(f"Calibration cannot use complex number: '{parameter}'") - new_parameters[i] = parameter - modified = True - if modified: - schedule.assign_parameters(mapping_view) - return (qubits, tuple(new_parameters)), schedule - - target._calibrations = defaultdict( - dict, - ( - ( - gate, - dict( - map_calibration(qubits, parameters, schedule) - for (qubits, parameters), schedule in calibrations.items() - ), - ) - for gate, calibrations in target._calibrations.items() - ), - ) return None if inplace else target def _unroll_param_dict( @@ -6770,57 +6627,6 @@ def continue_loop(self) -> InstructionSet: ContinueLoopOp(self.num_qubits, self.num_clbits), self.qubits, self.clbits, copy=False ) - @deprecate_pulse_dependency - def add_calibration( - self, - gate: Union[Gate, str], - qubits: Sequence[int], - # Schedule has the type `qiskit.pulse.Schedule`, but `qiskit.pulse` cannot be imported - # while this module is, and so Sphinx will not accept a forward reference to it. Sphinx - # needs the types available at runtime, whereas mypy will accept it, because it handles the - # type checking by static analysis. - schedule, - params: Sequence[ParameterValueType] | None = None, - ) -> None: - """Register a low-level, custom pulse definition for the given gate. - - Args: - gate (Union[Gate, str]): Gate information. - qubits (Union[int, Tuple[int]]): List of qubits to be measured. - schedule (Schedule): Schedule information. - params (Optional[List[Union[float, Parameter]]]): A list of parameters. - - Raises: - Exception: if the gate is of type string and params is None. - """ - - def _format(operand): - try: - # Using float/complex value as a dict key is not good idea. - # This makes the mapping quite sensitive to the rounding error. - # However, the mechanism is already tied to the execution model (i.e. pulse gate) - # and we cannot easily update this rule. - # The same logic exists in DAGCircuit.add_calibration. - evaluated = complex(operand) - if np.isreal(evaluated): - evaluated = float(evaluated.real) - if evaluated.is_integer(): - evaluated = int(evaluated) - return evaluated - except TypeError: - # Unassigned parameter - return operand - - if isinstance(gate, Gate): - params = gate.params - gate = gate.name - if params is not None: - params = tuple(map(_format, params)) - else: - params = () - - self._calibrations[gate][(tuple(qubits), params)] = schedule - # Functions only for scheduled circuits def qubit_duration(self, *qubits: Union[Qubit, int]) -> float: """Return the duration between the start and stop time of the first and last instructions, @@ -7093,5 +6899,4 @@ def _copy_metadata(original, cpy, vars_mode): else: # pragma: no cover raise ValueError(f"unknown vars_mode: '{vars_mode}'") - cpy._calibrations = _copy.deepcopy(original._calibrations) cpy._metadata = _copy.deepcopy(original._metadata) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 4d705481ce71..16e1da4a851b 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -66,12 +66,10 @@ additional_msg="The `target` parameter should be used instead. You can build a `Target` instance " "with defined properties with Target.from_configuration(..., backend_properties=...)", ) -@deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None) def transpile( # pylint: disable=too-many-return-statements circuits: _CircuitT, backend: Optional[Backend] = None, basis_gates: Optional[List[str]] = None, - inst_map: Optional[List[InstructionScheduleMap]] = None, coupling_map: Optional[Union[CouplingMap, List[List[int]]]] = None, backend_properties: Optional[BackendProperties] = None, initial_layout: Optional[Union[Layout, Dict, List]] = None, @@ -133,12 +131,6 @@ def transpile( # pylint: disable=too-many-return-statements will override the backend's. basis_gates: List of basis gate names to unroll to (e.g: ``['u1', 'u2', 'u3', 'cx']``). If ``None``, do not unroll. - inst_map: DEPRECATED. Mapping of unrolled gates to pulse schedules. If this is not provided, - transpiler tries to get from the backend. If any user defined calibration - is found in the map and this is used in a circuit, transpiler attaches - the custom gate definition to the circuit. This enables one to flexibly - override the low-level instruction implementation. This feature is available - iff the backend supports the pulse gate experiment. coupling_map: Directed coupling map (perhaps custom) to target in mapping. If the coupling map is symmetric, both directions need to be specified. @@ -429,7 +421,6 @@ def callback_func(**kwargs): instruction_durations=instruction_durations, backend_properties=backend_properties, timing_constraints=timing_constraints, - inst_map=inst_map, initial_layout=initial_layout, layout_method=layout_method, routing_method=routing_method, diff --git a/qiskit/converters/circuit_to_dagdependency.py b/qiskit/converters/circuit_to_dagdependency.py index e617cf30c4fd..26b5896f69f4 100644 --- a/qiskit/converters/circuit_to_dagdependency.py +++ b/qiskit/converters/circuit_to_dagdependency.py @@ -46,6 +46,4 @@ def circuit_to_dagdependency(circuit, create_preds_and_succs=True): dagdependency._add_predecessors() dagdependency._add_successors() - dagdependency._calibrations = circuit._calibrations_prop - return dagdependency diff --git a/qiskit/converters/circuit_to_dagdependency_v2.py b/qiskit/converters/circuit_to_dagdependency_v2.py index f55a0cf6716c..32a429ab1bac 100644 --- a/qiskit/converters/circuit_to_dagdependency_v2.py +++ b/qiskit/converters/circuit_to_dagdependency_v2.py @@ -27,7 +27,6 @@ def _circuit_to_dagdependency_v2(circuit): dagdependency = _DAGDependencyV2() dagdependency.name = circuit.name dagdependency.metadata = circuit.metadata - dagdependency._calibrations = circuit._calibrations_prop dagdependency.global_phase = circuit.global_phase dagdependency.add_qubits(circuit.qubits) diff --git a/qiskit/converters/dag_to_circuit.py b/qiskit/converters/dag_to_circuit.py index 5c81dd07a6bf..e05de30fb6f2 100644 --- a/qiskit/converters/dag_to_circuit.py +++ b/qiskit/converters/dag_to_circuit.py @@ -71,7 +71,6 @@ def dag_to_circuit(dag, copy_operations=True): for var in dag.iter_declared_vars(): circuit.add_uninitialized_var(var) circuit.metadata = dag.metadata - circuit._calibrations_prop = dag._calibrations_prop circuit._data = circuit_data diff --git a/qiskit/converters/dag_to_dagdependency.py b/qiskit/converters/dag_to_dagdependency.py index 19fb70a31bd6..55f88b0883d8 100644 --- a/qiskit/converters/dag_to_dagdependency.py +++ b/qiskit/converters/dag_to_dagdependency.py @@ -50,6 +50,5 @@ def dag_to_dagdependency(dag, create_preds_and_succs=True): # copy metadata dagdependency.global_phase = dag.global_phase - dagdependency._calibrations_prop = dag._calibrations_prop return dagdependency diff --git a/qiskit/converters/dag_to_dagdependency_v2.py b/qiskit/converters/dag_to_dagdependency_v2.py index 29eb12300f00..ce38f378aec1 100644 --- a/qiskit/converters/dag_to_dagdependency_v2.py +++ b/qiskit/converters/dag_to_dagdependency_v2.py @@ -27,7 +27,6 @@ def _dag_to_dagdependency_v2(dag): dagdependency.name = dag.name dagdependency.metadata = dag.metadata dagdependency.global_phase = dag.global_phase - dagdependency.calibrations = dag._calibrations_prop dagdependency.add_qubits(dag.qubits) dagdependency.add_clbits(dag.clbits) diff --git a/qiskit/converters/dagdependency_to_circuit.py b/qiskit/converters/dagdependency_to_circuit.py index 541207d175d2..9599adb84ac6 100644 --- a/qiskit/converters/dagdependency_to_circuit.py +++ b/qiskit/converters/dagdependency_to_circuit.py @@ -34,12 +34,6 @@ def dagdependency_to_circuit(dagdependency): ) circuit.metadata = dagdependency.metadata - if hasattr(dagdependency, "_calibrations_prop"): - circuit._calibrations_prop = dagdependency._calibrations_prop - else: - # This can be _DAGDependencyV2 - circuit._calibrations_prop = dagdependency.calibrations - for node in dagdependency.topological_nodes(): circuit._append(CircuitInstruction(node.op.copy(), node.qargs, node.cargs)) diff --git a/qiskit/converters/dagdependency_to_dag.py b/qiskit/converters/dagdependency_to_dag.py index 3b500d82c884..932d926ca10a 100644 --- a/qiskit/converters/dagdependency_to_dag.py +++ b/qiskit/converters/dagdependency_to_dag.py @@ -45,10 +45,5 @@ def dagdependency_to_dag(dagdependency): # copy metadata dagcircuit.global_phase = dagdependency.global_phase - if isinstance(dagdependency, DAGDependency): - dagcircuit._calibrations_prop = dagdependency._calibrations_prop - else: - # This can be _DAGDependencyV2 - dagcircuit._calibrations_prop = dagdependency.calibrations return dagcircuit diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 63e9114a6ca1..1ca633b0e9b4 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -115,7 +115,6 @@ def __init__(self): self.clbits = [] self._global_phase: float | ParameterExpression = 0.0 - self._calibrations: dict[str, dict[tuple, Schedule]] = defaultdict(dict) self.duration = None self.unit = "dt" @@ -146,37 +145,6 @@ def global_phase(self, angle: float | ParameterExpression): else: self._global_phase = angle % (2 * math.pi) - @property - @deprecate_pulse_dependency(is_property=True) - def calibrations(self) -> dict[str, dict[tuple, Schedule]]: - """Return calibration dictionary. - - The custom pulse definition of a given gate is of the form - ``{'gate_name': {(qubits, params): schedule}}``. - """ - return self._calibrations_prop - - @calibrations.setter - @deprecate_pulse_dependency(is_property=True) - def calibrations(self, calibrations: dict[str, dict[tuple, Schedule]]): - """Set the circuit calibration data from a dictionary of calibration definition. - - Args: - calibrations (dict): A dictionary of input in the format - {'gate_name': {(qubits, gate_params): schedule}} - """ - self._calibrations_prop = calibrations - - @property - def _calibrations_prop(self) -> dict[str, dict[tuple, Schedule]]: - """An alternative path to be used internally to avoid deprecation warnings""" - return dict(self._calibrations) - - @_calibrations_prop.setter - def _calibrations_prop(self, calibrations: dict[str, dict[tuple, Schedule]]): - """An alternative path to be used internally to avoid deprecation warnings""" - self._calibrations = defaultdict(dict, calibrations) - def to_retworkx(self): """Returns the DAGDependency in retworkx format.""" return self._multi_graph @@ -544,7 +512,7 @@ def draw(self, scale=0.7, filename=None, style="color"): Graphviz ` to be installed. Args: - scale (float): scaling factor + scale (float): sng factor filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py index 6389ecddad89..5e174e5e12d3 100644 --- a/qiskit/dagcircuit/dagdependency_v2.py +++ b/qiskit/dagcircuit/dagdependency_v2.py @@ -110,7 +110,6 @@ def __init__(self): self._clbit_indices: Dict[Clbit, BitLocations] = {} self._global_phase = 0 - self._calibrations = defaultdict(dict) # Map of number of each kind of op, keyed on op name self._op_names = {} @@ -142,81 +141,6 @@ def global_phase(self, angle): else: self._global_phase = angle % (2 * math.pi) - @property - def calibrations(self): - """Return calibration dictionary. - - The custom pulse definition of a given gate is of the form - ``{'gate_name': {(qubits, params): schedule}}``. - """ - return dict(self._calibrations) - - @calibrations.setter - def calibrations(self, calibrations): - """Set the circuit calibration data from a dictionary of calibration definition. - - Args: - calibrations (dict): A dictionary of input in the format - {'gate_name': {(qubits, gate_params): schedule}} - """ - self._calibrations = defaultdict(dict, calibrations) - - def add_calibration(self, gate, qubits, schedule, params=None): - """Register a low-level, custom pulse definition for the given gate. - - Args: - gate (Union[Gate, str]): Gate information. - qubits (Union[int, Tuple[int]]): List of qubits to be measured. - schedule (Schedule): Schedule information. - params (Optional[List[Union[float, Parameter]]]): A list of parameters. - - Raises: - Exception: if the gate is of type string and params is None. - """ - - def _format(operand): - try: - # Using float/complex value as a dict key is not good idea. - # This makes the mapping quite sensitive to the rounding error. - # However, the mechanism is already tied to the execution model (i.e. pulse gate) - # and we cannot easily update this rule. - # The same logic exists in QuantumCircuit.add_calibration. - evaluated = complex(operand) - if np.isreal(evaluated): - evaluated = float(evaluated.real) - if evaluated.is_integer(): - evaluated = int(evaluated) - return evaluated - except TypeError: - # Unassigned parameter - return operand - - if isinstance(gate, Gate): - params = gate.params - gate = gate.name - if params is not None: - params = tuple(map(_format, params)) - else: - params = () - - self._calibrations[gate][(tuple(qubits), params)] = schedule - - def has_calibration_for(self, node): - """Return True if the dag has a calibration defined for the node operation. In this - case, the operation does not need to be translated to the device basis. - """ - if not self.calibrations or node.op.name not in self.calibrations: - return False - qubits = tuple(self.qubits.index(qubit) for qubit in node.qargs) - params = [] - for p in node.op.params: - if isinstance(p, ParameterExpression) and not p.parameters: - params.append(float(p)) - else: - params.append(p) - params = tuple(params) - return (qubits, params) in self.calibrations[node.op.name] - def size(self): """Returns the number of gates in the circuit""" return len(self._multi_graph) diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py index 571500fa35e9..df6fa3e2d145 100644 --- a/qiskit/providers/backend_compat.py +++ b/qiskit/providers/backend_compat.py @@ -22,20 +22,16 @@ from qiskit.providers.models.backendconfiguration import BackendConfiguration from qiskit.providers.models.backendproperties import BackendProperties from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES, get_control_flow_name_mapping -from qiskit.providers.models.pulsedefaults import PulseDefaults from qiskit.providers.options import Options from qiskit.providers.exceptions import BackendPropertyError -from qiskit.utils.deprecate_pulse import deprecate_pulse_arg, deprecate_pulse_dependency logger = logging.getLogger(__name__) -@deprecate_pulse_arg("defaults") def convert_to_target( configuration: BackendConfiguration, properties: BackendProperties = None, - defaults: PulseDefaults = None, custom_name_mapping: Optional[Dict[str, Any]] = None, add_delay: bool = True, filter_faulty: bool = True, @@ -43,36 +39,18 @@ def convert_to_target( """Decode transpiler target from backend data set. This function generates :class:`.Target`` instance from intermediate - legacy objects such as :class:`.BackendProperties` and :class:`.PulseDefaults`. + legacy objects such as :class:`.BackendProperties`. These objects are usually components of the legacy :class:`.BackendV1` model. Args: configuration: Backend configuration as ``BackendConfiguration`` properties: Backend property dictionary or ``BackendProperties`` - defaults: DEPRECATED. Backend pulse defaults dictionary or ``PulseDefaults`` - custom_name_mapping: A name mapping must be supplied for the operation - not included in Qiskit Standard Gate name mapping, otherwise the operation - will be dropped in the resulting ``Target`` object. add_delay: If True, adds delay to the instruction set. filter_faulty: If True, this filters the non-operational qubits. Returns: A ``Target`` instance. """ - return _convert_to_target( - configuration, properties, defaults, custom_name_mapping, add_delay, filter_faulty - ) - - -def _convert_to_target( - configuration: BackendConfiguration, - properties: BackendProperties = None, - defaults: PulseDefaults = None, - custom_name_mapping: Optional[Dict[str, Any]] = None, - add_delay: bool = True, - filter_faulty: bool = True, -): - """An alternative private path to avoid pulse deprecations""" # importing packages where they are needed, to avoid cyclic-import. # pylint: disable=cyclic-import from qiskit.transpiler.target import ( @@ -248,44 +226,6 @@ def _get_value(prop_dict, prop_name): if not filter_faulty or (q not in faulty_qubits) } - if defaults: - inst_sched_map = defaults.instruction_schedule_map - - for name in inst_sched_map.instructions: - for qubits in inst_sched_map.qubits_with_instruction(name): - if not isinstance(qubits, tuple): - qubits = (qubits,) - if ( - name not in all_instructions - or name not in prop_name_map - or prop_name_map[name] is None - 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_sched_map._get_calibration_entry(name, qubits) - try: - prop_name_map[name][qubits]._calibration_prop = entry - except AttributeError: - # if instruction properties are "None", add entry - prop_name_map[name].update({qubits: InstructionProperties(None, None, entry)}) - logger.info( - "The PulseDefaults payload received contains an instruction %s on " - "qubits %s which is not present in the configuration or properties payload." - "A new properties entry will be added to include the new calibration data.", - name, - qubits, - ) # Add parsed properties to target target = Target(**in_data) for inst_name in all_instructions: @@ -345,21 +285,6 @@ class BackendV2Converter(BackendV2): common access patterns between :class:`~.BackendV1` and :class:`~.BackendV2`. This class should only be used if you need a :class:`~.BackendV2` and still need compatibility with :class:`~.BackendV1`. - - When using custom calibrations (or other custom workflows) it is **not** recommended - to mutate the ``BackendV1`` object before applying this converter. For example, in order to - convert a ``BackendV1`` object with a customized ``defaults().instruction_schedule_map``, - which has a custom calibration for an operation, the operation name must be in - ``configuration().basis_gates`` and ``name_mapping`` must be supplied for the operation. - Otherwise, the operation will be dropped in the resulting ``BackendV2`` object. - - Instead it is typically better to add custom calibrations **after** applying this converter - instead of updating ``BackendV1.defaults()`` in advance. For example:: - - backend_v2 = BackendV2Converter(backend_v1) - backend_v2.target.add_instruction( - custom_gate, {(0, 1): InstructionProperties(calibration=custom_sched)} - ) """ def __init__( @@ -420,7 +345,7 @@ def target(self): :rtype: Target """ if self._target is None: - self._target = _convert_to_target( + self._target = convert_to_target( configuration=self._config, properties=self._properties, defaults=self._defaults, @@ -446,21 +371,5 @@ def dtm(self) -> float: def meas_map(self) -> List[List[int]]: return self._config.meas_map - @deprecate_pulse_dependency - def drive_channel(self, qubit: int): - return self._config.drive(qubit) - - @deprecate_pulse_dependency - def measure_channel(self, qubit: int): - return self._config.measure(qubit) - - @deprecate_pulse_dependency - def acquire_channel(self, qubit: int): - return self._config.acquire(qubit) - - @deprecate_pulse_dependency - def control_channel(self, qubits: Iterable[int]): - return self._config.control(qubits) - def run(self, run_input, **options): return self._backend.run(run_input, **options) diff --git a/qiskit/providers/basic_provider/basic_simulator.py b/qiskit/providers/basic_provider/basic_simulator.py index 6025cae3df7c..ee7b5df74fce 100644 --- a/qiskit/providers/basic_provider/basic_simulator.py +++ b/qiskit/providers/basic_provider/basic_simulator.py @@ -125,7 +125,7 @@ def target(self) -> Target: def _build_basic_target(self) -> Target: """Helper method that returns a minimal target with a basis gate set but - no coupling map, instruction properties or calibrations. + no coupling map or instruction properties. Returns: The configured target. diff --git a/qiskit/providers/fake_provider/generic_backend_v2.py b/qiskit/providers/fake_provider/generic_backend_v2.py index 28a1061a9499..4d4aded058b7 100644 --- a/qiskit/providers/fake_provider/generic_backend_v2.py +++ b/qiskit/providers/fake_provider/generic_backend_v2.py @@ -300,7 +300,7 @@ def _add_noisy_instruction_to_target( rounded_duration = round(duration / dt) * dt # Clamp rounded duration to be between min and max values duration = max(noise_params[0], min(rounded_duration, noise_params[1])) - props.update({qargs: InstructionProperties(duration, error, None)}) + props.update({qargs: InstructionProperties(duration, error)}) self._target.add_instruction(instruction, props) diff --git a/qiskit/providers/fake_provider/utils/backend_converter.py b/qiskit/providers/fake_provider/utils/backend_converter.py index 9e038c39df38..bd0ccd3aae58 100644 --- a/qiskit/providers/fake_provider/utils/backend_converter.py +++ b/qiskit/providers/fake_provider/utils/backend_converter.py @@ -26,7 +26,7 @@ from qiskit.circuit.reset import Reset from qiskit.providers.models.pulsedefaults import PulseDefaults - +# TODO: do we need this function? def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict = None) -> Target: """Uses configuration, properties and pulse defaults dicts to construct and return Target class. @@ -44,7 +44,7 @@ def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict if props_dict: qubit_props = qubit_props_from_props(props_dict) target = Target(qubit_properties=qubit_props, concurrent_measurements=conf_dict.get("meas_map")) - # Parse from properties if it exsits + # Parse from properties if it exists if props_dict is not None: # Parse instructions gates = {} diff --git a/qiskit/scheduler/config.py b/qiskit/scheduler/config.py deleted file mode 100644 index a1c5ba9a2c59..000000000000 --- a/qiskit/scheduler/config.py +++ /dev/null @@ -1,37 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Scheduling container classes.""" - -from typing import List - -from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap -from qiskit.pulse.utils import format_meas_map -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency - - -class ScheduleConfig: - """Configuration for pulse scheduling.""" - - @deprecate_pulse_dependency(moving_to_dynamics=True) - def __init__(self, inst_map: InstructionScheduleMap, meas_map: List[List[int]], dt: float): - """ - Container for information needed to schedule a QuantumCircuit into a pulse Schedule. - - Args: - inst_map: The schedule definition of all gates supported on a backend. - meas_map: A list of groups of qubits which have to be measured together. - dt: Sample duration. - """ - self.inst_map = inst_map - self.meas_map = format_meas_map(meas_map) - self.dt = dt diff --git a/qiskit/scheduler/lowering.py b/qiskit/scheduler/lowering.py deleted file mode 100644 index f0fb33957d9e..000000000000 --- a/qiskit/scheduler/lowering.py +++ /dev/null @@ -1,187 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Lower gates to schedules. The relative timing within gates is respected. This -module handles the translation, but does not handle timing. -""" -from collections import namedtuple -from typing import Dict, List, Optional, Union - -from qiskit.circuit.barrier import Barrier -from qiskit.circuit.delay import Delay -from qiskit.circuit.duration import convert_durations_to_dt -from qiskit.circuit.measure import Measure -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.exceptions import QiskitError -from qiskit.pulse import Schedule -from qiskit.pulse import instructions as pulse_inst -from qiskit.pulse.channels import AcquireChannel, MemorySlot, DriveChannel -from qiskit.pulse.exceptions import PulseError -from qiskit.pulse.macros import measure -from qiskit.scheduler.config import ScheduleConfig -from qiskit.providers import BackendV1, BackendV2 - -CircuitPulseDef = namedtuple( - "CircuitPulseDef", - ["schedule", "qubits"], # The schedule which implements the quantum circuit command -) # The labels of the qubits involved in the command according to the circuit - - -def lower_gates( - circuit: QuantumCircuit, - schedule_config: ScheduleConfig, - backend: Optional[Union[BackendV1, BackendV2]] = None, -) -> List[CircuitPulseDef]: - """ - Return a list of Schedules and the qubits they operate on, for each element encountered in the - input circuit. - - Without concern for the final schedule, extract and return a list of Schedules and the qubits - they operate on, for each element encountered in the input circuit. Measures are grouped when - possible, so ``qc.measure(q0, c0)`` or ``qc.measure(q1, c1)`` will generate a synchronous - measurement pulse. - - Args: - circuit: The quantum circuit to translate. - schedule_config: Backend specific parameters used for building the Schedule. - backend: Pass in the backend used to build the Schedule, the backend could be BackendV1 - or BackendV2 - - Returns: - A list of CircuitPulseDefs: the pulse definition for each circuit element. - - Raises: - QiskitError: If circuit uses a command that isn't defined in config.inst_map. - """ - from qiskit.pulse.transforms.base_transforms import target_qobj_transform - - 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) - - def get_measure_schedule(qubit_mem_slots: Dict[int, int]) -> CircuitPulseDef: - """Create a schedule to measure the qubits queued for measuring.""" - sched = Schedule() - # Exclude acquisition on these qubits, since they are handled by the user calibrations - acquire_excludes = {} - if Measure().name in circuit.calibrations.keys(): - qubits = tuple(sorted(qubit_mem_slots.keys())) - params = () - for qubit in qubits: - try: - meas_q = circuit.calibrations[Measure().name][((qubit,), params)] - meas_q = target_qobj_transform(meas_q) - acquire_q = meas_q.filter(channels=[AcquireChannel(qubit)]) - mem_slot_index = [ - chan.index for chan in acquire_q.channels if isinstance(chan, MemorySlot) - ][0] - if mem_slot_index != qubit_mem_slots[qubit]: - raise KeyError( - "The measurement calibration is not defined on " - "the requested classical bits" - ) - sched |= meas_q - del qubit_mem_slots[qubit] - acquire_excludes[qubit] = mem_slot_index - except KeyError: - pass - - if qubit_mem_slots: - qubits = list(qubit_mem_slots.keys()) - qubit_mem_slots.update(acquire_excludes) - meas_sched = measure( - qubits=qubits, - backend=backend, - inst_map=inst_map, - meas_map=schedule_config.meas_map, - qubit_mem_slots=qubit_mem_slots, - ) - meas_sched = target_qobj_transform(meas_sched) - meas_sched = meas_sched.exclude( - channels=[AcquireChannel(qubit) for qubit in acquire_excludes] - ) - sched |= meas_sched - qubit_mem_slots.clear() - return CircuitPulseDef( - schedule=sched, - qubits=[chan.index for chan in sched.channels if isinstance(chan, AcquireChannel)], - ) - - qubit_indices = {bit: idx for idx, bit in enumerate(circuit.qubits)} - clbit_indices = {bit: idx for idx, bit in enumerate(circuit.clbits)} - - for instruction in circuit.data: - inst_qubits = [qubit_indices[qubit] for qubit in instruction.qubits] - - if any(q in qubit_mem_slots for q in inst_qubits): - # If we are operating on a qubit that was scheduled to be measured, process that first - circ_pulse_defs.append(get_measure_schedule(qubit_mem_slots)) - - if isinstance(instruction.operation, Barrier): - circ_pulse_defs.append( - CircuitPulseDef(schedule=instruction.operation, qubits=inst_qubits) - ) - elif isinstance(instruction.operation, Delay): - sched = Schedule(name=instruction.operation.name) - for qubit in inst_qubits: - for channel in [DriveChannel]: - sched += pulse_inst.Delay( - duration=instruction.operation.duration, channel=channel(qubit) - ) - circ_pulse_defs.append(CircuitPulseDef(schedule=sched, qubits=inst_qubits)) - elif isinstance(instruction.operation, Measure): - if len(inst_qubits) != 1 and len(instruction.clbits) != 1: - raise QiskitError( - f"Qubit '{inst_qubits}' or classical bit '{instruction.clbits}' errored because the " - "circuit Measure instruction only takes one of " - "each." - ) - qubit_mem_slots[inst_qubits[0]] = clbit_indices[instruction.clbits[0]] - else: - try: - gate_cals = circuit.calibrations[instruction.operation.name] - schedule = gate_cals[ - ( - tuple(inst_qubits), - tuple( - p if getattr(p, "parameters", None) else float(p) - for p in instruction.operation.params - ), - ) - ] - schedule = target_qobj_transform(schedule) - circ_pulse_defs.append(CircuitPulseDef(schedule=schedule, qubits=inst_qubits)) - continue - except KeyError: - pass # Calibration not defined for this operation - - try: - schedule = inst_map.get( - instruction.operation, inst_qubits, *instruction.operation.params - ) - schedule = target_qobj_transform(schedule) - circ_pulse_defs.append(CircuitPulseDef(schedule=schedule, qubits=inst_qubits)) - except PulseError as ex: - raise QiskitError( - f"Operation '{instruction.operation.name}' on qubit(s) {inst_qubits} " - "not supported by the backend command definition. Did you remember to " - "transpile your input circuit for the same backend?" - ) from ex - - if qubit_mem_slots: - circ_pulse_defs.append(get_measure_schedule(qubit_mem_slots)) - - return circ_pulse_defs diff --git a/qiskit/scheduler/methods/__init__.py b/qiskit/scheduler/methods/__init__.py deleted file mode 100644 index 6df887d54995..000000000000 --- a/qiskit/scheduler/methods/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Scheduling methods.""" - -from qiskit.scheduler.methods.basic import as_soon_as_possible, as_late_as_possible diff --git a/qiskit/scheduler/methods/basic.py b/qiskit/scheduler/methods/basic.py deleted file mode 100644 index b08f0f866ab0..000000000000 --- a/qiskit/scheduler/methods/basic.py +++ /dev/null @@ -1,140 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -The most straightforward scheduling methods: scheduling **as early** or **as late** as possible. -""" -from collections import defaultdict -from typing import List, Optional, Union - -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.circuit.barrier import Barrier -from qiskit.pulse.schedule import Schedule - -from qiskit.scheduler.config import ScheduleConfig -from qiskit.scheduler.lowering import lower_gates -from qiskit.providers import BackendV1, BackendV2 -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency - - -@deprecate_pulse_dependency(moving_to_dynamics=True) -def as_soon_as_possible( - circuit: QuantumCircuit, - schedule_config: ScheduleConfig, - backend: Optional[Union[BackendV1, BackendV2]] = None, -) -> Schedule: - """ - Return the pulse Schedule which implements the input circuit using an "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. - - Args: - circuit: The quantum circuit to translate. - schedule_config: Backend specific parameters used for building the Schedule. - backend: A backend used to build the Schedule, the backend could be BackendV1 - or BackendV2. - - Returns: - A schedule corresponding to the input ``circuit`` with pulses occurring as early as - possible. - """ - qubit_time_available = defaultdict(int) - - def update_times(inst_qubits: List[int], time: int = 0) -> None: - """Update the time tracker for all inst_qubits to the given time.""" - for q in inst_qubits: - qubit_time_available[q] = time - - start_times = [] - circ_pulse_defs = lower_gates(circuit, schedule_config, backend) - for circ_pulse_def in circ_pulse_defs: - start_time = max(qubit_time_available[q] for q in circ_pulse_def.qubits) - stop_time = start_time - if not isinstance(circ_pulse_def.schedule, Barrier): - stop_time += circ_pulse_def.schedule.duration - - start_times.append(start_time) - update_times(circ_pulse_def.qubits, stop_time) - - timed_schedules = [ - (time, cpd.schedule) - for time, cpd in zip(start_times, circ_pulse_defs) - if not isinstance(cpd.schedule, Barrier) - ] - schedule = Schedule.initialize_from(circuit) - for time, inst in timed_schedules: - schedule.insert(time, inst, inplace=True) - return schedule - - -@deprecate_pulse_dependency(moving_to_dynamics=True) -def as_late_as_possible( - circuit: QuantumCircuit, - schedule_config: ScheduleConfig, - backend: Optional[Union[BackendV1, BackendV2]] = None, -) -> Schedule: - """ - Return the pulse Schedule which implements the input circuit using an "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. - - This method should improves the outcome fidelity over ASAP scheduling, because we may - maximize the time that the qubit remains in the ground state. - - Args: - circuit: The quantum circuit to translate. - schedule_config: Backend specific parameters used for building the Schedule. - backend: A backend used to build the Schedule, the backend could be BackendV1 - or BackendV2. - - Returns: - A schedule corresponding to the input ``circuit`` with pulses occurring as late as - possible. - """ - qubit_time_available = defaultdict(int) - - def update_times(inst_qubits: List[int], time: int = 0) -> None: - """Update the time tracker for all inst_qubits to the given time.""" - for q in inst_qubits: - qubit_time_available[q] = time - - rev_stop_times = [] - circ_pulse_defs = lower_gates(circuit, schedule_config, backend) - for circ_pulse_def in reversed(circ_pulse_defs): - start_time = max(qubit_time_available[q] for q in circ_pulse_def.qubits) - stop_time = start_time - if not isinstance(circ_pulse_def.schedule, Barrier): - stop_time += circ_pulse_def.schedule.duration - - rev_stop_times.append(stop_time) - update_times(circ_pulse_def.qubits, stop_time) - - last_stop = max(t for t in qubit_time_available.values()) if qubit_time_available else 0 - start_times = [last_stop - t for t in reversed(rev_stop_times)] - - timed_schedules = [ - (time, cpd.schedule) - for time, cpd in zip(start_times, circ_pulse_defs) - if not isinstance(cpd.schedule, Barrier) - ] - schedule = Schedule.initialize_from(circuit) - for time, inst in timed_schedules: - schedule.insert(time, inst, inplace=True) - return schedule diff --git a/qiskit/scheduler/schedule_circuit.py b/qiskit/scheduler/schedule_circuit.py deleted file mode 100644 index 2cc32a8a7b3c..000000000000 --- a/qiskit/scheduler/schedule_circuit.py +++ /dev/null @@ -1,69 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""QuantumCircuit to Pulse scheduler.""" -from typing import Optional, Union - -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.exceptions import QiskitError - -from qiskit.pulse.schedule import Schedule -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 -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency - - -@deprecate_pulse_dependency(moving_to_dynamics=True) -def schedule_circuit( - circuit: QuantumCircuit, - schedule_config: ScheduleConfig, - method: Optional[str] = None, - backend: Optional[Union[BackendV1, BackendV2]] = None, -) -> Schedule: - """ - Basic scheduling pass from a circuit to a pulse Schedule, using the backend. If no method is - specified, then a basic, as late as possible scheduling pass is performed, i.e. pulses are - scheduled to occur as late as possible. - - Supported methods: - - * ``'as_soon_as_possible'``: Schedule pulses greedily, as early as possible on a - qubit resource. (alias: ``'asap'``) - * ``'as_late_as_possible'``: Schedule pulses late-- keep qubits in the ground state when - possible. (alias: ``'alap'``) - - Args: - circuit: The quantum circuit to translate. - schedule_config: Backend specific parameters used for building the Schedule. - method: The scheduling pass method to use. - backend: A backend used to build the Schedule, the backend could be BackendV1 - or BackendV2. - - Returns: - Schedule corresponding to the input circuit. - - Raises: - QiskitError: If method isn't recognized. - """ - methods = { - "as_soon_as_possible": as_soon_as_possible, - "asap": as_soon_as_possible, - "as_late_as_possible": as_late_as_possible, - "alap": as_late_as_possible, - } - if method is None: - method = "as_late_as_possible" - try: - return methods[method](circuit, schedule_config, backend) - except KeyError as ex: - raise QiskitError(f"Scheduling method {method} isn't recognized.") from ex diff --git a/qiskit/scheduler/sequence.py b/qiskit/scheduler/sequence.py deleted file mode 100644 index 7f69f5f65a6a..000000000000 --- a/qiskit/scheduler/sequence.py +++ /dev/null @@ -1,104 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -Mapping a scheduled QuantumCircuit to a pulse Schedule. -""" -from collections import defaultdict - -from typing import Optional, Union -from qiskit.circuit.barrier import Barrier -from qiskit.circuit.measure import Measure -from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.exceptions import QiskitError -from qiskit.pulse.schedule import Schedule -from qiskit.pulse.transforms import pad -from qiskit.scheduler.config import ScheduleConfig -from qiskit.scheduler.lowering import lower_gates -from qiskit.providers import BackendV1, BackendV2 -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency - - -@deprecate_pulse_dependency(moving_to_dynamics=True) -def sequence( - scheduled_circuit: QuantumCircuit, - schedule_config: ScheduleConfig, - backend: Optional[Union[BackendV1, BackendV2]] = None, -) -> Schedule: - """ - 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. - - Args: - scheduled_circuit: The scheduled quantum circuit to translate. - schedule_config: Backend specific parameters used for building the Schedule. - backend: A backend used to build the Schedule, the backend could be BackendV1 - or BackendV2 - - Returns: - A schedule corresponding to the input ``circuit``. - - Raises: - QiskitError: If invalid scheduled circuit is supplied. - """ - circ_pulse_defs = lower_gates(scheduled_circuit, schedule_config, backend) - - # find the measurement start time (assume measurement once) - def _meas_start_time(): - _qubit_time_available = defaultdict(int) - for instruction in scheduled_circuit.data: - if isinstance(instruction.operation, Measure): - return _qubit_time_available[instruction.qubits[0]] - for q in instruction.qubits: - _qubit_time_available[q] += instruction.operation.duration - return None - - meas_time = _meas_start_time() - - # restore start times - qubit_time_available = {} - start_times = [] - out_circ_pulse_defs = [] - for circ_pulse_def in circ_pulse_defs: - active_qubits = [q for q in circ_pulse_def.qubits if q in qubit_time_available] - - start_time = max((qubit_time_available[q] for q in active_qubits), default=0) - - for q in active_qubits: - if qubit_time_available[q] != start_time: - # print(q, ":", qubit_time_available[q], "!=", start_time) - raise QiskitError("Invalid scheduled circuit.") - - stop_time = start_time - if not isinstance(circ_pulse_def.schedule, Barrier): - stop_time += circ_pulse_def.schedule.duration - - delay_overlaps_meas = False - for q in circ_pulse_def.qubits: - qubit_time_available[q] = stop_time - if ( - meas_time is not None - and circ_pulse_def.schedule.name == "delay" - and stop_time > meas_time - ): - qubit_time_available[q] = meas_time - delay_overlaps_meas = True - # skip to delays overlapping measures and barriers - if not delay_overlaps_meas and not isinstance(circ_pulse_def.schedule, Barrier): - start_times.append(start_time) - out_circ_pulse_defs.append(circ_pulse_def) - - timed_schedules = [(time, cpd.schedule) for time, cpd in zip(start_times, out_circ_pulse_defs)] - sched = Schedule(*timed_schedules, name=scheduled_circuit.name) - return pad(sched) diff --git a/qiskit/transpiler/basepasses.py b/qiskit/transpiler/basepasses.py index b4993915c1cc..1e51e4165457 100644 --- a/qiskit/transpiler/basepasses.py +++ b/qiskit/transpiler/basepasses.py @@ -199,10 +199,7 @@ def execute( ) if state.workflow_status.previous_run == RunState.SUCCESS: - if isinstance(new_dag, DAGCircuit): - # Copy calibration data from the original program - new_dag._calibrations_prop = passmanager_ir._calibrations_prop - else: + if not isinstance(new_dag, DAGCircuit): raise TranspilerError( "Transformation passes should return a transformed dag." f"The pass {self.__class__.__name__} is returning a {type(new_dag)}" diff --git a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py index 885009a23e03..65f4015e8565 100644 --- a/qiskit/transpiler/passes/basis/unroll_3q_or_more.py +++ b/qiskit/transpiler/passes/basis/unroll_3q_or_more.py @@ -54,8 +54,6 @@ def run(self, dag): QiskitError: if a 3q+ gate is not decomposable """ for node in dag.multi_qubit_ops(): - if dag._has_calibration_for(node): - continue if isinstance(node.op, ControlFlowOp): dag.substitute_node( diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index 11ba6afcd1b3..eac6df17b33d 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -74,7 +74,7 @@ def run(self, dag): if getattr(node.op, "_directive", False): continue - if dag._has_calibration_for(node) or len(node.qargs) < self._min_qubits: + if len(node.qargs) < self._min_qubits: continue controlled_gate_open_ctrl = isinstance(node.op, ControlledGate) and node.op._open_ctrl diff --git a/qiskit/transpiler/passes/calibration/base_builder.py b/qiskit/transpiler/passes/calibration/base_builder.py deleted file mode 100644 index d69dcb171871..000000000000 --- a/qiskit/transpiler/passes/calibration/base_builder.py +++ /dev/null @@ -1,79 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Calibration builder base class.""" - -from abc import abstractmethod -from typing import List, Union - -from qiskit.circuit import Instruction as CircuitInst -from qiskit.dagcircuit import DAGCircuit -from qiskit.pulse import Schedule, ScheduleBlock -from qiskit.pulse.calibration_entries import CalibrationPublisher -from qiskit.transpiler.basepasses import TransformationPass - -from .exceptions import CalibrationNotAvailable - - -class CalibrationBuilder(TransformationPass): - """Abstract base class to inject calibrations into circuits.""" - - @abstractmethod - def supported(self, node_op: CircuitInst, qubits: List) -> bool: - """Determine if a given node supports the calibration. - - Args: - node_op: Target instruction object. - qubits: Integer qubit indices to check. - - Returns: - Return ``True`` is calibration can be provided. - """ - - @abstractmethod - def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]: - """Gets the calibrated schedule for the given instruction and qubits. - - Args: - node_op: Target instruction object. - qubits: Integer qubit indices to check. - - Returns: - Return Schedule of target gate instruction. - """ - - def run(self, dag: DAGCircuit) -> DAGCircuit: - """Run the calibration adder pass on `dag`. - - Args: - dag: DAG to schedule. - - Returns: - A DAG with calibrations added to it. - """ - for node in dag.gate_nodes(): - qubits = [dag.find_bit(q).index for q in node.qargs] - - if self.supported(node.op, qubits) and not dag.has_calibration_for(node): - # calibration can be provided and no user-defined calibration is already provided - try: - schedule = self.get_calibration(node.op, qubits) - except CalibrationNotAvailable: - # Fail in schedule generation. Just ignore. - continue - publisher = schedule.metadata.get("publisher", CalibrationPublisher.QISKIT) - - # add calibration if it is not backend default - if publisher != CalibrationPublisher.BACKEND_PROVIDER: - dag.add_calibration(gate=node.op, qubits=qubits, schedule=schedule) - - return dag diff --git a/qiskit/transpiler/passes/calibration/exceptions.py b/qiskit/transpiler/passes/calibration/exceptions.py deleted file mode 100644 index 7785f49d1d30..000000000000 --- a/qiskit/transpiler/passes/calibration/exceptions.py +++ /dev/null @@ -1,22 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Exception for errors raised by the calibration pass module.""" -from qiskit.exceptions import QiskitError - - -class CalibrationNotAvailable(QiskitError): - """Raised when calibration generation fails. - - .. note:: - This error is meant to caught by CalibrationBuilder and ignored. - """ diff --git a/qiskit/transpiler/passes/optimization/normalize_rx_angle.py b/qiskit/transpiler/passes/optimization/normalize_rx_angle.py index b6f36e07de36..9272c1e5dea6 100644 --- a/qiskit/transpiler/passes/optimization/normalize_rx_angle.py +++ b/qiskit/transpiler/passes/optimization/normalize_rx_angle.py @@ -25,6 +25,7 @@ from qiskit.circuit.library.standard_gates import RXGate, RZGate, SXGate, XGate +# TODO: do we still need this pass?? class NormalizeRXAngle(TransformationPass): """Normalize theta parameter of RXGate instruction. diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 55232ac1be20..8e75ce1e7a74 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -169,24 +169,20 @@ def _substitution_checks( if new_circ is None: return False - # do we even have calibrations? - has_cals_p = dag._calibrations_prop is not None and len(dag._calibrations_prop) > 0 - # does this run have uncalibrated gates? - uncalibrated_p = not has_cals_p or any(not dag._has_calibration_for(g) for g in old_run) - # does this run have gates not in the image of ._decomposers _and_ uncalibrated? + # does this run have gates not in the image of ._decomposers? if basis is not None: - uncalibrated_and_not_basis_p = any( - g.name not in basis and (not has_cals_p or not dag._has_calibration_for(g)) + not_basis_p = any( + g.name not in basis for g in old_run ) else: # If no basis is specified then we're always in the basis - uncalibrated_and_not_basis_p = False + not_basis_p = False # if we're outside of the basis set, we're obligated to logically decompose. # if we're outside of the set of gates for which we have physical definitions, # then we _try_ to decompose, using the results if we see improvement. - if not uncalibrated_and_not_basis_p: + if not not_basis_p: if new_error is None: new_error = self._error(new_circ, qubit) if old_error is None: @@ -196,8 +192,8 @@ def _substitution_checks( old_error = 0.0 return ( - uncalibrated_and_not_basis_p - or (uncalibrated_p and new_error < old_error) + not_basis_p + or (True and new_error < old_error) or (math.isclose(new_error[0], 0) and not math.isclose(old_error[0], 0)) ) diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py index cdbdd4654f3c..b998590319a1 100644 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ b/qiskit/transpiler/passes/scheduling/alap.py @@ -144,7 +144,6 @@ def run(self, dag): new_dag.name = dag.name new_dag.metadata = dag.metadata - new_dag._calibrations_prop = dag._calibrations_prop # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration diff --git a/qiskit/transpiler/passes/scheduling/alignments/check_durations.py b/qiskit/transpiler/passes/scheduling/alignments/check_durations.py index 3edfdf7ec741..6d0d1db4b8bc 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/check_durations.py +++ b/qiskit/transpiler/passes/scheduling/alignments/check_durations.py @@ -69,10 +69,3 @@ def run(self, dag: DAGCircuit): self.property_set["reschedule_required"] = True return - # Check custom gate durations - for inst_defs in dag._calibrations_prop.values(): - for caldef in inst_defs.values(): - dur = caldef.duration - if not (dur % self.acquire_align == 0 and dur % self.pulse_align == 0): - self.property_set["reschedule_required"] = True - return diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py index 13ff58b1b0f0..5a2a7352fce1 100644 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ b/qiskit/transpiler/passes/scheduling/asap.py @@ -167,7 +167,6 @@ def run(self, dag): new_dag.name = dag.name new_dag.metadata = dag.metadata - new_dag._calibrations_prop = dag._calibrations_prop # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py index c380c9f8c199..6a97329083c2 100644 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/base_scheduler.py @@ -263,15 +263,10 @@ def _get_node_duration( node: DAGOpNode, dag: DAGCircuit, ) -> int: - """A helper method to get duration from node or calibration.""" + """A helper method to get duration from node.""" indices = [dag.find_bit(qarg).index for qarg in node.qargs] - if dag._has_calibration_for(node): - # If node has calibration, this value should be the highest priority - cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - duration = dag._calibrations_prop[node.op.name][cal_key].duration - else: - duration = node.op.duration + duration = node.op.duration if isinstance(node.op, Reset): warnings.warn( diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index 08371c488422..c06a34983e7f 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -145,6 +145,7 @@ def __init__( self._skip_reset_qubits = skip_reset_qubits self._target = target if target is not None: + # The priority order for instruction durations is: target > standalone. self._durations = target.durations() for gate in dd_sequence: if gate.name not in target.operation_names: @@ -171,7 +172,9 @@ def run(self, dag): if dag.duration is None: raise TranspilerError("DD runs after circuit is scheduled.") - durations = self._update_inst_durations(dag) + durations = InstructionDurations() + if self._durations is not None: + durations.update(self._durations, getattr(self._durations, "dt", None)) num_pulses = len(self._dd_sequence) sequence_gphase = 0 @@ -282,30 +285,6 @@ def run(self, dag): return new_dag - def _update_inst_durations(self, dag): - """Update instruction durations with circuit information. If the dag contains gate - calibrations and no instruction durations were provided through the target or as a - standalone input, the circuit calibration durations will be used. - The priority order for instruction durations is: target > standalone > circuit. - """ - circ_durations = InstructionDurations() - - if dag._calibrations_prop: - cal_durations = [] - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - for gate, gate_cals in dag._calibrations_prop.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) - circ_durations.update(cal_durations, circ_durations.dt) - - if self._durations is not None: - circ_durations.update(self._durations, getattr(self._durations, "dt", None)) - - return circ_durations - def __gate_supported(self, gate: Gate, qarg: int) -> bool: """A gate is supported on the qubit (qarg) or not.""" if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)): diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index e8876920aa03..3f316f13dbf1 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -99,7 +99,6 @@ def run(self, dag: DAGCircuit): new_dag.name = dag.name new_dag.metadata = dag.metadata new_dag.unit = self.property_set["time_unit"] - new_dag._calibrations_prop = dag._calibrations_prop new_dag.global_phase = dag.global_phase idle_after = {bit: 0 for bit in dag.qubits} diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 6159253e93e7..3d25e17792c1 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -173,6 +173,7 @@ def __init__( self._dd_sequence_lengths: dict[Qubit, list[int]] = {} self._sequence_phase = 0 if target is not None: + # The priority order for instruction durations is: target > standalone. self._durations = target.durations() self._alignment = target.pulse_alignment for gate in dd_sequence: @@ -181,35 +182,12 @@ def __init__( f"{gate.name} in dd_sequence is not supported in the target" ) - def _update_inst_durations(self, dag): - """Update instruction durations with circuit information. If the dag contains gate - calibrations and no instruction durations were provided through the target or as a - standalone input, the circuit calibration durations will be used. - The priority order for instruction durations is: target > standalone > circuit. - """ - circ_durations = InstructionDurations() - - if dag._calibrations_prop: - cal_durations = [] - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - for gate, gate_cals in dag._calibrations_prop.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) - circ_durations.update(cal_durations, circ_durations.dt) - - if self._durations is not None: - circ_durations.update(self._durations, getattr(self._durations, "dt", None)) - - return circ_durations - def _pre_runhook(self, dag: DAGCircuit): super()._pre_runhook(dag) - durations = self._update_inst_durations(dag) - + durations = InstructionDurations() + if self._durations is not None: + durations.update(self._durations, getattr(self._durations, "dt", None)) num_pulses = len(self._dd_sequence) # Check if physical circuit is given @@ -255,31 +233,7 @@ def _pre_runhook(self, dag: DAGCircuit): sequence_lengths = [] for index, gate in enumerate(self._dd_sequence): - try: - # Check calibration. - params = self._resolve_params(gate) - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - gate_length = dag._calibrations_prop[gate.name][ - ((physical_index,), params) - ].duration - if gate_length % self._alignment != 0: - # This is necessary to implement lightweight scheduling logic for this pass. - # Usually the pulse alignment constraint and pulse data chunk size take - # the same value, however, we can intentionally violate this pattern - # at the gate level. For example, we can create a schedule consisting of - # a pi-pulse of 32 dt followed by a post buffer, i.e. delay, of 4 dt - # on the device with 16 dt constraint. Note that the pi-pulse length - # is multiple of 16 dt but the gate length of 36 is not multiple of it. - # Such pulse gate should be excluded. - raise TranspilerError( - f"Pulse gate {gate.name} with length non-multiple of {self._alignment} " - f"is not acceptable in {self.__class__.__name__} pass." - ) - except KeyError: - gate_length = durations.get(gate, physical_index) + gate_length = durations.get(gate, physical_index) sequence_lengths.append(gate_length) # Update gate duration. This is necessary for current timeline drawer, i.e. scheduled. gate = gate.to_mutable() diff --git a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py index 4c21a210c1c0..b0affa320fcd 100644 --- a/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py +++ b/qiskit/transpiler/passes/scheduling/scheduling/base_scheduler.py @@ -61,24 +61,10 @@ def _get_node_duration( node: DAGOpNode, dag: DAGCircuit, ) -> int: - """A helper method to get duration from node or calibration.""" + """A helper method to get duration from node""" indices = [dag.find_bit(qarg).index for qarg in node.qargs] - if dag._has_calibration_for(node): - # If node has calibration, this value should be the highest priority - cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - duration = dag._calibrations_prop[node.op.name][cal_key].duration - - # Note that node duration is updated (but this is analysis pass) - op = node.op.to_mutable() - op.duration = duration - dag.substitute_node(node, op, propagate_condition=False) - else: - duration = node.duration + duration = node.duration if isinstance(duration, ParameterExpression): raise TranspilerError( diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py index 8bb743ce6b3e..bf23f226a315 100644 --- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py +++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py @@ -51,6 +51,7 @@ def __init__(self, inst_durations: InstructionDurations = None, target: Target = super().__init__() self.inst_durations = inst_durations or InstructionDurations() if target is not None: + # The priority order for instruction durations is: target > standalone. self.inst_durations = target.durations() self._durations_provided = inst_durations is not None or target is not None @@ -67,7 +68,9 @@ def run(self, dag: DAGCircuit): TranspilerError: if the units are not unifiable """ - inst_durations = self._update_inst_durations(dag) + inst_durations = InstructionDurations() + if self._durations_provided: + inst_durations.update(self.inst_durations, getattr(self.inst_durations, "dt", None)) # Choose unit if inst_durations.dt is not None: @@ -114,30 +117,6 @@ def run(self, dag: DAGCircuit): self.property_set["time_unit"] = time_unit return dag - def _update_inst_durations(self, dag): - """Update instruction durations with circuit information. If the dag contains gate - calibrations and no instruction durations were provided through the target or as a - standalone input, the circuit calibration durations will be used. - The priority order for instruction durations is: target > standalone > circuit. - """ - circ_durations = InstructionDurations() - - if dag._calibrations_prop: - cal_durations = [] - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - for gate, gate_cals in dag._calibrations_prop.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) - circ_durations.update(cal_durations, circ_durations.dt) - - if self._durations_provided: - circ_durations.update(self.inst_durations, getattr(self.inst_durations, "dt", None)) - - return circ_durations - @staticmethod def _units_used_in_delays(dag: DAGCircuit) -> Set[str]: units_used = set() diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index 95adee1d05c7..cb025ea7896a 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -811,8 +811,7 @@ def _definitely_skip_node( node (which is _most_ nodes).""" if ( - dag._has_calibration_for(node) - or len(node.qargs) < self._min_qubits + len(node.qargs) < self._min_qubits or node.is_directive() or (self._instruction_supported(node.name, qubits) and not node.is_control_flow()) ): diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 52f3d65449e5..d704b095b212 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -17,18 +17,15 @@ from qiskit.transpiler.coupling import CouplingMap from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.utils.deprecate_pulse import deprecate_pulse_arg class PassManagerConfig: """Pass Manager Configuration.""" - @deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None) def __init__( self, initial_layout=None, basis_gates=None, - inst_map=None, coupling_map=None, layout_method=None, routing_method=None, @@ -53,7 +50,6 @@ def __init__( initial_layout (Layout): Initial position of virtual qubits on physical qubits. basis_gates (list): List of basis gate names to unroll to. - inst_map (InstructionScheduleMap): Mapping object that maps gate to schedule. coupling_map (CouplingMap): Directed graph represented a coupling map. layout_method (str): the pass to use for choosing initial qubit @@ -93,7 +89,6 @@ def __init__( """ self.initial_layout = initial_layout self.basis_gates = basis_gates - self.inst_map = inst_map self.coupling_map = coupling_map self.init_method = init_method self.layout_method = layout_method @@ -155,14 +150,6 @@ def from_backend(cls, backend, _skip_target=False, **pass_manager_options): res.basis_gates = getattr(config, "basis_gates", None) else: res.basis_gates = backend.operation_names - if res.inst_map is None: - if backend_version < 2: - if hasattr(backend, "defaults"): - defaults = backend.defaults() - if defaults is not None: - res.inst_map = defaults.instruction_schedule_map - else: - res.inst_map = backend._instruction_schedule_map if res.coupling_map is None: if backend_version < 2: cmap_edge_list = getattr(config, "coupling_map", None) @@ -198,7 +185,6 @@ def __str__(self): "Pass Manager Config:\n" f"\tinitial_layout: {self.initial_layout}\n" f"\tbasis_gates: {self.basis_gates}\n" - f"\tinst_map: {str(self.inst_map).replace(newline, newline_tab)}\n" f"\tcoupling_map: {self.coupling_map}\n" f"\tlayout_method: {self.layout_method}\n" f"\trouting_method: {self.routing_method}\n" diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index d1ffa8d3d1b0..2fb02f6dbfd3 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -690,16 +690,11 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana instruction_durations = pass_manager_config.instruction_durations scheduling_method = pass_manager_config.scheduling_method timing_constraints = pass_manager_config.timing_constraints - inst_map = pass_manager_config.inst_map target = pass_manager_config.target - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # Passing `inst_map` to `generate_scheduling` is deprecated in Qiskit 1.3 - # so filtering these warning when building pass managers - return common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map, target - ) + return common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, target + ) class AsapSchedulingPassManager(PassManagerStagePlugin): @@ -711,16 +706,11 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana instruction_durations = pass_manager_config.instruction_durations scheduling_method = pass_manager_config.scheduling_method timing_constraints = pass_manager_config.timing_constraints - inst_map = pass_manager_config.inst_map target = pass_manager_config.target - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # Passing `inst_map` to `generate_scheduling` is deprecated in Qiskit 1.3 - # so filtering these warning when building pass managers - return common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map, target - ) + return common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, target + ) class DefaultSchedulingPassManager(PassManagerStagePlugin): @@ -732,16 +722,12 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana instruction_durations = pass_manager_config.instruction_durations scheduling_method = None timing_constraints = pass_manager_config.timing_constraints or TimingConstraints() - inst_map = pass_manager_config.inst_map target = pass_manager_config.target - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # Passing `inst_map` to `generate_scheduling` is deprecated in Qiskit 1.3 - # so filtering these warning when building pass managers - return common.generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, inst_map, target - ) + + return common.generate_scheduling( + instruction_durations, scheduling_method, timing_constraints, target + ) class DefaultLayoutPassManager(PassManagerStagePlugin): diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index 0f0a6b7ea0a7..b919027fbe73 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -569,7 +569,7 @@ def _direction_condition(property_set): def generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, _, target=None + instruction_durations, scheduling_method, timing_constraints, target=None ): """Generate a post optimization scheduling :class:`~qiskit.transpiler.PassManager` diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 62f991990d4e..71a91cfda967 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -30,7 +30,6 @@ from qiskit.transpiler.target import Target, target_to_backend_properties from qiskit.transpiler.timing_constraints import TimingConstraints from qiskit.utils import deprecate_arg -from qiskit.utils.deprecate_pulse import deprecate_pulse_arg from .level0 import level_0_pass_manager from .level1 import level_1_pass_manager @@ -64,13 +63,11 @@ additional_msg="The `target` parameter should be used instead. You can build a `Target` instance " "with defined properties with Target.from_configuration(..., backend_properties=...)", ) -@deprecate_pulse_arg("inst_map", predicate=lambda inst_map: inst_map is not None) def generate_preset_pass_manager( optimization_level=2, backend=None, target=None, basis_gates=None, - inst_map=None, coupling_map=None, instruction_durations=None, backend_properties=None, @@ -102,7 +99,7 @@ def generate_preset_pass_manager( The target constraints for the pass manager construction can be specified through a :class:`.Target` instance, a :class:`.BackendV1` or :class:`.BackendV2` instance, or via loose constraints - (``basis_gates``, ``inst_map``, ``coupling_map``, ``backend_properties``, ``instruction_durations``, + (``basis_gates``, ``coupling_map``, ``backend_properties``, ``instruction_durations``, ``dt`` or ``timing_constraints``). The order of priorities for target constraints works as follows: if a ``target`` input is provided, it will take priority over any ``backend`` input or loose constraints. @@ -120,7 +117,6 @@ def generate_preset_pass_manager( **basis_gates** target basis_gates basis_gates **coupling_map** target coupling_map coupling_map **instruction_durations** target instruction_durations instruction_durations - **inst_map** target inst_map inst_map **dt** target dt dt **timing_constraints** target timing_constraints timing_constraints **backend_properties** target backend_properties backend_properties @@ -139,7 +135,7 @@ def generate_preset_pass_manager( * 3: even heavier optimization backend (Backend): An optional backend object which can be used as the - source of the default values for the ``basis_gates``, ``inst_map``, + source of the default values for the ``basis_gates``, ``coupling_map``, ``backend_properties``, ``instruction_durations``, ``timing_constraints``, and ``target``. If any of those other arguments are specified in addition to ``backend`` they will take precedence @@ -147,15 +143,10 @@ def generate_preset_pass_manager( target (Target): The :class:`~.Target` representing a backend compilation target. The following attributes will be inferred from this argument if they are not set: ``coupling_map``, ``basis_gates``, - ``instruction_durations``, ``inst_map``, ``timing_constraints`` + ``instruction_durations``, ``timing_constraints`` and ``backend_properties``. basis_gates (list): List of basis gate names to unroll to (e.g: ``['u1', 'u2', 'u3', 'cx']``). - inst_map (InstructionScheduleMap): DEPRECATED. Mapping object that maps gates to schedules. - If any user defined calibration is found in the map and this is used in a - circuit, transpiler attaches the custom gate definition to the circuit. - This enables one to flexibly override the low-level instruction - implementation. coupling_map (CouplingMap or list): Directed graph represented a coupling map. Multiple formats are supported: @@ -297,8 +288,6 @@ def generate_preset_pass_manager( ) backend = BackendV2Converter(backend) - # Check if a custom inst_map was specified before overwriting inst_map - _given_inst_map = bool(inst_map) # If there are no loose constraints => use backend target if available _no_loose_constraints = ( basis_gates is None @@ -321,11 +310,10 @@ def generate_preset_pass_manager( dt = _parse_dt(dt, backend) instruction_durations = _parse_instruction_durations(backend, instruction_durations, dt) timing_constraints = _parse_timing_constraints(backend, timing_constraints) - inst_map = _parse_inst_map(inst_map, backend) # The basis gates parser will set _skip_target to True if a custom basis gate is found # (known edge case). basis_gates, name_mapping, _skip_target = _parse_basis_gates( - basis_gates, backend, inst_map, _skip_target + basis_gates, backend, _skip_target ) coupling_map = _parse_coupling_map(coupling_map, backend) @@ -337,37 +325,22 @@ def generate_preset_pass_manager( # Only parse backend properties when the target isn't skipped to # preserve the former behavior of transpile. backend_properties = _parse_backend_properties(backend_properties, backend) - with warnings.catch_warnings(): - # TODO: inst_map will be removed in 2.0 - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``inst_map`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) - # Build target from constraints. - target = Target.from_configuration( - basis_gates=basis_gates, - num_qubits=backend.num_qubits if backend is not None else None, - coupling_map=coupling_map, - # If the instruction map has custom gates, do not give as config, the information - # will be added to the target with update_from_instruction_schedule_map - inst_map=inst_map if inst_map and not inst_map.has_custom_gate() else None, - backend_properties=backend_properties, - instruction_durations=instruction_durations, - concurrent_measurements=( - backend.target.concurrent_measurements if backend is not None else None - ), - dt=dt, - timing_constraints=timing_constraints, - custom_name_mapping=name_mapping, - ) - - # Update target with custom gate information. Note that this is an exception to the priority - # order (target > loose constraints), added to handle custom gates for scheduling passes. - if target is not None and _given_inst_map and inst_map.has_custom_gate(): - target = copy.deepcopy(target) - target.update_from_instruction_schedule_map(inst_map) + # Build target from constraints. + target = Target.from_configuration( + basis_gates=basis_gates, + num_qubits=backend.num_qubits if backend is not None else None, + coupling_map=coupling_map, + # If the instruction map has custom gates, do not give as config, the information + # will be added to the target with update_from_instruction_schedule_map + backend_properties=backend_properties, + instruction_durations=instruction_durations, + concurrent_measurements=( + backend.target.concurrent_measurements if backend is not None else None + ), + dt=dt, + timing_constraints=timing_constraints, + custom_name_mapping=name_mapping, + ) if target is not None: if coupling_map is None: @@ -376,8 +349,6 @@ def generate_preset_pass_manager( basis_gates = target.operation_names if instruction_durations is None: instruction_durations = target.durations() - if inst_map is None: - inst_map = target._get_instruction_schedule_map() if timing_constraints is None: timing_constraints = target.timing_constraints() if backend_properties is None: @@ -401,7 +372,6 @@ def generate_preset_pass_manager( pm_options = { "target": target, "basis_gates": basis_gates, - "inst_map": inst_map, "coupling_map": coupling_map, "instruction_durations": instruction_durations, "backend_properties": backend_properties, @@ -421,32 +391,25 @@ def generate_preset_pass_manager( "qubits_initially_zero": qubits_initially_zero, } - with warnings.catch_warnings(): - # inst_map is deprecated in the PassManagerConfig initializer - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*argument ``inst_map`` is deprecated as of Qiskit 1.3", - ) - if backend is not None: - pm_options["_skip_target"] = _skip_target - pm_config = PassManagerConfig.from_backend(backend, **pm_options) - else: - pm_config = PassManagerConfig(**pm_options) - if optimization_level == 0: - pm = level_0_pass_manager(pm_config) - elif optimization_level == 1: - pm = level_1_pass_manager(pm_config) - elif optimization_level == 2: - pm = level_2_pass_manager(pm_config) - elif optimization_level == 3: - pm = level_3_pass_manager(pm_config) - else: - raise ValueError(f"Invalid optimization level {optimization_level}") + if backend is not None: + pm_options["_skip_target"] = _skip_target + pm_config = PassManagerConfig.from_backend(backend, **pm_options) + else: + pm_config = PassManagerConfig(**pm_options) + if optimization_level == 0: + pm = level_0_pass_manager(pm_config) + elif optimization_level == 1: + pm = level_1_pass_manager(pm_config) + elif optimization_level == 2: + pm = level_2_pass_manager(pm_config) + elif optimization_level == 3: + pm = level_3_pass_manager(pm_config) + else: + raise ValueError(f"Invalid optimization level {optimization_level}") return pm -def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): +def _parse_basis_gates(basis_gates, backend, skip_target): standard_gates = get_standard_gate_name_mapping() # Add control flow gates by default to basis set and name mapping default_gates = {"measure", "delay", "reset"}.union(CONTROL_FLOW_OP_NAMES) @@ -485,7 +448,7 @@ def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): {name: backend.target.operation_from_name(name) for name in backend.operation_names} ) - # Check for custom instructions before removing calibrations + # Check for custom instructions for inst in instructions: if inst not in standard_gates and inst not in default_gates: if inst not in backend.operation_names: @@ -502,24 +465,9 @@ def _parse_basis_gates(basis_gates, backend, inst_map, skip_target): skip_target = True break - # Remove calibrated instructions, as they will be added later from the instruction schedule map - if inst_map is not None and not skip_target: - for inst in inst_map.instructions: - for qubit in inst_map.qubits_with_instruction(inst): - entry = inst_map._get_calibration_entry(inst, qubit) - if entry.user_provided and inst in instructions: - instructions.remove(inst) - return list(instructions) if instructions else None, name_mapping, skip_target -def _parse_inst_map(inst_map, backend): - # try getting inst_map from user, else backend - if inst_map is None and backend is not None: - inst_map = backend.target._get_instruction_schedule_map() - return inst_map - - def _parse_backend_properties(backend_properties, backend): # try getting backend_props from user, else backend if backend_properties is None and backend is not None: diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 51e1967473f3..2a7e46b4ff6d 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -37,27 +37,18 @@ BaseInstructionProperties, ) -from qiskit.circuit.parameter import Parameter -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.pulse.instruction_schedule_map import InstructionScheduleMap -from qiskit.pulse.calibration_entries import CalibrationEntry, ScheduleDef -from qiskit.pulse.schedule import Schedule, ScheduleBlock from qiskit.transpiler.coupling import CouplingMap from qiskit.transpiler.exceptions import TranspilerError 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.exceptions import QiskitError # import QubitProperties here to provide convenience alias for building a # full target from qiskit.providers.backend import QubitProperties # pylint: disable=unused-import from qiskit.providers.models.backendproperties import BackendProperties from qiskit.utils import deprecate_func -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency, deprecate_pulse_arg logger = logging.getLogger(__name__) @@ -72,10 +63,6 @@ class InstructionProperties(BaseInstructionProperties): custom attributes for those custom/additional properties by the backend. """ - __slots__ = [ - "_calibration", - ] - def __new__( # pylint: disable=keyword-arg-before-vararg cls, duration=None, # pylint: disable=keyword-arg-before-vararg @@ -87,12 +74,10 @@ def __new__( # pylint: disable=keyword-arg-before-vararg cls, duration, error ) - @deprecate_pulse_arg("calibration", predicate=lambda cals: cals is not None) def __init__( self, duration: float | None = None, # pylint: disable=unused-argument error: float | None = None, # pylint: disable=unused-argument - calibration: Schedule | ScheduleBlock | CalibrationEntry | None = None, ): """Create a new ``InstructionProperties`` object @@ -101,79 +86,19 @@ def __init__( specified set of qubits error: The average error rate for the instruction on the specified set of qubits. - calibration: DEPRECATED. The pulse representation of the instruction. """ super().__init__() - self._calibration: CalibrationEntry | None = None - self._calibration_prop = calibration - - @property - @deprecate_pulse_dependency(is_property=True) - def calibration(self): - """The pulse representation of the instruction. - - .. note:: - - This attribute always returns a Qiskit pulse program, but it is internally - wrapped by the :class:`.CalibrationEntry` to manage unbound parameters - and to uniformly handle different data representation, - for example, un-parsed Pulse Qobj JSON that a backend provider may provide. - - This value can be overridden through the property setter in following manner. - When you set either :class:`.Schedule` or :class:`.ScheduleBlock` this is - always treated as a user-defined (custom) calibration and - the transpiler may automatically attach the calibration data to the output circuit. - This calibration data may appear in the wire format as an inline calibration, - which may further update the backend standard instruction set architecture. - - If you are a backend provider who provides a default calibration data - that is not needed to be attached to the transpiled quantum circuit, - you can directly set :class:`.CalibrationEntry` instance to this attribute, - in which you should set :code:`user_provided=False` when you define - calibration data for the entry. End users can still intentionally utilize - the calibration data, for example, to run pulse-level simulation of the circuit. - However, such entry doesn't appear in the wire format, and backend must - use own definition to compile the circuit down to the execution format. - - """ - return self._calibration_prop - - @calibration.setter - @deprecate_pulse_dependency(is_property=True) - def calibration(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): - self._calibration_prop = calibration - - @property - def _calibration_prop(self): - if self._calibration is None: - return None - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # Clean this alternative path from deprecation warning emitted by `get_schedule` - return self._calibration.get_schedule() - - @_calibration_prop.setter - def _calibration_prop(self, calibration: Schedule | ScheduleBlock | CalibrationEntry): - if isinstance(calibration, (Schedule, ScheduleBlock)): - new_entry = ScheduleDef() - new_entry.define(calibration, user_provided=True) - else: - new_entry = calibration - self._calibration = new_entry def __repr__(self): return ( - f"InstructionProperties(duration={self.duration}, error={self.error}" - f", calibration={self._calibration})" + f"InstructionProperties(duration={self.duration}, error={self.error})" ) def __getstate__(self) -> tuple: - return (super().__getstate__(), self._calibration_prop, self._calibration) + return (super().__getstate__()) def __setstate__(self, state: tuple): super().__setstate__(state[0]) - self._calibration_prop = state[1] - self._calibration = state[2] class Target(BaseTarget): @@ -462,125 +387,6 @@ def update_instruction_properties(self, instruction, qargs, properties): self._instruction_durations = None self._instruction_schedule_map = None - @deprecate_pulse_dependency - def update_from_instruction_schedule_map(self, inst_map, inst_name_map=None, error_dict=None): - """Update the target from an instruction schedule map. - - If the input instruction schedule map contains new instructions not in - the target they will be added. However, if it contains additional qargs - for an existing instruction in the target it will error. - - Args: - inst_map (InstructionScheduleMap): The instruction - inst_name_map (dict): An optional dictionary that maps any - instruction name in ``inst_map`` to an instruction object. - If not provided, instruction is pulled from the standard Qiskit gates, - and finally custom gate instance is created with schedule name. - error_dict (dict): A dictionary of errors of the form:: - - {gate_name: {qarg: error}} - - for example:: - - {'rx': {(0, ): 1.4e-4, (1, ): 1.2e-4}} - - For each entry in the ``inst_map`` if ``error_dict`` is defined - 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. - """ - 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: - # Prepare dictionary of instruction properties - out_props = {} - for qargs in inst_map.qubits_with_instruction(inst_name): - try: - qargs = tuple(qargs) - except TypeError: - qargs = (qargs,) - try: - props = self._gate_map[inst_name][qargs] - except (KeyError, TypeError): - props = None - - 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 - except UnassignedDurationError: - # duration of schedule is parameterized - duration = None - else: - duration = None - props = InstructionProperties( - duration=duration, - calibration=entry, - ) - else: - if props is None: - # Edge case. Calibration is backend defined, but this is not - # registered in the backend target. Ignore this entry. - continue - try: - # Update gate error if provided. - props.error = error_dict[inst_name][qargs] - except (KeyError, TypeError): - pass - out_props[qargs] = props - if not out_props: - continue - # Prepare Qiskit Gate object assigned to the entries - if inst_name not in self._gate_map: - # Entry not found: Add new instruction - if inst_name in qiskit_inst_name_map: - # Remove qargs with length that doesn't match with instruction qubit number - inst_obj = qiskit_inst_name_map[inst_name] - normalized_props = {} - for qargs, prop in out_props.items(): - if len(qargs) != inst_obj.num_qubits: - continue - normalized_props[qargs] = prop - self.add_instruction(inst_obj, normalized_props, name=inst_name) - else: - # Check qubit length parameter name uniformity. - qlen = set() - param_names = set() - for qargs in inst_map.qubits_with_instruction(inst_name): - if isinstance(qargs, int): - qargs = (qargs,) - qlen.add(len(qargs)) - cal = getattr(out_props[tuple(qargs)], "_calibration") - param_names.add(tuple(cal.get_signature().parameters.keys())) - if len(qlen) > 1 or len(param_names) > 1: - raise QiskitError( - f"Schedules for {inst_name} are defined non-uniformly for " - f"multiple qubit lengths {qlen}, " - f"or different parameter names {param_names}. " - "Provide these schedules with inst_name_map or define them with " - "different names for different gate parameters." - ) - inst_obj = Gate( - name=inst_name, - num_qubits=next(iter(qlen)), - params=list(map(Parameter, next(iter(param_names)))), - ) - self.add_instruction(inst_obj, out_props, name=inst_name) - else: - # Entry found: Update "existing" instructions. - for qargs, prop in out_props.items(): - if qargs not in self._gate_map[inst_name]: - continue - self.update_instruction_properties(inst_name, qargs, prop) - def qargs_for_operation_name(self, operation): """Get the qargs for a given operation name @@ -620,104 +426,6 @@ def timing_constraints(self): self.granularity, self.min_length, self.pulse_alignment, self.acquire_alignment ) - @deprecate_pulse_dependency - def instruction_schedule_map(self): - """Return an :class:`~qiskit.pulse.InstructionScheduleMap` for the - instructions in the target with a pulse schedule defined. - - Returns: - InstructionScheduleMap: The instruction schedule map for the - instructions in this target with a pulse schedule defined. - """ - return self._get_instruction_schedule_map() - - def _get_instruction_schedule_map(self): - if self._instruction_schedule_map is not None: - return self._instruction_schedule_map - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `InstructionScheduleMap` is deprecated in Qiskit 1.3 but we want this alternative - # path to be clean of deprecation warnings - out_inst_schedule_map = InstructionScheduleMap() - - for instruction, qargs in self._gate_map.items(): - for qarg, properties in qargs.items(): - # Directly getting CalibrationEntry not to invoke .get_schedule(). - # This keeps PulseQobjDef un-parsed. - cal_entry = getattr(properties, "_calibration", None) - if cal_entry is not None: - # Use fast-path to add entries to the inst map. - out_inst_schedule_map._add(instruction, qarg, cal_entry) - self._instruction_schedule_map = out_inst_schedule_map - return out_inst_schedule_map - - @deprecate_pulse_dependency - def has_calibration( - self, - operation_name: str, - qargs: tuple[int, ...], - ) -> bool: - """Return whether the instruction (operation + qubits) defines a calibration. - - Args: - operation_name: The name of the operation for the instruction. - qargs: The tuple of qubit indices for the instruction. - - Returns: - Returns ``True`` if the calibration is supported and ``False`` if it isn't. - """ - return self._has_calibration(operation_name, qargs) - - def _has_calibration( - self, - operation_name: str, - qargs: tuple[int, ...], - ) -> bool: - qargs = tuple(qargs) - if operation_name not in self._gate_map: - return False - if qargs not in self._gate_map[operation_name]: - return False - return getattr(self._gate_map[operation_name][qargs], "_calibration", None) is not None - - @deprecate_pulse_dependency - def get_calibration( - self, - operation_name: str, - qargs: tuple[int, ...], - *args: ParameterValueType, - **kwargs: ParameterValueType, - ) -> Schedule | ScheduleBlock: - """Get calibrated pulse schedule for the instruction. - - If calibration is templated with parameters, one can also provide those values - to build a schedule with assigned parameters. - - Args: - operation_name: The name of the operation for the instruction. - qargs: The tuple of qubit indices for the instruction. - args: Parameter values to build schedule if any. - kwargs: Parameter values with name to build schedule if any. - - Returns: - Calibrated pulse schedule of corresponding instruction. - """ - return self._get_calibration(operation_name, qargs, *args, *kwargs) - - def _get_calibration( - self, - operation_name: str, - qargs: tuple[int, ...], - *args: ParameterValueType, - **kwargs: ParameterValueType, - ) -> Schedule | ScheduleBlock: - if not self._has_calibration(operation_name, qargs): - raise KeyError( - f"Calibration of instruction {operation_name} for qubit {qargs} is not defined." - ) - cal_entry = getattr(self._gate_map[operation_name][qargs], "_calibration") - return cal_entry.get_schedule(*args, **kwargs) - @property def operation_names(self): """Get the operation names in the target.""" @@ -940,9 +648,6 @@ def __str__(self): error = getattr(props, "error", None) if error is not None: prop_str_pieces.append(f"\t\t\tError Rate: {error:g}\n") - schedule = getattr(props, "_calibration", None) - if schedule is not None: - prop_str_pieces.append("\t\t\tWith pulse schedule calibration\n") extra_props = getattr(props, "properties", None) if extra_props is not None: extra_props_pieces = [ @@ -970,13 +675,11 @@ def __setstate__(self, state: tuple): super().__setstate__(state["base"]) @classmethod - @deprecate_pulse_arg("inst_map") def from_configuration( cls, basis_gates: list[str], num_qubits: int | None = None, coupling_map: CouplingMap | None = None, - inst_map: InstructionScheduleMap | None = None, backend_properties: BackendProperties | None = None, instruction_durations: InstructionDurations | None = None, concurrent_measurements: Optional[List[List[int]]] = None, @@ -1009,14 +712,6 @@ def from_configuration( 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 - ``coupling_map`` is used as the source of truth for connectivity - 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`. 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 @@ -1124,7 +819,6 @@ def from_configuration( for qubit in range(num_qubits): error = None duration = None - calibration = None if backend_properties is not None: if duration is None: try: @@ -1135,17 +829,6 @@ def from_configuration( 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: @@ -1153,19 +836,12 @@ def from_configuration( except TranspilerError: duration = None - if error is None and duration is None and calibration is None: + if error is None and duration is None: gate_properties[(qubit,)] = None else: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``calibration`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) - gate_properties[(qubit,)] = InstructionProperties( - duration=duration, error=error, calibration=calibration - ) + gate_properties[(qubit,)] = InstructionProperties( + duration=duration, error=error + ) target.add_instruction(name_mapping[gate], properties=gate_properties, name=gate) edges = list(coupling_map.get_edges()) for gate in two_qubit_gates: @@ -1173,7 +849,6 @@ def from_configuration( for edge in edges: error = None duration = None - calibration = None if backend_properties is not None: if duration is None: try: @@ -1184,17 +859,6 @@ def from_configuration( 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: @@ -1202,19 +866,12 @@ def from_configuration( except TranspilerError: duration = None - if error is None and duration is None and calibration is None: + if error is None and duration is None: gate_properties[edge] = None else: - with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``calibration`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) - gate_properties[edge] = InstructionProperties( - duration=duration, error=error, calibration=calibration - ) + gate_properties[edge] = InstructionProperties( + duration=duration, error=error + ) 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) diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index 6f7359021839..cc79b61e1e79 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -135,7 +135,6 @@ def __init__( self._initial_state = initial_state self._global_phase = self._circuit.global_phase - self._calibrations = self._circuit._calibrations_prop self._expr_len = expr_len self._cregbundle = cregbundle @@ -420,7 +419,7 @@ def _get_layer_widths(self, node_data, wire_map, outer_circuit, glob_data): base_type = getattr(op, "base_gate", None) gate_text, ctrl_text, raw_gate_text = get_gate_ctrl_text( - op, "mpl", style=self._style, calibrations=self._calibrations + op, "mpl", style=self._style ) node_data[node].gate_text = gate_text node_data[node].ctrl_text = ctrl_text @@ -1888,22 +1887,6 @@ def _swap(self, xy, node, node_data, color=None): self._swap_cross(xy[1], color=color) self._line(xy[0], xy[1], lc=color) - # add calibration text - gate_text = node_data[node].gate_text.split("\n")[-1] - if node_data[node].raw_gate_text in self._calibrations: - xpos, ypos = xy[0] - self._ax.text( - xpos, - ypos + 0.7 * HIG, - gate_text, - ha="center", - va="top", - fontsize=self._style["sfs"], - color=self._style["tc"], - clip_on=True, - zorder=PORDER_TEXT, - ) - def _swap_cross(self, xy, color=None): """Draw the Swap cross symbol""" xpos, ypos = xy diff --git a/test/python/circuit/test_calibrations.py b/test/python/circuit/test_calibrations.py deleted file mode 100644 index 6df7302ee50e..000000000000 --- a/test/python/circuit/test_calibrations.py +++ /dev/null @@ -1,61 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Test calibrations in quantum circuits.""" - -import unittest - -from qiskit.pulse import Schedule -from qiskit.circuit import QuantumCircuit -from qiskit.circuit.library import RZXGate -from test import QiskitTestCase # pylint: disable=wrong-import-order - - -class TestCalibrations(QiskitTestCase): - """Test composition of two circuits.""" - - def test_iadd(self): - """Test that __iadd__ keeps the calibrations.""" - qc_cal = QuantumCircuit(2) - qc_cal.rzx(0.5, 0, 1) - with self.assertWarns(DeprecationWarning): - qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule()) - - qc = QuantumCircuit(2) - qc &= qc_cal - - with self.assertWarns(DeprecationWarning): - self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) - self.assertEqual(qc_cal.calibrations, qc.calibrations) - - def test_add(self): - """Test that __add__ keeps the calibrations.""" - qc_cal = QuantumCircuit(2) - qc_cal.rzx(0.5, 0, 1) - with self.assertWarns(DeprecationWarning): - qc_cal.add_calibration(RZXGate, (0, 1), params=[0.5], schedule=Schedule()) - - qc = QuantumCircuit(2) & qc_cal - - with self.assertWarns(DeprecationWarning): - self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) - self.assertEqual(qc_cal.calibrations, qc.calibrations) - - qc = qc_cal & QuantumCircuit(2) - - with self.assertWarns(DeprecationWarning): - self.assertEqual(qc.calibrations[RZXGate], {((0, 1), (0.5,)): Schedule(name="test")}) - self.assertEqual(qc_cal.calibrations, qc.calibrations) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index bd35c8d920a5..f8667cf8afac 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -401,9 +401,6 @@ def test_copy_empty_like_circuit(self): qc.measure(qr[0], cr[0]) qc.measure(qr[1], cr[1]) - with self.assertWarns(DeprecationWarning): - sched = Schedule(Play(Gaussian(160, 0.1, 40), DriveChannel(0))) - qc.add_calibration("h", [0, 1], sched) copied = qc.copy_empty_like() qc.clear() @@ -411,8 +408,6 @@ def test_copy_empty_like_circuit(self): self.assertEqual(qc.global_phase, copied.global_phase) self.assertEqual(qc.name, copied.name) self.assertEqual(qc.metadata, copied.metadata) - with self.assertWarns(DeprecationWarning): - self.assertEqual(qc.calibrations, copied.calibrations) copied = qc.copy_empty_like("copy") self.assertEqual(copied.name, "copy") diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py index 14b5364b2168..038b60c59c03 100644 --- a/test/python/circuit/test_circuit_properties.py +++ b/test/python/circuit/test_circuit_properties.py @@ -1230,103 +1230,6 @@ def test_num_qubits_multiple_register_circuit(self): circ = QuantumCircuit(q_reg1, q_reg2, q_reg3) self.assertEqual(circ.num_qubits, 18) - def test_calibrations_basis_gates(self): - """Check if the calibrations for basis gates provided are added correctly.""" - circ = QuantumCircuit(2) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - with pulse.build() as q1_y90: - pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) - - # Add calibration - circ.add_calibration(RXGate(3.14), [0], q0_x180) - circ.add_calibration(RYGate(1.57), [1], q1_y90) - - self.assertEqual(set(circ.calibrations.keys()), {"rx", "ry"}) - self.assertEqual(set(circ.calibrations["rx"].keys()), {((0,), (3.14,))}) - self.assertEqual(set(circ.calibrations["ry"].keys()), {((1,), (1.57,))}) - self.assertEqual( - circ.calibrations["rx"][((0,), (3.14,))].instructions, q0_x180.instructions - ) - self.assertEqual( - circ.calibrations["ry"][((1,), (1.57,))].instructions, q1_y90.instructions - ) - - def test_calibrations_custom_gates(self): - """Check if the calibrations for custom gates with params provided are added correctly.""" - circ = QuantumCircuit(3) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibrations with a custom gate 'rxt' - circ.add_calibration("rxt", [0], q0_x180, params=[1.57, 3.14, 4.71]) - - self.assertEqual(set(circ.calibrations.keys()), {"rxt"}) - self.assertEqual(set(circ.calibrations["rxt"].keys()), {((0,), (1.57, 3.14, 4.71))}) - self.assertEqual( - circ.calibrations["rxt"][((0,), (1.57, 3.14, 4.71))].instructions, - q0_x180.instructions, - ) - - def test_calibrations_no_params(self): - """Check calibrations if the no params is provided with just gate name.""" - circ = QuantumCircuit(3) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - circ.add_calibration("h", [0], q0_x180) - - self.assertEqual(set(circ.calibrations.keys()), {"h"}) - self.assertEqual(set(circ.calibrations["h"].keys()), {((0,), ())}) - self.assertEqual(circ.calibrations["h"][((0,), ())].instructions, q0_x180.instructions) - - def test_has_calibration_for(self): - """Test that `has_calibration_for` returns a correct answer.""" - qc = QuantumCircuit(3) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("h", [0], q0_x180) - - qc.h(0) - qc.h(1) - - with self.assertWarns(DeprecationWarning): - self.assertTrue(qc.has_calibration_for(qc.data[0])) - self.assertFalse(qc.has_calibration_for(qc.data[1])) - - def test_has_calibration_for_legacy(self): - """Test that `has_calibration_for` returns a correct answer when presented with a legacy 3 - tuple.""" - qc = QuantumCircuit(3) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("h", [0], q0_x180) - - qc.h(0) - qc.h(1) - - with self.assertWarns(DeprecationWarning): - self.assertTrue( - qc.has_calibration_for( - (qc.data[0].operation, list(qc.data[0].qubits), list(qc.data[0].clbits)) - ) - ) - self.assertFalse( - qc.has_calibration_for( - (qc.data[1].operation, list(qc.data[1].qubits), list(qc.data[1].clbits)) - ) - ) - def test_metadata_copy_does_not_share_state(self): """Verify mutating the metadata of a circuit copy does not impact original.""" # ref: https://github.com/Qiskit/qiskit-terra/issues/6057 diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index 8221f81ad15e..d9d3cf79e8fa 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -591,38 +591,6 @@ def test_compose_gate(self): self.assertEqual(circuit_composed, circuit_expected) - def test_compose_calibrations(self): - """Test that composing two circuits updates calibrations.""" - circ_left = QuantumCircuit(1) - circ_right = QuantumCircuit(1) - with self.assertWarns(DeprecationWarning): - circ_left.add_calibration("h", [0], None) - circ_right.add_calibration("rx", [0], None) - circ = circ_left.compose(circ_right) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(circ.calibrations), 2) - self.assertEqual(len(circ_left.calibrations), 1) - - circ_left = QuantumCircuit(1) - circ_right = QuantumCircuit(1) - with self.assertWarns(DeprecationWarning): - circ_left.add_calibration("h", [0], None) - circ_right.add_calibration("h", [1], None) - circ = circ_left.compose(circ_right) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(circ.calibrations), 1) - self.assertEqual(len(circ.calibrations["h"]), 2) - self.assertEqual(len(circ_left.calibrations), 1) - - # Ensure that transpiled _calibration is defaultdict - qc = QuantumCircuit(2, 2) - qc.h(0) - qc.cx(0, 1) - qc.measure(0, 0) - qc = transpile(qc, None, basis_gates=["h", "cx"], coupling_map=[[0, 1], [1, 0]]) - with self.assertWarns(DeprecationWarning): - qc.add_calibration("cx", [0, 1], Schedule()) - def test_compose_one_liner(self): """Test building a circuit in one line, for fun.""" circ = QuantumCircuit(3) diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index bcd0316aee36..3480fb3221ec 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -723,165 +723,6 @@ def test_gate_multiplicity_binding(self): for instruction in qc2.data: self.assertEqual(float(instruction.operation.params[0]), 1.0) - def test_calibration_assignment(self): - """That that calibration mapping and the schedules they map are assigned together.""" - theta = Parameter("theta") - circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [theta]), [0]) - circ.measure(0, 0) - - with self.assertWarns(DeprecationWarning): - rxt_q0 = pulse.Schedule( - pulse.Play( - pulse.library.Gaussian(duration=128, sigma=16, amp=0.2 * theta / 3.14), - pulse.DriveChannel(0), - ) - ) - - circ.add_calibration("rxt", [0], rxt_q0, [theta]) - circ = circ.assign_parameters({theta: 3.14}) - - instruction = circ.data[0] - cal_key = ( - tuple(circ.find_bit(q).index for q in instruction.qubits), - tuple(instruction.operation.params), - ) - self.assertEqual(cal_key, ((0,), (3.14,))) - - with self.assertWarns(DeprecationWarning): - # Make sure that key from instruction data matches the calibrations dictionary - self.assertIn(cal_key, circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][cal_key] - self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) - - def test_calibration_assignment_doesnt_mutate(self): - """That that assignment doesn't mutate the original circuit.""" - theta = Parameter("theta") - circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [theta]), [0]) - circ.measure(0, 0) - - with self.assertWarns(DeprecationWarning): - rxt_q0 = pulse.Schedule( - pulse.Play( - pulse.library.Gaussian(duration=128, sigma=16, amp=0.2 * theta / 3.14), - pulse.DriveChannel(0), - ) - ) - - circ.add_calibration("rxt", [0], rxt_q0, [theta]) - circ_copy = copy.deepcopy(circ) - assigned_circ = circ.assign_parameters({theta: 3.14}) - - with self.assertWarns(DeprecationWarning): - self.assertEqual(circ.calibrations, circ_copy.calibrations) - self.assertNotEqual(assigned_circ.calibrations, circ.calibrations) - - def test_calibration_assignment_w_expressions(self): - """That calibrations with multiple parameters are assigned correctly""" - theta = Parameter("theta") - sigma = Parameter("sigma") - circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [theta / 2, sigma]), [0]) - circ.measure(0, 0) - - with self.assertWarns(DeprecationWarning): - rxt_q0 = pulse.Schedule( - pulse.Play( - pulse.library.Gaussian(duration=128, sigma=4 * sigma, amp=0.2 * theta / 3.14), - pulse.DriveChannel(0), - ) - ) - - circ.add_calibration("rxt", [0], rxt_q0, [theta / 2, sigma]) - circ = circ.assign_parameters({theta: 3.14, sigma: 4}) - - instruction = circ.data[0] - cal_key = ( - tuple(circ.find_bit(q).index for q in instruction.qubits), - tuple(instruction.operation.params), - ) - self.assertEqual(cal_key, ((0,), (3.14 / 2, 4))) - with self.assertWarns(DeprecationWarning): - # Make sure that key from instruction data matches the calibrations dictionary - self.assertIn(cal_key, circ.calibrations["rxt"]) - sched = circ.calibrations["rxt"][cal_key] - self.assertEqual(sched.instructions[0][1].pulse.amp, 0.2) - self.assertEqual(sched.instructions[0][1].pulse.sigma, 16) - - def test_substitution(self): - """Test Parameter substitution (vs bind).""" - alpha = Parameter("⍺") - beta = Parameter("beta") - with self.assertWarns(DeprecationWarning): - schedule = pulse.Schedule(pulse.ShiftPhase(alpha, pulse.DriveChannel(0))) - - circ = QuantumCircuit(3, 3) - circ.append(Gate("my_rz", 1, [alpha]), [0]) - with self.assertWarns(DeprecationWarning): - circ.add_calibration("my_rz", [0], schedule, [alpha]) - - circ = circ.assign_parameters({alpha: 2 * beta}) - - circ = circ.assign_parameters({beta: 1.57}) - with self.assertWarns(DeprecationWarning): - cal_sched = circ.calibrations["my_rz"][((0,), (3.14,))] - self.assertEqual(float(cal_sched.instructions[0][1].phase), 3.14) - - def test_partial_assignment(self): - """Expressions of parameters with partial assignment.""" - alpha = Parameter("⍺") - beta = Parameter("beta") - gamma = Parameter("γ") - phi = Parameter("ϕ") - - with self.assertWarns(DeprecationWarning): - with pulse.build() as my_cal: - pulse.set_frequency(alpha + beta, pulse.DriveChannel(0)) - pulse.shift_frequency(gamma + beta, pulse.DriveChannel(0)) - pulse.set_phase(phi, pulse.DriveChannel(1)) - - circ = QuantumCircuit(2, 2) - circ.append(Gate("custom", 2, [alpha, beta, gamma, phi]), [0, 1]) - with self.assertWarns(DeprecationWarning): - circ.add_calibration("custom", [0, 1], my_cal, [alpha, beta, gamma, phi]) - - # Partial bind - delta = 1e9 - freq = 4.5e9 - shift = 0.5e9 - phase = 3.14 / 4 - - circ = circ.assign_parameters({alpha: freq - delta}) - with self.assertWarns(DeprecationWarning): - cal_sched = list(circ.calibrations["custom"].values())[0] - with self.assertWarns(DeprecationWarning): - # instructions triggers conversion to Schedule - self.assertEqual(cal_sched.instructions[0][1].frequency, freq - delta + beta) - - circ = circ.assign_parameters({beta: delta}) - with self.assertWarns(DeprecationWarning): - cal_sched = list(circ.calibrations["custom"].values())[0] - with self.assertWarns(DeprecationWarning): - # instructions triggers conversion to Schedule - self.assertEqual(float(cal_sched.instructions[0][1].frequency), freq) - self.assertEqual(cal_sched.instructions[1][1].frequency, gamma + delta) - - circ = circ.assign_parameters({gamma: shift - delta}) - with self.assertWarns(DeprecationWarning): - cal_sched = list(circ.calibrations["custom"].values())[0] - with self.assertWarns(DeprecationWarning): - # instructions triggers conversion to Schedule - self.assertEqual(float(cal_sched.instructions[1][1].frequency), shift) - self.assertEqual(cal_sched.instructions[2][1].phase, phi) - - circ = circ.assign_parameters({phi: phase}) - with self.assertWarns(DeprecationWarning): - cal_sched = list(circ.calibrations["custom"].values())[0] - with self.assertWarns(DeprecationWarning): - # instructions triggers conversion to Schedule - self.assertEqual(float(cal_sched.instructions[2][1].phase), phase) - def test_circuit_generation(self): """Test creating a series of circuits parametrically""" theta = Parameter("θ") diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index f72b6d2adb23..83d59f7e8e9d 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -21,7 +21,6 @@ from qiskit.circuit.duration import convert_durations_to_dt from qiskit.providers.fake_provider import Fake27QPulseV1, GenericBackendV2 from qiskit.providers.basic_provider import BasicSimulator -from qiskit.scheduler import ScheduleConfig from qiskit.transpiler import InstructionProperties from qiskit.transpiler.exceptions import TranspilerError from qiskit.transpiler.instruction_durations import InstructionDurations diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index f1f691da5a89..acac023e6fce 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -1295,154 +1295,6 @@ def test_translation_method_synthesis(self, optimization_level, basis_gates): self.assertTrue(Operator(out).equiv(qc)) self.assertTrue(set(out.count_ops()).issubset(basis_gates)) - def test_transpiled_custom_gates_calibration(self): - """Test if transpiled calibrations is equal to custom gates circuit calibrations.""" - custom_180 = Gate("mycustom", 1, [3.14]) - custom_90 = Gate("mycustom", 1, [1.57]) - - circ = QuantumCircuit(2) - circ.append(custom_180, [0]) - circ.append(custom_90, [1]) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - with pulse.build() as q1_y90: - pulse.play(pulse.library.Gaussian(20, -1.0, 3.0), pulse.DriveChannel(1)) - - # Add calibration - circ.add_calibration(custom_180, [0], q0_x180) - circ.add_calibration(custom_90, [1], q1_y90) - - transpiled_circuit = transpile( - circ, - backend=GenericBackendV2(num_qubits=4, seed=42), - layout_method="trivial", - seed_transpiler=42, - ) - with self.assertWarns(DeprecationWarning): - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - self.assertEqual(list(transpiled_circuit.count_ops().keys()), ["mycustom"]) - self.assertEqual(list(transpiled_circuit.count_ops().values()), [2]) - - def test_transpiled_basis_gates_calibrations(self): - """Test if the transpiled calibrations is equal to basis gates circuit calibrations.""" - circ = QuantumCircuit(2) - circ.h(0) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibration - circ.add_calibration("h", [0], q0_x180) - - transpiled_circuit = transpile( - circ, backend=GenericBackendV2(num_qubits=4, seed=42), seed_transpiler=42 - ) - with self.assertWarns(DeprecationWarning): - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - - def test_transpile_calibrated_custom_gate_on_diff_qubit(self): - """Test if the custom, non calibrated gate raises QiskitError.""" - custom_180 = Gate("mycustom", 1, [3.14]) - - circ = QuantumCircuit(2) - circ.append(custom_180, [0]) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibration - circ.add_calibration(custom_180, [1], q0_x180) - - with self.assertRaises(QiskitError): - transpile( - circ, - backend=GenericBackendV2(num_qubits=4, seed=42), - layout_method="trivial", - seed_transpiler=42, - optimization_level=1, - ) - - def test_transpile_calibrated_nonbasis_gate_on_diff_qubit(self): - """Test if the non-basis gates are transpiled if they are on different qubit that - is not calibrated.""" - circ = QuantumCircuit(2) - circ.h(0) - circ.h(1) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - # Add calibration - circ.add_calibration("h", [1], q0_x180) - - transpiled_circuit = transpile( - circ, backend=GenericBackendV2(num_qubits=4), seed_transpiler=42, optimization_level=1 - ) - with self.assertWarns(DeprecationWarning): - self.assertEqual(transpiled_circuit.calibrations, circ.calibrations) - self.assertEqual(set(transpiled_circuit.count_ops().keys()), {"rz", "sx", "h"}) - - def test_transpile_subset_of_calibrated_gates(self): - """Test transpiling a circuit with both basis gate (not-calibrated) and - a calibrated gate on different qubits.""" - x_180 = Gate("mycustom", 1, [3.14]) - - circ = QuantumCircuit(2) - circ.h(0) - circ.append(x_180, [0]) - circ.h(1) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_x180: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - - circ.add_calibration(x_180, [0], q0_x180) - circ.add_calibration("h", [1], q0_x180) # 'h' is calibrated on qubit 1 - - transpiled_circ = transpile( - circ, - backend=GenericBackendV2(num_qubits=4, seed=42), - layout_method="trivial", - seed_transpiler=42, - ) - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rz", "sx", "mycustom", "h"}) - - def test_parameterized_calibrations_transpile(self): - """Check that gates can be matched to their calibrations before and after parameter - assignment.""" - tau = Parameter("tau") - circ = QuantumCircuit(3, 3) - circ.append(Gate("rxt", 1, [2 * 3.14 * tau]), [0]) - - def q0_rxt(tau): - with self.assertWarns(DeprecationWarning): - with pulse.build() as q0_rxt: - pulse.play(pulse.library.Gaussian(20, 0.4 * tau, 3.0), pulse.DriveChannel(0)) - return q0_rxt - - with self.assertWarns(DeprecationWarning): - circ.add_calibration("rxt", [0], q0_rxt(tau), [2 * 3.14 * tau]) - - transpiled_circ = transpile( - circ, - backend=GenericBackendV2(num_qubits=4, seed=42), - layout_method="trivial", - seed_transpiler=42, - ) - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) - circ = circ.assign_parameters({tau: 1}) - transpiled_circ = transpile( - circ, - backend=GenericBackendV2(num_qubits=4), - layout_method="trivial", - seed_transpiler=42, - ) - self.assertEqual(set(transpiled_circ.count_ops().keys()), {"rxt"}) def test_inst_durations_from_calibrations(self): """Test that circuit calibrations can be used instead of explicitly diff --git a/test/python/converters/test_circuit_to_dag.py b/test/python/converters/test_circuit_to_dag.py index 06d3dec654e4..644ccc28fd58 100644 --- a/test/python/converters/test_circuit_to_dag.py +++ b/test/python/converters/test_circuit_to_dag.py @@ -43,21 +43,6 @@ def test_circuit_and_dag(self): circuit_out = dag_to_circuit(dag) self.assertEqual(circuit_out, circuit_in) - def test_calibrations(self): - """Test that calibrations are properly copied over.""" - circuit_in = QuantumCircuit(1) - with self.assertWarns(DeprecationWarning): - circuit_in.add_calibration("h", [0], None) - self.assertEqual(len(circuit_in.calibrations), 1) - - dag = circuit_to_dag(circuit_in) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(dag.calibrations), 1) - - circuit_out = dag_to_circuit(dag) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(circuit_out.calibrations), 1) - def test_wires_from_expr_nodes_condition(self): """Test that the classical wires implied by an `Expr` node in a control-flow op's `condition` are correctly transferred.""" diff --git a/test/python/converters/test_circuit_to_dagdependency.py b/test/python/converters/test_circuit_to_dagdependency.py index 7d3bbfd2c49c..e1586fe69e99 100644 --- a/test/python/converters/test_circuit_to_dagdependency.py +++ b/test/python/converters/test_circuit_to_dagdependency.py @@ -61,21 +61,6 @@ def test_circuit_and_dag_canonical2(self): circuit_out = dagdependency_to_circuit(dag_dependency) self.assertEqual(circuit_out, circuit_in) - def test_calibrations(self): - """Test that calibrations are properly copied over.""" - circuit_in = QuantumCircuit(1) - with self.assertWarns(DeprecationWarning): - circuit_in.add_calibration("h", [0], None) - self.assertEqual(len(circuit_in.calibrations), 1) - - dag_dependency = circuit_to_dagdependency(circuit_in) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(dag_dependency.calibrations), 1) - - circuit_out = dagdependency_to_circuit(dag_dependency) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(circuit_out.calibrations), 1) - def test_metadata(self): """Test circuit metadata is preservered through conversion.""" meta_dict = {"experiment_id": "1234", "execution_number": 4} diff --git a/test/python/converters/test_circuit_to_dagdependency_v2.py b/test/python/converters/test_circuit_to_dagdependency_v2.py index c7a31dc5767c..3067e4f34227 100644 --- a/test/python/converters/test_circuit_to_dagdependency_v2.py +++ b/test/python/converters/test_circuit_to_dagdependency_v2.py @@ -42,20 +42,6 @@ def test_circuit_and_dag_canonical(self): circuit_out = dagdependency_to_circuit(dag_dependency) self.assertEqual(circuit_out, circuit_in) - def test_calibrations(self): - """Test that calibrations are properly copied over.""" - circuit_in = QuantumCircuit(1) - with self.assertWarns(DeprecationWarning): - circuit_in.add_calibration("h", [0], None) - self.assertEqual(len(circuit_in.calibrations), 1) - - dag_dependency = _circuit_to_dagdependency_v2(circuit_in) - self.assertEqual(len(dag_dependency.calibrations), 1) - - circuit_out = dagdependency_to_circuit(dag_dependency) - with self.assertWarns(DeprecationWarning): - self.assertEqual(len(circuit_out.calibrations), 1) - def test_metadata(self): """Test circuit metadata is preservered through conversion.""" meta_dict = {"experiment_id": "1234", "execution_number": 4} diff --git a/test/python/dagcircuit/test_compose.py b/test/python/dagcircuit/test_compose.py index 1bbd3031d310..52c7907e6ab7 100644 --- a/test/python/dagcircuit/test_compose.py +++ b/test/python/dagcircuit/test_compose.py @@ -632,23 +632,6 @@ def test_reject_inline_to_nonexistent_var(self): ): dest.compose(source, inline_captures=True) - def test_compose_calibrations(self): - """Test that compose carries over the calibrations.""" - dag_cal = QuantumCircuit(1) - dag_cal.append(Gate("", 1, []), qargs=[0]) - with self.assertWarns(DeprecationWarning): - dag_cal.add_calibration(Gate("", 1, []), [0], Schedule()) - - empty_dag = circuit_to_dag(QuantumCircuit(1)) - calibrated_dag = circuit_to_dag(dag_cal) - composed_dag = empty_dag.compose(calibrated_dag, inplace=False) - - with self.assertWarns(DeprecationWarning): - cal = {"": {((0,), ()): Schedule(name="sched0")}} - with self.assertWarns(DeprecationWarning): - self.assertEqual(composed_dag.calibrations, cal) - self.assertEqual(calibrated_dag.calibrations, cal) - if __name__ == "__main__": unittest.main() diff --git a/test/python/primitives/test_primitive.py b/test/python/primitives/test_primitive.py index 78bcff5ff405..55ef9c3f73f9 100644 --- a/test/python/primitives/test_primitive.py +++ b/test/python/primitives/test_primitive.py @@ -122,38 +122,13 @@ class TestCircuitKey(QiskitTestCase): def test_different_circuits(self): """Test collision of quantum circuits.""" - with self.subTest("Ry circuit"): - - def test_func(n): - qc = QuantumCircuit(1, 1, name="foo") - qc.ry(n, 0) - return qc - - keys = [_circuit_key(test_func(i)) for i in range(5)] - self.assertEqual(len(keys), len(set(keys))) - - with self.subTest("pulse circuit"): - - def test_with_scheduling(n): - with self.assertWarns(DeprecationWarning): - custom_gate = pulse.Schedule(name="custom_x_gate") - custom_gate.insert( - 0, - pulse.Play(pulse.Constant(160 * n, 0.1), pulse.DriveChannel(0)), - inplace=True, - ) - qc = QuantumCircuit(1) - qc.x(0) - with self.assertWarns(DeprecationWarning): - qc.add_calibration("x", qubits=(0,), schedule=custom_gate) - - backend = GenericBackendV2( - num_qubits=2, basis_gates=["id", "u1", "u2", "u3", "cx"], seed=42 - ) - return transpile(qc, backend, scheduling_method="alap", optimization_level=1) - - keys = [_circuit_key(test_with_scheduling(i)) for i in range(1, 5)] - self.assertEqual(len(keys), len(set(keys))) + def test_func(n): + qc = QuantumCircuit(1, 1, name="foo") + qc.ry(n, 0) + return qc + + keys = [_circuit_key(test_func(i)) for i in range(5)] + self.assertEqual(len(keys), len(set(keys))) def test_circuit_key_controlflow(self): """Test for a circuit with control flow.""" diff --git a/test/python/pulse/test_block.py b/test/python/pulse/test_block.py index 652eba3ccf18..138584c278cf 100644 --- a/test/python/pulse/test_block.py +++ b/test/python/pulse/test_block.py @@ -760,24 +760,7 @@ def test_filter_channels(self): self.assertTrue(ch in filtered_blk.channels) self.assertEqual(filtered_blk, blk) - def test_filter_channels_nested_block(self): - """Test filtering over channels in a nested block.""" - with pulse.build() as blk: - with pulse.align_sequential(): - pulse.play(self.test_waveform0, self.d0) - pulse.delay(5, self.d0) - pulse.call( - self.backend.defaults() - .instruction_schedule_map._get_calibration_entry("cx", (0, 1)) - .get_schedule() - ) - - for ch in [self.d0, self.d1, pulse.ControlChannel(0)]: - filtered_blk = self._filter_and_test_consistency(blk, channels=[ch]) - self.assertEqual(len(filtered_blk.channels), 1) - self.assertTrue(ch in filtered_blk.channels) - - def test_filter_inst_types(self): + def test_filter_inst_types(self): """Test filtering on instruction types.""" with pulse.build() as blk: pulse.acquire(5, pulse.AcquireChannel(0), pulse.MemorySlot(0)) diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index d56595cd639a..9d875deb9065 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -338,162 +338,6 @@ def test_insert_dd_ghz_everywhere(self): self.assertEqual(ghz4_dd, expected) - def test_insert_dd_with_pulse_gate_calibrations(self): - """Test DD gates are inserted without error when circuit calibrations are used - - ┌───┐ ┌───────────────┐ ┌───┐ » - q_0: ──────┤ H ├─────────■──┤ Delay(75[dt]) ├──────┤ X ├───────» - ┌─────┴───┴─────┐ ┌─┴─┐└───────────────┘┌─────┴───┴──────┐» - q_1: ┤ Delay(50[dt]) ├─┤ X ├────────■────────┤ Delay(300[dt]) ├» - ├───────────────┴┐└───┘ ┌─┴─┐ └────────────────┘» - q_2: ┤ Delay(750[dt]) ├───────────┤ X ├──────────────■─────────» - ├────────────────┤ └───┘ ┌─┴─┐ » - q_3: ┤ Delay(950[dt]) ├────────────────────────────┤ X ├───────» - └────────────────┘ └───┘ » - meas: 4/══════════════════════════════════════════════════════════» - » - « ┌────────────────┐┌───┐┌───────────────┐ ░ ┌─┐ - « q_0: ┤ Delay(150[dt]) ├┤ X ├┤ Delay(75[dt]) ├─░─┤M├───────── - « └────────────────┘└───┘└───────────────┘ ░ └╥┘┌─┐ - « q_1: ─────────────────────────────────────────░──╫─┤M├────── - « ░ ║ └╥┘┌─┐ - « q_2: ─────────────────────────────────────────░──╫──╫─┤M├─── - « ░ ║ ║ └╥┘┌─┐ - « q_3: ─────────────────────────────────────────░──╫──╫──╫─┤M├ - « ░ ║ ║ ║ └╥┘ - «meas: 4/════════════════════════════════════════════╩══╩══╩══╩═ - « 0 1 2 3 - """ - dd_sequence = [XGate(), XGate()] - pm = PassManager( - [ - ALAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[0]), - ] - ) - - # Change duration to 100 from the 50 in self.durations to make sure - # gate duration is used correctly. - with self.assertWarns(DeprecationWarning): - with pulse.builder.build() as x_sched: - pulse.builder.delay(100, pulse.DriveChannel(0)) - - circ_in = self.ghz4.measure_all(inplace=False) - with self.assertWarns(DeprecationWarning): - circ_in.add_calibration(XGate(), (0,), x_sched) - - ghz4_dd = pm.run(circ_in) - - expected = self.ghz4.copy() - expected = expected.compose(Delay(50), [1], front=True) - expected = expected.compose(Delay(750), [2], front=True) - expected = expected.compose(Delay(950), [3], front=True) - - # Delays different from those of the default case using self.durations - expected = expected.compose(Delay(75), [0]) - expected = expected.compose(XGate(), [0]) - expected = expected.compose(Delay(150), [0]) - expected = expected.compose(XGate(), [0]) - expected = expected.compose(Delay(75), [0]) - - expected = expected.compose(Delay(300), [1]) - - expected.measure_all() - with self.assertWarns(DeprecationWarning): - expected.add_calibration(XGate(), (0,), x_sched) - - self.assertEqual(ghz4_dd, expected) - - def test_insert_dd_with_pulse_gate_calibrations_with_parmas(self): - """Test DD gates are inserted without error when parameterized circuit calibrations are used - - ┌───┐ ┌───────────────┐ ┌───┐ » - q_0: ──────┤ H ├─────────■──┤ Delay(75[dt]) ├──────┤ X ├───────» - ┌─────┴───┴─────┐ ┌─┴─┐└───────────────┘┌─────┴───┴──────┐» - q_1: ┤ Delay(50[dt]) ├─┤ X ├────────■────────┤ Delay(300[dt]) ├» - ├───────────────┴┐└───┘ ┌─┴─┐ └────────────────┘» - q_2: ┤ Delay(750[dt]) ├───────────┤ X ├──────────────■─────────» - ├────────────────┤ └───┘ ┌─┴─┐ » - q_3: ┤ Delay(950[dt]) ├────────────────────────────┤ X ├───────» - └────────────────┘ └───┘ » - meas: 4/══════════════════════════════════════════════════════════» - » - « ┌────────────────┐┌───┐┌───────────────┐ ░ ┌─┐ - « q_0: ┤ Delay(150[dt]) ├┤ X ├┤ Delay(75[dt]) ├─░─┤M├───────── - « └────────────────┘└───┘└───────────────┘ ░ └╥┘┌─┐ - « q_1: ─────────────────────────────────────────░──╫─┤M├────── - « ░ ║ └╥┘┌─┐ - « q_2: ─────────────────────────────────────────░──╫──╫─┤M├─── - « ░ ║ ║ └╥┘┌─┐ - « q_3: ─────────────────────────────────────────░──╫──╫──╫─┤M├ - « ░ ║ ║ ║ └╥┘ - «meas: 4/════════════════════════════════════════════╩══╩══╩══╩═ - « 0 1 2 3 - """ - # Change duration to 100 from the 50 in self.durations to make sure - # gate duration is used correctly. - amp = Parameter("amp") - with self.assertWarns(DeprecationWarning): - with pulse.builder.build() as sched: - pulse.builder.play( - pulse.Gaussian(100, amp=amp, sigma=10.0), - pulse.DriveChannel(0), - ) - - class Echo(Gate): - """Dummy Gate subclass for testing - - In this test, we use a non-standard gate so we can add parameters - to it, in order to test the handling of parameters by - PadDynamicalDecoupling. PadDynamicalDecoupling checks that the DD - sequence is equivalent to the identity, so we can not use Gate - directly. Here we subclass Gate and add the identity as its matrix - representation to satisfy PadDynamicalDecoupling's check. - """ - - def __array__(self, dtype=None, copy=None): - if copy is False: - raise ValueError("cannot produce matrix without calculation") - return np.eye(2, dtype=dtype) - - # A gate with one unbound and one bound parameter to leave in the final - # circuit. - echo = Echo("echo", 1, [amp, 10.0]) - - circ_in = self.ghz4.measure_all(inplace=False) - with self.assertWarns(DeprecationWarning): - circ_in.add_calibration(echo, (0,), sched) - - dd_sequence = [echo, echo] - pm = PassManager( - [ - ALAPScheduleAnalysis(self.durations), - PadDynamicalDecoupling(self.durations, dd_sequence, qubits=[0]), - ] - ) - - ghz4_dd = pm.run(circ_in) - - expected = self.ghz4.copy() - expected = expected.compose(Delay(50), [1], front=True) - expected = expected.compose(Delay(750), [2], front=True) - expected = expected.compose(Delay(950), [3], front=True) - - # Delays different from those of the default case using self.durations - expected = expected.compose(Delay(75), [0]) - expected = expected.compose(echo, [0]) - expected = expected.compose(Delay(150), [0]) - expected = expected.compose(echo, [0]) - expected = expected.compose(Delay(75), [0]) - - expected = expected.compose(Delay(300), [1]) - - expected.measure_all() - with self.assertWarns(DeprecationWarning): - expected.add_calibration(echo, (0,), sched) - - self.assertEqual(ghz4_dd, expected) - def test_insert_dd_ghz_xy4(self): """Test XY4 sequence of DD gates. @@ -852,35 +696,6 @@ def test_insert_dd_bad_sequence(self): with self.assertRaises(TranspilerError): pm.run(self.ghz4) - @data(0.5, 1.5) - def test_dd_with_calibrations_with_parameters(self, param_value): - """Check that calibrations in a circuit with parameters work fine.""" - - circ = QuantumCircuit(2) - circ.x(0) - circ.cx(0, 1) - circ.rx(param_value, 1) - - rx_duration = int(param_value * 1000) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as rx: - pulse.play( - pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), pulse.DriveChannel(1) - ) - - with self.assertWarns(DeprecationWarning): - circ.add_calibration("rx", (1,), rx, params=[param_value]) - - durations = InstructionDurations([("x", None, 100), ("cx", None, 300)]) - - dd_sequence = [XGate(), XGate()] - pm = PassManager( - [ALAPScheduleAnalysis(durations), PadDynamicalDecoupling(durations, dd_sequence)] - ) - - self.assertEqual(pm.run(circ).duration, rx_duration + 100 + 300) - def test_insert_dd_ghz_xy4_with_alignment(self): """Test DD with pulse alignment constraints. diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index 41734d1ca8ee..bd6e8e91add9 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -495,21 +495,6 @@ def test_target_cannot_flip_message(self): with self.assertRaisesRegex(TranspilerError, "my_2q_gate would be supported.*"): pass_(circuit) - def test_target_cannot_flip_message_calibrated(self): - """A suitable error message should be emitted if the gate would be supported if it were - flipped.""" - target = Target(num_qubits=2) - target.add_instruction(CXGate(), properties={(0, 1): None}) - - gate = Gate("my_2q_gate", 2, []) - circuit = QuantumCircuit(2) - circuit.append(gate, (1, 0)) - with self.assertWarns(DeprecationWarning): - circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) - - pass_ = GateDirection(None, target) - with self.assertRaisesRegex(TranspilerError, "my_2q_gate would be supported.*"): - pass_(circuit) def test_target_unknown_gate_message(self): """A suitable error message should be emitted if the gate isn't valid in either direction on @@ -525,35 +510,6 @@ def test_target_unknown_gate_message(self): with self.assertRaisesRegex(TranspilerError, "my_2q_gate.*not supported on qubits .*"): pass_(circuit) - def test_allows_calibrated_gates_coupling_map(self): - """Test that the gate direction pass allows a gate that's got a calibration to pass through - without error.""" - cm = CouplingMap([(1, 0)]) - - gate = Gate("my_2q_gate", 2, []) - circuit = QuantumCircuit(2) - circuit.append(gate, (0, 1)) - with self.assertWarns(DeprecationWarning): - circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) - - pass_ = GateDirection(cm) - self.assertEqual(pass_(circuit), circuit) - - def test_allows_calibrated_gates_target(self): - """Test that the gate direction pass allows a gate that's got a calibration to pass through - without error.""" - target = Target(num_qubits=2) - target.add_instruction(CXGate(), properties={(0, 1): None}) - - gate = Gate("my_2q_gate", 2, []) - circuit = QuantumCircuit(2) - circuit.append(gate, (0, 1)) - with self.assertWarns(DeprecationWarning): - circuit.add_calibration(gate, (0, 1), pulse.ScheduleBlock()) - - pass_ = GateDirection(None, target) - self.assertEqual(pass_(circuit), circuit) - if __name__ == "__main__": unittest.main() diff --git a/test/python/transpiler/test_normalize_rx_angle.py b/test/python/transpiler/test_normalize_rx_angle.py index 6f1c7f22ea76..8c75891738e1 100644 --- a/test/python/transpiler/test_normalize_rx_angle.py +++ b/test/python/transpiler/test_normalize_rx_angle.py @@ -26,6 +26,7 @@ from test import QiskitTestCase # pylint: disable=wrong-import-order +# Should we move this test entirely? (after we remove the pass???) @ddt class TestNormalizeRXAngle(QiskitTestCase): """Tests the NormalizeRXAngle pass.""" diff --git a/test/python/transpiler/test_passmanager_config.py b/test/python/transpiler/test_passmanager_config.py index fccd68f0a261..6c589d7b86e9 100644 --- a/test/python/transpiler/test_passmanager_config.py +++ b/test/python/transpiler/test_passmanager_config.py @@ -35,7 +35,6 @@ def test_config_from_backend(self): backend = Fake27QPulseV1() config = PassManagerConfig.from_backend(backend) self.assertEqual(config.basis_gates, backend.configuration().basis_gates) - self.assertEqual(config.inst_map, backend.defaults().instruction_schedule_map) self.assertEqual( str(config.coupling_map), str(CouplingMap(backend.configuration().coupling_map)) ) @@ -45,8 +44,6 @@ def test_config_from_backend_v2(self): backend = GenericBackendV2(num_qubits=27, seed=42) config = PassManagerConfig.from_backend(backend) self.assertEqual(config.basis_gates, backend.operation_names) - with self.assertWarns(DeprecationWarning): - self.assertEqual(config.inst_map, backend.instruction_schedule_map) self.assertEqual(config.coupling_map.get_edges(), backend.coupling_map.get_edges()) def test_invalid_backend(self): @@ -72,7 +69,6 @@ def test_from_backend_and_user_v1(self): ) self.assertEqual(config.basis_gates, ["user_gate"]) self.assertNotEqual(config.basis_gates, backend.configuration().basis_gates) - self.assertIsNone(config.inst_map) self.assertEqual( str(config.coupling_map), str(CouplingMap(backend.configuration().coupling_map)) ) @@ -98,20 +94,9 @@ def test_from_backend_and_user(self): ) self.assertEqual(config.basis_gates, ["user_gate"]) self.assertNotEqual(config.basis_gates, backend.operation_names) - self.assertEqual(config.inst_map.instructions, []) self.assertEqual(str(config.coupling_map), str(CouplingMap(backend.coupling_map))) self.assertEqual(config.initial_layout, initial_layout) - def test_from_backendv1_inst_map_is_none(self): - """Test that from_backend() works with backend that has defaults defined as None.""" - with self.assertWarns(DeprecationWarning): - backend = Fake27QPulseV1() - backend.defaults = lambda: None - with self.assertWarns(DeprecationWarning): - config = PassManagerConfig.from_backend(backend) - self.assertIsInstance(config, PassManagerConfig) - self.assertIsNone(config.inst_map) - def test_invalid_user_option(self): """Test from_backend() with an invalid user option.""" backend = GenericBackendV2(num_qubits=20, seed=42) @@ -123,12 +108,10 @@ def test_str(self): pm_config = PassManagerConfig.from_backend(BasicSimulator()) # For testing remove instruction schedule map, its str output is non-deterministic # based on hash seed - pm_config.inst_map = None str_out = str(pm_config) expected = """Pass Manager Config: \tinitial_layout: None \tbasis_gates: ['ccx', 'ccz', 'ch', 'cp', 'crx', 'cry', 'crz', 'cs', 'csdg', 'cswap', 'csx', 'cu', 'cu1', 'cu3', 'cx', 'cy', 'cz', 'dcx', 'delay', 'ecr', 'global_phase', 'h', 'id', 'iswap', 'measure', 'p', 'r', 'rccx', 'reset', 'rx', 'rxx', 'ry', 'ryy', 'rz', 'rzx', 'rzz', 's', 'sdg', 'swap', 'sx', 'sxdg', 't', 'tdg', 'u', 'u1', 'u2', 'u3', 'unitary', 'x', 'xx_minus_yy', 'xx_plus_yy', 'y', 'z'] -\tinst_map: None \tcoupling_map: None \tlayout_method: None \trouting_method: None diff --git a/test/python/transpiler/test_preset_passmanagers.py b/test/python/transpiler/test_preset_passmanagers.py index 5b23e777fac4..f6e25914d39b 100644 --- a/test/python/transpiler/test_preset_passmanagers.py +++ b/test/python/transpiler/test_preset_passmanagers.py @@ -1362,7 +1362,6 @@ def test_with_no_backend(self, optimization_level): optimization_level, coupling_map=target.coupling_map, basis_gates=target.operation_names, - inst_map=target.instruction_schedule_map, instruction_durations=target.instruction_durations, timing_constraints=target.target.timing_constraints(), target=target.target, diff --git a/test/python/transpiler/test_scheduling_padding_pass.py b/test/python/transpiler/test_scheduling_padding_pass.py index f5ebc349d814..8bedca4f01b6 100644 --- a/test/python/transpiler/test_scheduling_padding_pass.py +++ b/test/python/transpiler/test_scheduling_padding_pass.py @@ -819,34 +819,6 @@ def test_dag_introduces_extra_dependency_between_conditionals(self): self.assertEqual(expected, scheduled) - def test_scheduling_with_calibration(self): - """Test if calibrated instruction can update node duration.""" - qc = QuantumCircuit(2) - qc.x(0) - qc.cx(0, 1) - qc.x(1) - qc.cx(0, 1) - - with self.assertWarns(DeprecationWarning): - xsched = Schedule(Play(Constant(300, 0.1), DriveChannel(0))) - qc.add_calibration("x", (0,), xsched) - - durations = InstructionDurations([("x", None, 160), ("cx", None, 600)]) - pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay()]) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2) - expected.x(0) - expected.delay(300, 1) - expected.cx(0, 1) - expected.x(1) - expected.delay(160, 0) - expected.cx(0, 1) - with self.assertWarns(DeprecationWarning): - expected.add_calibration("x", (0,), xsched) - - self.assertEqual(expected, scheduled) - def test_padding_not_working_without_scheduling(self): """Test padding fails when un-scheduled DAG is input.""" qc = QuantumCircuit(1, 1) diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index 694ef38dc4fa..dfb9e771a4af 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1019,11 +1019,10 @@ def __init__( self, duration=None, error=None, - calibration=None, tuned=None, diamond_norm_error=None, ): - super().__init__(duration=duration, error=error, calibration=calibration) + super().__init__(duration=duration, error=error) self.tuned = tuned self.diamond_norm_error = diamond_norm_error @@ -1698,7 +1697,7 @@ def test_empty_repr(self): properties = InstructionProperties() self.assertEqual( repr(properties), - "InstructionProperties(duration=None, error=None, calibration=None)", + "InstructionProperties(duration=None, error=None)", ) @@ -1760,17 +1759,14 @@ def test_inst_map(self): properties = fake_backend.properties() defaults = fake_backend.defaults() constraints = TimingConstraints(**config.timing_constraints) - with self.assertWarns(DeprecationWarning): - target = Target.from_configuration( - basis_gates=config.basis_gates, - num_qubits=config.num_qubits, - coupling_map=CouplingMap(config.coupling_map), - backend_properties=properties, - dt=config.dt, - inst_map=defaults.instruction_schedule_map, - timing_constraints=constraints, - ) - self.assertIsNotNone(target["sx"][(0,)].calibration) + target = Target.from_configuration( + basis_gates=config.basis_gates, + num_qubits=config.num_qubits, + coupling_map=CouplingMap(config.coupling_map), + backend_properties=properties, + dt=config.dt, + timing_constraints=constraints, + ) self.assertEqual(target.granularity, constraints.granularity) self.assertEqual(target.min_length, constraints.min_length) self.assertEqual(target.pulse_alignment, constraints.pulse_alignment) diff --git a/test/python/transpiler/test_vf2_layout.py b/test/python/transpiler/test_vf2_layout.py index 9fbc9ac45480..23396e7cc6c7 100644 --- a/test/python/transpiler/test_vf2_layout.py +++ b/test/python/transpiler/test_vf2_layout.py @@ -53,9 +53,8 @@ def assertLayout(self, dag, coupling_map, property_set, strict_direction=False): def run(dag, wire_map): for gate in dag.two_qubit_ops(): - with self.assertWarns(DeprecationWarning): - if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): - continue + if isinstance(gate.op, ControlFlowOp): + continue physical_q0 = wire_map[gate.qargs[0]] physical_q1 = wire_map[gate.qargs[1]] diff --git a/test/python/transpiler/test_vf2_post_layout.py b/test/python/transpiler/test_vf2_post_layout.py index 9aaab695197a..7a4535e4cb6b 100644 --- a/test/python/transpiler/test_vf2_post_layout.py +++ b/test/python/transpiler/test_vf2_post_layout.py @@ -46,9 +46,8 @@ def assertLayout(self, dag, coupling_map, property_set): def run(dag, wire_map): for gate in dag.two_qubit_ops(): - with self.assertWarns(DeprecationWarning): - if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): - continue + if isinstance(gate.op, ControlFlowOp): + continue physical_q0 = wire_map[gate.qargs[0]] physical_q1 = wire_map[gate.qargs[1]] self.assertTrue((physical_q0, physical_q1) in edges) @@ -72,9 +71,8 @@ def assertLayoutV2(self, dag, target, property_set): def run(dag, wire_map): for gate in dag.two_qubit_ops(): - with self.assertWarns(DeprecationWarning): - if dag.has_calibration_for(gate) or isinstance(gate.op, ControlFlowOp): - continue + if isinstance(gate.op, ControlFlowOp): + continue physical_q0 = wire_map[gate.qargs[0]] physical_q1 = wire_map[gate.qargs[1]] qargs = (physical_q0, physical_q1) @@ -556,9 +554,6 @@ def assertLayout(self, dag, coupling_map, property_set): layout = property_set["post_layout"] for gate in dag.two_qubit_ops(): - with self.assertWarns(DeprecationWarning): - if dag.has_calibration_for(gate): - continue physical_q0 = layout[gate.qargs[0]] physical_q1 = layout[gate.qargs[1]] self.assertTrue(coupling_map.graph.has_edge(physical_q0, physical_q1)) @@ -572,9 +567,6 @@ def assertLayoutV2(self, dag, target, property_set): layout = property_set["post_layout"] for gate in dag.two_qubit_ops(): - with self.assertWarns(DeprecationWarning): - if dag.has_calibration_for(gate): - continue physical_q0 = layout[gate.qargs[0]] physical_q1 = layout[gate.qargs[1]] qargs = (physical_q0, physical_q1) diff --git a/test/visual/mpl/circuit/references/calibrations.png b/test/visual/mpl/circuit/references/calibrations.png deleted file mode 100644 index c8f4d8fb08382ce9d95fcbb01342b4ba8c64dee5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7213 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 diff --git a/test/visual/mpl/circuit/references/calibrations_with_control_gates.png b/test/visual/mpl/circuit/references/calibrations_with_control_gates.png deleted file mode 100644 index 49db6bbbdf258d0e9ae4d62fc791a375fa8ed4f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8984 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 diff --git a/test/visual/mpl/circuit/references/calibrations_with_rzz_and_rxx.png b/test/visual/mpl/circuit/references/calibrations_with_rzz_and_rxx.png deleted file mode 100644 index 8c57d9d50352f3635fe933fe328d48e5afd3dde6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12305 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 BiH!gN diff --git a/test/visual/mpl/circuit/references/calibrations_with_swap_and_reset.png b/test/visual/mpl/circuit/references/calibrations_with_swap_and_reset.png deleted file mode 100644 index 2aa31394d7287ee4a0dbebaa35214d1415286945..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10213 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 diff --git a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py index 0a2aaad0e718..0f179d7d7a60 100644 --- a/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py +++ b/test/visual/mpl/circuit/test_circuit_matplotlib_drawer.py @@ -114,149 +114,6 @@ def test_empty_circuit(self): ) self.assertGreaterEqual(ratio, self.threshold) - def test_calibrations(self): - """Test calibrations annotations - See https://github.com/Qiskit/qiskit-terra/issues/5920 - """ - - circuit = QuantumCircuit(2, 2) - circuit.h(0) - - from qiskit import pulse - - with self.assertWarns(DeprecationWarning): - with pulse.build(name="hadamard") as h_q0: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(0) - ) - - circuit.add_calibration("h", [0], h_q0) - - fname = "calibrations.png" - self.circuit_drawer(circuit, output="mpl", filename=fname) - - ratio = VisualTestUtilities._save_diff( - self._image_path(fname), - self._reference_path(fname), - fname, - FAILURE_DIFF_DIR, - FAILURE_PREFIX, - ) - self.assertGreaterEqual(ratio, self.threshold) - - def test_calibrations_with_control_gates(self): - """Test calibrations annotations - See https://github.com/Qiskit/qiskit-terra/issues/5920 - """ - - circuit = QuantumCircuit(2, 2) - circuit.cx(0, 1) - circuit.ch(0, 1) - - from qiskit import pulse - - with self.assertWarns(DeprecationWarning): - with pulse.build(name="cnot") as cx_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - - circuit.add_calibration("cx", [0, 1], cx_q01) - - with pulse.build(name="ch") as ch_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - - circuit.add_calibration("ch", [0, 1], ch_q01) - - fname = "calibrations_with_control_gates.png" - self.circuit_drawer(circuit, output="mpl", filename=fname) - - ratio = VisualTestUtilities._save_diff( - self._image_path(fname), - self._reference_path(fname), - fname, - FAILURE_DIFF_DIR, - FAILURE_PREFIX, - ) - self.assertGreaterEqual(ratio, self.threshold) - - def test_calibrations_with_swap_and_reset(self): - """Test calibrations annotations - See https://github.com/Qiskit/qiskit-terra/issues/5920 - """ - - circuit = QuantumCircuit(2, 2) - circuit.swap(0, 1) - circuit.reset(0) - - from qiskit import pulse - - with self.assertWarns(DeprecationWarning): - with pulse.build(name="swap") as swap_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - - circuit.add_calibration("swap", [0, 1], swap_q01) - - with pulse.build(name="reset") as reset_q0: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - - circuit.add_calibration("reset", [0], reset_q0) - - fname = "calibrations_with_swap_and_reset.png" - self.circuit_drawer(circuit, output="mpl", filename=fname) - - ratio = VisualTestUtilities._save_diff( - self._image_path(fname), - self._reference_path(fname), - fname, - FAILURE_DIFF_DIR, - FAILURE_PREFIX, - ) - self.assertGreaterEqual(ratio, self.threshold) - - def test_calibrations_with_rzz_and_rxx(self): - """Test calibrations annotations - See https://github.com/Qiskit/qiskit-terra/issues/5920 - """ - circuit = QuantumCircuit(2, 2) - circuit.rzz(pi, 0, 1) - circuit.rxx(pi, 0, 1) - - from qiskit import pulse - - with self.assertWarns(DeprecationWarning): - with pulse.build(name="rzz") as rzz_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - - circuit.add_calibration("rzz", [0, 1], rzz_q01) - - with pulse.build(name="rxx") as rxx_q01: - pulse.play( - pulse.library.Gaussian(duration=128, amp=0.1, sigma=16), pulse.DriveChannel(1) - ) - - circuit.add_calibration("rxx", [0, 1], rxx_q01) - - fname = "calibrations_with_rzz_and_rxx.png" - self.circuit_drawer(circuit, output="mpl", filename=fname) - - ratio = VisualTestUtilities._save_diff( - self._image_path(fname), - self._reference_path(fname), - fname, - FAILURE_DIFF_DIR, - FAILURE_PREFIX, - ) - self.assertGreaterEqual(ratio, self.threshold) - def test_no_ops(self): """Test circuit with no ops. See https://github.com/Qiskit/qiskit-terra/issues/5393""" From 17ffa6ff2f5584cc09660f186176deb3cb153f5e Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 16 Feb 2025 14:17:12 +0200 Subject: [PATCH 08/18] Avoid generating pulse circuits in load_qpy & version >= 2.0 --- test/qpy_compat/test_qpy.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 0ba8e22d7690..628eff9984b5 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -426,6 +426,7 @@ def generate_control_flow_switch_circuits(): def generate_schedule_blocks(): """Standard QPY testcase for schedule blocks.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, channels, library current_version = current_version_str.split(".") @@ -493,6 +494,7 @@ def generate_schedule_blocks(): def generate_referenced_schedule(): """Test for QPY serialization of unassigned reference schedules.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, channels, library schedule_blocks = [] @@ -518,6 +520,7 @@ def generate_referenced_schedule(): def generate_calibrated_circuits(): """Test for QPY serialization with calibrations.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, Constant, DriveChannel circuits = [] @@ -588,6 +591,7 @@ def generate_open_controlled_gates(): def generate_acquire_instruction_with_kernel_and_discriminator(): """Test QPY serialization with Acquire instruction with kernel and discriminator.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, AcquireChannel, MemorySlot, Discriminator, Kernel schedule_blocks = [] @@ -820,8 +824,13 @@ def generate_v12_expr(): return [index, shift] -def generate_circuits(version_parts): - """Generate reference circuits.""" +def generate_circuits(version_parts, load_context=False): + """Generate reference circuits. + + If load_context is True, avoid generating Pulse-based reference + circuits. For those circuits, load_qpy only checks that the cached + circuits can be loaded without erroring.""" + output_circuits = { "full.qpy": [generate_full_circuit()], "unitary.qpy": [generate_unitary_gate_circuit()], @@ -849,10 +858,16 @@ def generate_circuits(version_parts): if version_parts >= (0, 19, 2): output_circuits["control_flow.qpy"] = generate_control_flow_circuits() if version_parts >= (0, 21, 0) and version_parts < (2, 0): - output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks() - output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits() + output_circuits["schedule_blocks.qpy"] = ( + None if load_context else generate_schedule_blocks() + ) + output_circuits["pulse_gates.qpy"] = ( + None if load_context else generate_calibrated_circuits() + ) if version_parts >= (0, 24, 0) and version_parts < (2, 0): - output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule() + output_circuits["referenced_schedule_blocks.qpy"] = ( + None if load_context else generate_referenced_schedule() + ) if version_parts >= (0, 24, 0): output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits() if version_parts >= (0, 24, 1): @@ -862,7 +877,7 @@ def generate_circuits(version_parts): output_circuits["layout.qpy"] = generate_layout_circuits() if version_parts >= (0, 25, 0) and version_parts < (2, 0): output_circuits["acquire_inst_with_kernel_and_disc.qpy"] = ( - generate_acquire_instruction_with_kernel_and_discriminator() + None if load_context else generate_acquire_instruction_with_kernel_and_discriminator() ) output_circuits["control_flow_expr.qpy"] = generate_control_flow_expr() if version_parts >= (0, 45, 2): @@ -1007,10 +1022,11 @@ def _main(): version_match = re.search(VERSION_PATTERN, args.version, re.VERBOSE | re.IGNORECASE) version_parts = tuple(int(x) for x in version_match.group("release").split(".")) - qpy_files = generate_circuits(version_parts) if args.command == "generate": + qpy_files = generate_circuits(version_parts) generate_qpy(qpy_files) else: + qpy_files = generate_circuits(version_parts, load_context=True) load_qpy(qpy_files, version_parts) From c2fabe40bda3766a338047a013426b549304b9da Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sun, 16 Feb 2025 18:18:49 +0200 Subject: [PATCH 09/18] Remove more stuff --- .../src/basis/basis_translator/mod.rs | 24 +++---- crates/accelerate/src/check_map.rs | 3 +- crates/accelerate/src/gate_direction.rs | 5 +- crates/circuit/src/converters.rs | 6 +- crates/circuit/src/dag_circuit.rs | 19 +----- qiskit/circuit/quantumcircuit.py | 4 +- qiskit/compiler/transpiler.py | 3 +- qiskit/converters/dagdependency_to_dag.py | 1 - qiskit/dagcircuit/dagdependency.py | 4 +- qiskit/dagcircuit/dagdependency_v2.py | 4 +- qiskit/providers/backend_compat.py | 6 +- .../fake_provider/utils/backend_converter.py | 2 +- qiskit/scheduler/__init__.py | 40 ------------ qiskit/transpiler/passes/__init__.py | 2 +- .../optimization/optimize_1q_commutation.py | 1 - .../optimization/optimize_1q_decomposition.py | 9 +-- .../passes/scheduling/alignments/__init__.py | 5 +- .../scheduling/alignments/check_durations.py | 1 - .../passes/scheduling/dynamical_decoupling.py | 1 - .../padding/dynamical_decoupling.py | 1 - .../passes/scheduling/time_unit_conversion.py | 1 - .../passes/synthesis/high_level_synthesis.py | 8 +-- .../preset_passmanagers/builtin_plugins.py | 2 - .../transpiler/preset_passmanagers/common.py | 4 +- .../generate_preset_pass_manager.py | 6 +- .../transpiler/preset_passmanagers/level3.py | 1 + qiskit/transpiler/target.py | 10 +-- qiskit/visualization/circuit/_utils.py | 9 +-- qiskit/visualization/circuit/matplotlib.py | 6 +- .../python/circuit/test_circuit_operations.py | 1 - .../python/circuit/test_circuit_properties.py | 4 +- test/python/circuit/test_compose.py | 2 - test/python/circuit/test_parameters.py | 2 - test/python/circuit/test_scheduled_circuit.py | 8 +-- test/python/compiler/test_transpiler.py | 48 -------------- test/python/dagcircuit/test_compose.py | 2 - test/python/primitives/test_primitive.py | 3 +- test/python/providers/test_fake_backends.py | 62 +------------------ test/python/pulse/test_block.py | 2 +- .../transpiler/test_dynamical_decoupling.py | 5 +- test/python/transpiler/test_gate_direction.py | 3 +- .../test_scheduling_padding_pass.py | 1 - test/python/transpiler/test_target.py | 1 - test/utils/providers/__init__.py | 15 ----- 44 files changed, 46 insertions(+), 301 deletions(-) delete mode 100644 qiskit/scheduler/__init__.py delete mode 100644 test/utils/providers/__init__.py diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index ee9471505d3c..8c9d6449d934 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -198,7 +198,7 @@ fn run( Ok(out_dag) } -/// Method that extracts all non-calibrated gate instances identifiers from a DAGCircuit. +/// Method that extracts all gate instances identifiers from a DAGCircuit. fn extract_basis( py: Python, circuit: &DAGCircuit, @@ -212,9 +212,8 @@ fn extract_basis( basis: &mut HashSet, min_qubits: usize, ) -> PyResult<()> { - for (node, operation) in circuit.op_nodes(true) { - if circuit.get_qargs(operation.qubits).len() >= min_qubits - { + for (_node, operation) in circuit.op_nodes(true) { + if circuit.get_qargs(operation.qubits).len() >= min_qubits { basis.insert((operation.op.name().to_string(), operation.op.num_qubits())); } if operation.op.control_flow() { @@ -243,8 +242,7 @@ fn extract_basis( .borrow(); for (index, inst) in circuit_data.iter().enumerate() { let instruction_object = circuit.get_item(index)?; - if circuit_data.get_qargs(inst.qubits).len() >= min_qubits - { + if circuit_data.get_qargs(inst.qubits).len() >= min_qubits { basis.insert((inst.op.name().to_string(), inst.op.num_qubits())); } if inst.op.control_flow() { @@ -263,7 +261,7 @@ fn extract_basis( } /// Method that extracts a mapping of all the qargs in the local_source basis -/// obtained from the [Target], to all non-calibrated gate instances identifiers from a DAGCircuit. +/// obtained from the [Target], to all gate instances identifiers from a DAGCircuit. /// When dealing with `ControlFlowOp` instances the function will perform a recursion call /// to a variant design to handle instances of `QuantumCircuit`. fn extract_basis_target( @@ -274,7 +272,7 @@ fn extract_basis_target( min_qubits: usize, qargs_with_non_global_operation: &HashMap, HashSet>, ) -> PyResult<()> { - for (node, node_obj) in dag.op_nodes(true) { + for (_node, node_obj) in dag.op_nodes(true) { let qargs: &[Qubit] = dag.get_qargs(node_obj.qubits); if qargs.len() < min_qubits { continue; @@ -318,8 +316,8 @@ fn extract_basis_target( unreachable!("Control flow op is not a control flow op. But control_flow is `true`") }; let bound_inst = op.instruction.bind(py); - // Use python side extraction instead of the Rust method `op.blocks` due to - // required usage of a python-space method `QuantumCircuit.has_calibration_for`. + // TODO: Use Rust method `op.blocks` instead of Python side extraction now that + // the usage of a python-space method `QuantumCircuit.has_calibration_for` is not needed anymore let blocks = bound_inst.getattr("blocks")?.try_iter()?; for block in blocks { extract_basis_target_circ( @@ -339,7 +337,6 @@ fn extract_basis_target( /// This needs to use a Python instance of `QuantumCircuit` due to it needing /// to access `has_calibration_for()` which is unavailable through rust. However, /// this API will be removed with the deprecation of `Pulse`. -// TODO: remove this fn extract_basis_target_circ( circuit: &Bound, source_basis: &mut HashSet, @@ -350,10 +347,9 @@ fn extract_basis_target_circ( let py = circuit.py(); let circ_data_bound = circuit.getattr("_data")?.downcast_into::()?; let circ_data = circ_data_bound.borrow(); - for (index, node_obj) in circ_data.iter().enumerate() { + for node_obj in circ_data.iter() { let qargs = circ_data.get_qargs(node_obj.qubits); - if qargs.len() < min_qubits - { + if qargs.len() < min_qubits { continue; } // Treat the instruction as on an incomplete basis if the qargs are in the diff --git a/crates/accelerate/src/check_map.rs b/crates/accelerate/src/check_map.rs index 5f8240fbb213..d439cdc48c5d 100644 --- a/crates/accelerate/src/check_map.rs +++ b/crates/accelerate/src/check_map.rs @@ -65,8 +65,7 @@ fn recurse<'py>( } } } - } else if qubits.len() == 2 && !check_qubits(qubits) - { + } else if qubits.len() == 2 && !check_qubits(qubits) { return Ok(Some(( inst.op.name().to_string(), [qubits[0].0, qubits[1].0], diff --git a/crates/accelerate/src/gate_direction.rs b/crates/accelerate/src/gate_direction.rs index 5a1871189b17..a345fde6fbe0 100755 --- a/crates/accelerate/src/gate_direction.rs +++ b/crates/accelerate/src/gate_direction.rs @@ -20,11 +20,9 @@ use pyo3::types::PyTuple; use qiskit_circuit::operations::OperationRef; use qiskit_circuit::packed_instruction::PackedOperation; use qiskit_circuit::{ - circuit_instruction::CircuitInstruction, circuit_instruction::ExtraInstructionAttributes, converters::{circuit_to_dag, QuantumCircuitData}, dag_circuit::DAGCircuit, - dag_node::{DAGNode, DAGOpNode}, imports, imports::get_std_gate_class, operations::Operation, @@ -335,8 +333,7 @@ where } } // No matching replacement found - if gate_complies(packed_inst, &[op_args1, op_args0]) - { + if gate_complies(packed_inst, &[op_args1, op_args0]) { return Err(TranspilerError::new_err(format!("{} would be supported on {:?} if the direction was swapped, but no rules are known to do that. {:?} can be automatically flipped.", packed_inst.op.name(), op_args, vec!["cx", "cz", "ecr", "swap", "rzx", "rxx", "ryy", "rzz"]))); // NOTE: Make sure to update the list of the supported gates if adding more replacements } else { diff --git a/crates/circuit/src/converters.rs b/crates/circuit/src/converters.rs index b49cfb1ed6cd..8ffe2e9c7a45 100644 --- a/crates/circuit/src/converters.rs +++ b/crates/circuit/src/converters.rs @@ -13,12 +13,8 @@ #[cfg(feature = "cache_pygates")] use std::sync::OnceLock; -use hashbrown::HashMap; use pyo3::prelude::*; -use pyo3::{ - intern, - types::{PyDict, PyList}, -}; +use pyo3::{intern, types::PyList}; use crate::circuit_data::CircuitData; use crate::dag_circuit::{DAGCircuit, NodeType}; diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 31b31beff86f..ea0a56a81f0c 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -44,7 +44,7 @@ use pyo3::prelude::*; use pyo3::IntoPyObjectExt; use pyo3::types::{ - IntoPyDict, PyDict, PyInt, PyIterator, PyList, PySequence, PySet, PyString, PyTuple, PyType, + IntoPyDict, PyDict, PyInt, PyIterator, PyList, PySet, PyString, PyTuple, PyType, }; use rustworkx_core::dag_algo::layers; @@ -6942,23 +6942,6 @@ pub(crate) fn add_global_phase(py: Python, phase: &Param, other: &Param) -> PyRe type SortKeyType<'a> = (&'a [Qubit], &'a [Clbit]); -/// Emit a Python `DeprecationWarning` for pulse-related dependencies. -fn emit_pulse_dependency_deprecation(py: Python, msg: &str) { - let _ = imports::WARNINGS_WARN.get_bound(py).call1(( - PyString::new( - py, - &format!( - "The {} is deprecated as of Qiskit 1.3.0. It will be removed in Qiskit 2.0.0. \ - The entire Qiskit Pulse package is being deprecated \ - and this is a dependency on the package.", - msg - ), - ), - py.get_type::(), - 1, - )); -} - #[cfg(all(test, not(miri)))] mod test { use crate::circuit_instruction::ExtraInstructionAttributes; diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 39f4fa255673..5bf0f1f1fa5d 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -21,7 +21,7 @@ import itertools import multiprocessing as mp import typing -from collections import OrderedDict, defaultdict, namedtuple +from collections import OrderedDict, namedtuple from typing import ( Union, Optional, @@ -33,7 +33,6 @@ Mapping, Iterable, Any, - DefaultDict, Literal, overload, ) @@ -48,7 +47,6 @@ from qiskit.circuit.parameter import Parameter from qiskit.circuit.exceptions import CircuitError from qiskit.utils import deprecate_func -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency from . import _classical_resource_map from .controlflow import ControlFlowOp, _builder_utils from .controlflow.builder import CircuitScopeInterface, ControlFlowBuilderBlock diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 16e1da4a851b..a7cab7945fc2 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -24,7 +24,7 @@ from qiskit.providers.backend import Backend from qiskit.providers.backend_compat import BackendV2Converter from qiskit.providers.models.backendproperties import BackendProperties -from qiskit.pulse import Schedule, InstructionScheduleMap +from qiskit.pulse import Schedule from qiskit.transpiler import Layout, CouplingMap, PropertySet from qiskit.transpiler.basepasses import BasePass from qiskit.transpiler.exceptions import TranspilerError, CircuitTooWideForTarget @@ -33,7 +33,6 @@ from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager from qiskit.transpiler.target import Target from qiskit.utils import deprecate_arg -from qiskit.utils.deprecate_pulse import deprecate_pulse_arg logger = logging.getLogger(__name__) diff --git a/qiskit/converters/dagdependency_to_dag.py b/qiskit/converters/dagdependency_to_dag.py index 932d926ca10a..d1f0e80836ca 100644 --- a/qiskit/converters/dagdependency_to_dag.py +++ b/qiskit/converters/dagdependency_to_dag.py @@ -12,7 +12,6 @@ """Helper function for converting a dag dependency to a dag circuit""" from qiskit.dagcircuit.dagcircuit import DAGCircuit -from qiskit.dagcircuit.dagdependency import DAGDependency def dagdependency_to_dag(dagdependency): diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 1ca633b0e9b4..1393bd0335b1 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -17,7 +17,7 @@ import math import heapq import typing -from collections import OrderedDict, defaultdict +from collections import OrderedDict from collections.abc import Iterator import rustworkx as rx @@ -28,8 +28,6 @@ from qiskit.circuit.classicalregister import ClassicalRegister, Clbit from qiskit.dagcircuit.exceptions import DAGDependencyError from qiskit.dagcircuit.dagdepnode import DAGDepNode -from qiskit.pulse import Schedule -from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency if typing.TYPE_CHECKING: from qiskit.circuit.parameterexpression import ParameterExpression diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py index 5e174e5e12d3..246692189d70 100644 --- a/qiskit/dagcircuit/dagdependency_v2.py +++ b/qiskit/dagcircuit/dagdependency_v2.py @@ -15,10 +15,9 @@ import itertools import math -from collections import OrderedDict, defaultdict, namedtuple +from collections import OrderedDict, namedtuple from typing import Dict, List, Generator, Any -import numpy as np import rustworkx as rx from qiskit.circuit import ( @@ -26,7 +25,6 @@ ClassicalRegister, Qubit, Clbit, - Gate, ParameterExpression, ) from qiskit.circuit.controlflow import condition_resources diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py index df6fa3e2d145..ed1b5bb1e175 100644 --- a/qiskit/providers/backend_compat.py +++ b/qiskit/providers/backend_compat.py @@ -15,7 +15,7 @@ from __future__ import annotations import logging import warnings -from typing import List, Iterable, Any, Dict, Optional +from typing import List, Any, Dict, Optional from qiskit.providers.backend import BackendV1, BackendV2 from qiskit.providers.backend import QubitProperties @@ -323,15 +323,12 @@ def __init__( ) self._options = self._backend._options self._properties = None - self._defaults = None with warnings.catch_warnings(): # The class QobjExperimentHeader is deprecated warnings.filterwarnings("ignore", category=DeprecationWarning, module="qiskit") if hasattr(self._backend, "properties"): self._properties = self._backend.properties() - if hasattr(self._backend, "defaults"): - self._defaults = self._backend.defaults() self._target = None self._name_mapping = name_mapping @@ -348,7 +345,6 @@ def target(self): self._target = convert_to_target( configuration=self._config, properties=self._properties, - defaults=self._defaults, custom_name_mapping=self._name_mapping, add_delay=self._add_delay, filter_faulty=self._filter_faulty, diff --git a/qiskit/providers/fake_provider/utils/backend_converter.py b/qiskit/providers/fake_provider/utils/backend_converter.py index bd0ccd3aae58..c5268dc2f414 100644 --- a/qiskit/providers/fake_provider/utils/backend_converter.py +++ b/qiskit/providers/fake_provider/utils/backend_converter.py @@ -26,7 +26,7 @@ from qiskit.circuit.reset import Reset from qiskit.providers.models.pulsedefaults import PulseDefaults -# TODO: do we need this function? + def convert_to_target(conf_dict: dict, props_dict: dict = None, defs_dict: dict = None) -> Target: """Uses configuration, properties and pulse defaults dicts to construct and return Target class. diff --git a/qiskit/scheduler/__init__.py b/qiskit/scheduler/__init__.py deleted file mode 100644 index 7062e01a941e..000000000000 --- a/qiskit/scheduler/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2019. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -=========================================== -Circuit Scheduler (:mod:`qiskit.scheduler`) -=========================================== - -.. currentmodule:: qiskit.scheduler - -A circuit scheduler compiles a circuit program to a pulse program. - -Core API -======== - -.. autoclass:: ScheduleConfig - -.. currentmodule:: qiskit.scheduler.schedule_circuit -.. autofunction:: schedule_circuit -.. currentmodule:: qiskit.scheduler - -Pulse scheduling methods -======================== - -.. currentmodule:: qiskit.scheduler.methods -.. autofunction:: as_soon_as_possible -.. autofunction:: as_late_as_possible -.. currentmodule:: qiskit.scheduler -""" -from qiskit.scheduler import schedule_circuit -from qiskit.scheduler.config import ScheduleConfig diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 1fd8454159a3..d675827ff582 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -262,7 +262,7 @@ from .synthesis import AQCSynthesisPlugin # calibration -from .calibration.rzx_templates import rzx_templates +from .calibration.rzx_templates import rzx_templates # TODO: remove this # circuit scheduling from .scheduling import TimeUnitConversion diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py index 450490734e46..7c4acb1b59e1 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py @@ -224,7 +224,6 @@ def _step(self, dag): # perform the replacement if it was indeed a good idea if self._optimize1q._substitution_checks( - dag, (preceding_run or []) + run + (succeeding_run or []), new_preceding_run.op_nodes() + new_run.op_nodes() + new_succeeding_run.op_nodes(), self._optimize1q._basis_gates, diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 8e75ce1e7a74..b00e4c9e5112 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -160,9 +160,7 @@ def _gate_sequence_to_dag(self, best_synth_circuit): out_dag.apply_operation_back(op.operation, qubits, check=False) return out_dag - def _substitution_checks( - self, dag, old_run, new_circ, basis, qubit, old_error=None, new_error=None - ): + def _substitution_checks(self, old_run, new_circ, basis, qubit, old_error=None, new_error=None): """ Returns `True` when it is recommended to replace `old_run` with `new_circ` over `basis`. """ @@ -171,10 +169,7 @@ def _substitution_checks( # does this run have gates not in the image of ._decomposers? if basis is not None: - not_basis_p = any( - g.name not in basis - for g in old_run - ) + not_basis_p = any(g.name not in basis for g in old_run) else: # If no basis is specified then we're always in the basis not_basis_p = False diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index 8ecd68eacdbb..ca8658fff21a 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -33,6 +33,7 @@ value in units of dt, thus circuits involving delays may violate the constraints, which may result in failure in the circuit execution on the backend. +TODO: mentions pulse gates below. Do we want to keep these passes? There are two alignment constraint values reported by your quantum backend. In addition, if you want to define a custom instruction as a pulse gate, i.e. calibration, the underlying pulse instruction should satisfy other two waveform constraints. @@ -63,7 +64,7 @@ configuration in units of dt. This is the constraint for a single pulse :class:`Play` instruction that may constitute your pulse gate. The length of waveform samples should be multiple of this constraint value. - Violation of this constraint may result in failue in backend execution. + Violation of this constraint may result in failure in backend execution. Minimum pulse length constraint @@ -71,7 +72,7 @@ configuration in units of dt. This is the constraint for a single pulse :class:`Play` instruction that may constitute your pulse gate. The length of waveform samples should be greater than this constraint value. - Violation of this constraint may result in failue in backend execution. + Violation of this constraint may result in failure in backend execution. """ diff --git a/qiskit/transpiler/passes/scheduling/alignments/check_durations.py b/qiskit/transpiler/passes/scheduling/alignments/check_durations.py index 6d0d1db4b8bc..134f1c116a08 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/check_durations.py +++ b/qiskit/transpiler/passes/scheduling/alignments/check_durations.py @@ -68,4 +68,3 @@ def run(self, dag: DAGCircuit): if not (dur % self.acquire_align == 0 and dur % self.pulse_align == 0): self.property_set["reschedule_required"] = True return - diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py index c06a34983e7f..0fa1fa6dc975 100644 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py @@ -13,7 +13,6 @@ """Dynamical Decoupling insertion pass.""" import itertools -import warnings import numpy as np from qiskit.circuit import Gate, Delay, Reset diff --git a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py index 3d25e17792c1..97b8df2aeac6 100644 --- a/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py +++ b/qiskit/transpiler/passes/scheduling/padding/dynamical_decoupling.py @@ -14,7 +14,6 @@ from __future__ import annotations import logging -import warnings import numpy as np from qiskit.circuit import Gate, ParameterExpression, Qubit diff --git a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py index bf23f226a315..2fb72f905622 100644 --- a/qiskit/transpiler/passes/scheduling/time_unit_conversion.py +++ b/qiskit/transpiler/passes/scheduling/time_unit_conversion.py @@ -12,7 +12,6 @@ """Unify time unit in circuit for scheduling and following passes.""" from typing import Set -import warnings from qiskit.circuit import Delay from qiskit.dagcircuit import DAGCircuit diff --git a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py index cb025ea7896a..de0679387594 100644 --- a/qiskit/transpiler/passes/synthesis/high_level_synthesis.py +++ b/qiskit/transpiler/passes/synthesis/high_level_synthesis.py @@ -313,7 +313,7 @@ def _run( if top_level: for node in dag.op_nodes(): qubits = tuple(dag.find_bit(q).index for q in node.qargs) - if not self._definitely_skip_node(node, qubits, dag): + if not self._definitely_skip_node(node, qubits): break else: # The for-loop terminates without reaching the break statement @@ -353,7 +353,7 @@ def _run( processed = True # check if synthesis for the operation can be skipped - elif self._definitely_skip_node(node, qubits, dag): + elif self._definitely_skip_node(node, qubits): tracker.set_dirty(context.to_globals(qubits)) # next check control flow @@ -800,9 +800,7 @@ def _apply_annotations( return synthesized - def _definitely_skip_node( - self, node: DAGOpNode, qubits: tuple[int] | None, dag: DAGCircuit - ) -> bool: + def _definitely_skip_node(self, node: DAGOpNode, qubits: tuple[int] | None) -> bool: """Fast-path determination of whether a node can certainly be skipped (i.e. nothing will attempt to synthesise it) without accessing its Python-space `Operation`. diff --git a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py index c799552979e1..7fd66a353978 100644 --- a/qiskit/transpiler/preset_passmanagers/builtin_plugins.py +++ b/qiskit/transpiler/preset_passmanagers/builtin_plugins.py @@ -13,7 +13,6 @@ """Built-in transpiler stage plugins for preset pass managers.""" import os -import warnings from qiskit.transpiler.passes.optimization.split_2q_unitaries import Split2QUnitaries from qiskit.transpiler.passmanager import PassManager @@ -731,7 +730,6 @@ def pass_manager(self, pass_manager_config, optimization_level=None) -> PassMana timing_constraints = pass_manager_config.timing_constraints or TimingConstraints() target = pass_manager_config.target - return common.generate_scheduling( instruction_durations, scheduling_method, timing_constraints, target ) diff --git a/qiskit/transpiler/preset_passmanagers/common.py b/qiskit/transpiler/preset_passmanagers/common.py index b919027fbe73..bf8ce1354744 100644 --- a/qiskit/transpiler/preset_passmanagers/common.py +++ b/qiskit/transpiler/preset_passmanagers/common.py @@ -568,9 +568,7 @@ def _direction_condition(property_set): return PassManager(unroll) -def generate_scheduling( - instruction_durations, scheduling_method, timing_constraints, target=None -): +def generate_scheduling(instruction_durations, scheduling_method, timing_constraints, target=None): """Generate a post optimization scheduling :class:`~qiskit.transpiler.PassManager` Args: diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 71a91cfda967..9adbbd3e9277 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -3,7 +3,6 @@ # (C) Copyright IBM 2024. # # This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. # # Any modifications or derivative works of this code must retain this @@ -14,7 +13,6 @@ Preset pass manager generation function """ -import copy import warnings from qiskit.circuit.controlflow import CONTROL_FLOW_OP_NAMES, get_control_flow_name_mapping @@ -312,9 +310,7 @@ def generate_preset_pass_manager( timing_constraints = _parse_timing_constraints(backend, timing_constraints) # The basis gates parser will set _skip_target to True if a custom basis gate is found # (known edge case). - basis_gates, name_mapping, _skip_target = _parse_basis_gates( - basis_gates, backend, _skip_target - ) + basis_gates, name_mapping, _skip_target = _parse_basis_gates(basis_gates, backend, _skip_target) coupling_map = _parse_coupling_map(coupling_map, backend) if target is None: diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index ab01cc95bbf0..89b8b73c1415 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -31,6 +31,7 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa This pass manager applies the user-given initial layout. If none is given, a search for a perfect layout (i.e. one that satisfies all 2-qubit interactions) is conducted. If no such layout is found, and device calibration information is available, the + # TODO: what does device calibration mean in this context? circuit is mapped to the qubits with best readouts and to CX gates with highest fidelity. The pass manager then transforms the circuit to match the coupling constraints. diff --git a/qiskit/transpiler/target.py b/qiskit/transpiler/target.py index 2a7e46b4ff6d..d6700a63aa72 100644 --- a/qiskit/transpiler/target.py +++ b/qiskit/transpiler/target.py @@ -90,15 +90,7 @@ def __init__( super().__init__() def __repr__(self): - return ( - f"InstructionProperties(duration={self.duration}, error={self.error})" - ) - - def __getstate__(self) -> tuple: - return (super().__getstate__()) - - def __setstate__(self, state: tuple): - super().__setstate__(state[0]) + return f"InstructionProperties(duration={self.duration}, error={self.error})" class Target(BaseTarget): diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index 0aa6e7c10188..9ea7774abb8f 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -48,7 +48,7 @@ def _is_boolean_expression(gate_text, op): return isinstance(op, BooleanExpression) and gate_text == op.name -def get_gate_ctrl_text(op, drawer, style=None, calibrations=None): +def get_gate_ctrl_text(op, drawer, style=None): """Load the gate_text and ctrl_text strings based on names and labels""" anno_list = [] anno_text = "" @@ -123,13 +123,6 @@ def get_gate_ctrl_text(op, drawer, style=None, calibrations=None): ) and (op_type is not PauliEvolutionGate): gate_text = gate_text.capitalize() - if drawer == "mpl" and op.name in calibrations: - if isinstance(op, ControlledGate): - ctrl_text = "" if ctrl_text is None else ctrl_text - ctrl_text = "(cal)\n" + ctrl_text - else: - gate_text = gate_text + "\n(cal)" - if anno_text: gate_text += " - " + anno_text diff --git a/qiskit/visualization/circuit/matplotlib.py b/qiskit/visualization/circuit/matplotlib.py index cc79b61e1e79..73160f0a1b51 100644 --- a/qiskit/visualization/circuit/matplotlib.py +++ b/qiskit/visualization/circuit/matplotlib.py @@ -1454,7 +1454,7 @@ def _multiqubit_gate(self, node, node_data, glob_data, xy=None): # Swap gate if isinstance(op, SwapGate): - self._swap(xy, node, node_data, node_data[node].lc) + self._swap(xy, node_data[node].lc) return # RZZ Gate @@ -1746,7 +1746,7 @@ def _control_gate(self, node, node_data, glob_data, mod_control): self._gate(node, node_data, glob_data, xy[num_ctrl_qubits:][0]) elif isinstance(base_type, SwapGate): - self._swap(xy[num_ctrl_qubits:], node, node_data, node_data[node].lc) + self._swap(xy[num_ctrl_qubits:], node_data[node].lc) else: self._multiqubit_gate(node, node_data, glob_data, xy[num_ctrl_qubits:]) @@ -1881,7 +1881,7 @@ def _symmetric_gate(self, node, node_data, base_type, glob_data): ) self._line(qubit_b, qubit_t, lc=lc) - def _swap(self, xy, node, node_data, color=None): + def _swap(self, xy, color=None): """Draw a Swap gate""" self._swap_cross(xy[0], color=color) self._swap_cross(xy[1], color=color) diff --git a/test/python/circuit/test_circuit_operations.py b/test/python/circuit/test_circuit_operations.py index f8667cf8afac..b3f5a75fbf85 100644 --- a/test/python/circuit/test_circuit_operations.py +++ b/test/python/circuit/test_circuit_operations.py @@ -31,7 +31,6 @@ from qiskit.circuit.quantumcircuitdata import CircuitInstruction from qiskit.circuit.quantumregister import AncillaQubit, AncillaRegister, Qubit from qiskit.providers.basic_provider import BasicSimulator -from qiskit.pulse import DriveChannel, Gaussian, Play, Schedule from qiskit.quantum_info import Operator from test import QiskitTestCase # pylint: disable=wrong-import-order diff --git a/test/python/circuit/test_circuit_properties.py b/test/python/circuit/test_circuit_properties.py index 038b60c59c03..9d06f6546cec 100644 --- a/test/python/circuit/test_circuit_properties.py +++ b/test/python/circuit/test_circuit_properties.py @@ -15,10 +15,10 @@ import unittest import numpy as np -from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit, pulse +from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit from qiskit.circuit import Clbit from qiskit.circuit.classical import expr, types -from qiskit.circuit.library import RXGate, RYGate, GlobalPhaseGate +from qiskit.circuit.library import GlobalPhaseGate from qiskit.circuit.exceptions import CircuitError from test import QiskitTestCase # pylint: disable=wrong-import-order diff --git a/test/python/circuit/test_compose.py b/test/python/circuit/test_compose.py index d9d3cf79e8fa..a9c47318a315 100644 --- a/test/python/circuit/test_compose.py +++ b/test/python/circuit/test_compose.py @@ -18,8 +18,6 @@ import numpy as np -from qiskit import transpile -from qiskit.pulse import Schedule from qiskit.circuit import ( QuantumRegister, ClassicalRegister, diff --git a/test/python/circuit/test_parameters.py b/test/python/circuit/test_parameters.py index 3480fb3221ec..2373fa7477f5 100644 --- a/test/python/circuit/test_parameters.py +++ b/test/python/circuit/test_parameters.py @@ -15,7 +15,6 @@ import unittest import cmath import math -import copy import pickle from operator import add, mul, sub, truediv import numpy @@ -29,7 +28,6 @@ from qiskit.circuit.parametertable import ParameterView from qiskit.circuit.exceptions import CircuitError from qiskit.compiler import transpile -from qiskit import pulse from qiskit.quantum_info import Operator from qiskit.providers.fake_provider import Fake5QV1, GenericBackendV2 from qiskit.providers.basic_provider import BasicSimulator diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 7808f6018596..8911060dd83f 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -430,12 +430,6 @@ def test_convert_duration_to_dt(self): Tests fix for bug reported in PR #11782.""" backend = GenericBackendV2(num_qubits=3, seed=42) - with self.assertWarns(DeprecationWarning): - schedule_config = ScheduleConfig( - inst_map=backend.target.instruction_schedule_map(), - meas_map=backend.meas_map, - dt=backend.dt, - ) circ = QuantumCircuit(2) circ.cx(0, 1) @@ -457,7 +451,7 @@ def test_convert_duration_to_dt(self): for circuit in [circuit_dt, circuit_s, circuit_ms]: with self.subTest(circuit=circuit): converted_circ = convert_durations_to_dt( - circuit, dt_in_sec=schedule_config.dt, inplace=False + circuit, dt_in_sec=2.22e-10, inplace=False ) self.assertEqual( converted_circ.duration, diff --git a/test/python/compiler/test_transpiler.py b/test/python/compiler/test_transpiler.py index f2a3c41fa6b6..79f90272246c 100644 --- a/test/python/compiler/test_transpiler.py +++ b/test/python/compiler/test_transpiler.py @@ -27,7 +27,6 @@ ClassicalRegister, QuantumCircuit, QuantumRegister, - pulse, qasm3, qpy, ) @@ -78,7 +77,6 @@ from qiskit.providers.fake_provider import Fake20QV1, Fake27QPulseV1, GenericBackendV2 from qiskit.providers.basic_provider import BasicSimulator from qiskit.providers.options import Options -from qiskit.pulse import InstructionScheduleMap from qiskit.quantum_info import Operator, random_unitary from qiskit.utils import parallel from qiskit.transpiler import CouplingMap, Layout, PassManager @@ -1295,23 +1293,6 @@ def test_translation_method_synthesis(self, optimization_level, basis_gates): self.assertTrue(Operator(out).equiv(qc)) self.assertTrue(set(out.count_ops()).issubset(basis_gates)) - - def test_inst_durations_from_calibrations(self): - """Test that circuit calibrations can be used instead of explicitly - supplying inst_durations. - """ - qc = QuantumCircuit(2) - qc.append(Gate("custom", 1, []), [0]) - - with self.assertWarns(DeprecationWarning): - with pulse.build() as cal: - pulse.play(pulse.library.Gaussian(20, 1.0, 3.0), pulse.DriveChannel(0)) - qc.add_calibration("custom", [0], cal) - - out = transpile(qc, scheduling_method="alap", seed_transpiler=42) - with self.assertWarns(DeprecationWarning): - self.assertEqual(out.duration, cal.duration) - @data(0, 1, 2, 3) def test_circuit_with_delay(self, optimization_level): """Verify a circuit with delay can transpile to a scheduled circuit.""" @@ -2677,35 +2658,6 @@ def test_transpile_with_multiple_coupling_maps(self): seed_transpiler=42, ) - @data(0, 1, 2, 3) - def test_backend_and_custom_gate(self, opt_level): - """Test transpile() with BackendV2, custom basis pulse gate.""" - backend = GenericBackendV2( - num_qubits=5, - coupling_map=[[0, 1], [1, 0], [1, 2], [1, 3], [2, 1], [3, 1], [3, 4], [4, 3]], - seed=42, - ) - with self.assertWarns(DeprecationWarning): - inst_map = InstructionScheduleMap() - inst_map.add("newgate", [0, 1], pulse.ScheduleBlock()) - newgate = Gate("newgate", 2, []) - circ = QuantumCircuit(2) - circ.append(newgate, [0, 1]) - - with self.assertWarns(DeprecationWarning): - tqc = transpile( - circ, - backend, - inst_map=inst_map, - basis_gates=["newgate"], - optimization_level=opt_level, - seed_transpiler=42, - ) - self.assertEqual(len(tqc.data), 1) - self.assertEqual(tqc.data[0].operation, newgate) - for x in tqc.data[0].qubits: - self.assertIn((tqc.find_bit(x).index,), backend.target.qargs) - @ddt class TestTranspileMultiChipTarget(QiskitTestCase): diff --git a/test/python/dagcircuit/test_compose.py b/test/python/dagcircuit/test_compose.py index 52c7907e6ab7..9230fdc0b0de 100644 --- a/test/python/dagcircuit/test_compose.py +++ b/test/python/dagcircuit/test_compose.py @@ -27,8 +27,6 @@ from qiskit.circuit.classical import expr, types from qiskit.dagcircuit import DAGCircuit, DAGCircuitError from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.pulse import Schedule -from qiskit.circuit.gate import Gate from test import QiskitTestCase # pylint: disable=wrong-import-order diff --git a/test/python/primitives/test_primitive.py b/test/python/primitives/test_primitive.py index 55ef9c3f73f9..17d1459aec0c 100644 --- a/test/python/primitives/test_primitive.py +++ b/test/python/primitives/test_primitive.py @@ -17,11 +17,10 @@ from ddt import data, ddt, unpack from numpy import array, float32, float64, int32, int64 -from qiskit import QuantumCircuit, pulse, transpile +from qiskit import QuantumCircuit from qiskit.circuit.random import random_circuit from qiskit.primitives.base import validation from qiskit.primitives.utils import _circuit_key -from qiskit.providers.fake_provider import GenericBackendV2 from test import QiskitTestCase # pylint: disable=wrong-import-order diff --git a/test/python/providers/test_fake_backends.py b/test/python/providers/test_fake_backends.py index 2a272b91daef..56c084ff7fbe 100644 --- a/test/python/providers/test_fake_backends.py +++ b/test/python/providers/test_fake_backends.py @@ -33,7 +33,7 @@ FakeOpenPulse2Q, GenericBackendV2, ) -from qiskit.providers.backend_compat import BackendV2Converter, convert_to_target +from qiskit.providers.backend_compat import BackendV2Converter from qiskit.providers.models.backendproperties import BackendProperties from qiskit.providers.models.backendconfiguration import GateConfig from qiskit.providers.backend import BackendV2 @@ -734,63 +734,3 @@ def test_faulty_full_path_transpile_connected_cmap(self, opt_level): tqc = transpile(qc, v2_backend, seed_transpiler=433, optimization_level=opt_level) connections = [tuple(sorted(tqc.find_bit(q).index for q in x.qubits)) for x in tqc.data] self.assertNotIn((0, 1), connections) - - def test_convert_to_target_control_flow(self): - with self.assertWarns(DeprecationWarning): - backend = Fake27QPulseV1() - properties = backend.properties() - configuration = backend.configuration() - configuration.supported_instructions = [ - "cx", - "id", - "delay", - "measure", - "reset", - "rz", - "sx", - "x", - "if_else", - "for_loop", - "switch_case", - ] - defaults = backend.defaults() - with self.assertWarns(DeprecationWarning): - target = convert_to_target(configuration, properties, defaults) - self.assertTrue(target.instruction_supported("if_else", ())) - self.assertFalse(target.instruction_supported("while_loop", ())) - self.assertTrue(target.instruction_supported("for_loop", ())) - self.assertTrue(target.instruction_supported("switch_case", ())) - - def test_convert_unrelated_supported_instructions(self): - with self.assertWarns(DeprecationWarning): - backend = Fake27QPulseV1() - properties = backend.properties() - configuration = backend.configuration() - configuration.supported_instructions = [ - "cx", - "id", - "delay", - "measure", - "reset", - "rz", - "sx", - "x", - "play", - "u2", - "u3", - "u1", - "shiftf", - "acquire", - "setf", - "if_else", - "for_loop", - "switch_case", - ] - defaults = backend.defaults() - with self.assertWarns(DeprecationWarning): - target = convert_to_target(configuration, properties, defaults) - self.assertTrue(target.instruction_supported("if_else", ())) - self.assertFalse(target.instruction_supported("while_loop", ())) - self.assertTrue(target.instruction_supported("for_loop", ())) - self.assertTrue(target.instruction_supported("switch_case", ())) - self.assertFalse(target.instruction_supported("u3", (0,))) diff --git a/test/python/pulse/test_block.py b/test/python/pulse/test_block.py index 138584c278cf..b80f6df43f51 100644 --- a/test/python/pulse/test_block.py +++ b/test/python/pulse/test_block.py @@ -760,7 +760,7 @@ def test_filter_channels(self): self.assertTrue(ch in filtered_blk.channels) self.assertEqual(filtered_blk, blk) - def test_filter_inst_types(self): + def test_filter_inst_types(self): """Test filtering on instruction types.""" with pulse.build() as blk: pulse.acquire(5, pulse.AcquireChannel(0), pulse.MemorySlot(0)) diff --git a/test/python/transpiler/test_dynamical_decoupling.py b/test/python/transpiler/test_dynamical_decoupling.py index 9d875deb9065..767b1b577c0c 100644 --- a/test/python/transpiler/test_dynamical_decoupling.py +++ b/test/python/transpiler/test_dynamical_decoupling.py @@ -15,10 +15,9 @@ import unittest import numpy as np from numpy import pi -from ddt import ddt, data +from ddt import ddt -from qiskit import pulse -from qiskit.circuit import Gate, QuantumCircuit, Delay, Measure, Reset, Parameter +from qiskit.circuit import QuantumCircuit, Delay, Measure, Reset, Parameter from qiskit.circuit.library import XGate, YGate, RXGate, UGate, CXGate, HGate from qiskit.quantum_info import Operator from qiskit.transpiler.instruction_durations import InstructionDurations diff --git a/test/python/transpiler/test_gate_direction.py b/test/python/transpiler/test_gate_direction.py index bd6e8e91add9..223c144fa12f 100644 --- a/test/python/transpiler/test_gate_direction.py +++ b/test/python/transpiler/test_gate_direction.py @@ -17,7 +17,7 @@ import ddt -from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit, pulse +from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit from qiskit.circuit import Parameter, Gate from qiskit.circuit.library import ( CXGate, @@ -495,7 +495,6 @@ def test_target_cannot_flip_message(self): with self.assertRaisesRegex(TranspilerError, "my_2q_gate would be supported.*"): pass_(circuit) - def test_target_unknown_gate_message(self): """A suitable error message should be emitted if the gate isn't valid in either direction on the target.""" diff --git a/test/python/transpiler/test_scheduling_padding_pass.py b/test/python/transpiler/test_scheduling_padding_pass.py index 8bedca4f01b6..41c9bea956e2 100644 --- a/test/python/transpiler/test_scheduling_padding_pass.py +++ b/test/python/transpiler/test_scheduling_padding_pass.py @@ -18,7 +18,6 @@ from qiskit import QuantumCircuit from qiskit.circuit import Measure from qiskit.circuit.library import CXGate, HGate -from qiskit.pulse import Schedule, Play, Constant, DriveChannel from qiskit.transpiler.instruction_durations import InstructionDurations from qiskit.transpiler.passes import ( ASAPScheduleAnalysis, diff --git a/test/python/transpiler/test_target.py b/test/python/transpiler/test_target.py index dfb9e771a4af..30fc6b2f0a07 100644 --- a/test/python/transpiler/test_target.py +++ b/test/python/transpiler/test_target.py @@ -1757,7 +1757,6 @@ def test_inst_map(self): fake_backend = Fake7QPulseV1() config = fake_backend.configuration() properties = fake_backend.properties() - defaults = fake_backend.defaults() constraints = TimingConstraints(**config.timing_constraints) target = Target.from_configuration( basis_gates=config.basis_gates, diff --git a/test/utils/providers/__init__.py b/test/utils/providers/__init__.py deleted file mode 100644 index 46829845c9db..000000000000 --- a/test/utils/providers/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Module containing one qubit unitary synthesis methods.""" - -from .one_qubit_decompose import OneQubitEulerDecomposer From fb53bab6c402bd3c3e0e508a97c03369c2eddab7 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Mon, 17 Feb 2025 11:22:34 +0200 Subject: [PATCH 10/18] Add reno and some misc fixes --- docs/apidoc/index.rst | 1 - docs/apidoc/scheduler.rst | 6 ----- qiskit/circuit/quantumcircuit.py | 2 +- qiskit/providers/backend_compat.py | 3 +++ .../generate_preset_pass_manager.py | 1 + ...e-pulse-calibrations-4486dc101b76ec51.yaml | 22 +++++++++++++++++++ test/python/circuit/test_scheduled_circuit.py | 4 +--- .../transpiler/test_normalize_rx_angle.py | 1 - 8 files changed, 28 insertions(+), 12 deletions(-) delete mode 100644 docs/apidoc/scheduler.rst create mode 100644 releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml diff --git a/docs/apidoc/index.rst b/docs/apidoc/index.rst index d0ec14a416e4..0fa886d83659 100644 --- a/docs/apidoc/index.rst +++ b/docs/apidoc/index.rst @@ -76,7 +76,6 @@ Pulse-level programming: :maxdepth: 1 pulse - scheduler Other: diff --git a/docs/apidoc/scheduler.rst b/docs/apidoc/scheduler.rst deleted file mode 100644 index 53328863ebee..000000000000 --- a/docs/apidoc/scheduler.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit-scheduler: - -.. automodule:: qiskit.scheduler - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 5bf0f1f1fa5d..3e5bfeed2751 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -805,7 +805,7 @@ class QuantumCircuit: .. automethod:: remove_final_measurements - Circuit properties + Circuit properties ================== Simple circuit metrics diff --git a/qiskit/providers/backend_compat.py b/qiskit/providers/backend_compat.py index ed1b5bb1e175..b4c34ee5b55c 100644 --- a/qiskit/providers/backend_compat.py +++ b/qiskit/providers/backend_compat.py @@ -45,6 +45,9 @@ def convert_to_target( Args: configuration: Backend configuration as ``BackendConfiguration`` properties: Backend property dictionary or ``BackendProperties`` + custom_name_mapping: A name mapping must be supplied for the operation + not included in Qiskit Standard Gate name mapping, otherwise the operation + will be dropped in the resulting ``Target`` object. add_delay: If True, adds delay to the instruction set. filter_faulty: If True, this filters the non-operational qubits. diff --git a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py index 9adbbd3e9277..0106e53fccea 100644 --- a/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py +++ b/qiskit/transpiler/preset_passmanagers/generate_preset_pass_manager.py @@ -3,6 +3,7 @@ # (C) Copyright IBM 2024. # # This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory # of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. # # Any modifications or derivative works of this code must retain this diff --git a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml new file mode 100644 index 000000000000..afe661448a61 --- /dev/null +++ b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml @@ -0,0 +1,22 @@ +--- +upgrade_circuits: + - | + As part of Pulse removal in Qiskit 2.0, the ``calibrations`` property has been removed + from the :class:`.QuantumCircuit`, :class:`.DAGCircuit` and :class:`.DAGDependency` classes. + In addition, the method ``has_calibration_for`` has been removed from the :class:`.QuantumCircuit` and + :class:`.DAGCircuit` classes and ``add_calibration`` has been removed from :class:`.QuantumCircuit`. +upgrade_transpiler: + - | + As part of Pulse removal in Qiskit 2.0, the ``inst_map`` argument has been removed from + the :func:`.generate_preset_pass_manager` and :func:`.transpile` functions, from the + :meth:`.Target.from_configuration` method and from the constructor of :class:`.PassManagerConfig`. + In addition, ``calibration`` has been removed from the :class:`.InstructionProperties` 's constructor and + is no longer a property of that class. + - | + As part of Pulse removal in Qiskit 2.0, the ``has_calibration``, ``get_calibration``, + ``instruction_schedule_map`` and ``update_from_instruction_schedule_map`` methods have been + removed from the :class:`.Target` class. +upgrade_misc: + - | + As part of Pulse removal in Qiskit 2.0, the ``sequence`` and ``schedule_circuit`` functions + together with the ``ScheduleConfig`` class have been removed. diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 8911060dd83f..f3f3464b3f2c 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -450,9 +450,7 @@ def test_convert_duration_to_dt(self): for circuit in [circuit_dt, circuit_s, circuit_ms]: with self.subTest(circuit=circuit): - converted_circ = convert_durations_to_dt( - circuit, dt_in_sec=2.22e-10, inplace=False - ) + converted_circ = convert_durations_to_dt(circuit, dt_in_sec=2.22e-10, inplace=False) self.assertEqual( converted_circ.duration, ref_duration, diff --git a/test/python/transpiler/test_normalize_rx_angle.py b/test/python/transpiler/test_normalize_rx_angle.py index 8c75891738e1..6f1c7f22ea76 100644 --- a/test/python/transpiler/test_normalize_rx_angle.py +++ b/test/python/transpiler/test_normalize_rx_angle.py @@ -26,7 +26,6 @@ from test import QiskitTestCase # pylint: disable=wrong-import-order -# Should we move this test entirely? (after we remove the pass???) @ddt class TestNormalizeRXAngle(QiskitTestCase): """Tests the NormalizeRXAngle pass.""" From c44bc0f8e9c04ee625cedcf1018e3c29df34143d Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Mon, 17 Feb 2025 18:25:38 +0200 Subject: [PATCH 11/18] Add recent changes from remove-pulse-qpy branch --- qiskit/qpy/binary_io/circuits.py | 2 +- test/qpy_compat/test_qpy.py | 30 +++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/qiskit/qpy/binary_io/circuits.py b/qiskit/qpy/binary_io/circuits.py index ee284233a41d..ed1b58d64db7 100644 --- a/qiskit/qpy/binary_io/circuits.py +++ b/qiskit/qpy/binary_io/circuits.py @@ -651,7 +651,7 @@ def _read_calibrations(file_obj, version, vectors, metadata_deserializer): if name: warnings.warn( category=exceptions.QPYLoadingDeprecatedFeatureWarning, - message="Support for loading dulse gates has been removed in Qiskit 2.0. " + message="Support for loading pulse gates has been removed in Qiskit 2.0. " f"If `{name}` is in the circuit, it will be left as a custom instruction" " without definition.", ) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 0ba8e22d7690..628eff9984b5 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -426,6 +426,7 @@ def generate_control_flow_switch_circuits(): def generate_schedule_blocks(): """Standard QPY testcase for schedule blocks.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, channels, library current_version = current_version_str.split(".") @@ -493,6 +494,7 @@ def generate_schedule_blocks(): def generate_referenced_schedule(): """Test for QPY serialization of unassigned reference schedules.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, channels, library schedule_blocks = [] @@ -518,6 +520,7 @@ def generate_referenced_schedule(): def generate_calibrated_circuits(): """Test for QPY serialization with calibrations.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, Constant, DriveChannel circuits = [] @@ -588,6 +591,7 @@ def generate_open_controlled_gates(): def generate_acquire_instruction_with_kernel_and_discriminator(): """Test QPY serialization with Acquire instruction with kernel and discriminator.""" + # pylint: disable=no-name-in-module from qiskit.pulse import builder, AcquireChannel, MemorySlot, Discriminator, Kernel schedule_blocks = [] @@ -820,8 +824,13 @@ def generate_v12_expr(): return [index, shift] -def generate_circuits(version_parts): - """Generate reference circuits.""" +def generate_circuits(version_parts, load_context=False): + """Generate reference circuits. + + If load_context is True, avoid generating Pulse-based reference + circuits. For those circuits, load_qpy only checks that the cached + circuits can be loaded without erroring.""" + output_circuits = { "full.qpy": [generate_full_circuit()], "unitary.qpy": [generate_unitary_gate_circuit()], @@ -849,10 +858,16 @@ def generate_circuits(version_parts): if version_parts >= (0, 19, 2): output_circuits["control_flow.qpy"] = generate_control_flow_circuits() if version_parts >= (0, 21, 0) and version_parts < (2, 0): - output_circuits["schedule_blocks.qpy"] = generate_schedule_blocks() - output_circuits["pulse_gates.qpy"] = generate_calibrated_circuits() + output_circuits["schedule_blocks.qpy"] = ( + None if load_context else generate_schedule_blocks() + ) + output_circuits["pulse_gates.qpy"] = ( + None if load_context else generate_calibrated_circuits() + ) if version_parts >= (0, 24, 0) and version_parts < (2, 0): - output_circuits["referenced_schedule_blocks.qpy"] = generate_referenced_schedule() + output_circuits["referenced_schedule_blocks.qpy"] = ( + None if load_context else generate_referenced_schedule() + ) if version_parts >= (0, 24, 0): output_circuits["control_flow_switch.qpy"] = generate_control_flow_switch_circuits() if version_parts >= (0, 24, 1): @@ -862,7 +877,7 @@ def generate_circuits(version_parts): output_circuits["layout.qpy"] = generate_layout_circuits() if version_parts >= (0, 25, 0) and version_parts < (2, 0): output_circuits["acquire_inst_with_kernel_and_disc.qpy"] = ( - generate_acquire_instruction_with_kernel_and_discriminator() + None if load_context else generate_acquire_instruction_with_kernel_and_discriminator() ) output_circuits["control_flow_expr.qpy"] = generate_control_flow_expr() if version_parts >= (0, 45, 2): @@ -1007,10 +1022,11 @@ def _main(): version_match = re.search(VERSION_PATTERN, args.version, re.VERBOSE | re.IGNORECASE) version_parts = tuple(int(x) for x in version_match.group("release").split(".")) - qpy_files = generate_circuits(version_parts) if args.command == "generate": + qpy_files = generate_circuits(version_parts) generate_qpy(qpy_files) else: + qpy_files = generate_circuits(version_parts, load_context=True) load_qpy(qpy_files, version_parts) From 46510ac2ea4a427ab7d884afbf8a30f43f01cf66 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Wed, 19 Feb 2025 16:33:11 +0200 Subject: [PATCH 12/18] Raise QpyError when loading ScheduleBlock payloads --- qiskit/qpy/__init__.py | 9 ++-- qiskit/qpy/interface.py | 41 ++++++---------- .../remove-pulse-qpy-07a96673c8f10e38.yaml | 11 ++--- test/qpy_compat/test_qpy.py | 48 +++++++++++++++---- 4 files changed, 60 insertions(+), 49 deletions(-) diff --git a/qiskit/qpy/__init__.py b/qiskit/qpy/__init__.py index c5fa51191dc3..5ce063710587 100644 --- a/qiskit/qpy/__init__.py +++ b/qiskit/qpy/__init__.py @@ -169,11 +169,10 @@ def open(*args): it to QPY setting ``use_symengine=False``. The resulting file can then be loaded by any later version of Qiskit. - With the removal of Pulse in Qiskit 2.0, QPY does not support loading ``ScheduleBlock`` programs - or pulse gates. If such payloads are being loaded, QPY will issue a warning and - return partial circuits. In the case of a ``ScheduleBlock`` payload, a circuit with only a name - and metadata will be loaded. It the case of pulse gates, the circuit will contain custom - instructions without calibration data attached, hence leaving them undefined. + With the removal of Pulse in Qiskit 2.0, QPY provides limited support for loading + payloads with pulse data. Loading a ``ScheduleBlock`` payload, a :class:`.QpyError` exception + will be raised. Loading a circuit with pulse gates, the circuit will contain custom + instructions without calibration data attached, leaving them undefined. QPY format version history -------------------------- diff --git a/qiskit/qpy/interface.py b/qiskit/qpy/interface.py index 0958fdd8cad9..f518d15ae32c 100644 --- a/qiskit/qpy/interface.py +++ b/qiskit/qpy/interface.py @@ -24,12 +24,12 @@ from qiskit.circuit import QuantumCircuit from qiskit.exceptions import QiskitError from qiskit.qpy import formats, common, binary_io, type_keys -from qiskit.qpy.exceptions import QPYLoadingDeprecatedFeatureWarning, QpyError +from qiskit.qpy.exceptions import QpyError from qiskit.version import __version__ # pylint: disable=invalid-name -QPY_SUPPORTED_TYPES = Union[QuantumCircuit] +QPY_SUPPORTED_TYPES = QuantumCircuit # This version pattern is taken from the pypa packaging project: # https://github.com/pypa/packaging/blob/21.3/packaging/version.py#L223-L254 @@ -157,29 +157,19 @@ def dump( Raises: - QpyError: When multiple data format is mixed in the output. TypeError: When invalid data type is input. - ValueError: When an unsupported version number is passed in for the ``version`` argument + ValueError: When an unsupported version number is passed in for the ``version`` argument. """ if not isinstance(programs, Iterable): programs = [programs] - program_types = set() + # dump accepts only QuantumCircuit typed objects for program in programs: - program_types.add(type(program)) + if not issubclass(type(program), QuantumCircuit): + raise TypeError(f"'{type(program)}' is not a supported data type.") - if len(program_types) > 1: - raise QpyError( - "Input programs contain multiple data types. " - "Different data type must be serialized separately." - ) - program_type = next(iter(program_types)) - - if issubclass(program_type, QuantumCircuit): - type_key = type_keys.Program.CIRCUIT - writer = binary_io.write_circuit - else: - raise TypeError(f"'{program_type}' is not supported data type.") + type_key = type_keys.Program.CIRCUIT + writer = binary_io.write_circuit if version is None: version = common.QPY_VERSION @@ -262,8 +252,9 @@ def load( A list is always returned, even if there is only 1 program in the QPY data. Raises: - QiskitError: if ``file_obj`` is not a valid QPY file - TypeError: When invalid data type is loaded. + QiskitError: if ``file_obj`` is not a valid QPY file. + QpyError: if known but unsupported data type is loaded. + TypeError: if invalid data type is loaded. """ # identify file header version @@ -326,13 +317,9 @@ def load( if type_key == type_keys.Program.CIRCUIT: loader = binary_io.read_circuit elif type_key == type_keys.Program.SCHEDULE_BLOCK: - loader = binary_io.read_schedule_block - warnings.warn( - category=QPYLoadingDeprecatedFeatureWarning, - message="Payloads of type `ScheduleBlock` cannot be loaded as of Qiskit 2.0. " - "An empty circuit (possibly with serialized metadata) will be loaded. " - "Use an earlier version of Qiskit if you want to load a `ScheduleBlock`" - " payload.", + raise QpyError( + "Payloads of type `ScheduleBlock` cannot be loaded as of Qiskit 2.0. " + "Use an earlier version of Qiskit if you want to load `ScheduleBlock` payloads." ) else: raise TypeError(f"Invalid payload format data kind '{type_key}'.") diff --git a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml index f393e7ccfd20..dde7f0ad5e73 100644 --- a/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml +++ b/releasenotes/notes/remove-pulse-qpy-07a96673c8f10e38.yaml @@ -2,10 +2,7 @@ upgrade_qpy: - | With the removal of Pulse in Qiskit 2.0, support for serializing ``ScheduleBlock`` programs - via the :func:`qiskit.qpy.dump` function has been removed. Furthermore, in order to keep - backward compatibility, users can still load payloads containing pulse data (i.e. either - ``ScheduleBlock`` s or containing pulse gates) using the :func:`qiskit.qpy.load` function. - However, pulse data is ignored, resulting with potentially partially specified circuits. - In particular, loading a ``ScheduleBlock`` payload will result with a circuit having only - a name and metadata. Loading a :class:`~.QuantumCircuit` payload with pulse gates will - result with a circuit containing undefined custom instructions. + via the :func:`qiskit.qpy.dump` function has been removed. Users can still load payloads + containing pulse gates using the :func:`qiskit.qpy.load` function, however those will be + treated as undefined custom instructions. Loading ``ScheduleBlock`` payloads is not supported + anymore and will result with a :class:`.QpyError` exception. diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 628eff9984b5..3de97403436c 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 + # This code is part of Qiskit. # # (C) Copyright IBM 2021. @@ -26,6 +27,7 @@ from qiskit.circuit.quantumregister import Qubit from qiskit.circuit.parameter import Parameter from qiskit.circuit.parametervector import ParameterVector +from qiskit.qpy.exceptions import QpyError from qiskit.quantum_info.random import random_unitary from qiskit.quantum_info import Operator from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, QFT, DCXGate, PauliGate @@ -967,22 +969,22 @@ def generate_qpy(qpy_files): def load_qpy(qpy_files, version_parts): """Load qpy circuits from files and compare to reference circuits.""" pulse_files = { - "schedule_blocks.qpy", - "pulse_gates.qpy", - "referenced_schedule_blocks.qpy", - "acquire_inst_with_kernel_and_disc.qpy", + "schedule_blocks.qpy": (0,21,0), + "pulse_gates.qpy": (0,21,0), + "referenced_schedule_blocks.qpy": (0,24,0), + "acquire_inst_with_kernel_and_disc.qpy": (0,25,0), } for path, circuits in qpy_files.items(): + if path in pulse_files.keys(): + # Qiskit Pulse was removed in version 2.0. Loading ScheduleBlock payloads + # raises an exception and loading pulse gates results with undefined instructions + # so not loading and comparing these payloads. + # See https://github.com/Qiskit/qiskit/pull/13814 + continue print(f"Loading qpy file: {path}") with open(path, "rb") as fd: qpy_circuits = load(fd) equivalent = path in {"open_controlled_gates.qpy", "controlled_gates.qpy"} - if path in pulse_files: - # Qiskit Pulse was removed in version 2.0. We want to be able to load - # pulse-based payloads, however these will be partially specified hence - # we should not compare them to the cached circuits. - # See https://github.com/Qiskit/qiskit/pull/13814 - continue for i, circuit in enumerate(circuits): bind = None if path == "parameterized.qpy": @@ -1002,6 +1004,32 @@ def load_qpy(qpy_files, version_parts): ) + while pulse_files: + path, version = pulse_files.popitem() + + if version_parts < version or version_parts >= (2,0): + continue + + if path == "pulse_gates.qpy": + try: + load(open(path, "rb")) + except: + msg = f"Loading circuit with pulse gates should not raise" + sys.stderr.write(msg) + sys.exit(1) + else: + try: + # A ScheduleBlock payload, should raise QpyError + load(open(path, "rb")) + except QpyError: + continue + + msg = f"Loading payload {path} didn't raise QpyError" + sys.stderr.write(msg) + sys.exit(1) + + + def _main(): parser = argparse.ArgumentParser(description="Test QPY backwards compatibility") parser.add_argument("command", choices=["generate", "load"]) From 888545b7ba0e668eef13d0cd3dd5adeb2fa8b347 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Wed, 19 Feb 2025 16:44:38 +0200 Subject: [PATCH 13/18] Clean up TODOs --- qiskit/transpiler/passes/__init__.py | 2 +- qiskit/transpiler/passes/optimization/normalize_rx_angle.py | 1 - qiskit/transpiler/passes/scheduling/alignments/__init__.py | 1 - qiskit/transpiler/preset_passmanagers/level3.py | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index d675827ff582..1fd8454159a3 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -262,7 +262,7 @@ from .synthesis import AQCSynthesisPlugin # calibration -from .calibration.rzx_templates import rzx_templates # TODO: remove this +from .calibration.rzx_templates import rzx_templates # circuit scheduling from .scheduling import TimeUnitConversion diff --git a/qiskit/transpiler/passes/optimization/normalize_rx_angle.py b/qiskit/transpiler/passes/optimization/normalize_rx_angle.py index 9272c1e5dea6..b6f36e07de36 100644 --- a/qiskit/transpiler/passes/optimization/normalize_rx_angle.py +++ b/qiskit/transpiler/passes/optimization/normalize_rx_angle.py @@ -25,7 +25,6 @@ from qiskit.circuit.library.standard_gates import RXGate, RZGate, SXGate, XGate -# TODO: do we still need this pass?? class NormalizeRXAngle(TransformationPass): """Normalize theta parameter of RXGate instruction. diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index ca8658fff21a..1080ae860b29 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -33,7 +33,6 @@ value in units of dt, thus circuits involving delays may violate the constraints, which may result in failure in the circuit execution on the backend. -TODO: mentions pulse gates below. Do we want to keep these passes? There are two alignment constraint values reported by your quantum backend. In addition, if you want to define a custom instruction as a pulse gate, i.e. calibration, the underlying pulse instruction should satisfy other two waveform constraints. diff --git a/qiskit/transpiler/preset_passmanagers/level3.py b/qiskit/transpiler/preset_passmanagers/level3.py index 89b8b73c1415..ab01cc95bbf0 100644 --- a/qiskit/transpiler/preset_passmanagers/level3.py +++ b/qiskit/transpiler/preset_passmanagers/level3.py @@ -31,7 +31,6 @@ def level_3_pass_manager(pass_manager_config: PassManagerConfig) -> StagedPassMa This pass manager applies the user-given initial layout. If none is given, a search for a perfect layout (i.e. one that satisfies all 2-qubit interactions) is conducted. If no such layout is found, and device calibration information is available, the - # TODO: what does device calibration mean in this context? circuit is mapped to the qubits with best readouts and to CX gates with highest fidelity. The pass manager then transforms the circuit to match the coupling constraints. From 0a760e76f2ee70df16171de1e923fdfbc3891c82 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Thu, 20 Feb 2025 12:50:19 +0200 Subject: [PATCH 14/18] Unify transpiler renos w.r.t pulse removal --- qiskit/dagcircuit/dagdependency.py | 2 +- ...e-pulse-calibrations-4486dc101b76ec51.yaml | 33 ++++++++++++++----- .../remove-pulse-passes-3128f27ed7e42bf6.yaml | 7 ---- 3 files changed, 25 insertions(+), 17 deletions(-) delete mode 100644 releasenotes/notes/remove-pulse-passes-3128f27ed7e42bf6.yaml diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 1393bd0335b1..e398ada92c99 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -510,7 +510,7 @@ def draw(self, scale=0.7, filename=None, style="color"): Graphviz ` to be installed. Args: - scale (float): sng factor + scale (float): scaling factor filename (str): file path to save image to (format inferred from name) style (str): 'plain': B&W graph 'color' (default): color input/output/op nodes diff --git a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml index afe661448a61..bfda9c8793b0 100644 --- a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml +++ b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml @@ -7,15 +7,30 @@ upgrade_circuits: :class:`.DAGCircuit` classes and ``add_calibration`` has been removed from :class:`.QuantumCircuit`. upgrade_transpiler: - | - As part of Pulse removal in Qiskit 2.0, the ``inst_map`` argument has been removed from - the :func:`.generate_preset_pass_manager` and :func:`.transpile` functions, from the - :meth:`.Target.from_configuration` method and from the constructor of :class:`.PassManagerConfig`. - In addition, ``calibration`` has been removed from the :class:`.InstructionProperties` 's constructor and - is no longer a property of that class. - - | - As part of Pulse removal in Qiskit 2.0, the ``has_calibration``, ``get_calibration``, - ``instruction_schedule_map`` and ``update_from_instruction_schedule_map`` methods have been - removed from the :class:`.Target` class. + As part of Pulse removal in Qiskit 2.0, all pulse and calibration related functionality + in the transpiler has been removed. This includes the following: + + The following passes have been removed: + + * ``qiskit.transpiler.passes.PulseGates`` + * ``qiskit.transpiler.passes.ValidatePulseGates`` + * ``qiskit.transpiler.passes.RXCalibrationBuilder`` + * ``qiskit.transpiler.passes.RZXCalibrationBuilder`` + * ``qiskit.transpiler.passes.RZXCalibrationBuilderNoEcho`` + * ``qiskit.transpiler.passes.EchoRZXWeylDecomposition`` + + The ``inst_map`` argument has been removed from the following elements: + + * The :func:`.generate_preset_pass_manager` and :func:`.transpile` functions + * The :meth:`.Target.from_configuration` method + * The constructor of the :class:`.PassManagerConfig` class + + Calibration support has been removed: + + * ``calibration`` has been removed from the :class:`.InstructionProperties` 's constructor and is no longer a property of that class. + * The ``has_calibration``, ``get_calibration``, ``instruction_schedule_map`` and ``update_from_instruction_schedule_map`` methods have been removed from the :class:`.Target` class. + + upgrade_misc: - | As part of Pulse removal in Qiskit 2.0, the ``sequence`` and ``schedule_circuit`` functions diff --git a/releasenotes/notes/remove-pulse-passes-3128f27ed7e42bf6.yaml b/releasenotes/notes/remove-pulse-passes-3128f27ed7e42bf6.yaml deleted file mode 100644 index 49e176ce93b1..000000000000 --- a/releasenotes/notes/remove-pulse-passes-3128f27ed7e42bf6.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -upgrade_transpiler: - - | - The ``PulseGates``, ``ValidatePulseGates``, ``RXCalibrationBuilder``, ``RZXCalibrationBuilder``, - ``RZXCalibrationBuilderNoEcho`` and ``EchoRZXWeylDecomposition`` passes have been removed, - following their deprecation in Qiskit 1.3. These passes depend on and relate to the Pulse - package which is also being removed in Qiskit 2.0. From b54f25e564ea23ee18ae6ff987e4c2bcf927663b Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 28 Feb 2025 12:01:57 +0200 Subject: [PATCH 15/18] Remove inst_map from transpile() docstring --- qiskit/compiler/transpiler.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 8c6e36478017..e96905bc6993 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -92,7 +92,7 @@ def transpile( # pylint: disable=too-many-return-statements The prioritization of transpilation target constraints works as follows: if a ``target`` input is provided, it will take priority over any ``backend`` input or loose constraints - (``basis_gates``, ``inst_map``, ``coupling_map``, ``instruction_durations``, + (``basis_gates``, ``coupling_map``, ``instruction_durations``, ``dt`` or ``timing_constraints``). If a ``backend`` is provided together with any loose constraint from the list above, the loose constraint will take priority over the corresponding backend constraint. This behavior is independent of whether the ``backend`` instance is of type @@ -107,7 +107,6 @@ def transpile( # pylint: disable=too-many-return-statements **basis_gates** target basis_gates basis_gates **coupling_map** target coupling_map coupling_map **instruction_durations** target instruction_durations instruction_durations - **inst_map** target inst_map inst_map **dt** target dt dt **timing_constraints** target timing_constraints timing_constraints ============================ ========= ======================== ======================= @@ -370,14 +369,8 @@ def callback_func(**kwargs): # Edge cases require using the old model (loose constraints) instead of building a target, # but we don't populate the passmanager config with loose constraints unless it's one of # the known edge cases to control the execution path. - # Filter instruction_durations, timing_constraints and inst_map deprecation + # Filter instruction_durations and timing_constraints deprecation with warnings.catch_warnings(): - warnings.filterwarnings( - "ignore", - category=DeprecationWarning, - message=".*``inst_map`` is deprecated as of Qiskit 1.3.*", - module="qiskit", - ) warnings.filterwarnings( "ignore", category=DeprecationWarning, From 31ea9454cafe2aa3acdf7e5164aacc2e67f7927e Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 28 Feb 2025 12:44:42 +0200 Subject: [PATCH 16/18] Small cleanup and reno update --- .../transpiler/passes/scheduling/padding/base_padding.py | 8 -------- .../notes/remove-pulse-calibrations-4486dc101b76ec51.yaml | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 8b359791ab1d..353feea3260d 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -81,14 +81,6 @@ def get_duration(self, node, dag): # pylint: disable=too-many-return-statements if not self.target and not self.durations: return None indices = [dag.find_bit(qarg).index for qarg in node.qargs] - if dag._has_calibration_for(node): - # If node has calibration, this value should be the highest priority - cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - return dag._calibrations_prop[node.op.name][cal_key].duration if self.target: props_dict = self.target.get(node.name) diff --git a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml index bfda9c8793b0..3bd8a0f9136f 100644 --- a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml +++ b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml @@ -10,7 +10,7 @@ upgrade_transpiler: As part of Pulse removal in Qiskit 2.0, all pulse and calibration related functionality in the transpiler has been removed. This includes the following: - The following passes have been removed: + Passes that have been removed: * ``qiskit.transpiler.passes.PulseGates`` * ``qiskit.transpiler.passes.ValidatePulseGates`` From edfd785dc7ed77b4b12c1ddaef6c8ab3d34c4166 Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Fri, 28 Feb 2025 14:23:11 +0200 Subject: [PATCH 17/18] Fix lint --- qiskit/transpiler/passes/scheduling/padding/base_padding.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index 353feea3260d..cdb3478f72bc 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -15,7 +15,6 @@ from collections.abc import Iterable import logging -import warnings from qiskit.circuit import Qubit, Clbit, Instruction from qiskit.circuit.delay import Delay From 8e14bd5775664e63f79eec16f72884a4275c742f Mon Sep 17 00:00:00 2001 From: Eli Arbel Date: Sat, 1 Mar 2025 17:40:22 +0200 Subject: [PATCH 18/18] Applying comments from Elena's review --- crates/accelerate/src/basis/basis_translator/mod.rs | 3 ++- .../notes/remove-pulse-calibrations-4486dc101b76ec51.yaml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/basis/basis_translator/mod.rs b/crates/accelerate/src/basis/basis_translator/mod.rs index 9ebed652b4f9..26f7d7d3051b 100644 --- a/crates/accelerate/src/basis/basis_translator/mod.rs +++ b/crates/accelerate/src/basis/basis_translator/mod.rs @@ -346,7 +346,8 @@ fn extract_basis_target( }; let bound_inst = op.instruction.bind(py); // TODO: Use Rust method `op.blocks` instead of Python side extraction now that - // the usage of a python-space method `QuantumCircuit.has_calibration_for` is not needed anymore + // the python-space method `QuantumCircuit.has_calibration_for` + // has been removed and we don't need to account for it. let blocks = bound_inst.getattr("blocks")?.try_iter()?; for block in blocks { extract_basis_target_circ( diff --git a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml index 3bd8a0f9136f..314e2e8893c6 100644 --- a/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml +++ b/releasenotes/notes/remove-pulse-calibrations-4486dc101b76ec51.yaml @@ -33,5 +33,5 @@ upgrade_transpiler: upgrade_misc: - | - As part of Pulse removal in Qiskit 2.0, the ``sequence`` and ``schedule_circuit`` functions + As part of Pulse removal in Qiskit 2.0, the ``sequence`` and ``schedule_circuit`` functions from :mod:`.qiskit.scheduler` together with the ``ScheduleConfig`` class have been removed.