From f08d8e2bfebc96eefd3544e5998b7fa36917367e Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 12:53:44 +0100 Subject: [PATCH 01/13] Implement algorithm to find and then remove repeated terms in PauliPolynomial --- synir/src/data_structures/pauli_polynomial.rs | 3 + .../pauli_polynomial/simplify.rs | 156 ++++++++++++++++++ 2 files changed, 159 insertions(+) create mode 100644 synir/src/data_structures/pauli_polynomial/simplify.rs diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index ff38897b..304ef769 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -5,6 +5,8 @@ use itertools::zip_eq; use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; +mod simplify; + // todo: Make this into a union / type Angle type Angle = f64; @@ -160,6 +162,7 @@ impl MaskedPropagateClifford for PauliPolynomial { self } } + #[cfg(test)] mod tests { use super::*; diff --git a/synir/src/data_structures/pauli_polynomial/simplify.rs b/synir/src/data_structures/pauli_polynomial/simplify.rs new file mode 100644 index 00000000..f2121103 --- /dev/null +++ b/synir/src/data_structures/pauli_polynomial/simplify.rs @@ -0,0 +1,156 @@ +use crate::data_structures::PauliPolynomial; +use itertools::Itertools; +use std::collections::HashMap; + +pub fn check_repeats(pp: &PauliPolynomial) -> Vec<(usize, Vec)> { + let size = pp.size(); + let length = pp.length(); + let mut repeats = HashMap::>::new(); + for index in 0..length { + let mut num = 0; + for letter in 0..size { + num += (pp.chain(letter).x(index) as usize) << 2 * letter; + num += (pp.chain(letter).z(index) as usize) << 2 * letter + 1; + } + repeats + .entry(num) + .and_modify(|e: &mut Vec| e.push(index)) + .or_insert(vec![index]); + } + repeats + .into_iter() + .filter(|(_, v)| v.len() > 1) + .sorted() + .collect_vec() +} + +pub fn merge_repeats( + mut pp: PauliPolynomial, + merge_list: Vec<(usize, Vec)>, +) -> PauliPolynomial { + let mut pp_merge_list = Vec::::new(); + // merge all the angles first + for (_, angle_merge_list) in merge_list { + let merge_index = angle_merge_list[0]; + let mut angle = pp.angle(merge_index); + for angle_index in angle_merge_list.iter().skip(1) { + angle += pp.angle(*angle_index); + } + pp.angles[merge_index] = angle; + pp_merge_list.extend_from_slice(&angle_merge_list[1..]); + } + // remove duplicate entries + pp_merge_list.sort_by(|a, b| b.cmp(a)); + for remove_index in pp_merge_list { + pp.angles.remove(remove_index); + for chain_index in 0..pp.chains().len() { + pp.chains[chain_index].x.remove(remove_index); + pp.chains[chain_index].z.remove(remove_index); + } + } + pp +} + +#[cfg(test)] +mod tests { + use crate::data_structures::PauliString; + + use super::*; + + #[test] + fn test_simple_check_repeats() { + // Combined reading from back -> 01 = 1 + let pp = PauliPolynomial::from_hamiltonian(vec![ + ("I", 1.0), + ("X", 2.0), + ("Z", 3.0), + ("X", 4.0), + ("X", 5.0), + ]); + let repeats = check_repeats(&pp); + assert!(repeats.len() == 1); + assert_eq!(repeats, vec![(1, vec![1, 3, 4])]); + } + + #[test] + fn test_check_repeats() { + // XIZY appears twice + // Z string -> 0011 + // X string -> 1001 + // Combined reading from back -> 11 10 00 01 = 225 + let pp = + PauliPolynomial::from_hamiltonian(vec![("XIZY", 1.0), ("XIZY", 2.0), ("YZZI", 3.0)]); + let repeats = check_repeats(&pp); + assert!(repeats.len() == 1); + assert_eq!(repeats, vec![(225, vec![0, 1])]); + } + + #[test] + fn test_multiple_repeats() { + // Combined reading from back -> 01 = 1 + let pp = PauliPolynomial::from_hamiltonian(vec![ + ("II", 1.0), + ("IX", 2.0), + ("ZZ", 3.0), + ("IX", 4.0), + ("ZZ", 5.0), + ]); + let repeats = check_repeats(&pp); + assert!(repeats.len() == 2); + assert_eq!(repeats, vec![(4, vec![1, 3]), (10, vec![2, 4])]); + } + + #[test] + fn test_simple_merge_repeats() { + // Combined reading from back -> 01 = 1 + let pp = PauliPolynomial::from_hamiltonian(vec![ + ("I", 1.0), + ("X", 2.0), + ("Z", 3.0), + ("X", 4.0), + ("X", 5.0), + ]); + let repeats = check_repeats(&pp); + + let pp = merge_repeats(pp, repeats); + + assert!(pp.chain(0).len() == 3); + assert_eq!(pp.chain(0), &PauliString::from_text("IXZ")); + assert_eq!(pp.angles, &[1.0, 11.0, 3.0]); + } + + #[test] + fn test_merge_repeats() { + let pp = + PauliPolynomial::from_hamiltonian(vec![("XIZY", 1.0), ("XIZY", 2.0), ("YZZI", 3.0)]); + let repeats = check_repeats(&pp); + let pp = merge_repeats(pp, repeats); + + assert!(pp.chain(0).len() == 2); + assert_eq!(pp.chain(0), &PauliString::from_text("XY")); + assert_eq!(pp.chain(1), &PauliString::from_text("IZ")); + assert_eq!(pp.chain(2), &PauliString::from_text("ZZ")); + assert_eq!(pp.chain(3), &PauliString::from_text("YI")); + assert_eq!(pp.angles, &[3.0, 3.0]); + } + + #[test] + fn test_multiple_merge_repeats() { + // Combined reading from back -> 01 = 1 + let pp = PauliPolynomial::from_hamiltonian(vec![ + ("II", 1.0), + ("IX", 2.0), + ("ZZ", 3.0), + ("IX", 4.0), + ("ZZ", 5.0), + ]); + + let repeats = check_repeats(&pp); + let pp = merge_repeats(pp, repeats); + + assert!(pp.chain(0).len() == 3); + assert_eq!(pp.chain(0), &PauliString::from_text("IIZ")); + assert_eq!(pp.chain(1), &PauliString::from_text("IXZ")); + assert_eq!(pp.angles, &[1.0, 6.0, 8.0]); + } +} From c44b791da18fdcad801b534f969d285c33c348e4 Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 13:57:37 +0100 Subject: [PATCH 02/13] Introduce the new Angle enum type that allows for radians and pi4 rotations --- synir/src/data_structures.rs | 4 +- synir/src/data_structures/angle.rs | 100 ++++++++++++++++++ synir/src/data_structures/pauli_polynomial.rs | 65 ++++++------ .../pauli_polynomial/simplify.rs | 61 ++++++----- synir/src/ir/pauli_polynomial/helper.rs | 2 +- synir/tests/pauli_exponential.rs | 9 +- synir/tests/pauli_polynomial.rs | 7 +- synpy/src/synthesis.rs | 11 +- 8 files changed, 193 insertions(+), 66 deletions(-) create mode 100644 synir/src/data_structures/angle.rs diff --git a/synir/src/data_structures.rs b/synir/src/data_structures.rs index 98aade8b..8ede657d 100644 --- a/synir/src/data_structures.rs +++ b/synir/src/data_structures.rs @@ -1,10 +1,12 @@ use crate::IndexType; +use bitvec::vec::BitVec; +pub mod angle; mod clifford_tableau; mod pauli_polynomial; mod pauli_string; -use bitvec::vec::BitVec; +pub use angle::Angle; pub use clifford_tableau::CliffordTableau; pub use pauli_polynomial::PauliPolynomial; pub use pauli_string::PauliString; diff --git a/synir/src/data_structures/angle.rs b/synir/src/data_structures/angle.rs new file mode 100644 index 00000000..862a2a16 --- /dev/null +++ b/synir/src/data_structures/angle.rs @@ -0,0 +1,100 @@ +use std::ops::{AddAssign, SubAssign}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Angle { + Angle(f64), + Pi4Rotations(usize), +} + +impl Angle { + pub fn from_angle(rad: f64) -> Self { + Angle::Angle(rad) + } + + pub fn from_angles(angles: &[f64]) -> Vec { + angles + .into_iter() + .map(|rad| Angle::from_angle(*rad)) + .collect() + } + + pub fn from_pi4_rotations(n: usize) -> Self { + Angle::Pi4Rotations(n % 8) + } + + pub fn forpi4_rotations(ns: &[usize]) -> Vec { + ns.into_iter() + .map(|n| Angle::from_pi4_rotations(*n)) + .collect() + } + + pub fn to_radians(&self) -> f64 { + match self { + Angle::Angle(rad) => *rad, + Angle::Pi4Rotations(n) => (*n as f64) * (std::f64::consts::FRAC_PI_4), + } + } + + pub fn flip(&mut self) { + match self { + Angle::Angle(rad) => *rad = -*rad, + Angle::Pi4Rotations(n) => *n = (8 - *n) % 8, + } + } +} + +impl AddAssign for Angle { + fn add_assign(&mut self, other: Self) { + match (self, other) { + (Angle::Angle(rad1), Angle::Angle(rad2)) => { + *rad1 += rad2; + } + (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { + *n1 = (*n1 + n2) % 8; + } + _ => panic!("Cannot add different types of Angles"), + } + } +} + +impl SubAssign for Angle { + fn sub_assign(&mut self, other: Self) { + match (self, other) { + (Angle::Angle(rad1), Angle::Angle(rad2)) => { + *rad1 -= rad2; + } + (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { + *n1 = (*n1 + (8 - n2)) % 8; + } + _ => panic!("Cannot subtract different types of Angles"), + } + } +} + +impl std::ops::Add for Angle { + type Output = Angle; + + fn add(self, other: Angle) -> Angle { + match (self, other) { + (Angle::Angle(rad1), Angle::Angle(rad2)) => Angle::Angle(rad1 + rad2), + (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { + Angle::Pi4Rotations((n1 + n2) % 8) + } + _ => panic!("Cannot add different types of Angles"), + } + } +} + +impl std::ops::Sub for Angle { + type Output = Angle; + + fn sub(self, other: Angle) -> Angle { + match (self, other) { + (Angle::Angle(rad1), Angle::Angle(rad2)) => Angle::Angle(rad1 - rad2), + (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { + Angle::Pi4Rotations((n1 + 8 - n2) % 8) + } + _ => panic!("Cannot add different types of Angles"), + } + } +} diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index 304ef769..5276ab23 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -1,15 +1,12 @@ use std::iter::zip; +use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; +use crate::data_structures::Angle; use bitvec::vec::BitVec; use itertools::zip_eq; -use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; - mod simplify; -// todo: Make this into a union / type Angle -type Angle = f64; - #[derive(Debug, Clone, Default)] pub struct PauliPolynomial { chains: Vec, @@ -80,7 +77,7 @@ impl PropagateClifford for PauliPolynomial { super::pauli_string::cx(control, target); for (angle, flip) in zip(self.angles.iter_mut(), bit_mask.iter()) { if *flip { - *angle *= -1.0; + angle.flip(); } } @@ -93,7 +90,7 @@ impl PropagateClifford for PauliPolynomial { let y_vec = chains_target.y_bitmask(); for (angle, flip) in zip(self.angles.iter_mut(), y_vec.iter()) { if *flip { - *angle *= -1.0; + angle.flip(); } } chains_target.s(); @@ -107,7 +104,7 @@ impl PropagateClifford for PauliPolynomial { let y_vec = chains_target.y_bitmask(); for (angle, flip) in zip(self.angles.iter_mut(), y_vec.iter()) { if *flip { - *angle *= -1.0; + angle.flip(); } } self @@ -128,7 +125,7 @@ impl MaskedPropagateClifford for PauliPolynomial { super::pauli_string::masked_cx(control, target, mask); for (angle, flip) in zip(self.angles.iter_mut(), bit_mask.iter()) { if *flip { - *angle *= -1.0; + angle.flip(); } } @@ -142,7 +139,7 @@ impl MaskedPropagateClifford for PauliPolynomial { let y_vec = chains_target.masked_y_bitmask(mask); for (angle, flip) in zip(self.angles.iter_mut(), y_vec.iter()) { if *flip { - *angle *= -1.0; + angle.flip(); } } chains_target.masked_s(mask); @@ -156,7 +153,7 @@ impl MaskedPropagateClifford for PauliPolynomial { let y_vec = chains_target.masked_y_bitmask(mask); for (angle, flip) in zip(self.angles.iter_mut(), y_vec.iter()) { if *flip { - *angle *= -1.0; + angle.flip(); } } self @@ -176,7 +173,11 @@ mod tests { #[test] fn test_pauli_polynomial_constructor() { let size = 3; - let ham = vec![("IXYZ", 0.3), ("XXII", 0.7), ("YYII", 0.12)]; + let ham = vec![ + ("IXYZ", Angle::from_angle(0.3)), + ("XXII", Angle::from_angle(0.7)), + ("YYII", Angle::from_angle(0.12)), + ]; let pp = PauliPolynomial::from_hamiltonian(ham); let pg1_ref = PauliString::from_text("IXY"); @@ -184,7 +185,7 @@ mod tests { let pg3_ref = PauliString::from_text("YII"); let pg4_ref = PauliString::from_text("ZII"); - let angles_ref = vec![0.3, 0.7, 0.12]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref, pg4_ref], @@ -204,7 +205,11 @@ mod tests { #[test] #[should_panic] fn test_pauli_polynomial_constructor_unequal_strings() { - let ham = vec![("IXYZ", 0.3), ("XXI", 0.7), ("YYII", 0.12)]; + let ham = vec![ + ("IXYZ", Angle::from_angle(0.3)), + ("XXI", Angle::from_angle(0.7)), + ("YYII", Angle::from_angle(0.12)), + ]; let _ = PauliPolynomial::from_hamiltonian(ham); } @@ -213,7 +218,7 @@ mod tests { let pg1_ref = PauliString::from_text("IXY"); let pg2_ref = PauliString::from_text("ZYX"); let pg3_ref = PauliString::from_text("YIX"); - let angles_ref = vec![0.3, 0.7, 0.12]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12]); PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -239,7 +244,7 @@ mod tests { let pg2_ref = PauliString::from_text("ZXY"); // YIX let pg3_ref = PauliString::from_text("YIX"); - let angles_ref = vec![0.3, -0.7, -0.12]; + let angles_ref = Angle::from_angles(&[0.3, -0.7, -0.12]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -264,7 +269,7 @@ mod tests { let pg2_ref = PauliString::from_text("YZX"); // YIX -> ZIX let pg3_ref = PauliString::from_text("ZIX"); - let angles_ref = vec![-0.3, 0.7, 0.12]; + let angles_ref = Angle::from_angles(&[-0.3, 0.7, 0.12]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -289,7 +294,7 @@ mod tests { let pg2_ref = PauliString::from_text("ZXY"); // YIX -> XI(-Y) let pg3_ref = PauliString::from_text("XIY"); - let angles_ref = vec![0.3, 0.7, 0.12]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -314,7 +319,7 @@ mod tests { let pg2_ref = PauliString::from_text("YZX"); // YIX -> (-Z)IX let pg3_ref = PauliString::from_text("ZIX"); - let angles_ref = vec![-0.3, -0.7, 0.12]; + let angles_ref = Angle::from_angles(&[-0.3, -0.7, 0.12]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -339,7 +344,7 @@ mod tests { let pg2_ref = PauliString::from_text("XYZ"); // YIX - let pg3_ref = PauliString::from_text("YIX"); - let angles_ref = vec![0.3, -0.7, -0.12]; + let angles_ref = Angle::from_angles(&[0.3, -0.7, -0.12]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -361,7 +366,7 @@ mod tests { let pg2_ref = PauliString::from_text("IXYZ"); let pg3_ref = PauliString::from_text("YIXZ"); - let angles_ref = vec![0.3, 0.7, 0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], @@ -388,7 +393,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = vec![0.3, 0.7, 0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -414,7 +419,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, -1] - let angles_ref = vec![0.3, 0.7, 0.12, -0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, -0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -440,7 +445,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, -1, 1] - let angles_ref = vec![0.3, 0.7, -0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, -0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -466,7 +471,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = vec![0.3, 0.7, 0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -493,7 +498,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = vec![0.3, 0.7, 0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -519,7 +524,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, -1, 1] - let angles_ref = vec![0.3, 0.7, -0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, 0.7, -0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -545,7 +550,7 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, -1, 1, 1] - let angles_ref = vec![0.3, -0.7, 0.12, 0.15]; + let angles_ref = Angle::from_angles(&[0.3, -0.7, 0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -570,8 +575,8 @@ mod tests { let pg2_ref = PauliString::from_text("IXYZ"); // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); - // [1, 1, -1, 1] - let angles_ref = vec![0.3, 0.7, 0.12, 0.15]; + // [1, 1, 1, 1] + let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, diff --git a/synir/src/data_structures/pauli_polynomial/simplify.rs b/synir/src/data_structures/pauli_polynomial/simplify.rs index f2121103..c70ca9bd 100644 --- a/synir/src/data_structures/pauli_polynomial/simplify.rs +++ b/synir/src/data_structures/pauli_polynomial/simplify.rs @@ -53,6 +53,7 @@ pub fn merge_repeats( #[cfg(test)] mod tests { + use crate::data_structures::Angle; use crate::data_structures::PauliString; use super::*; @@ -61,11 +62,11 @@ mod tests { fn test_simple_check_repeats() { // Combined reading from back -> 01 = 1 let pp = PauliPolynomial::from_hamiltonian(vec![ - ("I", 1.0), - ("X", 2.0), - ("Z", 3.0), - ("X", 4.0), - ("X", 5.0), + ("I", Angle::from_angle(1.0)), + ("X", Angle::from_angle(2.0)), + ("Z", Angle::from_angle(3.0)), + ("X", Angle::from_angle(4.0)), + ("X", Angle::from_angle(5.0)), ]); let repeats = check_repeats(&pp); assert!(repeats.len() == 1); @@ -78,8 +79,11 @@ mod tests { // Z string -> 0011 // X string -> 1001 // Combined reading from back -> 11 10 00 01 = 225 - let pp = - PauliPolynomial::from_hamiltonian(vec![("XIZY", 1.0), ("XIZY", 2.0), ("YZZI", 3.0)]); + let pp = PauliPolynomial::from_hamiltonian(vec![ + ("XIZY", Angle::from_angle(1.0)), + ("XIZY", Angle::from_angle(2.0)), + ("YZZI", Angle::from_angle(3.0)), + ]); let repeats = check_repeats(&pp); assert!(repeats.len() == 1); assert_eq!(repeats, vec![(225, vec![0, 1])]); @@ -89,11 +93,11 @@ mod tests { fn test_multiple_repeats() { // Combined reading from back -> 01 = 1 let pp = PauliPolynomial::from_hamiltonian(vec![ - ("II", 1.0), - ("IX", 2.0), - ("ZZ", 3.0), - ("IX", 4.0), - ("ZZ", 5.0), + ("II", Angle::from_angle(1.0)), + ("IX", Angle::from_angle(2.0)), + ("ZZ", Angle::from_angle(3.0)), + ("IX", Angle::from_angle(4.0)), + ("ZZ", Angle::from_angle(5.0)), ]); let repeats = check_repeats(&pp); assert!(repeats.len() == 2); @@ -104,11 +108,11 @@ mod tests { fn test_simple_merge_repeats() { // Combined reading from back -> 01 = 1 let pp = PauliPolynomial::from_hamiltonian(vec![ - ("I", 1.0), - ("X", 2.0), - ("Z", 3.0), - ("X", 4.0), - ("X", 5.0), + ("I", Angle::from_angle(1.0)), + ("X", Angle::from_angle(2.0)), + ("Z", Angle::from_angle(3.0)), + ("X", Angle::from_angle(4.0)), + ("X", Angle::from_angle(5.0)), ]); let repeats = check_repeats(&pp); @@ -116,13 +120,16 @@ mod tests { assert!(pp.chain(0).len() == 3); assert_eq!(pp.chain(0), &PauliString::from_text("IXZ")); - assert_eq!(pp.angles, &[1.0, 11.0, 3.0]); + assert_eq!(pp.angles, Angle::from_angles(&[1.0, 11.0, 3.0])); } #[test] fn test_merge_repeats() { - let pp = - PauliPolynomial::from_hamiltonian(vec![("XIZY", 1.0), ("XIZY", 2.0), ("YZZI", 3.0)]); + let pp = PauliPolynomial::from_hamiltonian(vec![ + ("XIZY", Angle::from_angle(1.0)), + ("XIZY", Angle::from_angle(2.0)), + ("YZZI", Angle::from_angle(3.0)), + ]); let repeats = check_repeats(&pp); let pp = merge_repeats(pp, repeats); @@ -131,18 +138,18 @@ mod tests { assert_eq!(pp.chain(1), &PauliString::from_text("IZ")); assert_eq!(pp.chain(2), &PauliString::from_text("ZZ")); assert_eq!(pp.chain(3), &PauliString::from_text("YI")); - assert_eq!(pp.angles, &[3.0, 3.0]); + assert_eq!(pp.angles, Angle::from_angles(&[3.0, 3.0])); } #[test] fn test_multiple_merge_repeats() { // Combined reading from back -> 01 = 1 let pp = PauliPolynomial::from_hamiltonian(vec![ - ("II", 1.0), - ("IX", 2.0), - ("ZZ", 3.0), - ("IX", 4.0), - ("ZZ", 5.0), + ("II", Angle::from_angle(1.0)), + ("IX", Angle::from_angle(2.0)), + ("ZZ", Angle::from_angle(3.0)), + ("IX", Angle::from_angle(4.0)), + ("ZZ", Angle::from_angle(5.0)), ]); let repeats = check_repeats(&pp); @@ -151,6 +158,6 @@ mod tests { assert!(pp.chain(0).len() == 3); assert_eq!(pp.chain(0), &PauliString::from_text("IIZ")); assert_eq!(pp.chain(1), &PauliString::from_text("IXZ")); - assert_eq!(pp.angles, &[1.0, 6.0, 8.0]); + assert_eq!(pp.angles, Angle::from_angles(&[1.0, 6.0, 8.0])); } } diff --git a/synir/src/ir/pauli_polynomial/helper.rs b/synir/src/ir/pauli_polynomial/helper.rs index 127f5ad1..536a747b 100644 --- a/synir/src/ir/pauli_polynomial/helper.rs +++ b/synir/src/ir/pauli_polynomial/helper.rs @@ -108,7 +108,7 @@ pub(super) fn push_down_pauli_polynomial_update( } } let last_qubit = *affected_qubits.last().unwrap(); - repr.rz(last_qubit, pauli_polynomial.angle(col)); + repr.rz(last_qubit, pauli_polynomial.angle(col).to_radians()); mask.replace(col, false); } } diff --git a/synir/tests/pauli_exponential.rs b/synir/tests/pauli_exponential.rs index 324aa30d..c19afa71 100644 --- a/synir/tests/pauli_exponential.rs +++ b/synir/tests/pauli_exponential.rs @@ -3,6 +3,7 @@ mod common; use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; +use synir::data_structures::Angle; use synir::data_structures::{CliffordTableau, HasAdjoint, PauliPolynomial}; use synir::ir::clifford_tableau::{CliffordTableauSynthStrategy, NaiveCliffordSynthesizer}; use synir::ir::pauli_exponential::PauliExponential; @@ -11,7 +12,7 @@ use synir::ir::pauli_polynomial::PauliPolynomialSynthStrategy; use synir::ir::Synthesizer; fn setup_simple_pe() -> PauliExponential { - let ham = vec![("IZZZ", 0.3)]; + let ham = vec![("IZZZ", Angle::from_angle(0.3))]; let pauli_polynomial = PauliPolynomial::from_hamiltonian(ham); let clifford_tableau = CliffordTableau::new(4); @@ -19,7 +20,11 @@ fn setup_simple_pe() -> PauliExponential { } fn setup_complex_pe() -> PauliExponential { - let ham = vec![("IXYZ", 0.3), ("XXII", 0.7), ("YYII", 0.12)]; + let ham = vec![ + ("IXYZ", Angle::from_angle(0.3)), + ("XXII", Angle::from_angle(0.7)), + ("YYII", Angle::from_angle(0.12)), + ]; let pauli_polynomial = PauliPolynomial::from_hamiltonian(ham); let clifford_tableau = CliffordTableau::new(4); diff --git a/synir/tests/pauli_polynomial.rs b/synir/tests/pauli_polynomial.rs index f4560e9b..7d1d8103 100644 --- a/synir/tests/pauli_polynomial.rs +++ b/synir/tests/pauli_polynomial.rs @@ -3,13 +3,14 @@ mod common; use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; +use synir::data_structures::Angle; use synir::data_structures::{CliffordTableau, PauliPolynomial}; use synir::ir::pauli_polynomial::NaivePauliPolynomialSynthesizer; use synir::ir::Synthesizer; fn setup_complex_pp() -> VecDeque { - let ham_1 = vec![("IZZZ", 0.3)]; - let ham_2 = vec![("XXII", 0.7)]; + let ham_1 = vec![("IZZZ", Angle::from_angle(0.3))]; + let ham_2 = vec![("XXII", Angle::from_angle(0.7))]; let pp_1 = PauliPolynomial::from_hamiltonian(ham_1); let pp_2 = PauliPolynomial::from_hamiltonian(ham_2); @@ -17,7 +18,7 @@ fn setup_complex_pp() -> VecDeque { } fn setup_simple_pp() -> VecDeque { - let ham = vec![("IXYZ", 0.3)]; + let ham = vec![("IXYZ", Angle::from_angle(0.3))]; let pauli_polynomial = PauliPolynomial::from_hamiltonian(ham); diff --git a/synpy/src/synthesis.rs b/synpy/src/synthesis.rs index 6e905add..65331032 100644 --- a/synpy/src/synthesis.rs +++ b/synpy/src/synthesis.rs @@ -3,6 +3,7 @@ use pyo3::{pyclass, pymethods, PyErr}; use std::ops::Deref; use pyo3::{pyfunction, PyRef, PyResult}; +use synir::data_structures::Angle; use synir::data_structures::PropagateClifford; use synir::data_structures::{CliffordTableau, PauliPolynomial}; use synir::ir::clifford_tableau::CliffordTableauSynthStrategy; @@ -210,8 +211,14 @@ pub fn synthesize_pauli_exponential( ) -> PyResult> { let converted_hamiltonian = hamiltonian .iter() - .map(|inner| inner.iter().map(PyPauliString::as_tuple).collect()) - .map(|inner: Vec<(&str, f64)>| PauliPolynomial::from_hamiltonian(inner)) + .map(|inner| { + inner + .iter() + .map(PyPauliString::as_tuple) + .map(|(pauli_str, phase)| (pauli_str, Angle::from_angle(phase))) + .collect::>() + }) + .map(|inner: Vec<(&str, Angle)>| PauliPolynomial::from_hamiltonian(inner)) .collect(); let clifford_gates: Vec = clifford_gates.iter().map(|cmd| *(cmd.deref())).collect(); From b268b883276d07ee488407b21dfed58ec9b51483 Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 15:07:30 +0100 Subject: [PATCH 03/13] Implements for PauliString and PauliPolynomial. --- synir/src/data_structures/pauli_polynomial.rs | 88 ++++++++++++++++++- synir/src/data_structures/pauli_string.rs | 61 +++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index 5276ab23..f1074aab 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -1,7 +1,7 @@ use std::iter::zip; use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; -use crate::data_structures::Angle; +use crate::data_structures::{Angle, PauliLetter}; use bitvec::vec::BitVec; use itertools::zip_eq; @@ -61,6 +61,38 @@ impl PauliPolynomial { pub fn angle(&self, i: usize) -> Angle { self.angles[i] } + + pub fn commutes_with(&self, other: &PauliPolynomial) -> bool { + let size = self.size(); + assert_eq!(size, other.size()); + + let self_length = self.length(); + let other_length = other.length(); + + for index_1 in 0..self_length { + let mut pauli_string = Vec::with_capacity(size); + for q1 in 0..size { + pauli_string.push(self.chain(q1).pauli(index_1)); + } + for index_2 in 0..other_length { + let mut other_pauli_string = Vec::with_capacity(size); + for q2 in 0..size { + other_pauli_string.push(other.chain(q2).pauli(index_2)); + } + let mut commutes = true; + for (p1, p2) in zip(&pauli_string, &other_pauli_string) { + if *p1 == PauliLetter::I || *p2 == PauliLetter::I || p1 == p2 { + continue; + } + commutes = !commutes; + } + if !commutes { + return false; + } + } + } + true + } } impl PropagateClifford for PauliPolynomial { @@ -163,6 +195,7 @@ impl MaskedPropagateClifford for PauliPolynomial { #[cfg(test)] mod tests { use super::*; + use itertools::Itertools; impl PartialEq for PauliPolynomial { fn eq(&self, other: &Self) -> bool { @@ -584,4 +617,57 @@ mod tests { }; assert_eq!(pp, pp_ref); } + + #[test] + fn test_commutes_with_simple() { + let pp1s = vec![ + vec![("I", Angle::from_angle(0.3))], + vec![("X", Angle::from_angle(0.5))], + vec![("Y", Angle::from_angle(0.7))], + vec![("Z", Angle::from_angle(0.9))], + ] + .into_iter() + .map(|ham| PauliPolynomial::from_hamiltonian(ham)) + .collect::>(); + + let pp2s = pp1s.clone(); + + for (i, (pp1, pp2)) in pp1s.iter().cartesian_product(pp2s.iter()).enumerate() { + if i <= 5 || i == 8 || i == 10 || i == 12 || i == 15 { + assert!(pp1.commutes_with(pp2)); + } else { + assert!(!pp1.commutes_with(pp2)); + } + } + } + + #[test] + fn test_commutes_with() { + let pp1 = PauliPolynomial::from_hamiltonian(vec![ + ("IYYX", Angle::from_angle(0.3)), + ("XXXI", Angle::from_angle(0.5)), + ]); + + let pp2 = PauliPolynomial::from_hamiltonian(vec![ + ("IYZZ", Angle::from_angle(0.7)), + ("ZZXI", Angle::from_angle(0.9)), + ]); + + assert!(pp1.commutes_with(&pp2)); + } + + #[test] + fn test_not_commutes_with() { + let pp1 = PauliPolynomial::from_hamiltonian(vec![ + ("IYYX", Angle::from_angle(0.3)), + ("XXXI", Angle::from_angle(0.5)), + ]); + + let pp2 = PauliPolynomial::from_hamiltonian(vec![ + ("IYZZ", Angle::from_angle(0.7)), + ("ZZXY", Angle::from_angle(0.9)), + ]); + + assert!(!pp1.commutes_with(&pp2)); + } } diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index d66a3f5a..549b5348 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -123,6 +123,21 @@ impl PauliString { mask &= &self.z; mask } + + pub(crate) fn commutes_with(&self, other: &PauliString) -> bool { + assert!(self.len() == other.len()); + let length = self.len(); + let mut commutes = true; + for index in 0..length { + let p1 = self.pauli(index); + let p2 = other.pauli(index); + if p1 == PauliLetter::I || p2 == PauliLetter::I || p1 == p2 { + continue; + } + commutes = !commutes; + } + commutes + } } pub(crate) fn cx(control: &mut PauliString, target: &mut PauliString) { @@ -164,6 +179,7 @@ mod tests { use super::*; use bitvec::prelude::Lsb0; use bitvec::{bits, bitvec}; + use itertools::Itertools; #[test] fn test_from_basis_int() { @@ -300,4 +316,49 @@ mod tests { let pauli_string = PauliString::from_text("IXYZI"); assert_eq!(pauli_string.to_string(), String::from("I X Y Z I")); } + + #[test] + fn test_check_commute_simple() { + let ps1 = vec![ + PauliString::from_text("I"), + PauliString::from_text("X"), + PauliString::from_text("Y"), + PauliString::from_text("Z"), + ]; + + let ps2 = vec![ + PauliString::from_text("I"), + PauliString::from_text("X"), + PauliString::from_text("Y"), + PauliString::from_text("Z"), + ]; + + for (p1, p2) in ps1.iter().cartesian_product(ps2.iter()) { + if p1 == &PauliString::from_text("I") || p2 == &PauliString::from_text("I") || p1 == p2 + { + assert!(p1.commutes_with(p2)); + continue; + } + assert!(!p1.commutes_with(p2)); + } + } + + #[test] + #[should_panic] + fn test_bad_commute() { + let ps1 = PauliString::from_text("IXXYZ"); + let ps2 = PauliString::from_text("IYZX"); + assert!(!ps1.commutes_with(&ps2)); + } + + #[test] + fn test_check_commute() { + let ps1 = PauliString::from_text("IXYZ"); + let ps2 = PauliString::from_text("IYZX"); + assert!(!ps1.commutes_with(&ps2)); + + let ps3 = PauliString::from_text("IXYZ"); + let ps4 = PauliString::from_text("IZXI"); + assert!(ps3.commutes_with(&ps4)); + } } From 12273a18468f4978402d44e77d66b8707356ab1a Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 17:09:47 +0100 Subject: [PATCH 04/13] Add methods for composing Clifford gadgets into CliffordTableau --- synir/src/data_structures/clifford_tableau.rs | 85 +++++++- synir/tests/ct_synthesis/ct_compose.rs | 193 ++++++++++++++++++ synir/tests/ct_synthesis/mod.rs | 1 + 3 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 synir/tests/ct_synthesis/ct_compose.rs diff --git a/synir/src/data_structures/clifford_tableau.rs b/synir/src/data_structures/clifford_tableau.rs index a5d34cc5..2efe28e5 100644 --- a/synir/src/data_structures/clifford_tableau.rs +++ b/synir/src/data_structures/clifford_tableau.rs @@ -10,7 +10,7 @@ use super::{ pauli_string::{cx, PauliString}, IndexType, PropagateClifford, }; -use crate::data_structures::PauliLetter; +use crate::data_structures::{Angle, PauliLetter}; #[derive(PartialEq, Eq, Debug, Clone, Default)] pub struct CliffordTableau { @@ -191,6 +191,89 @@ impl CliffordTableau { } } + /// Composes a gadget onto a Clifford tableau if the angle is Clifford + /// Decomposes the Pauli gadget by performing naive decomposition into mapping to Z legs, CNOT walls and Z-rotations + pub fn compose_gadget(&mut self, rhs: (PauliString, Angle)) { + let (pauli_string, angle) = rhs; + let size = self.size(); + assert_eq!( + size, + pauli_string.len(), + "Cannot compose Clifford tableau with PauliPolynomial of different size" + ); + let pi2rotations = match angle { + Angle::Angle(angle) => panic!( + "Cannot compose Clifford tableau with non-Clifford angle: {}", + angle + ), + Angle::Pi4Rotations(rotations) => { + if rotations % 2 == 1 { + panic!("Cannot compose Clifford tableau with non-Clifford angle: {} pi/4 rotations", rotations); + } + (rotations >> 1) % 4 + } + }; + let mut leg_numbers = Vec::with_capacity(size); + for i in 0..size { + match pauli_string.pauli(i) { + PauliLetter::I => {} + PauliLetter::X => { + self.h(i); + leg_numbers.push(i); + } + PauliLetter::Y => { + self.v(i); + leg_numbers.push(i); + } + PauliLetter::Z => { + leg_numbers.push(i); + } + } + } + + for (control, target) in leg_numbers.iter().tuple_windows() { + self.cx(*control, *target); + } + match pi2rotations { + 0 => {} + 1 => { + let target = *leg_numbers.last().unwrap(); + self.s(target); + } + 2 => { + let target = *leg_numbers.last().unwrap(); + self.z(target); + } + 3 => { + let target = *leg_numbers.last().unwrap(); + self.s_dgr(target); + } + _ => unreachable!(), + } + + for (control, target) in leg_numbers + .iter() + .tuple_windows() + .collect_vec() + .iter() + .rev() + { + self.cx(**control, **target); + } + for i in 0..size { + match pauli_string.pauli(i) { + PauliLetter::I => {} + PauliLetter::X => { + self.h(i); + } + PauliLetter::Y => { + self.v_dgr(i); + } + PauliLetter::Z => {} + } + } + } + pub fn permute(&mut self, permutation_vector: Vec) { assert_eq!( permutation_vector diff --git a/synir/tests/ct_synthesis/ct_compose.rs b/synir/tests/ct_synthesis/ct_compose.rs new file mode 100644 index 00000000..125e2e86 --- /dev/null +++ b/synir/tests/ct_synthesis/ct_compose.rs @@ -0,0 +1,193 @@ +extern crate rand; + +use rand::seq::SliceRandom; + +use crate::common::mock_circuit::{ + check_mock_equals_clifford_tableau, parse_clifford_commands, MockCircuit, MockCommand, +}; +use crate::common::sample_clifford_tableaus::{ + half_swap_0_1, half_swap_1_0, identity_2qb_ct, sample_2cnot_ladder, sample_cnot_gate, + sample_cnot_reverse_gate, sample_s_dgr_gate, sample_s_gate, sample_swap_ct, sample_v_dgr_gate, + sample_v_gate, setup_sample_ct, setup_sample_inverse_ct, +}; +use itertools::Itertools; +use synir::data_structures::{Angle, CliffordTableau, PropagateClifford}; +use synir::ir::clifford_tableau::CallbackCliffordSynthesizer; +use synir::ir::Synthesizer; + +fn run_synthesizer(clifford_tableau: &CliffordTableau) -> (MockCircuit, CliffordTableau) { + let mut mock = MockCircuit::new(); + // let mut rng = rand::rng(); //TODO make this from seed + let custom_columns = (0..clifford_tableau.size()).collect_vec(); + let mut custom_rows = (0..clifford_tableau.size()).collect_vec(); + // custom_rows.shuffle(&mut rng); + + let mut synthesizer = CallbackCliffordSynthesizer::custom_pivot(custom_columns, custom_rows); + let new_ct = synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + (mock, new_ct) +} + +macro_rules! test_clifford { + ($fun:ident, $expected:expr) => { + paste::item! { + #[test] + fn [< synthesize_ $fun>]() { + let clifford_tableau = $fun(); + let (mock, new_ct) = run_synthesizer(&clifford_tableau); + if $expected.is_some() { + assert_eq!(mock.commands(), $expected.unwrap()); + } + check_mock_equals_clifford_tableau(&clifford_tableau, &mock, new_ct.get_permutation()); + } + } + }; +} + +fn compose_x_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(2); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("XI"), + Angle::from_pi4_rotations(2), + )); + ct +} + +fn compose_z_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(2); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("ZI"), + Angle::from_pi4_rotations(4), + )); + ct +} + +fn compose_y_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(2); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("YI"), + Angle::from_pi4_rotations(6), + )); + ct +} + +test_clifford!(compose_x_gadget, Some(&vec![MockCommand::V(0),])); +test_clifford!(compose_z_gadget, Some(&vec![MockCommand::Z(0),])); + +test_clifford!( + compose_y_gadget, + Some(&vec![MockCommand::H(0), MockCommand::Z(0)]) +); + +fn compose_xx_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(2); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("XX"), + Angle::from_pi4_rotations(2), + )); + ct +} + +fn compose_zz_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(2); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("ZZ"), + Angle::from_pi4_rotations(2), + )); + ct +} + +fn compose_yy_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(2); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("YY"), + Angle::from_pi4_rotations(2), + )); + ct +} + +test_clifford!( + compose_xx_gadget, + Some(&vec![ + MockCommand::V(0), + MockCommand::H(1), + MockCommand::CX(1, 0), + MockCommand::H(1), + MockCommand::V(1) + ]) +); + +test_clifford!( + compose_zz_gadget, + Some(&vec![ + MockCommand::S(0), + MockCommand::H(1), + MockCommand::CX(0, 1), + MockCommand::S(1), + MockCommand::V(1), + MockCommand::Z(1) + ]) +); + +test_clifford!( + compose_yy_gadget, + Some(&vec![ + MockCommand::H(0), + MockCommand::S(1), + MockCommand::CX(0, 1), + MockCommand::H(1), + MockCommand::CX(1, 0), + MockCommand::S(1), + MockCommand::V(1), + MockCommand::X(1), + MockCommand::Z(0), + MockCommand::Z(1) + ]) +); + +fn compose_complex_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(3); + ct.compose_gadget(( + synir::data_structures::PauliString::from_text("XYZ"), + Angle::from_pi4_rotations(2), + )); + ct +} + +fn manual_compose_complex_gadget() -> CliffordTableau { + let mut ct = CliffordTableau::new(3); + ct.h(0); + ct.v(1); + ct.cx(0, 1); + ct.cx(1, 2); + ct.s(2); + ct.cx(1, 2); + ct.cx(0, 1); + ct.h(0); + ct.v_dgr(1); + ct +} + +test_clifford!( + compose_complex_gadget, + Some(&vec![ + MockCommand::V(0), + MockCommand::V(1), + MockCommand::CX(1, 0), + MockCommand::CX(2, 0), + MockCommand::S(1), + MockCommand::H(2), + MockCommand::CX(1, 2), + MockCommand::V(1), + MockCommand::S(2), + MockCommand::V(2), + MockCommand::X(1), + MockCommand::Z(2) + ]) +); + +#[test] +fn test_correctness() { + let ct1 = compose_complex_gadget(); + let ct2 = manual_compose_complex_gadget(); + assert_eq!(ct1, ct2); +} diff --git a/synir/tests/ct_synthesis/mod.rs b/synir/tests/ct_synthesis/mod.rs index 2fff57d6..3d2bebc6 100644 --- a/synir/tests/ct_synthesis/mod.rs +++ b/synir/tests/ct_synthesis/mod.rs @@ -1,3 +1,4 @@ +pub mod ct_compose; pub mod custom_callback; pub mod naive; pub mod naive_adjoint; From 62c8ac99621d4339912309e0b1ecc68d645ce720 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:24:41 +0100 Subject: [PATCH 05/13] Make imports for Sub, Add prettier --- synir/src/data_structures/angle.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/synir/src/data_structures/angle.rs b/synir/src/data_structures/angle.rs index 862a2a16..a9e92ad8 100644 --- a/synir/src/data_structures/angle.rs +++ b/synir/src/data_structures/angle.rs @@ -1,4 +1,4 @@ -use std::ops::{AddAssign, SubAssign}; +use std::ops::{AddAssign, SubAssign, Add, Sub}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Angle { @@ -71,7 +71,7 @@ impl SubAssign for Angle { } } -impl std::ops::Add for Angle { +impl Add for Angle { type Output = Angle; fn add(self, other: Angle) -> Angle { @@ -85,7 +85,7 @@ impl std::ops::Add for Angle { } } -impl std::ops::Sub for Angle { +impl Sub for Angle { type Output = Angle; fn sub(self, other: Angle) -> Angle { From c58e57cd9bf948cf5491e8d4b6c1561220804be4 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:26:07 +0100 Subject: [PATCH 06/13] Rename from_pi4rotation functions --- synir/src/data_structures/angle.rs | 6 +++--- synir/tests/ct_synthesis/ct_compose.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/synir/src/data_structures/angle.rs b/synir/src/data_structures/angle.rs index a9e92ad8..f4c5e647 100644 --- a/synir/src/data_structures/angle.rs +++ b/synir/src/data_structures/angle.rs @@ -18,13 +18,13 @@ impl Angle { .collect() } - pub fn from_pi4_rotations(n: usize) -> Self { + pub fn from_pi4_rotation(n: usize) -> Self { Angle::Pi4Rotations(n % 8) } - pub fn forpi4_rotations(ns: &[usize]) -> Vec { + pub fn from_pi4_rotations(ns: &[usize]) -> Vec { ns.into_iter() - .map(|n| Angle::from_pi4_rotations(*n)) + .map(|n| Angle::from_pi4_rotation(*n)) .collect() } diff --git a/synir/tests/ct_synthesis/ct_compose.rs b/synir/tests/ct_synthesis/ct_compose.rs index 125e2e86..0d08c3ea 100644 --- a/synir/tests/ct_synthesis/ct_compose.rs +++ b/synir/tests/ct_synthesis/ct_compose.rs @@ -47,7 +47,7 @@ fn compose_x_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); ct.compose_gadget(( synir::data_structures::PauliString::from_text("XI"), - Angle::from_pi4_rotations(2), + Angle::from_pi4_rotation(2), )); ct } @@ -56,7 +56,7 @@ fn compose_z_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); ct.compose_gadget(( synir::data_structures::PauliString::from_text("ZI"), - Angle::from_pi4_rotations(4), + Angle::from_pi4_rotation(4), )); ct } @@ -65,7 +65,7 @@ fn compose_y_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); ct.compose_gadget(( synir::data_structures::PauliString::from_text("YI"), - Angle::from_pi4_rotations(6), + Angle::from_pi4_rotation(6), )); ct } @@ -82,7 +82,7 @@ fn compose_xx_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); ct.compose_gadget(( synir::data_structures::PauliString::from_text("XX"), - Angle::from_pi4_rotations(2), + Angle::from_pi4_rotation(2), )); ct } @@ -91,7 +91,7 @@ fn compose_zz_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); ct.compose_gadget(( synir::data_structures::PauliString::from_text("ZZ"), - Angle::from_pi4_rotations(2), + Angle::from_pi4_rotation(2), )); ct } @@ -100,7 +100,7 @@ fn compose_yy_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); ct.compose_gadget(( synir::data_structures::PauliString::from_text("YY"), - Angle::from_pi4_rotations(2), + Angle::from_pi4_rotation(2), )); ct } @@ -148,7 +148,7 @@ fn compose_complex_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(3); ct.compose_gadget(( synir::data_structures::PauliString::from_text("XYZ"), - Angle::from_pi4_rotations(2), + Angle::from_pi4_rotation(2), )); ct } From c34cbadb3edbde67f32304599170ac2c35def7ea Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:39:25 +0100 Subject: [PATCH 07/13] Optimize other_pauli_string in implementation --- synir/src/data_structures/pauli_polynomial.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index f1074aab..02bc1357 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -75,13 +75,10 @@ impl PauliPolynomial { pauli_string.push(self.chain(q1).pauli(index_1)); } for index_2 in 0..other_length { - let mut other_pauli_string = Vec::with_capacity(size); - for q2 in 0..size { - other_pauli_string.push(other.chain(q2).pauli(index_2)); - } + let other_pauli_string = (0..size).map(|q2| other.chain(q2).pauli(index_2)); let mut commutes = true; - for (p1, p2) in zip(&pauli_string, &other_pauli_string) { - if *p1 == PauliLetter::I || *p2 == PauliLetter::I || p1 == p2 { + for (p1, p2) in zip(&pauli_string, other_pauli_string) { + if *p1 == PauliLetter::I || p2 == PauliLetter::I || p1 == &p2 { continue; } commutes = !commutes; From 82092db45ea36f4ce5f65cf594d5afc957f74feb Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:40:17 +0100 Subject: [PATCH 08/13] Update interface for Only expose single function that actually merges to avoid exposing internal data stuctures. --- .../pauli_polynomial/simplify.rs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/synir/src/data_structures/pauli_polynomial/simplify.rs b/synir/src/data_structures/pauli_polynomial/simplify.rs index c70ca9bd..65e009e5 100644 --- a/synir/src/data_structures/pauli_polynomial/simplify.rs +++ b/synir/src/data_structures/pauli_polynomial/simplify.rs @@ -2,7 +2,9 @@ use crate::data_structures::PauliPolynomial; use itertools::Itertools; use std::collections::HashMap; -pub fn check_repeats(pp: &PauliPolynomial) -> Vec<(usize, Vec)> { +/// Check for items in PauliPolynomial that has the same Pauli string +/// Returns a vector of a tuple of Pauli string identifier and the locations of this repeated string +pub(crate) fn check_repeats(pp: &PauliPolynomial) -> Vec<(usize, Vec)> { let size = pp.size(); let length = pp.length(); let mut repeats = HashMap::>::new(); @@ -24,7 +26,9 @@ pub fn check_repeats(pp: &PauliPolynomial) -> Vec<(usize, Vec)> { .collect_vec() } -pub fn merge_repeats( +/// Takes output of check_repeats and merges items with the same Pauli string +/// Assumes that all angles are of the same type +pub(crate) fn _merge_repeats( mut pp: PauliPolynomial, merge_list: Vec<(usize, Vec)>, ) -> PauliPolynomial { @@ -51,6 +55,12 @@ pub fn merge_repeats( pp } +/// Merges all items in PauliPolynomial that share the same Pauli string +pub fn merge_repeats(mut pp: PauliPolynomial) -> PauliPolynomial { + let repeats = check_repeats(&pp); + _merge_repeats(pp, repeats) +} + #[cfg(test)] mod tests { use crate::data_structures::Angle; @@ -116,7 +126,7 @@ mod tests { ]); let repeats = check_repeats(&pp); - let pp = merge_repeats(pp, repeats); + let pp = _merge_repeats(pp, repeats); assert!(pp.chain(0).len() == 3); assert_eq!(pp.chain(0), &PauliString::from_text("IXZ")); @@ -131,7 +141,7 @@ mod tests { ("YZZI", Angle::from_angle(3.0)), ]); let repeats = check_repeats(&pp); - let pp = merge_repeats(pp, repeats); + let pp = _merge_repeats(pp, repeats); assert!(pp.chain(0).len() == 2); assert_eq!(pp.chain(0), &PauliString::from_text("XY")); @@ -153,7 +163,7 @@ mod tests { ]); let repeats = check_repeats(&pp); - let pp = merge_repeats(pp, repeats); + let pp = _merge_repeats(pp, repeats); assert!(pp.chain(0).len() == 3); assert_eq!(pp.chain(0), &PauliString::from_text("IIZ")); From 7242af261db9da9f30802f399e56f05dbeeff2cf Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:41:57 +0100 Subject: [PATCH 09/13] Set assert! to assert_eq --- synir/src/data_structures/pauli_string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index 549b5348..571a5800 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -125,7 +125,7 @@ impl PauliString { } pub(crate) fn commutes_with(&self, other: &PauliString) -> bool { - assert!(self.len() == other.len()); + assert_eq!(self.len(), other.len()); let length = self.len(); let mut commutes = true; for index in 0..length { From 8275447c9eb44776e3314c927d27cb42f5be6411 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:42:45 +0100 Subject: [PATCH 10/13] Correct assert in to ensure source of panic --- synir/src/data_structures/pauli_string.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index 571a5800..590b4df4 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -348,7 +348,7 @@ mod tests { fn test_bad_commute() { let ps1 = PauliString::from_text("IXXYZ"); let ps2 = PauliString::from_text("IYZX"); - assert!(!ps1.commutes_with(&ps2)); + ps1.commutes_with(&ps2); } #[test] From fc7ec2fa527710b6bfa52caf54e68c7f1a3eb21d Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:47:22 +0100 Subject: [PATCH 11/13] Improve Angle struct Rename Angle::Angle to Angle::Arbitrary. Set to as it doesn't need a larger int. --- synir/src/data_structures/angle.rs | 24 +++++++++---------- synir/src/data_structures/clifford_tableau.rs | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/synir/src/data_structures/angle.rs b/synir/src/data_structures/angle.rs index f4c5e647..41607620 100644 --- a/synir/src/data_structures/angle.rs +++ b/synir/src/data_structures/angle.rs @@ -1,14 +1,14 @@ -use std::ops::{AddAssign, SubAssign, Add, Sub}; +use std::ops::{Add, AddAssign, Sub, SubAssign}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Angle { - Angle(f64), - Pi4Rotations(usize), + Arbitrary(f64), + Pi4Rotations(u8), } impl Angle { pub fn from_angle(rad: f64) -> Self { - Angle::Angle(rad) + Angle::Arbitrary(rad) } pub fn from_angles(angles: &[f64]) -> Vec { @@ -18,11 +18,11 @@ impl Angle { .collect() } - pub fn from_pi4_rotation(n: usize) -> Self { + pub fn from_pi4_rotation(n: u8) -> Self { Angle::Pi4Rotations(n % 8) } - pub fn from_pi4_rotations(ns: &[usize]) -> Vec { + pub fn from_pi4_rotations(ns: &[u8]) -> Vec { ns.into_iter() .map(|n| Angle::from_pi4_rotation(*n)) .collect() @@ -30,14 +30,14 @@ impl Angle { pub fn to_radians(&self) -> f64 { match self { - Angle::Angle(rad) => *rad, + Angle::Arbitrary(rad) => *rad, Angle::Pi4Rotations(n) => (*n as f64) * (std::f64::consts::FRAC_PI_4), } } pub fn flip(&mut self) { match self { - Angle::Angle(rad) => *rad = -*rad, + Angle::Arbitrary(rad) => *rad = -*rad, Angle::Pi4Rotations(n) => *n = (8 - *n) % 8, } } @@ -46,7 +46,7 @@ impl Angle { impl AddAssign for Angle { fn add_assign(&mut self, other: Self) { match (self, other) { - (Angle::Angle(rad1), Angle::Angle(rad2)) => { + (Angle::Arbitrary(rad1), Angle::Arbitrary(rad2)) => { *rad1 += rad2; } (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { @@ -60,7 +60,7 @@ impl AddAssign for Angle { impl SubAssign for Angle { fn sub_assign(&mut self, other: Self) { match (self, other) { - (Angle::Angle(rad1), Angle::Angle(rad2)) => { + (Angle::Arbitrary(rad1), Angle::Arbitrary(rad2)) => { *rad1 -= rad2; } (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { @@ -76,7 +76,7 @@ impl Add for Angle { fn add(self, other: Angle) -> Angle { match (self, other) { - (Angle::Angle(rad1), Angle::Angle(rad2)) => Angle::Angle(rad1 + rad2), + (Angle::Arbitrary(rad1), Angle::Arbitrary(rad2)) => Angle::Arbitrary(rad1 + rad2), (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { Angle::Pi4Rotations((n1 + n2) % 8) } @@ -90,7 +90,7 @@ impl Sub for Angle { fn sub(self, other: Angle) -> Angle { match (self, other) { - (Angle::Angle(rad1), Angle::Angle(rad2)) => Angle::Angle(rad1 - rad2), + (Angle::Arbitrary(rad1), Angle::Arbitrary(rad2)) => Angle::Arbitrary(rad1 - rad2), (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { Angle::Pi4Rotations((n1 + 8 - n2) % 8) } diff --git a/synir/src/data_structures/clifford_tableau.rs b/synir/src/data_structures/clifford_tableau.rs index 2efe28e5..94c0e99f 100644 --- a/synir/src/data_structures/clifford_tableau.rs +++ b/synir/src/data_structures/clifford_tableau.rs @@ -202,7 +202,7 @@ impl CliffordTableau { "Cannot compose Clifford tableau with PauliPolynomial of different size" ); let pi2rotations = match angle { - Angle::Angle(angle) => panic!( + Angle::Arbitrary(angle) => panic!( "Cannot compose Clifford tableau with non-Clifford angle: {}", angle ), From b29ec67266c471735b8ce70edfc02b672cf2ae1a Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 16:00:12 +0100 Subject: [PATCH 12/13] Update angle implementation to mixed arbitrary and pi4 angles --- synir/src/data_structures/angle.rs | 200 ++++++++++++++++++++++++++++- 1 file changed, 196 insertions(+), 4 deletions(-) diff --git a/synir/src/data_structures/angle.rs b/synir/src/data_structures/angle.rs index 41607620..703e1875 100644 --- a/synir/src/data_structures/angle.rs +++ b/synir/src/data_structures/angle.rs @@ -1,4 +1,7 @@ -use std::ops::{Add, AddAssign, Sub, SubAssign}; +use std::{ + f64::consts::PI, + ops::{Add, AddAssign, Sub, SubAssign}, +}; #[derive(Debug, Clone, Copy, PartialEq)] pub enum Angle { @@ -52,7 +55,10 @@ impl AddAssign for Angle { (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { *n1 = (*n1 + n2) % 8; } - _ => panic!("Cannot add different types of Angles"), + (Angle::Arbitrary(rad1), Angle::Pi4Rotations(n2)) => { + *rad1 += n2 as f64 * PI / 4.0; + } + _ => panic!("Cannot add Arbitrary Angle to Pi4 rotation"), } } } @@ -66,6 +72,9 @@ impl SubAssign for Angle { (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { *n1 = (*n1 + (8 - n2)) % 8; } + (Angle::Arbitrary(rad1), Angle::Pi4Rotations(n2)) => { + *rad1 -= n2 as f64 * PI / 4.0; + } _ => panic!("Cannot subtract different types of Angles"), } } @@ -80,7 +89,12 @@ impl Add for Angle { (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { Angle::Pi4Rotations((n1 + n2) % 8) } - _ => panic!("Cannot add different types of Angles"), + (Angle::Arbitrary(rad1), Angle::Pi4Rotations(n2)) => { + Angle::Arbitrary(rad1 + n2 as f64 * PI / 4.0) + } + (Angle::Pi4Rotations(n1), Angle::Arbitrary(rad2)) => { + Angle::Arbitrary(rad2 + n1 as f64 * PI / 4.0) + } } } } @@ -94,7 +108,185 @@ impl Sub for Angle { (Angle::Pi4Rotations(n1), Angle::Pi4Rotations(n2)) => { Angle::Pi4Rotations((n1 + 8 - n2) % 8) } - _ => panic!("Cannot add different types of Angles"), + (Angle::Arbitrary(rad1), Angle::Pi4Rotations(n2)) => { + Angle::Arbitrary(rad1 - n2 as f64 * PI / 4.0) + } + (Angle::Pi4Rotations(n1), Angle::Arbitrary(rad2)) => { + Angle::Arbitrary(n1 as f64 * PI / 4.0 - rad2) + } } } } + +#[cfg(test)] +mod tests { + use super::*; + + fn check_angle_approx(angle1: Angle, angle2: Angle) -> bool { + match (angle1, angle2) { + (Angle::Arbitrary(a1), Angle::Arbitrary(a2)) => (a1 - a2).abs() < 1e-9, + (Angle::Pi4Rotations(a1), Angle::Pi4Rotations(a2)) => a1 == a2, + _ => panic!("Not defined for Arbitrary Angles and Pi4 rotations"), + } + } + + #[test] + fn test_angle_simple_add() { + let n1 = 1; + let n2 = 2; + + let a1 = Angle::from_pi4_rotation(n1); + let a2 = Angle::from_pi4_rotation(n2); + + assert_eq!(a1 + a2, Angle::from_pi4_rotation(3)); + + let mut a3 = Angle::from_pi4_rotation(n1); + a3 += a2; + + assert_eq!(a3, Angle::from_pi4_rotation(3)); + } + + #[test] + fn test_angle_overflow_add() { + let n1 = 5; + let n2 = 6; + + let a1 = Angle::from_pi4_rotation(n1); + let a2 = Angle::from_pi4_rotation(n2); + + assert_eq!(a1 + a2, Angle::from_pi4_rotation(3)); + + let mut a3 = Angle::from_pi4_rotation(n1); + a3 += a2; + + assert_eq!(a3, Angle::from_pi4_rotation(3)); + } + + #[test] + fn test_angle_simple_sub() { + let n1 = 4; + let n2 = 2; + + let a1 = Angle::from_pi4_rotation(n1); + let a2 = Angle::from_pi4_rotation(n2); + + assert_eq!(a1 - a2, Angle::from_pi4_rotation(2)); + + let mut a3 = Angle::from_pi4_rotation(n1); + a3 -= a2; + + assert_eq!(a3, Angle::from_pi4_rotation(2)); + } + + #[test] + fn test_angle_overflow_sub() { + let n1 = 2; + let n2 = 6; + + let a1 = Angle::from_pi4_rotation(n1); + let a2 = Angle::from_pi4_rotation(n2); + + assert_eq!(a1 - a2, Angle::from_pi4_rotation(4)); + + let mut a3 = Angle::from_pi4_rotation(n1); + a3 -= a2; + + assert_eq!(a3, Angle::from_pi4_rotation(4)); + } + + #[test] + fn test_angle_float_simple_add() { + let n1 = 0.32; + let n2 = 0.64; + + let a1 = Angle::from_angle(n1); + let a2 = Angle::from_angle(n2); + + let ref_a = Angle::from_angle(0.96); + + assert!(check_angle_approx(a1 + a2, ref_a)); + + let mut a3 = Angle::from_angle(n1); + a3 += a2; + + assert!(check_angle_approx(a3, ref_a)); + } + + #[test] + fn test_angle_float_simple_sub() { + let n1 = 0.32; + let n2 = 0.64; + + let a1 = Angle::from_angle(n1); + let a2 = Angle::from_angle(n2); + + let ref_a = Angle::from_angle(-0.32); + + assert!(check_angle_approx(a1 - a2, ref_a)); + + let mut a3 = Angle::from_angle(n1); + a3 -= a2; + + assert!(check_angle_approx(a3, ref_a)); + } + + #[test] + fn test_angle_mixed_simple_add() { + let n1 = 0.32; + let n2 = 2; + + let a1 = Angle::from_angle(n1); + let a2 = Angle::from_pi4_rotation(n2); + + let ref_a = Angle::from_angle(1.8907963268); + assert!(check_angle_approx(a1 + a2, ref_a)); + assert!(check_angle_approx(a2 + a1, ref_a)); + + let mut a3 = Angle::from_angle(n1); + a3 += a2; + + assert!(check_angle_approx(a3, ref_a)); + } + + #[test] + #[should_panic] + fn test_angle_bad_mixed_simple_add() { + let n1 = 0.32; + let n2 = 2; + + let mut a2 = Angle::from_pi4_rotation(n2); + let a3 = Angle::from_angle(n1); + a2 += a3 + } + + #[test] + fn test_angle_mixed_simple_sub() { + let n1 = 0.32; + let n2 = 2; + + let a1 = Angle::from_angle(n1); + let a2 = Angle::from_pi4_rotation(n2); + + let ref_a1 = Angle::from_angle(-1.2507963268); + let ref_a2 = Angle::from_angle(1.2507963268); + + assert!(check_angle_approx(a1 - a2, ref_a1)); + assert!(check_angle_approx(a2 - a1, ref_a2)); + + let mut a3 = Angle::from_angle(n1); + a3 -= a2; + + assert!(check_angle_approx(a3, ref_a1)); + } + + #[test] + #[should_panic] + fn test_angle_bad_mixed_simple_sub() { + let n1 = 0.32; + let n2 = 2; + + let mut a2 = Angle::from_pi4_rotation(n2); + let a3 = Angle::from_angle(n1); + a2 -= a3 + } +} From b61376da4ac3ff0df2e559a51864b463b196022e Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 13 Jan 2026 14:55:55 +0100 Subject: [PATCH 13/13] Stashed implementation --- synpy/integration_tests/test_qiskit.py | 17 ++- synpy/src/synthesis.rs | 1 + synpy/src/wrapper/qiskit.rs | 188 +++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 synpy/src/wrapper/qiskit.rs diff --git a/synpy/integration_tests/test_qiskit.py b/synpy/integration_tests/test_qiskit.py index 3df06263..d34bb66b 100644 --- a/synpy/integration_tests/test_qiskit.py +++ b/synpy/integration_tests/test_qiskit.py @@ -12,6 +12,21 @@ def test_qiskit_bell() -> None: plugin = SynPyCliffordPlugin() circ = plugin.run(cliff, None, None, []) - # circ.draw() + + assert circ == qc + +def test_qiskit_circuit() -> None: + qc = QuantumCircuit(3) + qc.h(0) + qc.cx(0, 1) + qc.t(1) + qc.cx(1, 2) + qc.h(2) + qc.cz(0, 2) + + cliff = Clifford(qc) + + plugin = SynPyCliffordPlugin() + circ = plugin.run(cliff, None, None, []) assert circ == qc diff --git a/synpy/src/synthesis.rs b/synpy/src/synthesis.rs index 65331032..b7bb2cfa 100644 --- a/synpy/src/synthesis.rs +++ b/synpy/src/synthesis.rs @@ -1,5 +1,6 @@ use pyo3::exceptions::PyException; use pyo3::{pyclass, pymethods, PyErr}; +use synir::data_structures::Angle; use std::ops::Deref; use pyo3::{pyfunction, PyRef, PyResult}; diff --git a/synpy/src/wrapper/qiskit.rs b/synpy/src/wrapper/qiskit.rs new file mode 100644 index 00000000..997d20cf --- /dev/null +++ b/synpy/src/wrapper/qiskit.rs @@ -0,0 +1,188 @@ +extern crate pyo3; +extern crate pyo3_ffi; + +use pyo3::prelude::*; +use synir::ir::{CliffordGates, Gates}; + +#[pyclass] +pub struct QiskitSynIR { + circuit: Py, +} + +#[pymethods] +impl QiskitSynIR { + #[new] + pub fn new(qiskit_circuit: Py) -> Self { + QiskitSynIR { + circuit: qiskit_circuit, + } + } + + pub fn get_commands(&self, tgt: usize) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "h", (tgt,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn s(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "s", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn v(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "sx", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn s_dgr(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "sdg", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn v_dgr(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "sxdg", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn x(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "x", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn y(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "y", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn z(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "z", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn h(&mut self, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "h", (target,))?; + Ok(()) + }) + .unwrap(); + } + + pub fn cx(&mut self, control: synir::IndexType, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "cx", (control, target))?; + Ok(()) + }) + .unwrap(); + } + + pub fn cz(&mut self, control: synir::IndexType, target: synir::IndexType) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "cz", (control, target))?; + Ok(()) + }) + .unwrap(); + } + + pub fn rx(&mut self, target: synir::IndexType, angle: f64) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "rx", (angle, target))?; + Ok(()) + }) + .unwrap(); + } + + pub fn ry(&mut self, target: synir::IndexType, angle: f64) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "ry", (angle, target))?; + Ok(()) + }) + .unwrap(); + } + + pub fn rz(&mut self, target: synir::IndexType, angle: f64) { + Python::attach(|py| -> PyResult<()> { + self.circuit.call_method1(py, "rz", (angle, target))?; + Ok(()) + }) + .unwrap(); + } +} + +impl CliffordGates for QiskitSynIR { + fn s(&mut self, target: synir::IndexType) { + self.s(target); + } + + fn v(&mut self, target: synir::IndexType) { + self.v(target); + } + + fn s_dgr(&mut self, target: synir::IndexType) { + self.s_dgr(target); + } + + fn v_dgr(&mut self, target: synir::IndexType) { + self.v_dgr(target); + } + + fn x(&mut self, target: synir::IndexType) { + self.x(target); + } + + fn y(&mut self, target: synir::IndexType) { + self.y(target); + } + + fn z(&mut self, target: synir::IndexType) { + self.z(target); + } + + fn h(&mut self, target: synir::IndexType) { + self.h(target); + } + + fn cx(&mut self, control: synir::IndexType, target: synir::IndexType) { + self.cx(control, target); + } + + fn cz(&mut self, control: synir::IndexType, target: synir::IndexType) { + self.cz(control, target); + } +} + +impl Gates for QiskitSynIR { + fn rx(&mut self, target: synir::IndexType, angle: f64) { + self.rx(target, angle); + } + + fn ry(&mut self, target: synir::IndexType, angle: f64) { + self.ry(target, angle); + } + + fn rz(&mut self, target: synir::IndexType, angle: f64) { + self.rz(target, angle); + } +}