Skip to content

Commit 8b5e8d1

Browse files
DQI mistakes fixes (#1609)
A series of changes in the code to fix a mistake in the matrix
1 parent d606a95 commit 8b5e8d1

File tree

2 files changed

+62
-43
lines changed

2 files changed

+62
-43
lines changed

demonstrations_v2/tutorial_dqi/demo.py

Lines changed: 61 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
where :math:`\mathbf{b_i}` is the :math:`i`-th row of matrix :math:`B`. You can verify that this
3939
function represents the number of satisfied equations minus the number of unsatisfied ones by
4040
considering that when the equation is satisfied, the exponent
41-
:math:`v_i+\mathbf{b_i}\cdot\mathbf{x}` is always even, and that it is odd in the opposite case.
41+
:math:`v_i+\mathbf{b_i}\cdot\mathbf{x}` is always even, and odd in the opposite case.
4242
4343
Let’s define the conditions for our specific max-XORSAT problem and visualize the objective
4444
function in a histogram when randomly sampling bit strings :math:`\mathbf{x}` from a uniform distribution.
@@ -104,7 +104,7 @@ def objective_function(x):
104104
# :class: note
105105
#
106106
# The `Hadamard transform <https://lucatrevisan.github.io/teaching/cs259q-12/lecture07b.pdf>`__ can be thought of as a version of the discrete Fourier transform. You have likely used it
107-
# several times when applying a Hadamard gate at the beginning of a circuit to prepare an equal superposition of all basis states.
107+
# several times when applying a layer of Hadamard gates at the beginning of a circuit to prepare an equal superposition of all basis states.
108108
# For :math:`n` qubits and a basis state :math:`|\mathbf{x}\rangle`, we can write the
109109
# transformation as
110110
#
@@ -123,7 +123,7 @@ def objective_function(x):
123123
# where the coefficients :math:`w_k` are carefully chosen (see Section 8.1 of [#Jordan2024]_ for more about this result).
124124
#
125125
# The DQI algorithm for solving the max-XORSAT problem involves three qubit registers: a weight, an
126-
# error, and a syndrome register, with dimensions :math:`\left \lceil \log_{2} \ell \right \rceil`,
126+
# error, and a syndrome register, with dimensions :math:`\left \lceil \log_{2} (\ell+1) \right \rceil`,
127127
# :math:`m`, and :math:`n`, respectively. The algorithm’s steps are outlined below and summarized in Figure 1.
128128
#
129129
# 1. **Embed weight coefficients:** prepare the state :math:`\sum_{k=0}^{\ell} w_k|k\rangle` in the
@@ -161,10 +161,10 @@ def objective_function(x):
161161
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162162
#
163163
# We are going to prepare the superposition :math:`\sum_{k=0}^{\ell} w_k|k\rangle`. As previously stated,
164-
# the weight register is made up of :math:`\left \lceil \log_{2} \ell \right \rceil` qubits, which means
164+
# the weight register is made up of :math:`\left \lceil \log_{2} (\ell+1) \right \rceil` qubits, which means
165165
# that the index :math:`k` is being binary encoded. The coefficients :math:`w_k` are chosen such that
166166
# they maximize the number of satisfied linear equations. These optimal weights are the components
167-
# of the principal eigenvector of an :math:`(\ell+1)\times(\ell+1)` symmetric tridiagonal matrix [#Jordan2024]_:
167+
# of the principal eigenvector of an :math:`(\ell+1)\times(\ell+1)` symmetric tridiagonal matrix:
168168
#
169169
# .. math::
170170
#
@@ -183,8 +183,7 @@ def objective_function(x):
183183
# in this step.
184184
#
185185
# In this demo, we will use a polynomial of degree :math:`2` for a reason that will become clear during
186-
# the decoding step. For now, you might be wondering if :math:`\left \lceil \log_{2} 2\right \rceil=1`
187-
# qubit will be enough to encode :math:`k=0,1,2`. Well, let’s examine what we obtain for the
186+
# the decoding step. For now, let’s examine what we obtain for the
188187
# coefficients vector :math:`\mathbf{w}`.
189188
#
190189

@@ -193,16 +192,16 @@ def objective_function(x):
193192
d = (p - 2 * r) / pnp.sqrt(r * (p - r))
194193
l = 2
195194
# Define registers
196-
num_weight_qubits = int(pnp.ceil(pnp.log2(l)))
195+
num_weight_qubits = int(pnp.ceil(pnp.log2(l+1)))
197196
weight_register = range(num_weight_qubits)
198197
m_register = range(num_weight_qubits, m + num_weight_qubits)
199198
n_register = range(m + num_weight_qubits, n + m + num_weight_qubits)
200199

201200

202201
def w_k_optimal(m, l):
203-
"""Calculates optimal weights for superposition: principal vector of tridiagonal matrix."""
202+
"""Calculates optimal weights: principal vector of tridiagonal matrix."""
204203
diag_main = pnp.diag(pnp.arange(l + 1) * d)
205-
diag_sup = pnp.diag(pnp.sqrt(pnp.arange(l) * (m - pnp.arange(l) + 1)), 1)
204+
diag_sup = pnp.diag(pnp.sqrt(pnp.arange(1, l + 1) * (m - pnp.arange(1, l + 1) + 1)), 1)
206205
A = diag_main + diag_sup + pnp.transpose(diag_sup)
207206
_, eigenvectors = pnp.linalg.eigh(A)
208207
return eigenvectors[:, -1]
@@ -213,16 +212,14 @@ def w_k_optimal(m, l):
213212

214213

215214
######################################################################
216-
# Since :math:`w_0=0`, a single qubit is sufficient to encode the remaining non-zero coefficients. The
217-
# explicit form of this state will be the uniform superposition
218-
# :math:`\frac{1}{\sqrt{2}}(|0\rangle+|1\rangle)`, which can be readily prepared by a Hadamard gate. However,
219-
# to make our code more versatile, we will use ``qml.StatePrep`` in our ``embed_weights`` function shown below.
220-
# For subsequent steps. Let’s just keep in mind that the :math:`k` values we are encoding are :math:`1` and :math:`2`.
215+
# We will need two qubits to encode :math:`w_k` for :math:`k=0,1,2`. The state is given by a weighted superposition
216+
# of the basis states :math:`|00\rangle, |01\rangle, |10\rangle`.
217+
# To prepare it, we will use ``qml.StatePrep`` in our ``embed_weights`` function shown below.
221218
#
222219

223220
def embed_weights(w_k, weight_register):
224-
"""Prepares the weight register to be in superposition given coefficients."""
225-
qml.StatePrep(w_k[1:], wires=weight_register, pad_with=0)
221+
"""Prepares the weight register in superposition of given coefficients."""
222+
qml.StatePrep(w_k, wires=weight_register, pad_with=0)
226223

227224

228225
######################################################################
@@ -313,23 +310,28 @@ def format_state_vector(state_vector, tol: float = 1e-6):
313310
def SCS(m, k):
314311
"""Implements the Split & Cycle shift unitary."""
315312

313+
# To address the correct set of wires
314+
m_angle = m
315+
m = m + num_weight_qubits - 1
316+
316317
# Two-qubit gate
317318
qml.CNOT(wires=[m - 1, m])
318-
qml.CRY(2 * pnp.arccos(pnp.sqrt(1 / m)), wires=[m, m - 1])
319+
qml.CRY(2 * pnp.arccos(pnp.sqrt(1 / m_angle)), wires=[m, m - 1])
319320
qml.CNOT(wires=[m - 1, m])
320321

321322
# k-1 three-qubit gates
322323
for l in range(2, k + 1):
323324
qml.CNOT(wires=[m - l, m])
324-
qml.ctrl(qml.RY, (m, m - l + 1))(2 * pnp.arccos(pnp.sqrt(l / m)), wires=m - l)
325+
qml.ctrl(qml.RY, (m, m - l + 1))(2 * pnp.arccos(pnp.sqrt(l / m_angle)), wires=m - l)
325326
qml.CNOT(wires=[m - l, m])
326327

327328

329+
328330
def prepare_dicke_state(m, k):
329331
"""Prepares a Dicke state with m qubits and k excitations in a inductive form."""
330332

331333
# Prepares input state
332-
for wire_idx in range(m - k + 1, m + 1):
334+
for wire_idx in range(m - k + num_weight_qubits, m + num_weight_qubits):
333335
qml.X(wires=wire_idx)
334336

335337
# Applies the SCS unitaries
@@ -350,8 +352,8 @@ def weight_error_prep(m, n, l):
350352
embed_weights(w_k, weight_register)
351353

352354
# Prepare Dicke states conditioned on k values
353-
qml.ctrl(prepare_dicke_state, (0,), control_values=0)(m, k=1)
354-
qml.ctrl(prepare_dicke_state, (0,), control_values=1)(m, k=2)
355+
qml.ctrl(prepare_dicke_state, weight_register, control_values=(0,1))(m, k=1)
356+
qml.ctrl(prepare_dicke_state, weight_register, control_values=(1,0))(m, k=2)
355357

356358
return qml.state()
357359

@@ -366,11 +368,11 @@ def weight_error_prep(m, n, l):
366368
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
367369
# After preparing the Dicke states, we uncompute and discard the state of the weight register. In
368370
# general, this is a straightforward process, as the Hamming weights encoded are known.
369-
# We accomplished this by generating bit strings of length :math:`m` with Hamming weight of
370-
# :math:`2` using the ``generate_bit_strings`` function. We then applied a controlled bit flip to the
371-
# weight register for these specific cases. We did not need to perform any action for bit strings with
372-
# a Hamming weight of :math:`1`, as the qubit state was already :math:`|0\rangle`. From now on, we can
373-
# choose to disregard the weight register.
371+
# We accomplished this by generating bit strings of length :math:`m` with Hamming weights of
372+
# :math:`1` and :math:`2` using the ``generate_bit_strings`` function. We then applied controlled bit flips to the
373+
# qubits in the :math:`1` state of the weight register. We did not need to perform any action for bit strings with
374+
# a Hamming weight of :math:`0`, as the qubit state was already :math:`|00\rangle`. From now on, we can
375+
# disregard the weight register.
374376
#
375377

376378
from itertools import combinations
@@ -393,10 +395,19 @@ def generate_bit_strings(length, hamming_weight):
393395

394396

395397
def uncompute_weight(m, k):
396-
"""Uncomputes weight register when l=2"""
397-
bit_strings = list(generate_bit_strings(m, k))
398+
"""Uncomputes weight register."""
399+
bit_strings_dicke = list(generate_bit_strings(m, k))
400+
binary_string_weight = bin(k)[2:].zfill(num_weight_qubits)
401+
398402
for i in range(comb(m, k)):
399-
qml.ctrl(qml.X, m_register, control_values=bit_strings[i])(0)
403+
for j in range(len(binary_string_weight)):
404+
bit = int(binary_string_weight[j])
405+
if bit == 1:
406+
qml.ctrl(
407+
qml.X,
408+
m_register,
409+
control_values=bit_strings_dicke[i]
410+
)(weight_register[j])
400411

401412

402413
######################################################################
@@ -405,24 +416,25 @@ def uncompute_weight(m, k):
405416
#
406417
# To impart a phase :math:`(-1)^{\mathbf{v}\cdot\mathbf{y}}`, we perform a Pauli-Z on each qubit for
407418
# which :math:`v_i=1`. This is simply a conditional operation within a
408-
# ``for`` loop in the ``phase_Z`` function. Let’s now implement this step, together with the weight
419+
# ``for`` loop in the ``phase_Z`` function. Let’s now implement this step, together with the weight register
409420
# uncomputation, in a function ``encode_v``, and output the resulting quantum state.
410421
#
411422

412423
def phase_Z(v):
413424
"""Imparts a phase (-1)^{vy}."""
414425
for i in range(len(v)):
415-
qml.cond(v[i],qml.Z)(wires=i + 1)
426+
qml.cond(v[i],qml.Z)(wires=i + num_weight_qubits)
416427

417428

418429
@qml.qnode(dev)
419430
def encode_v(m, n, l):
420431
"""Quantum circuit uncomputing weight register and encoding vector of constraints."""
421432

422433
# Load the previous state
423-
qml.StatePrep(raw_state_vector, wires=range(0, m + 1))
434+
qml.StatePrep(raw_state_vector, wires=range(0, m + num_weight_qubits))
424435

425436
# Uncompute weight register
437+
uncompute_weight(m, k=1)
426438
uncompute_weight(m, k=2)
427439

428440
# Impart phase
@@ -462,10 +474,10 @@ def B_T_multiplication(B_T, n_register):
462474

463475
@qml.qnode(dev)
464476
def syndrome_prep(m, n, l):
465-
"""Quantum circuit preparing syndrome register"""
477+
"""Quantum circuit preparing syndrome register."""
466478

467479
# Load the previous state
468-
qml.StatePrep(raw_state_vector, wires=range(0, m + 1))
480+
qml.StatePrep(raw_state_vector, wires=range(0, m + num_weight_qubits))
469481

470482
# Compute s = B^T y into the syndrome register
471483
B_T_multiplication(B_T, n_register)
@@ -551,7 +563,7 @@ def decoding(m, n, l):
551563
"""Quantum circuit decoding and uncomputing error register"""
552564

553565
# Load the previous state
554-
qml.StatePrep(raw_state_vector, wires=range(0, m + n + 1))
566+
qml.StatePrep(raw_state_vector, wires=range(0, m + n + num_weight_qubits))
555567

556568
# Uncompute syndrome register using a Lookup table
557569
for syndrome, error in decoding_table:
@@ -569,7 +581,7 @@ def decoding(m, n, l):
569581
# Hadamard transform and sample
570582
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
571583
#
572-
# After the previous step, we obtained the Hadamard transform of the state we are looking for. The final
584+
# After the previous step, we obtained the Hadamard transform of the state we were looking for. The final
573585
# step is to apply the Hadamard transform to this state to obtain
574586
# :math:`|P(f)\rangle=\sum_{\mathbf{x}} P(f(\mathbf{x}))|\mathbf{x}\rangle`.
575587
# Let's write a ``DQI`` quantum function containing all the steps of the algorithm previously described.
@@ -578,16 +590,17 @@ def decoding(m, n, l):
578590
@partial(qml.set_shots, shots=n_samples)
579591
@qml.qnode(dev)
580592
def DQI(m, n, l):
581-
"""Quantum circuit implementing DQI algorithm to solve max-XORSAT."""
593+
"""Quantum circuit implementing the DQI algorithm to solve max-XORSAT."""
582594

583595
# Prepare weight register
584-
qml.Hadamard(wires=0)
596+
embed_weights(w_k,weight_register)
585597

586598
# Prepare Dicke states conditioned on k values
587-
qml.ctrl(prepare_dicke_state, (0), control_values=0)(m, 1)
588-
qml.ctrl(prepare_dicke_state, (0), control_values=1)(m, 2)
599+
qml.ctrl(prepare_dicke_state, weight_register, control_values=(0,1))(m, k=1)
600+
qml.ctrl(prepare_dicke_state, weight_register, control_values=(1,0))(m, k=2)
589601

590602
# Uncompute weight register
603+
uncompute_weight(m, k=1)
591604
uncompute_weight(m, k=2)
592605

593606
# Impart phase
@@ -656,21 +669,27 @@ def DQI(m, n, l):
656669
# -----------
657670
#
658671
# .. [#Jordan2024]
672+
#
659673
# Stephen P. Jordan, Noah Shutty, Mary Wootters, Adam Zalcman, Alexander Schmidhuber, Robbie King, Sergei V. Isakov, Tanuj Khattar, and Ryan Babbush.
660674
# "Optimization by Decoded Quantum Interferometry.",
661675
# `<https://arxiv.org/abs/2408.08292>`__, 2024.
662676
#
663677
# .. [#Bartschi2019]
678+
#
664679
# Andreas Bärtschi, and Stephan Eidenbenz.
665680
# "Deterministic Preparation of Dicke States.",
666681
# `<https://arxiv.org/abs/1904.07358>`__, 2019.
667682
#
668683
# .. [#Patamawisut2025]
684+
#
669685
# Natchapol Patamawisut, Naphan Benchasattabuse, Michal Hajdušek, and Rodney Van Meter.
670686
# "Quantum Circuit Design for Decoded Quantum Interferometry."
671687
# `<https://arxiv.org/abs/2504.18334>`__, 2025.
672688
#
673-
# .. [#Classiq2025]
689+
# .. [#Classiq2025]
690+
#
674691
# Classiq.
675692
# “Decoded Quantum Interferometry Algorithm.”
676693
# `<https://docs.classiq.io/latest/explore/algorithms/dqi/dqi_max_xorsat/>`__, 2025.
694+
#
695+

demonstrations_v2/tutorial_dqi/metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"executable_stable": true,
99
"executable_latest": true,
1010
"dateOfPublication": "2025-09-19T10:00:00+00:00",
11-
"dateOfLastModification": "2025-09-22T15:48:14+00:00",
11+
"dateOfLastModification": "2025-11-26T15:48:14+00:00",
1212
"categories": [
1313
"Algorithms"
1414
],

0 commit comments

Comments
 (0)