harishaseebat92 commited on
Commit
db4565c
·
verified ·
1 Parent(s): bfbcc6e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +501 -1114
app.py CHANGED
@@ -1,1137 +1,524 @@
 
1
  import math
2
-
3
  import tempfile
4
-
5
  import gradio as gr
6
-
7
  import cudaq
8
-
9
  import numpy as np
10
-
11
  import cupy as cp
12
-
13
  from pathlib import Path
14
-
15
  import plotly.graph_objects as go
16
-
17
  import plotly.io as pio
18
-
19
  import imageio
20
-
21
  from scipy.spatial import Delaunay
22
 
23
-
24
-
25
  # Set Plotly engine for image export
26
-
27
  try:
28
-
29
-     pio.kaleido.scope.mathjax = None
30
-
31
  except AttributeError:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
-     pass
34
-
35
-
36
-
37
- def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, ux_input: float, uy_input: float):
38
-
39
-     """
40
-
41
-     Simulates a 2D advection-diffusion problem using a Quantum Lattice Boltzmann Method (QLBM)
42
-
43
-     and generates a GIF animation and an interactive Plotly figure with a slider for selected time steps.
44
-
45
-     """
46
-
47
-     video_length = T
48
-
49
-     simulation_fps = 0.1
50
-
51
-     frames = int(simulation_fps * video_length)
52
-
53
-     if frames == 0:
54
-
55
-         frames = 1
56
-
57
-
58
-
59
-     num_anc = 3
60
-
61
-     num_qubits_total = 2 * num_reg_qubits + num_anc
62
-
63
-     current_N = 2**num_reg_qubits
64
-
65
-     N_tot_state_vector = 2**num_qubits_total
66
-
67
-     num_ranks = 1
68
-
69
-     rank = 0
70
-
71
-     N_sub_per_rank = int(N_tot_state_vector // num_ranks)
72
-
73
-
74
-
75
-     timesteps_per_frame = 1
76
-
77
-     if frames < T and frames > 0:
78
-
79
-         timesteps_per_frame = int(T / frames)
80
-
81
-     if timesteps_per_frame == 0:
82
-
83
-         timesteps_per_frame = 1
84
-
85
-
86
-
87
-     # Initial state setup
88
-
89
-     if distribution_type == "Sine Wave (Original)":
90
-
91
-         selected_initial_state_function_raw = lambda x, y, N_val_func: \
92
-
93
-             np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
94
-
95
-             np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
96
-
97
-     elif distribution_type == "Gaussian":
98
-
99
-         selected_initial_state_function_raw = lambda x, y, N_val_func: \
100
-
101
-             np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
102
-
103
-                      (y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
104
-
105
-     elif distribution_type == "Random":
106
-
107
-         selected_initial_state_function_raw = lambda x, y, N_val_func: \
108
-
109
-             np.random.rand(N_val_func, N_val_func) * 1.5 + 0.2 if isinstance(x, int) else \
110
-
111
-             np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
112
-
113
-     else:
114
-
115
-         print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sine Wave.")
116
-
117
-         selected_initial_state_function_raw = lambda x, y, N_val_func: \
118
-
119
-             np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
120
-
121
-             np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
122
-
123
-
124
-
125
-     initial_state_func_eval = lambda x_coords, y_coords: \
126
-
127
-         selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
128
-
129
-         (y_coords < current_N).astype(int)
130
-
131
-
132
-
133
-     with tempfile.TemporaryDirectory() as tmp_npy_dir:
134
-
135
-         intermediate_folder_path = Path(tmp_npy_dir)
136
-
137
-
138
-
139
-         cudaq.set_target('nvidia', option='fp64')
140
-
141
-
142
-
143
-         @cudaq.kernel
144
-
145
-         def alloc_kernel(num_qubits_alloc: int):
146
-
147
-             qubits = cudaq.qvector(num_qubits_alloc)
148
-
149
-
150
-
151
-         from cupy.cuda.memory import MemoryPointer, UnownedMemory
152
-
153
-
154
-
155
-         def to_cupy_array(state):
156
-
157
-             tensor = state.getTensor()
158
-
159
-             pDevice = tensor.data()
160
-
161
-             sizeByte = tensor.get_num_elements() * tensor.get_element_size()
162
-
163
-             mem = UnownedMemory(pDevice, sizeByte, owner=state)
164
-
165
-             memptr_obj = MemoryPointer(mem, 0)
166
-
167
-             cupy_array_val = cp.ndarray(tensor.get_num_elements(),
168
-
169
-                                     dtype=cp.complex128,
170
-
171
-                                     memptr=memptr_obj)
172
-
173
-             return cupy_array_val
174
-
175
-
176
-
177
-         class QLBMAdvecDiffD2Q5_new:
178
-
179
-             def __init__(self, ux=0.2, uy=0.15) -> None:
180
-
181
-                 self.dim = 2
182
-
183
-                 self.ndir = 5
184
-
185
-                 self.nq_dir = math.ceil(np.log2(self.ndir))
186
-
187
-                 self.dirs = []
188
-
189
-                 for dir_int in range(self.ndir):
190
-
191
-                     dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
192
-
193
-                     self.dirs.append(dir_bin)
194
-
195
-                 self.e_unitvec = np.array([0, 1, -1, 1, -1])
196
-
197
-                 self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
198
-
199
-                 self.cs = 1 / np.sqrt(3)
200
-
201
-                 self.ux = ux
202
-
203
-                 self.uy = uy
204
-
205
-                 self.u = np.array([0, self.ux, self.ux, self.uy, self.uy])
206
-
207
-                 self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
208
-
209
-                 self.create_circuit()
210
-
211
-
212
-
213
-             def create_circuit(self):
214
-
215
-                 v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
216
-
217
-                 v = v**0.5
218
-
219
-                 v[0] += 1
220
-
221
-                 v = v / np.linalg.norm(v)
222
-
223
-                 U_prep = 2 * np.outer(v, v) - np.eye(len(v))
224
-
225
-                 cudaq.register_operation("prep_op", U_prep)
226
-
227
-
228
-
229
-                 def collisionOp(dirs_list):
230
-
231
-                     dirs_i_list_val = []
232
-
233
-                     for dir_str in dirs_list:
234
-
235
-                         dirs_i = [(int(c)) for c in dir_str]
236
-
237
-                         dirs_i_list_val += dirs_i[::-1]
238
-
239
-                     return dirs_i_list_val
240
-
241
-
242
-
243
-                 self.dirs_i_list = collisionOp(self.dirs)
244
-
245
-
246
-
247
-                 @cudaq.kernel
248
-
249
-                 def rshift(q: cudaq.qview, n: int):
250
-
251
-                     for i in range(n):
252
-
253
-                         if i == n - 1:
254
-
255
-                             x(q[n - 1 - i])
256
-
257
-                         elif i == n - 2:
258
-
259
-                             x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
260
-
261
-                         else:
262
-
263
-                             x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
264
-
265
-
266
-
267
-                 @cudaq.kernel
268
-
269
-                 def lshift(q: cudaq.qview, n: int):
270
-
271
-                     for i in range(n):
272
-
273
-                         if i == 0:
274
-
275
-                             x(q[0])
276
-
277
-                         elif i == 1:
278
-
279
-                             x.ctrl(q[0], q[1])
280
-
281
-                         else:
282
-
283
-                             x.ctrl(q[0:i], q[i])
284
-
285
-
286
-
287
-                 @cudaq.kernel
288
-
289
-                 def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
290
-
291
-                     qx = q[0:nqx]
292
-
293
-                     qy = q[nqx:nqx + nqy]
294
-
295
-                     qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
296
-
297
-                     
298
-
299
-                     idx_lqx = 2
300
-
301
-                     b_list = dirs_i_val[idx_lqx * nq_dir_val:(idx_lqx + 1) * nq_dir_val]
302
-
303
-                     for j in range(nq_dir_val):
304
-
305
-                         if b_list[j] == 0: x(qdir[j])
306
-
307
-                     cudaq.control(lshift, qdir, qx, nqx)
308
-
309
-                     for j in range(nq_dir_val):
310
-
311
-                         if b_list[j] == 0: x(qdir[j])
312
-
313
-
314
-
315
-                     idx_rqx = 1
316
-
317
-                     b_list = dirs_i_val[idx_rqx * nq_dir_val:(idx_rqx + 1) * nq_dir_val]
318
-
319
-                     for j in range(nq_dir_val):
320
-
321
-                         if b_list[j] == 0: x(qdir[j])
322
-
323
-                     cudaq.control(rshift, qdir, qx, nqx)
324
-
325
-                     for j in range(nq_dir_val):
326
-
327
-                         if b_list[j] == 0: x(qdir[j])
328
-
329
-
330
-
331
-                     idx_lqy = 4
332
-
333
-                     b_list = dirs_i_val[idx_lqy * nq_dir_val:(idx_lqy + 1) * nq_dir_val]
334
-
335
-                     for j in range(nq_dir_val):
336
-
337
-                         if b_list[j] == 0: x(qdir[j])
338
-
339
-                     cudaq.control(lshift, qdir, qy, nqy)
340
-
341
-                     for j in range(nq_dir_val):
342
-
343
-                         if b_list[j] == 0: x(qdir[j])
344
-
345
-
346
-
347
-                     idx_rqy = 3
348
-
349
-                     b_list = dirs_i_val[idx_rqy * nq_dir_val:(idx_rqy + 1) * nq_dir_val]
350
-
351
-                     for j in range(nq_dir_val):
352
-
353
-                         if b_list[j] == 0: x(qdir[j])
354
-
355
-                     cudaq.control(rshift, qdir, qy, nqy)
356
-
357
-                     for j in range(nq_dir_val):
358
-
359
-                         if b_list[j] == 0: x(qdir[j])
360
-
361
-
362
-
363
-                 @cudaq.kernel
364
-
365
-                 def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
366
-
367
-                     q = cudaq.qvector(state_arg)
368
-
369
-                     qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
370
-
371
-                     prep_op(qdir[2], qdir[1], qdir[0])
372
-
373
-                     d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
374
-
375
-                     prep_op(qdir[2], qdir[1], qdir[0])
376
-
377
-
378
-
379
-                 @cudaq.kernel
380
-
381
-                 def d2q5_tstep_wrapper_hadamard(vec_arg: list[complex], nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
382
-
383
-                     q = cudaq.qvector(vec_arg)
384
-
385
-                     qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
386
-
387
-                     qy = q[nqx:nqx + nqy]
388
-
389
-                     prep_op(qdir[2], qdir[1], qdir[0])
390
-
391
-                     d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
392
-
393
-                     prep_op(qdir[2], qdir[1], qdir[0])
394
-
395
-                     for i in range(nqy):
396
-
397
-                         h(qy[i])
398
-
399
-
400
-
401
-                 def run_timestep_func(vec_arg, hadamard=False):
402
-
403
-                     if hadamard:
404
-
405
-                         result = cudaq.get_state(d2q5_tstep_wrapper_hadamard, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
406
-
407
-                     else:
408
-
409
-                         result = cudaq.get_state(d2q5_tstep_wrapper, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
410
-
411
-                     num_nonzero_ranks = num_ranks / (2**num_anc)
412
-
413
-                     rank_slice_cupy = to_cupy_array(result)
414
-
415
-                     if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
416
-
417
-                         sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
418
-
419
-                         cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
420
-
421
-                     if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
422
-
423
-                         limit_idx = int(N_tot_state_vector / (2**num_anc))
424
-
425
-                         if limit_idx < rank_slice_cupy.size:
426
-
427
-                             rank_slice_cupy[limit_idx:] = 0
428
-
429
-       ��             return result
430
-
431
-                 self.run_timestep = run_timestep_func
432
-
433
-
434
-
435
-             def write_state(self, state_to_write, t_step_str_val):
436
-
437
-                 rank_slice_cupy = to_cupy_array(state_to_write)
438
-
439
-                 num_nonzero_ranks = num_ranks / (2**num_anc)
440
-
441
-                 if rank < num_nonzero_ranks or (rank == 0 and num_nonzero_ranks <= 0):
442
-
443
-                     save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
444
-
445
-                     with open(save_path, 'wb') as f:
446
-
447
-                         arr_to_save = None
448
-
449
-                         data_limit = N_sub_per_rank
450
-
451
-                         if num_nonzero_ranks < 1 and rank == 0:
452
-
453
-                             data_limit = int(N_tot_state_vector / (2**num_anc))
454
-
455
-                         if data_limit > 0:
456
-
457
-                             relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
458
-
459
-                         else:
460
-
461
-                             relevant_part_cupy = cp.array([], dtype=cp.float64)
462
-
463
-                         if relevant_part_cupy.size >= current_N * current_N:
464
 
465
-                             arr_flat = relevant_part_cupy[:current_N * current_N]
466
 
467
-                             if downsampling_factor > 1 and current_N > 0:
 
 
 
 
 
 
 
468
 
469
-                                 arr_reshaped = arr_flat.reshape((current_N, current_N))
470
-
471
-                                 arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor]
472
-
473
-                                 arr_to_save = arr_downsampled.flatten()
474
-
475
-                             else:
476
-
477
-                                 arr_to_save = arr_flat
478
-
479
-                         elif relevant_part_cupy.size > 0:
480
-
481
-                             if downsampling_factor > 1:
482
-
483
-                                 arr_to_save = relevant_part_cupy[::downsampling_factor]
484
-
485
-                             else:
486
-
487
-                                 arr_to_save = relevant_part_cupy
488
-
489
-                         if arr_to_save is not None and arr_to_save.size > 0:
490
-
491
-                             np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
492
-
493
-
494
-
495
-             def run_evolution(self, initial_state_arg, total_timesteps, observable=False):
496
-
497
-                 current_state_val = initial_state_arg
498
-
499
-                 for t_iter in range(total_timesteps):
500
-
501
-                     next_state_val = None
502
-
503
-                     if t_iter == total_timesteps - 1 and observable:
504
-
505
-                         next_state_val = self.run_timestep(current_state_val, True)
506
-
507
-                         self.write_state(next_state_val, str(t_iter + 1) + "_h")
508
-
509
-                     else:
510
-
511
-                         next_state_val = self.run_timestep(current_state_val)
512
-
513
-                         # Save data at specific intervals for static plots
514
-
515
-                         if t_iter == 0 or t_iter == total_timesteps // 4 or t_iter == 3 * total_timesteps // 4 or t_iter == total_timesteps - 1 or (t_iter + 1) % timesteps_per_frame == 0:
516
-
517
-                             self.write_state(next_state_val, str(t_iter + 1))
518
-
519
-                             if rank == 0:
520
-
521
-                                 print(f"Timestep: {t_iter + 1}/{total_timesteps}")
522
-
523
-                     cp.get_default_memory_pool().free_all_blocks()
524
-
525
-                     current_state_val = next_state_val
526
-
527
-                 if rank == 0:
528
-
529
-                     print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
530
-
531
-                 cp.get_default_memory_pool().free_all_blocks()
532
-
533
-                 self.final_state = current_state_val
534
-
535
-
536
-
537
-         downsampling_factor = 2**5
538
-
539
-     ��   if current_N == 0:
540
-
541
-             print("Error: current_N is zero. num_reg_qubits likely too small.")
542
-
543
-             return None, None
544
-
545
-         if current_N < downsampling_factor:
546
-
547
-             downsampling_factor = current_N if current_N > 0 else 1
548
-
549
-
550
-
551
-         qlbm_obj = QLBMAdvecDiffD2Q5_new(ux=ux_input, uy=uy_input)
552
-
553
-         initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
554
-
555
-         
556
-
557
-         xv_init = np.arange(current_N)
558
-
559
-         yv_init = np.arange(current_N)
560
-
561
-         initial_grid_2d_X, initial_grid_2d_Y = np.meshgrid(xv_init, yv_init)
562
-
563
-
564
-
565
-         if distribution_type == "Random":
566
-
567
-             initial_grid_2d = selected_initial_state_function_raw(current_N, current_N, current_N)
568
-
569
-         else:
570
-
571
-             initial_grid_2d = initial_state_func_eval(initial_grid_2d_X, initial_grid_2d_Y)
572
-
573
-
574
-
575
-         sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
576
-
577
-         full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
578
-
579
-         num_computational_states = current_N * current_N
580
-
581
-
582
-
583
-         if len(sub_sv_init_flat) == num_computational_states:
584
-
585
-             if num_computational_states <= N_sub_per_rank:
586
-
587
-                 full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
588
-
589
-             else:
590
-
591
-                 print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
592
-
593
-                 return None, None
594
-
595
-         else:
596
-
597
-             print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
598
-
599
-             fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
600
-
601
-             full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
602
-
603
-         
604
-
605
-         rank_slice_init = to_cupy_array(initial_state_val)
606
-
607
-         print(f'Rank {rank}: Initializing state with {distribution_type} (ux={ux_input}, uy={uy_input})...')
608
-
609
-         cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
610
-
611
-         print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
612
-
613
-
614
-
615
-         print("Starting QLBM evolution...")
616
-
617
-         qlbm_obj.run_evolution(initial_state_val, T)
618
-
619
-         print("QLBM evolution complete.")
620
-
621
-
622
-
623
-         print("Generating animation and plots with Plotly...")
624
-
625
-         downsampled_N = current_N // downsampling_factor
626
-
627
-         if downsampled_N == 0 and current_N > 0:
628
-
629
-             downsampled_N = 1
630
-
631
-         elif current_N == 0:
632
-
633
-             print("Error: current_N is zero before Plotly stage.")
634
-
635
-             return None, None
636
-
637
-
638
-
639
-         # Load data for specific time steps for static plots
640
-
641
-         time_steps = [0, T//4, 3*T//4, T]
642
-
643
-         data_frames = []
644
-
645
-         actual_timesteps = []
646
-
647
-         for t in time_steps:
648
-
649
-             file_path = intermediate_folder_path / f"{t}_{rank}.npy"
650
-
651
-             if file_path.exists():
652
-
653
-                 sol_loaded = np.load(file_path)
654
-
655
-                 if sol_loaded.size == downsampled_N * downsampled_N:
656
-
657
-                     Z_data = np.reshape(sol_loaded, (downsampled_N, downsampled_N))
658
-
659
-                     data_frames.append(Z_data)
660
-
661
-                     actual_timesteps.append(t)
662
-
663
-                 else:
664
-
665
-                     print(f"Warning: File {file_path} size {sol_loaded.size} != expected {downsampled_N*downsampled_N}. Skipping.")
666
-
667
-             else:
668
-
669
-                 print(f"Warning: File {file_path} not found. Skipping.")
670
-
671
-
672
-
673
-         if not data_frames:
674
-
675
-             print("Error: No data frames loaded for static plots.")
676
-
677
-             return None, None
678
-
679
-
680
-
681
-         x_coords_plot = np.linspace(-10, 10, downsampled_N)
682
-
683
-         y_coords_plot = np.linspace(-10, 10, downsampled_N)
684
-
685
-
686
-
687
-         # Calculate global min/max for consistent scaling
688
-
689
-         z_min = min([np.min(Z) for Z in data_frames])
690
-
691
-         z_max = max([np.max(Z) for Z in data_frames])
692
-
693
-         if z_max == z_min:
694
-
695
-             z_max += 1e-9
696
-
697
-
698
-
699
-         # Create interactive Plotly figure with slider
700
-
701
-         fig = go.Figure()
702
-
703
-
704
-
705
-         for i, Z in enumerate(data_frames):
706
-
707
-             fig.add_trace(
708
-
709
-                 go.Surface(
710
-
711
-                     z=Z, x=x_coords_plot, y=y_coords_plot,
712
-
713
-                     colorscale='Viridis',
714
-
715
-                     cmin=z_min, cmax=z_max,
716
-
717
-                     name=f'Time: {actual_timesteps[i]}',
718
-
719
-                     showscale=(i == 0)
720
-
721
-                 )
722
-
723
-             )
724
-
725
-
726
-
727
-         steps = []
728
-
729
-         for i in range(len(data_frames)):
730
-
731
-             step = dict(
732
-
733
-                 method="update",
734
-
735
-                 args=[{"visible": [False] * len(data_frames)}],
736
-
737
-                 label=f"Time: {actual_timesteps[i]}"
738
-
739
-             )
740
-
741
-             step["args"][0]["visible"][i] = True
742
-
743
-             steps.append(step)
744
-
745
-
746
-
747
-         sliders = [dict(active=0, currentvalue={"prefix": "Time: "}, pad={"t": 50}, steps=steps)]
748
-
749
-
750
-
751
-         fig.update_layout(
752
-
753
-             title='QLBM Simulation - Density Evolution',
754
-
755
-             scene=dict(
756
-
757
-                 xaxis_title='X',
758
-
759
-                 yaxis_title='Y',
760
-
761
-                 zaxis_title='Density',
762
-
763
-                 xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
764
-
765
-                 yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
766
-
767
-                 zaxis=dict(range=[z_min, z_max]),
768
-
769
-             ),
770
-
771
-             sliders=sliders,
772
-
773
-             width=800,
774
-
775
-             height=700
776
-
777
-         )
778
-
779
-
780
-
781
-         # Generate GIF (unchanged from original)
782
-
783
-         plotted_timesteps_str = sorted(list(set([str(t) for t in range(0, T + 1, timesteps_per_frame) if (intermediate_folder_path / f"{t}_{rank}.npy").exists()] + ['0'] if T == 0 else [])), key=lambda k_str: int(k_str))
784
-
785
-         data_frames_list = []
786
-
787
-         actual_timesteps_for_title = []
788
-
789
-         for i_str_val in plotted_timesteps_str:
790
-
791
-             file_path_load = intermediate_folder_path / f"{i_str_val}_{rank}.npy"
792
-
793
-             if file_path_load.exists():
794
-
795
-                 sol_loaded = np.load(file_path_load)
796
-
797
-                 if sol_loaded.size == downsampled_N * downsampled_N:
798
-
799
-                     Z_data = np.reshape(sol_loaded, (downsampled_N, downsampled_N))
800
-
801
-                     data_frames_list.append(Z_data)
802
-
803
-                     actual_timesteps_for_title.append(i_str_val)
804
-
805
-
806
-
807
-         if not data_frames_list:
808
-
809
-             print("Error: No data frames loaded for GIF.")
810
-
811
-             return None, None
812
-
813
-
814
-
815
-         norm_factor_plotly = np.max(np.abs(data_frames_list[0])) if data_frames_list[0].size > 0 else 1.0
816
-
817
-         all_data_max_val = max([np.max(np.abs(d_frame)) for d_frame in data_frames_list]) if data_frames_list else norm_factor_plotly
818
-
819
-         clim_upper_plotly = (all_data_max_val / norm_factor_plotly) * 1.0 or 0.6
820
-
821
-
822
-
823
-         results_base_dir = "Results"
824
-
825
-         gif_frames_for_naming = int(simulation_fps * T) or 1
826
-
827
-         dist_name_part = distribution_type.replace(' ','').replace('(Original)','').replace('(','').replace(')','')
828
-
829
-         specific_folder_name = f"d2q5_plotly_N{current_N}_T{T}_fr{gif_frames_for_naming}_dist{dist_name_part}_ux{ux_input:.2f}_uy{uy_input:.2f}"
830
-
831
-         final_output_dir = Path(results_base_dir) / specific_folder_name
832
-
833
-         os.makedirs(final_output_dir, exist_ok=True)
834
-
835
-         gif_path_to_return = final_output_dir / "animation_plotly.gif"
836
-
837
-
838
-
839
-         Z_initial_placeholder = np.zeros((downsampled_N, downsampled_N))
840
-
841
-         gif_fig = go.Figure(data=[go.Surface(
842
-
843
-             z=Z_initial_placeholder, x=x_coords_plot, y=y_coords_plot,
844
-
845
-             colorscale='Viridis', cmin=0, cmax=clim_upper_plotly,
846
-
847
-             colorbar=dict(title=dict(text='Density', side='right', font=dict(color='white')),
848
-
849
-                           tickfont=dict(size=10, color='white'), bgcolor='rgba(0,0,0,0.4)',
850
-
851
-                           bordercolor='gray', outlinewidth=1, thickness=20, len=0.8, x=0.85, y=0.5))])
852
-
853
-         
854
-
855
-         gif_fig.update_layout(
856
-
857
-             paper_bgcolor='black', plot_bgcolor='black', font=dict(color='white'),
858
-
859
-             scene=dict(
860
-
861
-                 bgcolor='black',
862
-
863
-                 xaxis=dict(title=dict(text='X Axis', font=dict(color='white')), tickfont=dict(color='white'),
864
-
865
-                            gridcolor='rgba(128,128,128,0.3)', zerolinecolor='rgba(128,128,128,0.5)', linecolor='rgba(128,128,128,0.5)'),
866
-
867
-                 yaxis=dict(title=dict(text='Y Axis', font=dict(color='white')), tickfont=dict(color='white'),
868
-
869
-                            gridcolor='rgba(128,128,128,0.3)', zerolinecolor='rgba(128,128,128,0.5)', linecolor='rgba(128,128,128,0.5)'),
870
-
871
-                 zaxis=dict(title=dict(text='Density', font=dict(color='white')), tickfont=dict(color='white'),
872
-
873
-                            range=[0, clim_upper_plotly], gridcolor='rgba(128,128,128,0.3)',
874
-
875
-                            zerolinecolor='rgba(128,128,128,0.5)', linecolor='rgba(128,128,128,0.5)'),
876
-
877
-                 aspectratio=dict(x=1, y=1, z=0.7),
878
-
879
-                 camera=dict(eye=dict(x=1.5, y=1.5, z=1.0), center=dict(x=0, y=0, z=-0.1), up=dict(x=0,y=0,z=1))
880
-
881
-             ),
882
-
883
-             margin=dict(l=10, r=10, b=10, t=60), width=800, height=700
884
-
885
-         )
886
-
887
-
888
-
889
-         temp_image_files = []
890
-
891
-         gif_fps_val = 10
892
-
893
-
894
-
895
-         for k, Z_frame_data in enumerate(data_frames_list):
896
-
897
-             Z_plotly_frame = Z_frame_data / norm_factor_plotly
898
-
899
-             gif_fig.data[0].z = Z_plotly_frame
900
-
901
-             current_ts_title = actual_timesteps_for_title[k] if k < len(actual_timesteps_for_title) else "Unknown"
902
-
903
-             gif_fig.update_layout(
904
-
905
-                 title_text=f'QLBM Simulation (Plotly) - Timestep: {current_ts_title}',
906
-
907
-                 title_x=0.5, title_font_size=16
908
-
909
-             )
910
-
911
-             temp_image_path = intermediate_folder_path / f"plotly_frame_{k:04d}.png"
912
-
913
-             gif_fig.write_image(str(temp_image_path), engine="kaleido")
914
-
915
-             temp_image_files.append(str(temp_image_path))
916
-
917
-
918
-
919
-         if temp_image_files:
920
-
921
-             with imageio.get_writer(str(gif_path_to_return), mode='I', fps=gif_fps_val, loop=0) as writer:
922
-
923
-                 for filename in temp_image_files:
924
-
925
-                     image = imageio.imread(filename)
926
-
927
-                     writer.append_data(image)
928
-
929
-             print(f"Plotly animation saved to {gif_path_to_return}")
930
-
931
-         else:
932
-
933
-             print("Error: No Plotly frames generated for GIF.")
934
-
935
-             return None, None
936
-
937
-
938
-
939
-         return str(gif_path_to_return), fig
940
-
941
-
942
-
943
- # Gradio Interface Definition
944
-
945
- def qlbm_gradio_interface(num_reg_qubits_input: int, timescale_input: int, distribution_type_param: str, ux_param: float, uy_param: float):
946
-
947
-     num_reg_qubits_val = int(num_reg_qubits_input)
948
-
949
-     timescale_val = int(timescale_input)
950
-
951
-     ux_val = float(ux_param)
952
-
953
-     uy_val = float(uy_param)
954
-
955
-
956
-
957
-     print(f"Gradio Interface: num_reg_qubits={num_reg_qubits_val}, T={timescale_val}, Distribution={distribution_type_param}, ux={ux_val}, uy={uy_val}")
958
-
959
-
960
-
961
-     gif_path, plot_fig = simulate_qlbm_and_animate(
962
-
963
-         num_reg_qubits=num_reg_qubits_val,
964
-
965
-         T=timescale_val,
966
-
967
-         distribution_type=distribution_type_param,
968
-
969
-         ux_input=ux_val,
970
-
971
-         uy_input=uy_val
972
-
973
-     )
974
-
975
-
976
-
977
-     if gif_path is None or plot_fig is None:
978
-
979
-         gr.Warning("Simulation or plotting failed. Please check console for errors.")
980
-
981
-         return None, None
982
-
983
-     return gif_path, plot_fig
984
-
985
-
986
-
987
- with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Simulation with Plotly") as qlbm_demo:
988
-
989
-     gr.Markdown(
990
-
991
-     """
992
-
993
-     # ⚛️ Quantum Lattice Boltzmann Method (QLBM) Simulator (Plotly Animation)
994
-
995
-     Welcome to the Quantum Lattice Boltzmann Method (QLBM) simulator! This version uses Plotly for 3D animation and interactive plots.
996
-
997
-
998
-
999
-     **How this Simulator Works:**
1000
-
1001
-     This simulator implements a D2Q5 model on a quantum computer simulator (CUDA-Q).
1002
-
1003
-     - Control grid size (via Number of Register Qubits: $N=2^{\text{num_reg_qubits}}$).
1004
-
1005
-     - Set total simulation time (Timescale T).
1006
-
1007
-     - Choose initial distribution.
1008
-
1009
-     - Set advection velocities `ux` and `uy`.
1010
-
1011
-     The simulation generates a GIF animation and an interactive Plotly figure with a slider for selected time steps.
1012
-
1013
-
1014
-
1015
-     **Note:** Higher qubit counts and longer timescales are computationally intensive. Advection velocities should be small (e.g., < 0.3).
1016
-
1017
-     The output GIF loops indefinitely, and the Plotly figure allows interactive exploration of specific time steps.
1018
-
1019
-     """
1020
-
1021
-     )
1022
-
1023
-     with gr.Row():
1024
-
1025
-         with gr.Column(scale=1):
1026
-
1027
-             gr.Markdown("## Simulation Parameters")
1028
-
1029
-             num_reg_qubits_slider = gr.Slider(
1030
-
1031
-                 minimum=2, maximum=10, value=8, step=1,
1032
-
1033
-                 label="Number of Register Qubits (num_reg_qubits)",
1034
-
1035
-                 info="Grid N = 2^num_reg_qubits. Max 10 (Note: >8 slow; >9 may hit simulator/memory limits on free tiers)."
1036
-
1037
-             )
1038
-
1039
-             timescale_slider = gr.Slider(
1040
-
1041
-                 minimum=0, maximum=2000, value=100, step=10,
1042
-
1043
-                 label="Timescale (T)", info="Total number of timesteps. Max 2000."
1044
-
1045
-             )
1046
-
1047
-             distribution_options = ["Sine Wave (Original)", "Gaussian", "Random"]
1048
-
1049
-             distribution_type_input = gr.Radio(
1050
-
1051
-                 choices=distribution_options, value="Sine Wave (Original)",
1052
-
1053
-                 label="Initial Distribution Type", info="Select the initial pattern of the substance."
1054
-
1055
-             )
1056
-
1057
-             ux_slider = gr.Slider(
1058
-
1059
-                 minimum=-0.4, maximum=0.4, value=0.2, step=0.01,
1060
-
1061
-                 label="Advection Velocity ux", info="x-component of background advection."
1062
-
1063
-             )
1064
-
1065
-             uy_slider = gr.Slider(
1066
-
1067
-     ��           minimum=-0.4, maximum=0.4, value=0.15, step=0.01,
1068
-
1069
-                 label="Advection Velocity uy", info="y-component of background advection."
1070
-
1071
-             )
1072
-
1073
-             run_qlbm_btn = gr.Button("Run QLBM Simulation", variant="primary")
1074
-
1075
-
1076
-
1077
-         with gr.Column(scale=2):
1078
-
1079
-             qlbm_plot_output = gr.Image(label="QLBM Simulation Animation (GIF)", type="filepath", height=600)
1080
-
1081
-             qlbm_interactive_plot = gr.Plot(label="Interactive Density Plot with Slider")
1082
-
1083
-
1084
-
1085
-     qlbm_inputs_list = [num_reg_qubits_slider, timescale_slider, distribution_type_input, ux_slider, uy_slider]
1086
-
1087
-     run_qlbm_btn.click(
1088
-
1089
-         fn=qlbm_gradio_interface,
1090
-
1091
-         inputs=qlbm_inputs_list,
1092
-
1093
-         outputs=[qlbm_plot_output, qlbm_interactive_plot]
1094
-
1095
-     )
1096
-
1097
-     gr.Examples(
1098
-
1099
-         examples=[
1100
-
1101
-             [8, 100, "Sine Wave (Original)", 0.2, 0.15],
1102
-
1103
-             [6, 50, "Gaussian", 0.1, 0.05],
1104
-
1105
-             [4, 30, "Random", -0.05, 0.1],
1106
-
1107
-         ],
1108
-
1109
-         inputs=qlbm_inputs_list,
1110
-
1111
-         outputs=[qlbm_plot_output, qlbm_interactive_plot],
1112
-
1113
-         fn=qlbm_gradio_interface,
1114
-
1115
-         cache_examples=False
1116
-
1117
-     )
1118
 
 
1119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1120
 
1121
  if __name__ == "__main__":
1122
-
1123
-     try:
1124
-
1125
-         cudaq.set_target('nvidia', option='fp64')
1126
-
1127
-         print(f"CUDA-Q Target successfully set to: {cudaq.get_target().name}")
1128
-
1129
-     except Exception as e_target:
1130
-
1131
-         print(f"Warning: Could not set CUDA-Q target to 'nvidia'. Error: {e_target}")
1132
-
1133
-         print(f"Current CUDA-Q Target: {cudaq.get_target().name}. Performance may be affected.")
1134
-
1135
-     
1136
-
1137
-     qlbm_demo.launch()
 
1
+ import os
2
  import math
 
3
  import tempfile
 
4
  import gradio as gr
 
5
  import cudaq
 
6
  import numpy as np
 
7
  import cupy as cp
 
8
  from pathlib import Path
 
9
  import plotly.graph_objects as go
 
10
  import plotly.io as pio
 
11
  import imageio
 
12
  from scipy.spatial import Delaunay
13
 
 
 
14
  # Set Plotly engine for image export
 
15
  try:
16
+ pio.kaleido.scope.mathjax = None
 
 
17
  except AttributeError:
18
+ pass
19
+
20
+ def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, ux_input: float, uy_input: float, velocity_field_type: str):
21
+ """
22
+ Simulates a 2D advection-diffusion problem using a Quantum Lattice Boltzmann Method (QLBM)
23
+ and generates an interactive Plotly figure with a slider for selected time steps.
24
+ """
25
+
26
+ num_anc = 3
27
+ num_qubits_total = 2 * num_reg_qubits + num_anc
28
+ current_N = 2**num_reg_qubits
29
+ N_tot_state_vector = 2**num_qubits_total
30
+ num_ranks = 1
31
+ rank = 0
32
+ N_sub_per_rank = int(N_tot_state_vector // num_ranks)
33
+
34
+ # Initial state setup
35
+ if distribution_type == "Sine Wave (Original)":
36
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
37
+ np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
38
+ np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
39
+ elif distribution_type == "Gaussian":
40
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
41
+ np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
42
+ (y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
43
+ elif distribution_type == "Random":
44
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
45
+ np.random.rand(N_val_func, N_val_func) * 1.5 + 0.2 if isinstance(x, int) else \
46
+ np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
47
+ else:
48
+ print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sine Wave.")
49
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
50
+ np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
51
+ np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
52
+
53
+ initial_state_func_eval = lambda x_coords, y_coords: \
54
+ selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
55
+ (y_coords < current_N).astype(int)
56
+
57
+ with tempfile.TemporaryDirectory() as tmp_npy_dir:
58
+ intermediate_folder_path = Path(tmp_npy_dir)
59
+ cudaq.set_target('nvidia', option='fp64')
60
+
61
+ @cudaq.kernel
62
+ def alloc_kernel(num_qubits_alloc: int):
63
+ qubits = cudaq.qvector(num_qubits_alloc)
64
+
65
+ from cupy.cuda.memory import MemoryPointer, UnownedMemory
66
+
67
+ def to_cupy_array(state):
68
+ tensor = state.getTensor()
69
+ pDevice = tensor.data()
70
+ sizeByte = tensor.get_num_elements() * tensor.get_element_size()
71
+ mem = UnownedMemory(pDevice, sizeByte, owner=state)
72
+ memptr_obj = MemoryPointer(mem, 0)
73
+ cupy_array_val = cp.ndarray(tensor.get_num_elements(),
74
+ dtype=cp.complex128,
75
+ memptr=memptr_obj)
76
+ return cupy_array_val
77
+
78
+ class QLBMAdvecDiffD2Q5_new:
79
+ def __init__(self, ux=0.2, uy=0.15) -> None:
80
+ self.dim = 2
81
+ self.ndir = 5
82
+ self.nq_dir = math.ceil(np.log2(self.ndir))
83
+ self.dirs = []
84
+ for dir_int in range(self.ndir):
85
+ dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
86
+ self.dirs.append(dir_bin)
87
+
88
+ self.e_unitvec = np.array([0, 1, -1, 1, -1])
89
+ self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
90
+ self.cs = 1 / np.sqrt(3)
91
+ self.ux = ux
92
+ self.uy = uy
93
+ self.u = np.array([0, self.ux, self.ux, self.uy, self.uy])
94
+ self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
95
+ self.create_circuit()
96
+
97
+ def create_circuit(self):
98
+ v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
99
+ v = v**0.5
100
+ v[0] += 1 # This was missing in your original code
101
+ v = v / np.linalg.norm(v)
102
+
103
+ U_prep = 2 * np.outer(v, v) - np.eye(len(v))
104
+ cudaq.register_operation("prep_op", U_prep)
105
+
106
+ def collisionOp(dirs_list):
107
+ dirs_i_list_val = []
108
+ for dir_str in dirs_list:
109
+ dirs_i = [int(c) for c in dir_str]
110
+ dirs_i_list_val += dirs_i[::-1]
111
+ return dirs_i_list_val
112
+
113
+ self.dirs_i_list = collisionOp(self.dirs)
114
+
115
+ @cudaq.kernel
116
+ def rshift(q: cudaq.qview, n: int):
117
+ for i in range(n):
118
+ if i == n - 1:
119
+ x(q[n - 1 - i])
120
+ elif i == n - 2:
121
+ x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
122
+ else:
123
+ x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
124
+
125
+ @cudaq.kernel
126
+ def lshift(q: cudaq.qview, n: int):
127
+ for i in range(n):
128
+ if i == 0:
129
+ x(q[0])
130
+ elif i == 1:
131
+ x.ctrl(q[0], q[1])
132
+ else:
133
+ x.ctrl(q[0:i], q[i])
134
+
135
+ @cudaq.kernel
136
+ def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
137
+ qx = q[0:nqx]
138
+ qy = q[nqx:nqx + nqy]
139
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
140
+
141
+ idx_lqx = 2
142
+ b_list = dirs_i_val[idx_lqx * nq_dir_val:(idx_lqx + 1) * nq_dir_val]
143
+ for j in range(nq_dir_val):
144
+ if b_list[j] == 0: x(qdir[j])
145
+ cudaq.control(lshift, qdir, qx, nqx)
146
+ for j in range(nq_dir_val):
147
+ if b_list[j] == 0: x(qdir[j])
148
+
149
+ idx_rqx = 1
150
+ b_list = dirs_i_val[idx_rqx * nq_dir_val:(idx_rqx + 1) * nq_dir_val]
151
+ for j in range(nq_dir_val):
152
+ if b_list[j] == 0: x(qdir[j])
153
+ cudaq.control(rshift, qdir, qx, nqx)
154
+ for j in range(nq_dir_val):
155
+ if b_list[j] == 0: x(qdir[j])
156
+
157
+ idx_lqy = 4
158
+ b_list = dirs_i_val[idx_lqy * nq_dir_val:(idx_lqy + 1) * nq_dir_val]
159
+ for j in range(nq_dir_val):
160
+ if b_list[j] == 0: x(qdir[j])
161
+ cudaq.control(lshift, qdir, qy, nqy)
162
+ for j in range(nq_dir_val):
163
+ if b_list[j] == 0: x(qdir[j])
164
+
165
+ idx_rqy = 3
166
+ b_list = dirs_i_val[idx_rqy * nq_dir_val:(idx_rqy + 1) * nq_dir_val]
167
+ for j in range(nq_dir_val):
168
+ if b_list[j] == 0: x(qdir[j])
169
+ cudaq.control(rshift, qdir, qy, nqy)
170
+ for j in range(nq_dir_val):
171
+ if b_list[j] == 0: x(qdir[j])
172
+
173
+ @cudaq.kernel
174
+ def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
175
+ q = cudaq.qvector(state_arg)
176
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
177
+ prep_op(qdir[2], qdir[1], qdir[0]) # Uncommented
178
+ d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
179
+ prep_op(qdir[2], qdir[1], qdir[0]) # Uncommented
180
+
181
+ @cudaq.kernel
182
+ def d2q5_tstep_wrapper_hadamard(vec_arg: list[complex], nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
183
+ q = cudaq.qvector(vec_arg)
184
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
185
+ qy = q[nqx:nqx + nqy]
186
+ prep_op(qdir[2], qdir[1], qdir[0]) # Uncommented
187
+ d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
188
+ prep_op(qdir[2], qdir[1], qdir[0]) # Uncommented
189
+ for i in range(nqy):
190
+ h(qy[i])
191
+
192
+ def run_timestep_func(vec_arg, hadamard=False):
193
+ if hadamard:
194
+ result = cudaq.get_state(d2q5_tstep_wrapper_hadamard, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
195
+ else:
196
+ result = cudaq.get_state(d2q5_tstep_wrapper, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
197
+
198
+ num_nonzero_ranks = num_ranks / (2**num_anc)
199
+ rank_slice_cupy = to_cupy_array(result)
200
+
201
+ if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
202
+ sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
203
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
204
+
205
+ if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
206
+ limit_idx = int(N_tot_state_vector / (2**num_anc))
207
+ if limit_idx < rank_slice_cupy.size:
208
+ rank_slice_cupy[limit_idx:] = 0
209
+
210
+ return result
211
+
212
+ self.run_timestep = run_timestep_func
213
+
214
+ def write_state(self, state_to_write, t_step_str_val):
215
+ rank_slice_cupy = to_cupy_array(state_to_write)
216
+ num_nonzero_ranks = num_ranks / (2**num_anc)
217
+
218
+ if rank < num_nonzero_ranks or (rank == 0 and num_nonzero_ranks <= 0):
219
+ save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
220
+ with open(save_path, 'wb') as f:
221
+ arr_to_save = None
222
+ data_limit = N_sub_per_rank
223
+ if num_nonzero_ranks < 1 and rank == 0:
224
+ data_limit = int(N_tot_state_vector / (2**num_anc))
225
+
226
+ if data_limit > 0:
227
+ relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
228
+ else:
229
+ relevant_part_cupy = cp.array([], dtype=cp.float64)
230
+
231
+ if relevant_part_cupy.size >= current_N * current_N:
232
+ arr_flat = relevant_part_cupy[:current_N * current_N]
233
+ if downsampling_factor > 1 and current_N > 0:
234
+ arr_reshaped = arr_flat.reshape((current_N, current_N))
235
+ arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor]
236
+ arr_to_save = arr_downsampled.flatten()
237
+ else:
238
+ arr_to_save = arr_flat
239
+ elif relevant_part_cupy.size > 0:
240
+ if downsampling_factor > 1:
241
+ arr_to_save = relevant_part_cupy[::downsampling_factor]
242
+ else:
243
+ arr_to_save = relevant_part_cupy
244
+
245
+ if arr_to_save is not None and arr_to_save.size > 0:
246
+ np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
247
+
248
+ def run_evolution(self, initial_state_arg, total_timesteps, observable=False):
249
+ current_state_val = initial_state_arg
250
+ self.write_state(current_state_val, '0') # Save initial state
251
+
252
+ # Save data at regular intervals for better slider functionality
253
+ save_interval = max(1, total_timesteps // 10) # Save every 10% of simulation
254
+
255
+ for t_iter in range(total_timesteps):
256
+ next_state_val = None
257
+ if t_iter == total_timesteps - 1 and observable:
258
+ next_state_val = self.run_timestep(current_state_val, True)
259
+ self.write_state(next_state_val, str(t_iter + 1) + "_h")
260
+ else:
261
+ next_state_val = self.run_timestep(current_state_val)
262
+
263
+ # Save at regular intervals AND at specific timesteps
264
+ if (t_iter + 1) % save_interval == 0 or t_iter + 1 in [total_timesteps//4, 3*total_timesteps//4, total_timesteps]:
265
+ self.write_state(next_state_val, str(t_iter + 1))
266
+
267
+ if rank == 0 and (t_iter + 1) % 10 == 0:
268
+ print(f"Timestep: {t_iter + 1}/{total_timesteps}")
269
+
270
+ cp.get_default_memory_pool().free_all_blocks()
271
+ current_state_val = next_state_val
272
+
273
+ if rank == 0:
274
+ print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
275
+ cp.get_default_memory_pool().free_all_blocks()
276
+ self.final_state = current_state_val
277
+
278
+ downsampling_factor = 2**5
279
+ if current_N == 0:
280
+ print("Error: current_N is zero. num_reg_qubits likely too small.")
281
+ return None
282
+
283
+ if current_N < downsampling_factor:
284
+ downsampling_factor = current_N if current_N > 0 else 1
285
+
286
+ qlbm_obj = QLBMAdvecDiffD2Q5_new(ux=ux_input, uy=uy_input)
287
+
288
+ initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
289
+
290
+ xv_init = np.arange(current_N)
291
+ yv_init = np.arange(current_N)
292
+ initial_grid_2d_X, initial_grid_2d_Y = np.meshgrid(xv_init, yv_init)
293
+
294
+ if distribution_type == "Random":
295
+ initial_grid_2d = selected_initial_state_function_raw(current_N, current_N, current_N)
296
+ else:
297
+ initial_grid_2d = initial_state_func_eval(initial_grid_2d_X, initial_grid_2d_Y)
298
+
299
+ sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
300
+ full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
301
+ num_computational_states = current_N * current_N
302
+
303
+ if len(sub_sv_init_flat) == num_computational_states:
304
+ if num_computational_states <= N_sub_per_rank:
305
+ full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
306
+ else:
307
+ print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
308
+ return None
309
+ else:
310
+ print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
311
+ fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
312
+ full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
313
+
314
+ rank_slice_init = to_cupy_array(initial_state_val)
315
+ print(f'Rank {rank}: Initializing state with {distribution_type} (ux={ux_input}, uy={uy_input})...')
316
+ cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
317
+ print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
318
+
319
+ print("Starting QLBM evolution...")
320
+ qlbm_obj.run_evolution(initial_state_val, T)
321
+ print("QLBM evolution complete.")
322
+
323
+ print("Generating plots with Plotly...")
324
+ downsampled_N = current_N // downsampling_factor
325
+ if downsampled_N == 0 and current_N > 0:
326
+ downsampled_N = 1
327
+ elif current_N == 0:
328
+ print("Error: current_N is zero before Plotly stage.")
329
+ return None
330
+
331
+ # Load more timesteps for better slider experience
332
+ time_steps_to_load = list(range(0, T+1, max(1, T//10))) + [T] # 10 steps plus final
333
+ time_steps_to_load = sorted(list(set(time_steps_to_load))) # Remove duplicates and sort
334
+
335
+ data_frames = []
336
+ actual_timesteps_loaded = []
337
+
338
+ for t in time_steps_to_load:
339
+ file_path = intermediate_folder_path / f"{t}_{rank}.npy"
340
+ if file_path.exists():
341
+ sol_loaded = np.load(file_path)
342
+ if sol_loaded.size == downsampled_N * downsampled_N:
343
+ Z_data = np.reshape(sol_loaded, (downsampled_N, downsampled_N))
344
+ data_frames.append(Z_data)
345
+ actual_timesteps_loaded.append(t)
346
+ else:
347
+ print(f"Warning: File {file_path} size {sol_loaded.size} != expected {downsampled_N*downsampled_N}. Skipping.")
348
+ else:
349
+ print(f"Warning: File {file_path} not found. Skipping.")
350
+
351
+ if not data_frames:
352
+ print("Error: No data frames loaded for interactive plot.")
353
+ return None
354
+
355
+ x_coords_plot = np.linspace(-10, 10, downsampled_N)
356
+ y_coords_plot = np.linspace(-10, 10, downsampled_N)
357
+
358
+ # Calculate global min/max for consistent scaling
359
+ z_min = min([np.min(Z) for Z in data_frames])
360
+ z_max = max([np.max(Z) for Z in data_frames])
361
+ if z_max == z_min:
362
+ z_max += 1e-9
363
+
364
+ # Create interactive Plotly figure with slider
365
+ fig = go.Figure()
366
+
367
+ # Add all traces (one for each timestep)
368
+ for i, Z in enumerate(data_frames):
369
+ fig.add_trace(
370
+ go.Surface(
371
+ z=Z, x=x_coords_plot, y=y_coords_plot,
372
+ colorscale='Viridis',
373
+ cmin=z_min, cmax=z_max,
374
+ name=f'Time: {actual_timesteps_loaded[i]}',
375
+ visible=(i == 0), # Only first trace visible initially
376
+ showscale=(i == 0) # Only show colorbar for first trace
377
+ )
378
+ )
379
+
380
+ # Create slider steps correctly
381
+ steps = []
382
+ for i in range(len(data_frames)):
383
+ step = dict(
384
+ method="update",
385
+ args=[{"visible": [False] * len(data_frames)}],
386
+ label=f"Time: {actual_timesteps_loaded[i]}"
387
+ )
388
+ step["args"][0]["visible"][i] = True # Make only the i-th trace visible
389
+ steps.append(step)
390
+
391
+ # Configure sliders properly
392
+ sliders = [dict(
393
+ active=0,
394
+ currentvalue={"prefix": "Time: "},
395
+ pad={"t": 50},
396
+ steps=steps
397
+ )]
398
+
399
+ fig.update_layout(
400
+ title='QLBM Simulation - Density Evolution',
401
+ scene=dict(
402
+ xaxis_title='X',
403
+ yaxis_title='Y',
404
+ zaxis_title='Density',
405
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
406
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
407
+ zaxis=dict(range=[z_min, z_max]),
408
+ ),
409
+ sliders=sliders,
410
+ width=1000,
411
+ height=900
412
+ )
413
+
414
+ return fig
415
 
416
+ # Gradio Interface Definition
417
+ def qlbm_gradio_interface(num_reg_qubits_input: int, timescale_input: int, distribution_type_param: str, ux_param: float, uy_param: float, velocity_field_type_param: str):
418
+ num_reg_qubits_val = int(num_reg_qubits_input)
419
+ timescale_val = int(timescale_input)
420
+ ux_val = float(ux_param)
421
+ uy_val = float(uy_param)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
 
423
+ print(f"Gradio Interface: num_reg_qubits={num_reg_qubits_val}, T={timescale_val}, Distribution={distribution_type_param}, ux={ux_val}, uy={uy_val}, VelocityFieldType={velocity_field_type_param}")
424
 
425
+ plot_fig = simulate_qlbm_and_animate(
426
+ num_reg_qubits=num_reg_qubits_val,
427
+ T=timescale_val,
428
+ distribution_type=distribution_type_param,
429
+ ux_input=ux_val,
430
+ uy_input=uy_val,
431
+ velocity_field_type=velocity_field_type_param
432
+ )
433
 
434
+ if plot_fig is None:
435
+ gr.Warning("Simulation or plotting failed. Please check console for errors.")
436
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
 
438
+ return plot_fig
439
 
440
+ with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Simulation with Plotly") as qlbm_demo:
441
+ gr.Markdown(
442
+ """
443
+ # ⚛️ Quantum Lattice Boltzmann Method (QLBM) Simulator (Plotly Animation)
444
+ Welcome to the Quantum Lattice Boltzmann Method (QLBM) simulator! This version uses Plotly for 3D animation and interactive plots.
445
+ **How this Simulator Works:**
446
+ This simulator implements a D2Q5 model on a quantum computer simulator (CUDA-Q).
447
+ - Control grid size (via Number of Register Qubits: $N=2^{\text{num_reg_qubits}}$).
448
+ - Set total simulation time (Timescale T).
449
+ - Choose initial distribution.
450
+ - Set advection velocities `ux` and `uy`.
451
+ The simulation generates an interactive Plotly figure with a slider for selected time steps.
452
+ **Note:** Higher qubit counts and longer timescales are computationally intensive. Advection velocities should be small (e.g., < 0.3).
453
+ The Plotly figure allows interactive exploration of specific time steps.
454
+ """
455
+ )
456
+
457
+ with gr.Row():
458
+ with gr.Column(scale=1):
459
+ gr.Markdown("## Simulation Parameters")
460
+ num_reg_qubits_slider = gr.Slider(
461
+ minimum=2, maximum=10, value=8, step=1,
462
+ label="Number of Register Qubits (num_reg_qubits)",
463
+ info="Grid N = 2^num_reg_qubits. Max 10 (Note: >8 slow; >9 may hit simulator/memory limits on free tiers)."
464
+ )
465
+
466
+ timescale_slider = gr.Slider(
467
+ minimum=0, maximum=2000, value=100, step=10,
468
+ label="Timescale (T)", info="Total number of timesteps. Max 2000."
469
+ )
470
+
471
+ with gr.Accordion("Initial Conditions", open=True):
472
+ distribution_options = ["Sine Wave (Original)", "Gaussian", "Random"]
473
+ distribution_type_input = gr.Radio(
474
+ choices=distribution_options, value="Sine Wave (Original)",
475
+ label="Initial Distribution Type", info="Select the initial pattern of the substance."
476
+ )
477
+
478
+ with gr.Accordion("Velocity Fields", open=True):
479
+ velocity_field_options = ["Uniform", "Vortex", "Shear"]
480
+ velocity_field_type_input = gr.Radio(
481
+ choices=velocity_field_options, value="Uniform",
482
+ label="Velocity Field Type", info="Select the type of background velocity field."
483
+ )
484
+
485
+ ux_slider = gr.Slider(
486
+ minimum=-0.4, maximum=0.4, value=0.2, step=0.01,
487
+ label="Advection Velocity ux", info="x-component of background advection."
488
+ )
489
+
490
+ uy_slider = gr.Slider(
491
+ minimum=-0.4, maximum=0.4, value=0.15, step=0.01,
492
+ label="Advection Velocity uy", info="y-component of background advection."
493
+ )
494
+
495
+ run_qlbm_btn = gr.Button("Run QLBM Simulation", variant="primary")
496
+
497
+ with gr.Column(scale=2):
498
+ qlbm_interactive_plot = gr.Plot(label="Interactive Density Plot with Slider")
499
+
500
+ qlbm_inputs_list = [num_reg_qubits_slider, timescale_slider, distribution_type_input, ux_slider, uy_slider, velocity_field_type_input]
501
+
502
+ run_qlbm_btn.click(
503
+ fn=qlbm_gradio_interface,
504
+ inputs=qlbm_inputs_list,
505
+ outputs=[qlbm_interactive_plot]
506
+ )
507
+
508
+ gr.Examples(
509
+ examples=[[6, 50, "Gaussian", 0.1, 0.05, "Uniform"]],
510
+ inputs=qlbm_inputs_list,
511
+ outputs=[qlbm_interactive_plot],
512
+ fn=qlbm_gradio_interface,
513
+ cache_examples=False
514
+ )
515
 
516
  if __name__ == "__main__":
517
+ try:
518
+ cudaq.set_target('nvidia', option='fp64')
519
+ print(f"CUDA-Q Target successfully set to: {cudaq.get_target().name}")
520
+ except Exception as e_target:
521
+ print(f"Warning: Could not set CUDA-Q target to 'nvidia'. Error: {e_target}")
522
+ print(f"Current CUDA-Q Target: {cudaq.get_target().name}. Performance may be affected.")
523
+
524
+ qlbm_demo.launch()