Spaces:
Paused
Paused
File size: 13,954 Bytes
0b1fdba 5d756e6 0b1fdba 5d756e6 0b1fdba 5d756e6 0b1fdba 8b2e675 0b1fdba 8b2e675 0b1fdba 8b2e675 5d756e6 0b1fdba 5d756e6 0b1fdba 5d756e6 0b1fdba |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
from fluid import *
from gradio_litmodel3d import LitModel3D
import math, zipfile, tempfile
import numpy as np
import plotly.graph_objects as go
import gradio as gr
import cudaq
# Helper: snap grid size to nearest power of two within bounds
def _nearest_pow2(n: int, min_pow: int = 2**7, max_pow: int = 2**12) -> int:
if n <= 0:
return min_pow
# nearest power of two
p = 1 << (int(round(math.log2(max(n, 1)))))
# clamp to bounds and adjust if out of range
if p < min_pow:
p = min_pow
if p > max_pow:
# choose the closer between max_pow and previous power
p = max_pow
return p
# New update function: also updates the slider value after snapping
def update_grid_and_qubit_info(grid_size):
snapped = _nearest_pow2(int(grid_size))
num_reg_qubits = int(math.log2(snapped))
total_qubits = 2 * num_reg_qubits + 3
x = np.array([128, 256, 512, 1024, 2048, 4096])
y = np.log2(x).astype(int)
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction'))
fig.add_trace(go.Scatter(x=[snapped], y=[num_reg_qubits], mode='markers', marker=dict(size=12, color='red'), name='Current Selection'))
fig.update_layout(
xaxis_title="Grid Size (Points/Direction)",
yaxis_title="Qubits/Direction",
width=400,
height=300
)
total_qubits_display = f"Total Qubits: {total_qubits}"
warn_pow2 = " (snapped to nearest power of two)" if snapped != int(grid_size) else ""
warning = ("⚠️ Warning: Grid sizes > 1024 may exceed simulator/memory limits!" if snapped > 1024 else "") + warn_pow2
recommended_time_steps = num_reg_qubits * 200
recommended_display = f"Recommended time steps: {recommended_time_steps}"
return gr.update(value=snapped), fig, total_qubits_display, warning, recommended_display
# Modified interface functions to take num_reg_qubits_input
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):
snapped_grid = _nearest_pow2(int(grid_size_input))
if snapped_grid != int(grid_size_input):
gr.Warning(f"Grid size {grid_size_input} is not a power of two. Using {snapped_grid}.")
num_reg_qubits_val = int(math.log2(snapped_grid))
grid_size_val = snapped_grid
time_steps_val = int(time_steps_input)
vx_val = float(vx_param)
vy_val = float(vy_param)
print(f"Gradio Interface: Qubits/Direction={num_reg_qubits_val}, Grid Size={grid_size_val}, T={time_steps_val}, Distribution={distribution_type_param}, Velocity Field={velocity_field_param}, vx={vx_val}, vy={vy_val}, Boundary={boundary_condition_param}")
plot_fig, plotly_json_frames = simulate_qlbm_and_animate( # Modified to unpack two return values
num_reg_qubits=num_reg_qubits_val,
T=time_steps_val,
distribution_type=distribution_type_param,
velocity_field=velocity_field_param,
vx_input=vx_val,
vy_input=vy_val,
boundary_condition=boundary_condition_param
)
if plot_fig is None:
gr.Warning("Simulation or plotting failed. Please check console for errors.")
return None, None # Modified return
return plot_fig, plotly_json_frames # Modified return
# New functions for downloading Plotly objects
def download_plot_data(plotly_json_frames):
if not plotly_json_frames:
gr.Warning("No data to download.")
return None
zip_file_path = tempfile.NamedTemporaryFile(suffix=".zip", delete=False).name
with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zf:
for i, json_str in enumerate(plotly_json_frames):
zf.writestr(f"frame_{i}.json", json_str)
return zip_file_path
# Modified update functions to take grid_size (which is num_reg_qubits_input now)
def update_qubit_info(grid_size):
num_reg_qubits = int(math.log2(grid_size))
total_qubits = 2 * num_reg_qubits + 3
x = np.array([128, 256, 512, 1024, 2048, 4096])
y = np.log2(x).astype(int)
fig = go.Figure()
fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction'))
fig.add_trace(go.Scatter(x=[grid_size], y=[num_reg_qubits], mode='markers', marker=dict(size=12, color='red'), name='Current Selection'))
fig.update_layout(
xaxis_title="Grid Size (Points/Direction)",
yaxis_title="Qubits/Direction",
width=400,
height=300
)
total_qubits_display = f"Total Qubits: {total_qubits}"
warning = "⚠️ Warning: Grid sizes > 1024 may exceed simulator/memory limits!" if grid_size > 1024 else ""
recommended_time_steps = num_reg_qubits * 200
recommended_display = f"Recommended time steps: {recommended_time_steps}"
return fig, total_qubits_display, warning, recommended_display
# Modified example functions to set grid_size
def set_sinusoidal_example():
return (
gr.update(value=256), # grid_size = 256 (corresponds to 8 qubits)
gr.update(value=1600), # time_steps
gr.update(value="Sinusoidal"), # distribution_type
gr.update(value="Uniform"), # velocity_field
gr.update(value=0.2), # vx
gr.update(value=0.15), # vy
gr.update(value="Periodic"), # boundary_condition
)
def set_gaussian_example():
return (
gr.update(value=256), # grid_size = 256 (corresponds to 8 qubits)
gr.update(value=1600), # time_steps
gr.update(value="Gaussian"), # distribution_type
gr.update(value="Uniform"), # velocity_field
gr.update(value=0.2), # vx
gr.update(value=0.15), # vy
gr.update(value="Periodic"), # boundary_condition
)
# Gradio interface for Fluid Dynamics - 2D only
with gr.Blocks(theme=gr.themes.Soft(), title="QLBM Fluid Simulation (2D)") as demo:
with gr.Tabs():
with gr.TabItem("Introduction"):
gr.Markdown(
"""
# Quantum Lattice Boltzmann (QLBM)
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.
What is simulated
- D2Q5 advection–diffusion on a square grid (periodic BCs only).
- Uniform velocity (Vx, Vy) applied to all cells.
- State is evolved via CUDA-Q kernels; results are downsampled and visualized as 3D surfaces with a time slider.
How it works (from the implementation)
- Grid size N = 2^q (q = qubits per spatial dimension). Total qubits = 2*q + 3 direction qubits.
- 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.
- Up to 40 uniform time samples between 0..T are saved for visualization.
Visualization pipeline
- Only the real component of the state is saved and plotted; the initialized field is normalized before evolution.
- 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.
- Plotly renders one surface per saved frame; a slider toggles visibility. You can download per-frame Plotly JSON for offline analysis.
Parameters you control
- 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.
- Time Steps (T): total evolution steps; up to 40 frames are sampled in [0, T].
- Initial Distribution: Sinusoidal or Gaussian.
- Velocity Field: Uniform (set Vx, Vy).
- Boundary Condition: Periodic.
Practical notes
- Recommended time steps ≈ q * 200.
- N > 1024 may exceed available memory/time.
- Requires a CUDA-capable GPU and CUDA-Q runtime; CPU fallback will be slow or may fail.
Workflow
1) Review example initial distributions.
2) Choose grid size N and time steps T.
3) Set Vx and Vy; keep Periodic BC.
4) Run the simulation; use the slider to browse frames.
5) Optionally download the plotted frames as JSON for offline analysis.
"""
)
with gr.TabItem("Simulation"):
with gr.Row(): # Main row for top section
with gr.Column(scale=1): # Column for left-side controls
gr.Markdown("## Initial Distribution Examples")
with gr.Row():
with gr.Column(scale=1):
example1 = LitModel3D("Placeholder_Images/sinusoidal.stl", label="Sinusoidal")
sinusoidal_btn_2d = gr.Button("Sinusoidal")
with gr.Column(scale=1):
example2 = LitModel3D("Placeholder_Images/gaussian.stl", label="Gaussian")
gaussian_btn_2d = gr.Button("Gaussian")
gr.Markdown("## Simulation Parameters")
num_reg_qubits_input_2d = gr.Slider(
minimum=2**7, maximum=2**12, # Grid size values
value=2**8, step=1, # use step=1 for broader Gradio compatibility
label="Grid Size/Direction"
)
time_steps_slider_2d = gr.Slider(minimum=0, maximum=4000, value=1600, step=10, label="Time Steps")
qubit_plot_2d = gr.Plot(label="Qubits vs. Grid Size")
total_qubits_display_2d = gr.Markdown("Total Qubits: 19")
warning_display_2d = gr.Markdown("")
recommended_time_steps_display_2d = gr.Markdown("Recommended time steps: 1600")
with gr.Column(scale=2): # Column for the main plot
qlbm_interactive_plot_2d = gr.Plot(label="QLBM")
download_button_2d = gr.DownloadButton(label="Download Plot Data (JSON)", visible=False) # New download button
plotly_json_frames_state_2d = gr.State([]) # New state to store Plotly JSON frames
with gr.Row(): # Row for bottom section
with gr.Column(scale=1):
gr.Markdown("## Initialization (Uniform)")
selected_velocity_field_2d = gr.State("Uniform")
vx_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.2, step=0.01, label="V_x", visible=True)
vy_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.15, step=0.01, label="V_y", visible=True)
distribution_type_input_2d = gr.Radio(choices=["Gaussian", "Sinusoidal"], value="Sinusoidal", label="Initial Distribution Type")
with gr.Column(scale=1):
gr.Markdown("## Boundary Conditions")
boundary_condition_input_2d = gr.Radio(choices=["Periodic"], value="Periodic", label="Boundary Condition")
run_qlbm_btn_2d = gr.Button("Run Simulation", variant="primary") # Button below the sections
qlbm_inputs_list_2d = [
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
]
run_qlbm_btn_2d.click(
fn=qlbm_gradio_interface,
inputs=qlbm_inputs_list_2d,
outputs=[qlbm_interactive_plot_2d, plotly_json_frames_state_2d] # Modified output
).then(
lambda: gr.update(visible=True), # Make download button visible after simulation
outputs=[download_button_2d]
)
download_button_2d.click(
fn=download_plot_data,
inputs=[plotly_json_frames_state_2d],
outputs=[download_button_2d]
)
num_reg_qubits_input_2d.change(
fn=update_grid_and_qubit_info,
inputs=num_reg_qubits_input_2d,
outputs=[num_reg_qubits_input_2d, qubit_plot_2d, total_qubits_display_2d, warning_display_2d, recommended_time_steps_display_2d]
)
sinusoidal_btn_2d.click(
fn=set_sinusoidal_example,
inputs=[],
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]
)
gaussian_btn_2d.click(
fn=set_gaussian_example,
inputs=[],
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]
)
if __name__ == "__main__":
try:
cudaq.set_target('nvidia', option='fp64')
print(f"CUDA-Q Target successfully set to: {cudaq.get_target().name}")
except Exception as e_target:
print(f"Warning: Could not set CUDA-Q target to 'nvidia'. Error: {e_target}")
print(f"Current CUDA-Q Target: {cudaq.get_target().name}. Performance may be affected.")
# Force local serving; disable any implicit sharing/tunneling
import os
os.environ["GRADIO_SHARE"] = "0"
os.environ["GRADIO_ANALYTICS_ENABLED"] = "0"
os.environ["GRADIO_LAUNCH_BROWSER"] = "0"
# Bind to all interfaces so the host can reach it; no share
demo.launch(server_name="0.0.0.0", server_port=7860, share=False, inbrowser=False, show_error=True)
|