Skip to content

Commit bff8e52

Browse files
Compute cut differential on CPU
1 parent c0dfa9f commit bff8e52

File tree

4 files changed

+108
-26
lines changed

4 files changed

+108
-26
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
1010

1111
[project]
1212
name = "pyqrackising"
13-
version = "9.7.8"
13+
version = "9.8.0"
1414
requires-python = ">=3.8"
1515
description = "Fast MAXCUT, TSP, and sampling heuristics from near-ideal transverse field Ising model (TFIM)"
1616
readme = {file = "README.txt", content-type = "text/markdown"}

pyqrackising/maxcut_tfim_util.py

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ def convert_bool_to_uint(samples):
278278

279279
@njit
280280
def compute_energy(sample, G_m, n_qubits):
281-
energy = 0
281+
energy = 0.0
282282
for u in range(n_qubits):
283283
u_bit = sample[u]
284284
for v in range(u + 1, n_qubits):
@@ -299,9 +299,93 @@ def compute_cut(sample, G_m, n_qubits):
299299
return cut
300300

301301

302+
@njit
303+
def compute_energy_diff(u, sample, G_m, n_qubits):
304+
energy = 0.0
305+
u_bit = sample[u]
306+
for v in range(u):
307+
val = 2 * G_m[u, v]
308+
energy += val if u_bit == sample[v] else -val
309+
for v in range(u + 1, n_qubits):
310+
val = 2 * G_m[u, v]
311+
energy += val if u_bit == sample[v] else -val
312+
313+
return -energy
314+
315+
316+
@njit
317+
def compute_cut_diff(u, sample, G_m, n_qubits):
318+
energy = 0.0
319+
u_bit = sample[u]
320+
for v in range(u):
321+
val = G_m[u, v]
322+
energy += -val if u_bit == sample[v] else val
323+
for v in range(u + 1, n_qubits):
324+
val = G_m[u, v]
325+
energy += -val if u_bit == sample[v] else val
326+
327+
return energy
328+
329+
330+
@njit
331+
def compute_energy_diff_2(k, l, sample, G_m, n_qubits):
332+
if l < k:
333+
t = k
334+
k = l
335+
l = t
336+
energy = 0.0
337+
k_bit = sample[k]
338+
l_bit = sample[l]
339+
for v in range(k):
340+
val = 2 * G_m[k, v]
341+
energy += val if k_bit == sample[v] else -val
342+
val = 2 * G_m[l, v]
343+
energy += val if l_bit == sample[v] else -val
344+
for v in range(k + 1, l):
345+
val = 2 * G_m[k, v]
346+
energy += val if k_bit == sample[v] else -val
347+
val = 2 * G_m[l, v]
348+
energy += val if l_bit == sample[v] else -val
349+
for v in range(l + 1, n_qubits):
350+
val = 2 * G_m[k, v]
351+
energy += val if k_bit == sample[v] else -val
352+
val = 2 * G_m[l, v]
353+
energy += val if l_bit == sample[v] else -val
354+
355+
return -energy
356+
357+
358+
@njit
359+
def compute_cut_diff_2(k, l, sample, G_m, n_qubits):
360+
if l < k:
361+
t = k
362+
k = l
363+
l = t
364+
energy = 0.0
365+
k_bit = sample[k]
366+
l_bit = sample[l]
367+
for v in range(k):
368+
val = G_m[k, v]
369+
energy += -val if k_bit == sample[v] else val
370+
val = G_m[l, v]
371+
energy += -val if l_bit == sample[v] else val
372+
for v in range(k + 1, l):
373+
val = G_m[k, v]
374+
energy += -val if k_bit == sample[v] else val
375+
val = G_m[l, v]
376+
energy += -val if l_bit == sample[v] else val
377+
for v in range(l + 1, n_qubits):
378+
val = G_m[k, v]
379+
energy += -val if k_bit == sample[v] else val
380+
val = G_m[l, v]
381+
energy += -val if l_bit == sample[v] else val
382+
383+
return energy
384+
385+
302386
@njit
303387
def compute_energy_sparse(sample, G_data, G_rows, G_cols, n_qubits):
304-
energy = 0
388+
energy = 0.0
305389
for u in range(n_qubits):
306390
u_bit = sample[u]
307391
for col in range(G_rows[u], G_rows[u + 1]):
@@ -327,7 +411,7 @@ def compute_cut_sparse(sample, G_data, G_rows, G_cols, n_qubits):
327411

328412
@njit
329413
def compute_energy_streaming(sample, G_func, nodes, n_qubits):
330-
energy = 0
414+
energy = 0.0
331415
for u in range(n_qubits):
332416
u_bit = sample[u]
333417
for v in range(u + 1, n_qubits):

pyqrackising/spin_glass_solver.py

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .maxcut_tfim import maxcut_tfim
2-
from .maxcut_tfim_util import compute_cut, compute_energy, get_cut, gray_code_next, gray_mutation, heuristic_threshold, int_to_bitstring, make_G_m_buf, make_best_theta_buf, make_best_theta_buf_64, opencl_context, setup_opencl
2+
from .maxcut_tfim_util import compute_cut, compute_cut_diff, compute_cut_diff_2, compute_energy, compute_energy_diff, compute_energy_diff_2, get_cut, gray_code_next, gray_mutation, heuristic_threshold, int_to_bitstring, make_G_m_buf, make_best_theta_buf, make_best_theta_buf_64, opencl_context, setup_opencl
33
import networkx as nx
44
import numpy as np
55
from numba import njit, prange
@@ -28,12 +28,12 @@ def run_single_bit_flips(best_theta, is_spin_glass, G_m):
2828
for i in prange(n):
2929
state = best_theta.copy()
3030
state[i] = not state[i]
31-
energies[i] = compute_energy(state, G_m, n)
31+
energies[i] = compute_energy_diff(i, state, G_m, n)
3232
else:
3333
for i in prange(n):
3434
state = best_theta.copy()
3535
state[i] = not state[i]
36-
energies[i] = compute_cut(state, G_m, n)
36+
energies[i] = compute_cut_diff(i, state, G_m, n)
3737

