Skip to content

Commit b77d967

Browse files
Claude does not understand Gray code search at all
1 parent c55fe47 commit b77d967

2 files changed

Lines changed: 98 additions & 102 deletions

File tree

pyqrackising/maxcut_tfim_util.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,55 @@ def compute_cut_sparse(sample, G_data, G_rows, G_cols, n_qubits):
416416
return cut
417417

418418

419+
@njit(cache=True)
420+
def compute_cut_diff_sparse(u, sample, G_data, G_rows, G_cols, n_qubits):
421+
energy = 0.0
422+
u_bit = sample[u]
423+
for col in range(G_rows[u], G_rows[u + 1]):
424+
v = G_cols[col]
425+
val = G_data[col]
426+
energy += -val if u_bit == sample[v] else val
427+
428+
return energy
429+
430+
431+
@njit(cache=True)
432+
def compute_energy_diff_sparse(u, sample, G_data, G_rows, G_cols, n_qubits):
433+
energy = 0.0
434+
u_bit = sample[u]
435+
for col in range(G_rows[u], G_rows[u + 1]):
436+
v = G_cols[col]
437+
val = G_data[col]
438+
energy += -val if u_bit == sample[v] else val
439+
440+
return energy
441+
442+
443+
@njit(cache=True)
444+
def compute_cut_diff_2_sparse(k, l, sample, G_data, G_rows, G_cols, n_qubits):
445+
if l < k:
446+
t = k
447+
k = l
448+
l = t
449+
energy = 0.0
450+
k_bit = sample[k]
451+
l_bit = sample[l]
452+
for col in range(G_rows[k], G_rows[k + 1]):
453+
v = G_cols[col]
454+
if v == l:
455+
continue
456+
val = G_data[col]
457+
energy += -val if k_bit == sample[v] else val
458+
for col in range(G_rows[l], G_rows[l + 1]):
459+
v = G_cols[col]
460+
if v == k:
461+
continue
462+
val = G_data[col]
463+
energy += -val if l_bit == sample[v] else val
464+
465+
return energy
466+
467+
419468
@njit(cache=True)
420469
def compute_energy_streaming(sample, G_func, nodes, n_qubits):
421470
energy = 0.0

pyqrackising/spin_glass_solver_sparse.py

