Skip to content

Commit

Permalink
Merge pull request Qiskit#15 from alexanderivrii/rusty-quantum-cone
Browse files Browse the repository at this point in the history
attempt to port quantum_causal_cone to rust
  • Loading branch information
kevinhartman authored Jul 9, 2024
2 parents a7c697b + 04f6404 commit d5a33f7
Showing 1 changed file with 115 additions and 61 deletions.
176 changes: 115 additions & 61 deletions crates/circuit/src/dag_circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ use rustworkx_core::petgraph::prelude::StableDiGraph;
use rustworkx_core::petgraph::stable_graph::{DefaultIx, IndexType, Neighbors, NodeIndex};
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,
Expand Down Expand Up @@ -2774,21 +2775,34 @@ def _format(operand):

/// Returns iterator of the predecessors of a node that are
/// connected by a quantum edge as DAGOpNodes and DAGInNodes.
fn quantum_predecessors(&self, py: Python, node: &DAGNode) -> PyResult<Py<PyIterator>> {
let edges = self.dag.edges_directed(node.node.unwrap(), Incoming);
let filtered = edges.filter_map(|e| match e.weight() {
Wire::Qubit(_) => Some(e.source()),
_ => None,
});
let predecessors: PyResult<Vec<_>> =
filtered.unique().map(|i| self.get_node(py, i)).collect();
#[pyo3(name = "quantum_predecessors")]
fn py_quantum_predecessors(&self, py: Python, node: &DAGNode) -> PyResult<Py<PyIterator>> {
let predecessors: PyResult<Vec<_>> = self
.quantum_predecessors(node.node.unwrap())
.map(|i| self.get_node(py, i))
.collect();
Ok(PyTuple::new_bound(py, predecessors?)
.into_any()
.iter()
.unwrap()
.unbind())
}

/// Returns iterator of the successors of a node that are
/// connected by a quantum edge as DAGOpNodes and DAGOutNodes.
#[pyo3(name = "quantum_successors")]
fn py_quantum_successors(&self, py: Python, node: &DAGNode) -> PyResult<Py<PyIterator>> {
let successors: PyResult<Vec<_>> = self
.quantum_successors(node.node.unwrap())
.map(|i| self.get_node(py, i))
.collect();
Ok(PyTuple::new_bound(py, successors?)
.into_any()
.iter()
.unwrap()
.unbind())
}

/// Returns iterator of the predecessors of a node that are
/// connected by a classical edge as DAGOpNodes and DAGInNodes.
fn classical_predecessors(&self, py: Python, node: &DAGNode) -> PyResult<Py<PyIterator>> {
Expand Down Expand Up @@ -2848,23 +2862,6 @@ def _format(operand):
.unbind())
}

/// Returns iterator of the successors of a node that are
/// connected by a quantum edge as DAGOpNodes and DAGOutNodes.
fn quantum_successors(&self, py: Python, node: &DAGNode) -> PyResult<Py<PyIterator>> {
let edges = self.dag.edges_directed(node.node.unwrap(), Outgoing);
let filtered = edges.filter_map(|e| match e.weight() {
Wire::Qubit(_) => Some(e.target()),
_ => None,
});
let predecessors: PyResult<Vec<_>> =
filtered.unique().map(|i| self.get_node(py, i)).collect();
Ok(PyTuple::new_bound(py, predecessors?)
.into_any()
.iter()
.unwrap()
.unbind())
}