3838
best_index = np.argmax(energies)
3939
best_energy = energies[best_index]
@@ -71,7 +71,7 @@ def run_double_bit_flips(best_theta, is_spin_glass, G_m, thread_count):
7171
state[i] = not state[i]
7272
state[j] = not state[j]
7373

74-
states[t], energies[t] = state, compute_energy(state, G_m, n)
74+
states[t], energies[t] = state, compute_energy_diff_2(i, j, state, G_m, n)
7575

7676
s += thread_batch
7777
else:
@@ -93,7 +93,7 @@ def run_double_bit_flips(best_theta, is_spin_glass, G_m, thread_count):
9393
state[i] = not state[i]
9494
state[j] = not state[j]
9595

96-
states[t], energies[t] = state, compute_cut(state, G_m, n)
96+
states[t], energies[t] = state, compute_cut_diff_2(i, j, state, G_m, n)
9797

9898
s += thread_batch
9999

@@ -184,7 +184,7 @@ def run_gray_optimization(best_theta, iterators, energies, gray_iterations, thre
184184
return best_energy, best_state
185185

186186

187-
def run_bit_flips_opencl(is_double, n, kernel, best_energy, theta, theta_buf, G_m_buf, is_segmented, local_size, global_size, args_buf, local_energy_buf, local_index_buf, max_energy_host, max_index_host, max_energy_buf, max_index_buf):
187+
def run_bit_flips_opencl(is_double, n, kernel, theta, theta_buf, G_m_buf, is_segmented, local_size, global_size, args_buf, local_energy_buf, local_index_buf, max_energy_host, max_index_host, max_energy_buf, max_index_buf):
188188
queue = opencl_context.queue
189189

190190
# Set kernel args
@@ -227,9 +227,7 @@ def run_bit_flips_opencl(is_double, n, kernel, best_energy, theta, theta_buf, G_
227227

228228
if energy <= 0.0:
229229
# No improvement: we can exit early
230-
return best_energy, theta
231-
232-
energy += best_energy
230+
return 0.0, theta
233231

234232
# We need the best index
235233
queue.finish()
@@ -425,23 +423,23 @@ def spin_glass_solver(
425423
# Single bit flips with O(n^2)
426424
if is_opencl:
427425
theta_buf = make_best_theta_buf(best_theta)
428-
energy, state = run_bit_flips_opencl(False, n_qubits, single_bit_flips_kernel, max_energy, best_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
426+
energy, state = run_bit_flips_opencl(False, n_qubits, single_bit_flips_kernel, best_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
429427
else:
430428
energy, state = run_single_bit_flips(best_theta, is_spin_glass, G_m)
431-
if energy > max_energy:
432-
max_energy = energy
429+
if energy > 0.0:
430+
max_energy += energy
433431
best_theta = state
434432
improved = True
435433
continue
436434

437435
# Double bit flips with O(n^3)
438436
if is_opencl:
439437
# theta_buf has not changed
440-
energy, state = run_bit_flips_opencl(True, n_qubits, double_bit_flips_kernel, max_energy, best_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
438+
energy, state = run_bit_flips_opencl(True, n_qubits, double_bit_flips_kernel, best_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
441439
else:
442440
energy, state = run_double_bit_flips(best_theta, is_spin_glass, G_m, thread_count)
443-
if energy > max_energy:
444-
max_energy = energy
441+
if energy > 0.0:
442+
max_energy += energy
445443
best_theta = state
446444
improved = True
447445
continue
@@ -472,23 +470,23 @@ def spin_glass_solver(
472470
# Single bit flips with O(n^2)
473471
if is_opencl:
474472
theta_buf = make_best_theta_buf(reheat_theta)
475-
energy, state = run_bit_flips_opencl(False, n_qubits, single_bit_flips_kernel, max_energy, reheat_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
473+
energy, state = run_bit_flips_opencl(False, n_qubits, single_bit_flips_kernel, reheat_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
476474
else:
477475
energy, state = run_single_bit_flips(reheat_theta, is_spin_glass, G_m)
478-
if energy > max_energy:
479-
max_energy = energy
476+
if energy > 0.0:
477+
max_energy += energy
480478
best_theta = state
481479
improved = True
482480
continue
483481

484482
# Double bit flips with O(n^3)
485483
if is_opencl:
486484
# theta_buf has not changed
487-
energy, state = run_bit_flips_opencl(True, n_qubits, double_bit_flips_kernel, max_energy, reheat_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
485+
energy, state = run_bit_flips_opencl(True, n_qubits, double_bit_flips_kernel, reheat_theta, theta_buf, G_m_buf, is_segmented, *opencl_args)
488486
else:
489487
energy, state = run_double_bit_flips(reheat_theta, is_spin_glass, G_m, thread_count)
490-
if energy > max_energy:
491-
max_energy = energy
488+
if energy > 0.0:
489+
max_energy += energy
492490
best_theta = state
493491
improved = True
494492

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setup(
99
name='pyqrackising',
10-
version='9.7.8',
10+
version='9.8.0',
1111
author='Dan Strano',
1212
author_email='[email protected]',
1313
description='Fast MAXCUT, TSP, and sampling heuristics from near-ideal transverse field Ising model (TFIM)',

0 commit comments

Comments
 (0)