@@ -107,10 +107,10 @@ pub fn decompose_dense(
107107 operator. shape( )
108108 ) ) ) ;
109109 }
110- let ( stack, mut out_stack , mut scratch) =
110+ let ( stack, mut out_list , mut scratch) =
111111 decompose_first_level ( operator. as_array ( ) , num_qubits) ;
112- decompose_middle_levels ( stack, & mut out_stack , & mut scratch, num_qubits) ;
113- let out = decompose_last_level ( & mut out_stack , & scratch, num_qubits, tolerance) ?;
112+ decompose_middle_levels ( stack, & mut out_list , & mut scratch, num_qubits) ;
113+ let out = decompose_last_level ( & mut out_list , & scratch, num_qubits, tolerance) ?;
114114 Ok ( ZXPaulis {
115115 z : PyArray1 :: from_vec ( py, out. z )
116116 . reshape ( [ out. phases . len ( ) , num_qubits] ) ?
@@ -135,15 +135,15 @@ fn decompose_first_level(
135135 let side = 1 << num_qubits;
136136 let zero = Complex64 :: new ( 0.0 , 0.0 ) ;
137137 let mut stack = Vec :: < PauliLocation > :: with_capacity ( 4 ) ;
138- let mut out_stack = Vec :: < PauliLocation > :: new ( ) ;
138+ let mut out_list = Vec :: < PauliLocation > :: new ( ) ;
139139 let mut scratch = Vec :: < Complex64 > :: with_capacity ( side * side) ;
140140 match num_qubits {
141141 0 => { }
142142 1 => {
143143 // If we've only got one qubit, we just want to copy the data over in the correct
144144 // continuity and let the base case of the iteration take care of outputting it.
145145 scratch. extend ( in_op. iter ( ) ) ;
146- out_stack . push ( PauliLocation :: begin ( num_qubits) ) ;
146+ out_list . push ( PauliLocation :: begin ( num_qubits) ) ;
147147 }
148148 _ => {
149149 unsafe { scratch. set_len ( side * side) } ;
@@ -214,33 +214,31 @@ fn decompose_first_level(
214214 z_nonzero = z_nonzero || ( value != zero) ;
215215 }
216216 }
217- let target_stack = if cur_qubit == 1 {
218- & mut out_stack
217+ // The middle-levels `stack` is a LIFO, so if we push in this order, we'll consider the
218+ // Pauli terms in lexicographical order, which is the canonical order from
219+ // `SparsePauliOp.sort`. Populating the `out_list` (an initially empty `Vec`) effectively
220+ // reverses the stack, so we want to push its elements in the IXYZ order.
221+ if loc. qubit ( ) == 1 {
222+ i_nonzero. then ( || out_list. push ( loc. push_i ( ) ) ) ;
223+ x_nonzero. then ( || out_list. push ( loc. push_x ( ) ) ) ;
224+ y_nonzero. then ( || out_list. push ( loc. push_y ( ) ) ) ;
225+ z_nonzero. then ( || out_list. push ( loc. push_z ( ) ) ) ;
219226 } else {
220- & mut stack
221- } ;
222- if i_nonzero {
223- target_stack. push ( loc. push_i ( ) ) ;
224- }
225- if x_nonzero {
226- target_stack. push ( loc. push_x ( ) ) ;
227- }
228- if y_nonzero {
229- target_stack. push ( loc. push_y ( ) ) ;
230- }
231- if z_nonzero {
232- target_stack. push ( loc. push_z ( ) ) ;
227+ z_nonzero. then ( || stack. push ( loc. push_z ( ) ) ) ;
228+ y_nonzero. then ( || stack. push ( loc. push_y ( ) ) ) ;
229+ x_nonzero. then ( || stack. push ( loc. push_x ( ) ) ) ;
230+ i_nonzero. then ( || stack. push ( loc. push_i ( ) ) ) ;
233231 }
234232 }
235233 }
236- ( stack, out_stack , scratch)
234+ ( stack, out_list , scratch)
237235}
238236
239237/// Iteratively decompose the matrix at all levels other than the first and last. This populates
240238/// the `out_stack` with locations
241239fn decompose_middle_levels (
242240 mut stack : Vec < PauliLocation > ,
243- mut out_stack : & mut Vec < PauliLocation > ,
241+ out_list : & mut Vec < PauliLocation > ,
244242 scratch : & mut [ Complex64 ] ,
245243 num_qubits : usize ,
246244) {
@@ -256,11 +254,6 @@ fn decompose_middle_levels(
256254 // for X and Y) so we can re-use their scratch space and avoid re-allocating. We're doing
257255 // the multiple assignment `(I, Z) = (I + Z, I - Z)`.
258256 let mid = 1 << loc. qubit ( ) ;
259- let target_stack = if loc. qubit ( ) == 1 {
260- & mut out_stack
261- } else {
262- & mut stack
263- } ;
264257 let mut i_nonzero = false ;
265258 let mut z_nonzero = false ;
266259 let i_row_0 = loc. row ( ) ;
@@ -302,23 +295,26 @@ fn decompose_middle_levels(
302295 y_nonzero = y_nonzero || ( sub != zero) ;
303296 }
304297 }
305- if i_nonzero {
306- target_stack. push ( loc. push_i ( ) ) ;
307- }
308- if x_nonzero {
309- target_stack. push ( loc. push_x ( ) ) ;
310- }
311- if y_nonzero {
312- target_stack. push ( loc. push_y ( ) ) ;
313- }
314- if z_nonzero {
315- target_stack. push ( loc. push_z ( ) ) ;
298+ // The middle-levels `stack` is a LIFO, so if we push in this order, we'll consider the
299+ // Pauli terms in lexicographical order, which is the canonical order from
300+ // `SparsePauliOp.sort`. Populating the `out_list` (an initially empty `Vec`) effectively
301+ // reverses the stack, so we want to push its elements in the IXYZ order.
302+ if loc. qubit ( ) == 1 {
303+ i_nonzero. then ( || out_list. push ( loc. push_i ( ) ) ) ;
304+ x_nonzero. then ( || out_list. push ( loc. push_x ( ) ) ) ;
305+ y_nonzero. then ( || out_list. push ( loc. push_y ( ) ) ) ;
306+ z_nonzero. then ( || out_list. push ( loc. push_z ( ) ) ) ;
307+ } else {
308+ z_nonzero. then ( || stack. push ( loc. push_z ( ) ) ) ;
309+ y_nonzero. then ( || stack. push ( loc. push_y ( ) ) ) ;
310+ x_nonzero. then ( || stack. push ( loc. push_x ( ) ) ) ;
311+ i_nonzero. then ( || stack. push ( loc. push_i ( ) ) ) ;
316312 }
317313 }
318314}
319315
320316fn decompose_last_level (
321- out_stack : & mut Vec < PauliLocation > ,
317+ out_list : & mut Vec < PauliLocation > ,
322318 scratch : & [ Complex64 ] ,
323319 num_qubits : usize ,
324320 tolerance : f64 ,
@@ -329,16 +325,16 @@ fn decompose_last_level(
329325 // don't really pay much cost if we overallocate, but underallocating means that all four
330326 // outputs have to copy their data across to a new allocation.
331327 let mut out = DecomposeOut {
332- z : Vec :: with_capacity ( 4 * num_qubits * out_stack . len ( ) ) ,
333- x : Vec :: with_capacity ( 4 * num_qubits * out_stack . len ( ) ) ,
334- phases : Vec :: with_capacity ( 4 * out_stack . len ( ) ) ,
335- coeffs : Vec :: with_capacity ( 4 * out_stack . len ( ) ) ,
328+ z : Vec :: with_capacity ( 4 * num_qubits * out_list . len ( ) ) ,
329+ x : Vec :: with_capacity ( 4 * num_qubits * out_list . len ( ) ) ,
330+ phases : Vec :: with_capacity ( 4 * out_list . len ( ) ) ,
331+ coeffs : Vec :: with_capacity ( 4 * out_list . len ( ) ) ,
336332 scale,
337333 tol : ( tolerance * tolerance) / ( scale * scale) ,
338334 num_qubits,
339335 } ;
340336
341- for loc in out_stack . drain ( ..) {
337+ for loc in out_list . drain ( ..) {
342338 let row = loc. row ( ) ;
343339 let col = loc. col ( ) ;
344340 let base = row * side + col;
@@ -350,10 +346,12 @@ fn decompose_last_level(
350346 let x = row ^ col;
351347 let z = row;
352348 let phase = ( x & z) . count_ones ( ) as u8 ;
349+ // ... and pushing the last Pauli onto the chain happens forwards, since this is the
350+ // construction of the final object.
353351 push_pauli_if_nonzero ( x, z, phase, i_value, & mut out) ;
354- push_pauli_if_nonzero ( x, z | 1 , phase, z_value, & mut out) ;
355352 push_pauli_if_nonzero ( x | 1 , z, phase, x_value, & mut out) ;
356353 push_pauli_if_nonzero ( x | 1 , z | 1 , phase + 1 , y_value, & mut out) ;
354+ push_pauli_if_nonzero ( x, z | 1 , phase, z_value, & mut out) ;
357355 }
358356 // If we _wildly_ overallocated, then shrink back to a sensible size to avoid tying up too much
359357 // memory as we return to Python space.
0 commit comments