From 8625f747d21c5c269fd328bd9485baee3751ca92 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 12 Sep 2024 13:40:04 -0400 Subject: [PATCH] Initial: Add rust-native `apply_operation_front` and `apply_operation_back methods. - These methods allow rust users to add an operation to either the front or the back of the circuit without needing to have valid interned indices for the Qargs/Qargs of said operations. - Leverage the new methods in the existing python variants of them. --- crates/circuit/src/dag_circuit.rs | 198 +++++++++++++++++++++++------- 1 file changed, 155 insertions(+), 43 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index d872806c5315..f9bb51904241 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -13,6 +13,7 @@ use std::hash::{Hash, Hasher}; use ahash::RandomState; +use smallvec::SmallVec; use crate::bit_data::BitData; use crate::circuit_data::CircuitData; @@ -26,7 +27,7 @@ use crate::error::DAGCircuitError; use crate::imports; use crate::interner::{Interned, Interner}; use crate::operations::{Operation, OperationRef, Param, PyInstruction, StandardGate}; -use crate::packed_instruction::PackedInstruction; +use crate::packed_instruction::{PackedInstruction, PackedOperation}; use crate::rustworkx_core_vnext::isomorphism; use crate::{BitType, Clbit, Qubit, TupleLikeArg}; @@ -1681,28 +1682,25 @@ def _format(operand): let py_op = op.extract::()?; let qargs = qargs.map(|q| q.value); let cargs = cargs.map(|c| c.value); - let node = { - let qubits_id = self - .qargs_interner - .insert_owned(self.qubits.map_bits(qargs.iter().flatten())?.collect()); - let clbits_id = self - .cargs_interner - .insert_owned(self.clbits.map_bits(cargs.iter().flatten())?.collect()); - let instr = PackedInstruction { - op: py_op.operation, - qubits: qubits_id, - clbits: clbits_id, - params: (!py_op.params.is_empty()).then(|| Box::new(py_op.params)), - extra_attrs: py_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: op.unbind().into(), - }; - if check { - self.check_op_addition(py, &instr)?; - } - self.push_back(py, instr)? - }; + // Map the qargs to is respective indices + let qarg_indices: Vec = self.qubits.map_bits(qargs.iter().flatten())?.collect(); + // Map the qargs to is respective indices + let carg_indices: Vec = self.clbits.map_bits(cargs.iter().flatten())?.collect(); + + let params = (!py_op.params.is_empty()).then_some(py_op.params); + + let node = self.apply_operation_back( + py, + py_op.operation, + &qarg_indices, + &carg_indices, + params, + py_op.extra_attrs, + #[cfg(feature = "cache_pygates")] + Some(op.unbind()), + check, + )?; self.get_node(py, node) } @@ -1735,28 +1733,24 @@ def _format(operand): let py_op = op.extract::()?; let qargs = qargs.map(|q| q.value); let cargs = cargs.map(|c| c.value); - let node = { - let qubits_id = self - .qargs_interner - .insert_owned(self.qubits.map_bits(qargs.iter().flatten())?.collect()); - let clbits_id = self - .cargs_interner - .insert_owned(self.clbits.map_bits(cargs.iter().flatten())?.collect()); - let instr = PackedInstruction { - op: py_op.operation, - qubits: qubits_id, - clbits: clbits_id, - params: (!py_op.params.is_empty()).then(|| Box::new(py_op.params)), - extra_attrs: py_op.extra_attrs, - #[cfg(feature = "cache_pygates")] - py_op: op.unbind().into(), - }; + // Map the qargs to is respective indices + let qarg_indices: Vec = self.qubits.map_bits(qargs.iter().flatten())?.collect(); + // Map the qargs to is respective indices + let carg_indices: Vec = self.clbits.map_bits(cargs.iter().flatten())?.collect(); - if check { - self.check_op_addition(py, &instr)?; - } - self.push_front(py, instr)? - }; + let params = (!py_op.params.is_empty()).then_some(py_op.params); + + let node = self.apply_operation_front( + py, + py_op.operation, + &qarg_indices, + &carg_indices, + params, + py_op.extra_attrs, + #[cfg(feature = "cache_pygates")] + Some(op.unbind()), + check, + )?; self.get_node(py, node) } @@ -5263,6 +5257,124 @@ impl DAGCircuit { Ok(new_node) } + + /// Apply a [PackedOperation] to the back of the circuit. + #[allow(clippy::too_many_arguments)] + pub fn apply_operation_back( + &mut self, + py: Python, + op: PackedOperation, + qargs: &[Qubit], + cargs: &[Clbit], + params: Option>, + extra_attrs: Option>, + #[cfg(feature = "cache_pygates")] py_op: Option, + check: bool, + ) -> PyResult { + self.inner_apply_op( + py, + op, + qargs, + cargs, + params, + extra_attrs, + #[cfg(feature = "cache_pygates")] + py_op, + check, + false, + ) + } + + /// Apply a [PackedOperation] to the front of the circuit. + #[allow(clippy::too_many_arguments)] + pub fn apply_operation_front( + &mut self, + py: Python, + op: PackedOperation, + qargs: &[Qubit], + cargs: &[Clbit], + params: Option>, + extra_attrs: Option>, + #[cfg(feature = "cache_pygates")] py_op: Option, + check: bool, + ) -> PyResult { + self.inner_apply_op( + py, + op, + qargs, + cargs, + params, + extra_attrs, + #[cfg(feature = "cache_pygates")] + py_op, + check, + true, + ) + } + + #[allow(clippy::too_many_arguments)] + fn inner_apply_op( + &mut self, + py: Python, + op: PackedOperation, + qargs: &[Qubit], + cargs: &[Clbit], + params: Option>, + extra_attrs: Option>, + #[cfg(feature = "cache_pygates")] py_op: Option, + check: bool, + front: bool, + ) -> PyResult { + // Check that all qargs are within an acceptable range + qargs.iter().try_for_each(|qarg| { + if !(0..self.num_qubits() as u32).contains(&qarg.0) { + return Err(PyValueError::new_err(format!( + "Qubit index {} is out of range. This DAGCircuit currently has only {} qubits.", + qarg.0, + self.num_qubits() + ))); + } + Ok(()) + })?; + + // Check that all cargs are within an acceptable range + cargs.iter().try_for_each(|carg| { + if !(0..self.num_clbits() as u32).contains(&carg.0) { + return Err(PyValueError::new_err(format!( + "Clbit index {} is out of range. This DAGCircuit currently has only {} clbits.", + carg.0, + self.num_clbits() + ))); + } + Ok(()) + })?; + + #[cfg(feature = "cache_pygates")] + let py_op = if let Some(py_op) = py_op { + py_op.into() + } else { + OnceCell::new() + }; + let packed_instruction = PackedInstruction { + op, + qubits: self.qargs_interner.insert(qargs), + clbits: self.cargs_interner.insert(cargs), + params: params.map(|params: SmallVec<[Param; 3]>| Box::new(params)), + extra_attrs, + #[cfg(feature = "cache_pygates")] + py_op, + }; + if check { + self.check_op_addition(py, &packed_instruction)?; + } + + if front { + self.push_front(py, packed_instruction) + } else { + self.push_back(py, packed_instruction) + } + } + fn sort_key(&self, node: NodeIndex) -> SortKeyType { match &self.dag[node] { NodeType::Operation(packed) => (