From 83b994d8a7cf8bab4e20bf95377e3e2cf22e13a6 Mon Sep 17 00:00:00 2001 From: matt-ryley-Quant Date: Thu, 21 May 2026 12:00:33 +0100 Subject: [PATCH 1/2] test: Some basic trait tests added for qubits/traits --- zixy/src/container/word_iters/_word_iters.rs | 2 +- zixy/src/container/word_iters/term_set.rs | 2 +- zixy/src/qubit/traits.rs | 147 ++++++++++++++++++- 3 files changed, 144 insertions(+), 7 deletions(-) diff --git a/zixy/src/container/word_iters/_word_iters.rs b/zixy/src/container/word_iters/_word_iters.rs index 45ae010..d9a3995 100644 --- a/zixy/src/container/word_iters/_word_iters.rs +++ b/zixy/src/container/word_iters/_word_iters.rs @@ -567,7 +567,7 @@ pub mod test_defs { } #[cfg(test)] -pub mod tests { +mod tests { use crate::container::quicksort::QuickSortNoCoeffs; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; diff --git a/zixy/src/container/word_iters/term_set.rs b/zixy/src/container/word_iters/term_set.rs index b567d05..f4ffa01 100644 --- a/zixy/src/container/word_iters/term_set.rs +++ b/zixy/src/container/word_iters/term_set.rs @@ -761,7 +761,7 @@ pub mod test_defs { } #[cfg(test)] -pub mod tests { +mod tests { use super::test_defs::StringCmpnts; use crate::container::{ traits::EmptyFrom, diff --git a/zixy/src/qubit/traits.rs b/zixy/src/qubit/traits.rs index c88e585..36f9102 100644 --- a/zixy/src/qubit/traits.rs +++ b/zixy/src/qubit/traits.rs @@ -190,11 +190,7 @@ pub trait PauliWordRef: QubitsBased + Display { fn get_pauli_unchecked(&self, i_mode: usize) -> PauliMatrix; /// Return the Pauli matrix at `i_mode`, or `OutOfBounds` if `i_mode` is invalid. fn get_pauli(&self, i_mode: usize) -> Result { - OutOfBounds::check( - i_mode, - self.get_container().qubits().len(), - Dimension::Cmpnt, - )?; + OutOfBounds::check(i_mode, self.get_container().qubits().len(), Dimension::Mode)?; Ok(self.get_pauli_unchecked(i_mode)) } @@ -329,6 +325,7 @@ pub trait PauliWordMutRef: QubitsBased { /// Assign from a vector of Pauli matrices, with bounds checking on the highest index used. fn set_pauli_vec(&mut self, paulis: Vec) -> Result<(), OutOfBounds> { + // TODO: For a zero-qubit pauli word this fails. It would feel more intuitive if it was a no-op instead. OutOfBounds::check( paulis.len().saturating_sub(1), self.qubits().len(), @@ -508,3 +505,143 @@ pub trait PauliWordMutRef: QubitsBased { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Clone)] + struct TestContainer { + qubits: Qubits, + calls: Vec<&'static str>, + } + + impl QubitsBased for TestContainer { + fn qubits(&self) -> &Qubits { + &self.qubits + } + } + + impl proj::ToOwned for TestContainer { + type OwnedType = Self; + fn to_owned(&self) -> Self { + self.clone() + } + } + + impl QubitsStandardized for TestContainer {} + + impl QubitsStandardize for TestContainer { + fn general_standardize(&mut self, n_qubit: usize) { + self.calls.push("general"); + self.qubits = Qubits::from_count(n_qubit); + } + + fn resize_standardize(&mut self, n_qubit: usize) { + self.calls.push("resize"); + self.qubits = Qubits::from_count(n_qubit); + } + } + + impl QubitsRelabel for TestContainer { + fn qubits_mut(&mut self) -> &mut Qubits { + &mut self.qubits + } + } + + #[test] + fn test_standardize_calls() { + let mut test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + + test.standardize(5); + assert_eq!(test.calls, vec!["resize"]); + assert_eq!(test.qubits, Qubits::from_count(5)); + } + + #[test] + fn test_standardize_uses_general() { + let mut test = TestContainer { + qubits: Qubits::from_offset(2, 3), + calls: vec![], + }; + + test.standardize(3); + assert_eq!(test.calls, vec!["general"]); + assert_eq!(test.qubits, Qubits::from_count(3)); + } + + #[test] + fn test_standardize_no_op() { + let mut test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + + test.standardize(3); + assert!(test.calls.is_empty()); + assert_eq!(test.qubits, Qubits::from_count(3)); + } + + #[test] + fn test_general_standardized() { + let test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + + let standardized = test.general_standardized(4); + assert!(test.calls.is_empty()); + assert_eq!(standardized.calls, vec!["general"]); + assert_eq!(standardized.qubits, Qubits::from_count(4)); + } + + #[test] + fn test_resize_standardized() { + let test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + let standardized = test.resize_standardized(2); + assert!(test.calls.is_empty()); + assert_eq!(standardized.calls, vec!["resize"]); + assert_eq!(standardized.qubits, Qubits::from_count(2)); + assert_eq!(test.qubits, Qubits::from_count(3)); + } + + #[test] + fn test_push_standardized() { + let test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + let standardized = test.push_standardized(); + assert!(test.calls.is_empty()); + assert_eq!(standardized.calls, vec!["resize"]); + assert_eq!(standardized.qubits, Qubits::from_count(4)); + assert_eq!(test.qubits, Qubits::from_count(3)); + } + + #[test] + fn test_relabelled() -> Result<(), BasisError> { + let mut test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + test.relabel(Qubits::from_offset(4, 3))?; + assert_eq!(test.qubits, Qubits::from_offset(4, 3)); + Ok(()) + } + + #[test] + fn test_relabelled_invalid() { + let mut test = TestContainer { + qubits: Qubits::from_count(3), + calls: vec![], + }; + let result = test.relabel(Qubits::from_count(4)); + assert!(matches!(result, Err(BasisError::Counts(_)))); + } +} From 778bcba0ecc789612f8251d81b61f2c124485a64 Mon Sep 17 00:00:00 2001 From: matt-ryley-Quant Date: Thu, 21 May 2026 14:53:55 +0100 Subject: [PATCH 2/2] test: added PauliWordRef and PauliWordMutRef trait tests --- zixy/src/qubit/traits.rs | 170 +++++++++++++++++++++++++++++++-------- 1 file changed, 136 insertions(+), 34 deletions(-) diff --git a/zixy/src/qubit/traits.rs b/zixy/src/qubit/traits.rs index 36f9102..a4a4703 100644 --- a/zixy/src/qubit/traits.rs +++ b/zixy/src/qubit/traits.rs @@ -325,7 +325,6 @@ pub trait PauliWordMutRef: QubitsBased { /// Assign from a vector of Pauli matrices, with bounds checking on the highest index used. fn set_pauli_vec(&mut self, paulis: Vec) -> Result<(), OutOfBounds> { - // TODO: For a zero-qubit pauli word this fails. It would feel more intuitive if it was a no-op instead. OutOfBounds::check( paulis.len().saturating_sub(1), self.qubits().len(), @@ -508,6 +507,8 @@ pub trait PauliWordMutRef: QubitsBased { #[cfg(test)] mod tests { + use core::fmt; + use super::*; #[derive(Clone)] @@ -516,6 +517,23 @@ mod tests { calls: Vec<&'static str>, } + impl TestContainer { + fn new(qubits: Qubits) -> Self { + Self { + qubits, + calls: vec![], + } + } + + fn new_from_count(n: usize) -> Self { + Self::new(Qubits::from_count(n)) + } + + fn new_from_offset(i: usize, n: usize) -> Self { + Self::new(Qubits::from_offset(i, n)) + } + } + impl QubitsBased for TestContainer { fn qubits(&self) -> &Qubits { &self.qubits @@ -549,12 +567,64 @@ mod tests { } } + struct TestPauliWord { + qubits: Qubits, + paulis: Vec, + } + + impl TestPauliWord { + fn new(paulis: Vec) -> Self { + Self { + qubits: Qubits::from_count(paulis.len()), + paulis, + } + } + } + + impl QubitsBased for TestPauliWord { + fn qubits(&self) -> &Qubits { + &self.qubits + } + } + + impl PauliWordMutRef for TestPauliWord { + fn set_pauli_unchecked(&mut self, i_mode: usize, pauli: PauliMatrix) { + self.paulis[i_mode] = pauli; + } + + fn get_pauli_unchecked(&self, i_mode: usize) -> PauliMatrix { + self.paulis[i_mode] + } + } + + impl fmt::Display for TestPauliWord { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "TestPauliWord with qubits {:?} and paulis {:?}", + self.qubits, self.paulis + ) + } + } + + impl PauliWordRef for TestPauliWord { + type T = Self; + fn get_container(&self) -> &Self::T { + self + } + + fn get_pauli_unchecked(&self, i_mode: usize) -> PauliMatrix { + self.paulis[i_mode] + } + + fn count(&self, pauli: PauliMatrix) -> usize { + self.paulis.iter().filter(|&&p| p == pauli).count() + } + } + #[test] fn test_standardize_calls() { - let mut test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + let mut test = TestContainer::new_from_count(2); test.standardize(5); assert_eq!(test.calls, vec!["resize"]); @@ -563,10 +633,7 @@ mod tests { #[test] fn test_standardize_uses_general() { - let mut test = TestContainer { - qubits: Qubits::from_offset(2, 3), - calls: vec![], - }; + let mut test = TestContainer::new_from_offset(2, 3); test.standardize(3); assert_eq!(test.calls, vec!["general"]); @@ -575,10 +642,7 @@ mod tests { #[test] fn test_standardize_no_op() { - let mut test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + let mut test = TestContainer::new_from_count(3); test.standardize(3); assert!(test.calls.is_empty()); @@ -587,10 +651,7 @@ mod tests { #[test] fn test_general_standardized() { - let test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + let test = TestContainer::new_from_count(3); let standardized = test.general_standardized(4); assert!(test.calls.is_empty()); @@ -600,10 +661,7 @@ mod tests { #[test] fn test_resize_standardized() { - let test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + let test = TestContainer::new_from_count(3); let standardized = test.resize_standardized(2); assert!(test.calls.is_empty()); assert_eq!(standardized.calls, vec!["resize"]); @@ -613,10 +671,7 @@ mod tests { #[test] fn test_push_standardized() { - let test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + let test = TestContainer::new_from_count(3); let standardized = test.push_standardized(); assert!(test.calls.is_empty()); assert_eq!(standardized.calls, vec!["resize"]); @@ -626,22 +681,69 @@ mod tests { #[test] fn test_relabelled() -> Result<(), BasisError> { - let mut test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + let mut test = TestContainer::new_from_count(3); test.relabel(Qubits::from_offset(4, 3))?; assert_eq!(test.qubits, Qubits::from_offset(4, 3)); Ok(()) } #[test] - fn test_relabelled_invalid() { - let mut test = TestContainer { - qubits: Qubits::from_count(3), - calls: vec![], - }; + fn test_relabel_invalid() { + let mut test = TestContainer::new_from_count(3); let result = test.relabel(Qubits::from_count(4)); assert!(matches!(result, Err(BasisError::Counts(_)))); + assert_eq!(test.qubits, Qubits::from_count(3)); + } + + #[test] + fn test_pauliword_clear() { + let mut test = TestPauliWord::new(vec![ + PauliMatrix::X, + PauliMatrix::Y, + PauliMatrix::Z, + PauliMatrix::I, + ]); + test.clear(); + assert_eq!(test.paulis, vec![PauliMatrix::I; 4]); + } + + #[test] + fn test_pauliword_map() { + let test = TestPauliWord::new(vec![ + PauliMatrix::X, + PauliMatrix::Y, + PauliMatrix::I, + PauliMatrix::X, + PauliMatrix::I, + ]); + let map = test.get_pauli_map(); + assert_eq!(map.len(), 3); + assert_eq!(map[&0], PauliMatrix::X); + assert_eq!(map[&3], PauliMatrix::X); + assert_eq!(map[&1], PauliMatrix::Y); + } + + #[test] + fn test_setpaulimap() -> Result<(), OutOfBounds> { + let mut test = TestPauliWord::new(vec![PauliMatrix::I; 3]); + let mut map = HashMap::new(); + map.insert(1, PauliMatrix::X); + map.insert(2, PauliMatrix::Y); + test.set_pauli_map(map)?; + assert_eq!( + test.paulis, + vec![PauliMatrix::I, PauliMatrix::X, PauliMatrix::Y] + ); + Ok(()) + } + + #[test] + fn test_setpaulimap_invalid() { + let mut test = TestPauliWord::new(vec![PauliMatrix::I; 3]); + let mut map = HashMap::new(); + map.insert(100, PauliMatrix::Y); // invalid for word of length 3 + let result = test.set_pauli_map(map); + assert!(result.is_err()); + assert_eq!(test.paulis, vec![PauliMatrix::I; 3]); } }