Skip to content

Commit

Permalink
Deprecate Pulse package and dependencies (Qiskit#13164)
Browse files Browse the repository at this point in the history
* Deprecate classes, a couple functions & fix tests

* Deprecate classes, a couple functions & fix tests

* Deprecate some functionality that uses Pulse

* Filter deprecation warnings in QiskitTestCase

* Fix import path for deprecate_pulse_func

* Deprecate `schedule_circuit`

* Deprecate compiler's `schedule`, `sequence` and related functions

* Deprecate passing SchduleBlock(s) to qpy.dump

* More deprecations and misc updates

* Mark deprecated properties as such

* Add, refine and fix deprecations

Added deprecations in `dag_circuit.rs`
Added more deprecations in `builder.py`
Fixed some deprecations comments

* Add initial release notes and refine deprecations

* Warn about pulse gates serialization

* Handle deprecation warnings in unit testing

* Add alternative privates paths & refine deprecations

* Catch pulse deprecation warnings in certain methods

This commit adds `warnings.catch_warnings` blocks to some methods which
are not directly deprecated but otherwise use classes or methods which
are being deprecated. Adding this filter to specific methods which are
used in common workflow, e.g. transpilation and for which we don't want
pulse deprecations to be emitted if pulse is not used directly by the user.

* Misc changes to pulse deprecation functions  and release notes

* Fix lint issues

* Fix CI failure with Python 3.10 in `catch_warnings`

* Update releasenotes/notes/deprecate-pulse-package-07a621be1db7fa30.yaml

Co-authored-by: Elena Peña Tapia <[email protected]>

* Indicate explicitly dependency removal or move to Dynamics

* Fix unit test failure

The inner class `TestAddCalibration` in `test_transpiler.py::TestTranspilerParallel` calls a deprecated functionality.
In an attempt to use `self.assertWarns` inside the inner class, a reference to the outer
class was stored as a class attribute of `TestAddCalibration`.  dill got confused as to how to serialize
the outer class which derives from `QiskitTestCase`. So switched to using `warnings.catch_warnnigs`
instead in the inner class to avoid this reference of the outer class in the serialized inner class.

* Standardize docstring of `deprecate_pulse_dependency`

---------

Co-authored-by: Elena Peña Tapia <[email protected]>
  • Loading branch information
eliarbel and ElePT authored Oct 23, 2024
1 parent 9a1d8d3 commit 056f286
Show file tree
Hide file tree
Showing 145 changed files with 3,207 additions and 1,915 deletions.
5 changes: 4 additions & 1 deletion crates/circuit/src/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ 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"))?.extract().ok(),
calibrations: ob
.getattr(intern!(py, "_calibrations_prop"))?
.extract()
.ok(),
metadata: ob.getattr(intern!(py, "metadata")).ok(),
qregs: ob
.getattr(intern!(py, "qregs"))
Expand Down
72 changes: 68 additions & 4 deletions crates/circuit/src/dag_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ impl DAGCircuit {
let out_dict = PyDict::new_bound(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", self.calibrations.clone())?;
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())?;
Expand Down Expand Up @@ -648,7 +648,10 @@ impl DAGCircuit {
let dict_state = state.downcast_bound::<PyDict>(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")?.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()?;
Expand Down Expand Up @@ -864,8 +867,15 @@ impl DAGCircuit {
///
/// 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) -> HashMap<String, Py<PyDict>> {
fn get_calibrations(&self, py: Python) -> HashMap<String, Py<PyDict>> {
emit_pulse_dependency_deprecation(
py,
"property ``qiskit.dagcircuit.dagcircuit.DAGCircuit.calibrations``",
);

self.calibrations.clone()
}

Expand All @@ -874,8 +884,29 @@ impl DAGCircuit {
/// 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, calibrations: HashMap<String, Py<PyDict>>) {
fn set_calibrations(&mut self, py: Python, calibrations: HashMap<String, Py<PyDict>>) {
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<String, Py<PyDict>> {
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<String, Py<PyDict>>) {
self.calibrations = calibrations;
}

Expand All @@ -898,6 +929,11 @@ impl DAGCircuit {
schedule: Py<PyAny>,
mut params: Option<Bound<'py, PyAny>>,
) -> 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"))?;
Expand Down Expand Up @@ -955,7 +991,18 @@ def _format(operand):

/// 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
fn has_calibration_for(&self, py: Python, node: PyRef<DAGOpNode>) -> PyResult<bool> {
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<DAGOpNode>) -> PyResult<bool> {
if !self
.calibrations
.contains_key(node.instruction.operation.name())
Expand Down Expand Up @@ -6960,3 +7007,20 @@ fn add_global_phase(py: Python, phase: &Param, other: &Param) -> PyResult<Param>
}

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_bound(
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_bound::<PyDeprecationWarning>(),
1,
));
}
2 changes: 2 additions & 0 deletions qiskit/assembler/assemble_schedules.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
from qiskit.pulse import instructions, transforms, library, schedule, channels
from qiskit.qobj import utils as qobj_utils, converters
from qiskit.qobj.converters.pulse_instruction import ParametricPulseShapes
from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency


@deprecate_pulse_dependency
def assemble_schedules(
schedules: List[
Union[
Expand Down
32 changes: 28 additions & 4 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
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
Expand All @@ -70,6 +71,7 @@
from .delay import Delay
from .store import Store


if typing.TYPE_CHECKING:
import qiskit # pylint: disable=cyclic-import
from qiskit.transpiler.layout import TranspileLayout # pylint: disable=cyclic-import
Expand Down Expand Up @@ -1339,34 +1341,55 @@ 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 dict(self._calibrations)
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 or operation.name not in self.calibrations:
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 = []
Expand All @@ -1376,7 +1399,7 @@ def has_calibration_for(self, instruction: CircuitInstruction | tuple):
else:
params.append(p)
params = tuple(params)
return (qubits, params) in self.calibrations[operation.name]
return (qubits, params) in self._calibrations_prop[operation.name]

@property
def metadata(self) -> dict:
Expand Down Expand Up @@ -2017,7 +2040,7 @@ 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.items():
for gate, cals in other._calibrations_prop.items():
dest._calibrations[gate].update(cals)

dest.duration = None
Expand Down Expand Up @@ -6477,6 +6500,7 @@ 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],
Expand Down
2 changes: 2 additions & 0 deletions qiskit/compiler/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from qiskit.scheduler.config import ScheduleConfig
from qiskit.scheduler.schedule_circuit import schedule_circuit
from qiskit.utils.parallel import parallel_map
from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency

logger = logging.getLogger(__name__)

Expand All @@ -35,6 +36,7 @@ def _log_schedule_time(start_time, end_time):
logger.info(log_msg)


@deprecate_pulse_dependency(moving_to_dynamics=True)
def schedule(
circuits: Union[QuantumCircuit, List[QuantumCircuit]],
backend: Optional[Backend] = None,
Expand Down
2 changes: 2 additions & 0 deletions qiskit/compiler/sequencer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
from qiskit.pulse import InstructionScheduleMap, Schedule
from qiskit.scheduler import ScheduleConfig
from qiskit.scheduler.sequence import sequence as _sequence
from qiskit.utils.deprecate_pulse import deprecate_pulse_dependency


@deprecate_pulse_dependency(moving_to_dynamics=True)
def sequence(
scheduled_circuits: Union[QuantumCircuit, List[QuantumCircuit]],
backend: Optional[Backend] = None,
Expand Down
4 changes: 3 additions & 1 deletion qiskit/compiler/transpiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@
from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.target import Target
from qiskit.utils.deprecate_pulse import deprecate_pulse_arg

logger = logging.getLogger(__name__)

_CircuitT = TypeVar("_CircuitT", bound=Union[QuantumCircuit, List[QuantumCircuit]])


@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,
Expand Down Expand Up @@ -104,7 +106,7 @@ 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: Mapping of unrolled gates to pulse schedules. If this is not provided,
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
Expand Down
2 changes: 1 addition & 1 deletion qiskit/converters/circuit_to_dagdependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ def circuit_to_dagdependency(circuit, create_preds_and_succs=True):
dagdependency._add_predecessors()
dagdependency._add_successors()

dagdependency.calibrations = circuit.calibrations
dagdependency._calibrations = circuit._calibrations_prop

return dagdependency
2 changes: 1 addition & 1 deletion qiskit/converters/circuit_to_dagdependency_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def _circuit_to_dagdependency_v2(circuit):
dagdependency = _DAGDependencyV2()
dagdependency.name = circuit.name
dagdependency.metadata = circuit.metadata
dagdependency.calibrations = circuit.calibrations
dagdependency._calibrations = circuit._calibrations_prop
dagdependency.global_phase = circuit.global_phase

dagdependency.add_qubits(circuit.qubits)
Expand Down
2 changes: 1 addition & 1 deletion qiskit/converters/dag_to_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ 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 = dag.calibrations
circuit._calibrations_prop = dag._calibrations_prop

circuit._data = circuit_data

Expand Down
2 changes: 1 addition & 1 deletion qiskit/converters/dag_to_dagdependency.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ def dag_to_dagdependency(dag, create_preds_and_succs=True):

# copy metadata
dagdependency.global_phase = dag.global_phase
dagdependency.calibrations = dag.calibrations
dagdependency._calibrations_prop = dag._calibrations_prop

return dagdependency
2 changes: 1 addition & 1 deletion qiskit/converters/dag_to_dagdependency_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def _dag_to_dagdependency_v2(dag):
dagdependency.name = dag.name
dagdependency.metadata = dag.metadata
dagdependency.global_phase = dag.global_phase
dagdependency.calibrations = dag.calibrations
dagdependency.calibrations = dag._calibrations_prop

dagdependency.add_qubits(dag.qubits)
dagdependency.add_clbits(dag.clbits)
Expand Down
6 changes: 5 additions & 1 deletion qiskit/converters/dagdependency_to_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ def dagdependency_to_circuit(dagdependency):
)
circuit.metadata = dagdependency.metadata

circuit.calibrations = dagdependency.calibrations
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))
Expand Down
7 changes: 6 additions & 1 deletion qiskit/converters/dagdependency_to_dag.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

"""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):
Expand Down Expand Up @@ -44,6 +45,10 @@ def dagdependency_to_dag(dagdependency):

# copy metadata
dagcircuit.global_phase = dagdependency.global_phase
dagcircuit.calibrations = dagdependency.calibrations
if isinstance(dagdependency, DAGDependency):
dagcircuit._calibrations_prop = dagdependency._calibrations_prop
else:
# This can be _DAGDependencyV2
dagcircuit._calibrations_prop = dagdependency.calibrations

return dagcircuit
Loading

0 comments on commit 056f286

Please sign in to comment.