From 74dfa88ca5cd5a92841c9a7932d4c858b09151a2 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 28 Nov 2025 14:19:47 +0100 Subject: [PATCH 01/28] Shift definition of PauliExponential to `data_structures` --- synir/src/data_structures.rs | 2 ++ .../src/data_structures/pauli_exponential.rs | 21 +++++++++++++++ synir/src/ir/pauli_exponential.rs | 26 +++---------------- synir/tests/pauli_exponential.rs | 3 +-- synpy/src/synthesis.rs | 18 ++++++------- 5 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 synir/src/data_structures/pauli_exponential.rs diff --git a/synir/src/data_structures.rs b/synir/src/data_structures.rs index 98aade8b..4b5b6088 100644 --- a/synir/src/data_structures.rs +++ b/synir/src/data_structures.rs @@ -1,11 +1,13 @@ use crate::IndexType; mod clifford_tableau; +mod pauli_exponential; mod pauli_polynomial; mod pauli_string; use bitvec::vec::BitVec; pub use clifford_tableau::CliffordTableau; +pub use pauli_exponential::PauliExponential; pub use pauli_polynomial::PauliPolynomial; pub use pauli_string::PauliString; diff --git a/synir/src/data_structures/pauli_exponential.rs b/synir/src/data_structures/pauli_exponential.rs new file mode 100644 index 00000000..f9252d5a --- /dev/null +++ b/synir/src/data_structures/pauli_exponential.rs @@ -0,0 +1,21 @@ +use std::collections::VecDeque; + +use crate::data_structures::{CliffordTableau, PauliPolynomial}; + +#[derive(Default)] +pub struct PauliExponential { + pub(crate) pauli_polynomials: VecDeque, + pub(crate) clifford_tableau: CliffordTableau, +} + +impl PauliExponential { + pub fn new( + pauli_polynomials: VecDeque, + clifford_tableau: CliffordTableau, + ) -> Self { + PauliExponential { + pauli_polynomials, + clifford_tableau, + } + } +} diff --git a/synir/src/ir/pauli_exponential.rs b/synir/src/ir/pauli_exponential.rs index 79be5518..9faae0c9 100644 --- a/synir/src/ir/pauli_exponential.rs +++ b/synir/src/ir/pauli_exponential.rs @@ -1,7 +1,7 @@ -use std::collections::VecDeque; - -use crate::architecture::connectivity::Connectivity; -use crate::data_structures::{CliffordTableau, HasAdjoint, PauliPolynomial}; +use crate::{ + architecture::connectivity::Connectivity, + data_structures::{HasAdjoint, PauliExponential}, +}; use crate::ir::{CliffordGates, Gates, Synthesizer}; @@ -12,24 +12,6 @@ use crate::ir::{ pauli_polynomial::{naive::NaivePauliPolynomialSynthesizer, PauliPolynomialSynthStrategy}, }; -#[derive(Default)] -pub struct PauliExponential { - pauli_polynomials: VecDeque, - clifford_tableau: CliffordTableau, -} - -impl PauliExponential { - pub fn new( - pauli_polynomials: VecDeque, - clifford_tableau: CliffordTableau, - ) -> Self { - PauliExponential { - pauli_polynomials, - clifford_tableau, - } - } -} - #[derive(Default)] pub struct PauliExponentialSynthesizer { pauli_strategy: PauliPolynomialSynthStrategy, diff --git a/synir/tests/pauli_exponential.rs b/synir/tests/pauli_exponential.rs index 324aa30d..63729519 100644 --- a/synir/tests/pauli_exponential.rs +++ b/synir/tests/pauli_exponential.rs @@ -3,9 +3,8 @@ mod common; use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; -use synir::data_structures::{CliffordTableau, HasAdjoint, PauliPolynomial}; +use synir::data_structures::{CliffordTableau, HasAdjoint, PauliExponential, PauliPolynomial}; use synir::ir::clifford_tableau::{CliffordTableauSynthStrategy, NaiveCliffordSynthesizer}; -use synir::ir::pauli_exponential::PauliExponential; use synir::ir::pauli_exponential::PauliExponentialSynthesizer; use synir::ir::pauli_polynomial::PauliPolynomialSynthStrategy; use synir::ir::Synthesizer; diff --git a/synpy/src/synthesis.rs b/synpy/src/synthesis.rs index 6e905add..e5a9a6e5 100644 --- a/synpy/src/synthesis.rs +++ b/synpy/src/synthesis.rs @@ -3,15 +3,15 @@ use pyo3::{pyclass, pymethods, PyErr}; use std::ops::Deref; use pyo3::{pyfunction, PyRef, PyResult}; -use synir::data_structures::PropagateClifford; -use synir::data_structures::{CliffordTableau, PauliPolynomial}; -use synir::ir::clifford_tableau::CliffordTableauSynthStrategy; -use synir::ir::pauli_exponential::{PauliExponential, PauliExponentialSynthesizer}; -use synir::ir::pauli_polynomial::PauliPolynomialSynthStrategy; -use synir::ir::CliffordGates; -use synir::ir::Gates; -use synir::ir::Synthesizer; -use synir::IndexType; +use synir::{ + data_structures::{CliffordTableau, PauliExponential, PauliPolynomial, PropagateClifford}, + ir::{ + clifford_tableau::CliffordTableauSynthStrategy, + pauli_exponential::PauliExponentialSynthesizer, + pauli_polynomial::PauliPolynomialSynthStrategy, CliffordGates, Gates, Synthesizer, + }, + IndexType, +}; use crate::validation::validate; From 5765674fd5accb8113900a6d235b7f9f873499d3 Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 12:53:44 +0100 Subject: [PATCH 02/28] 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 d0ef9f23a1f7c9002bc99046de39033e4d309daa Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 13:57:37 +0100 Subject: [PATCH 03/28] 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 | 10 +- synir/tests/pauli_polynomial.rs | 7 +- synpy/src/synthesis.rs | 11 +- synpy/src/wrapper.rs | 7 +- 9 files changed, 195 insertions(+), 72 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 4b5b6088..3d19249b 100644 --- a/synir/src/data_structures.rs +++ b/synir/src/data_structures.rs @@ -1,11 +1,13 @@ use crate::IndexType; +use bitvec::vec::BitVec; +pub mod angle; mod clifford_tableau; mod pauli_exponential; mod pauli_polynomial; mod pauli_string; -use bitvec::vec::BitVec; +pub use angle::Angle; pub use clifford_tableau::CliffordTableau; pub use pauli_exponential::PauliExponential; pub use pauli_polynomial::PauliPolynomial; 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 63729519..2a70760b 100644 --- a/synir/tests/pauli_exponential.rs +++ b/synir/tests/pauli_exponential.rs @@ -3,14 +3,14 @@ mod common; use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; -use synir::data_structures::{CliffordTableau, HasAdjoint, PauliExponential, PauliPolynomial}; +use synir::data_structures::{Angle, CliffordTableau, HasAdjoint, PauliExponential, PauliPolynomial}; use synir::ir::clifford_tableau::{CliffordTableauSynthStrategy, NaiveCliffordSynthesizer}; use synir::ir::pauli_exponential::PauliExponentialSynthesizer; 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); @@ -18,7 +18,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 e5a9a6e5..f021858c 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}; @@ -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(); diff --git a/synpy/src/wrapper.rs b/synpy/src/wrapper.rs index ab24a0e6..51d09cf9 100644 --- a/synpy/src/wrapper.rs +++ b/synpy/src/wrapper.rs @@ -7,12 +7,9 @@ use std::collections::VecDeque; use pyo3::prelude::*; use synir::{ - data_structures::CliffordTableau, + data_structures::{CliffordTableau, PauliExponential}, ir::{ - clifford_tableau::CliffordTableauSynthStrategy, - pauli_exponential::{PauliExponential, PauliExponentialSynthesizer}, - pauli_polynomial::PauliPolynomialSynthStrategy, - CliffordGates, Gates, Synthesizer, + CliffordGates, Gates, Synthesizer, clifford_tableau::CliffordTableauSynthStrategy, pauli_exponential::PauliExponentialSynthesizer, pauli_polynomial::PauliPolynomialSynthStrategy }, }; From 0b0c072803053de3c036faa68d4f179fed0aacfb Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 15:07:30 +0100 Subject: [PATCH 04/28] 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 bb4a97e933287f4f3db454c208542f8ca0589855 Mon Sep 17 00:00:00 2001 From: Qunsheng Huang Date: Tue, 25 Nov 2025 17:09:47 +0100 Subject: [PATCH 05/28] 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 aeef9a5fad83b23cb9c945e9fa8c7dfa9e163ff9 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:24:41 +0100 Subject: [PATCH 06/28] 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 58b17ba4f4ca44c9a3c26d6bcef65d064ebd07b2 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:26:07 +0100 Subject: [PATCH 07/28] 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 c22a6223ecdf3b5d76c7fcbf28a416dbc091d47f Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:39:25 +0100 Subject: [PATCH 08/28] 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 871d268b20336305f017f4c805d1adddd098e767 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:40:17 +0100 Subject: [PATCH 09/28] 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 6eee67497c83a76b3d6895ad9cf39f9de508bd26 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:41:57 +0100 Subject: [PATCH 10/28] 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 55811d1e573c8e6fb14ccdcef36c0bcc2cef1be6 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:42:45 +0100 Subject: [PATCH 11/28] 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 f055ec5f55fd1534910c39e98f1d831e83bef941 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 14:47:22 +0100 Subject: [PATCH 12/28] 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 ec9f42bd6a3f55583a7e6a238cf70732fe7fc152 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 27 Nov 2025 16:00:12 +0100 Subject: [PATCH 13/28] 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 b56a65c1e0263bb0be6490e898341c5cc8f140e1 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 23 Dec 2025 20:32:36 +0100 Subject: [PATCH 14/28] Add new wrapper for Qiskit --- synir/src/ir/clifford_tableau.rs | 1 + synpy/integration_tests/test_qiskit.py | 22 ++++++++++++++++++++-- synpy/src/wrapper.rs | 5 +++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/synir/src/ir/clifford_tableau.rs b/synir/src/ir/clifford_tableau.rs index a43a2718..03a06575 100644 --- a/synir/src/ir/clifford_tableau.rs +++ b/synir/src/ir/clifford_tableau.rs @@ -18,6 +18,7 @@ pub enum CliffordTableauSynthStrategy { Custom(Vec, Vec), } + impl, To, Returns> Synthesizer for T { diff --git a/synpy/integration_tests/test_qiskit.py b/synpy/integration_tests/test_qiskit.py index 95dc3f37..4033dbad 100644 --- a/synpy/integration_tests/test_qiskit.py +++ b/synpy/integration_tests/test_qiskit.py @@ -33,14 +33,32 @@ def test_qiskit_synir() -> None: assert inst.params[0] == reference_param +def test_qiskit_synir() -> None: + qc = QuantumCircuit(2) + synir = QiskitSynIR(qc) + + synir.s(0) + synir.v(0) + synir.s_dgr(0) + synir.v_dgr(0) + synir.x(0) + synir.y(0) + synir.z(0) + synir.h(0) + synir.cx(0, 1) + synir.cz(0, 1) + synir.rx(0, 1.23) + synir.ry(0, 1.23) + synir.rz(0, 1.23) + + def test_qiskit_bell() -> None: qc = QuantumCircuit(2) qc.h(0) qc.cx(0, 1) - cliff = Clifford(qc) plugin = SynPyCliffordPlugin() circ = plugin.run(cliff, None, None, []) - assert circ == qc + assert circ == qc \ No newline at end of file diff --git a/synpy/src/wrapper.rs b/synpy/src/wrapper.rs index 51d09cf9..1d4566f3 100644 --- a/synpy/src/wrapper.rs +++ b/synpy/src/wrapper.rs @@ -9,7 +9,9 @@ use pyo3::prelude::*; use synir::{ data_structures::{CliffordTableau, PauliExponential}, ir::{ - CliffordGates, Gates, Synthesizer, clifford_tableau::CliffordTableauSynthStrategy, pauli_exponential::PauliExponentialSynthesizer, pauli_polynomial::PauliPolynomialSynthStrategy + clifford_tableau::CliffordTableauSynthStrategy, + pauli_exponential::PauliExponentialSynthesizer, + pauli_polynomial::PauliPolynomialSynthStrategy, CliffordGates, Gates, Synthesizer, }, }; @@ -44,7 +46,6 @@ impl PyPauliExponential { _ => panic!("Unknown Pauli polynomial synthesis strategy: {}", strategy), } } - pub fn set_tableau_strategy(&mut self, strategy: String) { match strategy.as_str() { "Naive" => self.tableau_strategy = CliffordTableauSynthStrategy::Naive, From 5b47d83ebb09a86ed3ba0c65feb7ccfa45985939 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 26 Dec 2025 17:10:53 +0100 Subject: [PATCH 15/28] Fixup --- synpy/src/synthesis.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/synpy/src/synthesis.rs b/synpy/src/synthesis.rs index f021858c..c6298a77 100644 --- a/synpy/src/synthesis.rs +++ b/synpy/src/synthesis.rs @@ -1,6 +1,5 @@ use pyo3::exceptions::PyException; use pyo3::{pyclass, pymethods, PyErr}; -use synir::data_structures::Angle; use std::ops::Deref; use pyo3::{pyfunction, PyRef, PyResult}; From f264893a995a029e1e5bfe0554e9e0b61b11a1a7 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 28 Dec 2025 14:36:34 +0100 Subject: [PATCH 16/28] Implement PropagateClifford for PauliExponential --- .../src/data_structures/pauli_exponential.rs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/synir/src/data_structures/pauli_exponential.rs b/synir/src/data_structures/pauli_exponential.rs index f9252d5a..4fd9ebf7 100644 --- a/synir/src/data_structures/pauli_exponential.rs +++ b/synir/src/data_structures/pauli_exponential.rs @@ -1,6 +1,6 @@ use std::collections::VecDeque; -use crate::data_structures::{CliffordTableau, PauliPolynomial}; +use crate::data_structures::{CliffordTableau, PauliPolynomial, PropagateClifford}; #[derive(Default)] pub struct PauliExponential { @@ -18,4 +18,32 @@ impl PauliExponential { clifford_tableau, } } + + pub fn chains(&mut self) -> &mut VecDeque{ + &mut self.pauli_polynomials + } + + pub fn size(&self) -> usize{ + self.clifford_tableau.size() + } } + +impl PropagateClifford for PauliExponential{ + fn cx(&mut self, control: crate::IndexType, target: crate::IndexType) -> &mut Self { + self.pauli_polynomials.cx(control, target); + self.clifford_tableau.cx(control, target); + self + } + + fn s(&mut self, target: crate::IndexType) -> &mut Self { + self.pauli_polynomials.s(target); + self.clifford_tableau.s(target); + self + } + + fn v(&mut self, target: crate::IndexType) -> &mut Self { + self.pauli_polynomials.v(target); + self.clifford_tableau.v(target); + self + } +} \ No newline at end of file From 5d88c7569ba14f00960f5fe0f18379361a88addc Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 28 Dec 2025 14:37:17 +0100 Subject: [PATCH 17/28] Implement full loop for qiskit to IR to qiskit --- synir/src/data_structures/pauli_polynomial.rs | 13 ++++ synpy/python/synpy/qiskit/plugin.py | 20 ++++++- synpy/src/synthesis.rs | 1 + synpy/src/wrapper.rs | 59 ++++++++++++++++++- synpy/src/wrapper/qiskit.rs | 6 +- 5 files changed, 94 insertions(+), 5 deletions(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index 02bc1357..b6af28f1 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -62,6 +62,18 @@ impl PauliPolynomial { self.angles[i] } + pub fn extend_z(&mut self, target: usize, angle: f64){ + for (i, chain) in self.chains.iter_mut().enumerate(){ + if i == target{ + chain.z.push(true); + }else{ + chain.z.push(false); + } + chain.x.push(false); + } + self.angles.push(Angle::Arbitrary(angle)); + } + pub fn commutes_with(&self, other: &PauliPolynomial) -> bool { let size = self.size(); assert_eq!(size, other.size()); @@ -95,6 +107,7 @@ impl PauliPolynomial { impl PropagateClifford for PauliPolynomial { fn cx(&mut self, control: IndexType, target: IndexType) -> &mut Self { let mut bit_mask: BitVec = BitVec::repeat(true, self.length()); + println!("Chains length: {:?}", self.chains.len()); let [control, target] = self.chains.get_disjoint_mut([control, target]).unwrap(); diff --git a/synpy/python/synpy/qiskit/plugin.py b/synpy/python/synpy/qiskit/plugin.py index d41ac09a..822d7595 100644 --- a/synpy/python/synpy/qiskit/plugin.py +++ b/synpy/python/synpy/qiskit/plugin.py @@ -1,12 +1,13 @@ from qiskit.transpiler import CouplingMap, Target from qiskit.transpiler.passes.synthesis.plugin import HighLevelSynthesisPlugin from qiskit.quantum_info import Clifford -from qiskit import QuantumCircuit +from qiskit import QuantumCircuit, transpile -from synpy.synpy_rust import PyCliffordTableau +from synpy.synpy_rust import PyCliffordTableau, PyPauliExponential from synpy.utils import pycommand_to_qasm + class SynPyCliffordPlugin(HighLevelSynthesisPlugin): def __init__(self) -> None: super().__init__() @@ -26,3 +27,18 @@ def run(self, clifford: Clifford, coupling_map: CouplingMap, target: Target, qub commands = synpy_tableau.synthesize() qasm = pycommand_to_qasm(n, commands) return QuantumCircuit.from_qasm_str(qasm) + +def qiskit_to_synir(circuit:QuantumCircuit) -> PyPauliExponential: + new_circuit = transpile(circuit, basis_gates=['cx', 'h', 'rz']) + pe = PyPauliExponential(new_circuit.num_qubits) + + for gate in reversed(new_circuit.data): + if gate.name == "cx": + pe.add_cx(gate.qubits[0]._index, gate.qubits[1]._index) + elif gate.name == "h": + pe.add_h(gate.qubits[0]._index) + elif gate.name == "rz": + pe.add_rz(gate.qubits[0]._index, gate.params[0]) + else: + raise Exception("Gate is not supported") + return pe \ No newline at end of file diff --git a/synpy/src/synthesis.rs b/synpy/src/synthesis.rs index c6298a77..f021858c 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.rs b/synpy/src/wrapper.rs index 1d4566f3..9c111307 100644 --- a/synpy/src/wrapper.rs +++ b/synpy/src/wrapper.rs @@ -7,7 +7,9 @@ use std::collections::VecDeque; use pyo3::prelude::*; use synir::{ - data_structures::{CliffordTableau, PauliExponential}, + data_structures::{ + Angle, CliffordTableau, PauliExponential, PauliPolynomial, PropagateClifford, + }, ir::{ clifford_tableau::CliffordTableauSynthStrategy, pauli_exponential::PauliExponentialSynthesizer, @@ -53,6 +55,59 @@ impl PyPauliExponential { _ => panic!("Unknown Clifford tableau synthesis strategy: {}", strategy), } } + + pub fn add_h(&mut self, target: usize) { + self.pe.h(target); + } + + pub fn add_s(&mut self, target: usize) { + self.pe.s(target); + } + + pub fn add_s_dgr(&mut self, target: usize) { + self.pe.s_dgr(target); + } + + pub fn add_x(&mut self, target: usize) { + self.pe.x(target); + } + + pub fn add_y(&mut self, target: usize) { + self.pe.y(target); + } + + pub fn add_z(&mut self, target: usize) { + self.pe.z(target); + } + + pub fn add_cx(&mut self, control: usize, target: usize) { + self.pe.cx(control, target); + } + + pub fn add_rz(&mut self, target: usize, angle: f64) { + let size = self.pe.size(); + let ppvec = self.pe.chains(); + if let Some(pp) = ppvec.front_mut() { + if pp.chain(target).x_weight() != 0 { + pp.extend_z(target, angle); + } + return; + } + let newpp = PauliPolynomial::from_hamiltonian(vec![(&to_pauli_component(size, &target, 'Z'), Angle::Arbitrary(angle))]); + ppvec.push_front(newpp); + } +} + +fn to_pauli_component(size: usize, target: &usize, pauli: char) -> String { + let mut term = String::new(); + for i in 0..size { + if i == *target { + term.push(pauli); + } else { + term.push('I'); + } + } + term } pub fn synthesize(pe: &mut PyPauliExponential, circuit: &mut G) @@ -64,5 +119,5 @@ where pe.tableau_strategy.clone(), ); let pe = std::mem::take(&mut pe.pe); - synth.synthesize(pe, circuit) + synth.synthesize(pe, circuit); } diff --git a/synpy/src/wrapper/qiskit.rs b/synpy/src/wrapper/qiskit.rs index 7e7f7606..da4a139c 100644 --- a/synpy/src/wrapper/qiskit.rs +++ b/synpy/src/wrapper/qiskit.rs @@ -1,7 +1,7 @@ extern crate pyo3; extern crate pyo3_ffi; -use pyo3::prelude::*; +use pyo3::{intern, prelude::*}; use synir::ir::{CliffordGates, Gates}; #[pyclass] @@ -18,6 +18,10 @@ impl QiskitSynIR { } } + fn get_circuit(&self, py: Python) -> Py { + self.circuit.clone_ref(py) + } + pub fn s(&mut self, target: synir::IndexType) { Python::attach(|py| -> PyResult<()> { self.circuit.call_method1(py, "s", (target,))?; From ce11a009734fa3c9a84452b3a0caed6344b43329 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 28 Dec 2025 14:37:29 +0100 Subject: [PATCH 18/28] Add __init__.py into qiskit folder --- synpy/python/synpy/qiskit/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 synpy/python/synpy/qiskit/__init__.py diff --git a/synpy/python/synpy/qiskit/__init__.py b/synpy/python/synpy/qiskit/__init__.py new file mode 100644 index 00000000..e69de29b From 87279187919709699d90f12238703d4c24fc165a Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 28 Dec 2025 14:37:43 +0100 Subject: [PATCH 19/28] Add test for loop --- synpy/integration_tests/test_qiskit.py | 33 +++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/synpy/integration_tests/test_qiskit.py b/synpy/integration_tests/test_qiskit.py index 4033dbad..3e0cf499 100644 --- a/synpy/integration_tests/test_qiskit.py +++ b/synpy/integration_tests/test_qiskit.py @@ -1,7 +1,8 @@ -from qiskit.quantum_info import Clifford +from qiskit.quantum_info import Clifford, Operator from qiskit import QuantumCircuit -from synpy.qiskit.plugin import SynPyCliffordPlugin + +from synpy.qiskit.plugin import SynPyCliffordPlugin, qiskit_to_synir from synpy.synpy_rust import QiskitSynIR @@ -61,4 +62,30 @@ def test_qiskit_bell() -> None: plugin = SynPyCliffordPlugin() circ = plugin.run(cliff, None, None, []) - assert circ == qc \ No newline at end of file + assert circ == qc + +def test_qiskit_loop() -> None: + circuit = QuantumCircuit(3) + circuit.h(0) + circuit.cx(0, 1) + circuit.rz(1.5, 1) + + import synpy + print(synpy.__file__) + print(dir(synpy.qiskit.plugin)) + + pe_wrap = qiskit_to_synir(circuit) + + synir_result = QiskitSynIR(QuantumCircuit(3)) + pe_wrap.synthesize_to_qiskit(synir_result) + circuit = synir_result.get_circuit() + + op = Operator.from_circuit(circuit) + + sample_circuit = QuantumCircuit(3) + sample_circuit.h(0) + sample_circuit.cx(0, 1) + sample_circuit.rz(1.5, 1) + + op2 = Operator.from_circuit(sample_circuit) + assert op.equiv(op2) \ No newline at end of file From 53b68ac415b00097b55b29902fbc36bc2b644107 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sun, 28 Dec 2025 14:38:53 +0100 Subject: [PATCH 20/28] Rename op to op1 for ease of reading --- synpy/integration_tests/test_qiskit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/synpy/integration_tests/test_qiskit.py b/synpy/integration_tests/test_qiskit.py index 3e0cf499..67628d7a 100644 --- a/synpy/integration_tests/test_qiskit.py +++ b/synpy/integration_tests/test_qiskit.py @@ -80,7 +80,7 @@ def test_qiskit_loop() -> None: pe_wrap.synthesize_to_qiskit(synir_result) circuit = synir_result.get_circuit() - op = Operator.from_circuit(circuit) + op1 = Operator.from_circuit(circuit) sample_circuit = QuantumCircuit(3) sample_circuit.h(0) @@ -88,4 +88,4 @@ def test_qiskit_loop() -> None: sample_circuit.rz(1.5, 1) op2 = Operator.from_circuit(sample_circuit) - assert op.equiv(op2) \ No newline at end of file + assert op1.equiv(op2) \ No newline at end of file From 17aa3fe0355c43812ffad1ec03043e913d4edfcc Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 14 Jan 2026 17:18:32 +0100 Subject: [PATCH 21/28] Format wrapper.rs --- synpy/src/wrapper.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/synpy/src/wrapper.rs b/synpy/src/wrapper.rs index 9c111307..6f991a14 100644 --- a/synpy/src/wrapper.rs +++ b/synpy/src/wrapper.rs @@ -93,7 +93,10 @@ impl PyPauliExponential { } return; } - let newpp = PauliPolynomial::from_hamiltonian(vec![(&to_pauli_component(size, &target, 'Z'), Angle::Arbitrary(angle))]); + let newpp = PauliPolynomial::from_hamiltonian(vec![( + &to_pauli_component(size, &target, 'Z'), + Angle::Arbitrary(angle), + )]); ppvec.push_front(newpp); } } From ff61d9e6a830be0d4d344a5c19c237febf477c32 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 14 Jan 2026 17:30:26 +0100 Subject: [PATCH 22/28] Format files --- synir/src/data_structures/pauli_exponential.rs | 8 ++++---- synir/src/data_structures/pauli_polynomial.rs | 8 ++++---- synir/src/ir/clifford_tableau.rs | 1 - 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/synir/src/data_structures/pauli_exponential.rs b/synir/src/data_structures/pauli_exponential.rs index 4fd9ebf7..84924dc1 100644 --- a/synir/src/data_structures/pauli_exponential.rs +++ b/synir/src/data_structures/pauli_exponential.rs @@ -19,16 +19,16 @@ impl PauliExponential { } } - pub fn chains(&mut self) -> &mut VecDeque{ + pub fn chains(&mut self) -> &mut VecDeque { &mut self.pauli_polynomials } - pub fn size(&self) -> usize{ + pub fn size(&self) -> usize { self.clifford_tableau.size() } } -impl PropagateClifford for PauliExponential{ +impl PropagateClifford for PauliExponential { fn cx(&mut self, control: crate::IndexType, target: crate::IndexType) -> &mut Self { self.pauli_polynomials.cx(control, target); self.clifford_tableau.cx(control, target); @@ -46,4 +46,4 @@ impl PropagateClifford for PauliExponential{ self.clifford_tableau.v(target); self } -} \ No newline at end of file +} diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index b6af28f1..68ae34ce 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -62,11 +62,11 @@ impl PauliPolynomial { self.angles[i] } - pub fn extend_z(&mut self, target: usize, angle: f64){ - for (i, chain) in self.chains.iter_mut().enumerate(){ - if i == target{ + pub fn extend_z(&mut self, target: usize, angle: f64) { + for (i, chain) in self.chains.iter_mut().enumerate() { + if i == target { chain.z.push(true); - }else{ + } else { chain.z.push(false); } chain.x.push(false); diff --git a/synir/src/ir/clifford_tableau.rs b/synir/src/ir/clifford_tableau.rs index 03a06575..a43a2718 100644 --- a/synir/src/ir/clifford_tableau.rs +++ b/synir/src/ir/clifford_tableau.rs @@ -18,7 +18,6 @@ pub enum CliffordTableauSynthStrategy { Custom(Vec, Vec), } - impl, To, Returns> Synthesizer for T { From 9c54decb069bc7111c7124dfc7f83688d23ec2e0 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 14 Jan 2026 17:45:02 +0100 Subject: [PATCH 23/28] Add trailing white-space --- synpy/integration_tests/test_qiskit.py | 27 +++++--------------------- synpy/python/synpy/qiskit/plugin.py | 10 +++++----- synpy/src/synthesis.rs | 2 +- 3 files changed, 11 insertions(+), 28 deletions(-) diff --git a/synpy/integration_tests/test_qiskit.py b/synpy/integration_tests/test_qiskit.py index 67628d7a..61e1f28b 100644 --- a/synpy/integration_tests/test_qiskit.py +++ b/synpy/integration_tests/test_qiskit.py @@ -34,25 +34,6 @@ def test_qiskit_synir() -> None: assert inst.params[0] == reference_param -def test_qiskit_synir() -> None: - qc = QuantumCircuit(2) - synir = QiskitSynIR(qc) - - synir.s(0) - synir.v(0) - synir.s_dgr(0) - synir.v_dgr(0) - synir.x(0) - synir.y(0) - synir.z(0) - synir.h(0) - synir.cx(0, 1) - synir.cz(0, 1) - synir.rx(0, 1.23) - synir.ry(0, 1.23) - synir.rz(0, 1.23) - - def test_qiskit_bell() -> None: qc = QuantumCircuit(2) qc.h(0) @@ -64,6 +45,7 @@ def test_qiskit_bell() -> None: assert circ == qc + def test_qiskit_loop() -> None: circuit = QuantumCircuit(3) circuit.h(0) @@ -71,15 +53,16 @@ def test_qiskit_loop() -> None: circuit.rz(1.5, 1) import synpy + print(synpy.__file__) print(dir(synpy.qiskit.plugin)) pe_wrap = qiskit_to_synir(circuit) - + synir_result = QiskitSynIR(QuantumCircuit(3)) pe_wrap.synthesize_to_qiskit(synir_result) circuit = synir_result.get_circuit() - + op1 = Operator.from_circuit(circuit) sample_circuit = QuantumCircuit(3) @@ -88,4 +71,4 @@ def test_qiskit_loop() -> None: sample_circuit.rz(1.5, 1) op2 = Operator.from_circuit(sample_circuit) - assert op1.equiv(op2) \ No newline at end of file + assert op1.equiv(op2) diff --git a/synpy/python/synpy/qiskit/plugin.py b/synpy/python/synpy/qiskit/plugin.py index 822d7595..9bd6855a 100644 --- a/synpy/python/synpy/qiskit/plugin.py +++ b/synpy/python/synpy/qiskit/plugin.py @@ -7,7 +7,6 @@ from synpy.utils import pycommand_to_qasm - class SynPyCliffordPlugin(HighLevelSynthesisPlugin): def __init__(self) -> None: super().__init__() @@ -28,10 +27,11 @@ def run(self, clifford: Clifford, coupling_map: CouplingMap, target: Target, qub qasm = pycommand_to_qasm(n, commands) return QuantumCircuit.from_qasm_str(qasm) -def qiskit_to_synir(circuit:QuantumCircuit) -> PyPauliExponential: - new_circuit = transpile(circuit, basis_gates=['cx', 'h', 'rz']) + +def qiskit_to_synir(circuit: QuantumCircuit) -> PyPauliExponential: + new_circuit = transpile(circuit, basis_gates=["cx", "h", "rz"]) pe = PyPauliExponential(new_circuit.num_qubits) - + for gate in reversed(new_circuit.data): if gate.name == "cx": pe.add_cx(gate.qubits[0]._index, gate.qubits[1]._index) @@ -41,4 +41,4 @@ def qiskit_to_synir(circuit:QuantumCircuit) -> PyPauliExponential: pe.add_rz(gate.qubits[0]._index, gate.params[0]) else: raise Exception("Gate is not supported") - return pe \ No newline at end of file + return pe diff --git a/synpy/src/synthesis.rs b/synpy/src/synthesis.rs index f021858c..8dc4898e 100644 --- a/synpy/src/synthesis.rs +++ b/synpy/src/synthesis.rs @@ -1,7 +1,7 @@ use pyo3::exceptions::PyException; use pyo3::{pyclass, pymethods, PyErr}; -use synir::data_structures::Angle; use std::ops::Deref; +use synir::data_structures::Angle; use pyo3::{pyfunction, PyRef, PyResult}; use synir::{ From 8d13e70b98bb5b854e544a7e37c85e3b91ac0ab5 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 15 Jan 2026 10:45:32 +0100 Subject: [PATCH 24/28] Removed unneeded copy from test_qiskit_loop --- synpy/integration_tests/test_qiskit.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/synpy/integration_tests/test_qiskit.py b/synpy/integration_tests/test_qiskit.py index 61e1f28b..488bf8bf 100644 --- a/synpy/integration_tests/test_qiskit.py +++ b/synpy/integration_tests/test_qiskit.py @@ -51,24 +51,15 @@ def test_qiskit_loop() -> None: circuit.h(0) circuit.cx(0, 1) circuit.rz(1.5, 1) - - import synpy - - print(synpy.__file__) - print(dir(synpy.qiskit.plugin)) + sample_circuit = circuit.copy() pe_wrap = qiskit_to_synir(circuit) - synir_result = QiskitSynIR(QuantumCircuit(3)) + synir_result = QiskitSynIR(circuit.copy_empty_like()) pe_wrap.synthesize_to_qiskit(synir_result) circuit = synir_result.get_circuit() op1 = Operator.from_circuit(circuit) - - sample_circuit = QuantumCircuit(3) - sample_circuit.h(0) - sample_circuit.cx(0, 1) - sample_circuit.rz(1.5, 1) - op2 = Operator.from_circuit(sample_circuit) + assert op1.equiv(op2) From ad35505a6504ed94fec952c2d45808619ffb59e4 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 15 Jan 2026 10:46:00 +0100 Subject: [PATCH 25/28] Remove print statements --- synir/src/data_structures/pauli_polynomial.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index 68ae34ce..7b1d239f 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -107,7 +107,6 @@ impl PauliPolynomial { impl PropagateClifford for PauliPolynomial { fn cx(&mut self, control: IndexType, target: IndexType) -> &mut Self { let mut bit_mask: BitVec = BitVec::repeat(true, self.length()); - println!("Chains length: {:?}", self.chains.len()); let [control, target] = self.chains.get_disjoint_mut([control, target]).unwrap(); From bd769ca0abd473633d8cfbb1901b2215eb3a59f6 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 15 Jan 2026 15:31:22 +0100 Subject: [PATCH 26/28] Set return of compose_gadget to Result --- synir/src/data_structures/clifford_tableau.rs | 3 ++- synir/tests/ct_synthesis/ct_compose.rs | 27 +++++++------------ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/synir/src/data_structures/clifford_tableau.rs b/synir/src/data_structures/clifford_tableau.rs index 94c0e99f..1c031a37 100644 --- a/synir/src/data_structures/clifford_tableau.rs +++ b/synir/src/data_structures/clifford_tableau.rs @@ -193,7 +193,7 @@ 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)) { + pub fn compose_gadget(&mut self, rhs: (PauliString, Angle)) -> Result<(), String> { let (pauli_string, angle) = rhs; let size = self.size(); assert_eq!( @@ -272,6 +272,7 @@ impl CliffordTableau { PauliLetter::Z => {} } } + Ok(()) } pub fn permute(&mut self, permutation_vector: Vec) { diff --git a/synir/tests/ct_synthesis/ct_compose.rs b/synir/tests/ct_synthesis/ct_compose.rs index 0d08c3ea..e7fbae63 100644 --- a/synir/tests/ct_synthesis/ct_compose.rs +++ b/synir/tests/ct_synthesis/ct_compose.rs @@ -1,15 +1,6 @@ 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 crate::common::mock_circuit::{check_mock_equals_clifford_tableau, MockCircuit, MockCommand}; use itertools::Itertools; use synir::data_structures::{Angle, CliffordTableau, PropagateClifford}; use synir::ir::clifford_tableau::CallbackCliffordSynthesizer; @@ -19,7 +10,7 @@ fn run_synthesizer(clifford_tableau: &CliffordTableau) -> (MockCircuit, Clifford 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(); + let custom_rows = (0..clifford_tableau.size()).collect_vec(); // custom_rows.shuffle(&mut rng); let mut synthesizer = CallbackCliffordSynthesizer::custom_pivot(custom_columns, custom_rows); @@ -45,7 +36,7 @@ macro_rules! test_clifford { fn compose_x_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("XI"), Angle::from_pi4_rotation(2), )); @@ -54,7 +45,7 @@ fn compose_x_gadget() -> CliffordTableau { fn compose_z_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("ZI"), Angle::from_pi4_rotation(4), )); @@ -63,7 +54,7 @@ fn compose_z_gadget() -> CliffordTableau { fn compose_y_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("YI"), Angle::from_pi4_rotation(6), )); @@ -80,7 +71,7 @@ test_clifford!( fn compose_xx_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("XX"), Angle::from_pi4_rotation(2), )); @@ -89,7 +80,7 @@ fn compose_xx_gadget() -> CliffordTableau { fn compose_zz_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("ZZ"), Angle::from_pi4_rotation(2), )); @@ -98,7 +89,7 @@ fn compose_zz_gadget() -> CliffordTableau { fn compose_yy_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(2); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("YY"), Angle::from_pi4_rotation(2), )); @@ -146,7 +137,7 @@ test_clifford!( fn compose_complex_gadget() -> CliffordTableau { let mut ct = CliffordTableau::new(3); - ct.compose_gadget(( + let _ = ct.compose_gadget(( synir::data_structures::PauliString::from_text("XYZ"), Angle::from_pi4_rotation(2), )); From 104c6826f23e77584114581d51aba7b1625f8368 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 15 Jan 2026 15:31:37 +0100 Subject: [PATCH 27/28] Include pi4 rotations in PauliPolynomial tests --- synir/src/data_structures/pauli_polynomial.rs | 99 ++++++++++++++++--- 1 file changed, 84 insertions(+), 15 deletions(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index 7b1d239f..4a5804e3 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -260,7 +260,11 @@ 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 = Angle::from_angles(&[0.3, 0.7, 0.12]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.12), + ]; PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -286,7 +290,11 @@ mod tests { let pg2_ref = PauliString::from_text("ZXY"); // YIX let pg3_ref = PauliString::from_text("YIX"); - let angles_ref = Angle::from_angles(&[0.3, -0.7, -0.12]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(6), + Angle::from_angle(-0.12), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -311,7 +319,11 @@ mod tests { let pg2_ref = PauliString::from_text("YZX"); // YIX -> ZIX let pg3_ref = PauliString::from_text("ZIX"); - let angles_ref = Angle::from_angles(&[-0.3, 0.7, 0.12]); + let angles_ref = vec![ + Angle::from_angle(-0.3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.12), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -336,7 +348,11 @@ mod tests { let pg2_ref = PauliString::from_text("ZXY"); // YIX -> XI(-Y) let pg3_ref = PauliString::from_text("XIY"); - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.12), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -361,7 +377,11 @@ mod tests { let pg2_ref = PauliString::from_text("YZX"); // YIX -> (-Z)IX let pg3_ref = PauliString::from_text("ZIX"); - let angles_ref = Angle::from_angles(&[-0.3, -0.7, 0.12]); + let angles_ref = vec![ + Angle::from_angle(-0.3), + Angle::from_pi4_rotation(6), + Angle::from_angle(0.12), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -386,7 +406,11 @@ mod tests { let pg2_ref = PauliString::from_text("XYZ"); // YIX - let pg3_ref = PauliString::from_text("YIX"); - let angles_ref = Angle::from_angles(&[0.3, -0.7, -0.12]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(6), + Angle::from_angle(-0.12), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -408,7 +432,12 @@ mod tests { let pg2_ref = PauliString::from_text("IXYZ"); let pg3_ref = PauliString::from_text("YIXZ"); - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.15), + ]; PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], @@ -435,7 +464,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -461,7 +495,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, -1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, -0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(2), + Angle::from_angle(-0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -487,7 +526,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, -1, 1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, -0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(6), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -513,7 +557,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -540,7 +589,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -566,7 +620,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, -1, 1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, -0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(6), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -592,7 +651,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, -1, 1, 1] - let angles_ref = Angle::from_angles(&[0.3, -0.7, 0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(5), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, @@ -618,7 +682,12 @@ mod tests { // YIXZ let pg3_ref = PauliString::from_text("YIXZ"); // [1, 1, 1, 1] - let angles_ref = Angle::from_angles(&[0.3, 0.7, 0.12, 0.15]); + let angles_ref = vec![ + Angle::from_angle(0.3), + Angle::from_pi4_rotation(3), + Angle::from_pi4_rotation(2), + Angle::from_angle(0.15), + ]; let pp_ref = PauliPolynomial { chains: vec![pg1_ref, pg2_ref, pg3_ref], angles: angles_ref, From 2da3a5352eba9a8646d5a2c67fb79cb47b201f51 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 15 Jan 2026 15:31:49 +0100 Subject: [PATCH 28/28] Format pauli_exponential.rs --- synir/tests/pauli_exponential.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/synir/tests/pauli_exponential.rs b/synir/tests/pauli_exponential.rs index 2a70760b..cc9c89c6 100644 --- a/synir/tests/pauli_exponential.rs +++ b/synir/tests/pauli_exponential.rs @@ -3,7 +3,9 @@ mod common; use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; -use synir::data_structures::{Angle, CliffordTableau, HasAdjoint, PauliExponential, PauliPolynomial}; +use synir::data_structures::{ + Angle, CliffordTableau, HasAdjoint, PauliExponential, PauliPolynomial, +}; use synir::ir::clifford_tableau::{CliffordTableauSynthStrategy, NaiveCliffordSynthesizer}; use synir::ir::pauli_exponential::PauliExponentialSynthesizer; use synir::ir::pauli_polynomial::PauliPolynomialSynthStrategy;