diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 98b65e44bab..365f51be2e1 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -17,7 +17,7 @@ use crate::circuit_instruction::{ }; use crate::dag_node::{DAGInNode, DAGNode, DAGOpNode, DAGOutNode}; use crate::error::DAGCircuitError; -use crate::imports::VARIABLE_MAPPER; +use crate::imports::{DAG_NODE, VARIABLE_MAPPER}; use crate::interner::{Index, IndexedInterner, Interner}; use crate::operations::{Operation, OperationType, Param}; use crate::{interner, BitType, Clbit, Qubit, SliceOrInt, TupleLikeArg}; @@ -44,6 +44,11 @@ use rustworkx_core::petgraph::stable_graph::{DefaultIx, IndexType, Neighbors, No use rustworkx_core::petgraph::visit::{IntoNodeReferences, NodeCount, NodeRef}; use rustworkx_core::petgraph::Incoming; use std::collections::VecDeque; +use rustworkx_core::traversal::{ + ancestors as core_ancestors, bfs_successors as core_bfs_successors, + descendants as core_descendants, +}; +use std::borrow::Borrow; use std::convert::Infallible; use std::f64::consts::PI; use std::ffi::c_double; @@ -134,7 +139,9 @@ pub struct DAGCircuit { dag: StableDiGraph, + #[pyo3(get)] qregs: Py, + #[pyo3(get)] cregs: Py, /// The cache used to intern instruction qargs. @@ -380,6 +387,35 @@ impl DAGCircuit { }) } + /// Returns the current sequence of registered :class:`.Qubit` instances as a list. + /// + /// .. warning:: + /// + /// Do not modify this list yourself. It will invalidate the :class:`DAGCircuit` data + /// structures. + /// + /// Returns: + /// list(:class:`.Qubit`): The current sequence of registered qubits. + #[getter] + pub fn qubits(&self, py: Python<'_>) -> Py { + self.qubits.cached().clone_ref(py) + } + + /// Returns the current sequence of registered :class:`.Clbit` + /// instances as a list. + /// + /// .. warning:: + /// + /// Do not modify this list yourself. It will invalidate the :class:`DAGCircuit` data + /// structures. + /// + /// Returns: + /// list(:class:`.Clbit`): The current sequence of registered clbits. + #[getter] + pub fn clbits(&self, py: Python<'_>) -> Py { + self.clbits.cached().clone_ref(py) + } + /// Return a list of the wires in order. #[getter] fn get_wires(&self, py: Python<'_>) -> Py { @@ -1874,12 +1910,20 @@ def _format(operand): } } - // - // def node_eq(node_self, node_other): - // return DAGNode.semantic_eq(node_self, node_other, self_bit_indices, other_bit_indices) - // - // return rx.is_isomorphic_node_match(self._multi_graph, other._multi_graph, node_eq) todo!() + // Check for VF2 isomorphic match. + // self.topological_nodes() + // let semantic_eq = DAG_NODE.get_bound(py).getattr(intern!(py, "semantic_eq"))?; + // let node_match = |n1, n2| -> bool { + // semantic_eq + // .call1((n1, n2, self_bit_indices, other_bit_indices)).map_or(false, |r| r.extract().unwrap_or(false)) + // }; + // Ok(petgraph::algo::is_isomorphic_matching( + // &self.dag, + // &other.dag, + // node_match, + // |_, _| true, + // )) } /// Yield nodes in topological order. @@ -2777,22 +2821,45 @@ def _format(operand): } /// Returns set of the ancestors of a node as DAGOpNodes and DAGInNodes. - fn ancestors(&self, py: Python, node: &DAGNode) -> PyResult> { - // return {self._multi_graph[x] for x in rx.ancestors(self._multi_graph, node._node_id)} - todo!() + #[pyo3(name = "ancestors")] + fn py_ancestors(&self, py: Python, node: &DAGNode) -> PyResult> { + let ancestors: PyResult> = self + .ancestors(node.node.unwrap()) + .map(|node| self.get_node(py, node)) + .collect(); + Ok(PySet::new_bound(py, &ancestors?)?.unbind()) } /// Returns set of the descendants of a node as DAGOpNodes and DAGOutNodes. - fn descendants(&self, py: Python, node: &DAGNode) -> PyResult> { - // return {self._multi_graph[x] for x in rx.descendants(self._multi_graph, node._node_id)} - todo!() + #[pyo3(name = "descendants")] + fn py_descendants(&self, py: Python, node: &DAGNode) -> PyResult> { + let descendants: PyResult> = self + .descendants(node.node.unwrap()) + .map(|node| self.get_node(py, node)) + .collect(); + Ok(PySet::new_bound(py, &descendants?)?.unbind()) } /// Returns an iterator of tuples of (DAGNode, [DAGNodes]) where the DAGNode is the current node /// and [DAGNode] is its successors in BFS order. - fn bfs_successors(&self, py: Python, node: &DAGNode) -> PyResult> { - // return iter(rx.bfs_successors(self._multi_graph, node._node_id)) - todo!() + #[pyo3(name = "bfs_successors")] + fn py_bfs_successors(&self, py: Python, node: &DAGNode) -> PyResult> { + let successor_index: PyResult)>> = self + .bfs_successors(node.node.unwrap()) + .map(|(node, nodes)| -> PyResult<(PyObject, Vec)> { + Ok(( + self.get_node(py, node)?, + nodes + .iter() + .map(|sub_node| self.get_node(py, *sub_node)) + .collect::>>()?, + )) + }) + .collect(); + Ok(PyList::new_bound(py, successor_index?) + .into_any() + .iter()? + .unbind()) } /// Returns iterator of the successors of a node that are @@ -3585,6 +3652,25 @@ impl DAGCircuit { } } + /// Returns an iterator of the ancestors indices of a node. + pub fn ancestors<'a>(&'a self, node: NodeIndex) -> impl Iterator + 'a { + core_ancestors(&self.dag, node).filter(move |next| next != &node) + } + + /// Returns an iterator of the descendants of a node as DAGOpNodes and DAGOutNodes. + pub fn descendants<'a>(&'a self, node: NodeIndex) -> impl Iterator + 'a { + core_descendants(&self.dag, node).filter(move |next| next != &node) + } + + /// Returns an iterator of tuples of (DAGNode, [DAGNodes]) where the DAGNode is the current node + /// and [DAGNode] is its successors in BFS order. + pub fn bfs_successors<'a>( + &'a self, + node: NodeIndex, + ) -> impl Iterator)> + 'a { + core_bfs_successors(&self.dag, node).filter(move |(_, others)| !others.is_empty()) + } + fn unpack_into(&self, py: Python, id: NodeIndex, weight: &NodeType) -> PyResult> { let dag_node = match weight { NodeType::QubitIn(qubit) => Py::new( diff --git a/crates/circuit/src/imports.rs b/crates/circuit/src/imports.rs index 0df3707cc02..b0c0d63ac86 100644 --- a/crates/circuit/src/imports.rs +++ b/crates/circuit/src/imports.rs @@ -74,6 +74,8 @@ pub static SINGLETON_CONTROLLED_GATE: ImportOnceCell = pub static VARIABLE_MAPPER: ImportOnceCell = ImportOnceCell::new("qiskit.circuit._classical_resource_map", "VariableMapper"); +pub static DAG_NODE: ImportOnceCell = ImportOnceCell::new("qiskit.dagcircuit", "DAGNode"); + /// A mapping from the enum variant in crate::operations::StandardGate to the python /// module path and class name to import it. This is used to populate the conversion table /// when a gate is added directly via the StandardGate path and there isn't a Python object diff --git a/crates/circuit/src/lib.rs b/crates/circuit/src/lib.rs index 3d5b1c702c4..af641d6a5ad 100644 --- a/crates/circuit/src/lib.rs +++ b/crates/circuit/src/lib.rs @@ -25,6 +25,7 @@ mod interner; use pyo3::prelude::*; use pyo3::types::{PySequence, PySlice, PyTuple}; +use pyo3::DowncastError; use std::ops::Deref; /// A private enumeration type used to extract arguments to pymethod @@ -49,9 +50,16 @@ pub struct TupleLikeArg<'py> { impl<'py> FromPyObject<'py> for TupleLikeArg<'py> { fn extract_bound(ob: &Bound<'py, PyAny>) -> PyResult { - Ok(TupleLikeArg { - value: ob.downcast::()?.to_tuple()?, - }) + let value = match ob.downcast::() { + Ok(seq) => seq.to_tuple()?, + Err(_) => PyTuple::new_bound( + ob.py(), + ob.iter()? + .map(|o| Ok(o?.unbind())) + .collect::>>()?, + ), + }; + Ok(TupleLikeArg { value }) } } diff --git a/qiskit/transpiler/passes/calibration/rzx_templates.py b/qiskit/transpiler/passes/calibration/rzx_templates.py index 406e5e75de0..12211f18b2b 100644 --- a/qiskit/transpiler/passes/calibration/rzx_templates.py +++ b/qiskit/transpiler/passes/calibration/rzx_templates.py @@ -20,17 +20,6 @@ from qiskit.circuit.library.templates import rzx -class RZXTemplateMap(Enum): - """Mapping of instruction name to decomposition template.""" - - ZZ1 = rzx.rzx_zz1() - ZZ2 = rzx.rzx_zz2() - ZZ3 = rzx.rzx_zz3() - YZ = rzx.rzx_yz() - XZ = rzx.rzx_xz() - CY = rzx.rzx_cy() - - def rzx_templates(template_list: List[str] = None) -> Dict: """Convenience function to get the cost_dict and templates for template matching. @@ -40,6 +29,16 @@ def rzx_templates(template_list: List[str] = None) -> Dict: Returns: Decomposition templates and cost values. """ + class RZXTempateMap(Enum): + """Mapping of instruction name to decomposition template.""" + + ZZ1 = rzx.rzx_zz1() + ZZ2 = rzx.rzx_zz2() + ZZ3 = rzx.rzx_zz3() + YZ = rzx.rzx_yz() + XZ = rzx.rzx_xz() + CY = rzx.rzx_cy() + if template_list is None: template_list = ["zz1", "zz2", "zz3", "yz", "xz", "cy"]