harishaseebat92 commited on
Commit
0b1fdba
·
1 Parent(s): 21f2672

First Push

Browse files
Files changed (3) hide show
  1. app.py +396 -0
  2. fluid.py +1030 -0
  3. requirements.txt +12 -0
app.py ADDED
@@ -0,0 +1,396 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ num_reg_qubits_val = int(math.log2(grid_size_input)) # Convert grid_size back to num_reg_qubits
8
+ grid_size_val = grid_size_input
9
+ time_steps_val = int(time_steps_input)
10
+ vx_val = float(vx_param)
11
+ vy_val = float(vy_param)
12
+
13
+ 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}")
14
+
15
+ plot_fig, plotly_json_frames = simulate_qlbm_and_animate( # Modified to unpack two return values
16
+ num_reg_qubits=num_reg_qubits_val,
17
+ T=time_steps_val,
18
+ distribution_type=distribution_type_param,
19
+ velocity_field=velocity_field_param,
20
+ vx_input=vx_val,
21
+ vy_input=vy_val,
22
+ boundary_condition=boundary_condition_param
23
+ )
24
+
25
+ if plot_fig is None:
26
+ gr.Warning("Simulation or plotting failed. Please check console for errors.")
27
+ return None, None # Modified return
28
+ return plot_fig, plotly_json_frames # Modified return
29
+
30
+ def qlbm_3D_gradio_interface(grid_size_input: int, time_steps_input: int, distribution_type_param: str, vx_param, vy_param, vz_param, boundary_condition_param: str):
31
+ num_reg_qubits_val = int(math.log2(grid_size_input)) # Convert grid_size back to num_reg_qubits
32
+ grid_size_val = grid_size_input
33
+ time_steps_val = int(time_steps_input)
34
+
35
+ vx_val = str(vx_param)
36
+ vy_val = str(vy_param)
37
+ vz_val = str(vz_param)
38
+
39
+ x_sym, y_sym, z_sym = symbols('x y z')
40
+ vx_sympified = sympify(vx_val)
41
+ vy_sympified = sympify(vy_val)
42
+ vz_sympified = sympify(vz_val)
43
+
44
+ vx = lambdify((x_sym, y_sym, z_sym), vx_sympified, modules="numpy")
45
+ vy = lambdify((x_sym, y_sym, z_sym), vy_sympified, modules="numpy")
46
+ vz = lambdify((x_sym, y_sym, z_sym), vz_sympified, modules="numpy")
47
+
48
+ print(f"Gradio Interface: Qubits/Direction={num_reg_qubits_val}, Grid Size={grid_size_val}, T={time_steps_val}, Distribution={distribution_type_param}, vx={vx_val}, vy={vy_val}, vz={vz_val}, Boundary={boundary_condition_param}")
49
+
50
+ plot_fig, plotly_json_frames = simulate_qlbm_3D_and_animate( # Modified to unpack two return values
51
+ num_reg_qubits=num_reg_qubits_val,
52
+ T=time_steps_val,
53
+ distribution_type=distribution_type_param,
54
+ vx_input=vx,
55
+ vy_input=vy,
56
+ vz_input=vz,
57
+ boundary_condition=boundary_condition_param
58
+ )
59
+
60
+ if plot_fig is None:
61
+ gr.Warning("Simulation or plotting failed. Please check console for errors.")
62
+ return None, None # Modified return
63
+ return plot_fig, plotly_json_frames # Modified return
64
+
65
+ # New functions for downloading Plotly objects
66
+ def download_plot_data(plotly_json_frames):
67
+ if not plotly_json_frames:
68
+ gr.Warning("No data to download.")
69
+ return None
70
+
71
+ zip_file_path = tempfile.NamedTemporaryFile(suffix=".zip", delete=False).name
72
+ with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zf:
73
+ for i, json_str in enumerate(plotly_json_frames):
74
+ zf.writestr(f"frame_{i}.json", json_str)
75
+ return zip_file_path
76
+
77
+ # Modified update functions to take grid_size (which is num_reg_qubits_input now)
78
+ def update_qubit_info(grid_size):
79
+ num_reg_qubits = int(math.log2(grid_size))
80
+ total_qubits = 2 * num_reg_qubits + 3
81
+
82
+ x = np.array([128, 256, 512, 1024, 2048, 4096])
83
+ y = np.log2(x).astype(int)
84
+ fig = go.Figure()
85
+ fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction'))
86
+ fig.add_trace(go.Scatter(x=[grid_size], y=[num_reg_qubits], mode='markers', marker=dict(size=12, color='red'), name='Current Selection'))
87
+ fig.update_layout(
88
+ xaxis_title="Grid Size (Points/Direction)",
89
+ yaxis_title="Qubits/Direction",
90
+ width=400,
91
+ height=300
92
+ )
93
+
94
+ total_qubits_display = f"Total Qubits: {total_qubits}"
95
+ warning = "⚠️ Warning: Grid sizes > 1024 may exceed simulator/memory limits!" if grid_size > 1024 else ""
96
+ recommended_time_steps = num_reg_qubits * 200
97
+ recommended_display = f"Recommended time steps: {recommended_time_steps}"
98
+
99
+ return fig, total_qubits_display, warning, recommended_display
100
+
101
+ def update_qubit_3D_info(grid_size):
102
+ num_reg_qubits = int(math.log2(grid_size))
103
+ total_qubits = 3 * num_reg_qubits + 3
104
+ grid_display = f"Grid Size: {grid_size} × {grid_size} × {grid_size}"
105
+
106
+ x = np.array([16, 32, 64, 128, 256])
107
+ y = np.log2(x).astype(int)
108
+ fig = go.Figure()
109
+ fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction'))
110
+ fig.add_trace(go.Scatter(x=[grid_size], y=[num_reg_qubits], mode='markers', marker=dict(size=12, color='red'), name='Current Selection'))
111
+ fig.update_layout(
112
+ xaxis_title="Grid Size (Points/Direction)",
113
+ yaxis_title="Qubits/Direction",
114
+ width=400,
115
+ height=300
116
+ )
117
+ warning = "⚠️ Warning: Grid sizes > 64 may exceed simulator/memory limits!" if grid_size > 64 else ""
118
+ return fig, grid_display, warning
119
+
120
+ # Modified example functions to set grid_size
121
+ def set_sinusoidal_example():
122
+ return (
123
+ gr.update(value=256), # grid_size = 256 (corresponds to 8 qubits)
124
+ gr.update(value=1600), # time_steps
125
+ gr.update(value="Sinusoidal"), # distribution_type
126
+ gr.update(value="User"), # velocity_field
127
+ gr.update(value=0.2), # vx
128
+ gr.update(value=0.15), # vy
129
+ gr.update(value="Periodic"), # boundary_condition
130
+ )
131
+
132
+ def set_gaussian_example():
133
+ return (
134
+ gr.update(value=256), # grid_size = 256 (corresponds to 8 qubits)
135
+ gr.update(value=1600), # time_steps
136
+ gr.update(value="Gaussian"), # distribution_type
137
+ gr.update(value="User"), # velocity_field
138
+ gr.update(value=0.2), # vx
139
+ gr.update(value=0.15), # vy
140
+ gr.update(value="Periodic"), # boundary_condition
141
+ )
142
+
143
+ def set_sinusoidal_3d_example():
144
+ return (
145
+ gr.update(value=32), # grid_size = 32 (corresponds to 5 qubits)
146
+ gr.update(value=100), # time_steps
147
+ gr.update(value="Sinusoidal"), # distribution_type
148
+ gr.update(value="0.2"), # vx
149
+ gr.update(value="-0.15"), # vy
150
+ gr.update(value="0.3"), # vz
151
+ gr.update(value="Periodic"), # boundary_condition
152
+ )
153
+
154
+ def set_gaussian_3d_example():
155
+ return (
156
+ gr.update(value=32), # grid_size = 32 (corresponds to 5 qubits)
157
+ gr.update(value=100), # time_steps
158
+ gr.update(value="Gaussian"), # distribution_type
159
+ gr.update(value="0.2"), # vx
160
+ gr.update(value="-0.15"), # vy
161
+ gr.update(value="0.3"), # vz
162
+ gr.update(value="Periodic"), # boundary_condition
163
+ )
164
+
165
+ # Gradio interface with modified layout and slider
166
+ with gr.Blocks(theme=gr.themes.Soft(), title="Quantum Simulation Demo") as demo:
167
+ problem_type = gr.Radio(
168
+ choices=["Fluid Dynamics", "EM", "Mechanical"],
169
+ value="Fluid Dynamics",
170
+ label="Select Problem Type"
171
+ )
172
+
173
+ with gr.Tabs() as tabs:
174
+ with gr.TabItem("Fluid Dynamics - 2D", visible=True) as fluid_tab_2D:
175
+ with gr.Row(): # Main row for top section
176
+ with gr.Column(scale=1): # Column for left-side controls
177
+ gr.Markdown("## Initial Distribution Examples")
178
+ with gr.Row():
179
+ with gr.Column(scale=1):
180
+ example1 = LitModel3D("Placeholder_Images/sinusoidal.stl", label="Sinusoidal")
181
+ sinusoidal_btn_2d = gr.Button("Sinusoidal")
182
+ with gr.Column(scale=1):
183
+ example2 = LitModel3D("Placeholder_Images/gaussian.stl", label="Gaussian")
184
+ gaussian_btn_2d = gr.Button("Gaussian")
185
+
186
+ gr.Markdown("## Simulation Parameters")
187
+ num_reg_qubits_input_2d = gr.Slider(
188
+ minimum=2**7, maximum=2**12, # Grid size values
189
+ value=2**8, step=None, # step=None for arbitrary powers of 2 (handled by update function)
190
+ label="Grid Size/Direction"
191
+ )
192
+ time_steps_slider_2d = gr.Slider(minimum=0, maximum=4000, value=1600, step=10, label="Time Steps")
193
+ qubit_plot_2d = gr.Plot(label="Qubits vs. Grid Size")
194
+ total_qubits_display_2d = gr.Markdown("Total Qubits: 19")
195
+ warning_display_2d = gr.Markdown("")
196
+ recommended_time_steps_display_2d = gr.Markdown("Recommended time steps: 1600")
197
+
198
+ with gr.Column(scale=2): # Column for the main plot
199
+ qlbm_interactive_plot_2d = gr.Plot(label="QLBM")
200
+ download_button_2d = gr.DownloadButton(label="Download Plot Data (JSON)", visible=False) # New download button
201
+ plotly_json_frames_state_2d = gr.State([]) # New state to store Plotly JSON frames
202
+
203
+ with gr.Row(): # Row for bottom section
204
+ with gr.Column(scale=1):
205
+ gr.Markdown("## Initialization")
206
+ with gr.Row():
207
+ shear_btn_2d = gr.Button("Shear")
208
+ tgv_btn_2d = gr.Button("TGV")
209
+ with gr.Row():
210
+ swirl_btn_2d = gr.Button("Swirl")
211
+ user_btn_2d = gr.Button("User")
212
+ selected_velocity_field_2d = gr.State("User")
213
+ vx_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.2, step=0.01, label="V_x", visible=True)
214
+ vy_slider_2d = gr.Slider(minimum=-0.3, maximum=0.3, value=0.15, step=0.01, label="V_y", visible=True)
215
+ distribution_type_input_2d = gr.Radio(choices=["Gaussian", "Sinusoidal", "Random"], value="Sinusoidal", label="Initial Distribution Type")
216
+
217
+ with gr.Column(scale=1):
218
+ gr.Markdown("## Boundary Conditions")
219
+ boundary_condition_input_2d = gr.Radio(choices=["Periodic", "Dirichlet", "Neumann"], value="Periodic", label="Boundary Condition")
220
+
221
+ run_qlbm_btn_2d = gr.Button("Run Simulation", variant="primary") # Button below the sections
222
+
223
+ qlbm_inputs_list_2d = [
224
+ num_reg_qubits_input_2d, time_steps_slider_2d, distribution_type_input_2d,
225
+ selected_velocity_field_2d, vx_slider_2d, vy_slider_2d, boundary_condition_input_2d
226
+ ]
227
+ run_qlbm_btn_2d.click(
228
+ fn=qlbm_gradio_interface,
229
+ inputs=qlbm_inputs_list_2d,
230
+ outputs=[qlbm_interactive_plot_2d, plotly_json_frames_state_2d] # Modified output
231
+ ).then(
232
+ lambda: gr.update(visible=True), # Make download button visible after simulation
233
+ outputs=[download_button_2d]
234
+ )
235
+ download_button_2d.click(
236
+ fn=download_plot_data,
237
+ inputs=[plotly_json_frames_state_2d],
238
+ outputs=[download_button_2d]
239
+ )
240
+ num_reg_qubits_input_2d.change(
241
+ fn=update_qubit_info,
242
+ inputs=num_reg_qubits_input_2d,
243
+ outputs=[qubit_plot_2d, total_qubits_display_2d, warning_display_2d, recommended_time_steps_display_2d]
244
+ )
245
+ sinusoidal_btn_2d.click(
246
+ fn=set_sinusoidal_example,
247
+ inputs=[],
248
+ 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]
249
+ )
250
+ gaussian_btn_2d.click(
251
+ fn=set_gaussian_example,
252
+ inputs=[],
253
+ 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]
254
+ )
255
+
256
+ def update_velocity_sliders_2d_from_button(velocity_type):
257
+ return velocity_type, gr.update(visible=velocity_type == "User"), gr.update(visible=velocity_type == "User")
258
+
259
+ shear_btn_2d.click(fn=lambda: update_velocity_sliders_2d_from_button("Shear"), outputs=[selected_velocity_field_2d, vx_slider_2d, vy_slider_2d])
260
+ tgv_btn_2d.click(fn=lambda: update_velocity_sliders_2d_from_button("TGV"), outputs=[selected_velocity_field_2d, vx_slider_2d, vy_slider_2d])
261
+ swirl_btn_2d.click(fn=lambda: update_velocity_sliders_2d_from_button("Swirl"), outputs=[selected_velocity_field_2d, vx_slider_2d, vy_slider_2d])
262
+ user_btn_2d.click(fn=lambda: update_velocity_sliders_2d_from_button("User"), outputs=[selected_velocity_field_2d, vx_slider_2d, vy_slider_2d])
263
+
264
+ with gr.TabItem("Fluid Dynamics - 3D", visible=True) as fluid_tab_3D:
265
+ with gr.Row(): # Main row for top section
266
+ with gr.Column(scale=1): # Column for left-side controls
267
+ gr.Markdown("## Initial Distribution Examples")
268
+ with gr.Row():
269
+ with gr.Column(scale=1):
270
+ example1 = LitModel3D("Placeholder_Images/sinusoidal_fluid.stl", label="Sinusoidal")
271
+ sinusoidal_3d_btn = gr.Button("Sinusoidal")
272
+ with gr.Column(scale=1):
273
+ example2 = LitModel3D("Placeholder_Images/gaussian_fluid.stl", label="Gaussian")
274
+ gaussian_3d_btn = gr.Button("Gaussian")
275
+ gr.Markdown("## Simulation Parameters")
276
+ num_reg_qubits_input_3d = gr.Slider(
277
+ minimum=2**4, maximum=2**8, # Grid size values
278
+ value=2**5, step=None, # step=None for arbitrary powers of 2
279
+ label="Grid Size/Direction"
280
+ )
281
+ time_steps_slider_3d = gr.Slider(minimum=0, maximum=2000, value=100, step=10, label="Time Steps")
282
+ # Removed num_slider_steps_slider_3d
283
+ qubit_plot_3d = gr.Plot(label="Qubits vs. Grid Size")
284
+ grid_display_3d = gr.Markdown("Grid Size: 32 × 32 × 32")
285
+ warning_display_3d = gr.Markdown("")
286
+
287
+ with gr.Column(scale=2): # Column for the main plot
288
+ qlbm_interactive_plot_3d = gr.Plot(label="QLBM")
289
+ download_button_3d = gr.DownloadButton(label="Download Plot Data (JSON)", visible=False) # New download button
290
+ plotly_json_frames_state_3d = gr.State([]) # New state to store Plotly JSON frames
291
+
292
+ with gr.Row(): # Row for bottom section
293
+ with gr.Column(scale=1):
294
+ gr.Markdown("## Initialization")
295
+ with gr.Row():
296
+ uniform_btn_3d = gr.Button("Uniform")
297
+ swirl_btn_3d = gr.Button("Swirl")
298
+ with gr.Row():
299
+ shear_btn_3d = gr.Button("Shear")
300
+ tgv_btn_3d = gr.Button("TGV")
301
+ vx_text_input_3d = gr.Textbox(value="0.2", label="Velocity vx")
302
+ vy_text_input_3d = gr.Textbox(value="-0.15", label="Velocity vy")
303
+ vz_text_input_3d = gr.Textbox(value="0.3", label="Velocity vz")
304
+ distribution_type_input_3d = gr.Radio(choices=["Gaussian", "Sinusoidal"], value="Sinusoidal", label="Initial Distribution Type")
305
+
306
+ with gr.Column(scale=1):
307
+ gr.Markdown("## Boundary Conditions")
308
+ boundary_condition_input_3d = gr.Radio(choices=["Periodic", "Dirichlet", "Neumann"], value="Periodic", label="Boundary Condition")
309
+
310
+ run_qlbm_btn_3d = gr.Button("Run Simulation", variant="primary") # Button below the sections
311
+
312
+ qlbm_inputs_list_3d = [
313
+ num_reg_qubits_input_3d, time_steps_slider_3d, distribution_type_input_3d,
314
+ vx_text_input_3d, vy_text_input_3d, vz_text_input_3d, boundary_condition_input_3d
315
+ # Removed num_slider_steps_slider_3d from inputs
316
+ ]
317
+ run_qlbm_btn_3d.click(
318
+ fn=qlbm_3D_gradio_interface,
319
+ inputs=qlbm_inputs_list_3d,
320
+ outputs=[qlbm_interactive_plot_3d, plotly_json_frames_state_3d] # Modified output
321
+ ).then(
322
+ lambda: gr.update(visible=True), # Make download button visible after simulation
323
+ outputs=[download_button_3d]
324
+ )
325
+ download_button_3d.click(
326
+ fn=download_plot_data,
327
+ inputs=[plotly_json_frames_state_3d],
328
+ outputs=[download_button_3d]
329
+ )
330
+ num_reg_qubits_input_3d.change(
331
+ fn=update_qubit_3D_info,
332
+ inputs=num_reg_qubits_input_3d,
333
+ outputs=[qubit_plot_3d, grid_display_3d, warning_display_3d]
334
+ )
335
+
336
+ sinusoidal_3d_btn.click(
337
+ fn=set_sinusoidal_3d_example,
338
+ inputs=[],
339
+ outputs=[num_reg_qubits_input_3d, time_steps_slider_3d, distribution_type_input_3d, vx_text_input_3d, vy_text_input_3d, vz_text_input_3d, boundary_condition_input_3d]
340
+ )
341
+ gaussian_3d_btn.click(
342
+ fn=set_gaussian_3d_example,
343
+ inputs=[],
344
+ outputs=[num_reg_qubits_input_3d, time_steps_slider_3d, distribution_type_input_3d, vx_text_input_3d, vy_text_input_3d, vz_text_input_3d, boundary_condition_input_3d]
345
+ )
346
+
347
+ def update_velocity_entries_3d(val):
348
+ fieldname_to_index = {"Uniform": 0, "Swirl": 1, "Shear": 2, "TGV": 3}
349
+ selection = fieldname_to_index[val]
350
+ return (
351
+ gr.update(value=(["0.2", "0.3*sin(-2*pi*z)", "abs(z-0.5)*1.2-0.3", "0.15*cos(2*pi*x)*sin(2*pi*y)*sin(2*pi*z)"])[selection]),
352
+ gr.update(value=(["-0.15", "0.2", "0", "-0.3*sin(2*pi*x)*cos(2*pi*y)*sin(2*pi*z)"])[selection]),
353
+ gr.update(value=(["0.3", "0.3*sin(2*pi*x)", "0", "0.15*sin(2*pi*x)*sin(2*pi*y)*cos(2*pi*z)"])[selection])
354
+ )
355
+
356
+ uniform_btn_3d.click(fn=lambda: update_velocity_entries_3d("Uniform"), outputs=[vx_text_input_3d, vy_text_input_3d, vz_text_input_3d])
357
+ swirl_btn_3d.click(fn=lambda: update_velocity_entries_3d("Swirl"), outputs=[vx_text_input_3d, vy_text_input_3d, vz_text_input_3d])
358
+ shear_btn_3d.click(fn=lambda: update_velocity_entries_3d("Shear"), outputs=[vx_text_input_3d, vy_text_input_3d, vz_text_input_3d])
359
+ tgv_btn_3d.click(fn=lambda: update_velocity_entries_3d("TGV"), outputs=[vx_text_input_3d, vy_text_input_3d, vz_text_input_3d])
360
+
361
+ with gr.TabItem("EM", visible=False) as em_tab:
362
+ gr.Markdown("## Coming Soon")
363
+ with gr.TabItem("Mechanical", visible=False) as mech_tab:
364
+ gr.Markdown("## Coming Soon")
365
+
366
+ def update_tabs(selection):
367
+ return (
368
+ gr.update(visible=selection == "Fluid Dynamics"),
369
+ gr.update(visible=selection == "Fluid Dynamics"),
370
+ gr.update(visible=selection == "EM"),
371
+ gr.update(visible=selection == "Mechanical")
372
+ )
373
+
374
+ problem_type.change(
375
+ fn=update_tabs,
376
+ inputs=problem_type,
377
+ outputs=[fluid_tab_2D, fluid_tab_3D, em_tab, mech_tab]
378
+ )
379
+
380
+ if __name__ == "__main__":
381
+ try:
382
+ cudaq.set_target('nvidia', option='fp64')
383
+ print(f"CUDA-Q Target successfully set to: {cudaq.get_target().name}")
384
+ except Exception as e_target:
385
+ print(f"Warning: Could not set CUDA-Q target to 'nvidia'. Error: {e_target}")
386
+ print(f"Current CUDA-Q Target: {cudaq.get_target().name}. Performance may be affected.")
387
+
388
+ # Force local serving; disable any implicit sharing/tunneling
389
+ import os
390
+ os.environ["GRADIO_SHARE"] = "0"
391
+ os.environ["GRADIO_ANALYTICS_ENABLED"] = "0"
392
+ os.environ["GRADIO_LAUNCH_BROWSER"] = "0"
393
+
394
+ # Bind to all interfaces so the host can reach it; no share
395
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False, inbrowser=False, show_error=True)
396
+
fluid.py ADDED
@@ -0,0 +1,1030 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import math
3
+ import tempfile
4
+ import gradio as gr
5
+ import cudaq
6
+ import numpy as np
7
+ import cupy as cp
8
+ from pathlib import Path
9
+ import plotly.graph_objects as go
10
+ import plotly.io as pio
11
+ from sympy import sympify, symbols, lambdify
12
+ from gradio_litmodel3d import LitModel3D
13
+ import zipfile
14
+
15
+ # Set Plotly engine for image export
16
+ try:
17
+ pio.kaleido.scope.mathjax = None
18
+ except AttributeError:
19
+ pass
20
+
21
+ # Existing functions (bin_to_gray, gray_to_bin, etc.) remain unchanged
22
+ def bin_to_gray(bin_s):
23
+ XOR=lambda x,y: (x or y) and not (x and y)
24
+ gray_s=bin_s[0]
25
+ for i in range(len(bin_s)-1):
26
+ c_bool=XOR(bool(int(bin_s[i])),bool(int(bin_s[i+1])))
27
+ gray_s+=str(int(c_bool))
28
+ return gray_s
29
+
30
+ def gray_to_bin(gray_s):
31
+ XOR=lambda x,y: (x or y) and not (x and y)
32
+ bin_s=gray_s[0]
33
+ for i in range(len(gray_s)-1):
34
+ c_bool=XOR(bool(int(bin_s[i])),bool(int(gray_s[i+1])))
35
+ bin_s+=str(int(c_bool))
36
+ return bin_s
37
+
38
+ def bin_to_int(bin_s):
39
+ return int(bin_s,2)
40
+
41
+ def int_to_bin(i,pad):
42
+ return bin(i)[2:].zfill(pad)
43
+
44
+ def fwht_approx(f,N,num_points_per_dim,threshold=1e-10):
45
+ linear_block_size=int(N//num_points_per_dim)
46
+ num_angles_per_block=int(np.log2(linear_block_size))
47
+
48
+ thetas={}
49
+
50
+ for k in range(num_points_per_dim):
51
+ for j in range(num_points_per_dim):
52
+ for i in range(num_points_per_dim):
53
+
54
+ avg_f=2*np.arccos(f(i*linear_block_size+(linear_block_size-1)/2,j*linear_block_size+(linear_block_size-1)/2,k*linear_block_size+(linear_block_size-1)/2))
55
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size]=avg_f
56
+
57
+ slope_x=(2*np.arccos(f(i*linear_block_size,j*linear_block_size+(linear_block_size-1)/2,k*linear_block_size+(linear_block_size-1)/2))-2*np.arccos(f(((i+1)%N)*linear_block_size,j*linear_block_size+(linear_block_size-1)/2,k*linear_block_size+(linear_block_size-1)/2)))/linear_block_size
58
+ slope_y=(2*np.arccos(f(i*linear_block_size+(linear_block_size-1)/2,j*linear_block_size,k*linear_block_size+(linear_block_size-1)/2))-2*np.arccos(f(i*linear_block_size+(linear_block_size-1)/2,((j+1)%N)*linear_block_size,k*linear_block_size+(linear_block_size-1)/2)))/linear_block_size
59
+ slope_z=(2*np.arccos(f(i*linear_block_size+(linear_block_size-1)/2,j*linear_block_size+(linear_block_size-1)/2,k*linear_block_size))-2*np.arccos(f(i*linear_block_size+(linear_block_size-1)/2,j*linear_block_size+(linear_block_size-1)/2,((k+1)%N)*linear_block_size)))/linear_block_size
60
+
61
+ for m in range(num_angles_per_block):
62
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size + 2**m]=slope_x*(2**(m-1))
63
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size + N*(2**m)]=slope_y*(2**(m-1))
64
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size + (N**2)*(2**m)]=slope_z*(2**(m-1))
65
+
66
+ h = linear_block_size
67
+ while h < N**3:
68
+ for i in range(0, N**3, h * 2):
69
+ if (i//N)%linear_block_size!=0:
70
+ continue
71
+ if (i//(N**2))%linear_block_size!=0:
72
+ continue
73
+ j=i
74
+ while j<i+h:
75
+ index=j
76
+ x = thetas[index]
77
+ y = thetas[index + h]
78
+ thetas[index] = (x + y)/2
79
+ thetas[index + h] = (x - y)/2
80
+
81
+ for ax in range(3):
82
+ for m in range(num_angles_per_block):
83
+ index = j + (N**ax) * (2**m)
84
+ x = thetas[index]
85
+ y = thetas[index + h]
86
+ thetas[index] = (x + y)/2
87
+ thetas[index + h] = (x - y)/2
88
+
89
+ j+=linear_block_size
90
+ if (j//N)%linear_block_size==1:
91
+ j+=(linear_block_size-1)*N
92
+ if (j//(N**2))%linear_block_size==1:
93
+ j+=(linear_block_size-1)*(N**2)
94
+
95
+ h *= 2
96
+ if h==N:
97
+ h=N*linear_block_size
98
+ if h==N**2:
99
+ h=(N**2)*linear_block_size
100
+
101
+ return [theta for theta in thetas.values() if abs(theta)>threshold],[key for key in thetas.keys() if abs(thetas[key])>threshold]
102
+
103
+ def get_circuit_inputs(f,num_reg_qubits,num_points_per_dim):
104
+ theta_vec,indices=fwht_approx(f,2**num_reg_qubits,num_points_per_dim)
105
+ circ_pos=[]
106
+ for ind in indices:
107
+ circ_pos+=[bin_to_int(gray_to_bin(int_to_bin(ind,num_reg_qubits*3)))]
108
+
109
+ sorted_theta_vec=sorted(zip(theta_vec,circ_pos),key=lambda el:el[1])
110
+ ctrls=[]
111
+
112
+ current_bs="0"*(3*num_reg_qubits)
113
+ for el in sorted_theta_vec:
114
+ new_bs=bin_to_gray(int_to_bin((el[1])%(2**(3*num_reg_qubits)),(3*num_reg_qubits)))
115
+ ctrls += [[i for i, (char1, char2) in enumerate(zip(current_bs, new_bs)) if char1 != char2]]
116
+ current_bs=new_bs
117
+ new_bs="0"*(3*num_reg_qubits)
118
+ ctrls += [[i for i, (char1, char2) in enumerate(zip(current_bs, new_bs)) if char1 != char2]]
119
+
120
+ ctrls_flat_list=[]
121
+ for ctrl_list in ctrls:
122
+ ctrls_flat_list+=[len(ctrl_list)]+ctrl_list
123
+
124
+ return [el[0] for el in sorted_theta_vec]+[0.0],ctrls_flat_list
125
+
126
+ # Simulation functions remain unchanged
127
+ def simulate_qlbm_and_animate(num_reg_qubits: int, T: int, distribution_type: str, velocity_field: str, vx_input: float, vy_input: float, boundary_condition: str):
128
+ num_anc = 3
129
+ num_qubits_total = 2 * num_reg_qubits + num_anc
130
+ current_N = 2**num_reg_qubits
131
+ N_tot_state_vector = 2**num_qubits_total
132
+ num_ranks = 1
133
+ rank = 0
134
+ N_sub_per_rank = int(N_tot_state_vector // num_ranks)
135
+ NUM_ANIMATION_FRAMES = 40
136
+
137
+ if T == 0:
138
+ time_steps = [0]
139
+ else:
140
+ num_points = min(T + 1, NUM_ANIMATION_FRAMES)
141
+ time_steps = np.linspace(start=0, stop=T, num=num_points, dtype=int)
142
+ time_steps = sorted(list(set(time_steps)))
143
+
144
+ if distribution_type == "Sinusoidal":
145
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
146
+ np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
147
+ np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
148
+ elif distribution_type == "Gaussian":
149
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
150
+ np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
151
+ (y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
152
+ elif distribution_type == "Random":
153
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
154
+ np.random.rand(N_val_func, N_val_func) * 1.5 + 0.2 if isinstance(x, int) else \
155
+ np.random.rand(x.shape[0], x.shape[1]) * 1.5 + 0.2
156
+ else:
157
+ print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sinusoidal.")
158
+ selected_initial_state_function_raw = lambda x, y, N_val_func: \
159
+ np.sin(x * 2 * np.pi / N_val_func) * (1 - 0.5 * x / N_val_func) * \
160
+ np.sin(y * 4 * np.pi / N_val_func) * (1 - 0.5 * y / N_val_func) + 1
161
+
162
+ initial_state_func_eval = lambda x_coords, y_coords: \
163
+ selected_initial_state_function_raw(x_coords, y_coords, current_N) * \
164
+ (y_coords < current_N).astype(int)
165
+
166
+ if velocity_field == "User":
167
+ pass
168
+ elif velocity_field == "Shear":
169
+ vx_input = vx_input * (current_N / 2)
170
+ elif velocity_field == "TGV":
171
+ vx_input = vx_input * np.sin(np.pi * current_N / 10)
172
+ vy_input = vy_input * np.cos(np.pi * current_N / 10)
173
+ elif velocity_field == "Swirl":
174
+ vx_input = vx_input * np.cos(np.pi * current_N / 5)
175
+ vy_input = vy_input * np.sin(np.pi * current_N / 5)
176
+
177
+ with tempfile.TemporaryDirectory() as tmp_npy_dir:
178
+ intermediate_folder_path = Path(tmp_npy_dir)
179
+ cudaq.set_target('nvidia', option='fp64')
180
+
181
+ @cudaq.kernel
182
+ def alloc_kernel(num_qubits_alloc: int):
183
+ qubits = cudaq.qvector(num_qubits_alloc)
184
+
185
+ from cupy.cuda.memory import MemoryPointer, UnownedMemory
186
+
187
+ def to_cupy_array(state):
188
+ tensor = state.getTensor()
189
+ pDevice = tensor.data()
190
+ sizeByte = tensor.get_num_elements() * tensor.get_element_size()
191
+ mem = UnownedMemory(pDevice, sizeByte, owner=state)
192
+ memptr_obj = MemoryPointer(mem, 0)
193
+ cupy_array_val = cp.ndarray(tensor.get_num_elements(),
194
+ dtype=cp.complex128,
195
+ memptr=memptr_obj)
196
+ return cupy_array_val
197
+
198
+ class QLBMAdvecDiffD2Q5_new:
199
+ def __init__(self, vx=0.2, vy=0.15) -> None:
200
+ self.dim = 2
201
+ self.ndir = 5
202
+ self.nq_dir = math.ceil(np.log2(self.ndir))
203
+ self.dirs = []
204
+ for dir_int in range(self.ndir):
205
+ dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
206
+ self.dirs.append(dir_bin)
207
+ self.e_unitvec = np.array([0, 1, -1, 1, -1])
208
+ self.wts = np.array([2/6, 1/6, 1/6, 1/6, 1/6])
209
+ self.cs = 1 / np.sqrt(3)
210
+ self.vx = vx
211
+ self.vy = vy
212
+ self.u = np.array([0, self.vx, self.vx, self.vy, self.vy])
213
+ self.wtcoeffs = np.multiply(self.wts, 1 + self.e_unitvec * self.u / self.cs**2)
214
+ self.create_circuit()
215
+
216
+ def create_circuit(self):
217
+ v = np.pad(self.wtcoeffs, (0, 2**num_anc - self.ndir))
218
+ v = v**0.5
219
+ v = v / np.linalg.norm(v)
220
+ U_prep = 2 * np.outer(v, v) - np.eye(len(v))
221
+ cudaq.register_operation("prep_op", U_prep)
222
+
223
+ def collisionOp(dirs_list):
224
+ dirs_i_list_val = []
225
+ for dir_str in dirs_list:
226
+ dirs_i = [(int(c)) for c in dir_str]
227
+ dirs_i_list_val += dirs_i[::-1]
228
+ return dirs_i_list_val
229
+
230
+ self.dirs_i_list = collisionOp(self.dirs)
231
+
232
+ @cudaq.kernel
233
+ def rshift(q: cudaq.qview, n: int):
234
+ for i in range(n):
235
+ if i == n - 1:
236
+ x(q[n - 1 - i])
237
+ elif i == n - 2:
238
+ x.ctrl(q[n - 1 - (i + 1)], q[n - 1 - i])
239
+ else:
240
+ x.ctrl(q[0:n - 1 - i], q[n - 1 - i])
241
+
242
+ @cudaq.kernel
243
+ def lshift(q: cudaq.qview, n: int):
244
+ for i in range(n):
245
+ if i == 0:
246
+ x(q[0])
247
+ elif i == 1:
248
+ x.ctrl(q[0], q[1])
249
+ else:
250
+ x.ctrl(q[0:i], q[i])
251
+
252
+ @cudaq.kernel
253
+ def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
254
+ qx = q[0:nqx]
255
+ qy = q[nqx:nqx + nqy]
256
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
257
+
258
+ idx_lqx = 2
259
+ b_list = dirs_i_val[idx_lqx * nq_dir_val:(idx_lqx + 1) * nq_dir_val]
260
+ for j in range(nq_dir_val):
261
+ if b_list[j] == 0: x(qdir[j])
262
+ cudaq.control(lshift, qdir, qx, nqx)
263
+ for j in range(nq_dir_val):
264
+ if b_list[j] == 0: x(qdir[j])
265
+
266
+ idx_rqx = 1
267
+ b_list = dirs_i_val[idx_rqx * nq_dir_val:(idx_rqx + 1) * nq_dir_val]
268
+ for j in range(nq_dir_val):
269
+ if b_list[j] == 0: x(qdir[j])
270
+ cudaq.control(rshift, qdir, qx, nqx)
271
+ for j in range(nq_dir_val):
272
+ if b_list[j] == 0: x(qdir[j])
273
+
274
+ idx_lqy = 4
275
+ b_list = dirs_i_val[idx_lqy * nq_dir_val:(idx_lqy + 1) * nq_dir_val]
276
+ for j in range(nq_dir_val):
277
+ if b_list[j] == 0: x(qdir[j])
278
+ cudaq.control(lshift, qdir, qy, nqy)
279
+ for j in range(nq_dir_val):
280
+ if b_list[j] == 0: x(qdir[j])
281
+
282
+ idx_rqy = 3
283
+ b_list = dirs_i_val[idx_rqy * nq_dir_val:(idx_rqy + 1) * nq_dir_val]
284
+ for j in range(nq_dir_val):
285
+ if b_list[j] == 0: x(qdir[j])
286
+ cudaq.control(rshift, qdir, qy, nqy)
287
+ for j in range(nq_dir_val):
288
+ if b_list[j] == 0: x(qdir[j])
289
+
290
+ @cudaq.kernel
291
+ def d2q5_tstep_wrapper(state_arg: cudaq.State, nqx: int, nqy: int, nq_dir_val: int, dirs_i_val: list[int]):
292
+ q = cudaq.qvector(state_arg)
293
+ qdir = q[nqx + nqy:nqx + nqy + nq_dir_val]
294
+ prep_op(qdir[2], qdir[1], qdir[0])
295
+ d2q5_tstep(q, nqx, nqy, nq_dir_val, dirs_i_val)
296
+ prep_op(qdir[2], qdir[1], qdir[0])
297
+
298
+ def run_timestep_func(vec_arg, hadamard=False):
299
+ result = cudaq.get_state(d2q5_tstep_wrapper, vec_arg, num_reg_qubits, num_reg_qubits, self.nq_dir, self.dirs_i_list)
300
+ num_nonzero_ranks = num_ranks / (2**num_anc)
301
+ rank_slice_cupy = to_cupy_array(result)
302
+ if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
303
+ sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
304
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
305
+ if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
306
+ limit_idx = int(N_tot_state_vector / (2**num_anc))
307
+ if limit_idx < rank_slice_cupy.size:
308
+ rank_slice_cupy[limit_idx:] = 0
309
+ return result
310
+ self.run_timestep = run_timestep_func
311
+
312
+ def write_state(self, state_to_write, t_step_str_val):
313
+ rank_slice_cupy = to_cupy_array(state_to_write)
314
+ num_nonzero_ranks = num_ranks / (2**num_anc)
315
+ if rank < num_nonzero_ranks or (rank == 0 and num_nonzero_ranks <= 0):
316
+ save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
317
+ with open(save_path, 'wb') as f:
318
+ arr_to_save = None
319
+ data_limit = N_sub_per_rank
320
+ if num_nonzero_ranks < 1 and rank == 0:
321
+ data_limit = int(N_tot_state_vector / (2**num_anc))
322
+ if data_limit > 0:
323
+ relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
324
+ else:
325
+ relevant_part_cupy = cp.array([], dtype=cp.float64)
326
+ if relevant_part_cupy.size >= current_N * current_N:
327
+ arr_flat = relevant_part_cupy[:current_N * current_N]
328
+ if downsampling_factor > 1 and current_N > 0:
329
+ arr_reshaped = arr_flat.reshape((current_N, current_N))
330
+ arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor]
331
+ arr_to_save = arr_downsampled.flatten()
332
+ else:
333
+ arr_to_save = arr_flat
334
+ elif relevant_part_cupy.size > 0:
335
+ if downsampling_factor > 1:
336
+ arr_to_save = relevant_part_cupy[::downsampling_factor]
337
+ else:
338
+ arr_to_save = relevant_part_cupy
339
+ if arr_to_save is not None and arr_to_save.size > 0:
340
+ np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
341
+
342
+ def run_evolution(self, initial_state_arg, total_timesteps, time_steps_to_save, observable=False):
343
+ current_state_val = initial_state_arg
344
+ save_times = set(time_steps_to_save)
345
+ if 0 in save_times:
346
+ self.write_state(current_state_val, '0')
347
+ for t_iter in range(total_timesteps):
348
+ if (t_iter + 1) in save_times:
349
+ next_state_val = self.run_timestep(current_state_val)
350
+ self.write_state(next_state_val, str(t_iter + 1))
351
+ current_state_val = next_state_val
352
+ else:
353
+ current_state_val = self.run_timestep(current_state_val)
354
+ cp.get_default_memory_pool().free_all_blocks()
355
+ if rank == 0:
356
+ print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
357
+ cp.get_default_memory_pool().free_all_blocks()
358
+ self.final_state = current_state_val
359
+
360
+ if boundary_condition == "Periodic":
361
+ pass
362
+ elif boundary_condition == "Dirichlet":
363
+ pass
364
+ elif boundary_condition == "Neumann":
365
+ pass
366
+
367
+ downsampling_factor = 2**5
368
+ if current_N == 0:
369
+ print("Error: current_N is zero. num_reg_qubits likely too small.")
370
+ return None, None # Modified return
371
+ if current_N < downsampling_factor:
372
+ downsampling_factor = current_N if current_N > 0 else 1
373
+
374
+ qlbm_obj = QLBMAdvecDiffD2Q5_new(vx=vx_input, vy=vy_input)
375
+ initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
376
+
377
+ xv_init = np.arange(current_N)
378
+ yv_init = np.arange(current_N)
379
+ initial_grid_2d_X, initial_grid_2d_Y = np.meshgrid(xv_init, yv_init)
380
+
381
+ if distribution_type == "Random":
382
+ initial_grid_2d = selected_initial_state_function_raw(current_N, current_N, current_N)
383
+ else:
384
+ initial_grid_2d = initial_state_func_eval(initial_grid_2d_X, initial_grid_2d_Y)
385
+
386
+ sub_sv_init_flat = initial_grid_2d.flatten().astype(np.complex128)
387
+ norm = np.linalg.norm(sub_sv_init_flat)
388
+ if norm > 0:
389
+ sub_sv_init_flat /= norm
390
+ else:
391
+ print("Error: Initial state norm is zero.")
392
+ return None, None # Modified return
393
+ full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
394
+ num_computational_states = current_N * current_N
395
+ if len(sub_sv_init_flat) == num_computational_states:
396
+ if num_computational_states <= N_sub_per_rank:
397
+ full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
398
+ else:
399
+ print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
400
+ return None, None # Modified return
401
+ else:
402
+ print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
403
+ fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
404
+ full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
405
+
406
+ rank_slice_init = to_cupy_array(initial_state_val)
407
+ print(f'Rank {rank}: Initializing state with {distribution_type} (vx={vx_input}, vy={vy_input})...')
408
+ cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
409
+ print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
410
+
411
+ print("Starting QLBM evolution...")
412
+ qlbm_obj.run_evolution(initial_state_val, T, time_steps)
413
+ print("QLBM evolution complete.")
414
+
415
+ print("Generating interactive plot with Plotly...")
416
+ downsampled_N = current_N // downsampling_factor
417
+ if downsampled_N == 0 and current_N > 0:
418
+ downsampled_N = 1
419
+ elif current_N == 0:
420
+ print("Error: current_N is zero before Plotly stage.")
421
+ return None, None # Modified return
422
+
423
+ data_frames = []
424
+ actual_timesteps = []
425
+ for t in time_steps:
426
+ file_path = intermediate_folder_path / f"{t}_{rank}.npy"
427
+ if file_path.exists():
428
+ sol_loaded = np.load(file_path)
429
+ if sol_loaded.size == downsampled_N * downsampled_N:
430
+ Z_data = np.reshape(sol_loaded, (downsampled_N, downsampled_N))
431
+ data_frames.append(Z_data)
432
+ actual_timesteps.append(t)
433
+ print(f"Time {t}: Min={np.min(Z_data)}, Max={np.max(Z_data)}, Mean={np.mean(Z_data)}")
434
+ else:
435
+ print(f"Warning: File {file_path} size {sol_loaded.size} != expected {downsampled_N*downsampled_N}. Skipping.")
436
+ else:
437
+ print(f"Warning: File {file_path} not found. Skipping.")
438
+
439
+ if not data_frames:
440
+ print("Error: No data frames loaded for plotting.")
441
+ return None, None # Modified return
442
+
443
+ x_coords_plot = np.linspace(0, 1, downsampled_N)
444
+ y_coords_plot = np.linspace(0, 1, downsampled_N)
445
+
446
+ z_min = min([np.min(Z) for Z in data_frames])
447
+ z_max = max([np.max(Z) for Z in data_frames])
448
+ if z_max == z_min:
449
+ z_max += 1e-9
450
+
451
+ fig = go.Figure()
452
+
453
+ # Store individual frames for download
454
+ plotly_json_frames = []
455
+
456
+ for i, Z in enumerate(data_frames):
457
+ frame_trace = go.Surface(
458
+ z=Z, x=x_coords_plot, y=y_coords_plot,
459
+ colorscale='Viridis',
460
+ cmin=z_min, cmax=z_max,
461
+ name=f'Time: {actual_timesteps[i]}',
462
+ showscale=True
463
+ )
464
+ fig.add_trace(frame_trace)
465
+
466
+ # Create a figure for the individual frame and convert to JSON
467
+ single_frame_fig = go.Figure(data=[frame_trace], layout=fig.layout)
468
+ single_frame_fig.update_layout(
469
+ title=f"Time: {actual_timesteps[i]}",
470
+ scene=dict(
471
+ xaxis_title='X',
472
+ yaxis_title='Y',
473
+ zaxis_title='Density',
474
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
475
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
476
+ zaxis=dict(range=[z_min, z_max]),
477
+ )
478
+ )
479
+ plotly_json_frames.append(single_frame_fig.to_json())
480
+
481
+
482
+ for trace in fig.data[1:]:
483
+ trace.visible = False
484
+
485
+ steps = []
486
+ for i in range(len(data_frames)):
487
+ step = dict(
488
+ method="update",
489
+ args=[{"visible": [False] * len(data_frames)}],
490
+ label=f"Time: {actual_timesteps[i]}"
491
+ )
492
+ step["args"][0]["visible"][i] = True
493
+ steps.append(step)
494
+
495
+ sliders = [dict(active=0, currentvalue={"prefix": "Time: "}, pad={"t": 50}, steps=steps)]
496
+
497
+ fig.update_layout(
498
+ title='', # Removed graph title
499
+ scene=dict(
500
+ xaxis_title='X',
501
+ yaxis_title='Y',
502
+ zaxis_title='Density',
503
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
504
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
505
+ zaxis=dict(range=[z_min, z_max]),
506
+ ),
507
+ sliders=sliders,
508
+ width=800,
509
+ height=700
510
+ )
511
+
512
+ return fig, plotly_json_frames # Modified return
513
+
514
+ def simulate_qlbm_3D_and_animate(num_reg_qubits: int, T: int, distribution_type: str, vx_input, vy_input, vz_input, boundary_condition: str):
515
+ num_anc = 3
516
+ num_qubits_total = 3 * num_reg_qubits + num_anc
517
+ current_N = 2**num_reg_qubits
518
+ N_tot_state_vector = 2**num_qubits_total
519
+ num_ranks = 1
520
+ rank = 0
521
+ N_sub_per_rank = int(N_tot_state_vector // num_ranks)
522
+
523
+ # Simplified time steps for 3D since slider steps are removed
524
+ NUM_ANIMATION_FRAMES_3D = 10 # Default number of frames if no specific slider steps
525
+
526
+ if T == 0:
527
+ time_steps = [0]
528
+ else:
529
+ num_points = min(T + 1, NUM_ANIMATION_FRAMES_3D)
530
+ time_steps = np.linspace(start=0, stop=T, num=num_points, dtype=int)
531
+ time_steps = sorted(list(set(time_steps)))
532
+
533
+
534
+ if distribution_type == "Sinusoidal":
535
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
536
+ np.sin(x * 2 * np.pi / N_val_func) * \
537
+ np.sin(y * 2 * np.pi / N_val_func) * \
538
+ np.sin(z * 2 * np.pi / N_val_func) + 1
539
+ elif distribution_type == "Gaussian":
540
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
541
+ np.exp(-((x - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) + (y - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2) +
542
+ (z - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
543
+ else:
544
+ print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sinusoidal.")
545
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
546
+ np.sin(x * 2 * np.pi / N_val_func) * \
547
+ np.sin(y * 2 * np.pi / N_val_func) * \
548
+ np.sin(z * 2 * np.pi / N_val_func) + 1
549
+
550
+ initial_state_func_eval = lambda i:\
551
+ selected_initial_state_function_raw(i%current_N,(i//current_N)%current_N,i//(current_N**2),current_N)*(i<(current_N**3)).astype(int)
552
+
553
+ with tempfile.TemporaryDirectory() as tmp_npy_dir:
554
+ intermediate_folder_path = Path(tmp_npy_dir)
555
+
556
+ cudaq.set_target('nvidia', option='fp64')
557
+
558
+ @cudaq.kernel
559
+ def alloc_kernel(num_qubits_alloc: int):
560
+ qubits = cudaq.qvector(num_qubits_alloc)
561
+
562
+ from cupy.cuda.memory import MemoryPointer, UnownedMemory
563
+
564
+ def to_cupy_array(state):
565
+ tensor = state.getTensor()
566
+ pDevice = tensor.data()
567
+ sizeByte = tensor.get_num_elements() * tensor.get_element_size()
568
+ mem = UnownedMemory(pDevice, sizeByte, owner=state)
569
+ memptr_obj = MemoryPointer(mem, 0)
570
+ cupy_array_val = cp.ndarray(tensor.get_num_elements(),
571
+ dtype=cp.complex128,
572
+ memptr=memptr_obj)
573
+ return cupy_array_val
574
+
575
+ class QLBMAdvecDiffD3Q7_new:
576
+ def __init__(self,vx,vy,vz) -> None:
577
+ self.dim = 3
578
+ self.ndir = 7
579
+ self.nq_dir = math.ceil(np.log2(self.ndir))
580
+ self.dirs=[]
581
+ for dir_int in range(self.ndir):
582
+ if dir_int==4:
583
+ dir_bin="111"
584
+ else:
585
+ dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
586
+ self.dirs.append(dir_bin)
587
+ self.cs = 1/np.sqrt(3)
588
+ self.ux = lambda x,y,z: vx(x,y,z)/self.cs**2
589
+ self.uy = lambda x,y,z: vy(x,y,z)/self.cs**2
590
+ self.uz = lambda x,y,z: vz(x,y,z)/self.cs**2
591
+ self.create_circuit()
592
+
593
+ def create_circuit(self):
594
+ print("Creating circuit")
595
+ x_coeffs,x_coeff_var_indices=get_circuit_inputs(lambda x,y,z: ((1+self.ux(x/current_N,y/current_N,z/current_N))/2)**0.5,num_reg_qubits,min(current_N,32))
596
+ y_coeffs,y_coeff_var_indices=get_circuit_inputs(lambda x,y,z: ((1+self.uy(x/current_N,y/current_N,z/current_N))/2)**0.5,num_reg_qubits,min(current_N,32))
597
+ z_coeffs,z_coeff_var_indices=get_circuit_inputs(lambda x,y,z: ((1+self.uz(x/current_N,y/current_N,z/current_N))/2)**0.5,num_reg_qubits,min(current_N,32))
598
+ x_coeffs_,x_coeff_var_indices_=get_circuit_inputs(lambda x,y,z: 0 if (1+self.ux((x-1)/current_N,y/current_N,z/current_N))==0 else \
599
+ ((1+self.ux((x-1)/current_N,y/current_N,z/current_N))/(2+self.ux((x-1)/current_N,y/current_N,z/current_N)-self.ux((x+1)/current_N,y/current_N,z/current_N)))**0.5,num_reg_qubits,min(current_N,32))
600
+ y_coeffs_,y_coeff_var_indices_=get_circuit_inputs(lambda x,y,z: 0 if (1+self.uy(x/current_N,(y-1)/current_N,z/current_N))==0 else \
601
+ ((1+self.uy(x/current_N,(y-1)/current_N,z/current_N))/(2+self.uy(x/current_N,(y-1)/current_N,z/current_N)-self.uy(x/current_N,(y+1)/current_N,z/current_N)))**0.5,num_reg_qubits,min(current_N,32))
602
+ z_coeffs_,z_coeff_var_indices_=get_circuit_inputs(lambda x,y,z: 0 if (1+self.uz(x/current_N,y/current_N,(z-1)/current_N))==0 else \
603
+ ((1+self.uz(x/current_N,y/current_N,(z-1)/current_N))/(2+self.uz(x/current_N,y/current_N,(z-1)/current_N)-self.uz(x/current_N,y/current_N,(z+1)/current_N)))**0.5,num_reg_qubits,min(current_N,32))
604
+ unprep1_coeffs,unprep1_coeff_var_indices=get_circuit_inputs(lambda x,y,z:\
605
+ (1/3**0.5)*(1+(self.ux((x-1)/current_N,y/current_N,z/current_N)-self.ux((x+1)/current_N,y/current_N,z/current_N))/2)**0.5,num_reg_qubits,min(current_N,32))
606
+ unprep2_coeffs, unprep2_coeff_var_indices = get_circuit_inputs(lambda x, y, z: ((1 + (self.uy(x/current_N, (y-1)/current_N, z/current_N) - self.uy(x/current_N, (y+1)/current_N, z/current_N))/2) /(2 - (self.ux((x-1)/current_N, y/current_N, z/current_N) - self.ux((x+1)/current_N, y/current_N, z/current_N))/2))**0.5, num_reg_qubits, min(current_N, 32))
607
+ print("Generated angles")
608
+ v=np.pad([1/4, 1/4, 0, 1/4, 0, 1/4, 0],(0,2**num_anc - self.ndir))
609
+ v=v**0.5
610
+ v[0]+=1
611
+ v=v/np.linalg.norm(v)
612
+ U_prep=2*np.outer(v,v)-np.eye(len(v))
613
+ cudaq.register_operation("prep_op", U_prep)
614
+ def collisionOp(dirs):
615
+ dirs_i_list=[]
616
+ for dir_ in dirs:
617
+ dirs_i=[(int(c)) for c in dir_]
618
+ dirs_i_list+=dirs_i[::-1]
619
+ return dirs_i_list
620
+ self.dirs_i_list=collisionOp(self.dirs)
621
+ print("Generated dirs_i_list")
622
+ @cudaq.kernel
623
+ def rshift(q: cudaq.qview, n: int):
624
+ for i in range(n):
625
+ if i == n-1:
626
+ x(q[n-1-i])
627
+ elif i == n-2:
628
+ x.ctrl(q[n-1-(i+1)], q[n-1-i])
629
+ else:
630
+ x.ctrl(q[0:n-1-i], q[n-1-i])
631
+ @cudaq.kernel
632
+ def lshift(q: cudaq.qview, n: int):
633
+ for i in range(n):
634
+ if i == 0:
635
+ x(q[0])
636
+ elif i == 1:
637
+ x.ctrl(q[0], q[1])
638
+ else:
639
+ x.ctrl(q[0:i], q[i])
640
+ @cudaq.kernel
641
+ def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nqz: int, nq_dir: int, dirs_i: list[int]):
642
+ qx=q[0:nqx]
643
+ qy=q[nqx:nqx+nqy]
644
+ qz=q[nqx+nqy:nqx+nqy+nqz]
645
+ qdir=q[nqx+nqy+nqz:nqx+nqy+nqz+nq_dir]
646
+ i=2
647
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
648
+ for j in range(nq_dir):
649
+ b=b_list[j]
650
+ if b==0:
651
+ x(qdir[j])
652
+ cudaq.control(lshift,qdir,qx,nqx)
653
+ for j in range(nq_dir):
654
+ b=b_list[j]
655
+ if b==0:
656
+ x(qdir[j])
657
+ i=1
658
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
659
+ for j in range(nq_dir):
660
+ b=b_list[j]
661
+ if b==0:
662
+ x(qdir[j])
663
+ cudaq.control(rshift,qdir,qx,nqx)
664
+ for j in range(nq_dir):
665
+ b=b_list[j]
666
+ if b==0:
667
+ x(qdir[j])
668
+ i=4
669
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
670
+ for j in range(nq_dir):
671
+ b=b_list[j]
672
+ if b==0:
673
+ x(qdir[j])
674
+ cudaq.control(lshift,qdir,qy,nqy)
675
+ for j in range(nq_dir):
676
+ b=b_list[j]
677
+ if b==0:
678
+ x(qdir[j])
679
+ i=3
680
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
681
+ for j in range(nq_dir):
682
+ b=b_list[j]
683
+ if b==0:
684
+ x(qdir[j])
685
+ cudaq.control(rshift,qdir,qy,nqy)
686
+ for j in range(nq_dir):
687
+ b=b_list[j]
688
+ if b==0:
689
+ x(qdir[j])
690
+ i=6
691
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
692
+ for j in range(nq_dir):
693
+ b=b_list[j]
694
+ if b==0:
695
+ x(qdir[j])
696
+ cudaq.control(lshift,qdir,qz,nqz)
697
+ for j in range(nq_dir):
698
+ b=b_list[j]
699
+ if b==0:
700
+ x(qdir[j])
701
+ i=5
702
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
703
+ for j in range(nq_dir):
704
+ b=b_list[j]
705
+ if b==0:
706
+ x(qdir[j])
707
+ cudaq.control(rshift,qdir,qz,nqz)
708
+ for j in range(nq_dir):
709
+ b=b_list[j]
710
+ if b==0:
711
+ x(qdir[j])
712
+ @cudaq.kernel
713
+ def d2q5_tstep_wrapper(state: cudaq.State,nqx:int,nqy:int,nqz:int,nq_dir:int,dirs_i:list[int],\
714
+ x_coeff_var_indices:list[int],x_coeffs:list[float],\
715
+ y_coeff_var_indices:list[int],y_coeffs:list[float],\
716
+ z_coeff_var_indices:list[int],z_coeffs:list[float],\
717
+ x_coeff_var_indices_:list[int],x_coeffs_:list[float],\
718
+ y_coeff_var_indices_:list[int],y_coeffs_:list[float],\
719
+ z_coeff_var_indices_:list[int],z_coeffs_:list[float],\
720
+ unprep1_coeff_var_indices:list[int],unprep1_coeffs:list[float],\
721
+ unprep2_coeff_var_indices:list[int],unprep2_coeffs:list[float]):
722
+ q=cudaq.qvector(state)
723
+ qdir=q[nqx+nqy+nqz:nqx+nqy+nqz+nq_dir]
724
+ prep_op(qdir[2],qdir[1],qdir[0])
725
+ x.ctrl(qdir[0],qdir[1])
726
+ ind=0
727
+ coeff_ind=0
728
+ x(qdir[2])
729
+ while ind<len(x_coeff_var_indices):
730
+ tuple_length=x_coeff_var_indices[ind]
731
+ for sub_ind in range(ind+1, ind+1+tuple_length):
732
+ x.ctrl(q[nqx+nqy+nqz-1-x_coeff_var_indices[sub_ind]],qdir[0])
733
+ ry.ctrl(-x_coeffs[coeff_ind],[qdir[2],qdir[1]],qdir[0])
734
+ coeff_ind+=1
735
+ ind+=(1+tuple_length)
736
+ x(qdir[2])
737
+ ind=0
738
+ coeff_ind=0
739
+ while ind<len(z_coeff_var_indices):
740
+ tuple_length=z_coeff_var_indices[ind]
741
+ for sub_ind in range(ind+1,ind+1+tuple_length):
742
+ x.ctrl(q[nqx+nqy+nqz-1-z_coeff_var_indices[sub_ind]],qdir[0])
743
+ ry.ctrl(-z_coeffs[coeff_ind],[qdir[2],qdir[1]],qdir[0])
744
+ coeff_ind+=1
745
+ ind+=(1+tuple_length)
746
+ x.ctrl(qdir[0],qdir[1])
747
+ ind=0
748
+ coeff_ind=0
749
+ while ind<len(y_coeff_var_indices):
750
+ tuple_length=y_coeff_var_indices[ind]
751
+ for sub_ind in range(ind+1,ind+1+tuple_length):
752
+ x.ctrl(q[nqx+nqy+nqz-1-y_coeff_var_indices[sub_ind]],qdir[2])
753
+ ry.ctrl(y_coeffs[coeff_ind],[qdir[0],qdir[1]],qdir[2])
754
+ coeff_ind+=1
755
+ ind+=(1+tuple_length)
756
+ d2q5_tstep(q,nqx,nqy,nqz,nq_dir,dirs_i)
757
+ ind=0
758
+ coeff_ind=0
759
+ while ind<len(y_coeff_var_indices_):
760
+ tuple_length=y_coeff_var_indices_[ind]
761
+ for sub_ind in range(ind+1,ind+1+tuple_length):
762
+ x.ctrl(q[nqx+nqy+nqz-1-y_coeff_var_indices_[sub_ind]],qdir[2])
763
+ ry.ctrl(-y_coeffs_[coeff_ind],[qdir[0],qdir[1]],qdir[2])
764
+ coeff_ind+=1
765
+ ind+=(1+tuple_length)
766
+ x.ctrl(qdir[0],qdir[1])
767
+ ind=0
768
+ coeff_ind=0
769
+ x(qdir[2])
770
+ while ind<len(x_coeff_var_indices_):
771
+ tuple_length=x_coeff_var_indices_[ind]
772
+ for sub_ind in range(ind+1,ind+1+tuple_length):
773
+ x.ctrl(q[nqx+nqy+nqz-1-x_coeff_var_indices_[sub_ind]],qdir[0])
774
+ ry.ctrl(x_coeffs_[coeff_ind],[qdir[1],qdir[2]],qdir[0])
775
+ coeff_ind+=1
776
+ ind+=(1+tuple_length)
777
+ x(qdir[2])
778
+ ind=0
779
+ coeff_ind=0
780
+ while ind<len(z_coeff_var_indices_):
781
+ tuple_length=z_coeff_var_indices_[ind]
782
+ for sub_ind in range(ind+1,ind+1+tuple_length):
783
+ x.ctrl(q[nqx+nqy+nqz-1-z_coeff_var_indices_[sub_ind]],qdir[0])
784
+ ry.ctrl(z_coeffs_[coeff_ind],[qdir[1],qdir[2]],qdir[0])
785
+ coeff_ind+=1
786
+ ind+=(1+tuple_length)
787
+ x.ctrl(qdir[0],qdir[1])
788
+ ind=0
789
+ coeff_ind=0
790
+ x.ctrl(qdir[1],qdir[2])
791
+ while ind<len(unprep2_coeff_var_indices):
792
+ tuple_length=unprep2_coeff_var_indices[ind]
793
+ for sub_ind in range(ind+1,ind+1+tuple_length):
794
+ x.ctrl(q[nqx+nqy+nqz-1-unprep2_coeff_var_indices[sub_ind]],qdir[1])
795
+ ry.ctrl(unprep2_coeffs[coeff_ind],qdir[2],qdir[1])
796
+ coeff_ind+=1
797
+ ind+=(1+tuple_length)
798
+ x.ctrl(qdir[1],qdir[2])
799
+ ind=0
800
+ coeff_ind=0
801
+ while ind<len(unprep1_coeff_var_indices):
802
+ tuple_length=unprep1_coeff_var_indices[ind]
803
+ for sub_ind in range(ind+1,ind+1+tuple_length):
804
+ x.ctrl(q[nqx+nqy+nqz-1-unprep1_coeff_var_indices[sub_ind]],qdir[1])
805
+ ry.ctrl(-unprep1_coeffs[coeff_ind],qdir[0],qdir[1])
806
+ coeff_ind+=1
807
+ ind+=(1+tuple_length)
808
+ ry(-2*np.pi/3,qdir[0])
809
+ print("Kernels defined")
810
+ def run_timestep_func(vec_arg, hadamard=False):
811
+ result=cudaq.get_state(d2q5_tstep_wrapper,vec_arg,num_reg_qubits,num_reg_qubits,num_reg_qubits,self.nq_dir,self.dirs_i_list,\
812
+ x_coeff_var_indices,x_coeffs,\
813
+ y_coeff_var_indices,y_coeffs,\
814
+ z_coeff_var_indices,z_coeffs,\
815
+ x_coeff_var_indices_,x_coeffs_,\
816
+ y_coeff_var_indices_,y_coeffs_,\
817
+ z_coeff_var_indices_,z_coeffs_,\
818
+ unprep1_coeff_var_indices,unprep1_coeffs,\
819
+ unprep2_coeff_var_indices,unprep2_coeffs)
820
+ num_nonzero_ranks = num_ranks / (2**num_anc)
821
+ rank_slice_cupy = to_cupy_array(result)
822
+ if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
823
+ sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
824
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
825
+ if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
826
+ limit_idx = int(N_tot_state_vector / (2**num_anc))
827
+ if limit_idx < rank_slice_cupy.size:
828
+ rank_slice_cupy[limit_idx:] = 0
829
+ return result
830
+ self.run_timestep = run_timestep_func
831
+ print("Circuit created")
832
+ def write_state(self, state_to_write, t_step_str_val):
833
+ rank_slice_cupy = to_cupy_array(state_to_write)
834
+ num_nonzero_ranks = num_ranks / (2**num_anc)
835
+ if rank < num_nonzero_ranks or (rank == 0 and num_nonzero_ranks <= 0):
836
+ save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
837
+ with open(save_path, 'wb') as f:
838
+ arr_to_save = None
839
+ data_limit = N_sub_per_rank
840
+ if num_nonzero_ranks < 1 and rank == 0:
841
+ data_limit = int(N_tot_state_vector / (2**num_anc))
842
+ if data_limit > 0:
843
+ relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
844
+ else:
845
+ relevant_part_cupy = cp.array([], dtype=cp.float64)
846
+ if relevant_part_cupy.size >= current_N * current_N * current_N:
847
+ arr_flat = relevant_part_cupy[:current_N * current_N * current_N]
848
+ if downsampling_factor > 1 and current_N > 0:
849
+ arr_reshaped = arr_flat.reshape((current_N, current_N, current_N))
850
+ arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor, ::downsampling_factor]
851
+ arr_to_save = arr_downsampled.flatten()
852
+ else:
853
+ arr_to_save = arr_flat
854
+ elif relevant_part_cupy.size > 0:
855
+ if downsampling_factor > 1:
856
+ arr_to_save = relevant_part_cupy[::downsampling_factor]
857
+ else:
858
+ arr_to_save = relevant_part_cupy
859
+ if arr_to_save is not None and arr_to_save.size > 0:
860
+ np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
861
+ print("Write state defined")
862
+ def run_evolution(self, initial_state_arg, total_timesteps, time_steps_to_save, observable=False):
863
+ current_state_val = initial_state_arg
864
+ save_times = set(time_steps_to_save)
865
+ if 0 in save_times:
866
+ print("Writing first state")
867
+ self.write_state(current_state_val, '0')
868
+ for t_iter in range(total_timesteps):
869
+ print("Running timestep")
870
+ next_state_val = self.run_timestep(current_state_val)
871
+ if (t_iter + 1) in save_times:
872
+ print("Writing next state")
873
+ self.write_state(next_state_val, str(t_iter + 1))
874
+ cp.get_default_memory_pool().free_all_blocks()
875
+ current_state_val = next_state_val
876
+ if rank == 0:
877
+ print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
878
+ cp.get_default_memory_pool().free_all_blocks()
879
+ self.final_state = current_state_val
880
+
881
+ if boundary_condition == "Periodic":
882
+ pass
883
+ elif boundary_condition == "Dirichlet":
884
+ pass
885
+ elif boundary_condition == "Neumann":
886
+ pass
887
+
888
+ downsampling_factor = 1
889
+ if current_N == 0:
890
+ print("Error: current_N is zero. num_reg_qubits likely too small.")
891
+ return None, None # Modified return
892
+ if current_N < downsampling_factor:
893
+ downsampling_factor = current_N if current_N > 0 else 1
894
+
895
+ qlbm_obj = QLBMAdvecDiffD3Q7_new(vx=vx_input, vy=vy_input, vz=vz_input)
896
+ initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
897
+
898
+ sub_sv_init_flat = initial_state_func_eval(np.arange(N_sub_per_rank)).astype(np.complex128)
899
+
900
+ norm = np.linalg.norm(sub_sv_init_flat)
901
+ if norm > 0:
902
+ sub_sv_init_flat /= norm
903
+ else:
904
+ print("Error: Initial state norm is zero.")
905
+ return None, None # Modified return
906
+ full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
907
+ num_computational_states = current_N ** 3
908
+ if len(sub_sv_init_flat) == num_computational_states:
909
+ if num_computational_states <= N_sub_per_rank:
910
+ full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
911
+ else:
912
+ print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
913
+ return None, None # Modified return
914
+ else:
915
+ print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
916
+ fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
917
+ full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
918
+
919
+ rank_slice_init = to_cupy_array(initial_state_val)
920
+ print(f'Rank {rank}: Initializing state with {distribution_type} (vx={vx_input}, vy={vy_input})...')
921
+ cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
922
+ print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
923
+
924
+ print("Starting QLBM evolution...")
925
+ qlbm_obj.run_evolution(initial_state_val, T, time_steps)
926
+ print("QLBM evolution complete.")
927
+
928
+ print("Generating interactive plot with Plotly...")
929
+ downsampled_N = current_N // downsampling_factor
930
+ if downsampled_N == 0 and current_N > 0:
931
+ downsampled_N = 1
932
+ elif current_N == 0:
933
+ print("Error: current_N is zero before Plotly stage.")
934
+ return None, None # Modified return
935
+
936
+ data_frames = []
937
+ actual_timesteps = []
938
+ for t in time_steps:
939
+ file_path = intermediate_folder_path / f"{t}_{rank}.npy"
940
+ if file_path.exists():
941
+ sol_loaded = np.load(file_path)
942
+ if sol_loaded.size == downsampled_N * downsampled_N* downsampled_N:
943
+ data = np.reshape(sol_loaded, (downsampled_N, downsampled_N, downsampled_N))
944
+ data_frames.append(data)
945
+ actual_timesteps.append(t)
946
+ print(f"Time {t}: Min={np.min(data)}, Max={np.max(data)}, Mean={np.mean(data)}")
947
+ else:
948
+ print(f"Warning: File {file_path} size {sol_loaded.size} != expected {downsampled_N*downsampled_N*downsampled_N}. Skipping.")
949
+ else:
950
+ print(f"Warning: File {file_path} not found. Skipping.")
951
+
952
+ if not data_frames:
953
+ print("Error: No data frames loaded for plotting.")
954
+ return None, None # Modified return
955
+
956
+ x_coords_plot = np.linspace(0, 1, downsampled_N)
957
+ y_coords_plot = np.linspace(0, 1, downsampled_N)
958
+ z_coords_plot = np.linspace(0, 1, downsampled_N)
959
+ Z_grid_mesh, Y_grid_mesh, X_grid_mesh = np.meshgrid(x_coords_plot, y_coords_plot, z_coords_plot, indexing='ij')
960
+
961
+ data_min = min([np.min(data) for data in data_frames])
962
+ data_max = max([np.max(data) for data in data_frames])
963
+ if data_max == data_min:
964
+ data_max += 1e-9
965
+
966
+ fig = go.Figure()
967
+
968
+ # Store individual frames for download
969
+ plotly_json_frames = []
970
+
971
+ for i, output_data in enumerate(data_frames):
972
+ frame_trace = go.Isosurface(
973
+ x=X_grid_mesh.flatten(),
974
+ y=Y_grid_mesh.flatten(),
975
+ z=Z_grid_mesh.flatten(),
976
+ value=output_data.flatten(),
977
+ isomin=data_min,
978
+ isomax=data_max,
979
+ opacity=0.4, # needs to be small to see through all surfaces
980
+ surface_count=7, # needs to be a large number for good volume rendering,
981
+ caps=dict(x_show=False, y_show=False, z_show=False)
982
+ )
983
+ fig.add_trace(frame_trace)
984
+
985
+ # Create a figure for the individual frame and convert to JSON
986
+ single_frame_fig = go.Figure(data=[frame_trace], layout=fig.layout)
987
+ single_frame_fig.update_layout(
988
+ title=f"Time: {actual_timesteps[i]}",
989
+ scene=dict(
990
+ xaxis_title='X',
991
+ yaxis_title='Y',
992
+ zaxis_title='Z',
993
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
994
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
995
+ zaxis=dict(range=[z_coords_plot[0], z_coords_plot[-1]]),
996
+ )
997
+ )
998
+ plotly_json_frames.append(single_frame_fig.to_json())
999
+
1000
+ for trace in fig.data[1:]:
1001
+ trace.visible = False
1002
+
1003
+ steps = []
1004
+ for i in range(len(data_frames)):
1005
+ step = dict(
1006
+ method="update",
1007
+ args=[{"visible": [False] * len(data_frames)}],
1008
+ label=f"Time: {actual_timesteps[i]}"
1009
+ )
1010
+ step["args"][0]["visible"][i] = True
1011
+ steps.append(step)
1012
+
1013
+ sliders = [dict(active=0, currentvalue={"prefix": "Time: "}, pad={"t": 50}, steps=steps)]
1014
+
1015
+ fig.update_layout(
1016
+ title='', # Removed graph title
1017
+ scene=dict(
1018
+ xaxis_title='X',
1019
+ yaxis_title='Y',
1020
+ zaxis_title='Z',
1021
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
1022
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
1023
+ zaxis=dict(range=[z_coords_plot[0], z_coords_plot[-1]]),
1024
+ ),
1025
+ sliders=sliders,
1026
+ width=800,
1027
+ height=700
1028
+ )
1029
+
1030
+ return fig, plotly_json_frames # Modified return
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ sympy
12
+ gradio-litmodel3d