From 748685d82fe4ca42d9148836008e211f81609d25 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 13:59:11 +0200 Subject: [PATCH 01/29] Add helper function in PauliString to calculate hamming weight --- synir/src/data_structures/pauli_string.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index d66a3f5a..7f07593a 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -61,6 +61,14 @@ impl PauliString { self.z.count_ones() } + pub fn x_weight(&self) -> usize { + self.x.read().unwrap().count_ones() + } + + pub fn z_weight(&self) -> usize { + self.z.read().unwrap().count_ones() + } + pub fn z(&self, i: usize) -> bool { self.z[i] } From 80898a72b4f448b8134e8a9690d31e890746c974 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:01:40 +0200 Subject: [PATCH 02/29] Update connectivity to fix unintended behavior Moved to StableGraph, remove iterations from 0..graph_size as graphs no longer compact. Ensure unweighted edges have a defaulta weight of 1. Add helper function to find edge and node count of Connectivity. Remove asserts to check if node index is less than node count as graphs are no longer compact. --- synir/src/architecture/connectivity.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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, From b9fd2c77557994cfc5e216125f7ad09601814d43 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:04:03 +0200 Subject: [PATCH 03/29] Correct pivot selection for custom callback Mixup between rows and columns fixed. Added functionality to perform x and z cleanup interchangeably. --- src/ir/clifford_tableau/helper.rs | 336 +++++++++++++++++++++++++ synir/src/ir/clifford_tableau/naive.rs | 3 +- 2 files changed, 338 insertions(+), 1 deletion(-) create mode 100644 src/ir/clifford_tableau/helper.rs diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs new file mode 100644 index 00000000..f94773d4 --- /dev/null +++ b/src/ir/clifford_tableau/helper.rs @@ -0,0 +1,336 @@ +use std::{iter::zip, usize}; + +use crate::{ + architecture::{connectivity::Connectivity, Architecture}, + data_structures::{CliffordTableau, PauliLetter, PauliString, PropagateClifford}, + ir::CliffordGates, +}; + +fn get_pauli(pauli_string: &PauliString, row: usize) -> PauliLetter { + PauliLetter::new(pauli_string.x(row), pauli_string.z(row)) +} + +#[allow(dead_code)] +fn is_i(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::I +} + +fn is_not_i(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::I +} + +fn is_x(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::X +} + +fn is_not_x(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::X +} + +fn is_y(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::Y +} + +#[allow(dead_code)] +fn is_not_y(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::Y +} + +fn is_z(pauli_letter: PauliLetter) -> bool { + pauli_letter == PauliLetter::Z +} + +fn is_not_z(pauli_letter: PauliLetter) -> bool { + pauli_letter != PauliLetter::Z +} + +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 +} diff --git a/synir/src/ir/clifford_tableau/naive.rs b/synir/src/ir/clifford_tableau/naive.rs index 5f50a6a2..799d5325 100644 --- a/synir/src/ir/clifford_tableau/naive.rs +++ b/synir/src/ir/clifford_tableau/naive.rs @@ -4,7 +4,8 @@ use crate::{ }; use super::helper::{ - clean_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, swap, + clean_naive_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, + swap, }; use crate::data_structures::PauliLetter; From f1d3cbf57ad1ff754f910ba4bf25a2913114ba45 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:07:17 +0200 Subject: [PATCH 04/29] Add helper functions for PermRowCol Add clean_prc for X and Z. Add pick_column and pick_row. --- src/ir/clifford_tableau/helper.rs | 202 ++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs index f94773d4..a453c6e6 100644 --- a/src/ir/clifford_tableau/helper.rs +++ b/src/ir/clifford_tableau/helper.rs @@ -334,3 +334,205 @@ pub(super) fn check_across_columns( } 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); + println!("affected cols: {:?}", affected_cols); + 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); + } +} From ce3de009c3993855cfc5c28bf26ab6c71107f283 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:07:32 +0200 Subject: [PATCH 05/29] Implement PermRowCol for CliffordTableau --- src/ir/clifford_tableau/permrowcol.rs | 144 ++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 src/ir/clifford_tableau/permrowcol.rs diff --git a/src/ir/clifford_tableau/permrowcol.rs b/src/ir/clifford_tableau/permrowcol.rs new file mode 100644 index 00000000..b5541561 --- /dev/null +++ b/src/ir/clifford_tableau/permrowcol.rs @@ -0,0 +1,144 @@ +use std::fmt::Debug; + +use crate::{ + architecture::connectivity::Connectivity, + data_structures::{CliffordTableau, PauliLetter}, + ir::{ + clifford_tableau::{ + self, + helper::{ + clean_observables, clean_pivot, clean_prc, clean_x_pivot, clean_z_pivot, + pick_column, pick_row, + }, + }, + AdjointSynthesizer, CliffordGates, + }, +}; + +use super::helper::{ + clean_naive_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, + swap, +}; + +#[derive(Default)] +pub struct PermRowColCliffordSynthesizer { + connectivity: Connectivity, + permutation: Vec, +} + +impl PermRowColCliffordSynthesizer { + pub fn new(connectivity: Connectivity) -> Self { + let size = connectivity.node_bound(); + + Self { + connectivity, + permutation: (0..size).collect(), + } + } + + pub fn permutation(&self) -> &[usize] { + &self.permutation + } +} + +impl AdjointSynthesizer for PermRowColCliffordSynthesizer +where + G: CliffordGates + Debug, +{ + fn synthesize_adjoint(&mut self, mut clifford_tableau: CliffordTableau, repr: &mut G) { + let num_qubits = clifford_tableau.size(); + let machine_size = self.connectivity.node_bound(); + 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 = pick_row(&clifford_tableau, &self.connectivity, &remaining_rows); + let pivot_column = pick_column(&clifford_tableau, &self.connectivity); + println!("pivot row: {}", pivot_row); + println!("pivot column: {}", pivot_column); + 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, + ); + println!("Cleaning: {:?}", first_letter); + println!("repr: {:?}", repr); + println!("clifford_tableau: {}", clifford_tableau); + + // 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, + ); + println!("PRC: {:?}", first_letter); + println!("repr: {:?}", repr); + println!("clifford_tableau: {}", clifford_tableau); + + clean_pivot( + repr, + &mut clifford_tableau, + pivot_column, + pivot_row, + second_letter, + ); + println!("Cleaning: {:?}", second_letter); + println!("repr: {:?}", repr); + println!("clifford_tableau: {}", clifford_tableau); + + // 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, + ); + println!("PRC: {:?}", second_letter); + println!("repr: {:?}", repr); + println!("clifford_tableau: {}", clifford_tableau); + } + + // 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); + println!("nodes: {:?}", self.connectivity.nodes()); + } + + clean_signs(repr, &mut clifford_tableau, &permutation); + + self.permutation = permutation; + } +} From 2daa86de36f420ff8d336c6ab20c6487b17179ab Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:07:43 +0200 Subject: [PATCH 06/29] Add tests for PermRowCol for CliffordTableau --- tests/clifford_tableau.rs | 298 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 tests/clifford_tableau.rs diff --git a/tests/clifford_tableau.rs b/tests/clifford_tableau.rs new file mode 100644 index 00000000..b615efb1 --- /dev/null +++ b/tests/clifford_tableau.rs @@ -0,0 +1,298 @@ +mod common; + +use bitvec::bitvec; +use bitvec::prelude::Lsb0; +use common::{parse_clifford_commands, MockCircuit, MockCommand}; +use syn::architecture::connectivity::Connectivity; +use syn::architecture::Architecture; +use syn::data_structures::{CliffordTableau, PauliString, PropagateClifford}; +use syn::ir::clifford_tableau::{ + CallbackCliffordSynthesizer, NaiveCliffordSynthesizer, PermRowColCliffordSynthesizer, +}; +use syn::ir::{AdjointSynthesizer, CliffordGates, Synthesizer}; + +fn setup_sample_ct() -> CliffordTableau { + // 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"); + + let signs = bitvec![0, 1, 0, 1, 0, 0]; + CliffordTableau::from_parts(vec![pauli_1, pauli_2, pauli_3], signs) +} + +fn setup_sample_inverse_ct() -> CliffordTableau { + // Stab: -ZIYZ, -ZZYZ, -XZXI, IZXX + // Destab: -YYIZ, -YYXZ, ZIXX, -XZXZ + // qubit 1x: ZZXI + // qubit 1z: YYZX + let pauli_1 = PauliString::from_text("ZZXIYYZX"); + + // qubit 2x: IZZZ + // qubit 2z: YYIZ + let pauli_2 = PauliString::from_text("IZZZYYIZ"); + + // qubit 3x: YYXX + // qubit 3z: IXXX + let pauli_3 = PauliString::from_text("YYXXIXXX"); + + // qubit 3x: ZZIX + // qubit 3z: ZZXZ + let pauli_4 = PauliString::from_text("ZZIXZZXZ"); + + let signs = bitvec![1, 1, 1, 0, 1, 1, 0, 1]; + CliffordTableau::from_parts(vec![pauli_1, pauli_2, pauli_3, pauli_4], signs) +} + +fn setup_2_qubit_clifford() -> CliffordTableau { + // qubit 1x: ZZXI + // qubit 1z: YYZX + let pauli_1 = PauliString::from_text("XIZI"); + + // qubit 2x: IZZZ + // qubit 2z: YYIZ + let pauli_2 = PauliString::from_text("IXIZ"); + + let signs = bitvec![0, 0, 0, 0]; + CliffordTableau::from_parts(vec![pauli_1, pauli_2], signs) +} + +#[test] +fn test_id_synthesis() { + let clifford_tableau = setup_2_qubit_clifford(); + let mut mock = MockCircuit::new(); + + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau, &mut mock); + assert_eq!(mock.commands(), &vec![]); +} + +#[test] +fn test_s_synthesis() { + let mut clifford_tableau = setup_2_qubit_clifford(); + clifford_tableau.s(1); + let mut mock = MockCircuit::new(); + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + assert_eq!(mock.commands(), &vec![MockCommand::S(1)]); +} + +#[test] +fn test_s_adjoint_synthesis() { + let mut clifford_tableau = setup_2_qubit_clifford(); + clifford_tableau.s(1); + let mut mock = MockCircuit::new(); + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize_adjoint(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(2, mock.commands()); + assert_eq!(clifford_tableau * ref_ct, CliffordTableau::new(2)); + + assert_eq!(mock.commands(), &vec![MockCommand::S(1), MockCommand::Z(1)]); +} + +#[test] +fn test_v_synthesis() { + let mut clifford_tableau = setup_2_qubit_clifford(); + clifford_tableau.v(1); + let mut mock = MockCircuit::new(); + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + assert_eq!( + mock.commands(), + &vec![ + MockCommand::S(1), + MockCommand::H(1), + MockCommand::S(1), + MockCommand::X(1) + ] + ); +} + +#[test] +fn test_v_adjoint_synthesis() { + let mut clifford_tableau = setup_2_qubit_clifford(); + clifford_tableau.v(1); + let mut mock = MockCircuit::new(); + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize_adjoint(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(2, mock.commands()); + assert_eq!(clifford_tableau * ref_ct, CliffordTableau::new(2)); + + assert_eq!( + mock.commands(), + &vec![MockCommand::S(1), MockCommand::H(1), MockCommand::S(1),] + ); +} + +#[test] +fn test_cnot_synthesis() { + let mut clifford_tableau = setup_2_qubit_clifford(); + clifford_tableau.cx(0, 1); + let mut mock = MockCircuit::new(); + + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + assert_eq!(mock.commands(), &vec![MockCommand::CX(0, 1)]); +} + +#[test] +fn test_cnot_reverse_synthesis() { + let mut clifford_tableau = setup_2_qubit_clifford(); + clifford_tableau.cx(1, 0); + let mut mock = MockCircuit::new(); + + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + assert_eq!(mock.commands(), &vec![MockCommand::CX(1, 0)]); +} + +#[test] +fn test_clifford_synthesis() { + let clifford_tableau = setup_sample_ct(); + let mut mock = MockCircuit::new(); + + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(3, mock.commands()); + + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_clifford_synthesis_large() { + let clifford_tableau = setup_sample_inverse_ct(); + let mut mock = MockCircuit::new(); + + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(4, mock.commands()); + + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_clifford_synthesis_simple() { + let mut clifford_tableau = CliffordTableau::new(3); + clifford_tableau.cx(0, 1); + clifford_tableau.cx(1, 2); + let mut mock = MockCircuit::new(); + + let mut synthesizer = NaiveCliffordSynthesizer::default(); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(3, mock.commands()); + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_custom_clifford_synthesis() { + let clifford_tableau = setup_sample_ct(); + let mut mock = MockCircuit::new(); + + let mut synthesizer = CallbackCliffordSynthesizer::custom_pivot(vec![0, 1, 2], vec![0, 1, 2]); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(3, mock.commands()); + + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_custom_clifford_synthesis_large() { + let clifford_tableau = setup_sample_inverse_ct(); + let mut mock = MockCircuit::new(); + + let mut synthesizer = + CallbackCliffordSynthesizer::custom_pivot(vec![0, 1, 2, 3], vec![0, 2, 1, 3]); + + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let mut ref_ct = parse_clifford_commands(4, mock.commands()); + ref_ct.permute(&[0, 2, 1, 3]); + + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_custom_clifford_synthesis_simple() { + let mut clifford_tableau = CliffordTableau::new(3); + clifford_tableau.cx(0, 1); + clifford_tableau.cx(1, 2); + let mut mock = MockCircuit::new(); + + let mut synthesizer = CallbackCliffordSynthesizer::custom_pivot(vec![0, 1, 2], vec![0, 1, 2]); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(3, mock.commands()); + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_prc_clifford_synthesis() { + let clifford_tableau = setup_sample_ct(); + let num_qubits = clifford_tableau.size(); + let mut mock = MockCircuit::new(); + let connectivity = Connectivity::complete(num_qubits); + let mut synthesizer = PermRowColCliffordSynthesizer::new(connectivity); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let mut ref_ct = parse_clifford_commands(3, mock.commands()); + ref_ct.permute(synthesizer.permutation()); + println!("ref_ct: {}", ref_ct); + println!("clifford_tableau: {}", clifford_tableau); + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_prc_clifford_synthesis_large() { + let mut clifford_tableau = setup_sample_inverse_ct(); + let mut mock = MockCircuit::new(); + + let connectivity = Connectivity::grid(2, 2); + let mut synthesizer = PermRowColCliffordSynthesizer::new(connectivity); + + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(4, mock.commands()); + clifford_tableau.permute(synthesizer.permutation()); + + assert_eq!(clifford_tableau, ref_ct); +} + +#[test] +fn test_prc_clifford_synthesis_simple() { + let num_qubits = 3; + let mut clifford_tableau = CliffordTableau::new(num_qubits); + + clifford_tableau.cx(2, 1); + clifford_tableau.cx(1, 2); + clifford_tableau.cx(0, 2); + let mut mock = MockCircuit::new(); + + let connectivity = Connectivity::line(num_qubits); + + let mut synthesizer = PermRowColCliffordSynthesizer::new(connectivity); + synthesizer.synthesize(clifford_tableau.clone(), &mut mock); + + let ref_ct = parse_clifford_commands(3, mock.commands()); + + clifford_tableau.permute(synthesizer.permutation()); + assert_eq!(clifford_tableau, ref_ct); +} From cd47a94dd636242f7b20aea013d5f3b035406552 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:17:08 +0200 Subject: [PATCH 07/29] Update benchmark code Remove unused imports. Switch deprecated functions to new names. Remove uneeded references. --- synir/benches/clifford_tableau.rs | 2 -- 1 file changed, 2 deletions(-) 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, From 67c388efb89ee1ddb4c4e938d113eb7eee24163e Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:18:20 +0200 Subject: [PATCH 08/29] Remove unused import --- src/ir/clifford_tableau/helper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs index a453c6e6..507adb4e 100644 --- a/src/ir/clifford_tableau/helper.rs +++ b/src/ir/clifford_tableau/helper.rs @@ -1,4 +1,4 @@ -use std::{iter::zip, usize}; +use std::iter::zip; use crate::{ architecture::{connectivity::Connectivity, Architecture}, From 4004b86d52dc4812612962b34448c99c46c1c051 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:18:30 +0200 Subject: [PATCH 09/29] Remove println statement --- src/ir/clifford_tableau/helper.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs index 507adb4e..3cfccd99 100644 --- a/src/ir/clifford_tableau/helper.rs +++ b/src/ir/clifford_tableau/helper.rs @@ -454,7 +454,6 @@ pub(super) fn clean_x_prc( .unwrap(); let affected_cols = check_across_columns(&*clifford_tableau, &terminals, pivot_row, is_y); - println!("affected cols: {:?}", affected_cols); for col in affected_cols { repr.s(col); clifford_tableau.s(col); From 735acfa946aac0786abecbbc67430dc8009d7cf5 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:18:42 +0200 Subject: [PATCH 10/29] Remove unused imports and print statements --- src/ir/clifford_tableau/permrowcol.rs | 28 ++------------------------- tests/clifford_tableau.rs | 6 ++---- 2 files changed, 4 insertions(+), 30 deletions(-) diff --git a/src/ir/clifford_tableau/permrowcol.rs b/src/ir/clifford_tableau/permrowcol.rs index b5541561..d7379e1e 100644 --- a/src/ir/clifford_tableau/permrowcol.rs +++ b/src/ir/clifford_tableau/permrowcol.rs @@ -4,21 +4,12 @@ use crate::{ architecture::connectivity::Connectivity, data_structures::{CliffordTableau, PauliLetter}, ir::{ - clifford_tableau::{ - self, - helper::{ - clean_observables, clean_pivot, clean_prc, clean_x_pivot, clean_z_pivot, - pick_column, pick_row, - }, - }, + clifford_tableau::helper::{clean_pivot, clean_prc, pick_column, pick_row}, AdjointSynthesizer, CliffordGates, }, }; -use super::helper::{ - clean_naive_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, - swap, -}; +use super::helper::clean_signs; #[derive(Default)] pub struct PermRowColCliffordSynthesizer { @@ -64,8 +55,6 @@ where while !remaining_columns.is_empty() { let pivot_row = pick_row(&clifford_tableau, &self.connectivity, &remaining_rows); let pivot_column = pick_column(&clifford_tableau, &self.connectivity); - println!("pivot row: {}", pivot_row); - println!("pivot column: {}", pivot_column); let column = clifford_tableau.column(pivot_column); let x_weight = column.x_weight(); let z_weight = column.z_weight(); @@ -86,9 +75,6 @@ where pivot_row, first_letter, ); - println!("Cleaning: {:?}", first_letter); - println!("repr: {:?}", repr); - println!("clifford_tableau: {}", clifford_tableau); // Use the pivot to remove all other terms in the X observable. clean_prc( @@ -100,9 +86,6 @@ where pivot_row, first_letter, ); - println!("PRC: {:?}", first_letter); - println!("repr: {:?}", repr); - println!("clifford_tableau: {}", clifford_tableau); clean_pivot( repr, @@ -111,9 +94,6 @@ where pivot_row, second_letter, ); - println!("Cleaning: {:?}", second_letter); - println!("repr: {:?}", repr); - println!("clifford_tableau: {}", clifford_tableau); // Use the pivot to remove all other terms in the Z observable. clean_prc( @@ -125,16 +105,12 @@ where pivot_row, second_letter, ); - println!("PRC: {:?}", second_letter); - println!("repr: {:?}", repr); - println!("clifford_tableau: {}", clifford_tableau); } // 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); - println!("nodes: {:?}", self.connectivity.nodes()); } clean_signs(repr, &mut clifford_tableau, &permutation); diff --git a/tests/clifford_tableau.rs b/tests/clifford_tableau.rs index b615efb1..c8b4d529 100644 --- a/tests/clifford_tableau.rs +++ b/tests/clifford_tableau.rs @@ -4,12 +4,11 @@ use bitvec::bitvec; use bitvec::prelude::Lsb0; use common::{parse_clifford_commands, MockCircuit, MockCommand}; use syn::architecture::connectivity::Connectivity; -use syn::architecture::Architecture; use syn::data_structures::{CliffordTableau, PauliString, PropagateClifford}; use syn::ir::clifford_tableau::{ CallbackCliffordSynthesizer, NaiveCliffordSynthesizer, PermRowColCliffordSynthesizer, }; -use syn::ir::{AdjointSynthesizer, CliffordGates, Synthesizer}; +use syn::ir::{AdjointSynthesizer, Synthesizer}; fn setup_sample_ct() -> CliffordTableau { // Stab: ZZZ, -YIY, XIX @@ -255,8 +254,7 @@ fn test_prc_clifford_synthesis() { let mut ref_ct = parse_clifford_commands(3, mock.commands()); ref_ct.permute(synthesizer.permutation()); - println!("ref_ct: {}", ref_ct); - println!("clifford_tableau: {}", clifford_tableau); + assert_eq!(clifford_tableau, ref_ct); } From 220fa3f7b579a88e98339aad70186770867eee72 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 6 Aug 2025 17:41:13 +0200 Subject: [PATCH 11/29] Add more PauliString and PauliPolynomial helper functions --- synir/src/data_structures/pauli_polynomial.rs | 31 ++++++++++++++++--- synir/src/data_structures/pauli_string.rs | 25 +++++++++++++++ 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index ff38897b..0bc8e955 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -3,10 +3,9 @@ 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 { @@ -36,13 +35,25 @@ impl PauliPolynomial { .map(|gadget| PauliString::from_text(gadget)) .collect::>(); - PauliPolynomial { + Self { chains, angles, size: num_qubits, } } + pub fn from_components( + chains: Vec, + angles: RwLock>, + size: usize, + ) -> Self { + Self { + chains, + angles, + size, + } + } + pub fn size(&self) -> usize { self.size } @@ -59,9 +70,21 @@ impl PauliPolynomial { &self.chains } + pub fn angles(&self) -> &RwLock> { + &self.angles + } + pub fn angle(&self, i: usize) -> Angle { self.angles[i] } + + pub fn chain(&self, index: usize) -> &PauliString { + &self.chains[index] + } + + pub fn mut_chains(&mut self) -> &mut Vec { + &mut self.chains + } } impl PropagateClifford for PauliPolynomial { diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index 7f07593a..960854fb 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -68,6 +68,15 @@ impl PauliString { pub fn z_weight(&self) -> usize { self.z.read().unwrap().count_ones() } + pub fn combine(&self) -> BitVec { + let mut new_string = self.z.read().unwrap().to_bitvec(); + new_string |= self.x.read().unwrap().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] @@ -77,6 +86,16 @@ impl PauliString { PauliLetter::new(self.x(i), self.z(i)) } + pub fn iter(&self) -> Vec { + self.x + .read() + .unwrap() + .iter() + .zip(self.z.read().unwrap().iter()) + .map(|(x, z)| PauliLetter::new(*x, *z)) + .collect::>() + } + pub fn len(&self) -> usize { self.x.len() } @@ -131,6 +150,12 @@ impl PauliString { mask &= &self.z; mask } + + pub(crate) fn swap_remove(&mut self, index: usize) -> PauliLetter { + let x = self.x.write().unwrap().swap_remove(index); + let z = self.z.write().unwrap().swap_remove(index); + PauliLetter::new(x, z) + } } pub(crate) fn cx(control: &mut PauliString, target: &mut PauliString) { From dac6380c974ec754c570a89f1cf813ff24c9947d Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 6 Aug 2025 17:41:56 +0200 Subject: [PATCH 12/29] Shift pauli checks up one folder level to allow all synthesis methods to access them --- src/ir/clifford_tableau/helper.rs | 39 ++++--------------------------- src/ir/helper.rs | 35 +++++++++++++++++++++++++++ synir/src/ir.rs | 1 + 3 files changed, 40 insertions(+), 35 deletions(-) create mode 100644 src/ir/helper.rs diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs index 3cfccd99..a0ac1faa 100644 --- a/src/ir/clifford_tableau/helper.rs +++ b/src/ir/clifford_tableau/helper.rs @@ -3,47 +3,16 @@ use std::iter::zip; use crate::{ architecture::{connectivity::Connectivity, Architecture}, data_structures::{CliffordTableau, PauliLetter, PauliString, PropagateClifford}, - ir::CliffordGates, + 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)) } -#[allow(dead_code)] -fn is_i(pauli_letter: PauliLetter) -> bool { - pauli_letter == PauliLetter::I -} - -fn is_not_i(pauli_letter: PauliLetter) -> bool { - pauli_letter != PauliLetter::I -} - -fn is_x(pauli_letter: PauliLetter) -> bool { - pauli_letter == PauliLetter::X -} - -fn is_not_x(pauli_letter: PauliLetter) -> bool { - pauli_letter != PauliLetter::X -} - -fn is_y(pauli_letter: PauliLetter) -> bool { - pauli_letter == PauliLetter::Y -} - -#[allow(dead_code)] -fn is_not_y(pauli_letter: PauliLetter) -> bool { - pauli_letter != PauliLetter::Y -} - -fn is_z(pauli_letter: PauliLetter) -> bool { - pauli_letter == PauliLetter::Z -} - -fn is_not_z(pauli_letter: PauliLetter) -> bool { - pauli_letter != PauliLetter::Z -} - pub(super) fn clean_naive_pivot( repr: &mut G, ct: &mut CliffordTableau, 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/src/ir.rs b/synir/src/ir.rs index e04f4037..df1dc311 100644 --- a/synir/src/ir.rs +++ b/synir/src/ir.rs @@ -1,6 +1,7 @@ use crate::{data_structures::HasAdjoint, IndexType}; pub mod clifford_tableau; +pub(crate) mod helper; pub mod pauli_exponential; pub mod pauli_polynomial; From 57938046e90547e191753c72ba72729306e5dab9 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 6 Aug 2025 17:43:01 +0200 Subject: [PATCH 13/29] Implement PSGS strategy for PauliPolynomials --- synir/src/data_structures.rs | 2 + synir/src/ir/pauli_polynomial.rs | 2 + synir/src/ir/pauli_polynomial/helper.rs | 383 +++++++++++++++++++++++- synir/src/ir/pauli_polynomial/psgs.rs | 53 ++++ 4 files changed, 436 insertions(+), 4 deletions(-) create mode 100644 synir/src/ir/pauli_polynomial/psgs.rs 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/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..94b43d90 100644 --- a/synir/src/ir/pauli_polynomial/helper.rs +++ b/synir/src/ir/pauli_polynomial/helper.rs @@ -1,14 +1,18 @@ -use std::collections::VecDeque; +use std::{collections::VecDeque, fs::remove_dir, iter::zip, ops::AddAssign, sync::RwLock}; -use bitvec::{bitvec, order::Lsb0}; +use bitvec::{bitvec, index, order::Lsb0}; use itertools::Itertools; +use typenum::Bit; use crate::{ + architecture::{connectivity::Connectivity, Architecture}, data_structures::{ - CliffordTableau, MaskedPropagateClifford, PauliLetter, PauliPolynomial, PropagateClifford, + Angle, CliffordTableau, MaskedPropagateClifford, PauliLetter, PauliPolynomial, PauliString, + PropagateClifford, }, - ir::{CliffordGates, Gates}, + ir::{helper::is_not_i, pauli_polynomial, CliffordGates, Gates}, }; + use bitvec::prelude::BitVec; impl PropagateClifford for VecDeque { @@ -112,3 +116,374 @@ 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; + let mut angles = angles.write().unwrap(); + 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 + return 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, + ); + + return 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/psgs.rs b/synir/src/ir/pauli_polynomial/psgs.rs new file mode 100644 index 00000000..92ecce61 --- /dev/null +++ b/synir/src/ir/pauli_polynomial/psgs.rs @@ -0,0 +1,53 @@ +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 { + clifford_tableau: CliffordTableau, + connectivity: Connectivity, +} + +impl PSGSPauliPolynomialSynthesizer { + pub fn set_clifford_tableau(&mut self, clifford_tableau: CliffordTableau) -> &mut Self { + self.clifford_tableau = clifford_tableau; + self + } + + 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 = std::mem::take(&mut self.clifford_tableau); + 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 + } +} From 218d9d8d7d45e5f09986b0cc62daa9a1ee0dc643 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 6 Aug 2025 17:43:38 +0200 Subject: [PATCH 14/29] Add tests for PSGS implementation, extend synthesis method enum to include PSGS --- synir/src/ir/pauli_exponential.rs | 14 ++++++--- synir/tests/pauli_polynomial.rs | 52 +++++++++++++++++++++++++++++++ tests.py | 17 ++++++++++ 3 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 tests.py diff --git a/synir/src/ir/pauli_exponential.rs b/synir/src/ir/pauli_exponential.rs index 79be5518..ca163efc 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,12 +74,17 @@ 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) + 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_clifford_tableau(clifford_tableau); + pauli_synthesizer.set_connectivity(Connectivity::complete(num_qubits)); + pauli_synthesizer.synthesize(pauli_polynomials, repr) } }; diff --git a/synir/tests/pauli_polynomial.rs b/synir/tests/pauli_polynomial.rs index f4560e9b..68bb2e1e 100644 --- a/synir/tests/pauli_polynomial.rs +++ b/synir/tests/pauli_polynomial.rs @@ -88,3 +88,55 @@ 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_clifford_tableau(CliffordTableau::new(4)); + 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_clifford_tableau(CliffordTableau::new(4)); + 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)); +} \ No newline at end of file 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) From 5cd6480f7d3cf2dcfc44b09ed449cd6d7724ad3d Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 6 Aug 2025 17:44:01 +0200 Subject: [PATCH 15/29] Update Cargo.lock --- synpy/Cargo.lock | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) 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" From 5394668a95e9054a40b74dd5b7e2f0a252b0c02e Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 14 Aug 2025 13:03:28 +0200 Subject: [PATCH 16/29] Remove uneeded references, return calls and imports --- synir/src/ir/pauli_exponential.rs | 17 +++++----- synir/src/ir/pauli_polynomial/helper.rs | 45 +++++++++++++------------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/synir/src/ir/pauli_exponential.rs b/synir/src/ir/pauli_exponential.rs index ca163efc..aa7e300f 100644 --- a/synir/src/ir/pauli_exponential.rs +++ b/synir/src/ir/pauli_exponential.rs @@ -77,14 +77,15 @@ where 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_clifford_tableau(clifford_tableau); - pauli_synthesizer.set_connectivity(Connectivity::complete(num_qubits)); - pauli_synthesizer.synthesize(pauli_polynomials, repr) + 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_clifford_tableau(clifford_tableau); + pauli_synthesizer.set_connectivity(Connectivity::complete(num_qubits)); + pauli_synthesizer.synthesize(pauli_polynomials, repr) } }; diff --git a/synir/src/ir/pauli_polynomial/helper.rs b/synir/src/ir/pauli_polynomial/helper.rs index 94b43d90..29e411cf 100644 --- a/synir/src/ir/pauli_polynomial/helper.rs +++ b/synir/src/ir/pauli_polynomial/helper.rs @@ -1,16 +1,14 @@ -use std::{collections::VecDeque, fs::remove_dir, iter::zip, ops::AddAssign, sync::RwLock}; +use std::{collections::VecDeque, iter::zip}; -use bitvec::{bitvec, index, order::Lsb0}; +use bitvec::{bitvec, order::Lsb0}; use itertools::Itertools; -use typenum::Bit; use crate::{ architecture::{connectivity::Connectivity, Architecture}, data_structures::{ - Angle, CliffordTableau, MaskedPropagateClifford, PauliLetter, PauliPolynomial, PauliString, - PropagateClifford, + CliffordTableau, MaskedPropagateClifford, PauliLetter, PauliPolynomial, PropagateClifford, }, - ir::{helper::is_not_i, pauli_polynomial, CliffordGates, Gates}, + ir::{CliffordGates, Gates}, }; use bitvec::prelude::BitVec; @@ -153,7 +151,7 @@ pub(super) fn check_columns( 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; @@ -268,7 +266,7 @@ pub(super) fn max_partition( (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(); @@ -305,9 +303,8 @@ pub(super) fn identity_recurse( 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, @@ -317,13 +314,13 @@ pub(super) fn identity_recurse( repr, ); // ensure remainder is synthesized - return identity_recurse( + 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, _) = @@ -332,11 +329,15 @@ pub(super) fn identity_recurse( 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)); - + 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); + identity_partition(pauli_polynomial, largest_mask, next_qubit); // Ensure that selected qubit is always Pauli::Z diagonalize_qubit( @@ -358,7 +359,7 @@ pub(super) fn identity_recurse( remaining_mask |= next_other_mask.as_bitslice(); let (identity_mask, other_mask) = - identity_partition(&pauli_polynomial, remaining_mask, selected_qubit); + identity_partition(pauli_polynomial, remaining_mask, selected_qubit); identity_recurse( pauli_polynomial, @@ -368,13 +369,13 @@ pub(super) fn identity_recurse( repr, ); - return identity_recurse( + identity_recurse( pauli_polynomial, clifford_tableau, connectivity, other_mask, repr, - ); + ) } else { let ( mut largest_mask, @@ -383,12 +384,12 @@ pub(super) fn identity_recurse( second_next_pauli, third_mask, _, - ) = max_partition(&pauli_polynomial, next_other_mask, next_qubit); - + ) = 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, @@ -398,7 +399,7 @@ pub(super) fn identity_recurse( is_x, is_y, ); - + identity_recurse( pauli_polynomial, clifford_tableau, From a2d0f1d90e547a3ad1783b00889b5f796750f96f Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Thu, 14 Aug 2025 13:04:37 +0200 Subject: [PATCH 17/29] Reformat docs --- synir/src/ir/pauli_polynomial/psgs.rs | 12 ++++--- synir/tests/pauli_polynomial.rs | 45 ++++++++++++++++----------- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/synir/src/ir/pauli_polynomial/psgs.rs b/synir/src/ir/pauli_polynomial/psgs.rs index 92ecce61..7426b9cb 100644 --- a/synir/src/ir/pauli_polynomial/psgs.rs +++ b/synir/src/ir/pauli_polynomial/psgs.rs @@ -4,9 +4,7 @@ use crate::{ architecture::connectivity::Connectivity, data_structures::{CliffordTableau, PauliPolynomial}, ir::{ - pauli_polynomial::{ - helper::{check_columns, identity_recurse}, - }, + pauli_polynomial::helper::{check_columns, identity_recurse}, CliffordGates, Gates, Synthesizer, }, }; @@ -46,7 +44,13 @@ where 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); + identity_recurse( + &mut pauli_polynomial, + &mut clifford_tableau, + &self.connectivity, + polynomial_mask, + repr, + ); } clifford_tableau } diff --git a/synir/tests/pauli_polynomial.rs b/synir/tests/pauli_polynomial.rs index 68bb2e1e..5c9cc0fb 100644 --- a/synir/tests/pauli_polynomial.rs +++ b/synir/tests/pauli_polynomial.rs @@ -98,18 +98,25 @@ fn test_psgs_pauli_exponential_synthesis_simple() { 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_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) + 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(); @@ -120,23 +127,23 @@ fn test_psgs_pauli_exponential_synthesis_complex() { 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) - ]; + 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), + 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)); -} \ No newline at end of file +} From 65632e80b0ca24ab43b1d9186f992f8d2c7ace4c Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 13:59:11 +0200 Subject: [PATCH 18/29] Add helper function in PauliString to calculate hamming weight --- synir/src/data_structures/pauli_string.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index 960854fb..7e5cac6f 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -78,6 +78,14 @@ impl PauliString { new_string.count_ones() } + pub fn x_weight(&self) -> usize { + self.x.read().unwrap().count_ones() + } + + pub fn z_weight(&self) -> usize { + self.z.read().unwrap().count_ones() + } + pub fn z(&self, i: usize) -> bool { self.z[i] } From 2738ac0adaa94864ea9a4820ccf9ab68128628b8 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:04:03 +0200 Subject: [PATCH 19/29] Correct pivot selection for custom callback Mixup between rows and columns fixed. Added functionality to perform x and z cleanup interchangeably. --- src/ir/clifford_tableau/helper.rs | 2 +- synir/src/ir/clifford_tableau/naive.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs index a0ac1faa..5c4b5c19 100644 --- a/src/ir/clifford_tableau/helper.rs +++ b/src/ir/clifford_tableau/helper.rs @@ -1,4 +1,4 @@ -use std::iter::zip; +use std::{iter::zip, usize}; use crate::{ architecture::{connectivity::Connectivity, Architecture}, diff --git a/synir/src/ir/clifford_tableau/naive.rs b/synir/src/ir/clifford_tableau/naive.rs index 799d5325..e4015fda 100644 --- a/synir/src/ir/clifford_tableau/naive.rs +++ b/synir/src/ir/clifford_tableau/naive.rs @@ -4,7 +4,7 @@ use crate::{ }; use super::helper::{ - clean_naive_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, + clean_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, swap, }; From b4b22577b0cd819a2a39afa0250b7ec4ecaba307 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:07:43 +0200 Subject: [PATCH 20/29] Add tests for PermRowCol for CliffordTableau --- tests/clifford_tableau.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/clifford_tableau.rs b/tests/clifford_tableau.rs index c8b4d529..c2e7f179 100644 --- a/tests/clifford_tableau.rs +++ b/tests/clifford_tableau.rs @@ -254,7 +254,6 @@ fn test_prc_clifford_synthesis() { let mut ref_ct = parse_clifford_commands(3, mock.commands()); ref_ct.permute(synthesizer.permutation()); - assert_eq!(clifford_tableau, ref_ct); } From 0e57fdcda46498468fbc3d50956ceeefcabb6033 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Fri, 4 Jul 2025 14:18:20 +0200 Subject: [PATCH 21/29] Remove unused import --- src/ir/clifford_tableau/helper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/clifford_tableau/helper.rs b/src/ir/clifford_tableau/helper.rs index 5c4b5c19..a0ac1faa 100644 --- a/src/ir/clifford_tableau/helper.rs +++ b/src/ir/clifford_tableau/helper.rs @@ -1,4 +1,4 @@ -use std::{iter::zip, usize}; +use std::iter::zip; use crate::{ architecture::{connectivity::Connectivity, Architecture}, From 6c4fccc03ed65ec14f7a86132698ad8536d37e5b Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Sat, 20 Sep 2025 10:57:35 +0200 Subject: [PATCH 22/29] Rename edge_ and node_bound to edge_ and node_count --- src/ir/clifford_tableau/permrowcol.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ir/clifford_tableau/permrowcol.rs b/src/ir/clifford_tableau/permrowcol.rs index d7379e1e..d2946863 100644 --- a/src/ir/clifford_tableau/permrowcol.rs +++ b/src/ir/clifford_tableau/permrowcol.rs @@ -19,7 +19,7 @@ pub struct PermRowColCliffordSynthesizer { impl PermRowColCliffordSynthesizer { pub fn new(connectivity: Connectivity) -> Self { - let size = connectivity.node_bound(); + let size = connectivity.node_count(); Self { connectivity, @@ -38,7 +38,7 @@ where { fn synthesize_adjoint(&mut self, mut clifford_tableau: CliffordTableau, repr: &mut G) { let num_qubits = clifford_tableau.size(); - let machine_size = self.connectivity.node_bound(); + let machine_size = self.connectivity.node_count(); assert!( num_qubits <= machine_size, "Number of qubits {} exceeds machine size {}", From b19a5aaf677a6dcb78a7cce98fe3bfed4cf90610 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 6 Oct 2025 17:06:56 +0200 Subject: [PATCH 23/29] Replace clean_naive_pivot with clean_pivot --- synir/src/ir/clifford_tableau/naive.rs | 1 - tests/clifford_tableau.rs | 7 ++----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/synir/src/ir/clifford_tableau/naive.rs b/synir/src/ir/clifford_tableau/naive.rs index e4015fda..3b1e862b 100644 --- a/synir/src/ir/clifford_tableau/naive.rs +++ b/synir/src/ir/clifford_tableau/naive.rs @@ -47,7 +47,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/tests/clifford_tableau.rs b/tests/clifford_tableau.rs index c2e7f179..526af119 100644 --- a/tests/clifford_tableau.rs +++ b/tests/clifford_tableau.rs @@ -111,10 +111,7 @@ fn test_v_synthesis() { assert_eq!( mock.commands(), &vec![ - MockCommand::S(1), - MockCommand::H(1), - MockCommand::S(1), - MockCommand::X(1) + MockCommand::V(1), ] ); } @@ -132,7 +129,7 @@ fn test_v_adjoint_synthesis() { assert_eq!( mock.commands(), - &vec![MockCommand::S(1), MockCommand::H(1), MockCommand::S(1),] + &vec![MockCommand::V(1), MockCommand::X(1)] ); } From 1fcaa69533edd017aa8422daaabf8e0fa06e90e2 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 6 Oct 2025 17:08:02 +0200 Subject: [PATCH 24/29] Add PermRowCol as strategy in pauli_exponential.rs --- src/ir/clifford_tableau/permrowcol.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir/clifford_tableau/permrowcol.rs b/src/ir/clifford_tableau/permrowcol.rs index d2946863..616d9cc3 100644 --- a/src/ir/clifford_tableau/permrowcol.rs +++ b/src/ir/clifford_tableau/permrowcol.rs @@ -34,7 +34,7 @@ impl PermRowColCliffordSynthesizer { impl AdjointSynthesizer for PermRowColCliffordSynthesizer where - G: CliffordGates + Debug, + G: CliffordGates, { fn synthesize_adjoint(&mut self, mut clifford_tableau: CliffordTableau, repr: &mut G) { let num_qubits = clifford_tableau.size(); From fc2f6525d268ef1deba3fae294274cd6943d5a77 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Mon, 6 Oct 2025 17:52:49 +0200 Subject: [PATCH 25/29] Add ability to modify row and column selection strategy for PermRowColCliffordSynthesizer --- src/ir/clifford_tableau/permrowcol.rs | 36 ++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/src/ir/clifford_tableau/permrowcol.rs b/src/ir/clifford_tableau/permrowcol.rs index 616d9cc3..1cc6d8eb 100644 --- a/src/ir/clifford_tableau/permrowcol.rs +++ b/src/ir/clifford_tableau/permrowcol.rs @@ -11,10 +11,23 @@ use crate::{ use super::helper::clean_signs; -#[derive(Default)] +// #[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 { @@ -24,12 +37,28 @@ impl PermRowColCliffordSynthesizer { 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 @@ -53,8 +82,9 @@ where let mut remaining_rows = (0..num_qubits).collect::>(); while !remaining_columns.is_empty() { - let pivot_row = pick_row(&clifford_tableau, &self.connectivity, &remaining_rows); - let pivot_column = pick_column(&clifford_tableau, &self.connectivity); + 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(); From 28ba059b45800e6c98f1a1b335348bfcd0eaab32 Mon Sep 17 00:00:00 2001 From: Aerylia Date: Wed, 15 Oct 2025 22:36:31 +0300 Subject: [PATCH 26/29] test refactor --- synir/tests/pp_synthesis/mod.rs | 2 + synir/tests/pp_synthesis/naive.rs | 76 ++++++++ synir/tests/pp_synthesis/psgs.rs | 75 ++++++++ synir/tests/pp_synthesis_tests.rs | 2 + tests/clifford_tableau.rs | 292 ------------------------------ 5 files changed, 155 insertions(+), 292 deletions(-) create mode 100644 synir/tests/pp_synthesis/mod.rs create mode 100644 synir/tests/pp_synthesis/naive.rs create mode 100644 synir/tests/pp_synthesis/psgs.rs create mode 100644 synir/tests/pp_synthesis_tests.rs delete mode 100644 tests/clifford_tableau.rs diff --git a/synir/tests/pp_synthesis/mod.rs b/synir/tests/pp_synthesis/mod.rs new file mode 100644 index 00000000..3c820ad0 --- /dev/null +++ b/synir/tests/pp_synthesis/mod.rs @@ -0,0 +1,2 @@ +pub mod naive; +pub mod psgs; \ No newline at end of file diff --git a/synir/tests/pp_synthesis/naive.rs b/synir/tests/pp_synthesis/naive.rs new file mode 100644 index 00000000..7fb02112 --- /dev/null +++ b/synir/tests/pp_synthesis/naive.rs @@ -0,0 +1,76 @@ +use std::collections::VecDeque; + +use crate::common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; +use crate::common::sample_pauli_poly::{setup_simple_pp, setup_complex_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(); + synthesizer.set_clifford_tableau(CliffordTableau::new(4)); + 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(); + synthesizer.set_clifford_tableau(CliffordTableau::new(4)); + 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)); +} \ No newline at end of file diff --git a/synir/tests/pp_synthesis/psgs.rs b/synir/tests/pp_synthesis/psgs.rs new file mode 100644 index 00000000..803d7fb3 --- /dev/null +++ b/synir/tests/pp_synthesis/psgs.rs @@ -0,0 +1,75 @@ +use std::collections::VecDeque; + +use crate::common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; +use crate::common::sample_pauli_poly::{setup_simple_pp, setup_complex_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(); + synthesizer.set_clifford_tableau(CliffordTableau::new(4)); + 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_clifford_tableau(CliffordTableau::new(4)); + 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_clifford_tableau(CliffordTableau::new(4)); + 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..311aa113 --- /dev/null +++ b/synir/tests/pp_synthesis_tests.rs @@ -0,0 +1,2 @@ +mod pp_synthesis; +mod common; \ No newline at end of file diff --git a/tests/clifford_tableau.rs b/tests/clifford_tableau.rs deleted file mode 100644 index 526af119..00000000 --- a/tests/clifford_tableau.rs +++ /dev/null @@ -1,292 +0,0 @@ -mod common; - -use bitvec::bitvec; -use bitvec::prelude::Lsb0; -use common::{parse_clifford_commands, MockCircuit, MockCommand}; -use syn::architecture::connectivity::Connectivity; -use syn::data_structures::{CliffordTableau, PauliString, PropagateClifford}; -use syn::ir::clifford_tableau::{ - CallbackCliffordSynthesizer, NaiveCliffordSynthesizer, PermRowColCliffordSynthesizer, -}; -use syn::ir::{AdjointSynthesizer, Synthesizer}; - -fn setup_sample_ct() -> CliffordTableau { - // 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"); - - let signs = bitvec![0, 1, 0, 1, 0, 0]; - CliffordTableau::from_parts(vec![pauli_1, pauli_2, pauli_3], signs) -} - -fn setup_sample_inverse_ct() -> CliffordTableau { - // Stab: -ZIYZ, -ZZYZ, -XZXI, IZXX - // Destab: -YYIZ, -YYXZ, ZIXX, -XZXZ - // qubit 1x: ZZXI - // qubit 1z: YYZX - let pauli_1 = PauliString::from_text("ZZXIYYZX"); - - // qubit 2x: IZZZ - // qubit 2z: YYIZ - let pauli_2 = PauliString::from_text("IZZZYYIZ"); - - // qubit 3x: YYXX - // qubit 3z: IXXX - let pauli_3 = PauliString::from_text("YYXXIXXX"); - - // qubit 3x: ZZIX - // qubit 3z: ZZXZ - let pauli_4 = PauliString::from_text("ZZIXZZXZ"); - - let signs = bitvec![1, 1, 1, 0, 1, 1, 0, 1]; - CliffordTableau::from_parts(vec![pauli_1, pauli_2, pauli_3, pauli_4], signs) -} - -fn setup_2_qubit_clifford() -> CliffordTableau { - // qubit 1x: ZZXI - // qubit 1z: YYZX - let pauli_1 = PauliString::from_text("XIZI"); - - // qubit 2x: IZZZ - // qubit 2z: YYIZ - let pauli_2 = PauliString::from_text("IXIZ"); - - let signs = bitvec![0, 0, 0, 0]; - CliffordTableau::from_parts(vec![pauli_1, pauli_2], signs) -} - -#[test] -fn test_id_synthesis() { - let clifford_tableau = setup_2_qubit_clifford(); - let mut mock = MockCircuit::new(); - - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau, &mut mock); - assert_eq!(mock.commands(), &vec![]); -} - -#[test] -fn test_s_synthesis() { - let mut clifford_tableau = setup_2_qubit_clifford(); - clifford_tableau.s(1); - let mut mock = MockCircuit::new(); - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - assert_eq!(mock.commands(), &vec![MockCommand::S(1)]); -} - -#[test] -fn test_s_adjoint_synthesis() { - let mut clifford_tableau = setup_2_qubit_clifford(); - clifford_tableau.s(1); - let mut mock = MockCircuit::new(); - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize_adjoint(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(2, mock.commands()); - assert_eq!(clifford_tableau * ref_ct, CliffordTableau::new(2)); - - assert_eq!(mock.commands(), &vec![MockCommand::S(1), MockCommand::Z(1)]); -} - -#[test] -fn test_v_synthesis() { - let mut clifford_tableau = setup_2_qubit_clifford(); - clifford_tableau.v(1); - let mut mock = MockCircuit::new(); - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - assert_eq!( - mock.commands(), - &vec![ - MockCommand::V(1), - ] - ); -} - -#[test] -fn test_v_adjoint_synthesis() { - let mut clifford_tableau = setup_2_qubit_clifford(); - clifford_tableau.v(1); - let mut mock = MockCircuit::new(); - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize_adjoint(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(2, mock.commands()); - assert_eq!(clifford_tableau * ref_ct, CliffordTableau::new(2)); - - assert_eq!( - mock.commands(), - &vec![MockCommand::V(1), MockCommand::X(1)] - ); -} - -#[test] -fn test_cnot_synthesis() { - let mut clifford_tableau = setup_2_qubit_clifford(); - clifford_tableau.cx(0, 1); - let mut mock = MockCircuit::new(); - - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - assert_eq!(mock.commands(), &vec![MockCommand::CX(0, 1)]); -} - -#[test] -fn test_cnot_reverse_synthesis() { - let mut clifford_tableau = setup_2_qubit_clifford(); - clifford_tableau.cx(1, 0); - let mut mock = MockCircuit::new(); - - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - assert_eq!(mock.commands(), &vec![MockCommand::CX(1, 0)]); -} - -#[test] -fn test_clifford_synthesis() { - let clifford_tableau = setup_sample_ct(); - let mut mock = MockCircuit::new(); - - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(3, mock.commands()); - - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_clifford_synthesis_large() { - let clifford_tableau = setup_sample_inverse_ct(); - let mut mock = MockCircuit::new(); - - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(4, mock.commands()); - - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_clifford_synthesis_simple() { - let mut clifford_tableau = CliffordTableau::new(3); - clifford_tableau.cx(0, 1); - clifford_tableau.cx(1, 2); - let mut mock = MockCircuit::new(); - - let mut synthesizer = NaiveCliffordSynthesizer::default(); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(3, mock.commands()); - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_custom_clifford_synthesis() { - let clifford_tableau = setup_sample_ct(); - let mut mock = MockCircuit::new(); - - let mut synthesizer = CallbackCliffordSynthesizer::custom_pivot(vec![0, 1, 2], vec![0, 1, 2]); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(3, mock.commands()); - - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_custom_clifford_synthesis_large() { - let clifford_tableau = setup_sample_inverse_ct(); - let mut mock = MockCircuit::new(); - - let mut synthesizer = - CallbackCliffordSynthesizer::custom_pivot(vec![0, 1, 2, 3], vec![0, 2, 1, 3]); - - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let mut ref_ct = parse_clifford_commands(4, mock.commands()); - ref_ct.permute(&[0, 2, 1, 3]); - - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_custom_clifford_synthesis_simple() { - let mut clifford_tableau = CliffordTableau::new(3); - clifford_tableau.cx(0, 1); - clifford_tableau.cx(1, 2); - let mut mock = MockCircuit::new(); - - let mut synthesizer = CallbackCliffordSynthesizer::custom_pivot(vec![0, 1, 2], vec![0, 1, 2]); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(3, mock.commands()); - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_prc_clifford_synthesis() { - let clifford_tableau = setup_sample_ct(); - let num_qubits = clifford_tableau.size(); - let mut mock = MockCircuit::new(); - let connectivity = Connectivity::complete(num_qubits); - let mut synthesizer = PermRowColCliffordSynthesizer::new(connectivity); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let mut ref_ct = parse_clifford_commands(3, mock.commands()); - ref_ct.permute(synthesizer.permutation()); - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_prc_clifford_synthesis_large() { - let mut clifford_tableau = setup_sample_inverse_ct(); - let mut mock = MockCircuit::new(); - - let connectivity = Connectivity::grid(2, 2); - let mut synthesizer = PermRowColCliffordSynthesizer::new(connectivity); - - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(4, mock.commands()); - clifford_tableau.permute(synthesizer.permutation()); - - assert_eq!(clifford_tableau, ref_ct); -} - -#[test] -fn test_prc_clifford_synthesis_simple() { - let num_qubits = 3; - let mut clifford_tableau = CliffordTableau::new(num_qubits); - - clifford_tableau.cx(2, 1); - clifford_tableau.cx(1, 2); - clifford_tableau.cx(0, 2); - let mut mock = MockCircuit::new(); - - let connectivity = Connectivity::line(num_qubits); - - let mut synthesizer = PermRowColCliffordSynthesizer::new(connectivity); - synthesizer.synthesize(clifford_tableau.clone(), &mut mock); - - let ref_ct = parse_clifford_commands(3, mock.commands()); - - clifford_tableau.permute(synthesizer.permutation()); - assert_eq!(clifford_tableau, ref_ct); -} From b9698ac945acec46ca0767e81af17746f7814ed9 Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Wed, 29 Oct 2025 16:41:11 +0100 Subject: [PATCH 27/29] Rebase on main --- synir/src/data_structures/pauli_polynomial.rs | 16 +++-------- synir/src/data_structures/pauli_string.rs | 27 ++++--------------- synir/src/ir.rs | 1 - synir/src/ir/clifford_tableau/naive.rs | 3 +-- synir/src/ir/pauli_polynomial/helper.rs | 1 - synir/tests/common/mod.rs | 1 + synir/tests/common/sample_pauli_poly.rs | 20 ++++++++++++++ synir/tests/pauli_polynomial.rs | 2 ++ synir/tests/pp_synthesis/mod.rs | 2 +- synir/tests/pp_synthesis/naive.rs | 8 +++--- synir/tests/pp_synthesis/psgs.rs | 6 ++--- synir/tests/pp_synthesis_tests.rs | 2 +- 12 files changed, 42 insertions(+), 47 deletions(-) create mode 100644 synir/tests/common/sample_pauli_poly.rs diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index 0bc8e955..e3b43c1d 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -9,8 +9,8 @@ use super::{pauli_string::PauliString, IndexType, MaskedPropagateClifford, Propa #[derive(Debug, Clone, Default)] pub struct PauliPolynomial { - chains: Vec, - angles: Vec, + pub(crate) chains: Vec, + pub(crate) angles: Vec, size: usize, } @@ -42,11 +42,7 @@ impl PauliPolynomial { } } - pub fn from_components( - chains: Vec, - angles: RwLock>, - size: usize, - ) -> Self { + pub fn from_components(chains: Vec, angles: Vec, size: usize) -> Self { Self { chains, angles, @@ -70,7 +66,7 @@ impl PauliPolynomial { &self.chains } - pub fn angles(&self) -> &RwLock> { + pub fn angles(&self) -> &Vec { &self.angles } @@ -78,10 +74,6 @@ impl PauliPolynomial { self.angles[i] } - pub fn chain(&self, index: usize) -> &PauliString { - &self.chains[index] - } - pub fn mut_chains(&mut self) -> &mut Vec { &mut self.chains } diff --git a/synir/src/data_structures/pauli_string.rs b/synir/src/data_structures/pauli_string.rs index 7e5cac6f..0ba6af1c 100644 --- a/synir/src/data_structures/pauli_string.rs +++ b/synir/src/data_structures/pauli_string.rs @@ -61,16 +61,9 @@ impl PauliString { self.z.count_ones() } - pub fn x_weight(&self) -> usize { - self.x.read().unwrap().count_ones() - } - - pub fn z_weight(&self) -> usize { - self.z.read().unwrap().count_ones() - } pub fn combine(&self) -> BitVec { - let mut new_string = self.z.read().unwrap().to_bitvec(); - new_string |= self.x.read().unwrap().as_bitslice(); + let mut new_string = self.z.to_bitvec(); + new_string |= self.x.as_bitslice(); new_string } pub fn weight(&self) -> usize { @@ -78,14 +71,6 @@ impl PauliString { new_string.count_ones() } - pub fn x_weight(&self) -> usize { - self.x.read().unwrap().count_ones() - } - - pub fn z_weight(&self) -> usize { - self.z.read().unwrap().count_ones() - } - pub fn z(&self, i: usize) -> bool { self.z[i] } @@ -96,10 +81,8 @@ impl PauliString { pub fn iter(&self) -> Vec { self.x - .read() - .unwrap() .iter() - .zip(self.z.read().unwrap().iter()) + .zip(self.z.iter()) .map(|(x, z)| PauliLetter::new(*x, *z)) .collect::>() } @@ -160,8 +143,8 @@ impl PauliString { } pub(crate) fn swap_remove(&mut self, index: usize) -> PauliLetter { - let x = self.x.write().unwrap().swap_remove(index); - let z = self.z.write().unwrap().swap_remove(index); + let x = self.x.swap_remove(index); + let z = self.z.swap_remove(index); PauliLetter::new(x, z) } } diff --git a/synir/src/ir.rs b/synir/src/ir.rs index df1dc311..e04f4037 100644 --- a/synir/src/ir.rs +++ b/synir/src/ir.rs @@ -1,7 +1,6 @@ use crate::{data_structures::HasAdjoint, IndexType}; pub mod clifford_tableau; -pub(crate) mod helper; pub mod pauli_exponential; pub mod pauli_polynomial; diff --git a/synir/src/ir/clifford_tableau/naive.rs b/synir/src/ir/clifford_tableau/naive.rs index 3b1e862b..d31bf037 100644 --- a/synir/src/ir/clifford_tableau/naive.rs +++ b/synir/src/ir/clifford_tableau/naive.rs @@ -4,8 +4,7 @@ use crate::{ }; use super::helper::{ - clean_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, - swap, + clean_pivot, clean_signs, clean_x_observables, clean_z_observables, naive_pivot_search, swap, }; use crate::data_structures::PauliLetter; diff --git a/synir/src/ir/pauli_polynomial/helper.rs b/synir/src/ir/pauli_polynomial/helper.rs index 29e411cf..a5024a49 100644 --- a/synir/src/ir/pauli_polynomial/helper.rs +++ b/synir/src/ir/pauli_polynomial/helper.rs @@ -165,7 +165,6 @@ pub(super) fn check_columns( angles, .. } = pauli_polynomial; - let mut angles = angles.write().unwrap(); for index in (0..length).rev() { if !invalid[index] && polynomial_mask[index] { polynomial_mask.swap_remove(index); 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 5c9cc0fb..f95f76f5 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::architecture::connectivity::Connectivity; use synir::data_structures::{CliffordTableau, PauliPolynomial}; +use synir::ir::pauli_polynomial::psgs::PSGSPauliPolynomialSynthesizer; use synir::ir::pauli_polynomial::NaivePauliPolynomialSynthesizer; use synir::ir::Synthesizer; diff --git a/synir/tests/pp_synthesis/mod.rs b/synir/tests/pp_synthesis/mod.rs index 3c820ad0..09c9873c 100644 --- a/synir/tests/pp_synthesis/mod.rs +++ b/synir/tests/pp_synthesis/mod.rs @@ -1,2 +1,2 @@ pub mod naive; -pub mod psgs; \ No newline at end of file +pub mod psgs; diff --git a/synir/tests/pp_synthesis/naive.rs b/synir/tests/pp_synthesis/naive.rs index 7fb02112..60e3b4a8 100644 --- a/synir/tests/pp_synthesis/naive.rs +++ b/synir/tests/pp_synthesis/naive.rs @@ -1,17 +1,17 @@ use std::collections::VecDeque; use crate::common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; -use crate::common::sample_pauli_poly::{setup_simple_pp, setup_complex_pp}; +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){ +fn run_synthesizer(pp: VecDeque) -> (MockCircuit, CliffordTableau) { let mut mock: MockCircuit = MockCircuit::new(); let mut synthesizer = NaivePauliPolynomialSynthesizer::default(); synthesizer.set_clifford_tableau(CliffordTableau::new(4)); let ct = synthesizer.synthesize(pp, &mut mock); - return (mock, ct) + return (mock, ct); } #[test] @@ -73,4 +73,4 @@ fn test_naive_pauli_exponential_synthesis_complex() { assert_eq!(mock.commands(), &ref_commands); assert_eq!(ct, parse_clifford_commands(4, &ref_clifford_commands)); -} \ No newline at end of file +} diff --git a/synir/tests/pp_synthesis/psgs.rs b/synir/tests/pp_synthesis/psgs.rs index 803d7fb3..6188c895 100644 --- a/synir/tests/pp_synthesis/psgs.rs +++ b/synir/tests/pp_synthesis/psgs.rs @@ -1,18 +1,18 @@ use std::collections::VecDeque; use crate::common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; -use crate::common::sample_pauli_poly::{setup_simple_pp, setup_complex_pp}; +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){ +fn run_synthesizer(pp: VecDeque) -> (MockCircuit, CliffordTableau) { let mut mock: MockCircuit = MockCircuit::new(); let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); synthesizer.set_clifford_tableau(CliffordTableau::new(4)); let ct = synthesizer.synthesize(pp, &mut mock); - return (mock, ct) + return (mock, ct); } #[test] diff --git a/synir/tests/pp_synthesis_tests.rs b/synir/tests/pp_synthesis_tests.rs index 311aa113..cbea208d 100644 --- a/synir/tests/pp_synthesis_tests.rs +++ b/synir/tests/pp_synthesis_tests.rs @@ -1,2 +1,2 @@ +mod common; mod pp_synthesis; -mod common; \ No newline at end of file From bd26205fe33f21b4a30d48b168b60fa299562e3e Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 4 Nov 2025 14:53:05 +0100 Subject: [PATCH 28/29] Implement tests from Cowtan, fix sign flip error --- synir/src/data_structures/pauli_polynomial.rs | 160 ++++++++---------- synir/tests/pauli_polynomial.rs | 2 +- synir/tests/pp_synthesis/psgs.rs | 2 +- 3 files changed, 75 insertions(+), 89 deletions(-) diff --git a/synir/src/data_structures/pauli_polynomial.rs b/synir/src/data_structures/pauli_polynomial.rs index e3b43c1d..a2111968 100644 --- a/synir/src/data_structures/pauli_polynomial.rs +++ b/synir/src/data_structures/pauli_polynomial.rs @@ -103,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()) { @@ -123,6 +124,7 @@ impl PropagateClifford for PauliPolynomial { *angle *= -1.0; } } + chains_target.v(); self } } @@ -220,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, }; @@ -261,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, }; @@ -311,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, }; @@ -336,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/tests/pauli_polynomial.rs b/synir/tests/pauli_polynomial.rs index f95f76f5..aae9f736 100644 --- a/synir/tests/pauli_polynomial.rs +++ b/synir/tests/pauli_polynomial.rs @@ -135,7 +135,7 @@ fn test_psgs_pauli_exponential_synthesis_complex() { MockCommand::H(1), MockCommand::S(0), MockCommand::CX(1, 0), - MockCommand::Ry(0, 0.7), + MockCommand::Ry(0, -0.7), ]; let ref_clifford_commands = [ diff --git a/synir/tests/pp_synthesis/psgs.rs b/synir/tests/pp_synthesis/psgs.rs index 6188c895..5972305a 100644 --- a/synir/tests/pp_synthesis/psgs.rs +++ b/synir/tests/pp_synthesis/psgs.rs @@ -59,7 +59,7 @@ fn test_psgs_pauli_exponential_synthesis_complex() { MockCommand::H(1), MockCommand::S(0), MockCommand::CX(1, 0), - MockCommand::Ry(0, 0.7), + MockCommand::Ry(0, -0.7), ]; let ref_clifford_commands = [ From 647fd96e84e4ba2f10d0a8822049c6a75506e3ee Mon Sep 17 00:00:00 2001 From: Keefe Huang Date: Tue, 4 Nov 2025 14:58:27 +0100 Subject: [PATCH 29/29] Remove CliffordTableau from PauliPolynomialSynthesizers Each synthesizer should produce a new CliffordTableau as Polynomials should not see them. The Tableaus can be arbitrarily composed. --- synir/src/ir/pauli_exponential.rs | 2 -- synir/src/ir/pauli_polynomial/naive.rs | 13 +++---------- synir/src/ir/pauli_polynomial/psgs.rs | 8 +------- synir/tests/pauli_polynomial.rs | 6 +----- synir/tests/pp_synthesis/naive.rs | 2 -- synir/tests/pp_synthesis/psgs.rs | 3 --- 6 files changed, 5 insertions(+), 29 deletions(-) diff --git a/synir/src/ir/pauli_exponential.rs b/synir/src/ir/pauli_exponential.rs index aa7e300f..7dd0321f 100644 --- a/synir/src/ir/pauli_exponential.rs +++ b/synir/src/ir/pauli_exponential.rs @@ -78,12 +78,10 @@ where 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_clifford_tableau(clifford_tableau); pauli_synthesizer.set_connectivity(Connectivity::complete(num_qubits)); pauli_synthesizer.synthesize(pauli_polynomials, repr) } 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 index 7426b9cb..19e05067 100644 --- a/synir/src/ir/pauli_polynomial/psgs.rs +++ b/synir/src/ir/pauli_polynomial/psgs.rs @@ -12,16 +12,10 @@ use bitvec::{bitvec, order::Lsb0}; #[derive(Default)] pub struct PSGSPauliPolynomialSynthesizer { - clifford_tableau: CliffordTableau, connectivity: Connectivity, } impl PSGSPauliPolynomialSynthesizer { - pub fn set_clifford_tableau(&mut self, clifford_tableau: CliffordTableau) -> &mut Self { - self.clifford_tableau = clifford_tableau; - self - } - pub fn set_connectivity(&mut self, connectivity: Connectivity) -> &mut Self { self.connectivity = connectivity; self @@ -38,7 +32,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 mut pauli_polynomial = pauli_polynomials.pop_front().unwrap(); let num_gadgets: usize = pauli_polynomial.length(); diff --git a/synir/tests/pauli_polynomial.rs b/synir/tests/pauli_polynomial.rs index aae9f736..45fef364 100644 --- a/synir/tests/pauli_polynomial.rs +++ b/synir/tests/pauli_polynomial.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use common::mock_circuit::{parse_clifford_commands, MockCircuit, MockCommand}; use synir::architecture::connectivity::Connectivity; -use synir::data_structures::{CliffordTableau, PauliPolynomial}; +use synir::data_structures::PauliPolynomial; use synir::ir::pauli_polynomial::psgs::PSGSPauliPolynomialSynthesizer; use synir::ir::pauli_polynomial::NaivePauliPolynomialSynthesizer; use synir::ir::Synthesizer; @@ -31,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 = [ @@ -58,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 = [ @@ -96,7 +94,6 @@ fn test_psgs_pauli_exponential_synthesis_simple() { let pp = setup_simple_pp(); let mut mock = MockCircuit::new(); let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); synthesizer.set_connectivity(Connectivity::complete(4)); let ct = synthesizer.synthesize(pp, &mut mock); @@ -124,7 +121,6 @@ fn test_psgs_pauli_exponential_synthesis_complex() { let pp = setup_complex_pp(); let mut mock = MockCircuit::new(); let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); synthesizer.set_connectivity(Connectivity::complete(4)); let ct = synthesizer.synthesize(pp, &mut mock); diff --git a/synir/tests/pp_synthesis/naive.rs b/synir/tests/pp_synthesis/naive.rs index 60e3b4a8..cc35c1eb 100644 --- a/synir/tests/pp_synthesis/naive.rs +++ b/synir/tests/pp_synthesis/naive.rs @@ -9,7 +9,6 @@ use synir::ir::Synthesizer; fn run_synthesizer(pp: VecDeque) -> (MockCircuit, CliffordTableau) { let mut mock: MockCircuit = MockCircuit::new(); let mut synthesizer = NaivePauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); let ct = synthesizer.synthesize(pp, &mut mock); return (mock, ct); } @@ -42,7 +41,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 = [ diff --git a/synir/tests/pp_synthesis/psgs.rs b/synir/tests/pp_synthesis/psgs.rs index 5972305a..8bc6a562 100644 --- a/synir/tests/pp_synthesis/psgs.rs +++ b/synir/tests/pp_synthesis/psgs.rs @@ -10,7 +10,6 @@ use synir::ir::Synthesizer; fn run_synthesizer(pp: VecDeque) -> (MockCircuit, CliffordTableau) { let mut mock: MockCircuit = MockCircuit::new(); let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); let ct = synthesizer.synthesize(pp, &mut mock); return (mock, ct); } @@ -20,7 +19,6 @@ fn test_psgs_pauli_exponential_synthesis_simple() { let pp = setup_simple_pp(); let mut mock = MockCircuit::new(); let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); synthesizer.set_connectivity(Connectivity::complete(4)); let ct = synthesizer.synthesize(pp, &mut mock); @@ -48,7 +46,6 @@ fn test_psgs_pauli_exponential_synthesis_complex() { let pp = setup_complex_pp(); let mut mock = MockCircuit::new(); let mut synthesizer = PSGSPauliPolynomialSynthesizer::default(); - synthesizer.set_clifford_tableau(CliffordTableau::new(4)); synthesizer.set_connectivity(Connectivity::complete(4)); let ct = synthesizer.synthesize(pp, &mut mock);