diff --git a/synir/examples/print_data_structure.rs b/synir/examples/print_data_structure.rs new file mode 100644 index 00000000..c003a15a --- /dev/null +++ b/synir/examples/print_data_structure.rs @@ -0,0 +1,107 @@ +use std::collections::VecDeque; + +use bitvec::bitvec; +use bitvec::prelude::Lsb0; +use synir::data_structures::{CliffordTableau, PauliPolynomial, PauliString}; +use synir::ir::pauli_exponential::PauliExponential; + +fn main() { + // test tableaus + // Stab: ZZZ, -YIY, XIX + // Destab: -IXI, XXI, IYY + // qubit 1x: ZYI + // qubit 1z: IZZ + let pauli_1 = PauliString::from_text("ZYIIZZ"); + // qubit 2x: ZIX + // qubit 2z: XII + let pauli_2 = PauliString::from_text("ZIXXII"); + // qubit 3x: ZYY + // qubit 3z: IIZ + let pauli_3 = PauliString::from_text("ZYYIIZ"); + // qubit 4x: ZYX + // qubit 4z: IZI + let signs = bitvec![0, 1, 0, 1, 0, 0, 1, 1]; + let my_tableaux = CliffordTableau::from_parts(vec![pauli_1, pauli_2, pauli_3], signs); + println!("Test clifford tableaux small"); + println!("{}", my_tableaux); + let big_tableaux = CliffordTableau::new(20); + println!("Test clifford tableaux big"); + println!("{}", big_tableaux); + + let ham = vec![("IXYZ", 0.3), ("XXII", 0.7), ("YYII", 0.12)]; + //pauli chain IXY; XXY; YII; ZII + let pp = PauliPolynomial::from_hamiltonian(ham); + println!("Test pauli polynomial"); + println!("{}", pp); + + let ham1 = vec![("IZZZ", 0.3)]; + let pp1 = PauliPolynomial::from_hamiltonian(ham1); + let ct = CliffordTableau::new(4); + let ham2 = vec![("XIII", 0.7)]; + let pp2 = PauliPolynomial::from_hamiltonian(ham2); + let pe = PauliExponential::new(VecDeque::from([pp, pp1, pp2]), ct); + + println!("Test pauli exponential"); + println!("{}", pe); + print!("\n\n"); + println!("Testing empty data structures"); + let result = std::panic::catch_unwind(|| { + test_empty_clifford_tableau(); + }); + if result.is_err() { + println!("test_empty_clifford_tableau panicked"); + } + + println!("Next test"); + let result = std::panic::catch_unwind(|| { + test_empty_pauli_polynomial(); + }); + if result.is_err() { + println!("test_empty_pauli_polynomial panicked"); + } + println!(" Next test"); + + let result = std::panic::catch_unwind(|| { + test_empty_pauli_exponential(); + }); + if result.is_err() { + println!("test_empty_pauli_exponential panicked"); + } + + let result = std::panic::catch_unwind(|| { + test_pauli_exponential_with_empty_polynomial(); + }); + if result.is_err() { + println!("test_pauli_exponential_with_empty_polynomial panicked"); + } +} + +fn test_empty_clifford_tableau() { + let empty_tableau = CliffordTableau::new(0); + println!("Empty Clifford Tableau:"); + println!("{}", empty_tableau); +} + +fn test_empty_pauli_polynomial() { + let empty_pauli_polynomial = PauliPolynomial::empty(5); + println!("Empty Pauli Polynomial:"); + println!("{}", empty_pauli_polynomial); +} + +fn test_empty_pauli_exponential() { + let empty_ct = CliffordTableau::new(5); + let empty_pp = PauliPolynomial::empty(5); + let empty_pe = PauliExponential::new(VecDeque::from([]), empty_ct); + println!("Empty Pauli Exponential:"); + print!("{}", empty_pe); +} + +fn test_pauli_exponential_with_empty_polynomial() { + let ct = CliffordTableau::new(5); + let empty_pp = PauliPolynomial::empty(5); + let ham2 = vec![("XIIIY", 0.7)]; + let pp2 = PauliPolynomial::from_hamiltonian(ham2); + let pe = PauliExponential::new(VecDeque::from([empty_pp, pp2]), ct); + println!("Pauli Exponential with empty and non-empty pp:"); + print!("{}", pe); +} diff --git a/synir/src/data_structures/clifford_tableau.rs b/synir/src/data_structures/clifford_tableau.rs index 3c7ac932..ea385ba0 100644 --- a/synir/src/data_structures/clifford_tableau.rs +++ b/synir/src/data_structures/clifford_tableau.rs @@ -4,6 +4,8 @@ use std::fmt; use std::iter::zip; use std::ops::Mul; +use crate::data_structures::PauliLetter; + use super::HasAdjoint; use super::{ pauli_string::{cx, PauliString}, @@ -41,6 +43,10 @@ impl CliffordTableau { self.size } + pub fn signs(&self) -> &BitVec { + &self.signs + } + pub(crate) fn x_signs(&self) -> BitVec { let n = self.size(); self.signs[0..n].to_bitvec() @@ -59,6 +65,71 @@ impl CliffordTableau { rhs.prepend(self) } + pub(crate) fn get_line_string(&self, i: usize) -> String { + let number_of_column = self.pauli_columns.len(); + let mut out = String::new(); + + //add sign for stabilizers + out.push(get_pauli_sign(self.signs[i])); + out.push(' '); + + //add stabilizers pauli + for column in self.pauli_columns.iter() { + let ch = get_pauli_char(&column.pauli(i)); + out.push(ch); + out.push(' '); + } + + if number_of_column <= 5 { + let space_left = 12 - 2 * number_of_column; + for _ in 0..space_left { + out.push(' '); + } + } + + //add separator between stabilizers and destabilizers + out.push_str("| "); + + //add sign for destabilizers + out.push(get_pauli_sign(self.signs[i + self.size()])); + out.push(' '); + + // add destabilizers pauli + for column in self.pauli_columns.iter() { + let ch = get_pauli_char(&column.pauli(i + self.size())); + out.push(ch); + out.push(' '); + } + + //add space due to the length of "destabilizers" string + if number_of_column <= 6 { + let space_left = 10 - 2 * self.pauli_columns.len(); + for _ in 0..space_left { + out.push(' '); + } + } + + out.push('|'); + out + } + pub(crate) fn get_first_line_string(&self) -> String { + let number_of_column = self.pauli_columns.len(); + let mut out = String::new(); + if number_of_column <= 5 { + out.push_str(" Destabilizers | Stabilizers |\n"); + } else { + out.push_str(" Destabilizers"); + for _ in 0..number_of_column - 6 { + out.push_str(" "); + } + out.push_str(" | Stabilizers"); + for _ in 0..number_of_column - 5 { + out.push_str(" "); + } + out.push_str(" |\n"); + } + out + } /// Implements algorithms from https://doi.org/10.22331/q-2022-06-13-734 and Qiskit Clifford implementation pub(crate) fn prepend(&self, lhs: &Self) -> Self { let size = self.size(); @@ -299,20 +370,47 @@ impl Mul for CliffordTableau { impl fmt::Display for CliffordTableau { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - writeln!(f, "CliffordTableau({})", self.size())?; - for pauli_column in self.pauli_columns.iter() { - writeln!(f, "{}", pauli_column)?; - } - let mut sign_str = String::new(); - for bit in self.signs.iter() { - match *bit { - true => sign_str.push('-'), - false => sign_str.push('+'), + let mut out: String = String::new(); + if self.pauli_columns.len() == 0 { + out.push_str("Destabilizers | Stabilizers |\n"); + writeln!(f, "{}", out)?; + writeln!(f) + } else { + out.push_str(" ||"); + out.push_str(&self.get_first_line_string()); + write!(f, "{}", out)?; + let column0 = self.pauli_columns[0].len(); + for i in 0..column0 / 2 { + let mut out = String::new(); + //beginning of line string + out.push_str("QB"); + out.push_str(&i.to_string()); + if i < 10 { + out.push(' '); + } + out.push_str("|| "); + out.push_str(&self.get_line_string(i)); + writeln!(f, "{}", out)?; } - sign_str.push(' ') + writeln!(f) } - sign_str.pop(); - write!(f, "{}", sign_str) + } +} + +pub fn get_pauli_sign(sign: bool) -> char { + if sign { + '-' + } else { + '+' + } +} + +pub(crate) fn get_pauli_char(letter: &PauliLetter) -> char { + match letter { + PauliLetter::I => 'I', + PauliLetter::X => 'X', + PauliLetter::Y => 'Y', + PauliLetter::Z => 'Z', } } @@ -1192,7 +1290,7 @@ mod tests { let ct = setup_sample_ct(); assert_eq!( ct.to_string(), - "CliffordTableau(3)\nZ Y I I Z Z\nZ I X X I I\nZ Y Y I I Z\n+ - + - + +" + " || Destabilizers | Stabilizers |\nQB0 || + Z Z Z | - I X I |\nQB1 || - Y I Y | + Z I I |\nQB2 || + I X Y | + Z I Z |\n\n" ); } } diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index ff38897b..9bc8b909 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -1,7 +1,10 @@ -use std::iter::zip; +// use std::iter::zip; use bitvec::vec::BitVec; use itertools::zip_eq; +// use itertools::Itertools; +use std::fmt; +use std::{iter::zip, sync::RwLock}; use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; @@ -62,6 +65,44 @@ impl PauliPolynomial { pub fn angle(&self, i: usize) -> Angle { self.angles[i] } + + pub fn get_line_string(&self, i: usize) -> String { + let mut out = String::new(); + if self.chains.is_empty() { + out.push_str("_ |"); + return out; + } else { + let chain_str = self.chains[i].to_string(); + for ch in chain_str.chars() { + out.push(ch); + if !ch.is_whitespace() { + out.push_str(" |"); + } + } + } + out + } + + pub fn get_first_line_string(&self) -> String { + let mut out = String::new(); + if self.angles.is_empty() { + out.push_str(" None |"); + } else { + for angle in self.angles.iter() { + out.push_str(&format!(" {:.3}", angle)); //force 3 decimal place for formatting + out.push_str(" |"); + } + } + out + } + + pub fn empty(i: usize) -> Self { + PauliPolynomial { + chains: vec![], + angles: vec![], + size: i, + } + } } impl PropagateClifford for PauliPolynomial { @@ -160,6 +201,45 @@ impl MaskedPropagateClifford for PauliPolynomial { self } } + +impl fmt::Display for PauliPolynomial { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut out = String::new(); + // handle empty case + if self.angles.is_empty() { + out.push_str("Angles ||"); + out.push_str(&self.get_first_line_string()); + out.push_str("\n"); + for i in 0..self.size() { + out.push_str("QB"); + out.push_str(&i.to_string()); + out.push_str(" || "); + out.push_str(&self.get_line_string(i)); + out.push_str("\n"); + // out.push_str("_ |\n"); + } + writeln!(f, "{}", out)?; + } else { + // write first line + out.push_str("Angles ||"); // I take this out from get_first_line_string because I want to reuse that function for pauli exponential + out.push_str(&self.get_first_line_string()); + writeln!(f, "{}", out)?; + + // write subsequent lines + let chains = self.chains(); + for (i, _) in chains.iter().enumerate() { + let mut out = String::new(); + out.push_str("QB"); + out.push_str(&i.to_string()); + out.push_str(" || "); + out.push_str(&self.get_line_string(i)); + writeln!(f, "{}", out)?; + } + } + writeln!(f) + } +} + #[cfg(test)] mod tests { use super::*; @@ -576,4 +656,12 @@ mod tests { }; assert_eq!(pp, pp_ref); } + #[test] + fn test_pauli_polynomial_display() { + let pp = setup_sample_pp(); + assert_eq!( + pp.to_string(), + "Angles || 0.300 | 0.700 | 0.120 |\nQB0 || I | X | Y |\nQB1 || Z | Y | X |\nQB2 || Y | I | X |\n\n" + ); + } } diff --git a/synir/src/ir/pauli_exponential.rs b/synir/src/ir/pauli_exponential.rs index ac3824e3..c9702a40 100644 --- a/synir/src/ir/pauli_exponential.rs +++ b/synir/src/ir/pauli_exponential.rs @@ -1,4 +1,5 @@ use std::collections::VecDeque; +use std::fmt; use crate::data_structures::{CliffordTableau, HasAdjoint, PauliPolynomial}; @@ -29,6 +30,50 @@ impl PauliExponential { } } +impl fmt::Display for PauliExponential { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut out: String = String::new(); + if self.pauli_polynomials.is_empty() { + out.push_str("Angles || No poly ||"); + out.push_str(&self.clifford_tableau.get_first_line_string()); + for i in 0..&self.clifford_tableau.column(0).len() / 2 { + out.push_str("QB"); + out.push_str(&i.to_string()); + if i < 10 { + out.push(' '); + } + out.push_str(" || _______ || "); + out.push_str(&self.clifford_tableau.get_line_string(i).as_str()); + out.push_str("\n"); + } + } else { + out.push_str("Angles ||"); + for pp in &self.pauli_polynomials { + out.push_str(pp.get_first_line_string().as_str()); + out.push('|'); + } + out.push_str(&self.clifford_tableau.get_first_line_string()); + + for i in 0..&self.clifford_tableau.column(0).len() / 2 { + out.push_str("QB"); + out.push_str(&i.to_string()); + if i < 10 { + out.push(' '); + } + out.push_str(" || "); + for pp in &self.pauli_polynomials { + out.push_str(pp.get_line_string(i).as_str()); + out.push_str("| "); + } + out.push_str(&self.clifford_tableau.get_line_string(i).as_str()); + out.push_str("\n"); + } + } + + write!(f, "{}", out)?; + writeln!(f) + } +} #[derive(Default)] pub struct PauliExponentialSynthesizer { pauli_strategy: PauliPolynomialSynthStrategy, diff --git a/synir/tests/clifford_tableau.rs b/synir/tests/clifford_tableau.rs index 11d54d04..a91c022e 100644 --- a/synir/tests/clifford_tableau.rs +++ b/synir/tests/clifford_tableau.rs @@ -18,7 +18,8 @@ fn setup_sample_ct() -> CliffordTableau { // qubit 2z: XII let pauli_2 = PauliString::from_text("ZIXXII"); - // qubit 3x: ZYY + // qubit 3x: ZY + // qubit 3z: IIZ let pauli_3 = PauliString::from_text("ZYYIIZ"); diff --git a/synpy/src/tableau.rs b/synpy/src/tableau.rs index 9aa2dafa..30e48cab 100644 --- a/synpy/src/tableau.rs +++ b/synpy/src/tableau.rs @@ -23,6 +23,9 @@ impl PyCliffordTableau { tableau: CliffordTableau::new(n), } } + fn __str__(&self) -> PyResult { + Ok(self.tableau.to_string()) + } #[staticmethod] pub fn from_parts(pauli_strings: Vec, signs: Vec) -> Self {