Lines changed: 49 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from .maxcut_tfim_util import (
33
compute_cut_sparse,
44
compute_energy_sparse,
5+
compute_cut_diff_sparse,
6+
compute_cut_diff_2_sparse,
57
get_cut,
68
gray_code_next,
79
gray_mutation,
@@ -35,31 +37,19 @@
3537
@njit(parallel=True, cache=True)
3638
def run_single_bit_flips(best_theta, is_spin_glass, G_data, G_rows, G_cols):
3739
n = len(best_theta)
38-
deltas = np.empty(n, dtype=dtype)
39-
40+
energies = np.empty(n, dtype=dtype)
4041
for i in prange(n):
41-
# Compute delta: walk only adjacency list of i (O(degree(i)))
42-
old_bit = best_theta[i]
43-
new_bit = not old_bit
44-
delta = dtype(0)
45-
for col in range(G_rows[i], G_rows[i + 1]):
46-
v = G_cols[col]
47-
val = G_data[col]
48-
v_bit = best_theta[v]
49-
old_cut = old_bit != v_bit
50-
new_cut = new_bit != v_bit
51-
if old_cut != new_cut:
52-
delta += val if new_cut else -val
53-
if is_spin_glass:
54-
delta *= dtype(2)
55-
deltas[i] = delta
56-
57-
best_index = np.argmax(deltas)
58-
best_delta = deltas[best_index]
42+
state = best_theta.copy()
43+
state[i] = not state[i]
44+
energies[i] = compute_cut_diff_sparse(i, state, G_data, G_rows, G_cols, n)
45+
best_index = np.argmax(energies)
46+
best_energy = energies[best_index]
47+
if is_spin_glass:
48+
best_energy *= 2.0
5949
best_state = best_theta.copy()
6050
best_state[best_index] = not best_state[best_index]
6151

62-
return best_delta, best_state
52+
return best_energy, best_state
6353

6454

6555
@njit(parallel=True, cache=True)
@@ -69,7 +59,7 @@ def run_double_bit_flips(best_theta, is_spin_glass, G_data, G_rows, G_cols, thre
6959
thread_batch = (combo_count + thread_count - 1) // thread_count
7060

7161
states = np.empty((thread_count, n), dtype=np.bool_)
72-
deltas = np.empty(thread_count, dtype=dtype)
62+
energies = np.empty(thread_count, dtype=dtype)
7363

7464
for t in prange(thread_count):
7565
s = int(t)
@@ -85,49 +75,21 @@ def run_double_bit_flips(best_theta, is_spin_glass, G_data, G_rows, G_cols, thre
8575
break
8676
j = int(c + i + 1)
8777

88-
# Delta: walk adjacency lists of i and j only
89-
old_i = best_theta[i]
90-
new_i = not old_i
91-
old_j = best_theta[j]
92-
new_j = not old_j
93-
delta = dtype(0)
94-
95-
for col in range(G_rows[i], G_rows[i + 1]):
96-
v = G_cols[col]
97-
if v == j:
98-
continue # k-l edge: both flip, XOR unchanged, zero delta
99-
val = G_data[col]
100-
v_bit = best_theta[v]
101-
if (old_i != v_bit) != (new_i != v_bit):
102-
delta += val if (new_i != v_bit) else -val
103-
104-
for col in range(G_rows[j], G_rows[j + 1]):
105-
v = G_cols[col]
106-
if v == i:
107-
continue
108-
val = G_data[col]
109-
v_bit = best_theta[v]
110-
if (old_j != v_bit) != (new_j != v_bit):
111-
delta += val if (new_j != v_bit) else -val
112-
113-
if is_spin_glass:
114-
delta *= dtype(2)
115-
116-
states[t, i] = new_i
117-
states[t, j] = new_j
118-
# copy other bits (only need to track i,j changes)
119-
for k in range(n):
120-
if k != i and k != j:
121-
states[t, k] = best_theta[k]
122-
deltas[t] = delta
78+
state = best_theta.copy()
79+
state[i] = not state[i]
80+
state[j] = not state[j]
81+
82+
states[t], energies[t] = state, compute_cut_diff_2_sparse(i, j, state, G_data, G_rows, G_cols, n)
12383

12484
s += thread_batch
12585

126-
best_index = np.argmax(deltas)
127-
best_delta = deltas[best_index]
86+
best_index = np.argmax(energies)
87+
best_energy = energies[best_index]
88+
if is_spin_glass:
89+
best_energy *= 2.0
12890
best_state = states[best_index]
12991

130-
return best_delta, best_state
92+
return best_energy, best_state
13193

13294

13395
@njit(parallel=True, cache=True)
@@ -167,50 +129,30 @@ def pick_gray_seeds(best_theta, thread_count, gray_seed_multiple, G_data, G_rows
167129

168130

169131
@njit(parallel=True, cache=True)
170-
def run_gray_optimization(
171-
best_theta,
172-
iterators,
173-
energies,
174-
gray_iterations,
175-
thread_count,
176-
is_spin_glass,
177-
G_data,
178-
G_rows,
179-
G_cols,
180-
):
132+
def run_gray_optimization(best_theta, iterators, gray_iterations, thread_count, is_spin_glass, G_data, G_rows, G_cols):
181133
n = len(best_theta)
182134
thread_iterations = (gray_iterations + thread_count - 1) // thread_count
183135
blocks = (n + 63) >> 6
136+
energies = np.empty(thread_count, dtype=dtype)
184137

185138
for i in prange(thread_count):
186139
iterator = iterators[i]
187-
best_energy = energies[i]
188140
for curr_idx in range(thread_iterations):
141+
best_energy = 0.0
189142
for block in range(blocks):
190143
flip_bit = gray_code_next(iterator, curr_idx, block << 6)
191-
# Incremental delta: walk only adjacency list of flip_bit
192-
new_bit = iterator[flip_bit]
193-
old_bit = not new_bit
194-
delta = dtype(0)
195-
for col in range(G_rows[flip_bit], G_rows[flip_bit + 1]):
196-
v = G_cols[col]
197-
val = G_data[col]
198-
v_bit = iterator[v]
199-
old_cut = old_bit != v_bit
200-
new_cut = new_bit != v_bit
201-
if old_cut != new_cut:
202-
delta += val if new_cut else -val
203-
if is_spin_glass:
204-
delta *= dtype(2)
205-
if delta > dtype(0):
206-
best_energy += delta
144+
energy = compute_cut_diff(flip_bit, iterator, G_data, G_rows, G_cols, n)
145+
if energy > 0.0:
146+
best_energy += energy
207147
else:
148+
# Revert iterator
208149
iterator[flip_bit] = not iterator[flip_bit]
209-
if best_energy > energies[i]:
210-
energies[i] = best_energy
150+
energies[i] += best_energy
211151

212152
best_index = np.argmax(energies)
213153
best_energy = energies[best_index]
154+
if is_spin_glass:
155+
best_energy *= 2.0
214156
best_state = iterators[best_index]
215157

216158
return best_energy, best_state
@@ -220,6 +162,7 @@ def run_bit_flips_opencl(
220162
is_double,
221163
n,
222164
kernel,
165+
best_energy,
223166
theta,
224167
theta_buf,
225168
G_data_buf,
@@ -276,12 +219,12 @@ def run_bit_flips_opencl(
276219
# Queue read for results we might not need
277220
cl.enqueue_copy(queue, max_index_host, max_index_buf)
278221

279-
# Find global best delta
222+
# Find global minimum
280223
best_x = np.argmax(max_energy_host)
281224
energy = max_energy_host[best_x]
282225

283226
if energy <= 0.0:
284-
# No improvement: best delta is non-positive, exit early.
227+
# No improvement: we can exit early
285228
return 0.0, theta
286229

287230
# We need the best index
@@ -360,9 +303,9 @@ def run_gray_search_opencl(
360303
best_x = np.argmax(max_energy_host)
361304
energy = max_energy_host[best_x]
362305

363-
if energy <= best_energy:
306+
if energy <= 0.0:
364307
# No improvement: we can exit early
365-
return best_energy, theta
308+
return 0.0, theta
366309

367310
# We need the best index
368311
queue.finish()
@@ -715,6 +658,7 @@ def spin_glass_solver_sparse(
715658
False,
716659
n_qubits,
717660
single_bit_flips_kernel,
661+
max_energy,
718662
best_theta,
719663
theta_buf,
720664
G_data_buf,
@@ -725,8 +669,8 @@ def spin_glass_solver_sparse(
725669
)
726670
else:
727671
energy, state = run_single_bit_flips(best_theta, is_spin_glass, G_m.data, G_m.indptr, G_m.indices)
728-
if energy > 0.0:
729-
max_energy += energy
672+
if energy > max_energy:
673+
max_energy = energy
730674
best_theta = state
731675
improved = True
732676
continue
@@ -738,6 +682,7 @@ def spin_glass_solver_sparse(
738682
True,
739683
n_qubits,
740684
double_bit_flips_kernel,
685+
max_energy,
741686
best_theta,
742687
theta_buf,
743688
G_data_buf,
@@ -748,8 +693,8 @@ def spin_glass_solver_sparse(
748693
)
749694
else:
750695
energy, state = run_double_bit_flips(best_theta, is_spin_glass, G_m.data, G_m.indptr, G_m.indices, thread_count)
751-
if energy > 0.0:
752-
max_energy += energy
696+
if energy > max_energy:
697+
max_energy = energy
753698
best_theta = state
754699
improved = True
755700
continue
@@ -815,6 +760,7 @@ def spin_glass_solver_sparse(
815760
False,
816761
n_qubits,
817762
single_bit_flips_kernel,
763+
max_energy,
818764
reheat_theta,
819765
theta_buf,
820766
G_data_buf,
@@ -825,8 +771,8 @@ def spin_glass_solver_sparse(
825771
)
826772
else:
827773
energy, state = run_single_bit_flips(reheat_theta, is_spin_glass, G_m.data, G_m.indptr, G_m.indices)
828-
if energy > 0.0:
829-
max_energy += energy
774+
if energy > max_energy:
775+
max_energy = energy
830776
best_theta = state
831777
improved = True
832778
continue
@@ -841,6 +787,7 @@ def spin_glass_solver_sparse(
841787
True,
842788
n_qubits,
843789
double_bit_flips_kernel,
790+
max_energy,
844791
reheat_theta,
845792
theta_buf,
846793
G_data_buf,
@@ -851,8 +798,8 @@ def spin_glass_solver_sparse(
851798
)
852799
else:
853800
energy, state = run_double_bit_flips(reheat_theta, is_spin_glass, G_m.data, G_m.indptr, G_m.indices, thread_count)
854-
if energy > 0.0:
855-
max_energy += energy
801+
if energy > max_energy:
802+
max_energy = energy
856803
best_theta = state
857804
improved = True
858805

0 commit comments

Comments
 (0)