harishaseebat92 commited on
Commit
699c195
·
verified ·
1 Parent(s): a057bce

Upload 5 files

Browse files
Files changed (5) hide show
  1. .gitattributes +35 -35
  2. Dockerfile.txt +27 -0
  3. README.md +12 -12
  4. app.py +565 -0
  5. requirements.txt +5 -0
.gitattributes CHANGED
@@ -1,35 +1,35 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an NVIDIA CUDA base image with Python
2
+ FROM nvidia/cuda:12.2.0-devel-ubuntu22.04
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ python3 \
10
+ python3-pip \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Upgrade pip
14
+ RUN pip3 install --upgrade pip
15
+
16
+ # Copy requirements.txt and install Python dependencies
17
+ COPY requirements.txt .
18
+ RUN pip3 install -r requirements.txt
19
+
20
+ # Copy the application code
21
+ COPY gaussian.py .
22
+
23
+ # Expose the port used by Gradio (default is 7860)
24
+ EXPOSE 7860
25
+
26
+ # Command to run the application
27
+ CMD ["python3", "gaussian.py"]
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: Container Trial
3
- emoji: 🏃
4
- colorFrom: gray
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: QLBM Container
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: Container Trial
3
+ emoji: 🏃
4
+ colorFrom: gray
5
+ colorTo: green
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ short_description: QLBM Container
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,565 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ import math
5
+ import threading
6
+ import tempfile
7
+ import gradio as gr
8
+
9
+ import cudaq
10
+ import numpy as np
11
+ import cupy as cp
12
+ # from scipy import interpolate # Not used
13
+
14
+ from pathlib import Path
15
+
16
+ from matplotlib import pyplot as plt
17
+ from matplotlib.animation import FuncAnimation
18
+
19
+ def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, ux_input: float, uy_input: float):
20
+ """
21
+ Simulates a 2D advection-diffusion problem using a Quantum Lattice Boltzmann Method (QLBM)
22
+ and generates a GIF animation of the simulation's evolution.
23
+
24
+ Args:
25
+ num_reg_qubits (int): The number of register qubits (determines grid size N=2^num_reg_qubits).
26
+ T (int): The total number of timesteps to run the simulation.
27
+ distribution_type (str): The type of initial distribution to use.
28
+ ux_input (float): Advection velocity in the x-direction.
29
+ uy_input (float): Advection velocity in the y-direction.
30
+
31
+ Returns:
32
+ str: The file path to the generated GIF animation, or None if an error occurs.
33
+ """
34
+
35
+ video_length = T
36
+ simulation_fps = 0.1
37
+ frames = int(simulation_fps * video_length)
38
+ if frames == 0:
39
+ frames = 1
40
+
41
+ num_anc = 3
42
+ num_qubits_total = 2 * num_reg_qubits + num_anc
43
+ current_N = 2**num_reg_qubits
44
+ N_tot_state_vector = 2**num_qubits_total
45
+ num_ranks = 1
46
+ rank = 0
47
+ N_sub_per_rank = int(N_tot_state_vector // num_ranks)
48
+
49
+ timesteps_per_frame = 1
50
+ if frames < T and frames > 0:
51
+ timesteps_per_frame = int(T / frames)
52
+ if timesteps_per_frame == 0:
53
+ timesteps_per_frame = 1
54
+
55
+ if distribution_type == "Sine Wave (Original)":
56
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
57
+ np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
58
+ np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
59
+ elif distribution_type == "Gaussian":
60
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
61
+ np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
62
+ (y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
63
+ elif distribution_type == "Random":
64
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
65
+ np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
66
+ else:
67
+ print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sine Wave.")
68
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
69
+ np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
70
+ np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
71
+
72
+ initial_state_func_eval = lambda x_coords, y_coords: \
73
+ selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
74
+ (y_coords < current_N).astype(int)
75
+
76
+
77
+ with tempfile.TemporaryDirectory() as tmp_npy_dir:
78
+ intermediate_folder_path = Path(tmp_npy_dir)
79
+
80
+ cudaq.set_target('nvidia', option='mgpu,fp64')
81
+
82
+ @cudaq.kernel
83
+ def alloc_kernel(num_qubits_alloc: int):
84
+ qubits = cudaq.qvector(num_qubits_alloc)
85
+
86
+ from cupy.cuda.memory import MemoryPointer, UnownedMemory
87
+
88
+ def to_cupy_array(state):
89
+ tensor = state.getTensor()
90
+ pDevice = tensor.data()
91
+ sizeByte = tensor.get_num_elements() * tensor.get_element_size()
92
+ mem = UnownedMemory(pDevice, sizeByte, owner=state)
93
+ memptr_obj = MemoryPointer(mem, 0)
94
+ cupy_array = cp.ndarray(tensor.get_num_elements(),
95
+ dtype=cp.complex128,
96
+ memptr=memptr_obj)
97
+ return cupy_array
98
+
99
+ class QLBMAdvecDiffD2Q5_new:
100
+ def __init__(self, ux=0.2, uy=0.15) -> None: # ux, uy are now passed here
101
+ self.dim = 2
102
+ self.ndir = 5
103
+ self.nq_dir = math.ceil(np.log2(self.ndir))
104
+ self.dirs = []
105
+ for dir_int in range(self.ndir):
106
+ dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
107
+ self.dirs.append(dir_bin)
108
+ self.e_unitvec = np.array([0, 1, -1, 1, -1])
109
+ self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
110
+ self.cs = 1 / np.sqrt(3)
111
+ self.ux = ux # Use passed ux
112
+ self.uy = uy # Use passed uy
113
+ self.u = np.array([0, self.ux, self.ux, self.uy, self.uy])
114
+ self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
115
+ self.create_circuit()
116
+
117
+ def create_circuit(self):
118
+ v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
119
+ v = v**0.5
120
+ v[0] += 1
121
+ v = v / np.linalg.norm(v)
122
+ U_prep = 2 * np.outer(v, v) - np.eye(len(v))
123
+ cudaq.register_operation("prep_op", U_prep)
124
+
125
+ def collisionOp(dirs):
126
+ dirs_i_list = []
127
+ for dir_ in dirs:
128
+ dirs_i = [(int(c)) for c in dir_]
129
+ dirs_i_list += dirs_i[::-1]
130
+ return dirs_i_list
131
+
132
+ self.dirs_i_list = collisionOp(self.dirs)
133
+
134
+ @cudaq.kernel
135
+ def rshift(q: cudaq.qview, n: int):
136
+ for i in range(n):
137
+ if i == n - 1:
138
+ x(q[n - 1 - i])
139
+ elif i == n - 2:
140
+ x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
141
+ else:
142
+ x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
143
+
144
+ @cudaq.kernel
145
+ def lshift(q: cudaq.qview, n: int):
146
+ for i in range(n):
147
+ if i == 0:
148
+ x(q[0])
149
+ elif i == 1:
150
+ x.ctrl(q[0], q[1])
151
+ else:
152
+ x.ctrl(q[0:i], q[i])
153
+
154
+ @cudaq.kernel
155
+ def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir: int, dirs_i: list[int]):
156
+ qx = q[0:nqx]
157
+ qy = q[nqx:nqx + nqy]
158
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir]
159
+ i = 2
160
+ b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
161
+ for j in range(nq_dir):
162
+ b = b_list[j]
163
+ if b == 0:
164
+ x(qdir[j])
165
+ cudaq.control(lshift, qdir, qx, nqx)
166
+ for j in range(nq_dir):
167
+ b = b_list[j]
168
+ if b == 0:
169
+ x(qdir[j])
170
+ i = 1
171
+ b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
172
+ for j in range(nq_dir):
173
+ b = b_list[j]
174
+ if b == 0:
175
+ x(qdir[j])
176
+ cudaq.control(rshift, qdir, qx, nqx)
177
+ for j in range(nq_dir):
178
+ b = b_list[j]
179
+ if b == 0:
180
+ x(qdir[j])
181
+ i = 4
182
+ b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
183
+ for j in range(nq_dir):
184
+ b = b_list[j]
185
+ if b == 0:
186
+ x(qdir[j])
187
+ cudaq.control(lshift, qdir, qy, nqy)
188
+ for j in range(nq_dir):
189
+ b = b_list[j]
190
+ if b == 0:
191
+ x(qdir[j])
192
+ i = 3
193
+ b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
194
+ for j in range(nq_dir):
195
+ b = b_list[j]
196
+ if b == 0:
197
+ x(qdir[j])
198
+ cudaq.control(rshift, qdir, qy, nqy)
199
+ for j in range(nq_dir):
200
+ b = b_list[j]
201
+ if b == 0:
202
+ x(qdir[j])
203
+
204
+ @cudaq.kernel
205
+ def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir: int, dirs_i: list[int]):
206
+ q = cudaq.qvector(state_arg)
207
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir]
208
+ prep_op(qdir[2], qdir[1], qdir[0])
209
+ d2q5_tstep(q, nqx, nqy, nq_dir, dirs_i)
210
+ prep_op(qdir[2], qdir[1], qdir[0])
211
+
212
+ @cudaq.kernel
213
+ def d2q5_tstep_wrapper_hadamard(vec: list[complex], nqx: int, nqy: int, nq_dir: int, dirs_i: list[int]):
214
+ q = cudaq.qvector(vec)
215
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir]
216
+ qy = q[nqx:nqx + nqy]
217
+ prep_op(qdir[2], qdir[1], qdir[0])
218
+ d2q5_tstep(q, nqx, nqy, nq_dir, dirs_i)
219
+ prep_op(qdir[2], qdir[1], qdir[0])
220
+ for i in range(nqy):
221
+ h(qy[i])
222
+
223
+ def run_timestep_func(vec, hadamard=False):
224
+ if hadamard:
225
+ result = cudaq.get_state(d2q5_tstep_wrapper_hadamard, vec, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
226
+ else:
227
+ result = cudaq.get_state(d2q5_tstep_wrapper, vec, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
228
+
229
+ num_nonzero_ranks = num_ranks / (2**num_anc)
230
+ rank_slice_cupy = to_cupy_array(result)
231
+
232
+ if rank >= num_nonzero_ranks:
233
+ sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
234
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
235
+
236
+ if rank == 0 and num_nonzero_ranks < 1:
237
+ sub_sv_get = (rank_slice_cupy).get()
238
+ sub_sv_get[int(N_tot_state_vector / (2**num_anc)):] = 0
239
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_get.ctypes.data, sub_sv_get.nbytes, cp.cuda.runtime.memcpyHostToDevice)
240
+ return result
241
+
242
+ self.run_timestep = run_timestep_func
243
+
244
+ def write_state(self, state_to_write, t_step):
245
+ rank_slice_cupy = to_cupy_array(state_to_write)
246
+ num_nonzero_ranks = num_ranks / (2**num_anc)
247
+
248
+ if rank < num_nonzero_ranks:
249
+ save_path = intermediate_folder_path / f"{t_step}_{rank}.npy"
250
+ with open(save_path, 'wb') as f:
251
+ arr_to_save = None
252
+ if num_nonzero_ranks < 1:
253
+ arr_to_save = cp.real(rank_slice_cupy)[:int(N_tot_state_vector / (2**num_anc))]
254
+ else:
255
+ arr_to_save = cp.real(rank_slice_cupy)
256
+
257
+ if len(arr_to_save) > current_N :
258
+ arr_to_save=arr_to_save.reshape((current_N, current_N))
259
+ arr_to_save=arr_to_save[::downsampling_factor,::downsampling_factor]
260
+ arr_to_save=arr_to_save.flatten()
261
+ elif len(arr_to_save) > 0 :
262
+ arr_to_save=arr_to_save[::downsampling_factor]
263
+
264
+ np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
265
+
266
+
267
+ def run_evolution(self, initial_state_arg, total_timesteps, observable=False):
268
+ current_state = initial_state_arg
269
+ for t_iter in range(total_timesteps):
270
+ next_state = None
271
+ if t_iter == total_timesteps - 1 and observable:
272
+ next_state = self.run_timestep(current_state, True)
273
+ self.write_state(next_state, str(t_iter + 1) + "_h")
274
+ else:
275
+ next_state = self.run_timestep(current_state)
276
+ if (t_iter + 1) % timesteps_per_frame == 0:
277
+ self.write_state(next_state, t_iter + 1)
278
+ if rank == 0:
279
+ print(f"Timestep: {t_iter + 1}/{total_timesteps}")
280
+
281
+ cp.get_default_memory_pool().free_all_blocks()
282
+ current_state = next_state
283
+
284
+ last_saved_timestep_name = str(total_timesteps) if not observable else str(total_timesteps) + "_h"
285
+ # Check if the very last state (T or T_h) was potentially saved by the loop or observable condition.
286
+ # If not, save it.
287
+ final_state_file_path = intermediate_folder_path.joinpath(f"{last_saved_timestep_name}_{rank}.npy")
288
+ # Also check if the non-observable final T state was saved if T aligns with timesteps_per_frame
289
+ final_T_state_file_path = intermediate_folder_path.joinpath(f"{total_timesteps}_{rank}.npy")
290
+
291
+ needs_final_save = True
292
+ if observable and final_state_file_path.exists(): # T_h was saved
293
+ needs_final_save = False
294
+ elif not observable and final_T_state_file_path.exists() and total_timesteps % timesteps_per_frame == 0 : # T was saved by loop
295
+ needs_final_save = False
296
+ elif not observable and final_T_state_file_path.exists(): # T was saved by a previous final write
297
+ needs_final_save = False
298
+
299
+
300
+ if needs_final_save and total_timesteps > 0:
301
+ if not observable: # Save as T_0.npy
302
+ self.write_state(current_state, total_timesteps)
303
+ # If observable, it should have been saved as T_h. If it wasn't (e.g. T=0), this won't run.
304
+
305
+ if rank == 0:
306
+ print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
307
+ cp.get_default_memory_pool().free_all_blocks()
308
+ self.final_state = current_state
309
+
310
+ downsampling_factor = 2**5
311
+ # Pass ux_input and uy_input to the constructor
312
+ qlbm_obj = QLBMAdvecDiffD2Q5_new(ux=ux_input, uy=uy_input)
313
+
314
+ initial_state = cudaq.get_state(alloc_kernel, num_qubits_total)
315
+ rank_slice_init = to_cupy_array(initial_state)
316
+
317
+ xv_init = np.arange(current_N)
318
+ yv_init = np.arange(current_N)
319
+
320
+ print(f'Start initializing state with {distribution_type} distribution (ux={ux_input}, uy={uy_input})...')
321
+ initial_grid_2d = initial_state_func_eval(*np.meshgrid(xv_init, yv_init))
322
+ sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
323
+
324
+ full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
325
+ num_computational_states = current_N * current_N
326
+
327
+ if len(sub_sv_init_flat) == num_computational_states:
328
+ full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
329
+ else:
330
+ print(f"Warning: Mismatch in expected initial state size {num_computational_states} and calculated {len(sub_sv_init_flat)}")
331
+ full_initial_sv_host[:len(sub_sv_init_flat)] = sub_sv_init_flat
332
+
333
+ print(f'Rank {rank}: Initial state (N*N part) prepared. Size: {len(sub_sv_init_flat)}. Total N_sub_per_rank: {N_sub_per_rank}')
334
+ cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
335
+ print(f'Rank {rank}: Initial state copied to device.')
336
+
337
+ print("Starting QLBM evolution...")
338
+ qlbm_obj.run_evolution(initial_state, T)
339
+ print("QLBM evolution complete.")
340
+
341
+ print("Generating animation...")
342
+ downsampled_N = current_N // downsampling_factor
343
+ if downsampled_N == 0:
344
+ print("Error: Downsampled grid size is zero. Check num_reg_qubits and downsampling_factor.")
345
+ downsampled_N = 1
346
+
347
+ plotted_timesteps_str = [] # To store frame names (can include '_h')
348
+ if timesteps_per_frame > 0:
349
+ # Add regular frames saved by the loop
350
+ for t_step in range(timesteps_per_frame, T + 1, timesteps_per_frame):
351
+ if intermediate_folder_path.joinpath(f"{t_step}_0.npy").exists():
352
+ plotted_timesteps_str.append(str(t_step))
353
+
354
+ # Ensure the very last timestep (T or T_h) is considered for plotting
355
+ final_T_path = intermediate_folder_path.joinpath(f"{T}_0.npy")
356
+ final_Th_path = intermediate_folder_path.joinpath(f"{T}_h_0.npy")
357
+
358
+ if T > 0:
359
+ if final_Th_path.exists(): # Prefer T_h if it exists
360
+ if str(T)+"_h" not in plotted_timesteps_str:
361
+ plotted_timesteps_str.append(str(T)+"_h")
362
+ elif final_T_path.exists(): # Else, use T if it exists
363
+ if str(T) not in plotted_timesteps_str:
364
+ plotted_timesteps_str.append(str(T))
365
+
366
+ # Remove duplicates and sort (handle numeric and '_h' strings appropriately for sorting if needed, though string sort is often fine here)
367
+ plotted_timesteps_str = sorted(list(set(plotted_timesteps_str)), key=lambda k: (int(str(k).replace('_h','')), str(k)))
368
+
369
+
370
+ if not plotted_timesteps_str and T > 0 : # Fallback if T is small and not caught by loop
371
+ if final_Th_path.exists(): plotted_timesteps_str = [str(T)+"_h"]
372
+ elif final_T_path.exists(): plotted_timesteps_str = [str(T)]
373
+ elif not plotted_timesteps_str and T == 0:
374
+ print("Warning: T=0, no frames to plot for animation.")
375
+ return None
376
+
377
+ data = []
378
+ actual_timesteps_for_title = []
379
+
380
+ for i_str in plotted_timesteps_str:
381
+ try:
382
+ file_path = intermediate_folder_path / f"{i_str}_0.npy"
383
+ sol = np.load(file_path)
384
+ if sol.size == downsampled_N * downsampled_N:
385
+ Z = np.reshape(sol, (downsampled_N, downsampled_N))
386
+ data.append(Z)
387
+ actual_timesteps_for_title.append(str(i_str).replace('_h',''))
388
+ else:
389
+ print(f"Warning: File {file_path} has unexpected size {sol.size}. Expected {downsampled_N*downsampled_N}. Skipping this frame.")
390
+ continue
391
+ except FileNotFoundError:
392
+ print(f"Warning: File not found for timestep {i_str} ({file_path}). Skipping this frame.")
393
+ continue
394
+ except Exception as e:
395
+ print(f"Error loading or reshaping file for timestep {i_str}: {e}. Skipping this frame.")
396
+ continue
397
+
398
+ if not data:
399
+ print("Error: No data available to create animation. Check .npy file generation and paths.")
400
+ return None
401
+
402
+ fig = plt.figure(figsize=(10, 8))
403
+ ax = fig.add_subplot(111, projection='3d')
404
+
405
+ x_plot = np.linspace(-10, 10, downsampled_N)
406
+ y_plot = np.linspace(-10, 10, downsampled_N)
407
+ X_plot, Y_plot = np.meshgrid(x_plot, y_plot)
408
+
409
+ norm_factor = np.linalg.norm(data[0]) if data[0].size > 0 else 1.0
410
+ if norm_factor == 0:
411
+ norm_factor = 1.0
412
+
413
+ def update_frame(frame_idx):
414
+ ax.clear()
415
+ Z = data[frame_idx]
416
+ surf = ax.plot_surface(X_plot, Y_plot, Z / norm_factor, cmap='viridis', linewidth=0, antialiased=False)
417
+ current_timestep_for_title = actual_timesteps_for_title[frame_idx] if frame_idx < len(actual_timesteps_for_title) else "Unknown"
418
+ ax.set_title(f'Quantum Simulation Evolution (Timestep: {current_timestep_for_title})')
419
+ ax.set_xlabel('x')
420
+ ax.set_ylabel('y')
421
+ ax.set_zlim(0, 0.6)
422
+ return surf,
423
+
424
+ gif_animation_fps = 10
425
+ ani = FuncAnimation(fig, update_frame, frames=len(data), blit=False)
426
+
427
+ results_base_dir = "Results"
428
+ gif_frames_for_naming = int(simulation_fps * T)
429
+ if gif_frames_for_naming == 0: gif_frames_for_naming = 1
430
+
431
+ # Sanitize distribution_type for filename
432
+ dist_name_part = distribution_type.replace(' ','').replace('(Original)','').replace('(','').replace(')','')
433
+
434
+ specific_folder_name = (f"d2q5_nq{current_N}x{current_N}_T{T}_fr{gif_frames_for_naming}_"
435
+ f"dist{dist_name_part}_ux{ux_input:.2f}_uy{uy_input:.2f}")
436
+ final_output_dir = Path(results_base_dir) / specific_folder_name
437
+ os.makedirs(final_output_dir, exist_ok=True)
438
+ gif_path_to_return = final_output_dir / "animation.gif"
439
+
440
+ try:
441
+ ani.save(str(gif_path_to_return), writer='pillow', fps=gif_animation_fps)
442
+ print(f"Animation saved to {gif_path_to_return}")
443
+ except Exception as e:
444
+ print(f"Error saving animation: {e}")
445
+ plt.close(fig)
446
+ return None
447
+ plt.close(fig)
448
+ return str(gif_path_to_return)
449
+
450
+ # --- Gradio Interface Definition ---
451
+
452
+ def qlbm_gradio_interface(num_reg_qubits_input: int, timescale_input: int, distribution_type_param: str, ux_param: float, uy_param: float):
453
+ num_reg_qubits_val = int(num_reg_qubits_input)
454
+ timescale_val = int(timescale_input)
455
+ ux_val = float(ux_param)
456
+ uy_val = float(uy_param)
457
+
458
+ print(f"Gradio Interface: num_reg_qubits={num_reg_qubits_val}, T={timescale_val}, Distribution={distribution_type_param}, ux={ux_val}, uy={uy_val}")
459
+
460
+ gif_path_output = simulate_qlbm_and_animate(
461
+ num_reg_qubits=num_reg_qubits_val,
462
+ T=timescale_val,
463
+ distribution_type=distribution_type_param,
464
+ ux_input=ux_val,
465
+ uy_input=uy_val
466
+ )
467
+
468
+ if gif_path_output is None:
469
+ gr.Warning("Animation generation failed. Please check console for errors.")
470
+ return None
471
+ return gif_path_output
472
+
473
+ with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Simulation") as qlbm_demo:
474
+ gr.Markdown(
475
+ """
476
+ # ⚛️ Quantum Lattice Boltzmann Method (QLBM) Simulator
477
+ Welcome to the Quantum Lattice Boltzmann Method (QLBM) simulator!
478
+
479
+ **What is QLBM?**
480
+ The Lattice Boltzmann Method (LBM) is a computational fluid dynamics technique for simulating fluid flow. QLBM is its quantum counterpart, leveraging quantum algorithms to potentially offer advantages for certain types of simulations. It's used here to model advection-diffusion phenomena, which describe how substances are transported due to bulk motion (advection) and spread out due to random motion (diffusion).
481
+
482
+ **How this Simulator Works:**
483
+ This simulator implements a D2Q5 model (2 dimensions, 5 velocity directions) on a quantum computer simulator (using CUDA-Q).
484
+ - You can control the grid size (via Number of Register Qubits, where grid size is $2^N \\times 2^N$).
485
+ - You can set the total simulation time (Timescale T).
486
+ - You can choose the initial distribution of the substance on the grid.
487
+ - You can set the advection velocities `ux` (x-direction) and `uy` (y-direction).
488
+ The simulation will then evolve this initial state over time, and an animation of the substance's density will be generated.
489
+
490
+ **Note:** Simulations with higher qubit counts and longer timescales can be computationally intensive and may take a significant amount of time to complete.
491
+ The $N$ in $2^N \\times 2^N$ grid refers to `num_reg_qubits`. Advection velocities `ux` and `uy` should generally be kept small (e.g., < 0.3) for model stability.
492
+ """
493
+ )
494
+
495
+ with gr.Row():
496
+ with gr.Column(scale=1):
497
+ gr.Markdown("## Simulation Parameters")
498
+ num_reg_qubits_slider = gr.Slider(
499
+ minimum=2,
500
+ maximum=11,
501
+ value=8,
502
+ step=1,
503
+ label="Number of Register Qubits (num_reg_qubits)",
504
+ info="Determines the N for grid size (2^N x 2^N). Max 11 (Note: >8 can be very slow)."
505
+ )
506
+ timescale_slider = gr.Slider(
507
+ minimum=10,
508
+ maximum=2000,
509
+ value=100,
510
+ step=10,
511
+ label="Timescale (T)",
512
+ info="Total number of timesteps. Max 2000 (Note: higher values increase run time significantly)."
513
+ )
514
+ distribution_options = ["Sine Wave (Original)", "Gaussian", "Random"]
515
+ distribution_type_input = gr.Radio(
516
+ choices=distribution_options,
517
+ value="Sine Wave (Original)",
518
+ label="Initial Distribution Type",
519
+ info="Select the initial pattern of the substance on the grid."
520
+ )
521
+ ux_slider = gr.Slider(
522
+ minimum=-0.4,
523
+ maximum=0.4,
524
+ value=0.2,
525
+ step=0.01,
526
+ label="Advection Velocity ux",
527
+ info="x-component of the background advection velocity."
528
+ )
529
+ uy_slider = gr.Slider(
530
+ minimum=-0.4,
531
+ maximum=0.4,
532
+ value=0.15,
533
+ step=0.01,
534
+ label="Advection Velocity uy",
535
+ info="y-component of the background advection velocity."
536
+ )
537
+
538
+ run_qlbm_btn = gr.Button("Run QLBM Simulation", variant="primary")
539
+
540
+ with gr.Column(scale=3):
541
+ qlbm_plot_output = gr.Image(label="QLBM Simulation Animation", type="filepath")
542
+
543
+ qlbm_inputs_list = [num_reg_qubits_slider, timescale_slider, distribution_type_input, ux_slider, uy_slider]
544
+
545
+ run_qlbm_btn.click(
546
+ fn=qlbm_gradio_interface,
547
+ inputs=qlbm_inputs_list,
548
+ outputs=qlbm_plot_output
549
+ )
550
+
551
+ gr.Examples(
552
+ examples=[
553
+ [8, 100, "Sine Wave (Original)", 0.2, 0.15],
554
+ [6, 50, "Gaussian", 0.1, 0.05],
555
+ [4, 30, "Random", -0.05, 0.1],
556
+ [7, 70, "Sine Wave (Original)", 0.0, 0.0] # Example with no advection
557
+ ],
558
+ inputs=qlbm_inputs_list,
559
+ outputs=qlbm_plot_output,
560
+ fn=qlbm_gradio_interface,
561
+ cache_examples=False
562
+ )
563
+
564
+ if __name__ == "__main__":
565
+ qlbm_demo.launch()
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio==3.50.2
2
+ matplotlib==3.8.0
3
+ numpy==1.26.0
4
+ cupy-cuda12x==12.2.0
5
+ cudaq==0.6.0