22from .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 ,
3537@njit (parallel = True , cache = True )
3638def 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