/// Returns iterator of the successors of a node that are
/// connected by a classical edge as DAGOpNodes and DAGOutNodes.
fn classical_successors(&self, py: Python, node: &DAGNode) -> PyResult<Py<PyIterator>> {
Expand Down Expand Up @@ -3204,42 +3201,79 @@ def _format(operand):
///
/// Returns:
/// Set[~qiskit.circuit.Qubit]: The set of qubits whose interactions affect ``qubit``.
fn quantum_causal_cone(&self, qubit: &Bound<PyAny>) -> PyResult<Py<PyAny>> {
// # Retrieve the output node from the qubit
// output_node = self.output_map.get(qubit, None)
// if not output_node:
// raise DAGCircuitError(f"Qubit {qubit} is not part of this circuit.")
// # Add the qubit to the causal cone.
// qubits_to_check = {qubit}
// # Add predecessors of output node to the queue.
// queue = deque(self.predecessors(output_node))
//
// # While queue isn't empty
// while queue:
// # Pop first element.
// node_to_check = queue.popleft()
// # Check whether element is input or output node.
// if isinstance(node_to_check, DAGOpNode):
// # Keep all the qubits in the operation inside a set.
// qubit_set = set(node_to_check.qargs)
// # Check if there are any qubits in common and that the operation is not a barrier.
// if (
// len(qubit_set.intersection(qubits_to_check)) > 0
// and node_to_check.op.name != "barrier"
// and not getattr(node_to_check.op, "_directive")
// ):
// # If so, add all the qubits to the causal cone.
// qubits_to_check = qubits_to_check.union(qubit_set)
// # For each predecessor of the current node, filter input/output nodes,
// # also make sure it has at least one qubit in common. Then append.
// for node in self.quantum_predecessors(node_to_check):
// if (
// isinstance(node, DAGOpNode)
// and len(qubits_to_check.intersection(set(node.qargs))) > 0
// ):
// queue.append(node)
// return qubits_to_check
todo!()
fn quantum_causal_cone(&self, py: Python, qubit: &Bound<PyAny>) -> PyResult<Py<PySet>> {
// Retrieve the output node from the qubit
let output_qubit = self.qubits.find(qubit).ok_or_else(|| {
DAGCircuitError::new_err(format!(
"The given qubit {:?} is not present in the circuit",
qubit
))
})?;
let output_node_index = self.qubit_output_map.get(&output_qubit).ok_or_else(|| {
DAGCircuitError::new_err(format!(
"The given qubit {:?} is not present in qubit_output_map",
qubit
))
})?;

let mut qubits_in_cone: HashSet<&Qubit> = HashSet::from([&output_qubit]);
let mut queue: VecDeque<NodeIndex> =
self.quantum_predecessors(*output_node_index).collect();

// The processed_non_directive_nodes stores the set of processed non-directive nodes.
// This is an optimization to avoid considering the same non-directive node multiple
// times when reached from different paths.
// The directive nodes (such as barriers or measures) are trickier since when processing
// them we only add their predecessors that intersect qubits_in_cone. Hence, directive
// nodes have to be considered multiple times.
let mut processed_non_directive_nodes: HashSet<NodeIndex> = HashSet::new();

while !queue.is_empty() {
let cur_index = queue.pop_front().unwrap();

if let NodeType::Operation(packed) = self.dag.node_weight(cur_index).unwrap() {
if !packed.op.directive() {
// If the operation is not a directive (in particular not a barrier nor a measure),
// we do not do anything if it was already processed. Otherwise, we add its qubits
// to qubits_in_cone, and append its predecessors to queue.
if processed_non_directive_nodes.contains(&cur_index) {
continue;
}
qubits_in_cone.extend(self.qargs_cache.intern(packed.qubits_id).iter());
processed_non_directive_nodes.insert(cur_index);

for pred_index in self.quantum_predecessors(cur_index) {
if let NodeType::Operation(pred_packed) =
self.dag.node_weight(pred_index).unwrap()
{
queue.push_back(pred_index);
}
}
} else {
// Directives (such as barriers and measures) may be defined over all the qubits,
// yet not all of these qubits should be considered in the causal cone. So we
// only add those predecessors that have qubits in common with qubits_in_cone.
for pred_index in self.quantum_predecessors(cur_index) {
if let NodeType::Operation(pred_packed) =
self.dag.node_weight(pred_index).unwrap()
{
if self
.qargs_cache
.intern(pred_packed.qubits_id)
.iter()
.any(|x| qubits_in_cone.contains(x))
{
queue.push_back(pred_index);
}
}
}
}
}
}

let qubits_in_cone_vec: Vec<_> = qubits_in_cone.iter().map(|&&qubit| qubit).collect();
let elements = self.qubits.map_indices(&qubits_in_cone_vec[..]);
Ok(PySet::new_bound(py, elements)?.unbind())
}

/// Return a dictionary of circuit properties.
Expand Down Expand Up @@ -3309,6 +3343,26 @@ impl DAGCircuit {
}
}

fn quantum_predecessors(&self, node: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
self.dag
.edges_directed(node, Incoming)
.filter_map(|e| match e.weight() {
Wire::Qubit(_) => Some(e.source()),
_ => None,
})
.unique()
}

fn quantum_successors(&self, node: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
self.dag
.edges_directed(node, Outgoing)
.filter_map(|e| match e.weight() {
Wire::Qubit(_) => Some(e.target()),
_ => None,
})
.unique()
}

fn topological_nodes(&self) -> PyResult<impl Iterator<Item = NodeIndex>> {
let key = |node: NodeIndex| -> Result<(Option<Index>, Option<Index>), Infallible> {
Ok(self.dag.node_weight(node).unwrap().key())
Expand Down

0 comments on commit d5a33f7

Please sign in to comment.