@@ -40,7 +40,7 @@ pub type StdPauliProp = PauliProp<VecSet<usize>, usize>;
4040/// - `zs`: Records qubits with Z Pauli operators
4141///
4242/// Y operators are implicitly represented by qubits present in both sets since Y = iXZ.
43- ///
43+ ///
4444/// Optionally, the sign and phase can be tracked for full Pauli string representation.
4545///
4646/// # Type Parameters
@@ -127,8 +127,8 @@ where
127127 PauliProp {
128128 xs : T :: new ( ) ,
129129 zs : T :: new ( ) ,
130- sign : Some ( false ) , // Start with +1
131- img : Some ( 0 ) , // Start with no imaginary component
130+ sign : Some ( false ) , // Start with +1
131+ img : Some ( 0 ) , // Start with no imaginary component
132132 num_qubits : Some ( num_qubits) ,
133133 _marker : PhantomData ,
134134 }
@@ -263,33 +263,37 @@ where
263263 /// * `num_is` - Number of i factors to add
264264 pub fn flip_img ( & mut self , num_is : usize ) {
265265 if let Some ( img) = self . img . as_mut ( ) {
266- * img = ( * img + num_is as u8 ) % 4 ;
267-
266+ // Use modulo 4 on num_is first to ensure it fits in u8
267+ // Safe to cast since modulo 4 guarantees result is 0-3
268+ #[ allow( clippy:: cast_possible_truncation) ]
269+ let num_is_mod = ( num_is % 4 ) as u8 ;
270+ * img = ( * img + num_is_mod) % 4 ;
271+
268272 // If we've accumulated 2 or 3 i's, flip the sign
269273 let should_flip = * img == 2 || * img == 3 ;
270-
271- * img %= 2 ; // Keep only 0 or 1 for the imaginary part
272-
274+
275+ * img %= 2 ; // Keep only 0 or 1 for the imaginary part
276+
273277 if should_flip {
274278 self . flip_sign ( ) ;
275279 }
276280 }
277281 }
278282
279- /// Adds Pauli operators from a BTreeMap representation.
283+ /// Adds Pauli operators from a ` BTreeMap` representation.
280284 ///
281285 /// The map should have keys "X", "Y", and "Z" with sets of qubit indices.
282286 /// This method properly handles operator composition with phase tracking if enabled.
283287 ///
284288 /// # Arguments
285- /// * `paulis` - BTreeMap with "X", "Y", "Z" keys mapping to sets of qubit indices
289+ /// * `paulis` - ` BTreeMap` with "X", "Y", "Z" keys mapping to sets of qubit indices
286290 ///
287291 /// # Example
288292 /// ```rust
289293 /// use std::collections::BTreeMap;
290294 /// use pecos_qsim::StdPauliProp;
291295 /// use pecos_core::{VecSet, Set};
292- ///
296+ ///
293297 /// let mut sim = StdPauliProp::with_sign_tracking(4);
294298 /// let mut paulis = BTreeMap::new();
295299 /// let mut x_set = VecSet::new();
@@ -298,7 +302,7 @@ where
298302 /// paulis.insert("X".to_string(), x_set);
299303 /// sim.add_paulis(&paulis);
300304 /// ```
301- pub fn add_paulis ( & mut self , paulis : & BTreeMap < String , T > )
305+ pub fn add_paulis ( & mut self , paulis : & BTreeMap < String , T > )
302306 where
303307 T : Clone ,
304308 E : Copy ,
@@ -308,9 +312,9 @@ where
308312 for & item in x_set. iter ( ) {
309313 let was_y = self . contains_y ( item) ;
310314 let was_z = self . contains_z ( item) && !was_y;
311-
315+
312316 self . add_x ( item) ;
313-
317+
314318 if self . sign . is_some ( ) {
315319 if was_y {
316320 // Y·X = -iZ (applying X after Y)
@@ -329,9 +333,9 @@ where
329333 for & item in z_set. iter ( ) {
330334 let was_y = self . contains_y ( item) ;
331335 let was_x = self . contains_x ( item) && !was_y;
332-
336+
333337 self . add_z ( item) ;
334-
338+
335339 if self . sign . is_some ( ) {
336340 if was_x {
337341 // X·Z = -iY (applying Z after X)
@@ -350,9 +354,9 @@ where
350354 for & item in y_set. iter ( ) {
351355 let was_x = self . contains_x ( item) && !self . contains_z ( item) ;
352356 let was_z = self . contains_z ( item) && !self . contains_x ( item) ;
353-
357+
354358 self . add_y ( item) ;
355-
359+
356360 if self . sign . is_some ( ) {
357361 if was_z {
358362 // Z·Y = -iX (applying Y after Z)
@@ -379,21 +383,21 @@ where
379383 count += 1 ;
380384 }
381385 }
382-
386+
383387 // Count Z-only qubits
384388 for item in self . zs . iter ( ) {
385389 if !self . xs . contains ( item) {
386390 count += 1 ;
387391 }
388392 }
389-
393+
390394 // Count Y qubits (both X and Z)
391395 for item in self . xs . iter ( ) {
392396 if self . zs . contains ( item) {
393397 count += 1 ;
394398 }
395399 }
396-
400+
397401 count
398402 }
399403
@@ -427,24 +431,24 @@ where
427431 /// A string like "+", "-", "+i", or "-i" depending on the phase
428432 pub fn sign_string ( & self ) -> String {
429433 match ( self . sign , self . img ) {
430- ( Some ( false ) , Some ( 0 ) ) | ( Some ( false ) , None ) => "+" . to_string ( ) ,
431- ( Some ( true ) , Some ( 0 ) ) | ( Some ( true ) , None ) => "-" . to_string ( ) ,
434+ ( Some ( false ) , Some ( 0 ) | None ) => "+" . to_string ( ) ,
435+ ( Some ( true ) , Some ( 0 ) | None ) => "-" . to_string ( ) ,
432436 ( Some ( false ) , Some ( 1 ) ) => "+i" . to_string ( ) ,
433437 ( Some ( true ) , Some ( 1 ) ) => "-i" . to_string ( ) ,
434- _ => "" . to_string ( ) ,
438+ _ => String :: new ( ) ,
435439 }
436440 }
437441
438442 /// Returns the operator string representation for sparse format.
439443 ///
440444 /// # Returns
441- /// A string like "X_0 Z_2 Y_3" representing non-identity operators
442- pub fn sparse_string ( & self ) -> String
445+ /// A string like "` X_0` ` Z_2` ` Y_3` " representing non-identity operators
446+ pub fn sparse_string ( & self ) -> String
443447 where
444448 E : Copy ,
445449 {
446450 let mut entries = Vec :: new ( ) ;
447-
451+
448452 // Collect all qubit indices with operators
449453 for & item in self . xs . iter ( ) {
450454 if self . contains_y ( item) {
@@ -453,19 +457,20 @@ where
453457 entries. push ( ( item, 'X' ) ) ;
454458 }
455459 }
456-
460+
457461 for & item in self . zs . iter ( ) {
458462 if !self . xs . contains ( & item) {
459463 entries. push ( ( item, 'Z' ) ) ;
460464 }
461465 }
462-
466+
463467 if entries. is_empty ( ) {
464468 "I" . to_string ( )
465469 } else {
466470 // Format as sparse representation
467- entries. iter ( )
468- . map ( |( idx, op) | format ! ( "{}{:?}" , op, idx) )
471+ entries
472+ . iter ( )
473+ . map ( |( idx, op) | format ! ( "{op}{idx:?}" ) )
469474 . collect :: < Vec < _ > > ( )
470475 . join ( " " )
471476 }
@@ -474,8 +479,8 @@ where
474479 /// Returns the full Pauli string representation with sign and operators.
475480 ///
476481 /// # Returns
477- /// A string like "+X_0 Z_2" in sparse format
478- pub fn to_pauli_string ( & self ) -> String
482+ /// A string like "+` X_0` ` Z_2` " in sparse format
483+ pub fn to_pauli_string ( & self ) -> String
479484 where
480485 E : Copy ,
481486 {
@@ -486,45 +491,54 @@ where
486491// Specialized implementation for StdPauliProp (usize indices)
487492impl StdPauliProp {
488493 /// Get all qubits with X operators (including those with Y)
494+ #[ must_use]
489495 pub fn get_x_qubits ( & self ) -> Vec < usize > {
490496 self . xs . iter ( ) . copied ( ) . collect ( )
491497 }
492-
498+
493499 /// Get all qubits with Z operators (including those with Y)
500+ #[ must_use]
494501 pub fn get_z_qubits ( & self ) -> Vec < usize > {
495502 self . zs . iter ( ) . copied ( ) . collect ( )
496503 }
497-
504+
498505 /// Get all qubits with only X operators (not Y)
506+ #[ must_use]
499507 pub fn get_x_only_qubits ( & self ) -> Vec < usize > {
500- self . xs . iter ( )
508+ self . xs
509+ . iter ( )
501510 . filter ( |& q| !self . contains_z ( * q) )
502511 . copied ( )
503512 . collect ( )
504513 }
505-
514+
506515 /// Get all qubits with only Z operators (not Y)
516+ #[ must_use]
507517 pub fn get_z_only_qubits ( & self ) -> Vec < usize > {
508- self . zs . iter ( )
518+ self . zs
519+ . iter ( )
509520 . filter ( |& q| !self . contains_x ( * q) )
510521 . copied ( )
511522 . collect ( )
512523 }
513-
524+
514525 /// Get all qubits with Y operators (both X and Z)
526+ #[ must_use]
515527 pub fn get_y_qubits ( & self ) -> Vec < usize > {
516- self . xs . iter ( )
528+ self . xs
529+ . iter ( )
517530 . filter ( |& q| self . contains_z ( * q) )
518531 . copied ( )
519532 . collect ( )
520533 }
521-
534+
522535 /// Returns the operator string as a dense representation.
523536 ///
524537 /// Requires `num_qubits` to be set.
525538 ///
526539 /// # Returns
527540 /// A string like "IXYZ" representing the Pauli operators on each qubit
541+ #[ must_use]
528542 pub fn dense_string ( & self ) -> String {
529543 if let Some ( n) = self . num_qubits {
530544 let mut result = String :: with_capacity ( n) ;
@@ -544,11 +558,12 @@ impl StdPauliProp {
544558 self . sparse_string ( )
545559 }
546560 }
547-
561+
548562 /// Returns the full dense Pauli string with sign.
549563 ///
550564 /// # Returns
551565 /// A string like "+IXYZ" or "-iXYZ"
566+ #[ must_use]
552567 pub fn to_dense_string ( & self ) -> String {
553568 format ! ( "{}{}" , self . sign_string( ) , self . dense_string( ) )
554569 }
@@ -694,19 +709,19 @@ mod tests {
694709 #[ test]
695710 fn test_sign_tracking ( ) {
696711 let mut sim = StdPauliProp :: with_sign_tracking ( 4 ) ;
697-
712+
698713 // Initially should be +
699714 assert_eq ! ( sim. sign_string( ) , "+" ) ;
700-
715+
701716 // Flip sign
702717 sim. flip_sign ( ) ;
703718 assert_eq ! ( sim. sign_string( ) , "-" ) ;
704-
719+
705720 // Add imaginary phase
706721 sim. flip_sign ( ) ; // Back to +
707722 sim. flip_img ( 1 ) ;
708723 assert_eq ! ( sim. sign_string( ) , "+i" ) ;
709-
724+
710725 // Two i's should give -1
711726 sim. flip_img ( 1 ) ;
712727 assert_eq ! ( sim. sign_string( ) , "-" ) ;
@@ -715,22 +730,22 @@ mod tests {
715730 #[ test]
716731 fn test_weight ( ) {
717732 let mut sim = StdPauliProp :: new ( ) ;
718-
733+
719734 // Empty should have weight 0
720735 assert_eq ! ( sim. weight( ) , 0 ) ;
721-
736+
722737 // Add X on qubit 0
723738 sim. add_x ( 0 ) ;
724739 assert_eq ! ( sim. weight( ) , 1 ) ;
725-
740+
726741 // Add Z on qubit 1
727742 sim. add_z ( 1 ) ;
728743 assert_eq ! ( sim. weight( ) , 2 ) ;
729-
744+
730745 // Add Y on qubit 2 (both X and Z)
731746 sim. add_y ( 2 ) ;
732747 assert_eq ! ( sim. weight( ) , 3 ) ;
733-
748+
734749 // Adding X to qubit with Z makes Y
735750 sim. add_x ( 1 ) ;
736751 assert_eq ! ( sim. weight( ) , 3 ) ; // Still 3 operators
@@ -739,35 +754,35 @@ mod tests {
739754 #[ test]
740755 fn test_dense_string ( ) {
741756 let mut sim = StdPauliProp :: with_sign_tracking ( 4 ) ;
742-
757+
743758 sim. add_x ( 0 ) ;
744759 sim. add_z ( 2 ) ;
745760 sim. add_y ( 3 ) ;
746-
761+
747762 assert_eq ! ( sim. dense_string( ) , "XIZY" ) ;
748763 assert_eq ! ( sim. to_dense_string( ) , "+XIZY" ) ;
749-
764+
750765 sim. flip_sign ( ) ;
751766 assert_eq ! ( sim. to_dense_string( ) , "-XIZY" ) ;
752767 }
753768
754769 #[ test]
755770 fn test_add_paulis ( ) {
756771 let mut sim = StdPauliProp :: with_sign_tracking ( 4 ) ;
757-
772+
758773 let mut paulis = BTreeMap :: new ( ) ;
759774 let mut x_set = VecSet :: new ( ) ;
760775 x_set. insert ( 0 ) ;
761776 x_set. insert ( 1 ) ;
762-
777+
763778 let mut z_set = VecSet :: new ( ) ;
764779 z_set. insert ( 2 ) ;
765-
780+
766781 paulis. insert ( "X" . to_string ( ) , x_set) ;
767782 paulis. insert ( "Z" . to_string ( ) , z_set) ;
768-
783+
769784 sim. add_paulis ( & paulis) ;
770-
785+
771786 assert ! ( sim. contains_x( 0 ) ) ;
772787 assert ! ( sim. contains_x( 1 ) ) ;
773788 assert ! ( sim. contains_z( 2 ) ) ;
@@ -777,18 +792,18 @@ mod tests {
777792 #[ test]
778793 fn test_pauli_composition_with_phase ( ) {
779794 let mut sim = StdPauliProp :: with_sign_tracking ( 2 ) ;
780-
795+
781796 // Start with X on qubit 0
782797 sim. add_x ( 0 ) ;
783-
798+
784799 // Add Z to same qubit: X·Z = -iY (applying Z after X)
785800 let mut paulis = BTreeMap :: new ( ) ;
786801 let mut z_set = VecSet :: new ( ) ;
787802 z_set. insert ( 0 ) ;
788803 paulis. insert ( "Z" . to_string ( ) , z_set) ;
789-
804+
790805 sim. add_paulis ( & paulis) ;
791-
806+
792807 // Should now have Y on qubit 0
793808 assert ! ( sim. contains_y( 0 ) ) ;
794809 // Phase should be -i (X·Z = -iY)
0 commit comments