Spaces:
Paused
Paused
Commit ·
5d756e6
1
Parent(s): f07ea24
Added Introduction page
Browse files
app.py
CHANGED
|
@@ -1,11 +1,58 @@
|
|
| 1 |
from fluid import *
|
| 2 |
from gradio_litmodel3d import LitModel3D
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
# Modified interface functions to take num_reg_qubits_input
|
| 6 |
def qlbm_gradio_interface(grid_size_input: int, time_steps_input: int, distribution_type_param: str, velocity_field_param: str, vx_param: float, vy_param: float, boundary_condition_param: str):
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
| 9 |
time_steps_val = int(time_steps_input)
|
| 10 |
vx_val = float(vx_param)
|
| 11 |
vy_val = float(vy_param)
|
|
@@ -88,80 +135,124 @@ def set_gaussian_example():
|
|
| 88 |
|
| 89 |
# Gradio interface for Fluid Dynamics - 2D only
|
| 90 |
with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Fluid Simulation (2D)") as demo:
|
| 91 |
-
with gr.
|
| 92 |
-
with gr.
|
| 93 |
-
gr.Markdown(
|
| 94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
with gr.Column(scale=1):
|
| 96 |
-
|
| 97 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
with gr.Column(scale=1):
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
gr.
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
)
|
| 108 |
-
time_steps_slider_2d = gr.Slider(minimum=0, maximum=4000, value=1600, step=10, label="Time Steps")
|
| 109 |
-
qubit_plot_2d = gr.Plot(label="Qubits vs. Grid Size")
|
| 110 |
-
total_qubits_display_2d = gr.Markdown("Total Qubits: 19")
|
| 111 |
-
warning_display_2d = gr.Markdown("")
|
| 112 |
-
recommended_time_steps_display_2d = gr.Markdown("Recommended time steps: 1600")
|
| 113 |
-
|
| 114 |
-
with gr.Column(scale=2): # Column for the main plot
|
| 115 |
-
qlbm_interactive_plot_2d = gr.Plot(label="QLBM")
|
| 116 |
-
download_button_2d = gr.DownloadButton(label="Download Plot Data (JSON)", visible=False) # New download button
|
| 117 |
-
plotly_json_frames_state_2d = gr.State([]) # New state to store Plotly JSON frames
|
| 118 |
-
|
| 119 |
-
with gr.Row(): # Row for bottom section
|
| 120 |
-
with gr.Column(scale=1):
|
| 121 |
-
gr.Markdown("## Initialization (Uniform)")
|
| 122 |
-
selected_velocity_field_2d = gr.State("Uniform")
|
| 123 |
-
vx_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.2, step=0.01, label="V_x", visible=True)
|
| 124 |
-
vy_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.15, step=0.01, label="V_y", visible=True)
|
| 125 |
-
distribution_type_input_2d = gr.Radio(choices=["Gaussian", "Sinusoidal"], value="Sinusoidal", label="Initial Distribution Type")
|
| 126 |
-
|
| 127 |
-
with gr.Column(scale=1):
|
| 128 |
-
gr.Markdown("## Boundary Conditions")
|
| 129 |
-
boundary_condition_input_2d = gr.Radio(choices=["Periodic"], value="Periodic", label="Boundary Condition")
|
| 130 |
-
|
| 131 |
-
run_qlbm_btn_2d = gr.Button("Run Simulation", variant="primary") # Button below the sections
|
| 132 |
-
|
| 133 |
-
qlbm_inputs_list_2d = [
|
| 134 |
-
num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d,
|
| 135 |
-
selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d
|
| 136 |
-
]
|
| 137 |
-
run_qlbm_btn_2d.click(
|
| 138 |
-
fn=qlbm_gradio_interface,
|
| 139 |
-
inputs=qlbm_inputs_list_2d,
|
| 140 |
-
outputs=[qlbm_interactive_plot_2d, plotly_json_frames_state_2d] # Modified output
|
| 141 |
-
).then(
|
| 142 |
-
lambda: gr.update(visible=True), # Make download button visible after simulation
|
| 143 |
-
outputs=[download_button_2d]
|
| 144 |
-
)
|
| 145 |
-
download_button_2d.click(
|
| 146 |
-
fn=download_plot_data,
|
| 147 |
-
inputs=[plotly_json_frames_state_2d],
|
| 148 |
-
outputs=[download_button_2d]
|
| 149 |
-
)
|
| 150 |
-
num_reg_qubits_input_2d.change(
|
| 151 |
-
fn=update_qubit_info,
|
| 152 |
-
inputs=num_reg_qubits_input_2d,
|
| 153 |
-
outputs=[qubit_plot_2d, total_qubits_display_2d, warning_display_2d, recommended_time_steps_display_2d]
|
| 154 |
-
)
|
| 155 |
-
sinusoidal_btn_2d.click(
|
| 156 |
-
fn=set_sinusoidal_example,
|
| 157 |
-
inputs=[],
|
| 158 |
-
outputs=[num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d, selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d]
|
| 159 |
-
)
|
| 160 |
-
gaussian_btn_2d.click(
|
| 161 |
-
fn=set_gaussian_example,
|
| 162 |
-
inputs=[],
|
| 163 |
-
outputs=[num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d, selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d]
|
| 164 |
-
)
|
| 165 |
|
| 166 |
if __name__ == "__main__":
|
| 167 |
try:
|
|
|
|
| 1 |
from fluid import *
|
| 2 |
from gradio_litmodel3d import LitModel3D
|
| 3 |
+
import math, zipfile, tempfile
|
| 4 |
+
import numpy as np
|
| 5 |
+
import plotly.graph_objects as go
|
| 6 |
+
import gradio as gr
|
| 7 |
+
import cudaq
|
| 8 |
|
| 9 |
+
# Helper: snap grid size to nearest power of two within bounds
|
| 10 |
+
def _nearest_pow2(n: int, min_pow: int = 2**7, max_pow: int = 2**12) -> int:
|
| 11 |
+
if n <= 0:
|
| 12 |
+
return min_pow
|
| 13 |
+
# nearest power of two
|
| 14 |
+
p = 1 << (int(round(math.log2(max(n, 1)))))
|
| 15 |
+
# clamp to bounds and adjust if out of range
|
| 16 |
+
if p < min_pow:
|
| 17 |
+
p = min_pow
|
| 18 |
+
if p > max_pow:
|
| 19 |
+
# choose the closer between max_pow and previous power
|
| 20 |
+
p = max_pow
|
| 21 |
+
return p
|
| 22 |
+
|
| 23 |
+
# New update function: also updates the slider value after snapping
|
| 24 |
+
def update_grid_and_qubit_info(grid_size):
|
| 25 |
+
snapped = _nearest_pow2(int(grid_size))
|
| 26 |
+
num_reg_qubits = int(math.log2(snapped))
|
| 27 |
+
total_qubits = 2 * num_reg_qubits + 3
|
| 28 |
+
|
| 29 |
+
x = np.array([128, 256, 512, 1024, 2048, 4096])
|
| 30 |
+
y = np.log2(x).astype(int)
|
| 31 |
+
fig = go.Figure()
|
| 32 |
+
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction'))
|
| 33 |
+
fig.add_trace(go.Scatter(x=[snapped], y=[num_reg_qubits], mode='markers', marker=dict(size=12, color='red'), name='Current Selection'))
|
| 34 |
+
fig.update_layout(
|
| 35 |
+
xaxis_title="Grid Size (Points/Direction)",
|
| 36 |
+
yaxis_title="Qubits/Direction",
|
| 37 |
+
width=400,
|
| 38 |
+
height=300
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
total_qubits_display = f"Total Qubits: {total_qubits}"
|
| 42 |
+
warn_pow2 = " (snapped to nearest power of two)" if snapped != int(grid_size) else ""
|
| 43 |
+
warning = ("⚠️ Warning: Grid sizes > 1024 may exceed simulator/memory limits!" if snapped > 1024 else "") + warn_pow2
|
| 44 |
+
recommended_time_steps = num_reg_qubits * 200
|
| 45 |
+
recommended_display = f"Recommended time steps: {recommended_time_steps}"
|
| 46 |
+
|
| 47 |
+
return gr.update(value=snapped), fig, total_qubits_display, warning, recommended_display
|
| 48 |
|
| 49 |
# Modified interface functions to take num_reg_qubits_input
|
| 50 |
def qlbm_gradio_interface(grid_size_input: int, time_steps_input: int, distribution_type_param: str, velocity_field_param: str, vx_param: float, vy_param: float, boundary_condition_param: str):
|
| 51 |
+
snapped_grid = _nearest_pow2(int(grid_size_input))
|
| 52 |
+
if snapped_grid != int(grid_size_input):
|
| 53 |
+
gr.Warning(f"Grid size {grid_size_input} is not a power of two. Using {snapped_grid}.")
|
| 54 |
+
num_reg_qubits_val = int(math.log2(snapped_grid))
|
| 55 |
+
grid_size_val = snapped_grid
|
| 56 |
time_steps_val = int(time_steps_input)
|
| 57 |
vx_val = float(vx_param)
|
| 58 |
vy_val = float(vy_param)
|
|
|
|
| 135 |
|
| 136 |
# Gradio interface for Fluid Dynamics - 2D only
|
| 137 |
with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Fluid Simulation (2D)") as demo:
|
| 138 |
+
with gr.Tabs():
|
| 139 |
+
with gr.TabItem("Introduction"):
|
| 140 |
+
gr.Markdown(
|
| 141 |
+
"""
|
| 142 |
+
# Quantum Lattice Boltzmann (QLBM)
|
| 143 |
+
This app runs a 2D quantum-inspired Lattice Boltzmann Method using a D2Q5 model (5 discrete velocity directions) to evolve a scalar density field with uniform advection and periodic boundaries.
|
| 144 |
+
|
| 145 |
+
What is simulated
|
| 146 |
+
- D2Q5 advection–diffusion on a square grid (periodic BCs only).
|
| 147 |
+
- Uniform velocity (Vx, Vy) applied to all cells.
|
| 148 |
+
- State is evolved via CUDA-Q kernels; results are downsampled and visualized as 3D surfaces with a time slider.
|
| 149 |
+
|
| 150 |
+
How it works (from the implementation)
|
| 151 |
+
- Grid size N = 2^q (q = qubits per spatial dimension). Total qubits = 2*q + 3 direction qubits.
|
| 152 |
+
- Per timestep: a Householder-based prep_op initializes direction amplitudes, then conditional shifts stream along x and y (lshift/rshift) controlled by direction bits, followed by an unprep.
|
| 153 |
+
- Up to 40 uniform time samples between 0..T are saved for visualization.
|
| 154 |
+
|
| 155 |
+
Visualization pipeline
|
| 156 |
+
- Only the real component of the state is saved and plotted; the initialized field is normalized before evolution.
|
| 157 |
+
- Data is saved as .npy in a temporary folder, downsampled by a factor of 2^5 (32). For very small grids, the factor is reduced to keep at least 1×1.
|
| 158 |
+
- Plotly renders one surface per saved frame; a slider toggles visibility. You can download per-frame Plotly JSON for offline analysis.
|
| 159 |
+
|
| 160 |
+
Parameters you control
|
| 161 |
+
- Grid Size/Direction: N = 2^q. The UI snaps any input to the nearest power of two in [128, 4096]. Larger N increases memory and runtime.
|
| 162 |
+
- Time Steps (T): total evolution steps; up to 40 frames are sampled in [0, T].
|
| 163 |
+
- Initial Distribution: Sinusoidal or Gaussian.
|
| 164 |
+
- Velocity Field: Uniform (set Vx, Vy).
|
| 165 |
+
- Boundary Condition: Periodic.
|
| 166 |
+
|
| 167 |
+
Practical notes
|
| 168 |
+
- Recommended time steps ≈ q * 200.
|
| 169 |
+
- N > 1024 may exceed available memory/time.
|
| 170 |
+
- Requires a CUDA-capable GPU and CUDA-Q runtime; CPU fallback will be slow or may fail.
|
| 171 |
+
|
| 172 |
+
Workflow
|
| 173 |
+
1) Review example initial distributions.
|
| 174 |
+
2) Choose grid size N and time steps T.
|
| 175 |
+
3) Set Vx and Vy; keep Periodic BC.
|
| 176 |
+
4) Run the simulation; use the slider to browse frames.
|
| 177 |
+
5) Optionally download the plotted frames as JSON for offline analysis.
|
| 178 |
+
"""
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
with gr.TabItem("Simulation"):
|
| 182 |
+
with gr.Row(): # Main row for top section
|
| 183 |
+
with gr.Column(scale=1): # Column for left-side controls
|
| 184 |
+
gr.Markdown("## Initial Distribution Examples")
|
| 185 |
+
with gr.Row():
|
| 186 |
+
with gr.Column(scale=1):
|
| 187 |
+
example1 = LitModel3D("Placeholder_Images/sinusoidal.stl", label="Sinusoidal")
|
| 188 |
+
sinusoidal_btn_2d = gr.Button("Sinusoidal")
|
| 189 |
+
with gr.Column(scale=1):
|
| 190 |
+
example2 = LitModel3D("Placeholder_Images/gaussian.stl", label="Gaussian")
|
| 191 |
+
gaussian_btn_2d = gr.Button("Gaussian")
|
| 192 |
+
|
| 193 |
+
gr.Markdown("## Simulation Parameters")
|
| 194 |
+
num_reg_qubits_input_2d = gr.Slider(
|
| 195 |
+
minimum=2**7, maximum=2**12, # Grid size values
|
| 196 |
+
value=2**8, step=1, # use step=1 for broader Gradio compatibility
|
| 197 |
+
label="Grid Size/Direction"
|
| 198 |
+
)
|
| 199 |
+
time_steps_slider_2d = gr.Slider(minimum=0, maximum=4000, value=1600, step=10, label="Time Steps")
|
| 200 |
+
qubit_plot_2d = gr.Plot(label="Qubits vs. Grid Size")
|
| 201 |
+
total_qubits_display_2d = gr.Markdown("Total Qubits: 19")
|
| 202 |
+
warning_display_2d = gr.Markdown("")
|
| 203 |
+
recommended_time_steps_display_2d = gr.Markdown("Recommended time steps: 1600")
|
| 204 |
+
|
| 205 |
+
with gr.Column(scale=2): # Column for the main plot
|
| 206 |
+
qlbm_interactive_plot_2d = gr.Plot(label="QLBM")
|
| 207 |
+
download_button_2d = gr.DownloadButton(label="Download Plot Data (JSON)", visible=False) # New download button
|
| 208 |
+
plotly_json_frames_state_2d = gr.State([]) # New state to store Plotly JSON frames
|
| 209 |
+
|
| 210 |
+
with gr.Row(): # Row for bottom section
|
| 211 |
with gr.Column(scale=1):
|
| 212 |
+
gr.Markdown("## Initialization (Uniform)")
|
| 213 |
+
selected_velocity_field_2d = gr.State("Uniform")
|
| 214 |
+
vx_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.2, step=0.01, label="V_x", visible=True)
|
| 215 |
+
vy_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.15, step=0.01, label="V_y", visible=True)
|
| 216 |
+
distribution_type_input_2d = gr.Radio(choices=["Gaussian", "Sinusoidal"], value="Sinusoidal", label="Initial Distribution Type")
|
| 217 |
+
|
| 218 |
with gr.Column(scale=1):
|
| 219 |
+
gr.Markdown("## Boundary Conditions")
|
| 220 |
+
boundary_condition_input_2d = gr.Radio(choices=["Periodic"], value="Periodic", label="Boundary Condition")
|
| 221 |
+
|
| 222 |
+
run_qlbm_btn_2d = gr.Button("Run Simulation", variant="primary") # Button below the sections
|
| 223 |
+
|
| 224 |
+
qlbm_inputs_list_2d = [
|
| 225 |
+
num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d,
|
| 226 |
+
selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d
|
| 227 |
+
]
|
| 228 |
+
run_qlbm_btn_2d.click(
|
| 229 |
+
fn=qlbm_gradio_interface,
|
| 230 |
+
inputs=qlbm_inputs_list_2d,
|
| 231 |
+
outputs=[qlbm_interactive_plot_2d, plotly_json_frames_state_2d] # Modified output
|
| 232 |
+
).then(
|
| 233 |
+
lambda: gr.update(visible=True), # Make download button visible after simulation
|
| 234 |
+
outputs=[download_button_2d]
|
| 235 |
+
)
|
| 236 |
+
download_button_2d.click(
|
| 237 |
+
fn=download_plot_data,
|
| 238 |
+
inputs=[plotly_json_frames_state_2d],
|
| 239 |
+
outputs=[download_button_2d]
|
| 240 |
+
)
|
| 241 |
+
num_reg_qubits_input_2d.change(
|
| 242 |
+
fn=update_grid_and_qubit_info,
|
| 243 |
+
inputs=num_reg_qubits_input_2d,
|
| 244 |
+
outputs=[num_reg_qubits_input_2d, qubit_plot_2d, total_qubits_display_2d, warning_display_2d, recommended_time_steps_display_2d]
|
| 245 |
+
)
|
| 246 |
+
sinusoidal_btn_2d.click(
|
| 247 |
+
fn=set_sinusoidal_example,
|
| 248 |
+
inputs=[],
|
| 249 |
+
outputs=[num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d, selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d]
|
| 250 |
+
)
|
| 251 |
+
gaussian_btn_2d.click(
|
| 252 |
+
fn=set_gaussian_example,
|
| 253 |
+
inputs=[],
|
| 254 |
+
outputs=[num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d, selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d]
|
| 255 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
|
| 257 |
if __name__ == "__main__":
|
| 258 |
try:
|