Skip to content

Commit 5025d7d

Browse files
committed
fixed merge conflicts
2 parents 047a08d + ee2dbfc commit 5025d7d

File tree

8 files changed

+526
-84
lines changed

8 files changed

+526
-84
lines changed

src/qrisp/operators/qubit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@
2222
from qrisp.operators.qubit.bound_qubit_operator import *
2323
#from qrisp.operators.qubit.pauli_measurement import *
2424
from qrisp.operators.qubit.operator_factors import *
25+
from qrisp.operators.qubit.commutativity_tools import *
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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 = S[:,independent_cols]
179+
R0_inv = S_reduced[:k, :]
180+
181+
####################
182+
# Step 1: Calculate S_1: The first k rows of the X component have full rank k
183+
####################
184+
185+
# Find independent rows in X component of S0
186+
S0X_reduced, independent_rows = gaussian_elimination_mod2(S0[-n:, :], type='column', show_pivots=True)
187+
188+
S1 = S0.copy()
189+
h_list = []
190+
# Construct S1 by applying a Hadamard (i.e., a swap) to the rows of S0 not in independent_rows
191+
if len(independent_rows)<k:
192+
h_list = [i for i in range(n) if i not in independent_rows]
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 if n>k
218+
####################
219+
220+
if n>k:
221+
222+
C = S2[:k, :]
223+
D = S2[k:n, :]
224+
F = S2[-(n-k):, :]
225+
226+
S3 = np.block([[C, np.transpose(D)],
227+
[D, np.zeros((n-k,n-k), dtype=int)],
228+
[np.eye(k, dtype=int), np.zeros((k,n-k), dtype=int)],
229+
[F, np.eye(n-k, dtype=int)]])
230+
R2_inv = np.block([[np.eye(k, dtype=int)],
231+
[np.zeros((n-k,k), dtype=int)]])
232+
233+
else:
234+
235+
S3 = S2
236+
R2_inv = np.eye(n, dtype=int)
237+
238+
####################
239+
# Step 4: Calculate S4
240+
####################
241+
242+
R3_inv = S3[-n:,:]
243+
R3 = inverse_mod2(R3_inv)
244+
245+
S4 = S3 @ R3 % 2
246+
247+
# Remove diagonal entries in upper left block
248+
s_list = []
249+
for i in range(n):
250+
if S4[:n, :][i,i]==1:
251+
s_list.append(i)
252+
253+
Q2 = np.block([[np.eye(n, dtype=int),S4[:n, :]*np.eye(n, dtype=int)],
254+
[np.zeros((n,n), dtype=int),np.eye(n, dtype=int)]])
255+
256+
S4 = Q2 @ S4 % 2
257+
258+
R_inv = R3_inv @ R2_inv @ R1_inv @ R0_inv % 2
259+
260+
# Adjacency matrix for the graph
261+
A = S4[:n, :]
262+
263+
return A, R_inv, h_list, s_list, perm

src/qrisp/operators/qubit/measurement.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def get_measurement(
3535
compilation_kwargs={},
3636
subs_dic={},
3737
precompiled_qc=None,
38+
diagonalisation_method="ommuting_qw",
3839
measurement_data=None # measurement settings
3940
):
4041
r"""
@@ -137,34 +138,35 @@ def get_measurement(
137138
qc = qc.transpile()
138139

139140
if measurement_data is None:
140-
measurement_data = QubitOperatorMeasurement(hamiltonian)
141+
measurement_data = QubitOperatorMeasurement(hamiltonian, diagonalisation_method = diagonalisation_method)
141142

142143
return measurement_data.get_measurement(qc, qarg, precision, backend)
143144

144145

145146
class QubitOperatorMeasurement:
146147

147-
def __init__(self, qubit_op):
148+
def __init__(self, qubit_op, diagonalisation_method="commuting_qw"):
148149

149150
n = qubit_op.find_minimal_qubit_amount()
150151
hamiltonian = qubit_op.hermitize()
151152
hamiltonian = hamiltonian.eliminate_ladder_conjugates()
152153

153-
self.groups = hamiltonian.commuting_qw_groups()
154+
if diagonalisation_method=="commuting_qw":
155+
self.groups = hamiltonian.commuting_qw_groups()
156+
else:
157+
self.groups = hamiltonian.group_up(lambda a, b: a.commute(b))
154158
# print(len(self.groups))
155159

156-
self.n = len(self.groups)
157-
158-
159160
self.stds = []
160-
self.conjugation_circuits = []
161+
self.change_of_basis_gates = []
161162
self.measurement_operators = []
162163

163164
for group in self.groups:
164165

165-
conj_circuit, meas_op = group.get_conjugation_circuit()
166+
qv = QuantumVariable(n)
166167

167-
self.conjugation_circuits.append(conj_circuit)
168+
meas_op = group.change_of_basis(qv, diagonalisation_method)
169+
self.change_of_basis_gates.append(qv.qs.to_gate())
168170
self.measurement_operators.append(meas_op)
169171

170172
# Collect standard deviation
@@ -180,17 +182,18 @@ def get_measurement(self, qc, qubit_list, precision, backend):
180182
meas_ops = []
181183
meas_coeffs = []
182184

183-
for i in range(self.n):
185+
for i in range(len(self.groups)):
184186

185187
group = self.groups[i]
186-
conjugation_circuit = self.conjugation_circuits[i]
188+
187189
shots = int(self.shots_list[i]/precision**2)
188190

191+
qubits = [qubit_list[j] for j in range(self.change_of_basis_gates[i].num_qubits)]
192+
189193
curr = qc.copy()
190-
qubits = [qubit_list[i] for i in range(conjugation_circuit.num_qubits())]
191-
curr.append(conjugation_circuit.to_gate(), qubits)
194+
curr.append(self.change_of_basis_gates[i], qubits)
192195

193-
res = get_measurement_from_qc(curr.transpile(), list(qubit_list), backend, shots)
196+
res = get_measurement_from_qc(curr, list(qubit_list), backend, shots)
194197
results.append(res)
195198

196199
temp_meas_ops = []
@@ -201,7 +204,7 @@ def get_measurement(self, qc, qubit_list, precision, backend):
201204

202205
meas_coeffs.append(temp_coeff)
203206
meas_ops.append(temp_meas_ops)
204-
207+
205208
samples = create_padded_array([list(res.keys()) for res in results]).astype(np.int64)
206209
probs = create_padded_array([list(res.values()) for res in results])
207210
meas_ops = create_padded_array(meas_ops, use_tuples = True).astype(np.int64)

0 commit comments

Comments
 (0)