Skip to content

Commit

Permalink
Remove calibrations APIs and related functionality (#13861)
Browse files Browse the repository at this point in the history
* Handle ScheduleBlock and pulse gates loading

* Add documentation and remove redundant code

* Limit QPY version when generating circuits for compatibility test

Fix some doc issues

* Handle QPY compatibility testing. Misc other fixes

* Update qiskit/qpy/binary_io/circuits.py

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>

* 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.

* First pass

* Avoid generating pulse circuits in load_qpy & version >= 2.0

* Remove more stuff

* Add reno and some misc fixes

* Add recent changes from remove-pulse-qpy branch

* Raise QpyError when loading ScheduleBlock payloads

* Clean up TODOs

* Unify transpiler renos w.r.t pulse removal

* Remove inst_map from transpile() docstring

* Small cleanup and reno update

* Fix lint

* Applying comments from Elena's review

---------

Co-authored-by: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com>
  • Loading branch information
eliarbel and raynelfss authored Mar 2, 2025
1 parent 3c8bae4 commit 5184ca4
Show file tree
Hide file tree
Showing 80 changed files with 176 additions and 3,308 deletions.
56 changes: 12 additions & 44 deletions crates/accelerate/src/basis/basis_translator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,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,
Expand All @@ -232,10 +232,8 @@ fn extract_basis(
basis: &mut IndexSet<GateIdentifier, ahash::RandomState>,
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
{
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() {
Expand Down Expand Up @@ -264,11 +262,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()));
}
if inst.op.control_flow() {
Expand All @@ -287,7 +281,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(
Expand All @@ -306,9 +300,9 @@ fn extract_basis_target(
ahash::RandomState,
>,
) -> 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 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
Expand Down Expand Up @@ -351,8 +345,9 @@ 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 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(
Expand Down Expand Up @@ -390,13 +385,9 @@ fn extract_basis_target_circ(
let py = circuit.py();
let circ_data_bound = circuit.getattr("_data")?.downcast_into::<CircuitData>()?;
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 circuit
.call_method1("_has_calibration_for", (circuit.get_item(index)?,))?
.is_truthy()?
|| qargs.len() < min_qubits
{
if qargs.len() < min_qubits {
continue;
}
// Treat the instruction as on an incomplete basis if the qargs are in the
Expand Down Expand Up @@ -578,29 +569,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.label.as_ref().map(|x| x.as_ref().clone()),
#[cfg(feature = "cache_pygates")]
None,
)?;
continue;
}
let unique_qargs: Option<Qargs> = if qubit_set.is_empty() {
None
} else {
Expand Down
7 changes: 2 additions & 5 deletions crates/accelerate/src/check_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -65,10 +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(),
[qubits[0].0, qubits[1].0],
Expand Down
12 changes: 0 additions & 12 deletions crates/accelerate/src/euler_one_qubit_decomposer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
38 changes: 2 additions & 36 deletions crates/accelerate/src/gate_direction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ use pyo3::types::PyTuple;
use qiskit_circuit::operations::OperationRef;
use qiskit_circuit::packed_instruction::PackedOperation;
use qiskit_circuit::{
circuit_instruction::CircuitInstruction,
converters::{circuit_to_dag, QuantumCircuitData},
dag_circuit::DAGCircuit,
dag_node::{DAGNode, DAGOpNode},
imports,
imports::get_std_gate_class,
operations::Operation,
Expand Down Expand Up @@ -291,7 +289,7 @@ where
}
}

if op_args.len() != 2 || dag.has_calibration_for_index(py, node)? {
if op_args.len() != 2 {
continue;
};

Expand Down Expand Up @@ -334,9 +332,7 @@ 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])?
{
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 {
Expand Down Expand Up @@ -375,36 +371,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<bool> {
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(),
label: packed_inst.label.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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ impl InstructionProperties {
/// specified set of qubits
/// error (Option<f64>): The average error rate for the instruction on the specified
/// set of qubits.
/// calibration (Option<PyObject>): The pulse representation of the instruction.
#[new]
#[pyo3(signature = (duration=None, error=None))]
pub fn new(_py: Python<'_>, duration: Option<f64>, error: Option<f64>) -> Self {
Expand Down
11 changes: 1 addition & 10 deletions crates/circuit/src/converters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -30,7 +26,6 @@ use crate::packed_instruction::PackedInstruction;
pub struct QuantumCircuitData<'py> {
pub data: CircuitData,
pub name: Option<Bound<'py, PyAny>>,
pub calibrations: Option<HashMap<String, Py<PyDict>>>,
pub metadata: Option<Bound<'py, PyAny>>,
pub qregs: Option<Bound<'py, PyList>>,
pub cregs: Option<Bound<'py, PyList>>,
Expand All @@ -47,10 +42,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"))
Expand Down
Loading

0 comments on commit 5184ca4

Please sign in to comment.