@@ -54,8 +54,7 @@ pub struct Tableau<T: Config> {
5454}
5555
5656impl < T : Config > Tableau < T > {
57- /// Construct a fresh tableau initialised to `|0…0⟩`.
58- pub fn new ( n_qubits : usize ) -> Self {
57+ fn new_data ( n_qubits : usize ) -> Vec < PhasedPauliWordNoHash < T :: Storage , T :: BuildHasher > > {
5958 // Initialize tableau for 0 state
6059 let mut data: Vec < PhasedPauliWordNoHash < T :: Storage , T :: BuildHasher > > =
6160 Vec :: with_capacity ( 2 * n_qubits) ;
@@ -72,7 +71,12 @@ impl<T: Config> Tableau<T> {
7271 pw. set ( i, Pauli :: Z ) ;
7372 data. push ( pw) ;
7473 }
74+ data
75+ }
7576
77+ /// Construct a fresh tableau initialised to `|0…0⟩`.
78+ pub fn new ( n_qubits : usize ) -> Self {
79+ let data = Tableau :: < T > :: new_data ( n_qubits) ;
7680 Self {
7781 n_qubits,
7882 data,
@@ -87,6 +91,11 @@ impl<T: Config> Tableau<T> {
8791 t
8892 }
8993
94+ pub fn reset_all ( & mut self ) {
95+ let data = Tableau :: < T > :: new_data ( self . n_qubits ) ;
96+ self . data = data;
97+ }
98+
9099 /// View of the stabilizer rows (the upper half of the tableau).
91100 #[ inline]
92101 pub fn stabilizers ( & self ) -> & [ PhasedPauliWordNoHash < T :: Storage , T :: BuildHasher > ] {
@@ -537,7 +546,9 @@ where
537546 Complex < CoeffType > :
538547 std:: ops:: Mul < Output = Complex < CoeffType > > + std:: ops:: AddAssign + From < Complex64 > + Copy ,
539548{
540- if items. len ( ) >= RAYON_COEFF_THRESHOLD {
549+ // See `branch_coefficients_parallel`: avoid nesting rayon inside shot-level
550+ // parallelism; the main-thread (single-shot) path is unaffected.
551+ if items. len ( ) >= RAYON_COEFF_THRESHOLD && rayon:: current_thread_index ( ) . is_none ( ) {
541552 use rayon:: prelude:: * ;
542553
543554 return items
@@ -675,6 +686,22 @@ where
675686 s
676687 }
677688
689+ pub fn reset_all ( & mut self ) {
690+ self . tableau . reset_all ( ) ;
691+
692+ let mut coefficients = C :: new ( ) ;
693+ let complex_one = Complex {
694+ re : T :: Coeff :: one ( ) ,
695+ im : T :: Coeff :: zero ( ) ,
696+ } ;
697+ coefficients. unsafe_insert ( I :: zero ( ) , complex_one) ;
698+ self . coefficients = coefficients;
699+ for l in self . is_lost . iter_mut ( ) {
700+ * l &= false ;
701+ }
702+ self . measurement_record . clear ( ) ;
703+ }
704+
678705 /// Clone the quantum state but reinitialize the RNG, producing an independent simulation
679706 /// branch. If `seed` is `Some`, the new RNG is seeded deterministically; if `None`, it is
680707 /// seeded from OS entropy.
@@ -848,6 +875,36 @@ where
848875 ( p_word. phase , stab_anticomm_bits, destab_anticomm_bits)
849876 }
850877
878+ /// Multi-qubit generalization of [`compute_decomposition`]: conjugate an
879+ /// arbitrary `PauliWord` through the tableau and return the same triple
880+ /// `(phase, stab_anticomm_bits, destab_anticomm_bits)`.
881+ ///
882+ /// Algorithm: call [`compute_decomposition`] for each non-identity qubit
883+ /// in the input, then multiply the resulting single-qubit conjugates in
884+ /// canonical-basis form `i^φ X^x Z^z`. Pauli multiplication picks up a
885+ /// `(-1)^{popcount(z_running & x_new)}` cross-phase from
886+ /// `Z^z_a X^x_b = (-1)^{z_a · x_b} X^x_b Z^z_a`.
887+ pub ( crate ) fn compute_decomposition_word < W : PauliWordTrait > ( & self , word : & W ) -> ( u8 , I , I )
888+ where
889+ <<T as Config >:: Storage as BitView >:: Store : PrimInt ,
890+ {
891+ let mut phase = 0u8 ;
892+ let mut stab_anticomm = I :: zero ( ) ;
893+ let mut destab_anticomm = I :: zero ( ) ;
894+ for q in 0 ..self . n_qubits ( ) {
895+ let p_q = word. get ( q) ;
896+ if p_q == Pauli :: I {
897+ continue ;
898+ }
899+ let ( q_phase, q_stab, q_destab) = self . compute_decomposition ( q, p_q) ;
900+ let cross = 2 * ( symplectic_inner ( destab_anticomm, q_stab) as u8 % 2 ) ;
901+ phase = ( phase + q_phase + cross) % 4 ;
902+ stab_anticomm = stab_anticomm ^ q_stab;
903+ destab_anticomm = destab_anticomm ^ q_destab;
904+ }
905+ ( phase, stab_anticomm, destab_anticomm)
906+ }
907+
851908 /// every basis index is a bit string alpha defining the basis state
852909 /// the phase when applying a Pauli is the product of all destabilizer phases
853910 /// and the phase contributions from the commutation relations
@@ -1415,4 +1472,75 @@ mod tests {
14151472 snapshot_tableau( & tab2. tableau)
14161473 ) ;
14171474 }
1475+
1476+ // ─── reset_all ────────────────────────────────────────────────────
1477+
1478+ /// `GeneralizedTableau::reset_all` restores the full state to a fresh
1479+ /// `|0…0⟩` tableau: identical stabilizer/destabilizer rows and a single
1480+ /// identity coefficient, even after non-Clifford branching.
1481+ #[ test]
1482+ fn reset_all_restores_fresh_state ( ) {
1483+ let mut tab: TestTableau = GeneralizedTableau :: new ( 3 , 1e-12 ) ;
1484+ let fresh: TestTableau = GeneralizedTableau :: new ( 3 , 1e-12 ) ;
1485+
1486+ tab. h ( 0 ) ;
1487+ tab. cnot ( 0 , 1 ) ;
1488+ tab. ry ( 2 , 0.7 ) ; // non-Clifford: branches the coefficient vector
1489+ assert ! (
1490+ tab. coefficients. iter( ) . count( ) > 1 ,
1491+ "rotation should branch the coefficient vector"
1492+ ) ;
1493+
1494+ tab. reset_all ( ) ;
1495+
1496+ assert_eq ! (
1497+ snapshot_tableau( & tab. tableau) ,
1498+ snapshot_tableau( & fresh. tableau)
1499+ ) ;
1500+ let coeffs: Vec < _ > = tab. coefficients . iter ( ) . copied ( ) . collect ( ) ;
1501+ let fresh_coeffs: Vec < _ > = fresh. coefficients . iter ( ) . copied ( ) . collect ( ) ;
1502+ assert_eq ! ( coeffs, fresh_coeffs) ;
1503+ }
1504+
1505+ /// A full reset clears the measurement record. Regression guard: an earlier
1506+ /// version left it intact, so `current_measurement_record` returned stale
1507+ /// outcomes after a reset.
1508+ #[ test]
1509+ fn reset_all_clears_measurement_record ( ) {
1510+ let mut tab: TestTableau = GeneralizedTableau :: new ( 2 , 1e-12 ) ;
1511+ tab. append_measurement_record ( Some ( true ) ) ;
1512+ tab. append_measurement_record ( None ) ;
1513+ assert_eq ! ( tab. current_measurement_record( ) . len( ) , 2 ) ;
1514+
1515+ tab. reset_all ( ) ;
1516+
1517+ assert ! ( tab. current_measurement_record( ) . is_empty( ) ) ;
1518+ }
1519+
1520+ /// A full reset clears per-qubit loss flags.
1521+ #[ test]
1522+ fn reset_all_clears_loss_flags ( ) {
1523+ let mut tab: TestTableau = GeneralizedTableau :: new ( 3 , 1e-12 ) ;
1524+ tab. is_lost [ 0 ] = true ;
1525+ tab. is_lost [ 2 ] = true ;
1526+
1527+ tab. reset_all ( ) ;
1528+
1529+ assert ! ( tab. is_lost. iter( ) . all( |& lost| !lost) ) ;
1530+ }
1531+
1532+ /// `Tableau::reset_all` restores the fresh identity tableau rows.
1533+ #[ test]
1534+ fn tableau_reset_all_restores_fresh_rows ( ) {
1535+ let mut tab: Tableau < TestConfig > = Tableau :: new ( 4 ) ;
1536+ let fresh: Tableau < TestConfig > = Tableau :: new ( 4 ) ;
1537+
1538+ tab. h ( 0 ) ;
1539+ tab. s ( 1 ) ;
1540+ tab. h ( 3 ) ;
1541+
1542+ tab. reset_all ( ) ;
1543+
1544+ assert_eq ! ( snapshot_tableau( & tab) , snapshot_tableau( & fresh) ) ;
1545+ }
14181546}
0 commit comments