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 1/5] 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) => ( From 4e0b9f731ed97a6b1e51442d487b997fe1feb3fc Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:01:20 -0400 Subject: [PATCH 2/5] Fix: Remove extra blank space --- crates/circuit/src/dag_circuit.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index f9bb51904241..3bdbf47a38ce 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5257,7 +5257,6 @@ impl DAGCircuit { Ok(new_node) } - /// Apply a [PackedOperation] to the back of the circuit. #[allow(clippy::too_many_arguments)] pub fn apply_operation_back( From 855796de88b99cda4a27c997f16dec322b1ea136 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Fri, 13 Sep 2024 11:32:50 -0400 Subject: [PATCH 3/5] Fix: Revert changes to python `apply_op` methods. - To avoid an ownership issue, usage of the new methods from the Python variants has been removed. - As requested, the insertion check in the rust `apply_op` has been removed. --- crates/circuit/src/dag_circuit.rs | 90 +++++++++++++++---------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 3bdbf47a38ce..ded20a6d92f1 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -1682,25 +1682,28 @@ 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(); - - 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, - )?; + if check { + self.check_op_addition(py, &instr)?; + } + self.push_back(py, instr)? + }; self.get_node(py, node) } @@ -1733,24 +1736,28 @@ def _format(operand): let py_op = op.extract::()?; let qargs = qargs.map(|q| q.value); let cargs = cargs.map(|c| c.value); - // 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 = { + 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(), + }; - 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, - )?; + if check { + self.check_op_addition(py, &instr)?; + } + self.push_front(py, instr)? + }; self.get_node(py, node) } @@ -5268,7 +5275,6 @@ impl DAGCircuit { params: Option>, extra_attrs: Option>, #[cfg(feature = "cache_pygates")] py_op: Option, - check: bool, ) -> PyResult { self.inner_apply_op( py, @@ -5279,7 +5285,6 @@ impl DAGCircuit { extra_attrs, #[cfg(feature = "cache_pygates")] py_op, - check, false, ) } @@ -5295,7 +5300,6 @@ impl DAGCircuit { params: Option>, extra_attrs: Option>, #[cfg(feature = "cache_pygates")] py_op: Option, - check: bool, ) -> PyResult { self.inner_apply_op( py, @@ -5306,11 +5310,11 @@ impl DAGCircuit { extra_attrs, #[cfg(feature = "cache_pygates")] py_op, - check, true, ) } + #[inline] #[allow(clippy::too_many_arguments)] fn inner_apply_op( &mut self, @@ -5321,12 +5325,11 @@ impl DAGCircuit { 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) { + if qarg.0 as usize >= self.num_qubits() { return Err(PyValueError::new_err(format!( "Qubit index {} is out of range. This DAGCircuit currently has only {} qubits.", qarg.0, @@ -5338,7 +5341,7 @@ impl DAGCircuit { // 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) { + if carg.0 as usize >= self.num_clbits() { return Err(PyValueError::new_err(format!( "Clbit index {} is out of range. This DAGCircuit currently has only {} clbits.", carg.0, @@ -5363,9 +5366,6 @@ impl DAGCircuit { #[cfg(feature = "cache_pygates")] py_op, }; - if check { - self.check_op_addition(py, &packed_instruction)?; - } if front { self.push_front(py, packed_instruction) From 587874ea9cfc0d7de9a5ba39bfc7769fc6d8e64f Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Mon, 16 Sep 2024 09:33:28 -0400 Subject: [PATCH 4/5] Fix: Adapt to new `ExtraInstructionProperties` infrastructure. --- crates/circuit/src/dag_circuit.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 74776c23dc7b..4711c697971c 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5194,7 +5194,6 @@ impl DAGCircuit { } /// Apply a [PackedOperation] to the back of the circuit. - #[allow(clippy::too_many_arguments)] pub fn apply_operation_back( &mut self, py: Python, @@ -5202,7 +5201,7 @@ impl DAGCircuit { qargs: &[Qubit], cargs: &[Clbit], params: Option>, - extra_attrs: Option>, + extra_attrs: ExtraInstructionAttributes, #[cfg(feature = "cache_pygates")] py_op: Option, ) -> PyResult { self.inner_apply_op( @@ -5219,7 +5218,6 @@ impl DAGCircuit { } /// Apply a [PackedOperation] to the front of the circuit. - #[allow(clippy::too_many_arguments)] pub fn apply_operation_front( &mut self, py: Python, @@ -5227,7 +5225,7 @@ impl DAGCircuit { qargs: &[Qubit], cargs: &[Clbit], params: Option>, - extra_attrs: Option>, + extra_attrs: ExtraInstructionAttributes, #[cfg(feature = "cache_pygates")] py_op: Option, ) -> PyResult { self.inner_apply_op( @@ -5252,7 +5250,7 @@ impl DAGCircuit { qargs: &[Qubit], cargs: &[Clbit], params: Option>, - extra_attrs: Option>, + extra_attrs: ExtraInstructionAttributes, #[cfg(feature = "cache_pygates")] py_op: Option, front: bool, ) -> PyResult { From 35c79d6b794f08445d6e0e613529878c017120cf Mon Sep 17 00:00:00 2001 From: Raynel Sanchez Date: Mon, 16 Sep 2024 10:53:58 -0400 Subject: [PATCH 5/5] Format: Change redundant iteration statement for params --- crates/circuit/src/dag_circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/circuit/src/dag_circuit.rs b/crates/circuit/src/dag_circuit.rs index 4711c697971c..54f270b30558 100644 --- a/crates/circuit/src/dag_circuit.rs +++ b/crates/circuit/src/dag_circuit.rs @@ -5288,7 +5288,7 @@ impl DAGCircuit { op, qubits: self.qargs_interner.insert(qargs), clbits: self.cargs_interner.insert(cargs), - params: params.map(|params: SmallVec<[Param; 3]>| Box::new(params)), + params: params.map(Box::new), extra_attrs, #[cfg(feature = "cache_pygates")] py_op,