Spaces:
Sleeping
Sleeping
harishaseebat92 commited on
Commit ·
7bf5c9d
1
Parent(s): b816b11
Initial upload of source code
Browse files- README.md +8 -9
- app.py +603 -0
- app_o.py +568 -0
- app_pyvista.py +666 -0
- index.html +19 -0
- new-working-branch +0 -0
- packages.txt +4 -0
- requirements.txt +11 -0
- style.css +28 -0
README.md
CHANGED
|
@@ -1,14 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title: AlphaQuantDemo
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 5.34.0
|
| 8 |
-
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
-
license:
|
| 11 |
-
short_description:
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AlphaQuantDemo
|
| 3 |
+
emoji: 👀
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: gray
|
| 6 |
sdk: gradio
|
|
|
|
|
|
|
| 7 |
pinned: false
|
| 8 |
+
license: apache-2.0
|
| 9 |
+
short_description: testing platforms to build a quantum computing demo
|
| 10 |
+
sdk_version: 5.32.0
|
| 11 |
---
|
| 12 |
|
| 13 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import math
|
| 3 |
+
import tempfile
|
| 4 |
+
import gradio as gr
|
| 5 |
+
|
| 6 |
+
import cudaq
|
| 7 |
+
import numpy as np
|
| 8 |
+
import cupy as cp
|
| 9 |
+
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
|
| 12 |
+
# --- Plotly Imports ---
|
| 13 |
+
import plotly.graph_objects as go
|
| 14 |
+
import plotly.io as pio
|
| 15 |
+
import imageio # For compiling GIF from frames
|
| 16 |
+
|
| 17 |
+
# Set a default Plotly engine for image export
|
| 18 |
+
try:
|
| 19 |
+
pio.kaleido.scope.mathjax = None # Can speed up Kaleido if MathJax not needed
|
| 20 |
+
except AttributeError:
|
| 21 |
+
pass
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, ux_input: float, uy_input: float):
|
| 25 |
+
"""
|
| 26 |
+
Simulates a 2D advection-diffusion problem using a Quantum Lattice Boltzmann Method (QLBM)
|
| 27 |
+
and generates a GIF animation of the simulation's evolution using Plotly.
|
| 28 |
+
"""
|
| 29 |
+
|
| 30 |
+
video_length = T
|
| 31 |
+
simulation_fps = 0.1
|
| 32 |
+
frames = int(simulation_fps * video_length)
|
| 33 |
+
if frames == 0:
|
| 34 |
+
frames = 1
|
| 35 |
+
|
| 36 |
+
num_anc = 3
|
| 37 |
+
num_qubits_total = 2 * num_reg_qubits + num_anc
|
| 38 |
+
current_N = 2**num_reg_qubits
|
| 39 |
+
N_tot_state_vector = 2**num_qubits_total
|
| 40 |
+
num_ranks = 1
|
| 41 |
+
rank = 0
|
| 42 |
+
N_sub_per_rank = int(N_tot_state_vector // num_ranks)
|
| 43 |
+
|
| 44 |
+
timesteps_per_frame = 1
|
| 45 |
+
if frames < T and frames > 0:
|
| 46 |
+
timesteps_per_frame = int(T / frames)
|
| 47 |
+
if timesteps_per_frame == 0:
|
| 48 |
+
timesteps_per_frame = 1
|
| 49 |
+
|
| 50 |
+
if distribution_type == "Sine Wave (Original)":
|
| 51 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 52 |
+
np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
|
| 53 |
+
np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
|
| 54 |
+
elif distribution_type == "Gaussian":
|
| 55 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 56 |
+
np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
|
| 57 |
+
(y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
|
| 58 |
+
elif distribution_type == "Random":
|
| 59 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 60 |
+
np.random.rand(N_val_func, N_val_func) * 1.5 + 0.2 if isinstance(x, int) else \
|
| 61 |
+
np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
|
| 62 |
+
else:
|
| 63 |
+
print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sine Wave.")
|
| 64 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 65 |
+
np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
|
| 66 |
+
np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
|
| 67 |
+
|
| 68 |
+
initial_state_func_eval = lambda x_coords, y_coords: \
|
| 69 |
+
selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
|
| 70 |
+
(y_coords < current_N).astype(int)
|
| 71 |
+
|
| 72 |
+
with tempfile.TemporaryDirectory() as tmp_npy_dir:
|
| 73 |
+
intermediate_folder_path = Path(tmp_npy_dir)
|
| 74 |
+
|
| 75 |
+
cudaq.set_target('nvidia', option='fp64')
|
| 76 |
+
|
| 77 |
+
@cudaq.kernel
|
| 78 |
+
def alloc_kernel(num_qubits_alloc: int):
|
| 79 |
+
qubits = cudaq.qvector(num_qubits_alloc)
|
| 80 |
+
|
| 81 |
+
from cupy.cuda.memory import MemoryPointer, UnownedMemory
|
| 82 |
+
|
| 83 |
+
def to_cupy_array(state):
|
| 84 |
+
tensor = state.getTensor()
|
| 85 |
+
pDevice = tensor.data()
|
| 86 |
+
sizeByte = tensor.get_num_elements() * tensor.get_element_size()
|
| 87 |
+
mem = UnownedMemory(pDevice, sizeByte, owner=state)
|
| 88 |
+
memptr_obj = MemoryPointer(mem, 0)
|
| 89 |
+
cupy_array_val = cp.ndarray(tensor.get_num_elements(),
|
| 90 |
+
dtype=cp.complex128,
|
| 91 |
+
memptr=memptr_obj)
|
| 92 |
+
return cupy_array_val
|
| 93 |
+
|
| 94 |
+
class QLBMAdvecDiffD2Q5_new:
|
| 95 |
+
def __init__(self, ux=0.2, uy=0.15) -> None:
|
| 96 |
+
self.dim = 2
|
| 97 |
+
self.ndir = 5
|
| 98 |
+
self.nq_dir = math.ceil(np.log2(self.ndir))
|
| 99 |
+
self.dirs = []
|
| 100 |
+
for dir_int in range(self.ndir):
|
| 101 |
+
dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
|
| 102 |
+
self.dirs.append(dir_bin)
|
| 103 |
+
self.e_unitvec = np.array([0, 1, -1, 1, -1])
|
| 104 |
+
self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
|
| 105 |
+
self.cs = 1 / np.sqrt(3)
|
| 106 |
+
self.ux = ux
|
| 107 |
+
self.uy = uy
|
| 108 |
+
self.u = np.array([0, self.ux, self.ux, self.uy, self.uy])
|
| 109 |
+
self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
|
| 110 |
+
self.create_circuit()
|
| 111 |
+
|
| 112 |
+
def create_circuit(self):
|
| 113 |
+
v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
|
| 114 |
+
v = v**0.5
|
| 115 |
+
v[0] += 1
|
| 116 |
+
v = v / np.linalg.norm(v)
|
| 117 |
+
U_prep = 2 * np.outer(v, v) - np.eye(len(v))
|
| 118 |
+
cudaq.register_operation("prep_op", U_prep)
|
| 119 |
+
|
| 120 |
+
def collisionOp(dirs_list):
|
| 121 |
+
dirs_i_list_val = []
|
| 122 |
+
for dir_str in dirs_list:
|
| 123 |
+
dirs_i = [(int(c)) for c in dir_str]
|
| 124 |
+
dirs_i_list_val += dirs_i[::-1]
|
| 125 |
+
return dirs_i_list_val
|
| 126 |
+
|
| 127 |
+
self.dirs_i_list = collisionOp(self.dirs)
|
| 128 |
+
|
| 129 |
+
@cudaq.kernel
|
| 130 |
+
def rshift(q: cudaq.qview, n: int):
|
| 131 |
+
for i in range(n):
|
| 132 |
+
if i == n - 1:
|
| 133 |
+
x(q[n - 1 - i])
|
| 134 |
+
elif i == n - 2:
|
| 135 |
+
x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
|
| 136 |
+
else:
|
| 137 |
+
x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
|
| 138 |
+
|
| 139 |
+
@cudaq.kernel
|
| 140 |
+
def lshift(q: cudaq.qview, n: int):
|
| 141 |
+
for i in range(n):
|
| 142 |
+
if i == 0:
|
| 143 |
+
x(q[0])
|
| 144 |
+
elif i == 1:
|
| 145 |
+
x.ctrl(q[0], q[1])
|
| 146 |
+
else:
|
| 147 |
+
x.ctrl(q[0:i], q[i])
|
| 148 |
+
|
| 149 |
+
@cudaq.kernel
|
| 150 |
+
def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
|
| 151 |
+
qx = q[0:nqx]
|
| 152 |
+
qy = q[nqx:nqx + nqy]
|
| 153 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
|
| 154 |
+
|
| 155 |
+
idx_lqx = 2
|
| 156 |
+
b_list = dirs_i_val[idx_lqx * nq_dir_val:(idx_lqx + 1) * nq_dir_val]
|
| 157 |
+
for j in range(nq_dir_val):
|
| 158 |
+
if b_list[j] == 0: x(qdir[j])
|
| 159 |
+
cudaq.control(lshift, qdir, qx, nqx)
|
| 160 |
+
for j in range(nq_dir_val):
|
| 161 |
+
if b_list[j] == 0: x(qdir[j])
|
| 162 |
+
|
| 163 |
+
idx_rqx = 1
|
| 164 |
+
b_list = dirs_i_val[idx_rqx * nq_dir_val:(idx_rqx + 1) * nq_dir_val]
|
| 165 |
+
for j in range(nq_dir_val):
|
| 166 |
+
if b_list[j] == 0: x(qdir[j])
|
| 167 |
+
cudaq.control(rshift, qdir, qx, nqx)
|
| 168 |
+
for j in range(nq_dir_val):
|
| 169 |
+
if b_list[j] == 0: x(qdir[j])
|
| 170 |
+
|
| 171 |
+
idx_lqy = 4
|
| 172 |
+
b_list = dirs_i_val[idx_lqy * nq_dir_val:(idx_lqy + 1) * nq_dir_val]
|
| 173 |
+
for j in range(nq_dir_val):
|
| 174 |
+
if b_list[j] == 0: x(qdir[j])
|
| 175 |
+
cudaq.control(lshift, qdir, qy, nqy)
|
| 176 |
+
for j in range(nq_dir_val):
|
| 177 |
+
if b_list[j] == 0: x(qdir[j])
|
| 178 |
+
|
| 179 |
+
idx_rqy = 3
|
| 180 |
+
b_list = dirs_i_val[idx_rqy * nq_dir_val:(idx_rqy + 1) * nq_dir_val]
|
| 181 |
+
for j in range(nq_dir_val):
|
| 182 |
+
if b_list[j] == 0: x(qdir[j])
|
| 183 |
+
cudaq.control(rshift, qdir, qy, nqy)
|
| 184 |
+
for j in range(nq_dir_val):
|
| 185 |
+
if b_list[j] == 0: x(qdir[j])
|
| 186 |
+
|
| 187 |
+
@cudaq.kernel
|
| 188 |
+
def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
|
| 189 |
+
q = cudaq.qvector(state_arg)
|
| 190 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
|
| 191 |
+
prep_op(qdir[2], qdir[1], qdir[0]) # Assumes nq_dir_val is always 3
|
| 192 |
+
d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
|
| 193 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 194 |
+
|
| 195 |
+
@cudaq.kernel
|
| 196 |
+
def d2q5_tstep_wrapper_hadamard(vec_arg: list[complex], nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
|
| 197 |
+
q = cudaq.qvector(vec_arg)
|
| 198 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
|
| 199 |
+
qy = q[nqx:nqx + nqy]
|
| 200 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 201 |
+
d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
|
| 202 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 203 |
+
for i in range(nqy):
|
| 204 |
+
h(qy[i])
|
| 205 |
+
|
| 206 |
+
# MODIFIED run_timestep_func for memory optimization
|
| 207 |
+
def run_timestep_func(vec_arg, hadamard=False):
|
| 208 |
+
if hadamard:
|
| 209 |
+
result = cudaq.get_state(d2q5_tstep_wrapper_hadamard, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
|
| 210 |
+
else:
|
| 211 |
+
result = cudaq.get_state(d2q5_tstep_wrapper, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
|
| 212 |
+
|
| 213 |
+
num_nonzero_ranks = num_ranks / (2**num_anc)
|
| 214 |
+
rank_slice_cupy = to_cupy_array(result) # This is a cupy.ndarray view
|
| 215 |
+
|
| 216 |
+
if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
|
| 217 |
+
# This part of the original code implies multi-GPU/rank scenarios not fully active with num_ranks=1
|
| 218 |
+
# For num_ranks=1, rank is 0, so this block isn't hit if num_nonzero_ranks > 0.
|
| 219 |
+
# If num_nonzero_ranks is effectively 0 (e.g. 1/(2^3) < 1, which it is), this condition for rank=0 is also false.
|
| 220 |
+
# The original logic: if rank >= num_nonzero_ranks: ...
|
| 221 |
+
# If num_ranks=1, num_anc=3 -> num_nonzero_ranks = 1/8. rank=0 is not >= 1/8.
|
| 222 |
+
# This block seems intended for ranks that should contain no data.
|
| 223 |
+
# For now, keeping original logic path, but the optimization is for the other 'if' block.
|
| 224 |
+
sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
|
| 225 |
+
cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 226 |
+
|
| 227 |
+
# OPTIMIZED BLOCK: Modify directly on GPU if conditions are met
|
| 228 |
+
if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
|
| 229 |
+
# num_nonzero_ranks = 1/8 < 1, so this condition is met for rank 0
|
| 230 |
+
limit_idx = int(N_tot_state_vector / (2**num_anc))
|
| 231 |
+
if limit_idx < rank_slice_cupy.size:
|
| 232 |
+
rank_slice_cupy[limit_idx:] = 0 # Modify directly on GPU using CuPy
|
| 233 |
+
# No explicit memcpy back to GPU needed as rank_slice_cupy is the GPU array view.
|
| 234 |
+
return result
|
| 235 |
+
self.run_timestep = run_timestep_func
|
| 236 |
+
# END OF MODIFIED run_timestep_func
|
| 237 |
+
|
| 238 |
+
def write_state(self, state_to_write, t_step_str_val):
|
| 239 |
+
rank_slice_cupy = to_cupy_array(state_to_write)
|
| 240 |
+
num_nonzero_ranks = num_ranks / (2**num_anc)
|
| 241 |
+
|
| 242 |
+
if rank < num_nonzero_ranks or (rank==0 and num_nonzero_ranks <=0):
|
| 243 |
+
save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
|
| 244 |
+
with open(save_path, 'wb') as f:
|
| 245 |
+
arr_to_save = None
|
| 246 |
+
data_limit = N_sub_per_rank
|
| 247 |
+
if num_nonzero_ranks < 1 and rank == 0: # Only part of rank 0 has data
|
| 248 |
+
data_limit = int(N_tot_state_vector / (2**num_anc))
|
| 249 |
+
|
| 250 |
+
if data_limit > 0:
|
| 251 |
+
relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
|
| 252 |
+
else:
|
| 253 |
+
relevant_part_cupy = cp.array([], dtype=cp.float64)
|
| 254 |
+
|
| 255 |
+
if relevant_part_cupy.size >= current_N * current_N: # Enough data for full grid
|
| 256 |
+
arr_flat = relevant_part_cupy[:current_N * current_N]
|
| 257 |
+
# CORRECTED CONDITION: Removed reference to downsampled_N
|
| 258 |
+
if downsampling_factor > 1 and current_N > 0:
|
| 259 |
+
arr_reshaped = arr_flat.reshape((current_N, current_N))
|
| 260 |
+
arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor]
|
| 261 |
+
arr_to_save = arr_downsampled.flatten()
|
| 262 |
+
else: # No downsampling or conditions not met for it
|
| 263 |
+
arr_to_save = arr_flat
|
| 264 |
+
elif relevant_part_cupy.size > 0 : # Partial data
|
| 265 |
+
if downsampling_factor > 1:
|
| 266 |
+
arr_to_save = relevant_part_cupy[::downsampling_factor]
|
| 267 |
+
else:
|
| 268 |
+
arr_to_save = relevant_part_cupy
|
| 269 |
+
|
| 270 |
+
if arr_to_save is not None and arr_to_save.size > 0:
|
| 271 |
+
np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
|
| 272 |
+
|
| 273 |
+
def run_evolution(self, initial_state_arg, total_timesteps, observable=False):
|
| 274 |
+
current_state_val = initial_state_arg
|
| 275 |
+
for t_iter in range(total_timesteps):
|
| 276 |
+
next_state_val = None
|
| 277 |
+
if t_iter == total_timesteps - 1 and observable:
|
| 278 |
+
next_state_val = self.run_timestep(current_state_val, True)
|
| 279 |
+
self.write_state(next_state_val, str(t_iter + 1) + "_h")
|
| 280 |
+
else:
|
| 281 |
+
next_state_val = self.run_timestep(current_state_val)
|
| 282 |
+
if (t_iter + 1) % timesteps_per_frame == 0:
|
| 283 |
+
self.write_state(next_state_val, str(t_iter + 1))
|
| 284 |
+
if rank == 0:
|
| 285 |
+
print(f"Timestep: {t_iter + 1}/{total_timesteps}")
|
| 286 |
+
|
| 287 |
+
cp.get_default_memory_pool().free_all_blocks()
|
| 288 |
+
current_state_val = next_state_val
|
| 289 |
+
|
| 290 |
+
last_saved_timestep_name = str(total_timesteps) if not observable else str(total_timesteps) + "_h"
|
| 291 |
+
final_state_file_path = intermediate_folder_path / f"{last_saved_timestep_name}_{rank}.npy"
|
| 292 |
+
final_T_state_file_path = intermediate_folder_path / f"{total_timesteps}_{rank}.npy"
|
| 293 |
+
|
| 294 |
+
needs_final_save = True
|
| 295 |
+
if observable and final_state_file_path.exists():
|
| 296 |
+
needs_final_save = False
|
| 297 |
+
elif not observable and final_T_state_file_path.exists() and total_timesteps > 0 and total_timesteps % timesteps_per_frame == 0 :
|
| 298 |
+
needs_final_save = False
|
| 299 |
+
elif not observable and final_T_state_file_path.exists():
|
| 300 |
+
needs_final_save = False
|
| 301 |
+
|
| 302 |
+
if needs_final_save and total_timesteps > 0 :
|
| 303 |
+
if not observable:
|
| 304 |
+
self.write_state(current_state_val, str(total_timesteps))
|
| 305 |
+
elif total_timesteps == 0 and current_state_val is not None:
|
| 306 |
+
self.write_state(current_state_val, "0")
|
| 307 |
+
|
| 308 |
+
if rank == 0:
|
| 309 |
+
print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
|
| 310 |
+
cp.get_default_memory_pool().free_all_blocks()
|
| 311 |
+
self.final_state = current_state_val
|
| 312 |
+
# --- END OF QLBM CLASS ---
|
| 313 |
+
|
| 314 |
+
downsampling_factor = 2**5
|
| 315 |
+
if current_N == 0:
|
| 316 |
+
print("Error: current_N is zero. num_reg_qubits likely too small.")
|
| 317 |
+
return None
|
| 318 |
+
if current_N < downsampling_factor:
|
| 319 |
+
downsampling_factor = current_N if current_N > 0 else 1
|
| 320 |
+
|
| 321 |
+
qlbm_obj = QLBMAdvecDiffD2Q5_new(ux=ux_input, uy=uy_input)
|
| 322 |
+
initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
|
| 323 |
+
|
| 324 |
+
xv_init = np.arange(current_N)
|
| 325 |
+
yv_init = np.arange(current_N)
|
| 326 |
+
initial_grid_2d_X, initial_grid_2d_Y = np.meshgrid(xv_init, yv_init)
|
| 327 |
+
|
| 328 |
+
if distribution_type == "Random":
|
| 329 |
+
initial_grid_2d = selected_initial_state_function_raw(current_N, current_N, current_N) # Pass N for shape
|
| 330 |
+
else:
|
| 331 |
+
initial_grid_2d = initial_state_func_eval(initial_grid_2d_X, initial_grid_2d_Y)
|
| 332 |
+
|
| 333 |
+
sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
|
| 334 |
+
full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
|
| 335 |
+
num_computational_states = current_N * current_N
|
| 336 |
+
|
| 337 |
+
if len(sub_sv_init_flat) == num_computational_states:
|
| 338 |
+
if num_computational_states <= N_sub_per_rank:
|
| 339 |
+
full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
|
| 340 |
+
else:
|
| 341 |
+
print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
|
| 342 |
+
return None
|
| 343 |
+
else:
|
| 344 |
+
print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
|
| 345 |
+
fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
|
| 346 |
+
full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
|
| 347 |
+
|
| 348 |
+
rank_slice_init = to_cupy_array(initial_state_val)
|
| 349 |
+
print(f'Rank {rank}: Initializing state with {distribution_type} (ux={ux_input}, uy={uy_input})...')
|
| 350 |
+
cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 351 |
+
print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
|
| 352 |
+
|
| 353 |
+
print("Starting QLBM evolution...")
|
| 354 |
+
qlbm_obj.run_evolution(initial_state_val, T)
|
| 355 |
+
print("QLBM evolution complete.")
|
| 356 |
+
|
| 357 |
+
print("Generating animation with Plotly...")
|
| 358 |
+
downsampled_N = current_N // downsampling_factor
|
| 359 |
+
if downsampled_N == 0 and current_N > 0:
|
| 360 |
+
downsampled_N = 1
|
| 361 |
+
elif current_N == 0:
|
| 362 |
+
print("Error: current_N is zero before Plotly stage.")
|
| 363 |
+
return None
|
| 364 |
+
|
| 365 |
+
plotted_timesteps_str = []
|
| 366 |
+
if T == 0:
|
| 367 |
+
if (intermediate_folder_path / f"0_{rank}.npy").exists():
|
| 368 |
+
plotted_timesteps_str.append("0")
|
| 369 |
+
else:
|
| 370 |
+
if timesteps_per_frame > 0:
|
| 371 |
+
for t_step_val in range(timesteps_per_frame, T + 1, timesteps_per_frame):
|
| 372 |
+
if intermediate_folder_path.joinpath(f"{t_step_val}_{rank}.npy").exists():
|
| 373 |
+
plotted_timesteps_str.append(str(t_step_val))
|
| 374 |
+
final_T_path = intermediate_folder_path.joinpath(f"{T}_{rank}.npy")
|
| 375 |
+
if final_T_path.exists() and str(T) not in plotted_timesteps_str:
|
| 376 |
+
plotted_timesteps_str.append(str(T))
|
| 377 |
+
|
| 378 |
+
plotted_timesteps_str = sorted(list(set(plotted_timesteps_str)), key=lambda k_str: int(k_str))
|
| 379 |
+
|
| 380 |
+
if not plotted_timesteps_str :
|
| 381 |
+
print(f"Warning: No .npy files found for plotting T={T}. Animation may fail.")
|
| 382 |
+
return None
|
| 383 |
+
|
| 384 |
+
data_frames_list = []
|
| 385 |
+
actual_timesteps_for_title = []
|
| 386 |
+
for i_str_val in plotted_timesteps_str:
|
| 387 |
+
try:
|
| 388 |
+
file_path_load = intermediate_folder_path / f"{i_str_val}_{rank}.npy"
|
| 389 |
+
sol_loaded = np.load(file_path_load)
|
| 390 |
+
if sol_loaded.size == downsampled_N * downsampled_N:
|
| 391 |
+
Z_data = np.reshape(sol_loaded, (downsampled_N, downsampled_N))
|
| 392 |
+
data_frames_list.append(Z_data)
|
| 393 |
+
actual_timesteps_for_title.append(i_str_val)
|
| 394 |
+
else:
|
| 395 |
+
print(f"Warning: File {file_path_load} size {sol_loaded.size} != expected {downsampled_N*downsampled_N}. Skipping.")
|
| 396 |
+
continue
|
| 397 |
+
except FileNotFoundError:
|
| 398 |
+
print(f"Warning: File {file_path_load} not found. Skipping.")
|
| 399 |
+
continue
|
| 400 |
+
except Exception as e_load:
|
| 401 |
+
print(f"Error loading/reshaping {file_path_load}: {e_load}. Skipping.")
|
| 402 |
+
continue
|
| 403 |
+
|
| 404 |
+
if not data_frames_list:
|
| 405 |
+
print("Error: No data frames loaded. Cannot create animation.")
|
| 406 |
+
return None
|
| 407 |
+
|
| 408 |
+
x_coords_plot = np.linspace(-10, 10, downsampled_N)
|
| 409 |
+
y_coords_plot = np.linspace(-10, 10, downsampled_N)
|
| 410 |
+
|
| 411 |
+
norm_factor_plotly = np.max(np.abs(data_frames_list[0])) if data_frames_list[0].size > 0 and np.max(np.abs(data_frames_list[0])) != 0 else 1.0
|
| 412 |
+
if norm_factor_plotly == 0: norm_factor_plotly = 1.0
|
| 413 |
+
|
| 414 |
+
all_data_max_val = norm_factor_plotly
|
| 415 |
+
if len(data_frames_list) > 0: # Should always be true if we reached here
|
| 416 |
+
max_vals_frames = [np.max(np.abs(d_frame)) for d_frame in data_frames_list if d_frame.size > 0]
|
| 417 |
+
if max_vals_frames:
|
| 418 |
+
global_max_val_frames = max(max_vals_frames)
|
| 419 |
+
if global_max_val_frames > 0 : all_data_max_val = global_max_val_frames
|
| 420 |
+
|
| 421 |
+
clim_upper_plotly = (all_data_max_val / norm_factor_plotly) * 1.0
|
| 422 |
+
if clim_upper_plotly == 0 : clim_upper_plotly = 0.6
|
| 423 |
+
|
| 424 |
+
results_base_dir = "Results"
|
| 425 |
+
gif_frames_for_naming = int(simulation_fps * T)
|
| 426 |
+
if gif_frames_for_naming == 0: gif_frames_for_naming = 1
|
| 427 |
+
dist_name_part = distribution_type.replace(' ','').replace('(Original)','').replace('(','').replace(')','')
|
| 428 |
+
specific_folder_name = (f"d2q5_plotly_N{current_N}_T{T}_fr{gif_frames_for_naming}_"
|
| 429 |
+
f"dist{dist_name_part}_ux{ux_input:.2f}_uy{uy_input:.2f}")
|
| 430 |
+
final_output_dir = Path(results_base_dir) / specific_folder_name
|
| 431 |
+
os.makedirs(final_output_dir, exist_ok=True)
|
| 432 |
+
gif_path_to_return = final_output_dir / "animation_plotly.gif"
|
| 433 |
+
|
| 434 |
+
Z_initial_placeholder = np.zeros((downsampled_N, downsampled_N))
|
| 435 |
+
fig = go.Figure(data=[go.Surface(
|
| 436 |
+
z=Z_initial_placeholder, x=x_coords_plot, y=y_coords_plot,
|
| 437 |
+
colorscale='Viridis', cmin=0, cmax=clim_upper_plotly,
|
| 438 |
+
colorbar=dict(
|
| 439 |
+
title=dict(text='Density', side='right', font=dict(color='white')),
|
| 440 |
+
tickfont=dict(size=10, color='white'),
|
| 441 |
+
bgcolor='rgba(0,0,0,0.4)', bordercolor='gray', outlinewidth=1,
|
| 442 |
+
thickness=20, len=0.8, x=0.85, y=0.5
|
| 443 |
+
))])
|
| 444 |
+
|
| 445 |
+
fig.update_layout(
|
| 446 |
+
paper_bgcolor='black', plot_bgcolor='black', font=dict(color='white'),
|
| 447 |
+
scene=dict(
|
| 448 |
+
bgcolor='black',
|
| 449 |
+
xaxis=dict(title=dict(text='X Axis', font=dict(color='white')),
|
| 450 |
+
tickfont=dict(color='white'),
|
| 451 |
+
gridcolor='rgba(128,128,128,0.3)', zerolinecolor='rgba(128,128,128,0.5)',
|
| 452 |
+
linecolor='rgba(128,128,128,0.5)'),
|
| 453 |
+
yaxis=dict(title=dict(text='Y Axis', font=dict(color='white')),
|
| 454 |
+
tickfont=dict(color='white'),
|
| 455 |
+
gridcolor='rgba(128,128,128,0.3)', zerolinecolor='rgba(128,128,128,0.5)',
|
| 456 |
+
linecolor='rgba(128,128,128,0.5)'),
|
| 457 |
+
zaxis=dict(title=dict(text='Density', font=dict(color='white')),
|
| 458 |
+
tickfont=dict(color='white'),
|
| 459 |
+
range=[0, clim_upper_plotly], gridcolor='rgba(128,128,128,0.3)',
|
| 460 |
+
zerolinecolor='rgba(128,128,128,0.5)', linecolor='rgba(128,128,128,0.5)'),
|
| 461 |
+
aspectratio=dict(x=1, y=1, z=0.7),
|
| 462 |
+
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))
|
| 463 |
+
),
|
| 464 |
+
margin=dict(l=10, r=10, b=10, t=60), width=800, height=700
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
temp_image_files = []
|
| 468 |
+
gif_fps_val = 10
|
| 469 |
+
|
| 470 |
+
try:
|
| 471 |
+
for k, Z_frame_data in enumerate(data_frames_list):
|
| 472 |
+
Z_plotly_frame = Z_frame_data / norm_factor_plotly
|
| 473 |
+
fig.data[0].z = Z_plotly_frame
|
| 474 |
+
current_ts_title = actual_timesteps_for_title[k] if k < len(actual_timesteps_for_title) else "Unknown"
|
| 475 |
+
fig.update_layout(
|
| 476 |
+
title_text=f'QLBM Simulation (Plotly) - Timestep: {current_ts_title}',
|
| 477 |
+
title_x=0.5, title_font_size=16
|
| 478 |
+
)
|
| 479 |
+
temp_image_path = intermediate_folder_path / f"plotly_frame_{k:04d}.png"
|
| 480 |
+
fig.write_image(str(temp_image_path), engine="kaleido")
|
| 481 |
+
temp_image_files.append(str(temp_image_path))
|
| 482 |
+
|
| 483 |
+
if not temp_image_files:
|
| 484 |
+
print("Error: No Plotly frames were successfully generated.")
|
| 485 |
+
# Cleanup potentially created empty directory
|
| 486 |
+
if not any(final_output_dir.iterdir()): # Check if directory is empty
|
| 487 |
+
try:
|
| 488 |
+
os.rmdir(final_output_dir)
|
| 489 |
+
except OSError:
|
| 490 |
+
pass # Ignore if it fails (e.g. not empty for other reasons)
|
| 491 |
+
return None
|
| 492 |
+
|
| 493 |
+
with imageio.get_writer(str(gif_path_to_return), mode='I', fps=gif_fps_val, loop=0) as writer:
|
| 494 |
+
for filename in temp_image_files:
|
| 495 |
+
image = imageio.imread(filename)
|
| 496 |
+
writer.append_data(image)
|
| 497 |
+
|
| 498 |
+
print(f"Plotly animation saved to {gif_path_to_return}")
|
| 499 |
+
|
| 500 |
+
except Exception as e_plotly_anim:
|
| 501 |
+
print(f"Error during Plotly animation/saving: {e_plotly_anim}")
|
| 502 |
+
print("Ensure 'kaleido' and 'imageio' are installed ('pip install kaleido imageio').")
|
| 503 |
+
return None
|
| 504 |
+
|
| 505 |
+
return str(gif_path_to_return)
|
| 506 |
+
|
| 507 |
+
# --- Gradio Interface Definition ---
|
| 508 |
+
def qlbm_gradio_interface(num_reg_qubits_input: int, timescale_input: int, distribution_type_param: str, ux_param: float, uy_param: float):
|
| 509 |
+
num_reg_qubits_val = int(num_reg_qubits_input)
|
| 510 |
+
timescale_val = int(timescale_input)
|
| 511 |
+
ux_val = float(ux_param)
|
| 512 |
+
uy_val = float(uy_param)
|
| 513 |
+
|
| 514 |
+
print(f"Gradio Interface: num_reg_qubits={num_reg_qubits_val}, T={timescale_val}, Distribution={distribution_type_param}, ux={ux_val}, uy={uy_val}")
|
| 515 |
+
|
| 516 |
+
gif_path_output = simulate_qlbm_and_animate(
|
| 517 |
+
num_reg_qubits=num_reg_qubits_val,
|
| 518 |
+
T=timescale_val,
|
| 519 |
+
distribution_type=distribution_type_param,
|
| 520 |
+
ux_input=ux_val,
|
| 521 |
+
uy_input=uy_val
|
| 522 |
+
)
|
| 523 |
+
|
| 524 |
+
if gif_path_output is None:
|
| 525 |
+
gr.Warning("Animation generation failed. Please check console for errors.")
|
| 526 |
+
return None
|
| 527 |
+
return gif_path_output
|
| 528 |
+
|
| 529 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Simulation with Plotly") as qlbm_demo:
|
| 530 |
+
gr.Markdown(
|
| 531 |
+
"""
|
| 532 |
+
# ⚛️ Quantum Lattice Boltzmann Method (QLBM) Simulator (Plotly Animation)
|
| 533 |
+
Welcome to the Quantum Lattice Boltzmann Method (QLBM) simulator! This version uses Plotly for 3D animation.
|
| 534 |
+
|
| 535 |
+
**How this Simulator Works:**
|
| 536 |
+
This simulator implements a D2Q5 model on a quantum computer simulator (CUDA-Q).
|
| 537 |
+
- Control grid size (via Number of Register Qubits: $N=2^{\text{num_reg_qubits}}$).
|
| 538 |
+
- Set total simulation time (Timescale T).
|
| 539 |
+
- Choose initial distribution.
|
| 540 |
+
- Set advection velocities `ux` and `uy`.
|
| 541 |
+
The simulation evolves this state, and a Plotly animation of the density is generated as a GIF.
|
| 542 |
+
|
| 543 |
+
**Note:** Higher qubit counts and longer timescales are computationally intensive. Advection velocities should be small (e.g., < 0.3).
|
| 544 |
+
The output GIF will loop indefinitely. Values for "Number of Register Qubits" above 9 may be unstable or very slow on shared/limited hardware.
|
| 545 |
+
"""
|
| 546 |
+
)
|
| 547 |
+
with gr.Row():
|
| 548 |
+
with gr.Column(scale=1):
|
| 549 |
+
gr.Markdown("## Simulation Parameters")
|
| 550 |
+
num_reg_qubits_slider = gr.Slider(
|
| 551 |
+
minimum=2, maximum=10, value=8, step=1, # Max set to 10
|
| 552 |
+
label="Number of Register Qubits (num_reg_qubits)",
|
| 553 |
+
info="Grid N = 2^num_reg_qubits. Max 10 (Note: >8 slow; >9 may hit simulator/memory limits on free tiers)."
|
| 554 |
+
)
|
| 555 |
+
timescale_slider = gr.Slider(
|
| 556 |
+
minimum=0, maximum=2000, value=100, step=10,
|
| 557 |
+
label="Timescale (T)", info="Total number of timesteps. Max 2000."
|
| 558 |
+
)
|
| 559 |
+
distribution_options = ["Sine Wave (Original)", "Gaussian", "Random"]
|
| 560 |
+
distribution_type_input = gr.Radio(
|
| 561 |
+
choices=distribution_options, value="Sine Wave (Original)",
|
| 562 |
+
label="Initial Distribution Type", info="Select the initial pattern of the substance."
|
| 563 |
+
)
|
| 564 |
+
ux_slider = gr.Slider(
|
| 565 |
+
minimum=-0.4, maximum=0.4, value=0.2, step=0.01,
|
| 566 |
+
label="Advection Velocity ux", info="x-component of background advection."
|
| 567 |
+
)
|
| 568 |
+
uy_slider = gr.Slider(
|
| 569 |
+
minimum=-0.4, maximum=0.4, value=0.15, step=0.01,
|
| 570 |
+
label="Advection Velocity uy", info="y-component of background advection."
|
| 571 |
+
)
|
| 572 |
+
run_qlbm_btn = gr.Button("Run QLBM Simulation (Plotly)", variant="primary")
|
| 573 |
+
|
| 574 |
+
with gr.Column(scale=2):
|
| 575 |
+
qlbm_plot_output = gr.Image(label="QLBM Simulation Animation (Plotly GIF)", type="filepath", height=600)
|
| 576 |
+
|
| 577 |
+
qlbm_inputs_list = [num_reg_qubits_slider, timescale_slider, distribution_type_input, ux_slider, uy_slider]
|
| 578 |
+
run_qlbm_btn.click(
|
| 579 |
+
fn=qlbm_gradio_interface,
|
| 580 |
+
inputs=qlbm_inputs_list,
|
| 581 |
+
outputs=qlbm_plot_output
|
| 582 |
+
)
|
| 583 |
+
gr.Examples(
|
| 584 |
+
examples=[
|
| 585 |
+
[8, 100, "Sine Wave (Original)", 0.2, 0.15],
|
| 586 |
+
[6, 50, "Gaussian", 0.1, 0.05],
|
| 587 |
+
[4, 30, "Random", -0.05, 0.1],
|
| 588 |
+
],
|
| 589 |
+
inputs=qlbm_inputs_list,
|
| 590 |
+
outputs=qlbm_plot_output,
|
| 591 |
+
fn=qlbm_gradio_interface,
|
| 592 |
+
cache_examples=False
|
| 593 |
+
)
|
| 594 |
+
|
| 595 |
+
if __name__ == "__main__":
|
| 596 |
+
try:
|
| 597 |
+
cudaq.set_target('nvidia', option='fp64')
|
| 598 |
+
print(f"CUDA-Q Target successfully set to: {cudaq.get_target().name}")
|
| 599 |
+
except Exception as e_target:
|
| 600 |
+
print(f"Warning: Could not set CUDA-Q target to 'nvidia'. Error: {e_target}")
|
| 601 |
+
print(f"Current CUDA-Q Target: {cudaq.get_target().name}. Performance may be affected.")
|
| 602 |
+
|
| 603 |
+
qlbm_demo.launch()
|
app_o.py
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
import matplotlib
|
| 20 |
+
matplotlib.use('Agg')
|
| 21 |
+
import matplotlib.pyplot as plt
|
| 22 |
+
|
| 23 |
+
def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, ux_input: float, uy_input: float):
|
| 24 |
+
"""
|
| 25 |
+
Simulates a 2D advection-diffusion problem using a Quantum Lattice Boltzmann Method (QLBM)
|
| 26 |
+
and generates a GIF animation of the simulation's evolution.
|
| 27 |
+
|
| 28 |
+
Args:
|
| 29 |
+
num_reg_qubits (int): The number of register qubits (determines grid size N=2^num_reg_qubits).
|
| 30 |
+
T (int): The total number of timesteps to run the simulation.
|
| 31 |
+
distribution_type (str): The type of initial distribution to use.
|
| 32 |
+
ux_input (float): Advection velocity in the x-direction.
|
| 33 |
+
uy_input (float): Advection velocity in the y-direction.
|
| 34 |
+
|
| 35 |
+
Returns:
|
| 36 |
+
str: The file path to the generated GIF animation, or None if an error occurs.
|
| 37 |
+
"""
|
| 38 |
+
|
| 39 |
+
video_length = T
|
| 40 |
+
simulation_fps = 0.1
|
| 41 |
+
frames = int(simulation_fps * video_length)
|
| 42 |
+
if frames == 0:
|
| 43 |
+
frames = 1
|
| 44 |
+
|
| 45 |
+
num_anc = 3
|
| 46 |
+
num_qubits_total = 2 * num_reg_qubits + num_anc
|
| 47 |
+
current_N = 2**num_reg_qubits
|
| 48 |
+
N_tot_state_vector = 2**num_qubits_total
|
| 49 |
+
num_ranks = 1
|
| 50 |
+
rank = 0
|
| 51 |
+
N_sub_per_rank = int(N_tot_state_vector // num_ranks)
|
| 52 |
+
|
| 53 |
+
timesteps_per_frame = 1
|
| 54 |
+
if frames < T and frames > 0:
|
| 55 |
+
timesteps_per_frame = int(T / frames)
|
| 56 |
+
if timesteps_per_frame == 0:
|
| 57 |
+
timesteps_per_frame = 1
|
| 58 |
+
|
| 59 |
+
if distribution_type == "Sine Wave (Original)":
|
| 60 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 61 |
+
np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
|
| 62 |
+
np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
|
| 63 |
+
elif distribution_type == "Gaussian":
|
| 64 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 65 |
+
np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
|
| 66 |
+
(y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
|
| 67 |
+
elif distribution_type == "Random":
|
| 68 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 69 |
+
np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
|
| 70 |
+
else:
|
| 71 |
+
print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sine Wave.")
|
| 72 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 73 |
+
np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
|
| 74 |
+
np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
|
| 75 |
+
|
| 76 |
+
initial_state_func_eval = lambda x_coords, y_coords: \
|
| 77 |
+
selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
|
| 78 |
+
(y_coords < current_N).astype(int)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
with tempfile.TemporaryDirectory() as tmp_npy_dir:
|
| 82 |
+
intermediate_folder_path = Path(tmp_npy_dir)
|
| 83 |
+
|
| 84 |
+
cudaq.set_target('nvidia', option='fp64')
|
| 85 |
+
|
| 86 |
+
@cudaq.kernel
|
| 87 |
+
def alloc_kernel(num_qubits_alloc: int):
|
| 88 |
+
qubits = cudaq.qvector(num_qubits_alloc)
|
| 89 |
+
|
| 90 |
+
from cupy.cuda.memory import MemoryPointer, UnownedMemory
|
| 91 |
+
|
| 92 |
+
def to_cupy_array(state):
|
| 93 |
+
tensor = state.getTensor()
|
| 94 |
+
pDevice = tensor.data()
|
| 95 |
+
sizeByte = tensor.get_num_elements() * tensor.get_element_size()
|
| 96 |
+
mem = UnownedMemory(pDevice, sizeByte, owner=state)
|
| 97 |
+
memptr_obj = MemoryPointer(mem, 0)
|
| 98 |
+
cupy_array = cp.ndarray(tensor.get_num_elements(),
|
| 99 |
+
dtype=cp.complex128,
|
| 100 |
+
memptr=memptr_obj)
|
| 101 |
+
return cupy_array
|
| 102 |
+
|
| 103 |
+
class QLBMAdvecDiffD2Q5_new:
|
| 104 |
+
def __init__(self, ux=0.2, uy=0.15) -> None: # ux, uy are now passed here
|
| 105 |
+
self.dim = 2
|
| 106 |
+
self.ndir = 5
|
| 107 |
+
self.nq_dir = math.ceil(np.log2(self.ndir))
|
| 108 |
+
self.dirs = []
|
| 109 |
+
for dir_int in range(self.ndir):
|
| 110 |
+
dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
|
| 111 |
+
self.dirs.append(dir_bin)
|
| 112 |
+
self.e_unitvec = np.array([0, 1, -1, 1, -1])
|
| 113 |
+
self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
|
| 114 |
+
self.cs = 1 / np.sqrt(3)
|
| 115 |
+
self.ux = ux # Use passed ux
|
| 116 |
+
self.uy = uy # Use passed uy
|
| 117 |
+
self.u = np.array([0, self.ux, self.ux, self.uy, self.uy])
|
| 118 |
+
self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
|
| 119 |
+
self.create_circuit()
|
| 120 |
+
|
| 121 |
+
def create_circuit(self):
|
| 122 |
+
v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
|
| 123 |
+
v = v**0.5
|
| 124 |
+
v[0] += 1
|
| 125 |
+
v = v / np.linalg.norm(v)
|
| 126 |
+
U_prep = 2 * np.outer(v, v) - np.eye(len(v))
|
| 127 |
+
cudaq.register_operation("prep_op", U_prep)
|
| 128 |
+
|
| 129 |
+
def collisionOp(dirs):
|
| 130 |
+
dirs_i_list = []
|
| 131 |
+
for dir_ in dirs:
|
| 132 |
+
dirs_i = [(int(c)) for c in dir_]
|
| 133 |
+
dirs_i_list += dirs_i[::-1]
|
| 134 |
+
return dirs_i_list
|
| 135 |
+
|
| 136 |
+
self.dirs_i_list = collisionOp(self.dirs)
|
| 137 |
+
|
| 138 |
+
@cudaq.kernel
|
| 139 |
+
def rshift(q: cudaq.qview, n: int):
|
| 140 |
+
for i in range(n):
|
| 141 |
+
if i == n - 1:
|
| 142 |
+
x(q[n - 1 - i])
|
| 143 |
+
elif i == n - 2:
|
| 144 |
+
x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
|
| 145 |
+
else:
|
| 146 |
+
x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
|
| 147 |
+
|
| 148 |
+
@cudaq.kernel
|
| 149 |
+
def lshift(q: cudaq.qview, n: int):
|
| 150 |
+
for i in range(n):
|
| 151 |
+
if i == 0:
|
| 152 |
+
x(q[0])
|
| 153 |
+
elif i == 1:
|
| 154 |
+
x.ctrl(q[0], q[1])
|
| 155 |
+
else:
|
| 156 |
+
x.ctrl(q[0:i], q[i])
|
| 157 |
+
|
| 158 |
+
@cudaq.kernel
|
| 159 |
+
def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir: int, dirs_i: list[int]):
|
| 160 |
+
qx = q[0:nqx]
|
| 161 |
+
qy = q[nqx:nqx + nqy]
|
| 162 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir]
|
| 163 |
+
i = 2
|
| 164 |
+
b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
|
| 165 |
+
for j in range(nq_dir):
|
| 166 |
+
b = b_list[j]
|
| 167 |
+
if b == 0:
|
| 168 |
+
x(qdir[j])
|
| 169 |
+
cudaq.control(lshift, qdir, qx, nqx)
|
| 170 |
+
for j in range(nq_dir):
|
| 171 |
+
b = b_list[j]
|
| 172 |
+
if b == 0:
|
| 173 |
+
x(qdir[j])
|
| 174 |
+
i = 1
|
| 175 |
+
b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
|
| 176 |
+
for j in range(nq_dir):
|
| 177 |
+
b = b_list[j]
|
| 178 |
+
if b == 0:
|
| 179 |
+
x(qdir[j])
|
| 180 |
+
cudaq.control(rshift, qdir, qx, nqx)
|
| 181 |
+
for j in range(nq_dir):
|
| 182 |
+
b = b_list[j]
|
| 183 |
+
if b == 0:
|
| 184 |
+
x(qdir[j])
|
| 185 |
+
i = 4
|
| 186 |
+
b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
|
| 187 |
+
for j in range(nq_dir):
|
| 188 |
+
b = b_list[j]
|
| 189 |
+
if b == 0:
|
| 190 |
+
x(qdir[j])
|
| 191 |
+
cudaq.control(lshift, qdir, qy, nqy)
|
| 192 |
+
for j in range(nq_dir):
|
| 193 |
+
b = b_list[j]
|
| 194 |
+
if b == 0:
|
| 195 |
+
x(qdir[j])
|
| 196 |
+
i = 3
|
| 197 |
+
b_list = dirs_i[i * nq_dir:(i + 1) * nq_dir]
|
| 198 |
+
for j in range(nq_dir):
|
| 199 |
+
b = b_list[j]
|
| 200 |
+
if b == 0:
|
| 201 |
+
x(qdir[j])
|
| 202 |
+
cudaq.control(rshift, qdir, qy, nqy)
|
| 203 |
+
for j in range(nq_dir):
|
| 204 |
+
b = b_list[j]
|
| 205 |
+
if b == 0:
|
| 206 |
+
x(qdir[j])
|
| 207 |
+
|
| 208 |
+
@cudaq.kernel
|
| 209 |
+
def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir: int, dirs_i: list[int]):
|
| 210 |
+
q = cudaq.qvector(state_arg)
|
| 211 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir]
|
| 212 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 213 |
+
d2q5_tstep(q, nqx, nqy, nq_dir, dirs_i)
|
| 214 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 215 |
+
|
| 216 |
+
@cudaq.kernel
|
| 217 |
+
def d2q5_tstep_wrapper_hadamard(vec: list[complex], nqx: int, nqy: int, nq_dir: int, dirs_i: list[int]):
|
| 218 |
+
q = cudaq.qvector(vec)
|
| 219 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir]
|
| 220 |
+
qy = q[nqx:nqx + nqy]
|
| 221 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 222 |
+
d2q5_tstep(q, nqx, nqy, nq_dir, dirs_i)
|
| 223 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 224 |
+
for i in range(nqy):
|
| 225 |
+
h(qy[i])
|
| 226 |
+
|
| 227 |
+
def run_timestep_func(vec, hadamard=False):
|
| 228 |
+
if hadamard:
|
| 229 |
+
result = cudaq.get_state(d2q5_tstep_wrapper_hadamard, vec, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
|
| 230 |
+
else:
|
| 231 |
+
result = cudaq.get_state(d2q5_tstep_wrapper, vec, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
|
| 232 |
+
|
| 233 |
+
num_nonzero_ranks = num_ranks / (2**num_anc)
|
| 234 |
+
rank_slice_cupy = to_cupy_array(result)
|
| 235 |
+
|
| 236 |
+
if rank >= num_nonzero_ranks:
|
| 237 |
+
sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
|
| 238 |
+
cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 239 |
+
|
| 240 |
+
if rank == 0 and num_nonzero_ranks < 1:
|
| 241 |
+
sub_sv_get = (rank_slice_cupy).get()
|
| 242 |
+
sub_sv_get[int(N_tot_state_vector / (2**num_anc)):] = 0
|
| 243 |
+
cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_get.ctypes.data, sub_sv_get.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 244 |
+
return result
|
| 245 |
+
|
| 246 |
+
self.run_timestep = run_timestep_func
|
| 247 |
+
|
| 248 |
+
def write_state(self, state_to_write, t_step):
|
| 249 |
+
rank_slice_cupy = to_cupy_array(state_to_write)
|
| 250 |
+
num_nonzero_ranks = num_ranks / (2**num_anc)
|
| 251 |
+
|
| 252 |
+
if rank < num_nonzero_ranks:
|
| 253 |
+
save_path = intermediate_folder_path / f"{t_step}_{rank}.npy"
|
| 254 |
+
with open(save_path, 'wb') as f:
|
| 255 |
+
arr_to_save = None
|
| 256 |
+
if num_nonzero_ranks < 1:
|
| 257 |
+
arr_to_save = cp.real(rank_slice_cupy)[:int(N_tot_state_vector / (2**num_anc))]
|
| 258 |
+
else:
|
| 259 |
+
arr_to_save = cp.real(rank_slice_cupy)
|
| 260 |
+
|
| 261 |
+
if len(arr_to_save) > current_N :
|
| 262 |
+
arr_to_save=arr_to_save.reshape((current_N, current_N))
|
| 263 |
+
arr_to_save=arr_to_save[::downsampling_factor,::downsampling_factor]
|
| 264 |
+
arr_to_save=arr_to_save.flatten()
|
| 265 |
+
elif len(arr_to_save) > 0 :
|
| 266 |
+
arr_to_save=arr_to_save[::downsampling_factor]
|
| 267 |
+
|
| 268 |
+
np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def run_evolution(self, initial_state_arg, total_timesteps, observable=False):
|
| 272 |
+
current_state = initial_state_arg
|
| 273 |
+
for t_iter in range(total_timesteps):
|
| 274 |
+
next_state = None
|
| 275 |
+
if t_iter == total_timesteps - 1 and observable:
|
| 276 |
+
next_state = self.run_timestep(current_state, True)
|
| 277 |
+
self.write_state(next_state, str(t_iter + 1) + "_h")
|
| 278 |
+
else:
|
| 279 |
+
next_state = self.run_timestep(current_state)
|
| 280 |
+
if (t_iter + 1) % timesteps_per_frame == 0:
|
| 281 |
+
self.write_state(next_state, t_iter + 1)
|
| 282 |
+
if rank == 0:
|
| 283 |
+
print(f"Timestep: {t_iter + 1}/{total_timesteps}")
|
| 284 |
+
|
| 285 |
+
cp.get_default_memory_pool().free_all_blocks()
|
| 286 |
+
current_state = next_state
|
| 287 |
+
|
| 288 |
+
last_saved_timestep_name = str(total_timesteps) if not observable else str(total_timesteps) + "_h"
|
| 289 |
+
# Check if the very last state (T or T_h) was potentially saved by the loop or observable condition.
|
| 290 |
+
# If not, save it.
|
| 291 |
+
final_state_file_path = intermediate_folder_path.joinpath(f"{last_saved_timestep_name}_{rank}.npy")
|
| 292 |
+
# Also check if the non-observable final T state was saved if T aligns with timesteps_per_frame
|
| 293 |
+
final_T_state_file_path = intermediate_folder_path.joinpath(f"{total_timesteps}_{rank}.npy")
|
| 294 |
+
|
| 295 |
+
needs_final_save = True
|
| 296 |
+
if observable and final_state_file_path.exists(): # T_h was saved
|
| 297 |
+
needs_final_save = False
|
| 298 |
+
elif not observable and final_T_state_file_path.exists() and total_timesteps % timesteps_per_frame == 0 : # T was saved by loop
|
| 299 |
+
needs_final_save = False
|
| 300 |
+
elif not observable and final_T_state_file_path.exists(): # T was saved by a previous final write
|
| 301 |
+
needs_final_save = False
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
if needs_final_save and total_timesteps > 0:
|
| 305 |
+
if not observable: # Save as T_0.npy
|
| 306 |
+
self.write_state(current_state, total_timesteps)
|
| 307 |
+
# If observable, it should have been saved as T_h. If it wasn't (e.g. T=0), this won't run.
|
| 308 |
+
|
| 309 |
+
if rank == 0:
|
| 310 |
+
print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
|
| 311 |
+
cp.get_default_memory_pool().free_all_blocks()
|
| 312 |
+
self.final_state = current_state
|
| 313 |
+
|
| 314 |
+
downsampling_factor = 2**5
|
| 315 |
+
# Pass ux_input and uy_input to the constructor
|
| 316 |
+
qlbm_obj = QLBMAdvecDiffD2Q5_new(ux=ux_input, uy=uy_input)
|
| 317 |
+
|
| 318 |
+
initial_state = cudaq.get_state(alloc_kernel, num_qubits_total)
|
| 319 |
+
rank_slice_init = to_cupy_array(initial_state)
|
| 320 |
+
|
| 321 |
+
xv_init = np.arange(current_N)
|
| 322 |
+
yv_init = np.arange(current_N)
|
| 323 |
+
|
| 324 |
+
print(f'Start initializing state with {distribution_type} distribution (ux={ux_input}, uy={uy_input})...')
|
| 325 |
+
initial_grid_2d = initial_state_func_eval(*np.meshgrid(xv_init, yv_init))
|
| 326 |
+
sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
|
| 327 |
+
|
| 328 |
+
full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
|
| 329 |
+
num_computational_states = current_N * current_N
|
| 330 |
+
|
| 331 |
+
if len(sub_sv_init_flat) == num_computational_states:
|
| 332 |
+
full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
|
| 333 |
+
else:
|
| 334 |
+
print(f"Warning: Mismatch in expected initial state size {num_computational_states} and calculated {len(sub_sv_init_flat)}")
|
| 335 |
+
full_initial_sv_host[:len(sub_sv_init_flat)] = sub_sv_init_flat
|
| 336 |
+
|
| 337 |
+
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}')
|
| 338 |
+
cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 339 |
+
print(f'Rank {rank}: Initial state copied to device.')
|
| 340 |
+
|
| 341 |
+
print("Starting QLBM evolution...")
|
| 342 |
+
qlbm_obj.run_evolution(initial_state, T)
|
| 343 |
+
print("QLBM evolution complete.")
|
| 344 |
+
|
| 345 |
+
print("Generating animation...")
|
| 346 |
+
downsampled_N = current_N // downsampling_factor
|
| 347 |
+
if downsampled_N == 0:
|
| 348 |
+
print("Error: Downsampled grid size is zero. Check num_reg_qubits and downsampling_factor.")
|
| 349 |
+
downsampled_N = 1
|
| 350 |
+
|
| 351 |
+
plotted_timesteps_str = [] # To store frame names (can include '_h')
|
| 352 |
+
if timesteps_per_frame > 0:
|
| 353 |
+
# Add regular frames saved by the loop
|
| 354 |
+
for t_step in range(timesteps_per_frame, T + 1, timesteps_per_frame):
|
| 355 |
+
if intermediate_folder_path.joinpath(f"{t_step}_0.npy").exists():
|
| 356 |
+
plotted_timesteps_str.append(str(t_step))
|
| 357 |
+
|
| 358 |
+
# Ensure the very last timestep (T or T_h) is considered for plotting
|
| 359 |
+
final_T_path = intermediate_folder_path.joinpath(f"{T}_0.npy")
|
| 360 |
+
final_Th_path = intermediate_folder_path.joinpath(f"{T}_h_0.npy")
|
| 361 |
+
|
| 362 |
+
if T > 0:
|
| 363 |
+
if final_Th_path.exists(): # Prefer T_h if it exists
|
| 364 |
+
if str(T)+"_h" not in plotted_timesteps_str:
|
| 365 |
+
plotted_timesteps_str.append(str(T)+"_h")
|
| 366 |
+
elif final_T_path.exists(): # Else, use T if it exists
|
| 367 |
+
if str(T) not in plotted_timesteps_str:
|
| 368 |
+
plotted_timesteps_str.append(str(T))
|
| 369 |
+
|
| 370 |
+
# Remove duplicates and sort (handle numeric and '_h' strings appropriately for sorting if needed, though string sort is often fine here)
|
| 371 |
+
plotted_timesteps_str = sorted(list(set(plotted_timesteps_str)), key=lambda k: (int(str(k).replace('_h','')), str(k)))
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
if not plotted_timesteps_str and T > 0 : # Fallback if T is small and not caught by loop
|
| 375 |
+
if final_Th_path.exists(): plotted_timesteps_str = [str(T)+"_h"]
|
| 376 |
+
elif final_T_path.exists(): plotted_timesteps_str = [str(T)]
|
| 377 |
+
elif not plotted_timesteps_str and T == 0:
|
| 378 |
+
print("Warning: T=0, no frames to plot for animation.")
|
| 379 |
+
return None
|
| 380 |
+
|
| 381 |
+
data = []
|
| 382 |
+
actual_timesteps_for_title = []
|
| 383 |
+
|
| 384 |
+
for i_str in plotted_timesteps_str:
|
| 385 |
+
try:
|
| 386 |
+
file_path = intermediate_folder_path / f"{i_str}_0.npy"
|
| 387 |
+
sol = np.load(file_path)
|
| 388 |
+
if sol.size == downsampled_N * downsampled_N:
|
| 389 |
+
Z = np.reshape(sol, (downsampled_N, downsampled_N))
|
| 390 |
+
data.append(Z)
|
| 391 |
+
actual_timesteps_for_title.append(str(i_str).replace('_h',''))
|
| 392 |
+
else:
|
| 393 |
+
print(f"Warning: File {file_path} has unexpected size {sol.size}. Expected {downsampled_N*downsampled_N}. Skipping this frame.")
|
| 394 |
+
continue
|
| 395 |
+
except FileNotFoundError:
|
| 396 |
+
print(f"Warning: File not found for timestep {i_str} ({file_path}). Skipping this frame.")
|
| 397 |
+
continue
|
| 398 |
+
except Exception as e:
|
| 399 |
+
print(f"Error loading or reshaping file for timestep {i_str}: {e}. Skipping this frame.")
|
| 400 |
+
continue
|
| 401 |
+
|
| 402 |
+
if not data:
|
| 403 |
+
print("Error: No data available to create animation. Check .npy file generation and paths.")
|
| 404 |
+
return None
|
| 405 |
+
|
| 406 |
+
fig = plt.figure(figsize=(10, 8))
|
| 407 |
+
ax = fig.add_subplot(111, projection='3d')
|
| 408 |
+
|
| 409 |
+
x_plot = np.linspace(-10, 10, downsampled_N)
|
| 410 |
+
y_plot = np.linspace(-10, 10, downsampled_N)
|
| 411 |
+
X_plot, Y_plot = np.meshgrid(x_plot, y_plot)
|
| 412 |
+
|
| 413 |
+
norm_factor = np.linalg.norm(data[0]) if data[0].size > 0 else 1.0
|
| 414 |
+
if norm_factor == 0:
|
| 415 |
+
norm_factor = 1.0
|
| 416 |
+
|
| 417 |
+
def update_frame(frame_idx):
|
| 418 |
+
ax.clear()
|
| 419 |
+
Z = data[frame_idx]
|
| 420 |
+
surf = ax.plot_surface(X_plot, Y_plot, Z / norm_factor, cmap='viridis', linewidth=0, antialiased=False)
|
| 421 |
+
current_timestep_for_title = actual_timesteps_for_title[frame_idx] if frame_idx < len(actual_timesteps_for_title) else "Unknown"
|
| 422 |
+
ax.set_title(f'Quantum Simulation Evolution (Timestep: {current_timestep_for_title})')
|
| 423 |
+
ax.set_xlabel('x')
|
| 424 |
+
ax.set_ylabel('y')
|
| 425 |
+
ax.set_zlim(0, 0.6)
|
| 426 |
+
return surf,
|
| 427 |
+
|
| 428 |
+
gif_animation_fps = 10
|
| 429 |
+
ani = FuncAnimation(fig, update_frame, frames=len(data), blit=False)
|
| 430 |
+
|
| 431 |
+
results_base_dir = "Results"
|
| 432 |
+
gif_frames_for_naming = int(simulation_fps * T)
|
| 433 |
+
if gif_frames_for_naming == 0: gif_frames_for_naming = 1
|
| 434 |
+
|
| 435 |
+
# Sanitize distribution_type for filename
|
| 436 |
+
dist_name_part = distribution_type.replace(' ','').replace('(Original)','').replace('(','').replace(')','')
|
| 437 |
+
|
| 438 |
+
specific_folder_name = (f"d2q5_nq{current_N}x{current_N}_T{T}_fr{gif_frames_for_naming}_"
|
| 439 |
+
f"dist{dist_name_part}_ux{ux_input:.2f}_uy{uy_input:.2f}")
|
| 440 |
+
final_output_dir = Path(results_base_dir) / specific_folder_name
|
| 441 |
+
os.makedirs(final_output_dir, exist_ok=True)
|
| 442 |
+
gif_path_to_return = final_output_dir / "animation.gif"
|
| 443 |
+
|
| 444 |
+
try:
|
| 445 |
+
ani.save(str(gif_path_to_return), writer='pillow', fps=gif_animation_fps)
|
| 446 |
+
print(f"Animation saved to {gif_path_to_return}")
|
| 447 |
+
except Exception as e:
|
| 448 |
+
print(f"Error saving animation: {e}")
|
| 449 |
+
plt.close(fig)
|
| 450 |
+
return None
|
| 451 |
+
plt.close(fig)
|
| 452 |
+
return str(gif_path_to_return)
|
| 453 |
+
|
| 454 |
+
# --- Gradio Interface Definition ---
|
| 455 |
+
|
| 456 |
+
def qlbm_gradio_interface(num_reg_qubits_input: int, timescale_input: int, distribution_type_param: str, ux_param: float, uy_param: float):
|
| 457 |
+
num_reg_qubits_val = int(num_reg_qubits_input)
|
| 458 |
+
timescale_val = int(timescale_input)
|
| 459 |
+
ux_val = float(ux_param)
|
| 460 |
+
uy_val = float(uy_param)
|
| 461 |
+
|
| 462 |
+
print(f"Gradio Interface: num_reg_qubits={num_reg_qubits_val}, T={timescale_val}, Distribution={distribution_type_param}, ux={ux_val}, uy={uy_val}")
|
| 463 |
+
|
| 464 |
+
gif_path_output = simulate_qlbm_and_animate(
|
| 465 |
+
num_reg_qubits=num_reg_qubits_val,
|
| 466 |
+
T=timescale_val,
|
| 467 |
+
distribution_type=distribution_type_param,
|
| 468 |
+
ux_input=ux_val,
|
| 469 |
+
uy_input=uy_val
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
if gif_path_output is None:
|
| 473 |
+
gr.Warning("Animation generation failed. Please check console for errors.")
|
| 474 |
+
return None
|
| 475 |
+
return gif_path_output
|
| 476 |
+
|
| 477 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Simulation") as qlbm_demo:
|
| 478 |
+
gr.Markdown(
|
| 479 |
+
"""
|
| 480 |
+
# ⚛️ Quantum Lattice Boltzmann Method (QLBM) Simulator
|
| 481 |
+
Welcome to the Quantum Lattice Boltzmann Method (QLBM) simulator!
|
| 482 |
+
|
| 483 |
+
**What is QLBM?**
|
| 484 |
+
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).
|
| 485 |
+
|
| 486 |
+
**How this Simulator Works:**
|
| 487 |
+
This simulator implements a D2Q5 model (2 dimensions, 5 velocity directions) on a quantum computer simulator (using CUDA-Q).
|
| 488 |
+
- You can control the grid size (via Number of Register Qubits, where grid size is $2^N \\times 2^N$).
|
| 489 |
+
- You can set the total simulation time (Timescale T).
|
| 490 |
+
- You can choose the initial distribution of the substance on the grid.
|
| 491 |
+
- You can set the advection velocities `ux` (x-direction) and `uy` (y-direction).
|
| 492 |
+
The simulation will then evolve this initial state over time, and an animation of the substance's density will be generated.
|
| 493 |
+
|
| 494 |
+
**Note:** Simulations with higher qubit counts and longer timescales can be computationally intensive and may take a significant amount of time to complete.
|
| 495 |
+
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.
|
| 496 |
+
"""
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
with gr.Row():
|
| 500 |
+
with gr.Column(scale=1):
|
| 501 |
+
gr.Markdown("## Simulation Parameters")
|
| 502 |
+
num_reg_qubits_slider = gr.Slider(
|
| 503 |
+
minimum=8,
|
| 504 |
+
maximum=30,
|
| 505 |
+
value=8,
|
| 506 |
+
step=1,
|
| 507 |
+
label="Number of Register Qubits (num_reg_qubits)",
|
| 508 |
+
info="Determines the N for grid size (2^N x 2^N). Max 30 (Note: >8 can be very slow)."
|
| 509 |
+
)
|
| 510 |
+
timescale_slider = gr.Slider(
|
| 511 |
+
minimum=10,
|
| 512 |
+
maximum=10000,
|
| 513 |
+
value=100,
|
| 514 |
+
step=100,
|
| 515 |
+
label="Timestep (T)",
|
| 516 |
+
info="Total number of timesteps. Max 2000 (Note: higher values increase run time significantly)."
|
| 517 |
+
)
|
| 518 |
+
distribution_options = ["Sine Wave (Original)", "Gaussian", "Random"]
|
| 519 |
+
distribution_type_input = gr.Radio(
|
| 520 |
+
choices=distribution_options,
|
| 521 |
+
value="Sine Wave (Original)",
|
| 522 |
+
label="Initial Distribution Type",
|
| 523 |
+
info="Select the initial pattern of the substance on the grid."
|
| 524 |
+
)
|
| 525 |
+
ux_slider = gr.Slider(
|
| 526 |
+
minimum=-0.4,
|
| 527 |
+
maximum=0.4,
|
| 528 |
+
value=0.2,
|
| 529 |
+
step=0.01,
|
| 530 |
+
label="Advection Velocity ux",
|
| 531 |
+
info="x-component of the background advection velocity."
|
| 532 |
+
)
|
| 533 |
+
uy_slider = gr.Slider(
|
| 534 |
+
minimum=-0.4,
|
| 535 |
+
maximum=0.4,
|
| 536 |
+
value=0.15,
|
| 537 |
+
step=0.01,
|
| 538 |
+
label="Advection Velocity uy",
|
| 539 |
+
info="y-component of the background advection velocity."
|
| 540 |
+
)
|
| 541 |
+
|
| 542 |
+
run_qlbm_btn = gr.Button("Run QLBM Simulation", variant="primary")
|
| 543 |
+
|
| 544 |
+
with gr.Column(scale=3):
|
| 545 |
+
qlbm_plot_output = gr.Image(label="QLBM Simulation Animation", type="filepath")
|
| 546 |
+
|
| 547 |
+
qlbm_inputs_list = [num_reg_qubits_slider, timescale_slider, distribution_type_input, ux_slider, uy_slider]
|
| 548 |
+
|
| 549 |
+
run_qlbm_btn.click(
|
| 550 |
+
fn=qlbm_gradio_interface,
|
| 551 |
+
inputs=qlbm_inputs_list,
|
| 552 |
+
outputs=qlbm_plot_output
|
| 553 |
+
)
|
| 554 |
+
|
| 555 |
+
gr.Examples(
|
| 556 |
+
examples=[
|
| 557 |
+
[8, 100, "Sine Wave (Original)", 0.2, 0.15],
|
| 558 |
+
[6, 50, "Gaussian", 0.1, 0.05],
|
| 559 |
+
[4, 30, "Random", -0.05, 0.1],
|
| 560 |
+
],
|
| 561 |
+
inputs=qlbm_inputs_list,
|
| 562 |
+
outputs=qlbm_plot_output,
|
| 563 |
+
fn=qlbm_gradio_interface,
|
| 564 |
+
cache_examples=False
|
| 565 |
+
)
|
| 566 |
+
|
| 567 |
+
if __name__ == "__main__":
|
| 568 |
+
qlbm_demo.launch()
|
app_pyvista.py
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys # Not explicitly used in the core logic, but often good for path manipulations
|
| 3 |
+
# import time # Not explicitly used in the core logic after modifications
|
| 4 |
+
import math
|
| 5 |
+
# import threading # Not explicitly used in the core logic
|
| 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 |
+
# --- PyVista Import ---
|
| 17 |
+
import pyvista as pv
|
| 18 |
+
# Set a global theme for PyVista if desired (optional, can be set per plotter too)
|
| 19 |
+
pv.global_theme.anti_aliasing = 'fxaa' # Smooths plot edges
|
| 20 |
+
pv.global_theme.lighting = True # Enable lighting for better 3D effect
|
| 21 |
+
|
| 22 |
+
# --- Removed Matplotlib imports ---
|
| 23 |
+
# from matplotlib import pyplot as plt
|
| 24 |
+
# from matplotlib.animation import FuncAnimation
|
| 25 |
+
# import matplotlib
|
| 26 |
+
# matplotlib.use('Agg') # Not needed for PyVista off-screen plotting
|
| 27 |
+
# import matplotlib.pyplot as plt # Duplicate, also removed
|
| 28 |
+
|
| 29 |
+
def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, ux_input: float, uy_input: float):
|
| 30 |
+
"""
|
| 31 |
+
Simulates a 2D advection-diffusion problem using a Quantum Lattice Boltzmann Method (QLBM)
|
| 32 |
+
and generates a GIF animation of the simulation's evolution using PyVista.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
num_reg_qubits (int): The number of register qubits (determines grid size N=2^num_reg_qubits).
|
| 36 |
+
T (int): The total number of timesteps to run the simulation.
|
| 37 |
+
distribution_type (str): The type of initial distribution to use.
|
| 38 |
+
ux_input (float): Advection velocity in the x-direction.
|
| 39 |
+
uy_input (float): Advection velocity in the y-direction.
|
| 40 |
+
|
| 41 |
+
Returns:
|
| 42 |
+
str: The file path to the generated GIF animation, or None if an error occurs.
|
| 43 |
+
"""
|
| 44 |
+
|
| 45 |
+
video_length = T
|
| 46 |
+
simulation_fps = 0.1
|
| 47 |
+
frames = int(simulation_fps * video_length)
|
| 48 |
+
if frames == 0:
|
| 49 |
+
frames = 1
|
| 50 |
+
|
| 51 |
+
num_anc = 3
|
| 52 |
+
num_qubits_total = 2 * num_reg_qubits + num_anc
|
| 53 |
+
current_N = 2**num_reg_qubits
|
| 54 |
+
N_tot_state_vector = 2**num_qubits_total
|
| 55 |
+
num_ranks = 1
|
| 56 |
+
rank = 0
|
| 57 |
+
N_sub_per_rank = int(N_tot_state_vector // num_ranks)
|
| 58 |
+
|
| 59 |
+
timesteps_per_frame = 1
|
| 60 |
+
if frames < T and frames > 0:
|
| 61 |
+
timesteps_per_frame = int(T / frames)
|
| 62 |
+
if timesteps_per_frame == 0:
|
| 63 |
+
timesteps_per_frame = 1
|
| 64 |
+
|
| 65 |
+
# Initial state selection (as per the provided snippet)
|
| 66 |
+
if distribution_type == "Sine Wave (Original)":
|
| 67 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 68 |
+
np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
|
| 69 |
+
np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
|
| 70 |
+
elif distribution_type == "Gaussian":
|
| 71 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 72 |
+
np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
|
| 73 |
+
(y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
|
| 74 |
+
elif distribution_type == "Random":
|
| 75 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 76 |
+
np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
|
| 77 |
+
else:
|
| 78 |
+
print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sine Wave.")
|
| 79 |
+
selected_initial_state_function_raw = lambda x, y, N_val_func: \
|
| 80 |
+
np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
|
| 81 |
+
np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
|
| 82 |
+
|
| 83 |
+
initial_state_func_eval = lambda x_coords, y_coords: \
|
| 84 |
+
selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
|
| 85 |
+
(y_coords < current_N).astype(int)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
with tempfile.TemporaryDirectory() as tmp_npy_dir:
|
| 89 |
+
intermediate_folder_path = Path(tmp_npy_dir)
|
| 90 |
+
|
| 91 |
+
cudaq.set_target('nvidia', option='fp64') # As per user's snippet
|
| 92 |
+
|
| 93 |
+
@cudaq.kernel
|
| 94 |
+
def alloc_kernel(num_qubits_alloc: int):
|
| 95 |
+
qubits = cudaq.qvector(num_qubits_alloc)
|
| 96 |
+
|
| 97 |
+
from cupy.cuda.memory import MemoryPointer, UnownedMemory
|
| 98 |
+
|
| 99 |
+
def to_cupy_array(state):
|
| 100 |
+
tensor = state.getTensor()
|
| 101 |
+
pDevice = tensor.data()
|
| 102 |
+
sizeByte = tensor.get_num_elements() * tensor.get_element_size()
|
| 103 |
+
mem = UnownedMemory(pDevice, sizeByte, owner=state)
|
| 104 |
+
memptr_obj = MemoryPointer(mem, 0)
|
| 105 |
+
cupy_array_val = cp.ndarray(tensor.get_num_elements(), # Renamed cupy_array
|
| 106 |
+
dtype=cp.complex128,
|
| 107 |
+
memptr=memptr_obj)
|
| 108 |
+
return cupy_array_val
|
| 109 |
+
|
| 110 |
+
class QLBMAdvecDiffD2Q5_new:
|
| 111 |
+
def __init__(self, ux=0.2, uy=0.15) -> None: # ux, uy are now passed here
|
| 112 |
+
self.dim = 2
|
| 113 |
+
self.ndir = 5
|
| 114 |
+
self.nq_dir = math.ceil(np.log2(self.ndir))
|
| 115 |
+
self.dirs = []
|
| 116 |
+
for dir_int in range(self.ndir):
|
| 117 |
+
dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
|
| 118 |
+
self.dirs.append(dir_bin)
|
| 119 |
+
self.e_unitvec = np.array([0, 1, -1, 1, -1])
|
| 120 |
+
self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
|
| 121 |
+
self.cs = 1 / np.sqrt(3)
|
| 122 |
+
self.ux = ux # Use passed ux
|
| 123 |
+
self.uy = uy # Use passed uy
|
| 124 |
+
self.u = np.array([0, self.ux, self.ux, self.uy, self.uy])
|
| 125 |
+
self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
|
| 126 |
+
self.create_circuit()
|
| 127 |
+
|
| 128 |
+
def create_circuit(self):
|
| 129 |
+
v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
|
| 130 |
+
v = v**0.5
|
| 131 |
+
v[0] += 1 # This specific logic from original snippet
|
| 132 |
+
v = v / np.linalg.norm(v)
|
| 133 |
+
U_prep = 2 * np.outer(v, v) - np.eye(len(v))
|
| 134 |
+
cudaq.register_operation("prep_op", U_prep)
|
| 135 |
+
|
| 136 |
+
def collisionOp(dirs_list): # Renamed dirs
|
| 137 |
+
dirs_i_list_val = [] # Renamed dirs_i_list
|
| 138 |
+
for dir_str in dirs_list: # Renamed dir_
|
| 139 |
+
dirs_i = [(int(c)) for c in dir_str]
|
| 140 |
+
dirs_i_list_val += dirs_i[::-1]
|
| 141 |
+
return dirs_i_list_val
|
| 142 |
+
|
| 143 |
+
self.dirs_i_list = collisionOp(self.dirs)
|
| 144 |
+
|
| 145 |
+
@cudaq.kernel
|
| 146 |
+
def rshift(q: cudaq.qview, n: int):
|
| 147 |
+
for i in range(n):
|
| 148 |
+
if i == n - 1:
|
| 149 |
+
x(q[n - 1 - i])
|
| 150 |
+
elif i == n - 2:
|
| 151 |
+
x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
|
| 152 |
+
else:
|
| 153 |
+
x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
|
| 154 |
+
|
| 155 |
+
@cudaq.kernel
|
| 156 |
+
def lshift(q: cudaq.qview, n: int):
|
| 157 |
+
for i in range(n):
|
| 158 |
+
if i == 0:
|
| 159 |
+
x(q[0])
|
| 160 |
+
elif i == 1:
|
| 161 |
+
x.ctrl(q[0], q[1])
|
| 162 |
+
else:
|
| 163 |
+
x.ctrl(q[0:i], q[i])
|
| 164 |
+
|
| 165 |
+
@cudaq.kernel
|
| 166 |
+
def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]): # Renamed args
|
| 167 |
+
qx = q[0:nqx]
|
| 168 |
+
qy = q[nqx:nqx + nqy]
|
| 169 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir_val] # Renamed nq_dir
|
| 170 |
+
# Original logic for d2q5_tstep based on fixed indices i=1,2,3,4
|
| 171 |
+
# This assumes a specific mapping of self.dirs indices to shift operations.
|
| 172 |
+
# For +x (rshift qx), assume it corresponds to i=1's block in dirs_i_val
|
| 173 |
+
# For -x (lshift qx), assume it corresponds to i=2's block in dirs_i_val
|
| 174 |
+
# For +y (rshift qy), assume it corresponds to i=3's block in dirs_i_val
|
| 175 |
+
# For -y (lshift qy), assume it corresponds to i=4's block in dirs_i_val
|
| 176 |
+
# Important: The `dirs_i_val` is constructed based on `self.dirs`. The mapping between `self.dirs[i]`
|
| 177 |
+
# and the actual shift operation (+x, -x, +y, -y) must be consistent with these hardcoded indices.
|
| 178 |
+
# The snippet used i=1,2,3,4 which implies self.dirs[1] to self.dirs[4] are active.
|
| 179 |
+
|
| 180 |
+
# Example interpretation (matching original snippet structure):
|
| 181 |
+
# Original i=2 -> lshift qx (-x)
|
| 182 |
+
idx_lqx = 2
|
| 183 |
+
b_list = dirs_i_val[idx_lqx * nq_dir_val:(idx_lqx + 1) * nq_dir_val]
|
| 184 |
+
for j in range(nq_dir_val):
|
| 185 |
+
if b_list[j] == 0: x(qdir[j])
|
| 186 |
+
cudaq.control(lshift, qdir, qx, nqx)
|
| 187 |
+
for j in range(nq_dir_val):
|
| 188 |
+
if b_list[j] == 0: x(qdir[j])
|
| 189 |
+
|
| 190 |
+
# Original i=1 -> rshift qx (+x)
|
| 191 |
+
idx_rqx = 1
|
| 192 |
+
b_list = dirs_i_val[idx_rqx * nq_dir_val:(idx_rqx + 1) * nq_dir_val]
|
| 193 |
+
for j in range(nq_dir_val):
|
| 194 |
+
if b_list[j] == 0: x(qdir[j])
|
| 195 |
+
cudaq.control(rshift, qdir, qx, nqx)
|
| 196 |
+
for j in range(nq_dir_val):
|
| 197 |
+
if b_list[j] == 0: x(qdir[j])
|
| 198 |
+
|
| 199 |
+
# Original i=4 -> lshift qy (-y)
|
| 200 |
+
idx_lqy = 4
|
| 201 |
+
b_list = dirs_i_val[idx_lqy * nq_dir_val:(idx_lqy + 1) * nq_dir_val]
|
| 202 |
+
for j in range(nq_dir_val):
|
| 203 |
+
if b_list[j] == 0: x(qdir[j])
|
| 204 |
+
cudaq.control(lshift, qdir, qy, nqy)
|
| 205 |
+
for j in range(nq_dir_val):
|
| 206 |
+
if b_list[j] == 0: x(qdir[j])
|
| 207 |
+
|
| 208 |
+
# Original i=3 -> rshift qy (+y)
|
| 209 |
+
idx_rqy = 3
|
| 210 |
+
b_list = dirs_i_val[idx_rqy * nq_dir_val:(idx_rqy + 1) * nq_dir_val]
|
| 211 |
+
for j in range(nq_dir_val):
|
| 212 |
+
if b_list[j] == 0: x(qdir[j])
|
| 213 |
+
cudaq.control(rshift, qdir, qy, nqy)
|
| 214 |
+
for j in range(nq_dir_val):
|
| 215 |
+
if b_list[j] == 0: x(qdir[j])
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
@cudaq.kernel
|
| 219 |
+
def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]): # Renamed args
|
| 220 |
+
q = cudaq.qvector(state_arg)
|
| 221 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
|
| 222 |
+
prep_op(qdir[2], qdir[1], qdir[0]) # Assuming nq_dir_val is always 3 for prep_op
|
| 223 |
+
d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
|
| 224 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 225 |
+
|
| 226 |
+
@cudaq.kernel
|
| 227 |
+
def d2q5_tstep_wrapper_hadamard(vec_arg: list[complex], nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]): # Renamed args
|
| 228 |
+
q = cudaq.qvector(vec_arg) # Renamed vec
|
| 229 |
+
qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
|
| 230 |
+
qy = q[nqx:nqx + nqy]
|
| 231 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 232 |
+
d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
|
| 233 |
+
prep_op(qdir[2], qdir[1], qdir[0])
|
| 234 |
+
for i in range(nqy):
|
| 235 |
+
h(qy[i])
|
| 236 |
+
|
| 237 |
+
def run_timestep_func(vec_arg, hadamard=False): # Renamed vec
|
| 238 |
+
if hadamard:
|
| 239 |
+
result = cudaq.get_state(d2q5_tstep_wrapper_hadamard, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
|
| 240 |
+
else:
|
| 241 |
+
result = cudaq.get_state(d2q5_tstep_wrapper, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
|
| 242 |
+
|
| 243 |
+
num_nonzero_ranks = num_ranks / (2**num_anc)
|
| 244 |
+
rank_slice_cupy = to_cupy_array(result)
|
| 245 |
+
|
| 246 |
+
if rank >= num_nonzero_ranks and num_nonzero_ranks > 0: # Check num_nonzero_ranks > 0
|
| 247 |
+
sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
|
| 248 |
+
cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 249 |
+
|
| 250 |
+
if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0 : # Check N_sub_per_rank > 0
|
| 251 |
+
sub_sv_get = (rank_slice_cupy).get()
|
| 252 |
+
limit_idx = int(N_tot_state_vector / (2**num_anc))
|
| 253 |
+
if limit_idx < N_sub_per_rank : # Ensure index is within bounds
|
| 254 |
+
sub_sv_get[limit_idx:] = 0
|
| 255 |
+
cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_get.ctypes.data, sub_sv_get.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 256 |
+
return result
|
| 257 |
+
self.run_timestep = run_timestep_func
|
| 258 |
+
|
| 259 |
+
def write_state(self, state_to_write, t_step_str_val): # Renamed t_step, t_step_str
|
| 260 |
+
rank_slice_cupy = to_cupy_array(state_to_write)
|
| 261 |
+
num_nonzero_ranks = num_ranks / (2**num_anc)
|
| 262 |
+
|
| 263 |
+
if rank < num_nonzero_ranks or (rank==0 and num_nonzero_ranks <=0): # Logic for saving if this rank has data
|
| 264 |
+
save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy" # Use t_step_str_val
|
| 265 |
+
with open(save_path, 'wb') as f:
|
| 266 |
+
arr_to_save = None
|
| 267 |
+
data_limit = N_sub_per_rank
|
| 268 |
+
if num_nonzero_ranks < 1 and rank == 0:
|
| 269 |
+
data_limit = int(N_tot_state_vector / (2**num_anc))
|
| 270 |
+
|
| 271 |
+
if data_limit > 0:
|
| 272 |
+
relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
|
| 273 |
+
else:
|
| 274 |
+
relevant_part_cupy = cp.array([], dtype=cp.float64) # Empty array if no data
|
| 275 |
+
|
| 276 |
+
# Reshape and downsample based on current_N and downsampling_factor
|
| 277 |
+
# This assumes the relevant_part_cupy[:current_N*current_N] is the 2D grid data
|
| 278 |
+
if relevant_part_cupy.size >= current_N * current_N:
|
| 279 |
+
arr_flat = relevant_part_cupy[:current_N * current_N]
|
| 280 |
+
if downsampling_factor > 1:
|
| 281 |
+
arr_reshaped = arr_flat.reshape((current_N, current_N))
|
| 282 |
+
arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor]
|
| 283 |
+
arr_to_save = arr_downsampled.flatten()
|
| 284 |
+
else:
|
| 285 |
+
arr_to_save = arr_flat # No downsampling needed
|
| 286 |
+
elif relevant_part_cupy.size > 0 : # Partial data less than full grid
|
| 287 |
+
# This case is tricky for downsampling. For now, save what is available if it's 1D after downsampling.
|
| 288 |
+
# The original code had a direct downsample on 1D array.
|
| 289 |
+
# For consistency, if it's smaller than current_N*current_N, it's likely already "flat" for that part.
|
| 290 |
+
if downsampling_factor > 1:
|
| 291 |
+
arr_to_save = relevant_part_cupy[::downsampling_factor]
|
| 292 |
+
else:
|
| 293 |
+
arr_to_save = relevant_part_cupy
|
| 294 |
+
|
| 295 |
+
if arr_to_save is not None and arr_to_save.size > 0:
|
| 296 |
+
np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
|
| 297 |
+
# else: print(f"Debug: No data to save for {save_path}")
|
| 298 |
+
|
| 299 |
+
def run_evolution(self, initial_state_arg, total_timesteps, observable=False):
|
| 300 |
+
current_state_val = initial_state_arg # Renamed current_state
|
| 301 |
+
for t_iter in range(total_timesteps):
|
| 302 |
+
next_state_val = None # Renamed next_state
|
| 303 |
+
if t_iter == total_timesteps - 1 and observable:
|
| 304 |
+
next_state_val = self.run_timestep(current_state_val, True)
|
| 305 |
+
self.write_state(next_state_val, str(t_iter + 1) + "_h")
|
| 306 |
+
else:
|
| 307 |
+
next_state_val = self.run_timestep(current_state_val)
|
| 308 |
+
if (t_iter + 1) % timesteps_per_frame == 0:
|
| 309 |
+
self.write_state(next_state_val, str(t_iter + 1)) # Pass as string
|
| 310 |
+
if rank == 0:
|
| 311 |
+
print(f"Timestep: {t_iter + 1}/{total_timesteps}")
|
| 312 |
+
|
| 313 |
+
cp.get_default_memory_pool().free_all_blocks()
|
| 314 |
+
current_state_val = next_state_val
|
| 315 |
+
|
| 316 |
+
last_saved_timestep_name = str(total_timesteps) if not observable else str(total_timesteps) + "_h"
|
| 317 |
+
final_state_file_path = intermediate_folder_path / f"{last_saved_timestep_name}_{rank}.npy"
|
| 318 |
+
final_T_state_file_path = intermediate_folder_path / f"{total_timesteps}_{rank}.npy"
|
| 319 |
+
|
| 320 |
+
needs_final_save = True
|
| 321 |
+
if observable and final_state_file_path.exists():
|
| 322 |
+
needs_final_save = False
|
| 323 |
+
elif not observable and final_T_state_file_path.exists() and total_timesteps > 0 and total_timesteps % timesteps_per_frame == 0 :
|
| 324 |
+
needs_final_save = False
|
| 325 |
+
elif not observable and final_T_state_file_path.exists(): # Saved by previous final write
|
| 326 |
+
needs_final_save = False
|
| 327 |
+
|
| 328 |
+
if needs_final_save and total_timesteps > 0 :
|
| 329 |
+
if not observable:
|
| 330 |
+
self.write_state(current_state_val, str(total_timesteps)) # Pass as string
|
| 331 |
+
elif total_timesteps == 0: # Save initial state if T=0
|
| 332 |
+
self.write_state(current_state_val, "0")
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
if rank == 0:
|
| 336 |
+
print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
|
| 337 |
+
cp.get_default_memory_pool().free_all_blocks()
|
| 338 |
+
self.final_state = current_state_val
|
| 339 |
+
|
| 340 |
+
# downsampling_factor from user's snippet
|
| 341 |
+
downsampling_factor = 2**5
|
| 342 |
+
if current_N < downsampling_factor and current_N > 0: # Adjust if N is too small
|
| 343 |
+
downsampling_factor = current_N
|
| 344 |
+
elif current_N == 0: # Should be caught by num_reg_qubits check
|
| 345 |
+
print("Error: current_N is 0, cannot proceed.")
|
| 346 |
+
return None
|
| 347 |
+
|
| 348 |
+
qlbm_obj = QLBMAdvecDiffD2Q5_new(ux=ux_input, uy=uy_input)
|
| 349 |
+
|
| 350 |
+
initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total) # Renamed initial_state
|
| 351 |
+
rank_slice_init = to_cupy_array(initial_state_val)
|
| 352 |
+
|
| 353 |
+
xv_init = np.arange(current_N)
|
| 354 |
+
yv_init = np.arange(current_N)
|
| 355 |
+
|
| 356 |
+
print(f'Start initializing state with {distribution_type} distribution (ux={ux_input}, uy={uy_input})...')
|
| 357 |
+
initial_grid_2d = initial_state_func_eval(*np.meshgrid(xv_init, yv_init))
|
| 358 |
+
sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
|
| 359 |
+
|
| 360 |
+
full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
|
| 361 |
+
num_computational_states = current_N * current_N
|
| 362 |
+
|
| 363 |
+
if len(sub_sv_init_flat) == num_computational_states:
|
| 364 |
+
if num_computational_states <= N_sub_per_rank:
|
| 365 |
+
full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
|
| 366 |
+
else: # Should not happen if N_sub_per_rank is correctly N_tot_state_vector
|
| 367 |
+
print(f"Error: Grid data {num_computational_states} too large for N_sub_per_rank {N_sub_per_rank}")
|
| 368 |
+
return None
|
| 369 |
+
else:
|
| 370 |
+
print(f"Warning: Mismatch in expected initial state size {num_computational_states} and calculated {len(sub_sv_init_flat)}")
|
| 371 |
+
fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
|
| 372 |
+
full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
|
| 373 |
+
|
| 374 |
+
|
| 375 |
+
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}')
|
| 376 |
+
cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
|
| 377 |
+
print(f'Rank {rank}: Initial state copied to device.')
|
| 378 |
+
|
| 379 |
+
print("Starting QLBM evolution...")
|
| 380 |
+
qlbm_obj.run_evolution(initial_state_val, T) # observable is False by default
|
| 381 |
+
print("QLBM evolution complete.")
|
| 382 |
+
|
| 383 |
+
print("Generating animation with PyVista...")
|
| 384 |
+
downsampled_N = current_N // downsampling_factor
|
| 385 |
+
if downsampled_N == 0 and current_N > 0: # Ensure at least 1x1 grid if not empty
|
| 386 |
+
downsampled_N = 1
|
| 387 |
+
elif current_N == 0:
|
| 388 |
+
print("Error: Downsampled grid size is zero because current_N is zero.")
|
| 389 |
+
return None
|
| 390 |
+
|
| 391 |
+
plotted_timesteps_str = []
|
| 392 |
+
if T == 0: # Handle T=0 case for plotting initial state
|
| 393 |
+
if (intermediate_folder_path / f"0_{rank}.npy").exists():
|
| 394 |
+
plotted_timesteps_str.append("0")
|
| 395 |
+
else:
|
| 396 |
+
if timesteps_per_frame > 0:
|
| 397 |
+
for t_step_val in range(timesteps_per_frame, T + 1, timesteps_per_frame): # Renamed t_step
|
| 398 |
+
if intermediate_folder_path.joinpath(f"{t_step_val}_{rank}.npy").exists(): # rank is 0
|
| 399 |
+
plotted_timesteps_str.append(str(t_step_val))
|
| 400 |
+
|
| 401 |
+
final_T_path = intermediate_folder_path.joinpath(f"{T}_{rank}.npy")
|
| 402 |
+
final_Th_path = intermediate_folder_path.joinpath(f"{T}_h_{rank}.npy") # Though observable=False
|
| 403 |
+
|
| 404 |
+
if T > 0: # Redundant due to outer T==0 check, but safe
|
| 405 |
+
if final_Th_path.exists():
|
| 406 |
+
if str(T)+"_h" not in plotted_timesteps_str:
|
| 407 |
+
plotted_timesteps_str.append(str(T)+"_h")
|
| 408 |
+
elif final_T_path.exists():
|
| 409 |
+
if str(T) not in plotted_timesteps_str:
|
| 410 |
+
plotted_timesteps_str.append(str(T))
|
| 411 |
+
|
| 412 |
+
plotted_timesteps_str = sorted(list(set(plotted_timesteps_str)), key=lambda k_str: (int(k_str.replace('_h','')), k_str))
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
if not plotted_timesteps_str : # Combined check for T=0 and T>0
|
| 416 |
+
print(f"Warning: No .npy files found for plotting for T={T}. Animation may be empty or fail.")
|
| 417 |
+
return None # No frames to plot
|
| 418 |
+
|
| 419 |
+
data_frames_list = [] # Renamed data
|
| 420 |
+
actual_timesteps_for_title = []
|
| 421 |
+
|
| 422 |
+
for i_str_val in plotted_timesteps_str: # Renamed i_str
|
| 423 |
+
try:
|
| 424 |
+
file_path_load = intermediate_folder_path / f"{i_str_val}_{rank}.npy" # Renamed file_path
|
| 425 |
+
sol_loaded = np.load(file_path_load) # Renamed sol
|
| 426 |
+
if sol_loaded.size == downsampled_N * downsampled_N:
|
| 427 |
+
Z_data = np.reshape(sol_loaded, (downsampled_N, downsampled_N)) # Renamed Z
|
| 428 |
+
data_frames_list.append(Z_data)
|
| 429 |
+
actual_timesteps_for_title.append(str(i_str_val).replace('_h',''))
|
| 430 |
+
else:
|
| 431 |
+
print(f"Warning: File {file_path_load} has unexpected size {sol_loaded.size}. Expected {downsampled_N*downsampled_N}. Skipping this frame.")
|
| 432 |
+
continue
|
| 433 |
+
except FileNotFoundError:
|
| 434 |
+
print(f"Warning: File not found for timestep {i_str_val} ({file_path_load}). Skipping this frame.")
|
| 435 |
+
continue
|
| 436 |
+
except Exception as e_load: # Renamed e
|
| 437 |
+
print(f"Error loading or reshaping file for timestep {i_str_val}: {e_load}. Skipping this frame.")
|
| 438 |
+
continue
|
| 439 |
+
|
| 440 |
+
if not data_frames_list:
|
| 441 |
+
print("Error: No data available to create animation after loading. Check .npy file generation and paths.")
|
| 442 |
+
return None
|
| 443 |
+
|
| 444 |
+
# --- PyVista Animation Setup ---
|
| 445 |
+
# Define plot ranges (as in original matplotlib version)
|
| 446 |
+
x_coords_plot = np.linspace(-10, 10, downsampled_N) # Renamed x_plot
|
| 447 |
+
y_coords_plot = np.linspace(-10, 10, downsampled_N) # Renamed y_plot
|
| 448 |
+
X_mesh_plot, Y_mesh_plot = np.meshgrid(x_coords_plot, y_coords_plot) # Renamed X_plot, Y_plot
|
| 449 |
+
|
| 450 |
+
# Normalization factor for Z values (geometry and color)
|
| 451 |
+
# Use the max absolute value of the first frame for consistent scaling reference
|
| 452 |
+
norm_factor_pv = np.max(np.abs(data_frames_list[0])) if data_frames_list[0].size > 0 and np.max(np.abs(data_frames_list[0])) != 0 else 1.0
|
| 453 |
+
if norm_factor_pv == 0: norm_factor_pv = 1.0
|
| 454 |
+
|
| 455 |
+
# Determine a Z-axis limit for color mapping (clim)
|
| 456 |
+
# Based on max value across all frames (normalized by norm_factor_pv for consistency if geometry is also normalized by it)
|
| 457 |
+
all_data_max_val = norm_factor_pv # Initialize with first frame's norm
|
| 458 |
+
if len(data_frames_list) > 0:
|
| 459 |
+
max_vals_frames = [np.max(np.abs(d_frame)) for d_frame in data_frames_list if d_frame.size > 0] # Renamed d
|
| 460 |
+
if max_vals_frames:
|
| 461 |
+
global_max_val_frames = max(max_vals_frames) # Renamed global_max_val
|
| 462 |
+
if global_max_val_frames > 0 : all_data_max_val = global_max_val_frames
|
| 463 |
+
|
| 464 |
+
# clim_upper is based on the range of actual scalar values being plotted
|
| 465 |
+
# If we plot Z_frame/norm_factor_pv as scalars, clim_upper is all_data_max_val/norm_factor_pv
|
| 466 |
+
# If we plot Z_frame as scalars, clim_upper is all_data_max_val
|
| 467 |
+
# Let's plot Z_frame/norm_factor_pv as scalars for consistency with previous z_lim=0.6 idea
|
| 468 |
+
|
| 469 |
+
clim_upper_pv = (all_data_max_val / norm_factor_pv) * 1.0 # Target a bit above max normalized value, e.g. *1.0 for exact max
|
| 470 |
+
if clim_upper_pv == 0 : clim_upper_pv = 0.6 # Fallback like original
|
| 471 |
+
|
| 472 |
+
# GIF generation setup
|
| 473 |
+
results_base_dir = "Results"
|
| 474 |
+
gif_frames_for_naming = int(simulation_fps * T)
|
| 475 |
+
if gif_frames_for_naming == 0: gif_frames_for_naming = 1
|
| 476 |
+
dist_name_part = distribution_type.replace(' ','').replace('(Original)','').replace('(','').replace(')','')
|
| 477 |
+
specific_folder_name = (f"d2q5_pv_N{current_N}_T{T}_fr{gif_frames_for_naming}_"
|
| 478 |
+
f"dist{dist_name_part}_ux{ux_input:.2f}_uy{uy_input:.2f}")
|
| 479 |
+
final_output_dir = Path(results_base_dir) / specific_folder_name
|
| 480 |
+
os.makedirs(final_output_dir, exist_ok=True)
|
| 481 |
+
gif_path_to_return = final_output_dir / "animation_pv.gif" # Changed name slightly
|
| 482 |
+
|
| 483 |
+
plotter = pv.Plotter(off_screen=True, window_size=[1000, 800], notebook=False)
|
| 484 |
+
# Set a fixed camera position for consistent view across frames
|
| 485 |
+
plotter.camera_position = 'xy' # Simple top-down-ish view
|
| 486 |
+
plotter.camera.azimuth = 30 # Rotate a bit
|
| 487 |
+
plotter.camera.elevation = 25 # Look down a bit
|
| 488 |
+
plotter.camera.zoom(1.2) # Zoom in slightly
|
| 489 |
+
|
| 490 |
+
try:
|
| 491 |
+
plotter.open_gif(str(gif_path_to_return), fps=10)
|
| 492 |
+
|
| 493 |
+
# Corrected sargs dictionary
|
| 494 |
+
sargs = dict(
|
| 495 |
+
title='Density',
|
| 496 |
+
vertical=True, # Ensure this is 'vertical'
|
| 497 |
+
position_x=0.05, # X position of the scalar bar
|
| 498 |
+
position_y=0.1, # Y position of the scalar bar
|
| 499 |
+
height=0.8, # Height of the scalar bar
|
| 500 |
+
width=0.1, # Width of the scalar bar
|
| 501 |
+
label_font_size=16, # Font size for labels on the bar
|
| 502 |
+
title_font_size=18 # Font size for the title of the bar
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
for frame_idx, Z_frame_data in enumerate(data_frames_list):
|
| 506 |
+
plotter.clear_actors()
|
| 507 |
+
|
| 508 |
+
z_coords_geometry = (Z_frame_data / norm_factor_pv)
|
| 509 |
+
|
| 510 |
+
if not X_mesh_plot.flags.c_contiguous: X_mesh_plot_flat = X_mesh_plot.ravel(order='C')
|
| 511 |
+
else: X_mesh_plot_flat = X_mesh_plot.ravel()
|
| 512 |
+
if not Y_mesh_plot.flags.c_contiguous: Y_mesh_plot_flat = Y_mesh_plot.ravel(order='C')
|
| 513 |
+
else: Y_mesh_plot_flat = Y_mesh_plot.ravel()
|
| 514 |
+
if not z_coords_geometry.flags.c_contiguous: z_coords_geometry_flat = z_coords_geometry.ravel(order='C')
|
| 515 |
+
else: z_coords_geometry_flat = z_coords_geometry.ravel()
|
| 516 |
+
|
| 517 |
+
surface_mesh = pv.StructuredGrid()
|
| 518 |
+
surface_mesh.points = np.column_stack((X_mesh_plot_flat, Y_mesh_plot_flat, z_coords_geometry_flat))
|
| 519 |
+
surface_mesh.dimensions = (downsampled_N, downsampled_N, 1)
|
| 520 |
+
surface_mesh["density"] = z_coords_geometry_flat
|
| 521 |
+
|
| 522 |
+
plotter.add_mesh(surface_mesh, scalars="density", cmap="viridis",
|
| 523 |
+
clim=[0, clim_upper_pv], scalar_bar_args=sargs, # sargs is passed here
|
| 524 |
+
show_edges=False)
|
| 525 |
+
|
| 526 |
+
current_ts_title = actual_timesteps_for_title[frame_idx] if frame_idx < len(actual_timesteps_for_title) else "Unknown"
|
| 527 |
+
plotter.add_text(f'QLBM Simulation (Timestep: {current_ts_title})',
|
| 528 |
+
position='upper_edge', font_size=18, color='black', font='arial')
|
| 529 |
+
|
| 530 |
+
plotter.write_frame()
|
| 531 |
+
|
| 532 |
+
plotter.close()
|
| 533 |
+
print(f"PyVista animation saved to {gif_path_to_return}")
|
| 534 |
+
|
| 535 |
+
except Exception as e_pv_anim:
|
| 536 |
+
print(f"Error during PyVista animation/saving: {e_pv_anim}")
|
| 537 |
+
if 'plotter' in locals() and hasattr(plotter, 'close'):
|
| 538 |
+
plotter.close()
|
| 539 |
+
return None
|
| 540 |
+
|
| 541 |
+
return str(gif_path_to_return)
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
# --- Gradio Interface Definition (remains largely the same) ---
|
| 546 |
+
def qlbm_gradio_interface(num_reg_qubits_input: int, timescale_input: int, distribution_type_param: str, ux_param: float, uy_param: float):
|
| 547 |
+
num_reg_qubits_val = int(num_reg_qubits_input)
|
| 548 |
+
timescale_val = int(timescale_input)
|
| 549 |
+
ux_val = float(ux_param)
|
| 550 |
+
uy_val = float(uy_param)
|
| 551 |
+
|
| 552 |
+
print(f"Gradio Interface: num_reg_qubits={num_reg_qubits_val}, T={timescale_val}, Distribution={distribution_type_param}, ux={ux_val}, uy={uy_val}")
|
| 553 |
+
|
| 554 |
+
gif_path_output = simulate_qlbm_and_animate(
|
| 555 |
+
num_reg_qubits=num_reg_qubits_val,
|
| 556 |
+
T=timescale_val,
|
| 557 |
+
distribution_type=distribution_type_param,
|
| 558 |
+
ux_input=ux_val,
|
| 559 |
+
uy_input=uy_val
|
| 560 |
+
)
|
| 561 |
+
|
| 562 |
+
if gif_path_output is None:
|
| 563 |
+
gr.Warning("Animation generation failed. Please check console for errors.")
|
| 564 |
+
return None
|
| 565 |
+
return gif_path_output
|
| 566 |
+
|
| 567 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Simulation with PyVista") as qlbm_demo:
|
| 568 |
+
gr.Markdown(
|
| 569 |
+
"""
|
| 570 |
+
# ⚛️ Quantum Lattice Boltzmann Method (QLBM) Simulator (PyVista Animation)
|
| 571 |
+
Welcome to the Quantum Lattice Boltzmann Method (QLBM) simulator! This version uses PyVista for 3D animation.
|
| 572 |
+
|
| 573 |
+
**What is QLBM?**
|
| 574 |
+
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.
|
| 575 |
+
|
| 576 |
+
**How this Simulator Works:**
|
| 577 |
+
This simulator implements a D2Q5 model on a quantum computer simulator (CUDA-Q).
|
| 578 |
+
- Control grid size (via Number of Register Qubits: $N=2^{\text{num_reg_qubits}}$).
|
| 579 |
+
- Set total simulation time (Timescale T).
|
| 580 |
+
- Choose initial distribution.
|
| 581 |
+
- Set advection velocities `ux` and `uy`.
|
| 582 |
+
The simulation evolves this state, and a PyVista animation of the density is generated.
|
| 583 |
+
|
| 584 |
+
**Note:** Higher qubit counts and longer timescales are computationally intensive. Advection velocities should be small (e.g., < 0.3).
|
| 585 |
+
"""
|
| 586 |
+
)
|
| 587 |
+
|
| 588 |
+
with gr.Row():
|
| 589 |
+
with gr.Column(scale=1):
|
| 590 |
+
gr.Markdown("## Simulation Parameters")
|
| 591 |
+
num_reg_qubits_slider = gr.Slider(
|
| 592 |
+
minimum=2,
|
| 593 |
+
maximum=11, # Max from original snippet
|
| 594 |
+
value=8,
|
| 595 |
+
step=1,
|
| 596 |
+
label="Number of Register Qubits (num_reg_qubits)",
|
| 597 |
+
info="Determines N for grid size (2^N x 2^N). Max 11 (Note: >8 can be very slow)."
|
| 598 |
+
)
|
| 599 |
+
timescale_slider = gr.Slider(
|
| 600 |
+
minimum=0, # Allow T=0
|
| 601 |
+
maximum=2000, # Max from original snippet
|
| 602 |
+
value=100,
|
| 603 |
+
step=10, # Step from original snippet
|
| 604 |
+
label="Timescale (T)",
|
| 605 |
+
info="Total number of timesteps. Max 2000."
|
| 606 |
+
)
|
| 607 |
+
distribution_options = ["Sine Wave (Original)", "Gaussian", "Random"]
|
| 608 |
+
distribution_type_input = gr.Radio(
|
| 609 |
+
choices=distribution_options,
|
| 610 |
+
value="Sine Wave (Original)",
|
| 611 |
+
label="Initial Distribution Type",
|
| 612 |
+
info="Select the initial pattern of the substance on the grid."
|
| 613 |
+
)
|
| 614 |
+
ux_slider = gr.Slider(
|
| 615 |
+
minimum=-0.4,
|
| 616 |
+
maximum=0.4,
|
| 617 |
+
value=0.2,
|
| 618 |
+
step=0.01,
|
| 619 |
+
label="Advection Velocity ux",
|
| 620 |
+
info="x-component of the background advection velocity."
|
| 621 |
+
)
|
| 622 |
+
uy_slider = gr.Slider(
|
| 623 |
+
minimum=-0.4,
|
| 624 |
+
maximum=0.4,
|
| 625 |
+
value=0.15,
|
| 626 |
+
step=0.01,
|
| 627 |
+
label="Advection Velocity uy",
|
| 628 |
+
info="y-component of the background advection velocity."
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
run_qlbm_btn = gr.Button("Run QLBM Simulation (PyVista)", variant="primary")
|
| 632 |
+
|
| 633 |
+
with gr.Column(scale=3):
|
| 634 |
+
qlbm_plot_output = gr.Image(label="QLBM Simulation Animation (PyVista GIF)", type="filepath", height=600) # Keep as Image for GIF
|
| 635 |
+
|
| 636 |
+
qlbm_inputs_list = [num_reg_qubits_slider, timescale_slider, distribution_type_input, ux_slider, uy_slider]
|
| 637 |
+
|
| 638 |
+
run_qlbm_btn.click(
|
| 639 |
+
fn=qlbm_gradio_interface,
|
| 640 |
+
inputs=qlbm_inputs_list,
|
| 641 |
+
outputs=qlbm_plot_output
|
| 642 |
+
)
|
| 643 |
+
|
| 644 |
+
gr.Examples(
|
| 645 |
+
examples=[
|
| 646 |
+
[8, 100, "Sine Wave (Original)", 0.2, 0.15],
|
| 647 |
+
[6, 50, "Gaussian", 0.1, 0.05],
|
| 648 |
+
[4, 30, "Random", -0.05, 0.1],
|
| 649 |
+
[7, 70, "Sine Wave (Original)", 0.0, 0.0] # Example with no advection
|
| 650 |
+
],
|
| 651 |
+
inputs=qlbm_inputs_list,
|
| 652 |
+
outputs=qlbm_plot_output,
|
| 653 |
+
fn=qlbm_gradio_interface,
|
| 654 |
+
cache_examples=False
|
| 655 |
+
)
|
| 656 |
+
|
| 657 |
+
if __name__ == "__main__":
|
| 658 |
+
# It's good practice to set the CUDA-Q target early if it's fixed.
|
| 659 |
+
try:
|
| 660 |
+
cudaq.set_target('nvidia', option='fp64')
|
| 661 |
+
print(f"CUDA-Q Target successfully set to: {cudaq.get_target().name}")
|
| 662 |
+
except Exception as e_target:
|
| 663 |
+
print(f"Warning: Could not set CUDA-Q target to 'nvidia'. Error: {e_target}")
|
| 664 |
+
print(f"Current CUDA-Q Target: {cudaq.get_target().name}. Performance may be affected if not using GPU.")
|
| 665 |
+
|
| 666 |
+
qlbm_demo.launch()
|
index.html
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width" />
|
| 6 |
+
<title>My static Space</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="card">
|
| 11 |
+
<h1>Welcome to your static Space!</h1>
|
| 12 |
+
<p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
|
| 13 |
+
<p>
|
| 14 |
+
Also don't forget to check the
|
| 15 |
+
<a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
|
| 16 |
+
</p>
|
| 17 |
+
</div>
|
| 18 |
+
</body>
|
| 19 |
+
</html>
|
new-working-branch
ADDED
|
File without changes
|
packages.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
libgl1-mesa-glx
|
| 2 |
+
libosmesa6-dev
|
| 3 |
+
libegl1-mesa-dev
|
| 4 |
+
xvfb
|
requirements.txt
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pyvista
|
| 2 |
+
kaleido # <-- Ensure this line is present
|
| 3 |
+
imageio
|
| 4 |
+
matplotlib
|
| 5 |
+
plotly
|
| 6 |
+
imageio-ffmpeg # Still recommended for broader codec support
|
| 7 |
+
gradio
|
| 8 |
+
cudaq # <--- This is the corrected line
|
| 9 |
+
numpy
|
| 10 |
+
cupy-cuda12x # Replace XXX with your target CUDA version (e.g., cupy-cuda118 or cupy-cuda12x)
|
| 11 |
+
|
style.css
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
padding: 2rem;
|
| 3 |
+
font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
h1 {
|
| 7 |
+
font-size: 16px;
|
| 8 |
+
margin-top: 0;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
p {
|
| 12 |
+
color: rgb(107, 114, 128);
|
| 13 |
+
font-size: 15px;
|
| 14 |
+
margin-bottom: 10px;
|
| 15 |
+
margin-top: 5px;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
.card {
|
| 19 |
+
max-width: 620px;
|
| 20 |
+
margin: 0 auto;
|
| 21 |
+
padding: 16px;
|
| 22 |
+
border: 1px solid lightgray;
|
| 23 |
+
border-radius: 16px;
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
.card p:last-child {
|
| 27 |
+
margin-bottom: 0;
|
| 28 |
+
}
|