@@ -19,6 +19,17 @@ class StabilizerCircuit(AbstractCircuit):
19
19
20
20
# Add gate sets as class attributes
21
21
clifford_gates = ["h" , "x" , "y" , "z" , "cnot" , "cz" , "swap" , "s" , "sd" ]
22
+ gate_map = {
23
+ "h" : "H" ,
24
+ "x" : "X" ,
25
+ "y" : "Y" ,
26
+ "z" : "Z" ,
27
+ "cnot" : "CNOT" ,
28
+ "cz" : "CZ" ,
29
+ "swap" : "SWAP" ,
30
+ "s" : "S" ,
31
+ "sd" : "S_DAG" ,
32
+ }
22
33
23
34
def __init__ (self , nqubits : int ) -> None :
24
35
self ._nqubits = nqubits
@@ -55,36 +66,24 @@ def apply_general_gate(
55
66
"gate" : gate ,
56
67
"index" : index ,
57
68
"name" : name ,
58
- "split" : kws . get ( "split" , False ) ,
59
- "mpo" : kws . get ( "mpo" , False ) ,
69
+ "split" : False ,
70
+ "mpo" : False ,
60
71
}
61
- ir_dict = kws . get ( "ir_dict" , None )
72
+ ir_dict = kws [ "ir_dict" ]
62
73
if ir_dict is not None :
63
74
ir_dict .update (gate_dict )
64
75
else :
65
76
ir_dict = gate_dict
66
77
self ._qir .append (ir_dict )
67
78
68
- # Convert negative indices
69
- index = tuple ([i if i >= 0 else self ._nqubits + i for i in index ])
70
-
71
79
# Map TensorCircuit gates to Stim gates
72
- gate_map = {
73
- "h" : "H" ,
74
- "x" : "X" ,
75
- "y" : "Y" ,
76
- "z" : "Z" ,
77
- "cnot" : "CNOT" ,
78
- "cz" : "CZ" ,
79
- "swap" : "SWAP" ,
80
- "s" : "S" ,
81
- "sd" : "S_DAG" ,
82
- }
83
- if name .lower () in gate_map :
84
- self ._stim_circuit .append (gate_map [name .lower ()], list (index ))
85
- instruction = stim .Circuit ()
86
- instruction .append (gate_map [name .lower ()], list (index ))
87
- self .current_sim .do (instruction )
80
+
81
+ if name .lower () in self .gate_map :
82
+ # self._stim_circuit.append(gate_map[name.lower()], list(index))
83
+ instruction = f"{ self .gate_map [name .lower ()]} { ' ' .join (map (str , index ))} "
84
+ self ._stim_circuit .append_from_stim_program_text (instruction )
85
+ # append is much slower
86
+ self .current_sim .do (stim .Circuit (instruction ))
88
87
else :
89
88
raise ValueError (f"Gate { name } is not supported in stabilizer simulation" )
90
89
@@ -125,7 +124,8 @@ def cond_measurement(self, index: int) -> Tensor:
125
124
# Convert negative indices
126
125
127
126
# Add measurement instructions
128
- self ._stim_circuit .append ("M" , index )
127
+ self ._stim_circuit .append_from_stim_program_text ("M " + str (index ))
128
+ # self.current_sim = None
129
129
m = self .current_simulator ().measure (index )
130
130
# Sample once from the circuit using sampler
131
131
@@ -296,7 +296,74 @@ def current_tableau(self) -> stim.Tableau:
296
296
"""
297
297
Return the current tableau of the circuit.
298
298
"""
299
- self .current_simulator ().current_inverse_tableau () ** - 1
299
+ return self .current_simulator ().current_inverse_tableau () ** - 1
300
+
301
+ def entanglement_entropy (self , cut : Sequence [int ]) -> float :
302
+ """
303
+ Calculate the entanglement entropy for a subset of qubits using stabilizer formalism.
304
+
305
+ :param cut: Indices of qubits to calculate entanglement entropy for
306
+ :type cut: Sequence[int]
307
+ :return: Entanglement entropy
308
+ :rtype: float
309
+ """
310
+ # Get stabilizer tableau
311
+ tableau = self .current_tableau ()
312
+ N = len (tableau )
313
+
314
+ # Pre-allocate binary matrix with proper dtype
315
+ # binary_matrix = np.zeros((N, 2 * N), dtype=np.int8)
316
+
317
+ # Vectorized conversion of stabilizers to binary matrix
318
+ # z_outputs = np.array([tableau.z_output(k) for k in range(N)])
319
+ # x_part = z_outputs == 1 # X
320
+ # z_part = z_outputs == 3 # Z
321
+ # y_part = z_outputs == 2 # Y
322
+
323
+ # binary_matrix[:, :N] = x_part | y_part
324
+ # binary_matrix[:, N:] = z_part | y_part
325
+
326
+ _ , _ , z2x , z2z , _ , _ = tableau .to_numpy ()
327
+ binary_matrix = np .concatenate ([z2x , z2z ], axis = 1 )
328
+ # Get reduced matrix for the cut using boolean indexing
329
+ cut_set = set (cut )
330
+ cut_indices = np .array (
331
+ [i for i in range (N ) if i in cut_set ]
332
+ + [i + N for i in range (N ) if i in cut_set ]
333
+ )
334
+ reduced_matrix = binary_matrix [:, cut_indices ]
335
+
336
+ # Efficient rank calculation using Gaussian elimination
337
+ matrix = reduced_matrix .copy ()
338
+ n_rows , n_cols = matrix .shape
339
+ rank = 0
340
+ row = 0
341
+
342
+ for col in range (n_cols ):
343
+ # Vectorized pivot finding
344
+ pivot_rows = np .nonzero (matrix [row :, col ])[0 ]
345
+ if len (pivot_rows ) > 0 :
346
+ pivot_row = pivot_rows [0 ] + row
347
+
348
+ # Swap rows if necessary
349
+ if pivot_row != row :
350
+ matrix [row ], matrix [pivot_row ] = (
351
+ matrix [pivot_row ].copy (),
352
+ matrix [row ].copy (),
353
+ )
354
+
355
+ # Vectorized elimination
356
+ eliminate_mask = matrix [row + 1 :, col ] == 1
357
+ matrix [row + 1 :][eliminate_mask ] ^= matrix [row ]
358
+
359
+ rank += 1
360
+ row += 1
361
+
362
+ if row == n_rows :
363
+ break
364
+
365
+ # Calculate entropy
366
+ return float ((rank - len (cut )) * np .log (2 ))
300
367
301
368
302
369
# Call _meta_apply at module level to register the gates
0 commit comments