diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs new file mode 100644 index 00000000..a0ac1faa --- /dev/null +++ b/src/ir/clifford_tableau/helper.rs @@ -0,0 +1,506 @@ +use std::iter::zip; + +use crate::{ + architecture::{connectivity::Connectivity, Architecture}, + data_structures::{CliffordTableau, PauliLetter, PauliString, PropagateClifford}, + ir::{ + helper::{is_i, is_not_i, is_not_x, is_not_z, is_x, is_y, is_z}, + CliffordGates, + }, +}; + +fn get_pauli(pauli_string: &PauliString, row: usize) -> PauliLetter { + PauliLetter::new(pauli_string.x(row), pauli_string.z(row)) +} + +pub(super) fn clean_naive_pivot( + repr: &mut G, + ct: &mut CliffordTableau, + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + let num_qubits = ct.size(); + if check_pauli(&*ct, pivot_row, pivot_column + num_qubits, is_y) { + ct.s(pivot_row); + repr.s(pivot_row); + } + + if check_pauli(&*ct, pivot_row, pivot_column + num_qubits, is_not_z) { + ct.h(pivot_row); + repr.h(pivot_row); + } + + if check_pauli(&*ct, pivot_row, pivot_column, is_not_x) { + ct.s(pivot_row); + repr.s(pivot_row); + } +} + +pub(super) fn clean_pivot( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + pivot_column: usize, + pivot_row: usize, + letter: PauliLetter, +) where + G: CliffordGates, +{ + match letter { + PauliLetter::X => clean_x_pivot(repr, clifford_tableau, pivot_column, pivot_row), + PauliLetter::Z => clean_z_pivot(repr, clifford_tableau, pivot_column, pivot_row), + _ => panic!("Invalid Pauli letter for pivot cleaning"), + } +} + +pub(super) fn clean_observables( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + remaining_rows: &[usize], + pivot_column: usize, + pivot_row: usize, + letter: PauliLetter, +) where + G: CliffordGates, +{ + match letter { + PauliLetter::X => clean_x_observables( + repr, + clifford_tableau, + remaining_rows, + pivot_column, + pivot_row, + ), + PauliLetter::Z => clean_z_observables( + repr, + clifford_tableau, + remaining_rows, + pivot_column, + pivot_row, + ), + _ => panic!("Invalid Pauli letter for observable cleaning"), + } +} + +pub(super) fn clean_x_pivot( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + // These are switched around because of implementation + if check_pauli(&*clifford_tableau, pivot_column, pivot_row, is_y) { + clifford_tableau.s(pivot_column); + repr.s(pivot_column); + } + + // These are switched around because of implementation + if check_pauli(&*clifford_tableau, pivot_column, pivot_row, is_z) { + clifford_tableau.h(pivot_column); + repr.h(pivot_column); + } +} + +pub(super) fn clean_z_pivot( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + let num_qubits = clifford_tableau.size(); + + if check_pauli( + &*clifford_tableau, + pivot_column, + pivot_row + num_qubits, + is_y, + ) { + clifford_tableau.v(pivot_column); + repr.v(pivot_column); + } + + if check_pauli( + &*clifford_tableau, + pivot_column, + pivot_row + num_qubits, + is_x, + ) { + clifford_tableau.h(pivot_column); + repr.h(pivot_column); + } +} + +pub(super) fn clean_x_observables( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + remaining_columns: &[usize], + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + let affected_cols = + check_across_columns(&*clifford_tableau, remaining_columns, pivot_row, is_y); + + for col in affected_cols { + repr.s(col); + clifford_tableau.s(col); + } + + let affected_cols = + check_across_columns(&*clifford_tableau, remaining_columns, pivot_row, is_z); + + for col in affected_cols { + repr.h(col); + clifford_tableau.h(col); + } + + let affected_cols = + check_across_columns(&*clifford_tableau, remaining_columns, pivot_row, is_not_i); + + for col in affected_cols { + repr.cx(pivot_column, col); + clifford_tableau.cx(pivot_column, col); + } +} + +pub(super) fn clean_z_observables( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + remaining_columns: &[usize], + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + let num_qubits = clifford_tableau.size(); + let affected_cols = check_across_columns( + &*clifford_tableau, + remaining_columns, + pivot_row + num_qubits, + is_y, + ); + for col in affected_cols { + repr.v(col); + clifford_tableau.v(col); + } + + let affected_cols = check_across_columns( + &*clifford_tableau, + remaining_columns, + pivot_row + num_qubits, + is_x, + ); + for col in affected_cols { + repr.h(col); + clifford_tableau.h(col); + } + + let affected_cols = check_across_columns( + &*clifford_tableau, + remaining_columns, + pivot_row + num_qubits, + is_not_i, + ); + for col in affected_cols { + repr.cx(col, pivot_column); + clifford_tableau.cx(col, pivot_column); + } +} + +pub(super) fn clean_signs( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + row_permutation: &[usize], +) where + G: CliffordGates, +{ + let z_signs = clifford_tableau.z_signs(); + + for (sign, row) in zip(z_signs, row_permutation) { + if sign { + repr.x(*row); + clifford_tableau.x(*row); + } + } + + let x_signs = clifford_tableau.x_signs(); + + for (sign, row) in zip(x_signs, row_permutation) { + if sign { + repr.z(*row); + clifford_tableau.z(*row); + } + } +} + +pub(super) fn swap( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + row: usize, + pivot_col: usize, +) where + G: CliffordGates, +{ + repr.cx(pivot_col, row); + repr.cx(row, pivot_col); + repr.cx(pivot_col, row); + + clifford_tableau.cx(pivot_col, row); + clifford_tableau.cx(row, pivot_col); + clifford_tableau.cx(pivot_col, row); +} + +pub(super) fn naive_pivot_search( + clifford_tableau: &CliffordTableau, + num_qubits: usize, + row: usize, +) -> usize { + let mut pivot_col = 0; + + for col in 0..num_qubits { + let column = clifford_tableau.column(col); + let x_pauli = get_pauli(column, row); + let z_pauli = get_pauli(column, row + num_qubits); + if x_pauli != PauliLetter::I && z_pauli != PauliLetter::I && x_pauli != z_pauli { + pivot_col = col; + break; + } + } + pivot_col +} + +pub(super) fn check_pauli( + clifford_tableau: &CliffordTableau, + column: usize, + row: usize, + pauli_check: fn(PauliLetter) -> bool, +) -> bool { + let pauli_string = clifford_tableau.column(column); + pauli_check(get_pauli(pauli_string, row)) +} + +/// Helper function that returns indices for a particular column `col` of `clifford_tableau` that match the provided closure `pauli_check`. +pub(super) fn check_across_columns( + clifford_tableau: &CliffordTableau, + columns: &[usize], + checked_row: usize, + pauli_check: fn(PauliLetter) -> bool, +) -> Vec { + let mut affected_cols = Vec::new(); + + for column in columns { + let pauli_string = clifford_tableau.column(*column); + + if pauli_check(get_pauli(pauli_string, checked_row)) { + affected_cols.push(*column); + } + } + affected_cols +} + +/// function to pick a stabilizer / destabilizer to set to identity in Clifford tableau. +pub(super) fn pick_row( + clifford_tableau: &CliffordTableau, + connectivity: &Connectivity, + remaining_rows: &[usize], +) -> usize { + let mut row_weights = vec![usize::MAX; clifford_tableau.size()]; + for row in remaining_rows { + row_weights[*row] = 0; + for qubit in connectivity.nodes() { + if is_not_i(clifford_tableau.stabilizer(qubit, *row)) { + row_weights[*row] += 1; + } + if is_not_i(clifford_tableau.destabilizer(qubit, *row)) { + row_weights[*row] += 1; + } + } + } + + row_weights + .into_iter() + .enumerate() + .min_by_key(|&(_, weight)| weight) + .map(|(index, _)| index) + .unwrap() +} + +/// function to pick a qubit to disconnect in Clifford tableau. +pub(super) fn pick_column( + clifford_tableau: &CliffordTableau, + connectivity: &Connectivity, +) -> usize { + let mut column_weights = vec![usize::MAX; clifford_tableau.size()]; + + let non_cutting = connectivity.non_cutting(); + + for qubit in non_cutting { + column_weights[*qubit] = 0; + for interaction in connectivity.nodes() { + let mult = (clifford_tableau.stabilizer(*qubit, interaction) != PauliLetter::I) + as usize + + (clifford_tableau.destabilizer(*qubit, interaction) != PauliLetter::I) as usize; + if mult > 0 { + column_weights[*qubit] += connectivity.distance(*qubit, interaction) * mult; + } + } + } + + column_weights + .iter() + .enumerate() + .min_by_key(|&(_, &weight)| weight) + .map(|(index, _)| index) + .unwrap() +} + +pub(super) fn clean_prc( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + connectivity: &Connectivity, + remaining_rows: &[usize], + pivot_column: usize, + pivot_row: usize, + letter: PauliLetter, +) where + G: CliffordGates, +{ + match letter { + PauliLetter::X => clean_x_prc( + repr, + clifford_tableau, + connectivity, + remaining_rows, + pivot_column, + pivot_row, + ), + PauliLetter::Z => clean_z_prc( + repr, + clifford_tableau, + connectivity, + remaining_rows, + pivot_column, + pivot_row, + ), + _ => panic!("Invalid Pauli letter for observable cleaning"), + } +} + +pub(super) fn clean_x_prc( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + connectivity: &Connectivity, + remaining_columns: &[usize], + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + let mut terminals = remaining_columns + .iter() + .filter_map(|qubit| { + if is_not_i(clifford_tableau.destabilizer(*qubit, pivot_row)) { + Some(*qubit) + } else { + None + } + }) + .collect::>(); + + if terminals.is_empty() { + return; + } + terminals.push(pivot_column); + + let traversal = connectivity + .get_cx_ladder(&terminals, &pivot_column) + .unwrap(); + + let affected_cols = check_across_columns(&*clifford_tableau, &terminals, pivot_row, is_y); + for col in affected_cols { + repr.s(col); + clifford_tableau.s(col); + } + + let affected_cols = check_across_columns(&*clifford_tableau, &terminals, pivot_row, is_z); + for col in affected_cols { + repr.h(col); + clifford_tableau.h(col); + } + + for (parent, child) in traversal.iter().rev() { + if is_i(clifford_tableau.destabilizer(*parent, pivot_row)) { + repr.cx(*child, *parent); + clifford_tableau.cx(*child, *parent); + } + } + + for (parent, child) in traversal.iter().rev() { + repr.cx(*parent, *child); + clifford_tableau.cx(*parent, *child); + } +} + +pub(super) fn clean_z_prc( + repr: &mut G, + clifford_tableau: &mut CliffordTableau, + connectivity: &Connectivity, + remaining_columns: &[usize], + pivot_column: usize, + pivot_row: usize, +) where + G: CliffordGates, +{ + let num_qubits = clifford_tableau.size(); + let mut terminals = remaining_columns + .iter() + .filter_map(|qubit| { + if is_not_i(clifford_tableau.stabilizer(*qubit, pivot_row)) { + Some(*qubit) + } else { + None + } + }) + .collect::>(); + if terminals.is_empty() { + return; + } + terminals.push(pivot_column); + + let traversal = connectivity + .get_cx_ladder(&terminals, &pivot_column) + .unwrap(); + + let affected_cols = + check_across_columns(&*clifford_tableau, &terminals, pivot_row + num_qubits, is_y); + + for col in affected_cols { + repr.v(col); + clifford_tableau.v(col); + } + + let affected_cols = + check_across_columns(&*clifford_tableau, &terminals, pivot_row + num_qubits, is_x); + for col in affected_cols { + repr.h(col); + clifford_tableau.h(col); + } + + for (parent, child) in traversal.iter().rev() { + if is_i(clifford_tableau.stabilizer(*parent, pivot_row)) { + repr.cx(*parent, *child); + clifford_tableau.cx(*parent, *child); + } + } + + for (parent, child) in traversal.iter().rev() { + repr.cx(*child, *parent); + clifford_tableau.cx(*child, *parent); + } +} diff --git a/src/ir/clifford_tableau/permrowcol.rs b/src/ir/clifford_tableau/permrowcol.rs new file mode 100644 index 00000000..1cc6d8eb --- /dev/null +++ b/src/ir/clifford_tableau/permrowcol.rs @@ -0,0 +1,150 @@ +use std::fmt::Debug; + +use crate::{ + architecture::connectivity::Connectivity, + data_structures::{CliffordTableau, PauliLetter}, + ir::{ + clifford_tableau::helper::{clean_pivot, clean_prc, pick_column, pick_row}, + AdjointSynthesizer, CliffordGates, + }, +}; + +use super::helper::clean_signs; + +// #[derive(Default)] +pub struct PermRowColCliffordSynthesizer { + connectivity: Connectivity, + permutation: Vec, + row_strategy: fn(&CliffordTableau, &Connectivity, &[usize]) -> usize, + column_strategy: fn(&CliffordTableau, &Connectivity) -> usize, +} + +impl Default for PermRowColCliffordSynthesizer { + fn default() -> Self { + PermRowColCliffordSynthesizer { + connectivity: Connectivity::default(), + permutation: Vec::::default(), + row_strategy: pick_row, + column_strategy: pick_column, + } + } +} + +impl PermRowColCliffordSynthesizer { + pub fn new(connectivity: Connectivity) -> Self { + let size = connectivity.node_count(); + + Self { + connectivity, + permutation: (0..size).collect(), + row_strategy: pick_row, + column_strategy: pick_column, + } + } + + pub fn permutation(&self) -> &[usize] { + &self.permutation + } + + pub fn set_row_strategy( + &mut self, + row_strategy: fn(&CliffordTableau, &Connectivity, &[usize]) -> usize, + ) { + (self.row_strategy) = row_strategy; + } + + pub fn set_column_strategy( + &mut self, + column_strategy: fn(&CliffordTableau, &Connectivity) -> usize, + ) { + (self.column_strategy) = column_strategy; + } +} + +impl AdjointSynthesizer for PermRowColCliffordSynthesizer +where + G: CliffordGates, +{ + fn synthesize_adjoint(&mut self, mut clifford_tableau: CliffordTableau, repr: &mut G) { + let num_qubits = clifford_tableau.size(); + let machine_size = self.connectivity.node_count(); + assert!( + num_qubits <= machine_size, + "Number of qubits {} exceeds machine size {}", + num_qubits, + machine_size + ); + // Mapping between logical qubit to physical qubit + let mut permutation = (0..num_qubits).collect::>(); + // logical qubit remaining to be disconnected + let mut remaining_columns = (0..num_qubits).collect::>(); + // stabilizers / destabilizers that are not yet identity rows + let mut remaining_rows = (0..num_qubits).collect::>(); + + while !remaining_columns.is_empty() { + let pivot_row = + (self.row_strategy)(&clifford_tableau, &self.connectivity, &remaining_rows); + let pivot_column = (self.column_strategy)(&clifford_tableau, &self.connectivity); + let column = clifford_tableau.column(pivot_column); + let x_weight = column.x_weight(); + let z_weight = column.z_weight(); + + let (first_letter, second_letter) = if z_weight > x_weight { + (PauliLetter::Z, PauliLetter::X) + } else { + (PauliLetter::X, PauliLetter::Z) + }; + + remaining_columns.retain(|&x| x != pivot_column); + remaining_rows.retain(|&x| x != pivot_row); + { + clean_pivot( + repr, + &mut clifford_tableau, + pivot_column, + pivot_row, + first_letter, + ); + + // Use the pivot to remove all other terms in the X observable. + clean_prc( + repr, + &mut clifford_tableau, + &self.connectivity, + &remaining_columns, + pivot_column, + pivot_row, + first_letter, + ); + + clean_pivot( + repr, + &mut clifford_tableau, + pivot_column, + pivot_row, + second_letter, + ); + + // Use the pivot to remove all other terms in the Z observable. + clean_prc( + repr, + &mut clifford_tableau, + &self.connectivity, + &remaining_columns, + pivot_column, + pivot_row, + second_letter, + ); + } + + // If the pivot row is now an identity row, we can remove it from the tableau. + + permutation[pivot_row] = pivot_column; + self.connectivity.remove_node(pivot_column); + } + + clean_signs(repr, &mut clifford_tableau, &permutation); + + self.permutation = permutation; + } +} diff --git a/src/ir/helper.rs b/src/ir/helper.rs new file mode 100644 index 00000000..727f95fa --- /dev/null +++ b/src/ir/helper.rs @@ -0,0 +1,35 @@ +use crate::data_structures::PauliLetter; + +#[allow(dead_code)] +pub(super) fn is_i(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::I +} + +pub(super) fn is_not_i(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::I +} + +pub(super) fn is_x(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::X +} + +pub(super) fn is_not_x(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::X +} + +pub(super) fn is_y(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::Y +} + +#[allow(dead_code)] +pub(super) fn is_not_y(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::Y +} + +pub(super) fn is_z(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::Z +} + +pub(super) fn is_not_z(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::Z +} diff --git a/synir/benches/clifford_tableau.rs b/synir/benches/clifford_tableau.rs index 7acb33e2..cae2a682 100644 --- a/synir/benches/clifford_tableau.rs +++ b/synir/benches/clifford_tableau.rs @@ -10,8 +10,6 @@ use synir::ir::CliffordGates; use synir::ir::Synthesizer; use synir::IndexType; -mod connectivity; - #[derive(Debug, Default)] pub struct MockCircuit { commands: Vec, diff --git a/synir/src/architecture/connectivity.rs b/synir/src/architecture/connectivity.rs index 96069e0a..2aa20186 100644 --- a/synir/src/architecture/connectivity.rs +++ b/synir/src/architecture/connectivity.rs @@ -28,7 +28,7 @@ fn get_non_cutting_vertices( .collect() } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Connectivity { graph: StableUnGraph, non_cutting: Vec, diff --git a/synir/src/data_structures.rs b/synir/src/data_structures.rs index 98aade8b..ad2d8d1b 100644 --- a/synir/src/data_structures.rs +++ b/synir/src/data_structures.rs @@ -9,6 +9,8 @@ pub use clifford_tableau::CliffordTableau; pub use pauli_polynomial::PauliPolynomial; pub use pauli_string::PauliString; +pub type Angle = f64; + pub trait HasAdjoint { fn adjoint(&self) -> Self; } diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index ff38897b..a2111968 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -3,15 +3,14 @@ use std::iter::zip; use bitvec::vec::BitVec; use itertools::zip_eq; -use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; +use crate::data_structures::Angle; -// todo: Make this into a union / type Angle -type Angle = f64; +use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, PropagateClifford}; #[derive(Debug, Clone, Default)] pub struct PauliPolynomial { - chains: Vec, - angles: Vec, + pub(crate) chains: Vec, + pub(crate) angles: Vec, size: usize, } @@ -36,13 +35,21 @@ impl PauliPolynomial { .map(|gadget| PauliString::from_text(gadget)) .collect::>(); - PauliPolynomial { + Self { chains, angles, size: num_qubits, } } + pub fn from_components(chains: Vec, angles: Vec, size: usize) -> Self { + Self { + chains, + angles, + size, + } + } + pub fn size(&self) -> usize { self.size } @@ -59,9 +66,17 @@ impl PauliPolynomial { &self.chains } + pub fn angles(&self) -> &Vec { + &self.angles + } + pub fn angle(&self, i: usize) -> Angle { self.angles[i] } + + pub fn mut_chains(&mut self) -> &mut Vec { + &mut self.chains + } } impl PropagateClifford for PauliPolynomial { @@ -88,19 +103,20 @@ impl PropagateClifford for PauliPolynomial { fn s(&mut self, target: IndexType) -> &mut Self { let chains_target = self.chains.get_mut(target).unwrap(); // Update angles + chains_target.s(); let y_vec = chains_target.y_bitmask(); for (angle, flip) in zip(self.angles.iter_mut(), y_vec.iter()) { if *flip { *angle *= -1.0; } } - chains_target.s(); + self } fn v(&mut self, target: IndexType) -> &mut Self { let chains_target = self.chains.get_mut(target).unwrap(); - chains_target.v(); + // Update angles let y_vec = chains_target.y_bitmask(); for (angle, flip) in zip(self.angles.iter_mut(), y_vec.iter()) { @@ -108,6 +124,7 @@ impl PropagateClifford for PauliPolynomial { *angle *= -1.0; } } + chains_target.v(); self } } @@ -205,40 +222,34 @@ mod tests { let _ = PauliPolynomial::from_hamiltonian(ham); } - fn setup_sample_pp() -> PauliPolynomial { - let size = 3; - 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]; + fn setup_basic_pp() -> PauliPolynomial { + let size = 1; + let pg_ref = PauliString::from_text("IXYZ"); + + let angles_ref = vec![0.3, 0.7, 0.12, 0.15]; PauliPolynomial { - chains: vec![pg1_ref, pg2_ref, pg3_ref], + chains: vec![pg_ref], angles: angles_ref, size, } } #[test] - fn test_pauli_polynomial_s() { - // Polynomials: IZY, XYI, YXX - let size = 3; - let mut pp = setup_sample_pp(); + fn test_basic_pauli_polynomial_h() { + let size = 1; + let mut pp = setup_basic_pp(); - // Apply S to qubits 0 and 1. - pp.s(0); - pp.s(1); + pp.h(0); - // Polynomials: IZY, -YXI, -XYX + // I -> I + // X -> Z + // Y -> -Y + // Z -> X + let pg_ref = PauliString::from_text("IZYX"); + let angles_ref = vec![0.3, 0.7, -0.12, 0.15]; - // IXY -> IY(-X) - let pg1_ref = PauliString::from_text("IYX"); - // ZYX -> Z(-X)Y - 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 pp_ref = PauliPolynomial { - chains: vec![pg1_ref, pg2_ref, pg3_ref], + chains: vec![pg_ref], angles: angles_ref, size, }; @@ -246,49 +257,44 @@ mod tests { } #[test] - fn test_pauli_polynomial_v() { - // Polynomials: IZY, XYI, YXX - let size = 3; - let mut pp = setup_sample_pp(); + fn test_basic_pauli_polynomial_s() { + let size = 1; + let mut pp = setup_basic_pp(); + let pp = pp.s(0); - // Apply V to qubits 0 and 1. - pp.v(1); - pp.v(2); + // I -> I + // X -> -Y + // Y -> X + // Z -> Z + let pg_ref = PauliString::from_text("IYXZ"); + + let angles_ref = vec![0.3, -0.7, 0.12, 0.15]; - // IXY - let pg1_ref = PauliString::from_text("IXY"); - // ZYX -> (-Y)ZX - 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 pp_ref = PauliPolynomial { - chains: vec![pg1_ref, pg2_ref, pg3_ref], + chains: vec![pg_ref], angles: angles_ref, size, }; - assert_eq!(pp, pp_ref); + assert_eq!(pp, &pp_ref); } #[test] - fn test_pauli_polynomial_s_dgr() { - // Polynomials: IZY, XYI, YXX - let size = 3; - let mut pp = setup_sample_pp(); + fn test_basic_pauli_polynomial_sdr() { + let size = 1; + let mut pp = setup_basic_pp(); - // Apply Sdgr to qubits 1 and 2. - pp.s_dgr(1); - pp.s_dgr(2); + pp.s_dgr(0); + + // I -> I + // X -> -Y + // Y -> X + // Z -> Z + let pg_ref = PauliString::from_text("IYXZ"); + + let angles_ref = vec![0.3, 0.7, -0.12, 0.15]; - // IXY - let pg1_ref = PauliString::from_text("IXY"); - // ZYX -> ZX(-Y) - 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 pp_ref = PauliPolynomial { - chains: vec![pg1_ref, pg2_ref, pg3_ref], + chains: vec![pg_ref], angles: angles_ref, size, }; @@ -296,24 +302,22 @@ mod tests { } #[test] - fn test_pauli_polynomial_v_dgr() { - // Polynomials: IZY, XYI, YXX - let size = 3; - let mut pp = setup_sample_pp(); + fn test_basic_pauli_polynomial_v() { + let size = 1; + let mut pp = setup_basic_pp(); - // Apply Vdgr to qubits 1 and 2. - pp.v_dgr(1); - pp.v_dgr(2); + pp.v(0); + + // I -> I + // X -> X + // Y -> -Z + // Z -> Y + let pg_ref = PauliString::from_text("IXZY"); + + let angles_ref = vec![0.3, 0.7, -0.12, 0.15]; - // IXY - let pg1_ref = PauliString::from_text("IXY"); - // ZYX -> Y(-Z)X - 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 pp_ref = PauliPolynomial { - chains: vec![pg1_ref, pg2_ref, pg3_ref], + chains: vec![pg_ref], angles: angles_ref, size, }; @@ -321,24 +325,21 @@ mod tests { } #[test] - fn test_pauli_polynomial_h() { - // Polynomials: IZY, XYI, YXX - let size = 3; - let mut pp = setup_sample_pp(); + fn test_basic_pauli_polynomial_vdgr() { + let size = 1; + let mut pp = setup_basic_pp(); + + pp.v_dgr(0); + + // I -> I + // X -> X + // Y -> Z + // Z -> -Y + let pg_ref = PauliString::from_text("IXZY"); + let angles_ref = vec![0.3, 0.7, 0.12, -0.15]; - // Apply H to qubits 0 and 1. - pp.h(0); - pp.h(1); - - // IXY -> IZ(-Y) - let pg1_ref = PauliString::from_text("IZY"); - // ZYX -> X(-Y)Z - 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 pp_ref = PauliPolynomial { - chains: vec![pg1_ref, pg2_ref, pg3_ref], + chains: vec![pg_ref], angles: angles_ref, size, }; diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index d66a3f5a..0ba6af1c 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -61,6 +61,16 @@ impl PauliString { self.z.count_ones() } + pub fn combine(&self) -> BitVec { + let mut new_string = self.z.to_bitvec(); + new_string |= self.x.as_bitslice(); + new_string + } + pub fn weight(&self) -> usize { + let new_string = self.combine(); + new_string.count_ones() + } + pub fn z(&self, i: usize) -> bool { self.z[i] } @@ -69,6 +79,14 @@ impl PauliString { PauliLetter::new(self.x(i), self.z(i)) } + pub fn iter(&self) -> Vec { + self.x + .iter() + .zip(self.z.iter()) + .map(|(x, z)| PauliLetter::new(*x, *z)) + .collect::>() + } + pub fn len(&self) -> usize { self.x.len() } @@ -123,6 +141,12 @@ impl PauliString { mask &= &self.z; mask } + + pub(crate) fn swap_remove(&mut self, index: usize) -> PauliLetter { + let x = self.x.swap_remove(index); + let z = self.z.swap_remove(index); + PauliLetter::new(x, z) + } } pub(crate) fn cx(control: &mut PauliString, target: &mut PauliString) { diff --git a/synir/src/ir/clifford_tableau/naive.rs b/synir/src/ir/clifford_tableau/naive.rs index 5f50a6a2..d31bf037 100644 --- a/synir/src/ir/clifford_tableau/naive.rs +++ b/synir/src/ir/clifford_tableau/naive.rs @@ -46,7 +46,6 @@ where clean_x_observables(repr, &mut clifford_tableau, &checked_rows, row, row); clean_pivot(repr, &mut clifford_tableau, row, row, PauliLetter::Z); - // Use the pivot to remove all other terms in the Z observable. clean_z_observables(repr, &mut clifford_tableau, &checked_rows, row, row); } diff --git a/synir/src/ir/pauli_exponential.rs b/synir/src/ir/pauli_exponential.rs index 79be5518..7dd0321f 100644 --- a/synir/src/ir/pauli_exponential.rs +++ b/synir/src/ir/pauli_exponential.rs @@ -3,6 +3,7 @@ use std::collections::VecDeque; use crate::architecture::connectivity::Connectivity; use crate::data_structures::{CliffordTableau, HasAdjoint, PauliPolynomial}; +use crate::ir::pauli_polynomial::psgs::PSGSPauliPolynomialSynthesizer; use crate::ir::{CliffordGates, Gates, Synthesizer}; use crate::ir::clifford_tableau::NaiveCliffordSynthesizer; @@ -73,11 +74,15 @@ where pauli_polynomials, clifford_tableau, } = pauli_exponential; - + let num_qubits = clifford_tableau.size(); let clifford_tableau = match self.pauli_strategy { PauliPolynomialSynthStrategy::Naive => { let mut pauli_synthesizer = NaivePauliPolynomialSynthesizer::default(); - pauli_synthesizer.set_clifford_tableau(clifford_tableau); + pauli_synthesizer.synthesize(pauli_polynomials, repr) + } + PauliPolynomialSynthStrategy::PSGS => { + let mut pauli_synthesizer = PSGSPauliPolynomialSynthesizer::default(); + pauli_synthesizer.set_connectivity(Connectivity::complete(num_qubits)); pauli_synthesizer.synthesize(pauli_polynomials, repr) } }; diff --git a/synir/src/ir/pauli_polynomial.rs b/synir/src/ir/pauli_polynomial.rs index 47c420d3..db830e2b 100644 --- a/synir/src/ir/pauli_polynomial.rs +++ b/synir/src/ir/pauli_polynomial.rs @@ -1,5 +1,6 @@ mod helper; pub mod naive; +pub mod psgs; pub use naive::NaivePauliPolynomialSynthesizer; @@ -7,4 +8,5 @@ pub use naive::NaivePauliPolynomialSynthesizer; pub enum PauliPolynomialSynthStrategy { #[default] Naive, + PSGS, } diff --git a/synir/src/ir/pauli_polynomial/helper.rs b/synir/src/ir/pauli_polynomial/helper.rs index 127f5ad1..a5024a49 100644 --- a/synir/src/ir/pauli_polynomial/helper.rs +++ b/synir/src/ir/pauli_polynomial/helper.rs @@ -1,14 +1,16 @@ -use std::collections::VecDeque; +use std::{collections::VecDeque, iter::zip}; use bitvec::{bitvec, order::Lsb0}; use itertools::Itertools; use crate::{ + architecture::{connectivity::Connectivity, Architecture}, data_structures::{ CliffordTableau, MaskedPropagateClifford, PauliLetter, PauliPolynomial, PropagateClifford, }, ir::{CliffordGates, Gates}, }; + use bitvec::prelude::BitVec; impl PropagateClifford for VecDeque { @@ -112,3 +114,376 @@ pub(super) fn push_down_pauli_polynomial_update( mask.replace(col, false); } } + +pub(super) fn pick_qubit( + pauli_polynomial: &PauliPolynomial, + polynomial_mask: &BitVec, + selected_qubits: &[usize], +) -> usize { + let weight_i = 10; + let mut costs = vec![0usize; selected_qubits.len()]; + for (index, qubit) in selected_qubits.iter().enumerate() { + let chain = pauli_polynomial.chain(*qubit); + for (bit, mask) in zip(chain.combine(), polynomial_mask.iter()) { + if !mask { + continue; + } + let weight = if bit { 1 } else { weight_i }; + costs[index] += weight; + } + } + // If all costs are zero, return the first qubit + // Find the qubit with the maximum cost + selected_qubits[costs.iter().position_max().unwrap()] +} + +/// Checks if any Pauli Gadgets have no or 1 identity legs. +/// The gadget is removed from scope and the bit in polynomial mask is set to 0 +pub(super) fn check_columns( + repr: &mut G, + pauli_polynomial: &mut PauliPolynomial, + polynomial_mask: &mut BitVec, +) where + G: CliffordGates + Gates, +{ + let invalid = { + let length = pauli_polynomial.length(); + + let mut invalid = BitVec::repeat(false, length); + let mut seen_one = BitVec::repeat(false, length); + + for chain in pauli_polynomial.chains().iter() { + let combination = chain.combine(); + invalid |= seen_one.clone() & &combination; + seen_one |= combination; + } + invalid + }; + let length = pauli_polynomial.length(); + let PauliPolynomial { + ref mut chains, + angles, + .. + } = pauli_polynomial; + for index in (0..length).rev() { + if !invalid[index] && polynomial_mask[index] { + polynomial_mask.swap_remove(index); + let angle = angles.swap_remove(index); + for (qubit, chain) in chains.iter_mut().enumerate() { + let letter = chain.swap_remove(index); + match letter { + PauliLetter::X => { + repr.rx(qubit, angle); + } + PauliLetter::Y => { + repr.ry(qubit, angle); + } + PauliLetter::Z => { + repr.rz(qubit, angle); + } + PauliLetter::I => {} + } + } + } + } +} + +/// Partition the input polynomial mask on the selected qubit. If the gadget has an identity leg, it is assigned to the identity mask. Otherwise, it is assigned to the other mask. +pub(super) fn identity_partition( + pauli_polynomial: &PauliPolynomial, + mut polynomial_mask: BitVec, + selected_qubit: usize, +) -> (BitVec, BitVec) { + let pauli_polynomial_length = pauli_polynomial.length(); + let mut identity_mask = BitVec::with_capacity(pauli_polynomial_length); + let mut other_mask = BitVec::with_capacity(pauli_polynomial_length); + + let pauli_chain = pauli_polynomial.chain(selected_qubit).iter(); + + for pauli in pauli_chain.iter() { + match pauli { + PauliLetter::I => { + identity_mask.push(polynomial_mask.pop().unwrap()); + other_mask.push(false); + } + _ => { + other_mask.push(polynomial_mask.pop().unwrap()); + identity_mask.push(false); + } + } + } + + (identity_mask, other_mask) +} + +/// Partition the input polynomial mask on the selected qubit. If the gadget has an identity leg, it is assigned to the identity mask. Otherwise, it is assigned to the other mask. +pub(super) fn max_partition( + pauli_polynomial: &PauliPolynomial, + mut polynomial_mask: BitVec, + selected_qubit: usize, +) -> ( + BitVec, + PauliLetter, + BitVec, + PauliLetter, + BitVec, + PauliLetter, +) { + let pauli_polynomial_length = pauli_polynomial.length(); + + let mut x_mask = BitVec::with_capacity(pauli_polynomial_length); + let mut y_mask = BitVec::with_capacity(pauli_polynomial_length); + let mut z_mask = BitVec::with_capacity(pauli_polynomial_length); + + let pauli_chain = pauli_polynomial.chain(selected_qubit).iter(); + + for pauli in pauli_chain.iter() { + match pauli { + PauliLetter::I => { + panic!("Cannot partition polynomial with identity leg on selected qubit"); + } + PauliLetter::X => { + x_mask.push(polynomial_mask.pop().unwrap()); + y_mask.push(false); + z_mask.push(false); + } + PauliLetter::Y => { + x_mask.push(false); + y_mask.push(polynomial_mask.pop().unwrap()); + z_mask.push(false); + } + PauliLetter::Z => { + x_mask.push(false); + y_mask.push(false); + z_mask.push(polynomial_mask.pop().unwrap()); + } + } + } + let mut polynomial_parts = vec![ + (x_mask, PauliLetter::X), + (y_mask, PauliLetter::Y), + (z_mask, PauliLetter::Z), + ]; + polynomial_parts.sort_by(|a, b| a.0.count_ones().cmp(&b.0.count_ones())); + + let (largest_mask, largest_pauli) = polynomial_parts.pop().unwrap(); + let (second_mask, second_pauli) = polynomial_parts.pop().unwrap(); + let (third_mask, third_pauli) = polynomial_parts.pop().unwrap(); + + ( + largest_mask, + largest_pauli, + second_mask, + second_pauli, + third_mask, + third_pauli, + ) +} + +pub(super) fn identity_recurse( + pauli_polynomial: &mut PauliPolynomial, + clifford_tableau: &mut CliffordTableau, + connectivity: &Connectivity, + mut polynomial_mask: BitVec, + // selected_qubits: &[usize], + repr: &mut G, +) where + G: CliffordGates + Gates, +{ + // Remove gadgets with single rotation. + check_columns(repr, pauli_polynomial, &mut polynomial_mask); + if polynomial_mask.count_ones() == 0 { + return; + } + let selected_qubits = connectivity.non_cutting(); + let selected_qubit = pick_qubit(pauli_polynomial, &polynomial_mask, selected_qubits); + // Create new connectivity without the selected qubit + let reduced_connectivity = connectivity.disconnect(selected_qubit); + + let (identity_mask, other_mask) = + identity_partition(pauli_polynomial, polynomial_mask, selected_qubit); + + if identity_mask.count_ones() > 0 { + // recurse down identity mask + identity_recurse( + pauli_polynomial, + clifford_tableau, + &reduced_connectivity, + identity_mask, + repr, + ); + // ensure remainder is synthesized + identity_recurse( + pauli_polynomial, + clifford_tableau, + connectivity, + other_mask, + repr, + ) + } else { + // `identity_mask` is empty, we do not process it + let (largest_mask, largest_pauli, mut remaining_mask, _, third_mask, _) = + max_partition(pauli_polynomial, other_mask, selected_qubit); + + remaining_mask |= third_mask.as_bitslice(); + + // Ensure `next_qubit` is always a neighbor of `selected_qubit` + let next_qubit = pick_qubit( + pauli_polynomial, + &largest_mask, + &connectivity.neighbors(selected_qubit), + ); + + // Check if there are identities on `next_qubit` + let (next_identity_mask, next_other_mask) = + identity_partition(pauli_polynomial, largest_mask, next_qubit); + + // Ensure that selected qubit is always Pauli::Z + diagonalize_qubit( + pauli_polynomial, + clifford_tableau, + repr, + selected_qubit, + largest_pauli, + ); + + if next_identity_mask.count_ones() > 0 { + disconnect_i( + pauli_polynomial, + clifford_tableau, + repr, + selected_qubit, + next_qubit, + ); + remaining_mask |= next_other_mask.as_bitslice(); + + let (identity_mask, other_mask) = + identity_partition(pauli_polynomial, remaining_mask, selected_qubit); + + identity_recurse( + pauli_polynomial, + clifford_tableau, + &reduced_connectivity, + identity_mask, + repr, + ); + + identity_recurse( + pauli_polynomial, + clifford_tableau, + connectivity, + other_mask, + repr, + ) + } else { + let ( + mut largest_mask, + largest_next_pauli, + second_mask, + second_next_pauli, + third_mask, + _, + ) = max_partition(pauli_polynomial, next_other_mask, next_qubit); + + largest_mask |= second_mask.as_bitslice(); + let is_x = largest_next_pauli == PauliLetter::X || second_next_pauli == PauliLetter::X; + let is_y = largest_next_pauli == PauliLetter::Y || second_next_pauli == PauliLetter::Y; + + disconnect( + pauli_polynomial, + clifford_tableau, + repr, + selected_qubit, + next_qubit, + is_x, + is_y, + ); + + identity_recurse( + pauli_polynomial, + clifford_tableau, + &reduced_connectivity, + largest_mask, + repr, + ); + identity_recurse( + pauli_polynomial, + clifford_tableau, + connectivity, + third_mask, + repr, + ); + } + } +} + +fn disconnect_i( + pauli_polynomial: &mut PauliPolynomial, + clifford_tableau: &mut CliffordTableau, + repr: &mut G, + selected_qubit: usize, + next_qubit: usize, +) where + G: CliffordGates + Gates, +{ + pauli_polynomial.cx(selected_qubit, next_qubit); + pauli_polynomial.cx(next_qubit, selected_qubit); + clifford_tableau.cx(selected_qubit, next_qubit); + clifford_tableau.cx(next_qubit, selected_qubit); + repr.cx(selected_qubit, next_qubit); + repr.cx(next_qubit, selected_qubit); +} + +fn disconnect( + pauli_polynomial: &mut PauliPolynomial, + clifford_tableau: &mut CliffordTableau, + repr: &mut G, + selected_qubit: usize, + next_qubit: usize, + is_x: bool, + is_y: bool, +) where + G: CliffordGates + Gates, +{ + match (is_x, is_y) { + (true, true) => { + pauli_polynomial.h(next_qubit); + clifford_tableau.h(next_qubit); + repr.h(next_qubit); + } + (true, false) => { + pauli_polynomial.s(next_qubit); + clifford_tableau.s(next_qubit); + repr.s(next_qubit); + } + _ => {} + } + pauli_polynomial.cx(selected_qubit, next_qubit); + clifford_tableau.cx(selected_qubit, next_qubit); + repr.cx(selected_qubit, next_qubit); +} + +fn diagonalize_qubit( + pauli_polynomial: &mut PauliPolynomial, + clifford_tableau: &mut CliffordTableau, + repr: &mut G, + selected_qubit: usize, + largest_pauli: PauliLetter, +) where + G: CliffordGates + Gates, +{ + match largest_pauli { + PauliLetter::I => panic!("Should not have Pauli::I here"), + PauliLetter::X => { + pauli_polynomial.h(selected_qubit); + clifford_tableau.h(selected_qubit); + repr.h(selected_qubit); + } + PauliLetter::Y => { + pauli_polynomial.v(selected_qubit); + clifford_tableau.v(selected_qubit); + repr.v(selected_qubit); + } + PauliLetter::Z => {} + } +} diff --git a/synir/src/ir/pauli_polynomial/naive.rs b/synir/src/ir/pauli_polynomial/naive.rs index 234b60c9..29d924d7 100644 --- a/synir/src/ir/pauli_polynomial/naive.rs +++ b/synir/src/ir/pauli_polynomial/naive.rs @@ -9,16 +9,9 @@ use bitvec::{bitvec, order::Lsb0}; use super::helper::push_down_pauli_polynomial_update; #[derive(Default)] -pub struct NaivePauliPolynomialSynthesizer { - clifford_tableau: CliffordTableau, -} +pub struct NaivePauliPolynomialSynthesizer {} -impl NaivePauliPolynomialSynthesizer { - pub fn set_clifford_tableau(&mut self, clifford_tableau: CliffordTableau) -> &mut Self { - self.clifford_tableau = clifford_tableau; - self - } -} +impl NaivePauliPolynomialSynthesizer {} impl Synthesizer, G, CliffordTableau> for NaivePauliPolynomialSynthesizer @@ -30,7 +23,7 @@ where mut pauli_polynomials: VecDeque, repr: &mut G, ) -> CliffordTableau { - let mut clifford_tableau = std::mem::take(&mut self.clifford_tableau); + let mut clifford_tableau = CliffordTableau::new(pauli_polynomials[0].size()); while !pauli_polynomials.is_empty() { let pauli_polynomial = pauli_polynomials.pop_front().unwrap(); let num_gadgets = pauli_polynomial.length(); diff --git a/synir/src/ir/pauli_polynomial/psgs.rs b/synir/src/ir/pauli_polynomial/psgs.rs new file mode 100644 index 00000000..19e05067 --- /dev/null +++ b/synir/src/ir/pauli_polynomial/psgs.rs @@ -0,0 +1,51 @@ +use std::collections::VecDeque; + +use crate::{ + architecture::connectivity::Connectivity, + data_structures::{CliffordTableau, PauliPolynomial}, + ir::{ + pauli_polynomial::helper::{check_columns, identity_recurse}, + CliffordGates, Gates, Synthesizer, + }, +}; +use bitvec::{bitvec, order::Lsb0}; + +#[derive(Default)] +pub struct PSGSPauliPolynomialSynthesizer { + connectivity: Connectivity, +} + +impl PSGSPauliPolynomialSynthesizer { + pub fn set_connectivity(&mut self, connectivity: Connectivity) -> &mut Self { + self.connectivity = connectivity; + self + } +} + +impl Synthesizer, G, CliffordTableau> + for PSGSPauliPolynomialSynthesizer +where + G: CliffordGates + Gates, +{ + fn synthesize( + &mut self, + mut pauli_polynomials: VecDeque, + repr: &mut G, + ) -> CliffordTableau { + let mut clifford_tableau = CliffordTableau::new(pauli_polynomials[0].size()); + while !pauli_polynomials.is_empty() { + let mut pauli_polynomial = pauli_polynomials.pop_front().unwrap(); + let num_gadgets: usize = pauli_polynomial.length(); + let mut polynomial_mask = bitvec![usize, Lsb0; 1; num_gadgets]; + check_columns(repr, &mut pauli_polynomial, &mut polynomial_mask); + identity_recurse( + &mut pauli_polynomial, + &mut clifford_tableau, + &self.connectivity, + polynomial_mask, + repr, + ); + } + clifford_tableau + } +} diff --git a/synir/tests/common/mod.rs b/synir/tests/common/mod.rs index 17c2f05b..631da735 100644 --- a/synir/tests/common/mod.rs +++ b/synir/tests/common/mod.rs @@ -1,2 +1,3 @@ pub mod mock_circuit; pub mod sample_clifford_tableaus; +pub mod sample_pauli_poly; diff --git a/synir/tests/common/sample_pauli_poly.rs b/synir/tests/common/sample_pauli_poly.rs new file mode 100644 index 00000000..e2b9b7c0 --- /dev/null +++ b/synir/tests/common/sample_pauli_poly.rs @@ -0,0 +1,20 @@ +use std::collections::VecDeque; + +use synir::data_structures::PauliPolynomial; + +pub fn setup_complex_pp() -> VecDeque { + let ham_1 = vec![("IZZZ", 0.3)]; + let ham_2 = vec![("XXII", 0.7)]; + + let pp_1 = PauliPolynomial::from_hamiltonian(ham_1); + let pp_2 = PauliPolynomial::from_hamiltonian(ham_2); + VecDeque::from([pp_1, pp_2]) +} + +pub fn setup_simple_pp() -> VecDeque { + let ham = vec![("IXYZ", 0.3)]; + + let pauli_polynomial = PauliPolynomial::from_hamiltonian(ham); + + VecDeque::from([pauli_polynomial]) +} diff --git a/synir/tests/pauli_polynomial.rs b/synir/tests/pauli_polynomial.rs index f4560e9b..45fef364 100644 --- a/synir/tests/pauli_polynomial.rs +++ b/synir/tests/pauli_polynomial.rs @@ -3,7 +3,9 @@ mod common; use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; -use synir::data_structures::{CliffordTableau, PauliPolynomial}; +use synir::architecture::connectivity::Connectivity; +use synir::data_structures::PauliPolynomial; +use synir::ir::pauli_polynomial::psgs::PSGSPauliPolynomialSynthesizer; use synir::ir::pauli_polynomial::NaivePauliPolynomialSynthesizer; use synir::ir::Synthesizer; @@ -29,7 +31,6 @@ fn test_naive_pauli_exponential_synthesis() { let pp = setup_simple_pp(); let mut mock = MockCircuit::new(); let mut synthesizer = NaivePauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); let ct = synthesizer.synthesize(pp, &mut mock); let ref_commands = [ @@ -56,7 +57,6 @@ fn test_naive_pauli_exponential_synthesis_complex() { let pp = setup_complex_pp(); let mut mock = MockCircuit::new(); let mut synthesizer = NaivePauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); let ct = synthesizer.synthesize(pp, &mut mock); let ref_commands = [ @@ -88,3 +88,60 @@ fn test_naive_pauli_exponential_synthesis_complex() { assert_eq!(mock.commands(), &ref_commands); assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); } + +#[test] +fn test_psgs_pauli_exponential_synthesis_simple() { + let pp = setup_simple_pp(); + let mut mock = MockCircuit::new(); + let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); + synthesizer.set_connectivity(Connectivity::complete(4)); + let ct = synthesizer.synthesize(pp, &mut mock); + + let ref_commands = [ + MockCommand::S(1), + MockCommand::CX(3, 1), + MockCommand::V(2), + MockCommand::CX(2, 1), + MockCommand::Ry(1, 0.3), + ]; + + let ref_clifford_commands = [ + MockCommand::S(1), + MockCommand::CX(3, 1), + MockCommand::V(2), + MockCommand::CX(2, 1), + ]; + + assert_eq!(mock.commands(), &ref_commands); + assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); +} + +#[test] +fn test_psgs_pauli_exponential_synthesis_complex() { + let pp = setup_complex_pp(); + let mut mock = MockCircuit::new(); + let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); + synthesizer.set_connectivity(Connectivity::complete(4)); + let ct = synthesizer.synthesize(pp, &mut mock); + + let ref_commands = [ + MockCommand::CX(3, 1), + MockCommand::CX(2, 1), + MockCommand::Rz(1, 0.3), + MockCommand::H(1), + MockCommand::S(0), + MockCommand::CX(1, 0), + MockCommand::Ry(0, -0.7), + ]; + + let ref_clifford_commands = [ + MockCommand::CX(3, 1), + MockCommand::CX(2, 1), + MockCommand::H(1), + MockCommand::S(0), + MockCommand::CX(1, 0), + ]; + + assert_eq!(mock.commands(), &ref_commands); + assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); +} diff --git a/synir/tests/pp_synthesis/mod.rs b/synir/tests/pp_synthesis/mod.rs new file mode 100644 index 00000000..09c9873c --- /dev/null +++ b/synir/tests/pp_synthesis/mod.rs @@ -0,0 +1,2 @@ +pub mod naive; +pub mod psgs; diff --git a/synir/tests/pp_synthesis/naive.rs b/synir/tests/pp_synthesis/naive.rs new file mode 100644 index 00000000..cc35c1eb --- /dev/null +++ b/synir/tests/pp_synthesis/naive.rs @@ -0,0 +1,74 @@ +use std::collections::VecDeque; + +use crate::common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; +use crate::common::sample_pauli_poly::{setup_complex_pp, setup_simple_pp}; +use synir::data_structures::{CliffordTableau, PauliPolynomial}; +use synir::ir::pauli_polynomial::NaivePauliPolynomialSynthesizer; +use synir::ir::Synthesizer; + +fn run_synthesizer(pp: VecDeque) -> (MockCircuit, CliffordTableau) { + let mut mock: MockCircuit = MockCircuit::new(); + let mut synthesizer = NaivePauliPolynomialSynthesizer::default(); + let ct = synthesizer.synthesize(pp, &mut mock); + return (mock, ct); +} + +#[test] +fn test_naive_pauli_exponential_synthesis() { + let pp = setup_simple_pp(); + let (mock, ct) = run_synthesizer(pp); + let ref_commands = [ + MockCommand::H(1), + MockCommand::V(2), + MockCommand::CX(1, 2), + MockCommand::CX(2, 3), + MockCommand::Rz(3, 0.3), + ]; + + let ref_clifford_commands = [ + MockCommand::H(1), + MockCommand::V(2), + MockCommand::CX(1, 2), + MockCommand::CX(2, 3), + ]; + + assert_eq!(mock.commands(), &ref_commands); + assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); +} + +#[test] +fn test_naive_pauli_exponential_synthesis_complex() { + let pp = setup_complex_pp(); + let mut mock = MockCircuit::new(); + let mut synthesizer = NaivePauliPolynomialSynthesizer::default(); + let ct = synthesizer.synthesize(pp, &mut mock); + + let ref_commands = [ + MockCommand::CX(1, 2), + MockCommand::CX(2, 3), + MockCommand::Rz(3, 0.3), + MockCommand::H(0), + MockCommand::H(1), + MockCommand::H(2), + MockCommand::H(3), + MockCommand::CX(0, 1), + MockCommand::CX(1, 2), + MockCommand::CX(2, 3), + MockCommand::Rz(3, 0.7), + ]; + + let ref_clifford_commands = [ + MockCommand::CX(1, 2), + MockCommand::CX(2, 3), + MockCommand::H(0), + MockCommand::H(1), + MockCommand::H(2), + MockCommand::H(3), + MockCommand::CX(0, 1), + MockCommand::CX(1, 2), + MockCommand::CX(2, 3), + ]; + + assert_eq!(mock.commands(), &ref_commands); + assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); +} diff --git a/synir/tests/pp_synthesis/psgs.rs b/synir/tests/pp_synthesis/psgs.rs new file mode 100644 index 00000000..8bc6a562 --- /dev/null +++ b/synir/tests/pp_synthesis/psgs.rs @@ -0,0 +1,72 @@ +use std::collections::VecDeque; + +use crate::common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; +use crate::common::sample_pauli_poly::{setup_complex_pp, setup_simple_pp}; +use synir::architecture::connectivity::Connectivity; +use synir::data_structures::{CliffordTableau, PauliPolynomial}; +use synir::ir::pauli_polynomial::psgs::PSGSPauliPolynomialSynthesizer; +use synir::ir::Synthesizer; + +fn run_synthesizer(pp: VecDeque) -> (MockCircuit, CliffordTableau) { + let mut mock: MockCircuit = MockCircuit::new(); + let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); + let ct = synthesizer.synthesize(pp, &mut mock); + return (mock, ct); +} + +#[test] +fn test_psgs_pauli_exponential_synthesis_simple() { + let pp = setup_simple_pp(); + let mut mock = MockCircuit::new(); + let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); + synthesizer.set_connectivity(Connectivity::complete(4)); + let ct = synthesizer.synthesize(pp, &mut mock); + + let ref_commands = [ + MockCommand::S(1), + MockCommand::CX(3, 1), + MockCommand::V(2), + MockCommand::CX(2, 1), + MockCommand::Ry(1, 0.3), + ]; + + let ref_clifford_commands = [ + MockCommand::S(1), + MockCommand::CX(3, 1), + MockCommand::V(2), + MockCommand::CX(2, 1), + ]; + + assert_eq!(mock.commands(), &ref_commands); + assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); +} + +#[test] +fn test_psgs_pauli_exponential_synthesis_complex() { + let pp = setup_complex_pp(); + let mut mock = MockCircuit::new(); + let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); + synthesizer.set_connectivity(Connectivity::complete(4)); + let ct = synthesizer.synthesize(pp, &mut mock); + + let ref_commands = [ + MockCommand::CX(3, 1), + MockCommand::CX(2, 1), + MockCommand::Rz(1, 0.3), + MockCommand::H(1), + MockCommand::S(0), + MockCommand::CX(1, 0), + MockCommand::Ry(0, -0.7), + ]; + + let ref_clifford_commands = [ + MockCommand::CX(3, 1), + MockCommand::CX(2, 1), + MockCommand::H(1), + MockCommand::S(0), + MockCommand::CX(1, 0), + ]; + + assert_eq!(mock.commands(), &ref_commands); + assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); +} diff --git a/synir/tests/pp_synthesis_tests.rs b/synir/tests/pp_synthesis_tests.rs new file mode 100644 index 00000000..cbea208d --- /dev/null +++ b/synir/tests/pp_synthesis_tests.rs @@ -0,0 +1,2 @@ +mod common; +mod pp_synthesis; diff --git a/synpy/Cargo.lock b/synpy/Cargo.lock index e173122f..a54a01ed 100644 --- a/synpy/Cargo.lock +++ b/synpy/Cargo.lock @@ -44,6 +44,12 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "funty" version = "2.0.0" @@ -55,6 +61,9 @@ name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] [[package]] name = "heck" @@ -110,11 +119,13 @@ checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "petgraph" -version = "0.7.1" -source = "git+https://github.com/daehiff/petgraph?branch=add-mst-prim#705c658dd87d9f571453cff90871e1153d6f96fa" +version = "0.8.2" +source = "git+https://github.com/keefehuang/petgraph#1cdbae4cecd2fb6591697eb8cba3d2c9a76c438e" dependencies = [ "fixedbitset", + "hashbrown", "indexmap", + "serde", ] [[package]] @@ -210,6 +221,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.99", +] + [[package]] name = "syn" version = "0.1.0" @@ -217,6 +248,7 @@ dependencies = [ "bitvec", "itertools", "petgraph", + "typenum", ] [[package]] @@ -251,6 +283,12 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" diff --git a/tests.py b/tests.py new file mode 100644 index 00000000..e9d10ff4 --- /dev/null +++ b/tests.py @@ -0,0 +1,17 @@ +import qiskit +from qiskit.circuit import QuantumCircuit +from qiskit.quantum_info import Operator + +x = QuantumCircuit(1) +x.sdg(0) +x.x(0) +x.s(0) + +op = Operator.from_circuit(x) +print(op) + +x = QuantumCircuit(1) +x.y(0) + +op = Operator.from_circuit(x) +print(op)