From e663f577698a8b117f9252f8cd6ac83d171f79b8 Mon Sep 17 00:00:00 2001 From: AlexanderIvrii Date: Thu, 27 Jun 2024 14:10:56 +0300 Subject: [PATCH] attempt to port quantum_causal_cone to rust --- crates/circuit/src/dag_circuit.rs | 172 +++++++++++++++++++----------- 1 file changed, 111 insertions(+), 61 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index d3131cdfacc6..7a869644415b 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -10,6 +10,7 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use std::collections::VecDeque; use crate::bit_data::BitData; use crate::circuit_instruction::PackedInstruction; use crate::circuit_instruction::{ @@ -2730,14 +2731,12 @@ 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> { - 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> = - filtered.unique().map(|i| self.get_node(py, i)).collect(); + #[pyo3(name = "quantum_predecessors")] + fn py_quantum_predecessors(&self, py: Python, node: &DAGNode) -> PyResult> { + let predecessors: PyResult> = self + .quantum_predecessors(node.node.unwrap()) + .map(|i| self.get_node(py, i)) + .collect(); Ok(PyTuple::new_bound(py, predecessors?) .into_any() .iter() @@ -2745,6 +2744,21 @@ def _format(operand): .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> { + let successors: PyResult> = 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> { @@ -2781,23 +2795,6 @@ def _format(operand): todo!() } - /// 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> { - 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> = - 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> { @@ -3137,42 +3134,75 @@ def _format(operand): /// /// Returns: /// Set[~qiskit.circuit.Qubit]: The set of qubits whose interactions affect ``qubit``. - fn quantum_causal_cone(&self, qubit: &Bound) -> PyResult> { - // # 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) -> PyResult> { + // 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 = + 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 = 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 !qubits_in_cone.is_disjoint(&HashSet::<&Qubit>::from_iter( + self.qargs_cache.intern(pred_packed.qubits_id).iter(), + )) { + queue.push_back(pred_index); + } + } + } + } + } + } + + let elements : Vec<_> = qubits_in_cone.iter().map(|&qubit| qubit.0.into_py(py)).collect(); + Ok(PySet::new_bound(py, &elements)?.unbind()) } /// Return a dictionary of circuit properties. @@ -3242,6 +3272,26 @@ impl DAGCircuit { } } + fn quantum_predecessors(&self, node: NodeIndex) -> impl Iterator + '_ { + 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 + '_ { + self.dag + .edges_directed(node, Outgoing) + .filter_map(|e| match e.weight() { + Wire::Qubit(_) => Some(e.source()), + _ => None, + }) + .unique() +} + fn topological_nodes(&self) -> PyResult> { let key = |node: NodeIndex| -> Result<(Option, Option), Infallible> { Ok(self.dag.node_weight(node).unwrap().key())