Skip to content

Commit 6d5d72f

Browse files
first commit for dqi_alogrithm: dqi.py as main file + testcase
1 parent 1331777 commit 6d5d72f

File tree

2 files changed

+461
-0
lines changed

2 files changed

+461
-0
lines changed

src/qrisp/algorithms/dqi.py

Lines changed: 355 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
1+
"""
2+
********************************************************************************
3+
* Copyright (c) 2025 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+
20+
21+
from qrisp import control, cx , swap, QuantumFloat, QuantumArray, h, z, invert,ry, gate_wrap
22+
23+
24+
from scipy.sparse.linalg import eigsh
25+
from qrisp.alg_primitives import dicke_state
26+
from scipy.sparse import diags
27+
28+
import numpy as np
29+
30+
def gauss_jordan_operations_general(matrix):
31+
"""
32+
Extracts the actions of a Gauss-Jordan-elimination, which affect the relevants (last) column of an augmented matrix (the augmenting rows are not included in the input matrix) and returns the operations,
33+
encoded as a list of strings (gates) and integers (qubits).
34+
35+
Parameters
36+
----------
37+
matrix : np.ndarray
38+
Definition of the matrix for extraction of the operations.
39+
40+
Returns
41+
-------
42+
operations : List
43+
The operations on the relevant rows, encoded as a list of strings (gates) and integers (qubits).
44+
mat : np.ndarray
45+
The matrix after the operations.
46+
47+
"""
48+
# quick exit on empty or invalid matrix
49+
if not matrix or not matrix[0]:
50+
return [], []
51+
m = len(matrix)
52+
num_cols = len(matrix[0])
53+
n = num_cols - 1 # last column is RHS
54+
# work on a copy
55+
mat = [row.copy() for row in matrix]
56+
operations = []
57+
pivot_row = 0
58+
pivot_col = 0
59+
while pivot_row < m and pivot_col < n:
60+
# find pivot in this column at or below pivot_row
61+
pivot_idx = None
62+
for r in range(pivot_row, m):
63+
if mat[r][pivot_col] == 1:
64+
pivot_idx = r
65+
break
66+
if pivot_idx is None:
67+
pivot_col += 1
68+
continue
69+
70+
# swap into pivot position if needed
71+
if pivot_idx != pivot_row:
72+
operations.append(('swap', pivot_row, pivot_idx))
73+
mat[pivot_row], mat[pivot_idx] = mat[pivot_idx], mat[pivot_row]
74+
75+
# eliminate other rows
76+
pivot_data = mat[pivot_row]
77+
for r in range(m):
78+
if r != pivot_row and mat[r][pivot_col] == 1:
79+
operations.append(('xor', pivot_row, r))
80+
# XOR rows from pivot_col onward
81+
row_r = mat[r]
82+
row_r[pivot_col:] = [a ^ b for a, b in zip(row_r[pivot_col:], pivot_data[pivot_col:])]
83+
84+
pivot_row += 1
85+
pivot_col += 1
86+
87+
return operations, mat
88+
89+
def syndrome_decoding(matrix, qv_syndrome):
90+
"""
91+
Performs the decoding step of the DQI algorithm on a binary ``QuantumArray``, by extracting the operations in a classical way.
92+
Only the actions, which affect the relevants (last) column of an augmented matrix are considered. The row, by which the matrix is augmented is given by ``qv_syn``.
93+
94+
Parameters
95+
----------
96+
matrix : np.ndarray
97+
Definition of the matrix on which the the decoding is based on.
98+
qv_syndrome : QuantumArray
99+
``QuantumArray`` to be decoded.
100+
101+
Returns
102+
-------
103+
mat : np.ndarray
104+
The matrix after the operations, according to function ``gauss_jordan_operations_general```.
105+
qv_syn : QuantumArray
106+
``QuantumArray`` after decoding.
107+
108+
"""
109+
110+
qv_syn = qv_syndrome
111+
if isinstance(matrix, np.ndarray):
112+
mat_list = matrix.tolist()
113+
ops, mat = gauss_jordan_operations_general(mat_list)
114+
num_qubits = len(mat_list)
115+
for op_type, src, tgt in ops:
116+
if op_type == "swap":
117+
swap(qv_syn[src][0], qv_syn[tgt][0])
118+
elif op_type == "xor":
119+
cx(qv_syn[src][0], qv_syn[tgt][0])
120+
121+
return mat , qv_syn
122+
123+
124+
125+
126+
127+
128+
# m is number of constraints, p is the that defines F_p (=2 in case of MAX-XORSAT), r=1 in case of MAX-XORSAT
129+
def get_optimal_w(m: int, l: int, p: int, r: int) -> np.ndarray:
130+
r"""
131+
Solve for the eigenvector of the symmetric tridiagonal matrix, which emerges from the expected number of solved constraints.
132+
Used for a fan-out Uniary Amplitude Encoding state-preparation.
133+
(See Lemma 9.2 in the `DQI paper <https://arxiv.org/pdf/2408.08292>`_)
134+
135+
Parameters
136+
----------
137+
m : int
138+
The number of constraints, i.e. rows of the input matrix.
139+
l : int
140+
Degree for polynomial to encode objective function in. Can be chosen in an optimized way.
141+
p : int
142+
Defines the modulus of the field, i.e. $\mathcal{F}_p$.
143+
r : int
144+
Number of inputs yielding $+1$.
145+
146+
Returns
147+
-------
148+
weights : np.ndarray
149+
Eigenvector of the symmetric tridiagonal matrix.
150+
151+
"""
152+
d = (p - 2*r) / np.sqrt(r * (p - r))
153+
diag = np.arange(l + 1) * d
154+
off = np.sqrt(np.arange(1, l + 1) * (m - np.arange(1, l + 1) + 1))
155+
156+
A = diags([off, diag, off], offsets=(-1, 0, 1), format="csr")
157+
158+
_, vecs = eigsh(A, k=1, which="LA")
159+
w = vecs.flatten()
160+
161+
orig = len(w)
162+
pad_len = 2**int(np.ceil(np.log2(l+1)))
163+
weights = list(w)
164+
weights += [0 for i in range((pad_len -orig)) ]
165+
return weights
166+
167+
168+
#@gate_wrap
169+
def uae_encoding(qa_error, num_constraints, weights):
170+
r"""
171+
Fan-out Uniary Amplitude Encoding state-preparation, based on the weights received from solving for the eigenvector of the symmetric tridiagonal matrix.
172+
173+
Parameters
174+
----------
175+
qa_error : QuantumArray
176+
The ``QuantumArray`` (in the form of single qubit ``QuantumVariables``) to encode.
177+
num_constraints : int
178+
The number of constraints, i.e. rows of the input matrix.
179+
weights : np.ndarray
180+
Eigenvector of the symmetric tridiagonal matrix.
181+
182+
"""
183+
184+
weights = weights/sum(weights)
185+
w2 = weights
186+
cum = np.concatenate(([0.0], np.cumsum(w2[:-1])))
187+
denom = 1.0 - cum
188+
# Avoid division by zero, clip ratios
189+
ratio = np.where(denom > 0, w2 / denom, 0.0)
190+
ratio = np.clip(ratio, 0.0, 1.0)
191+
betas = 2.0 * np.arccos(np.sqrt(ratio))
192+
193+
# Apply first RY if nonzero
194+
if betas[0] != 0.0 and not np.isnan(betas[0]):
195+
ry(betas[0], qa_error[0][0])
196+
197+
# Controlled rotations
198+
for i in range(1, num_constraints):
199+
if i < betas.size:
200+
b = betas[i]
201+
if b != 0.0 and not np.isnan(b):
202+
with control( qa_error[i-1][0]):
203+
ry(b, qa_error[i][0])
204+
205+
206+
def specific_phase_encoding(qa_error, v):
207+
r"""
208+
Specific phase encoding for DQI problem instance.
209+
210+
Parameters
211+
----------
212+
qa_error : QuantumArray
213+
The ``QuantumArray`` (in the form of single qubit ``QuantumVariables``) to encode.
214+
v_j : List
215+
Phase information corresponding to the problem constraints
216+
217+
"""
218+
for index in range(len(v)):
219+
if v[index] == 1:
220+
z(qa_error[index])
221+
222+
223+
224+
def constraint_encoding(qa_error, qv_syndrome, B):
225+
r"""
226+
Constraint encoding for DQI problem instance.
227+
228+
Parameters
229+
----------
230+
qa_error : QuantumArray
231+
The ``QuantumArray`` (in the form of single qubit ``QuantumVariables``) which represents the error register.
232+
qa_error : QuantumArray
233+
The ``QuantumArray`` (in the form of single qubit ``QuantumVariables``) which represents the syndrome register.
234+
B : np.ndarray
235+
Problem matrix to encode.
236+
237+
"""
238+
# reverse indices due to circuit definition
239+
qa_error, qv_syndrome = qa_error[::-1], qv_syndrome[::-1]
240+
241+
# create B^T
242+
B_t = np.transpose(B)
243+
244+
# encode constraints
245+
i_ind, j_ind = B_t.shape
246+
for i in range(i_ind):
247+
for j in range(j_ind):
248+
if B_t[i][j] == 1:
249+
cx(qa_error[j], qv_syndrome[i])
250+
251+
return qa_error, qv_syndrome
252+
253+
254+
255+
256+
257+
258+
def dqi_optimization(B, v, l=2, p=2, r=1):
259+
r"""
260+
Implementation of `Optimization by Decoded Quantum Interferometry <https://arxiv.org/pdf/2408.08292>`_, with inspiration from `Quantum Circuit Design for Decoded Quantum
261+
Interferometry <https://arxiv.org/pdf/2504.18334>`_.
262+
This algorithm is used to solve optimization problems, by using the quantum
263+
Fourier transform (or more so properties) to reduce optimization problems to decoding problems.
264+
For an indepth explanation, please refer to the referenced papers.
265+
266+
Parameters
267+
----------
268+
B : np.ndarray
269+
Definition of the problem instance.
270+
v : List
271+
Constraints enforced on the solution.
272+
l : int
273+
Degree for polynomial to encode objective function in. Can be chosen in an optimized way.
274+
p : int
275+
Defines the modulus of the field, i.e. $\mathcal{F}_p$.
276+
r : int
277+
Number of inputs yielding $+1$.
278+
279+
Returns
280+
-------
281+
qa_error : QuantumArray
282+
The ``QuantumArray`` (in the form of single qubit ``QuantumVariables``) which represents the final error register.
283+
qa_error : QuantumArray
284+
The ``QuantumArray`` (in the form of single qubit ``QuantumVariables``) which represents the final syndrome register.
285+
286+
Examples
287+
--------
288+
289+
We create an example case for a maxCut instance.
290+
291+
::
292+
293+
B = np.array([
294+
[1, 1, 0, 0, 0, 0],
295+
[1, 0, 0, 0, 1, 0],
296+
[0, 1, 1, 0, 0, 0],
297+
[0, 0, 1, 1, 0, 0],
298+
[0, 0, 0, 1, 0, 1],
299+
[0, 0, 0, 0, 1, 1]
300+
])
301+
302+
v = np.array(
303+
[1, 1, 1, 1, 1, 1],
304+
)
305+
306+
307+
308+
qvin, resqf = dqi_optimization(B, v)
309+
res_dict = multi_measurement([qvin, resqf])
310+
311+
"""
312+
# L =2 for MaxCut
313+
314+
m = B.shape[0]
315+
n = B.shape[1]
316+
q_type = QuantumFloat(1, 0)
317+
qa_error = QuantumArray(qtype= q_type , shape=(m,))
318+
319+
# create syndrome quantumArray
320+
qa_syndrome = QuantumArray(qtype= q_type , shape=(m,))
321+
322+
# l can be chosen to be equal to m, or as rank(B) as an optimized version (or even more optimized)
323+
# --> it defines the uae_eigenvector consideration and size qv_inner,
324+
princ_eigenvec = get_optimal_w(m,l,p,r)
325+
326+
# UAE encoding encoding
327+
uae_encoding(qa_error, m, princ_eigenvec)
328+
329+
# need to reverse due to dicke_state function definition
330+
dicke_state(qa_error[::-1], len(qa_error))
331+
332+
# phase encoding
333+
specific_phase_encoding(qa_error, v)
334+
335+
# constraint encoding
336+
qa_error, qa_syndrome = constraint_encoding(qa_error, qa_syndrome, B)
337+
338+
# syndrome decoding step, this will in the future be replaced by a quantum version
339+
syndrome_decoding(B.T, qa_syndrome)
340+
341+
# then perform further application onto the error register
342+
for i in range(m):
343+
cx(qa_syndrome[i], qa_error[i])
344+
345+
with invert():
346+
#quantum_GJE(Bt_full)
347+
syndrome_decoding(B.T, qa_syndrome)
348+
349+
# H-transform for readout
350+
h(qa_syndrome)
351+
352+
353+
return qa_error, qa_syndrome
354+
355+

0 commit comments

Comments
 (0)