1
+ """
2
+ \********************************************************************************
3
+ * Copyright (c) 2024 the Qrisp authors
4
+ *
5
+ * This program and the accompanying materials are made available under the
6
+ * terms of the Eclipse Public License 2.0 which is available at
7
+ * http://www.eclipse.org/legal/epl-2.0.
8
+ *
9
+ * This Source Code may also be made available under the following Secondary
10
+ * Licenses when the conditions for such availability set forth in the Eclipse
11
+ * Public License, v. 2.0 are satisfied: GNU General Public License, version 2
12
+ * with the GNU Classpath Exception which is
13
+ * available at https://www.gnu.org/software/classpath/license.html.
14
+ *
15
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16
+ ********************************************************************************/
17
+ """
18
+
19
+ import numpy as np
20
+
21
+
22
+ def inverse_mod2 (matrix ):
23
+ """
24
+ Calculates the inverse of a binary matrix over the field of integers modulo 2.
25
+
26
+ Parameters
27
+ ----------
28
+ matrix : numpy.array
29
+ An invertible binary matrix.
30
+ result : numpy.array
31
+ The inverse of the given matrix over the field of integers modulo 2.
32
+
33
+ """
34
+ rows , columns = matrix .shape
35
+ if rows != columns :
36
+ raise Exception ("The matrix must be a square matrix" )
37
+
38
+ matrix = matrix .copy ()
39
+ result = np .eye (rows , dtype = int )
40
+
41
+ for i in range (rows ):
42
+ # Find the pivot row
43
+ max_row = i + np .argmax (matrix [i :,i ])
44
+
45
+ # Swap the current row with the pivot row
46
+ matrix [[i , max_row ]] = matrix [[max_row , i ]]
47
+ result [[i , max_row ]] = result [[max_row , i ]]
48
+
49
+ # Eliminate all rows below the pivot
50
+ for j in range (i + 1 , rows ):
51
+ if matrix [j , i ] == 1 :
52
+ matrix [j ] = (matrix [j ] + matrix [i ]) % 2
53
+ result [j ] = (result [j ] + result [i ]) % 2
54
+
55
+ # Backward elimination
56
+ for i in range (rows - 1 , - 1 , - 1 ):
57
+ for j in range (i - 1 , - 1 , - 1 ):
58
+ if matrix [j , i ] == 1 :
59
+ matrix [j ] = (matrix [j ] + matrix [i ]) % 2
60
+ result [j ] = (result [j ] + result [i ]) % 2
61
+
62
+ return result
63
+
64
+
65
+ def gaussian_elimination_mod2 (matrix , type = 'row' , show_pivots = False ):
66
+ r"""
67
+ Performs Gaussian elimination in the field $F_2$.
68
+
69
+ Parameters
70
+ ----------
71
+ matrix : numpy.array
72
+ A binary matrix.
73
+ type : str, optional
74
+ Available aore ``row`` for row echelon form, and ``column`` for column echelon form.
75
+ The default is ``row``.
76
+ show_pivots : Boolean, optional
77
+ If ``True``, the pivot columns (rows) are returned.
78
+
79
+ Returns
80
+ -------
81
+ matrix : numpy.array
82
+ The row (column) echelon form of the given matrix.
83
+ pivots : list[int], optional
84
+ A list of indices for the pivot columns (rows).
85
+
86
+ """
87
+
88
+ matrix = matrix .copy ()
89
+
90
+ rows , columns = matrix .shape
91
+ column_index = 0
92
+ row_index = 0
93
+ pivots = [] # the pivot columns (type='row') or rows (type='column')
94
+
95
+ if type == 'row' :
96
+ while row_index < rows and column_index < columns :
97
+ # Find the pivot row
98
+ max_row = row_index + np .argmax (matrix [row_index :,column_index ])
99
+
100
+ if matrix [max_row ,column_index ]== 0 :
101
+ column_index += 1
102
+ else :
103
+ # Pivot
104
+ pivots .append (column_index )
105
+
106
+ # Swap the current row with the pivot row
107
+ matrix [[row_index , max_row ]] = matrix [[max_row , row_index ]]
108
+
109
+ # Eliminate all rows below the pivot
110
+ for j in range (row_index + 1 , rows ):
111
+ if matrix [j , column_index ] == 1 :
112
+ matrix [j ] = (matrix [j ] + matrix [row_index ]) % 2
113
+
114
+ row_index += 1
115
+ column_index += 1
116
+
117
+ elif type == 'column' :
118
+ while row_index < rows and column_index < columns :
119
+ # Find the pivot column
120
+ max_column = column_index + np .argmax (matrix [row_index ,column_index :])
121
+
122
+ if matrix [row_index ,max_column ]== 0 :
123
+ row_index += 1
124
+ else :
125
+ # Pivot
126
+ pivots .append (row_index )
127
+
128
+ # Swap the current column with the pivot column
129
+ matrix [:,[column_index , max_column ]] = matrix [:,[max_column , column_index ]]
130
+
131
+ # Eliminate all rows below the pivot
132
+ for j in range (column_index + 1 , columns ):
133
+ if matrix [row_index , j ] == 1 :
134
+ matrix [:,j ] = (matrix [:,j ] + matrix [:,column_index ]) % 2
135
+
136
+ row_index += 1
137
+ column_index += 1
138
+
139
+ if show_pivots :
140
+ return matrix , pivots #(pivot_rows,pivot_cols)
141
+ else :
142
+ return matrix
143
+
144
+
145
+ def construct_change_of_basis (S ):
146
+ """
147
+ Implements the CZ construction outlined in https://quantum-journal.org/papers/q-2021-01-20-385/.
148
+
149
+ Parameters
150
+ ----------
151
+ S : numpy.array
152
+ A matrix representing a list of commuting Pauli operators: Each column is a Pauli operator in binary representation.
153
+
154
+ Returns
155
+ -------
156
+ A : numpy.array
157
+ Adjacency matrix for the graph state.
158
+ R_inv : numpy.array
159
+ A matrix representing a list of new Pauli operators.
160
+ h_list : numpy.array
161
+ A list indicating the qubits on which a Hadamard gate is applied.
162
+ s_list : numpy.array
163
+ A list indicating the qubits on which an S gate is applied.
164
+ perm_vec : numpy.array
165
+ A vector repesenting a permutation.
166
+
167
+ """
168
+
169
+ n = int (S .shape [0 ]/ 2 )
170
+
171
+ ####################
172
+ # Step 0: Calculate S_0: Independent columns (i.e., Pauli terms) of S
173
+ ####################
174
+
175
+ S_reduced , independent_cols = gaussian_elimination_mod2 (S , show_pivots = True )
176
+ k = len (independent_cols )
177
+
178
+ #S0 = np.vstack((z_matrix[:,independent_cols], x_matrix[:,independent_cols]))
179
+ S0 = S [:,independent_cols ]
180
+
181
+ R0_inv = S_reduced [:k , :]
182
+
183
+ ####################
184
+ # Step 1: Calculate S_1: The first k rows of the X component have full rank k
185
+ ####################
186
+
187
+ # Find independent rows in X component of S0
188
+ S0X_reduced , independent_rows = gaussian_elimination_mod2 (S0 [- n :, :], type = 'column' , show_pivots = True )
189
+
190
+ # Construnct S1 by applying a Hadamard (i.e., a swap) to the rows of S0 not in independent_rows
191
+ h_list = [i for i in range (n ) if i not in independent_rows ]
192
+ S1 = S0 .copy ()
193
+ for i in h_list :
194
+ S1 [[i , n + i ]] = S1 [[n + i , i ]]
195
+
196
+ # Find independent rows in X component of S1
197
+ S1X_reduced , independent_rows = gaussian_elimination_mod2 (S1 [- n :, :], type = "column" , show_pivots = True )
198
+
199
+ # Construct permutation achieving that the first k rows in X component of S1 are independent
200
+ perm = np .arange (0 ,n )
201
+ for index1 , index2 in enumerate (independent_rows ):
202
+ perm [index1 ] = index2
203
+ perm [index2 ] = index1
204
+
205
+ # Apply permutation to rows of S1 for Z and X component
206
+ S1 = np .vstack ((S1 [:n ,:][perm ],S1 [- n :,:][perm ]))
207
+
208
+ ####################
209
+ # Step 2: Calculate S2: The first k rows of the X component are the identity matrix
210
+ ####################
211
+
212
+ R1_inv = S1 [n :n + k ,:]
213
+ R1 = inverse_mod2 (R1_inv )
214
+ S2 = S1 @ R1 % 2
215
+
216
+ ####################
217
+ # Step 3: Calculate S3: Basis extension
218
+ ####################
219
+
220
+ C = S2 [:k , :]
221
+ D = S2 [k :n , :]
222
+ F = S2 [- (n - k ):, :]
223
+
224
+ S3 = np .block ([[C , np .transpose (D )],
225
+ [D , np .zeros ((n - k ,n - k ), dtype = int )],
226
+ [np .eye (k , dtype = int ), np .zeros ((k ,n - k ), dtype = int )],
227
+ [F , np .eye (n - k , dtype = int )]])
228
+ R2_inv = np .block ([[np .eye (k , dtype = int )],
229
+ [np .zeros ((n - k ,k ), dtype = int )]])
230
+
231
+ ####################
232
+ # Step 4: Calculate S4
233
+ ####################
234
+
235
+ R3_inv = S3 [- n :,:]
236
+ R3 = inverse_mod2 (R3_inv )
237
+
238
+ S4 = S3 @ R3 % 2
239
+
240
+ # Remove diagonal entries in upper left block
241
+ s_list = []
242
+ for i in range (n ):
243
+ if S4 [:n , :][i ,i ]== 1 :
244
+ s_list .append (i )
245
+
246
+ Q2 = np .block ([[np .eye (n , dtype = int ),S4 [:n , :]* np .eye (n , dtype = int )],
247
+ [np .zeros ((n ,n ), dtype = int ),np .eye (n , dtype = int )]])
248
+
249
+ S4 = Q2 @ S4 % 2
250
+
251
+ R_inv = R3_inv @ R2_inv @ R1_inv @ R0_inv % 2
252
+
253
+ # Adjacency matrix for the graph
254
+ A = S4 [:n , :]
255
+
256
+ return A , R_inv , h_list , s_list , perm
0 commit comments