4949epsilon = opencl_context .epsilon
5050
5151
52- # Fixed-Hamming-weight Kawasaki swaps with acceptance probability, by Elara
52+ # ── Geometry ───────────────────────────────────────────────────────────────────
53+
5354def random_fixed_hamming_state (n_qubits , h ):
5455 bits = np .zeros (n_qubits , dtype = np .bool_ )
5556 bits [:h ] = True
@@ -59,139 +60,122 @@ def random_fixed_hamming_state(n_qubits, h):
5960 state <<= 1
6061 if b :
6162 state |= 1
62-
6363 return state
6464
6565
6666def build_neighbors (n_rows , n_cols ):
6767 L = n_rows * n_cols
6868 neighbors = [[] for _ in range (L )]
69-
7069 for i in range (n_rows ):
7170 for j in range (n_cols ):
7271 idx = i * n_cols + j
73-
7472 right = i * n_cols + ((j + 1 ) % n_cols )
75- left = i * n_cols + ((j - 1 ) % n_cols )
76- down = ((i + 1 ) % n_rows ) * n_cols + j
77- up = ((i - 1 ) % n_rows ) * n_cols + j
78-
73+ left = i * n_cols + ((j - 1 ) % n_cols )
74+ down = ((i + 1 ) % n_rows ) * n_cols + j
75+ up = ((i - 1 ) % n_rows ) * n_cols + j
7976 neighbors [idx ] = [right , left , down , up ]
80-
8177 return neighbors
8278
8379
84- def count_like_edges (state , neighbors ):
85- like = 0
80+ def count_unlike_edges (state , neighbors ):
81+ unlike = 0
8682 visited = set ()
87-
8883 for i , nbrs in enumerate (neighbors ):
8984 si = (state >> i ) & 1
9085 for j in nbrs :
9186 if (j , i ) in visited :
9287 continue
93- sj = (state >> j ) & 1
94- if si == sj :
95- like += 1
88+ if si != ((state >> j ) & 1 ):
89+ unlike += 1
9690 visited .add ((i , j ))
97-
98- return like
91+ return unlike
9992
10093
10194def delta_like_edges (state , i , j , neighbors ):
10295 si = (state >> i ) & 1
10396 sj = (state >> j ) & 1
104-
10597 delta = 0
106-
10798 for idx in [i , j ]:
10899 s_old = (state >> idx ) & 1
109-
110100 for nbr in neighbors [idx ]:
111101 if nbr == i or nbr == j :
112102 continue
113-
114- s_nbr = (state >> nbr ) & 1
115-
103+ s_nbr = (state >> nbr ) & 1
116104 old_like = s_old == s_nbr
117105 new_spin = sj if idx == i else si
118106 new_like = new_spin == s_nbr
119-
120- delta += int (new_like ) - int (old_like )
121-
122- # handle edge between i and j if neighbors
107+ delta += int (new_like ) - int (old_like )
123108 if j in neighbors [i ]:
124- old_like = si == sj
125- new_like = sj == si
126- delta += int (new_like ) - int (old_like )
127-
109+ delta += int (sj == si ) - int (si == sj )
128110 return delta
129111
130112
113+ # ── Spatial sampler: repulsive Kawasaki ───────────────────────────────────────
114+ # Electrons carry intrinsic magnetic dipole moments; antiparallel (unlike)
115+ # neighbours have lower magnetic potential energy. The acceptance criterion
116+ # therefore unconditionally favours unlike neighbours, independent of J.
117+
131118def sample_fixed_hamming_weight (h_weight , count , n_rows , n_cols , burnin = 10 ):
132119 if count == 0 :
133120 return []
134121
135- n_qubits = n_rows * n_cols
136- neighbors = build_neighbors (n_rows , n_cols )
137-
138- like_edges = 0
139- while like_edges == 0 :
140- state = random_fixed_hamming_state (n_qubits , h_weight )
141- like_edges = count_like_edges (state , neighbors )
142-
143- samples = []
144-
145- burn = burnin * n_qubits
122+ n_qubits = n_rows * n_cols
123+ neighbors = build_neighbors (n_rows , n_cols )
124+ total_edges = 2 * n_qubits # 4 neighbours per site, each edge once → 2n
125+
126+ unlike_edges = 0
127+ attempts = 0
128+ while unlike_edges == 0 :
129+ state = random_fixed_hamming_state (n_qubits , h_weight )
130+ unlike_edges = count_unlike_edges (state , neighbors )
131+ attempts += 1
132+ if attempts > 1000 :
133+ return [state ] * count
134+
135+ samples = []
136+ burn = burnin * n_qubits
146137 thinning = n_qubits
147138
148- ones = set ([ i for i in range (n_qubits ) if (state >> i ) & 1 ] )
149- zeros = set ([ i for i in range (n_qubits ) if not (state >> i ) & 1 ] )
139+ ones = set (i for i in range (n_qubits ) if (state >> i ) & 1 )
140+ zeros = set (i for i in range (n_qubits ) if not (state >> i ) & 1 )
150141
151142 for step in range (burn + thinning * count ):
152- # choose random 1-bit and 0-bit
153-
154143 i = random .choice (tuple (ones ))
155144 j = random .choice (tuple (zeros ))
156145
157- delta = delta_like_edges (state , i , j , neighbors )
158- new_like = like_edges + delta
146+ delta_like = delta_like_edges (state , i , j , neighbors )
147+ new_unlike = unlike_edges - delta_like # unlike = total - like
159148
160- if new_like > 0 :
161- accept_prob = new_like / like_edges
162- if np .random .random () < accept_prob :
149+ if new_unlike > 0 :
150+ # Repulsive: accept proportional to new_unlike / unlike_edges
151+ if np .random .random () < new_unlike / unlike_edges :
163152 state ^= 1 << i
164- ones .remove (i )
165- zeros .add (i )
166-
153+ ones .remove (i ); zeros .add (i )
167154 state ^= 1 << j
168- zeros .remove (j )
169- ones .add (j )
170-
171- like_edges = new_like
155+ zeros .remove (j ); ones .add (j )
156+ unlike_edges = new_unlike
172157
173158 if step >= burn and (step - burn ) % thinning == 0 :
174159 samples .append (state )
175160
176161 return samples
177162
178163
164+ # ── Numba helpers ──────────────────────────────────────────────────────────────
165+
179166@njit (cache = True )
180167def factor_width (width , is_transpose = False ):
181168 col_len = math .floor (math .sqrt (width ))
182169 while ((width // col_len ) * col_len ) != width :
183170 col_len -= 1
184171 row_len = width // col_len
185-
186172 return (col_len , row_len ) if is_transpose else (row_len , col_len )
187173
188174
189175@njit (cache = True )
190176def comb (n , k ):
191- if (k < 0 ) or (k > n ):
192- return 0
193- if (k == 0 ) or (k == n ):
194- return 1
177+ if (k < 0 ) or (k > n ): return 0
178+ if (k == 0 ) or (k == n ): return 1
195179 res = 1
196180 for i in range (1 , k + 1 ):
197181 res = res * (n - k + i ) // i
@@ -201,13 +185,12 @@ def comb(n, k):
201185@njit (cache = True )
202186def fix_cdf (hamming_prob ):
203187 tot_prob = 0.0
204- n_bias = len (hamming_prob )
188+ n_bias = len (hamming_prob )
205189 cum_prob = np .empty (n_bias , dtype = np .float64 )
206190 for i in range (n_bias ):
207- tot_prob += hamming_prob [i ]
208- cum_prob [i ] = tot_prob
191+ tot_prob += hamming_prob [i ]
192+ cum_prob [i ] = tot_prob
209193 cum_prob [- 1 ] = 1.0
210-
211194 return cum_prob
212195
213196
@@ -232,6 +215,8 @@ def get_tfim_hamming_distribution(J=-1.0, h=2.0, z=4, theta=0.174532925199432957
232215 return probability_by_hamming_weight (J , h , z , theta , t , n_qubits + 1 , normalized = True , omega = omega )
233216
234217
218+ # ── Public generators ──────────────────────────────────────────────────────────
219+
235220def generate_tfim_samples (J = - 1.0 , h = 2.0 , z = 4 , theta = 0.174532925199432957 , t = 5 , n_qubits = 56 , shots = 100 ):
236221 samples = []
237222
@@ -244,36 +229,34 @@ def generate_tfim_samples(J=-1.0, h=2.0, z=4, theta=0.174532925199432957, t=5, n
244229 if np .random .random () < prob :
245230 sample |= 1
246231 samples .append (sample )
247-
248232 return samples
249233
250234 n_rows , n_cols = factor_width (n_qubits )
251235
252- # First dimension : Hamming weight (envelope applied inside)
253- bias = get_tfim_hamming_distribution (J = J , h = h , z = z , theta = theta , t = t , n_qubits = n_qubits )
236+ # Dimension 1 : Hamming weight with time-dependent envelope
237+ bias = get_tfim_hamming_distribution (J = J , h = h , z = z , theta = theta , t = t , n_qubits = n_qubits )
254238 counts = np .random .multinomial (shots , bias )
255239
256240 samples += [0 ] * counts [0 ]
257241 samples += [(1 << n_qubits ) - 1 ] * counts [- 1 ]
258242 if n_qubits > 1 :
259243 samples += [int (1 << np .random .randint (n_qubits )) for _ in range (counts [1 ])]
260244 if n_qubits > 2 :
261- mask = (1 << n_qubits ) - 1
245+ mask = (1 << n_qubits ) - 1
262246 samples += [(mask ^ int (1 << np .random .randint (n_qubits ))) for _ in range (counts [- 2 ])]
263247
264- # Second dimension: magnetic localization
248+ # Dimension 2: spatial magnetic localization (repulsive dipole coupling)
265249 for h_weight in range (2 , len (bias ) - 2 ):
266250 samples += sample_fixed_hamming_weight (h_weight , counts [h_weight ], n_rows , n_cols )
267251
268252 np .random .shuffle (samples )
269-
270253 return samples
271254
272255
273256def generate_fermi_hubbard_samples (J = - 1.0 , h = 2.0 , z = 4 , theta = 0.174532925199432957 , t = 5 , n_qubits = 56 , shots = 100 , omega = 1.5 * np .pi ):
274257 shots_x , shots_y , shots_z = np .random .multinomial (shots , [1 / 3 , 1 / 3 , 1 / 3 ])
275258 return (
276- generate_tfim_samples (J = J , h = h , z = z , theta = theta , t = t , n_qubits = n_qubits , shots = shots_z )
259+ generate_tfim_samples (J = J , h = h , z = z , theta = theta , t = t , n_qubits = n_qubits , shots = shots_z )
277260 + generate_tfim_samples (J = - h , h = - J , z = z , theta = theta + np .pi / 2 , t = t , n_qubits = n_qubits , shots = shots_x )
278- + generate_tfim_samples (J = J , h = h , z = z , theta = theta + np .pi / 2 , t = t , n_qubits = n_qubits , shots = shots_y )
261+ + generate_tfim_samples (J = J , h = h , z = z , theta = theta + np .pi / 2 , t = t , n_qubits = n_qubits , shots = shots_y )
279262 )
0 commit comments