harishaseebat92 commited on
Commit
eb4058d
·
1 Parent(s): 49e1dbd

Corrected Local Error

Browse files
app.py CHANGED
@@ -4,10 +4,10 @@ from trame_vuetify.widgets import vuetify3
4
  from trame.widgets import html as trame_html
5
  import os
6
 
7
- # Import pages
8
- # from pages import em_page # <-- remove eager import to avoid side effects
9
- em_page = None # lazy-import in EM tab
10
- import qlbm as qlbm_page
11
 
12
  # Force embedded mode for nested pages (avoid iframes/secondary servers)
13
  os.environ.setdefault("TRAME_EMBEDDED", "1")
@@ -73,23 +73,19 @@ with SinglePageLayout(server) as layout:
73
  with vuetify3.VContainer(v_if="current_page === 'EM'", fluid=True, classes="pa-0 fill-height"):
74
  try:
75
  if not globals().get("em_page"):
76
- from importlib import import_module
77
- mod = import_module("pages.em_page")
78
- globals()["em_page"] = mod
79
- if hasattr(em_page, "build"):
80
- em_page.build(server)
81
- else:
82
- trame_html.Div("EM module missing build(server).", style="padding:8px;color:#666;")
83
  except Exception as e:
84
- trame_html.Div(
85
- f"EM page failed to load in embedded mode: {e}. "
86
- "Refactor EM to avoid starting a secondary server/iframe.",
87
- style="padding:8px;color:#b00020;"
88
- )
89
 
90
  # Mount QLBM page
91
  with vuetify3.VContainer(v_if="current_page === 'QLBM'", fluid=True, classes="pa-0 fill-height"):
92
- qlbm_page.build(server)
 
 
 
 
 
93
 
94
  if __name__ == "__main__":
95
  # If PORT is provided (e.g., Hugging Face), bind on 0.0.0.0; otherwise use localhost
 
4
  from trame.widgets import html as trame_html
5
  import os
6
 
7
+ # Import embedded page wrappers (lazy load inside tabs)
8
+ from importlib import import_module
9
+ em_page = None
10
+ qlbm_page = None
11
 
12
  # Force embedded mode for nested pages (avoid iframes/secondary servers)
13
  os.environ.setdefault("TRAME_EMBEDDED", "1")
 
73
  with vuetify3.VContainer(v_if="current_page === 'EM'", fluid=True, classes="pa-0 fill-height"):
74
  try:
75
  if not globals().get("em_page"):
76
+ globals()["em_page"] = import_module("pages.em_page")
77
+ em_page.build(server)
 
 
 
 
 
78
  except Exception as e:
79
+ trame_html.Div(f"EM embed failed: {e}", style="padding:8px;color:#b00020;")
 
 
 
 
80
 
81
  # Mount QLBM page
82
  with vuetify3.VContainer(v_if="current_page === 'QLBM'", fluid=True, classes="pa-0 fill-height"):
83
+ try:
84
+ if not globals().get("qlbm_page"):
85
+ globals()["qlbm_page"] = import_module("pages.qlbm_page")
86
+ qlbm_page.build(server)
87
+ except Exception as e:
88
+ trame_html.Div(f"QLBM embed failed: {e}", style="padding:8px;color:#b00020;")
89
 
90
  if __name__ == "__main__":
91
  # If PORT is provided (e.g., Hugging Face), bind on 0.0.0.0; otherwise use localhost
em.ipynb ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 17,
6
+ "id": "fe68f8a9",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ "Available backends: [<qiskit_ionq.ionq_backend.IonQSimulatorBackend object at 0x7f91854ff520>, <qiskit_ionq.ionq_backend.IonQQPUBackend object at 0x7f91851f8d90>]\n"
14
+ ]
15
+ },
16
+ {
17
+ "name": "stderr",
18
+ "output_type": "stream",
19
+ "text": [
20
+ "/home/cudaq/.local/lib/python3.10/site-packages/qiskit_ionq/ionq_backend.py:127: IonQTranspileLevelWarning: Transpiler default optimization_level=2. IonQ (QIS) recommends 0-1 to avoid aggressive re-synthesis; use transpile(..., optimization_level=1).\n",
21
+ " warn_bad_transpile_level()\n"
22
+ ]
23
+ }
24
+ ],
25
+ "source": [
26
+ "from qiskit import QuantumCircuit\n",
27
+ "from qiskit_ionq import IonQProvider\n",
28
+ "import os\n",
29
+ "my_api_key = os.getenv(\"IONQ_API_KEY\")\n",
30
+ "# # provider = IonQProvider()\n",
31
+ "\n",
32
+ "api_token = \"SgUkiDq1r2bVEadyiUfvtuxQ03Qci6UW\"\n",
33
+ "provider = IonQProvider(api_token)\n",
34
+ "\n",
35
+ "\n",
36
+ "backends = provider.backends()\n",
37
+ "print(\"Available backends:\", backends)"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "code",
42
+ "execution_count": 15,
43
+ "id": "6b2b90ef",
44
+ "metadata": {},
45
+ "outputs": [
46
+ {
47
+ "name": "stderr",
48
+ "output_type": "stream",
49
+ "text": [
50
+ "/home/cudaq/.local/lib/python3.10/site-packages/qiskit_ionq/ionq_backend.py:127: IonQTranspileLevelWarning: Transpiler default optimization_level=2. IonQ (QIS) recommends 0-1 to avoid aggressive re-synthesis; use transpile(..., optimization_level=1).\n",
51
+ " warn_bad_transpile_level()\n"
52
+ ]
53
+ },
54
+ {
55
+ "name": "stdout",
56
+ "output_type": "stream",
57
+ "text": [
58
+ "Expectation values: [1.55664062]\n"
59
+ ]
60
+ }
61
+ ],
62
+ "source": [
63
+ "from qiskit import QuantumCircuit\n",
64
+ "from qiskit_ionq import IonQProvider\n",
65
+ "\n",
66
+ "backend = provider.get_backend(\"simulator\")\n",
67
+ "\n",
68
+ "from qiskit.circuit.library import real_amplitudes\n",
69
+ "from qiskit.quantum_info import SparsePauliOp\n",
70
+ "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
71
+ "from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator\n",
72
+ "from qiskit.primitives import StatevectorEstimator\n",
73
+ "\n",
74
+ "# service = QiskitRuntimeService()\n",
75
+ "# backend = service.least_busy(operational=True, simulator=False)\n",
76
+ " \n",
77
+ "psi = real_amplitudes(num_qubits=2, reps=2)\n",
78
+ "hamiltonian = SparsePauliOp.from_list([(\"II\", 1), (\"IZ\", 2), (\"XI\", 3)])\n",
79
+ "theta = [0, 1, 1, 2, 3, 5]\n",
80
+ " \n",
81
+ "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
82
+ "isa_psi = pm.run(psi)\n",
83
+ "isa_observables = hamiltonian.apply_layout(isa_psi.layout)\n",
84
+ " \n",
85
+ "estimator = Estimator(mode=backend)\n",
86
+ " \n",
87
+ "# calculate [ <psi(theta)|hamiltonian|psi(theta)> ]\n",
88
+ "job = estimator.run([(isa_psi, isa_observables, [theta])])\n",
89
+ "pub_result = job.result()[0]\n",
90
+ "print(f\"Expectation values: {pub_result.data.evs}\")"
91
+ ]
92
+ },
93
+ {
94
+ "cell_type": "code",
95
+ "execution_count": 16,
96
+ "id": "4147b18a",
97
+ "metadata": {},
98
+ "outputs": [
99
+ {
100
+ "name": "stdout",
101
+ "output_type": "stream",
102
+ "text": [
103
+ "Expectation values: [1.55555728]\n"
104
+ ]
105
+ }
106
+ ],
107
+ "source": [
108
+ "qiskit_estimator = StatevectorEstimator()\n",
109
+ "job2 = qiskit_estimator.run([(psi, hamiltonian, [theta])])\n",
110
+ "pub_result = job2.result()[0]\n",
111
+ "print(f\"Expectation values: {pub_result.data.evs}\") "
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "id": "109a7251",
118
+ "metadata": {},
119
+ "outputs": [],
120
+ "source": [
121
+ "def run_sim_ionq(nx, na, R, initial_state, T, snapshot_dt=None, stop_check=None, progress_callback=None):\n",
122
+ " \"\"\"\n",
123
+ " Runs the quantum simulation for electromagnetic scattering with fixed dt=0.1.\n",
124
+ " Captures frames only at user-defined snapshot times: [0, Δt, 2Δt, ..., ≤ T_eff],\n",
125
+ " always including t=0 and the final solver-aligned T (T_eff = floor(T/dt)*dt).\n",
126
+ "\n",
127
+ " Returns:\n",
128
+ " frames (np.ndarray), snapshot_times (np.ndarray)\n",
129
+ " \"\"\"\n",
130
+ " dt = 0.1\n",
131
+ " # Validate total time and compute solver-aligned end time\n",
132
+ " try:\n",
133
+ " T_val = float(T)\n",
134
+ " except Exception:\n",
135
+ " return np.array([]), np.array([])\n",
136
+ " if T_val <= 0:\n",
137
+ " return np.array([]), np.array([])\n",
138
+ "\n",
139
+ " steps = int(np.floor(T_val / dt))\n",
140
+ " if steps <= 0:\n",
141
+ " return np.array([]), np.array([])\n",
142
+ " T_eff = steps * dt\n",
143
+ "\n",
144
+ " # Determine snapshot Δt on solver grid\n",
145
+ " tol = 1e-12\n",
146
+ " if snapshot_dt is None:\n",
147
+ " snapshot_dt_val = dt\n",
148
+ " else:\n",
149
+ " try:\n",
150
+ " snapshot_dt_val = float(snapshot_dt)\n",
151
+ " except Exception:\n",
152
+ " snapshot_dt_val = dt\n",
153
+ " if snapshot_dt_val < dt - tol:\n",
154
+ " snapshot_dt_val = dt\n",
155
+ " k = max(1, int(round(snapshot_dt_val / dt)))\n",
156
+ " snapshot_dt_eff = k * dt\n",
157
+ "\n",
158
+ " # Build requested snapshot times on solver grid\n",
159
+ " target_times = [0.0]\n",
160
+ " t = 0.0\n",
161
+ " while t + snapshot_dt_eff <= T_eff + tol:\n",
162
+ " t = round(t + snapshot_dt_eff, 12)\n",
163
+ " if t <= T_eff + tol:\n",
164
+ " target_times.append(min(t, T_eff))\n",
165
+ " if abs(target_times[-1] - T_eff) > tol:\n",
166
+ " target_times.append(T_eff)\n",
167
+ "\n",
168
+ " # Setup circuit\n",
169
+ " nq = int(np.ceil(np.log2(nx)))\n",
170
+ " dp = 2 * R * np.pi / 2 ** na\n",
171
+ " p = np.arange(-R * np.pi, R * np.pi, step=dp)\n",
172
+ " fp = np.exp(-np.abs(p))\n",
173
+ " system, ancilla = QuantumRegister(2 * nq + 2), QuantumRegister(na)\n",
174
+ " qc = QuantumCircuit(system, ancilla)\n",
175
+ " qc.append(StatePreparation(initial_state), system)\n",
176
+ " qc.append(StatePreparation(fp / np.linalg.norm(fp)), ancilla)\n",
177
+ " expA1 = V1(nx, dt).to_gate()\n",
178
+ " expA2 = V2(nx, dt)\n",
179
+ "\n",
180
+ " frames = []\n",
181
+ " \n",
182
+ " # Run the circuit on IonQ's platform:\n",
183
+ " job = simulator_backend.run(qc, shots=10000)\n",
184
+ "\n",
185
+ " # Print the counts\n",
186
+ " # print(job.get_counts())\n",
187
+ " # # Capture initial frame at t=0\n",
188
+ " # sv0 = np.real(Statevector(qc)).reshape(2 ** na, 2 ** (2 * nq + 2))\n",
189
+ " # frames.append(sv0[2 ** (na - 1)])\n",
190
+ " # next_idx = 1 # next target_times index to capture\n",
191
+ "\n",
192
+ " # for i in range(steps):\n",
193
+ " # if stop_check and stop_check():\n",
194
+ " # print(f\"Simulation interrupted at step {i}/{steps}\")\n",
195
+ " # break\n",
196
+ " # # One solver step\n",
197
+ " # qc.append(QFTGate(na), ancilla)\n",
198
+ " # qc.x(ancilla[-1])\n",
199
+ " # for j in range(na - 1):\n",
200
+ " # qc.append(expA1.control().repeat(2 ** j), [ancilla[j]] + system[:])\n",
201
+ " # qc.append(expA1.inverse().control(ctrl_state=\"0\").repeat(2 ** (na - 1)), [ancilla[na - 1]] + system[:])\n",
202
+ " # qc.append(expA2, system[:])\n",
203
+ " # qc.x(ancilla[-1])\n",
204
+ " # qc.append(QFTGate(na).inverse(), ancilla)\n",
205
+ "\n",
206
+ " # current_time = (i + 1) * dt\n",
207
+ " # if next_idx < len(target_times) and abs(current_time - target_times[next_idx]) <= tol:\n",
208
+ " # u = np.real(Statevector(qc)).reshape(2 ** na, 2 ** (2 * nq + 2))\n",
209
+ " # frames.append(u[2 ** (na - 1)])\n",
210
+ " # next_idx += 1\n",
211
+ "\n",
212
+ " # if progress_callback:\n",
213
+ " # try:\n",
214
+ " # progress = ((i + 1) / steps) * 100\n",
215
+ " # progress_callback(progress)\n",
216
+ " # except Exception:\n",
217
+ " # pass\n",
218
+ "\n",
219
+ " # if progress_callback:\n",
220
+ " # try:\n",
221
+ " # progress_callback(100.0)\n",
222
+ " # except Exception:\n",
223
+ " # pass\n",
224
+ "\n",
225
+ " # # Ensure snapshot_times align with number of captured frames (covers early stop)\n",
226
+ " # frames_arr = np.asarray(frames)\n",
227
+ " # times_arr = np.asarray(target_times[: len(frames_arr)])\n",
228
+ " # return frames_arr, times_arr\n",
229
+ "\n"
230
+ ]
231
+ }
232
+ ],
233
+ "metadata": {
234
+ "kernelspec": {
235
+ "display_name": "Python 3",
236
+ "language": "python",
237
+ "name": "python3"
238
+ },
239
+ "language_info": {
240
+ "codemirror_mode": {
241
+ "name": "ipython",
242
+ "version": 3
243
+ },
244
+ "file_extension": ".py",
245
+ "mimetype": "text/x-python",
246
+ "name": "python",
247
+ "nbconvert_exporter": "python",
248
+ "pygments_lexer": "ipython3",
249
+ "version": "3.10.12"
250
+ }
251
+ },
252
+ "nbformat": 4,
253
+ "nbformat_minor": 5
254
+ }
em_trame.py CHANGED
@@ -2600,9 +2600,16 @@ def build(host_server):
2600
  update_initial_state_preview()
2601
 
2602
  if __name__ == "__main__":
2603
- # Allow running standalone on a dedicated port (default 8701)
2604
- # so it can be embedded via IFrame in the multipage app
2605
  port = int(os.environ.get("EM_APP_PORT", "8701"))
2606
- server.start(port=port, open_browser=False)
 
 
 
 
 
 
 
 
2607
 
2608
 
 
2600
  update_initial_state_preview()
2601
 
2602
  if __name__ == "__main__":
2603
+ # Standalone launch: prefer IPv4 loopback to avoid OSError 99 on ::1
 
2604
  port = int(os.environ.get("EM_APP_PORT", "8701"))
2605
+ host = os.environ.get("EM_HOST", "127.0.0.1")
2606
+ try:
2607
+ server.start(host=host, port=port, open_browser=False)
2608
+ except OSError as e:
2609
+ # Fallback: try any local interface
2610
+ try:
2611
+ server.start(host="0.0.0.0", port=port, open_browser=False)
2612
+ except Exception:
2613
+ print(f"EM server failed to bind on {host}:{port} -> {e}")
2614
 
2615
 
fluid.py ADDED
@@ -0,0 +1,1043 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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='Blues',
460
+ cmin=z_min, cmax=z_max,
461
+ name=f'Time: {actual_timesteps[i]}',
462
+ showscale=False
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
+ fig.update_layout(
512
+ title='', # Removed graph title
513
+ scene=dict(
514
+ xaxis_title='X',
515
+ yaxis_title='Y',
516
+ zaxis_title='Density',
517
+ xaxis=dict(visible=False),
518
+ yaxis=dict(visible=False),
519
+ zaxis=dict(visible=False),
520
+ ),
521
+ sliders=sliders,
522
+ width=800,
523
+ height=700
524
+ )
525
+ return fig, plotly_json_frames # Modified return
526
+
527
+ def simulate_qlbm_3D_and_animate(num_reg_qubits: int, T: int, distribution_type: str, vx_input, vy_input, vz_input, boundary_condition: str):
528
+ num_anc = 3
529
+ num_qubits_total = 3 * num_reg_qubits + num_anc
530
+ current_N = 2**num_reg_qubits
531
+ N_tot_state_vector = 2**num_qubits_total
532
+ num_ranks = 1
533
+ rank = 0
534
+ N_sub_per_rank = int(N_tot_state_vector // num_ranks)
535
+
536
+ # Simplified time steps for 3D since slider steps are removed
537
+ NUM_ANIMATION_FRAMES_3D = 10 # Default number of frames if no specific slider steps
538
+
539
+ if T == 0:
540
+ time_steps = [0]
541
+ else:
542
+ num_points = min(T + 1, NUM_ANIMATION_FRAMES_3D)
543
+ time_steps = np.linspace(start=0, stop=T, num=num_points, dtype=int)
544
+ time_steps = sorted(list(set(time_steps)))
545
+
546
+
547
+ if distribution_type == "Sinusoidal":
548
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
549
+ np.sin(x * 2 * np.pi / N_val_func) * \
550
+ np.sin(y * 2 * np.pi / N_val_func) * \
551
+ np.sin(z * 2 * np.pi / N_val_func) + 1
552
+ elif distribution_type == "Gaussian":
553
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
554
+ 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) +
555
+ (z - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
556
+ else:
557
+ print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sinusoidal.")
558
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
559
+ np.sin(x * 2 * np.pi / N_val_func) * \
560
+ np.sin(y * 2 * np.pi / N_val_func) * \
561
+ np.sin(z * 2 * np.pi / N_val_func) + 1
562
+
563
+ initial_state_func_eval = lambda i:\
564
+ 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)
565
+
566
+ with tempfile.TemporaryDirectory() as tmp_npy_dir:
567
+ intermediate_folder_path = Path(tmp_npy_dir)
568
+
569
+ cudaq.set_target('nvidia', option='fp64')
570
+
571
+ @cudaq.kernel
572
+ def alloc_kernel(num_qubits_alloc: int):
573
+ qubits = cudaq.qvector(num_qubits_alloc)
574
+
575
+ from cupy.cuda.memory import MemoryPointer, UnownedMemory
576
+
577
+ def to_cupy_array(state):
578
+ tensor = state.getTensor()
579
+ pDevice = tensor.data()
580
+ sizeByte = tensor.get_num_elements() * tensor.get_element_size()
581
+ mem = UnownedMemory(pDevice, sizeByte, owner=state)
582
+ memptr_obj = MemoryPointer(mem, 0)
583
+ cupy_array_val = cp.ndarray(tensor.get_num_elements(),
584
+ dtype=cp.complex128,
585
+ memptr=memptr_obj)
586
+ return cupy_array_val
587
+
588
+ class QLBMAdvecDiffD3Q7_new:
589
+ def __init__(self,vx,vy,vz) -> None:
590
+ self.dim = 3
591
+ self.ndir = 7
592
+ self.nq_dir = math.ceil(np.log2(self.ndir))
593
+ self.dirs=[]
594
+ for dir_int in range(self.ndir):
595
+ if dir_int==4:
596
+ dir_bin="111"
597
+ else:
598
+ dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
599
+ self.dirs.append(dir_bin)
600
+ self.cs = 1/np.sqrt(3)
601
+ self.ux = lambda x,y,z: vx(x,y,z)/self.cs**2
602
+ self.uy = lambda x,y,z: vy(x,y,z)/self.cs**2
603
+ self.uz = lambda x,y,z: vz(x,y,z)/self.cs**2
604
+ self.create_circuit()
605
+
606
+ def create_circuit(self):
607
+ print("Creating circuit")
608
+ 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))
609
+ 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))
610
+ 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))
611
+ 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 \
612
+ ((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))
613
+ 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 \
614
+ ((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))
615
+ 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 \
616
+ ((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))
617
+ unprep1_coeffs,unprep1_coeff_var_indices=get_circuit_inputs(lambda x,y,z:\
618
+ (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))
619
+ 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))
620
+ print("Generated angles")
621
+ v=np.pad([1/4, 1/4, 0, 1/4, 0, 1/4, 0],(0,2**num_anc - self.ndir))
622
+ v=v**0.5
623
+ v[0]+=1
624
+ v=v/np.linalg.norm(v)
625
+ U_prep=2*np.outer(v,v)-np.eye(len(v))
626
+ cudaq.register_operation("prep_op", U_prep)
627
+ def collisionOp(dirs):
628
+ dirs_i_list=[]
629
+ for dir_ in dirs:
630
+ dirs_i=[(int(c)) for c in dir_]
631
+ dirs_i_list+=dirs_i[::-1]
632
+ return dirs_i_list
633
+ self.dirs_i_list=collisionOp(self.dirs)
634
+ print("Generated dirs_i_list")
635
+ @cudaq.kernel
636
+ def rshift(q: cudaq.qview, n: int):
637
+ for i in range(n):
638
+ if i == n-1:
639
+ x(q[n-1-i])
640
+ elif i == n-2:
641
+ x.ctrl(q[n-1-(i+1)], q[n-1-i])
642
+ else:
643
+ x.ctrl(q[0:n-1-i], q[n-1-i])
644
+ @cudaq.kernel
645
+ def lshift(q: cudaq.qview, n: int):
646
+ for i in range(n):
647
+ if i == 0:
648
+ x(q[0])
649
+ elif i == 1:
650
+ x.ctrl(q[0], q[1])
651
+ else:
652
+ x.ctrl(q[0:i], q[i])
653
+ @cudaq.kernel
654
+ def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nqz: int, nq_dir: int, dirs_i: list[int]):
655
+ qx=q[0:nqx]
656
+ qy=q[nqx:nqx+nqy]
657
+ qz=q[nqx+nqy:nqx+nqy+nqz]
658
+ qdir=q[nqx+nqy+nqz:nqx+nqy+nqz+nq_dir]
659
+ i=2
660
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
661
+ for j in range(nq_dir):
662
+ b=b_list[j]
663
+ if b==0:
664
+ x(qdir[j])
665
+ cudaq.control(lshift,qdir,qx,nqx)
666
+ for j in range(nq_dir):
667
+ b=b_list[j]
668
+ if b==0:
669
+ x(qdir[j])
670
+ i=1
671
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
672
+ for j in range(nq_dir):
673
+ b=b_list[j]
674
+ if b==0:
675
+ x(qdir[j])
676
+ cudaq.control(rshift,qdir,qx,nqx)
677
+ for j in range(nq_dir):
678
+ b=b_list[j]
679
+ if b==0:
680
+ x(qdir[j])
681
+ i=4
682
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
683
+ for j in range(nq_dir):
684
+ b=b_list[j]
685
+ if b==0:
686
+ x(qdir[j])
687
+ cudaq.control(lshift,qdir,qy,nqy)
688
+ for j in range(nq_dir):
689
+ b=b_list[j]
690
+ if b==0:
691
+ x(qdir[j])
692
+ i=3
693
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
694
+ for j in range(nq_dir):
695
+ b=b_list[j]
696
+ if b==0:
697
+ x(qdir[j])
698
+ cudaq.control(rshift,qdir,qy,nqy)
699
+ for j in range(nq_dir):
700
+ b=b_list[j]
701
+ if b==0:
702
+ x(qdir[j])
703
+ i=6
704
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
705
+ for j in range(nq_dir):
706
+ b=b_list[j]
707
+ if b==0:
708
+ x(qdir[j])
709
+ cudaq.control(lshift,qdir,qz,nqz)
710
+ for j in range(nq_dir):
711
+ b=b_list[j]
712
+ if b==0:
713
+ x(qdir[j])
714
+ i=5
715
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
716
+ for j in range(nq_dir):
717
+ b=b_list[j]
718
+ if b==0:
719
+ x(qdir[j])
720
+ cudaq.control(rshift,qdir,qz,nqz)
721
+ for j in range(nq_dir):
722
+ b=b_list[j]
723
+ if b==0:
724
+ x(qdir[j])
725
+ @cudaq.kernel
726
+ def d2q5_tstep_wrapper(state: cudaq.State,nqx:int,nqy:int,nqz:int,nq_dir:int,dirs_i:list[int],\
727
+ x_coeff_var_indices:list[int],x_coeffs:list[float],\
728
+ y_coeff_var_indices:list[int],y_coeffs:list[float],\
729
+ z_coeff_var_indices:list[int],z_coeffs:list[float],\
730
+ x_coeff_var_indices_:list[int],x_coeffs_:list[float],\
731
+ y_coeff_var_indices_:list[int],y_coeffs_:list[float],\
732
+ z_coeff_var_indices_:list[int],z_coeffs_:list[float],\
733
+ unprep1_coeff_var_indices:list[int],unprep1_coeffs:list[float],\
734
+ unprep2_coeff_var_indices:list[int],unprep2_coeffs:list[float]):
735
+ q=cudaq.qvector(state)
736
+ qdir=q[nqx+nqy+nqz:nqx+nqy+nqz+nq_dir]
737
+ prep_op(qdir[2],qdir[1],qdir[0])
738
+ x.ctrl(qdir[0],qdir[1])
739
+ ind=0
740
+ coeff_ind=0
741
+ x(qdir[2])
742
+ while ind<len(x_coeff_var_indices):
743
+ tuple_length=x_coeff_var_indices[ind]
744
+ for sub_ind in range(ind+1, ind+1+tuple_length):
745
+ x.ctrl(q[nqx+nqy+nqz-1-x_coeff_var_indices[sub_ind]],qdir[0])
746
+ ry.ctrl(-x_coeffs[coeff_ind],[qdir[2],qdir[1]],qdir[0])
747
+ coeff_ind+=1
748
+ ind+=(1+tuple_length)
749
+ x(qdir[2])
750
+ ind=0
751
+ coeff_ind=0
752
+ while ind<len(z_coeff_var_indices):
753
+ tuple_length=z_coeff_var_indices[ind]
754
+ for sub_ind in range(ind+1,ind+1+tuple_length):
755
+ x.ctrl(q[nqx+nqy+nqz-1-z_coeff_var_indices[sub_ind]],qdir[0])
756
+ ry.ctrl(-z_coeffs[coeff_ind],[qdir[2],qdir[1]],qdir[0])
757
+ coeff_ind+=1
758
+ ind+=(1+tuple_length)
759
+ x.ctrl(qdir[0],qdir[1])
760
+ ind=0
761
+ coeff_ind=0
762
+ while ind<len(y_coeff_var_indices):
763
+ tuple_length=y_coeff_var_indices[ind]
764
+ for sub_ind in range(ind+1,ind+1+tuple_length):
765
+ x.ctrl(q[nqx+nqy+nqz-1-y_coeff_var_indices[sub_ind]],qdir[2])
766
+ ry.ctrl(y_coeffs[coeff_ind],[qdir[0],qdir[1]],qdir[2])
767
+ coeff_ind+=1
768
+ ind+=(1+tuple_length)
769
+ d2q5_tstep(q,nqx,nqy,nqz,nq_dir,dirs_i)
770
+ ind=0
771
+ coeff_ind=0
772
+ while ind<len(y_coeff_var_indices_):
773
+ tuple_length=y_coeff_var_indices_[ind]
774
+ for sub_ind in range(ind+1,ind+1+tuple_length):
775
+ x.ctrl(q[nqx+nqy+nqz-1-y_coeff_var_indices_[sub_ind]],qdir[2])
776
+ ry.ctrl(-y_coeffs_[coeff_ind],[qdir[0],qdir[1]],qdir[2])
777
+ coeff_ind+=1
778
+ ind+=(1+tuple_length)
779
+ x.ctrl(qdir[0],qdir[1])
780
+ ind=0
781
+ coeff_ind=0
782
+ x(qdir[2])
783
+ while ind<len(x_coeff_var_indices_):
784
+ tuple_length=x_coeff_var_indices_[ind]
785
+ for sub_ind in range(ind+1,ind+1+tuple_length):
786
+ x.ctrl(q[nqx+nqy+nqz-1-x_coeff_var_indices_[sub_ind]],qdir[0])
787
+ ry.ctrl(x_coeffs_[coeff_ind],[qdir[1],qdir[2]],qdir[0])
788
+ coeff_ind+=1
789
+ ind+=(1+tuple_length)
790
+ x(qdir[2])
791
+ ind=0
792
+ coeff_ind=0
793
+ while ind<len(z_coeff_var_indices_):
794
+ tuple_length=z_coeff_var_indices_[ind]
795
+ for sub_ind in range(ind+1,ind+1+tuple_length):
796
+ x.ctrl(q[nqx+nqy+nqz-1-z_coeff_var_indices_[sub_ind]],qdir[0])
797
+ ry.ctrl(z_coeffs_[coeff_ind],[qdir[1],qdir[2]],qdir[0])
798
+ coeff_ind+=1
799
+ ind+=(1+tuple_length)
800
+ x.ctrl(qdir[0],qdir[1])
801
+ ind=0
802
+ coeff_ind=0
803
+ x.ctrl(qdir[1],qdir[2])
804
+ while ind<len(unprep2_coeff_var_indices):
805
+ tuple_length=unprep2_coeff_var_indices[ind]
806
+ for sub_ind in range(ind+1,ind+1+tuple_length):
807
+ x.ctrl(q[nqx+nqy+nqz-1-unprep2_coeff_var_indices[sub_ind]],qdir[1])
808
+ ry.ctrl(unprep2_coeffs[coeff_ind],qdir[2],qdir[1])
809
+ coeff_ind+=1
810
+ ind+=(1+tuple_length)
811
+ x.ctrl(qdir[1],qdir[2])
812
+ ind=0
813
+ coeff_ind=0
814
+ while ind<len(unprep1_coeff_var_indices):
815
+ tuple_length=unprep1_coeff_var_indices[ind]
816
+ for sub_ind in range(ind+1,ind+1+tuple_length):
817
+ x.ctrl(q[nqx+nqy+nqz-1-unprep1_coeff_var_indices[sub_ind]],qdir[1])
818
+ ry.ctrl(-unprep1_coeffs[coeff_ind],qdir[0],qdir[1])
819
+ coeff_ind+=1
820
+ ind+=(1+tuple_length)
821
+ ry(-2*np.pi/3,qdir[0])
822
+ print("Kernels defined")
823
+ def run_timestep_func(vec_arg, hadamard=False):
824
+ 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,\
825
+ x_coeff_var_indices,x_coeffs,\
826
+ y_coeff_var_indices,y_coeffs,\
827
+ z_coeff_var_indices,z_coeffs,\
828
+ x_coeff_var_indices_,x_coeffs_,\
829
+ y_coeff_var_indices_,y_coeffs_,\
830
+ z_coeff_var_indices_,z_coeffs_,\
831
+ unprep1_coeff_var_indices,unprep1_coeffs,\
832
+ unprep2_coeff_var_indices,unprep2_coeffs)
833
+ num_nonzero_ranks = num_ranks / (2**num_anc)
834
+ rank_slice_cupy = to_cupy_array(result)
835
+ if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
836
+ sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
837
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
838
+ if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
839
+ limit_idx = int(N_tot_state_vector / (2**num_anc))
840
+ if limit_idx < rank_slice_cupy.size:
841
+ rank_slice_cupy[limit_idx:] = 0
842
+ return result
843
+ self.run_timestep = run_timestep_func
844
+ print("Circuit created")
845
+ def write_state(self, state_to_write, t_step_str_val):
846
+ rank_slice_cupy = to_cupy_array(state_to_write)
847
+ num_nonzero_ranks = num_ranks / (2**num_anc)
848
+ if rank < num_nonzero_ranks or (rank == 0 and num_nonzero_ranks <= 0):
849
+ save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
850
+ with open(save_path, 'wb') as f:
851
+ arr_to_save = None
852
+ data_limit = N_sub_per_rank
853
+ if num_nonzero_ranks < 1 and rank == 0:
854
+ data_limit = int(N_tot_state_vector / (2**num_anc))
855
+ if data_limit > 0:
856
+ relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
857
+ else:
858
+ relevant_part_cupy = cp.array([], dtype=cp.float64)
859
+ if relevant_part_cupy.size >= current_N * current_N * current_N:
860
+ arr_flat = relevant_part_cupy[:current_N * current_N * current_N]
861
+ if downsampling_factor > 1 and current_N > 0:
862
+ arr_reshaped = arr_flat.reshape((current_N, current_N, current_N))
863
+ arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor, ::downsampling_factor]
864
+ arr_to_save = arr_downsampled.flatten()
865
+ else:
866
+ arr_to_save = arr_flat
867
+ elif relevant_part_cupy.size > 0:
868
+ if downsampling_factor > 1:
869
+ arr_to_save = relevant_part_cupy[::downsampling_factor]
870
+ else:
871
+ arr_to_save = relevant_part_cupy
872
+ if arr_to_save is not None and arr_to_save.size > 0:
873
+ np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
874
+ print("Write state defined")
875
+ def run_evolution(self, initial_state_arg, total_timesteps, time_steps_to_save, observable=False):
876
+ current_state_val = initial_state_arg
877
+ save_times = set(time_steps_to_save)
878
+ if 0 in save_times:
879
+ print("Writing first state")
880
+ self.write_state(current_state_val, '0')
881
+ for t_iter in range(total_timesteps):
882
+ print("Running timestep")
883
+ next_state_val = self.run_timestep(current_state_val)
884
+ if (t_iter + 1) in save_times:
885
+ print("Writing next state")
886
+ self.write_state(next_state_val, str(t_iter + 1))
887
+ cp.get_default_memory_pool().free_all_blocks()
888
+ current_state_val = next_state_val
889
+ if rank == 0:
890
+ print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
891
+ cp.get_default_memory_pool().free_all_blocks()
892
+ self.final_state = current_state_val
893
+
894
+ if boundary_condition == "Periodic":
895
+ pass
896
+ elif boundary_condition == "Dirichlet":
897
+ pass
898
+ elif boundary_condition == "Neumann":
899
+ pass
900
+
901
+ downsampling_factor = 1
902
+ if current_N == 0:
903
+ print("Error: current_N is zero. num_reg_qubits likely too small.")
904
+ return None, None # Modified return
905
+ if current_N < downsampling_factor:
906
+ downsampling_factor = current_N if current_N > 0 else 1
907
+
908
+ qlbm_obj = QLBMAdvecDiffD3Q7_new(vx=vx_input, vy=vy_input, vz=vz_input)
909
+ initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
910
+
911
+ sub_sv_init_flat = initial_state_func_eval(np.arange(N_sub_per_rank)).astype(np.complex128)
912
+
913
+ norm = np.linalg.norm(sub_sv_init_flat)
914
+ if norm > 0:
915
+ sub_sv_init_flat /= norm
916
+ else:
917
+ print("Error: Initial state norm is zero.")
918
+ return None, None # Modified return
919
+ full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
920
+ num_computational_states = current_N ** 3
921
+ if len(sub_sv_init_flat) == num_computational_states:
922
+ if num_computational_states <= N_sub_per_rank:
923
+ full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
924
+ else:
925
+ print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
926
+ return None, None # Modified return
927
+ else:
928
+ print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
929
+ fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
930
+ full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
931
+
932
+ rank_slice_init = to_cupy_array(initial_state_val)
933
+ print(f'Rank {rank}: Initializing state with {distribution_type} (vx={vx_input}, vy={vy_input})...')
934
+ cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
935
+ print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
936
+
937
+ print("Starting QLBM evolution...")
938
+ qlbm_obj.run_evolution(initial_state_val, T, time_steps)
939
+ print("QLBM evolution complete.")
940
+
941
+ print("Generating interactive plot with Plotly...")
942
+ downsampled_N = current_N // downsampling_factor
943
+ if downsampled_N == 0 and current_N > 0:
944
+ downsampled_N = 1
945
+ elif current_N == 0:
946
+ print("Error: current_N is zero before Plotly stage.")
947
+ return None, None # Modified return
948
+
949
+ data_frames = []
950
+ actual_timesteps = []
951
+ for t in time_steps:
952
+ file_path = intermediate_folder_path / f"{t}_{rank}.npy"
953
+ if file_path.exists():
954
+ sol_loaded = np.load(file_path)
955
+ if sol_loaded.size == downsampled_N * downsampled_N* downsampled_N:
956
+ data = np.reshape(sol_loaded, (downsampled_N, downsampled_N, downsampled_N))
957
+ data_frames.append(data)
958
+ actual_timesteps.append(t)
959
+ print(f"Time {t}: Min={np.min(data)}, Max={np.max(data)}, Mean={np.mean(data)}")
960
+ else:
961
+ print(f"Warning: File {file_path} size {sol_loaded.size} != expected {downsampled_N*downsampled_N*downsampled_N}. Skipping.")
962
+ else:
963
+ print(f"Warning: File {file_path} not found. Skipping.")
964
+
965
+ if not data_frames:
966
+ print("Error: No data frames loaded for plotting.")
967
+ return None, None # Modified return
968
+
969
+ x_coords_plot = np.linspace(0, 1, downsampled_N)
970
+ y_coords_plot = np.linspace(0, 1, downsampled_N)
971
+ z_coords_plot = np.linspace(0, 1, downsampled_N)
972
+ Z_grid_mesh, Y_grid_mesh, X_grid_mesh = np.meshgrid(x_coords_plot, y_coords_plot, z_coords_plot, indexing='ij')
973
+
974
+ data_min = min([np.min(data) for data in data_frames])
975
+ data_max = max([np.max(data) for data in data_frames])
976
+ if data_max == data_min:
977
+ data_max += 1e-9
978
+
979
+ fig = go.Figure()
980
+
981
+ # Store individual frames for download
982
+ plotly_json_frames = []
983
+
984
+ for i, output_data in enumerate(data_frames):
985
+ frame_trace = go.Isosurface(
986
+ x=X_grid_mesh.flatten(),
987
+ y=Y_grid_mesh.flatten(),
988
+ z=Z_grid_mesh.flatten(),
989
+ value=output_data.flatten(),
990
+ isomin=data_min,
991
+ isomax=data_max,
992
+ opacity=0.4, # needs to be small to see through all surfaces
993
+ surface_count=7, # needs to be a large number for good volume rendering,
994
+ caps=dict(x_show=False, y_show=False, z_show=False)
995
+ )
996
+ fig.add_trace(frame_trace)
997
+
998
+ # Create a figure for the individual frame and convert to JSON
999
+ single_frame_fig = go.Figure(data=[frame_trace], layout=fig.layout)
1000
+ single_frame_fig.update_layout(
1001
+ title=f"Time: {actual_timesteps[i]}",
1002
+ scene=dict(
1003
+ xaxis_title='X',
1004
+ yaxis_title='Y',
1005
+ zaxis_title='Z',
1006
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
1007
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
1008
+ zaxis=dict(range=[z_coords_plot[0], z_coords_plot[-1]]),
1009
+ )
1010
+ )
1011
+ plotly_json_frames.append(single_frame_fig.to_json())
1012
+
1013
+ for trace in fig.data[1:]:
1014
+ trace.visible = False
1015
+
1016
+ steps = []
1017
+ for i in range(len(data_frames)):
1018
+ step = dict(
1019
+ method="update",
1020
+ args=[{"visible": [False] * len(data_frames)}],
1021
+ label=f"Time: {actual_timesteps[i]}"
1022
+ )
1023
+ step["args"][0]["visible"][i] = True
1024
+ steps.append(step)
1025
+
1026
+ sliders = [dict(active=0, currentvalue={"prefix": "Time: "}, pad={"t": 50}, steps=steps)]
1027
+
1028
+ fig.update_layout(
1029
+ title='', # Removed graph title
1030
+ scene=dict(
1031
+ xaxis_title='X',
1032
+ yaxis_title='Y',
1033
+ zaxis_title='Z',
1034
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
1035
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
1036
+ zaxis=dict(range=[z_coords_plot[0], z_coords_plot[-1]]),
1037
+ ),
1038
+ sliders=sliders,
1039
+ width=800,
1040
+ height=700
1041
+ )
1042
+
1043
+ return fig, plotly_json_frames # Modified return
fluid3d_pyvista.py ADDED
@@ -0,0 +1,638 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import math
3
+ import tempfile
4
+ import cudaq
5
+ import numpy as np
6
+ import cupy as cp
7
+ from pathlib import Path
8
+ import plotly.graph_objects as go
9
+ import plotly.io as pio
10
+
11
+ # Set Plotly engine for image export
12
+ try:
13
+ pio.kaleido.scope.mathjax = None
14
+ except AttributeError:
15
+ pass
16
+
17
+ # Existing functions (bin_to_gray, gray_to_bin, etc.) remain unchanged
18
+ def bin_to_gray(bin_s):
19
+ XOR=lambda x,y: (x or y) and not (x and y)
20
+ gray_s=bin_s[0]
21
+ for i in range(len(bin_s)-1):
22
+ c_bool=XOR(bool(int(bin_s[i])),bool(int(bin_s[i+1])))
23
+ gray_s+=str(int(c_bool))
24
+ return gray_s
25
+
26
+ def gray_to_bin(gray_s):
27
+ XOR=lambda x,y: (x or y) and not (x and y)
28
+ bin_s=gray_s[0]
29
+ for i in range(len(gray_s)-1):
30
+ c_bool=XOR(bool(int(bin_s[i])),bool(int(gray_s[i+1])))
31
+ bin_s+=str(int(c_bool))
32
+ return bin_s
33
+
34
+ def bin_to_int(bin_s):
35
+ return int(bin_s,2)
36
+
37
+ def int_to_bin(i,pad):
38
+ return bin(i)[2:].zfill(pad)
39
+
40
+ def fwht_approx(f,N,num_points_per_dim,threshold=1e-10):
41
+ linear_block_size=int(N//num_points_per_dim)
42
+ num_angles_per_block=int(np.log2(linear_block_size))
43
+
44
+ thetas={}
45
+
46
+ for k in range(num_points_per_dim):
47
+ for j in range(num_points_per_dim):
48
+ for i in range(num_points_per_dim):
49
+
50
+ 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))
51
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size]=avg_f
52
+
53
+ 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
54
+ 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
55
+ 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
56
+
57
+ for m in range(num_angles_per_block):
58
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size + 2**m]=slope_x*(2**(m-1))
59
+ thetas[k*(N**2)*linear_block_size+j*N*linear_block_size+i*linear_block_size + N*(2**m)]=slope_y*(2**(m-1))
60
+ 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))
61
+
62
+ h = linear_block_size
63
+ while h < N**3:
64
+ for i in range(0, N**3, h * 2):
65
+ if (i//N)%linear_block_size!=0:
66
+ continue
67
+ if (i//(N**2))%linear_block_size!=0:
68
+ continue
69
+ j=i
70
+ while j<i+h:
71
+ index=j
72
+ x = thetas[index]
73
+ y = thetas[index + h]
74
+ thetas[index] = (x + y)/2
75
+ thetas[index + h] = (x - y)/2
76
+
77
+ for ax in range(3):
78
+ for m in range(num_angles_per_block):
79
+ index = j + (N**ax) * (2**m)
80
+ x = thetas[index]
81
+ y = thetas[index + h]
82
+ thetas[index] = (x + y)/2
83
+ thetas[index + h] = (x - y)/2
84
+
85
+ j+=linear_block_size
86
+ if (j//N)%linear_block_size==1:
87
+ j+=(linear_block_size-1)*N
88
+ if (j//(N**2))%linear_block_size==1:
89
+ j+=(linear_block_size-1)*(N**2)
90
+
91
+ h *= 2
92
+ if h==N:
93
+ h=N*linear_block_size
94
+ if h==N**2:
95
+ h=(N**2)*linear_block_size
96
+
97
+ return [theta for theta in thetas.values() if abs(theta)>threshold],[key for key in thetas.keys() if abs(thetas[key])>threshold]
98
+
99
+ def get_circuit_inputs(f,num_reg_qubits,num_points_per_dim):
100
+ theta_vec,indices=fwht_approx(f,2**num_reg_qubits,num_points_per_dim)
101
+ circ_pos=[]
102
+ for ind in indices:
103
+ circ_pos+=[bin_to_int(gray_to_bin(int_to_bin(ind,num_reg_qubits*3)))]
104
+
105
+ sorted_theta_vec=sorted(zip(theta_vec,circ_pos),key=lambda el:el[1])
106
+ ctrls=[]
107
+
108
+ current_bs="0"*(3*num_reg_qubits)
109
+ for el in sorted_theta_vec:
110
+ new_bs=bin_to_gray(int_to_bin((el[1])%(2**(3*num_reg_qubits)),(3*num_reg_qubits)))
111
+ ctrls += [[i for i, (char1, char2) in enumerate(zip(current_bs, new_bs)) if char1 != char2]]
112
+ current_bs=new_bs
113
+ new_bs="0"*(3*num_reg_qubits)
114
+ ctrls += [[i for i, (char1, char2) in enumerate(zip(current_bs, new_bs)) if char1 != char2]]
115
+
116
+ ctrls_flat_list=[]
117
+ for ctrl_list in ctrls:
118
+ ctrls_flat_list+=[len(ctrl_list)]+ctrl_list
119
+
120
+ return [el[0] for el in sorted_theta_vec]+[0.0],ctrls_flat_list
121
+
122
+ def simulate_qlbm_3D_and_animate(num_reg_qubits: int, T: int, distribution_type: str, vx_input, vy_input, vz_input, boundary_condition: str):
123
+ num_anc = 3
124
+ num_qubits_total = 3 * num_reg_qubits + num_anc
125
+ current_N = 2**num_reg_qubits
126
+ N_tot_state_vector = 2**num_qubits_total
127
+ num_ranks = 1
128
+ rank = 0
129
+ N_sub_per_rank = int(N_tot_state_vector // num_ranks)
130
+
131
+ # Simplified time steps for 3D since slider steps are removed
132
+ NUM_ANIMATION_FRAMES_3D = 10 # Default number of frames if no specific slider steps
133
+
134
+ if T == 0:
135
+ time_steps = [0]
136
+ else:
137
+ num_points = min(T + 1, NUM_ANIMATION_FRAMES_3D)
138
+ time_steps = np.linspace(start=0, stop=T, num=num_points, dtype=int)
139
+ time_steps = sorted(list(set(time_steps)))
140
+
141
+
142
+ if distribution_type == "Sinusoidal":
143
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
144
+ np.sin(x * 2 * np.pi / N_val_func) * \
145
+ np.sin(y * 2 * np.pi / N_val_func) * \
146
+ np.sin(z * 2 * np.pi / N_val_func) + 1
147
+ elif distribution_type == "Gaussian":
148
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
149
+ 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) +
150
+ (z - N_val_func / 2)**2 / (2 * (N_val_func / 5)**2))) * 1.8 + 0.2
151
+ else:
152
+ print(f"Warning: Unknown distribution type '{distribution_type}'. Defaulting to Sinusoidal.")
153
+ selected_initial_state_function_raw = lambda x, y, z, N_val_func: \
154
+ np.sin(x * 2 * np.pi / N_val_func) * \
155
+ np.sin(y * 2 * np.pi / N_val_func) * \
156
+ np.sin(z * 2 * np.pi / N_val_func) + 1
157
+
158
+ initial_state_func_eval = lambda i:\
159
+ 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)
160
+
161
+ with tempfile.TemporaryDirectory() as tmp_npy_dir:
162
+ intermediate_folder_path = Path(tmp_npy_dir)
163
+
164
+ cudaq.set_target('nvidia', option='fp64')
165
+
166
+ @cudaq.kernel
167
+ def alloc_kernel(num_qubits_alloc: int):
168
+ qubits = cudaq.qvector(num_qubits_alloc)
169
+
170
+ from cupy.cuda.memory import MemoryPointer, UnownedMemory
171
+
172
+ def to_cupy_array(state):
173
+ tensor = state.getTensor()
174
+ pDevice = tensor.data()
175
+ sizeByte = tensor.get_num_elements() * tensor.get_element_size()
176
+ mem = UnownedMemory(pDevice, sizeByte, owner=state)
177
+ memptr_obj = MemoryPointer(mem, 0)
178
+ cupy_array_val = cp.ndarray(tensor.get_num_elements(),
179
+ dtype=cp.complex128,
180
+ memptr=memptr_obj)
181
+ return cupy_array_val
182
+
183
+ class QLBMAdvecDiffD3Q7_new:
184
+ def __init__(self,vx,vy,vz) -> None:
185
+ self.dim = 3
186
+ self.ndir = 7
187
+ self.nq_dir = math.ceil(np.log2(self.ndir))
188
+ self.dirs=[]
189
+ for dir_int in range(self.ndir):
190
+ if dir_int==4:
191
+ dir_bin="111"
192
+ else:
193
+ dir_bin = f"{dir_int:b}".zfill(self.nq_dir)
194
+ self.dirs.append(dir_bin)
195
+ self.cs = 1/np.sqrt(3)
196
+ self.ux = lambda x,y,z: vx(x,y,z)/self.cs**2
197
+ self.uy = lambda x,y,z: vy(x,y,z)/self.cs**2
198
+ self.uz = lambda x,y,z: vz(x,y,z)/self.cs**2
199
+ self.create_circuit()
200
+
201
+ def create_circuit(self):
202
+ print("Creating circuit")
203
+ 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))
204
+ 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))
205
+ 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))
206
+ 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 \
207
+ ((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))
208
+ 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 \
209
+ ((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))
210
+ 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 \
211
+ ((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))
212
+ unprep1_coeffs,unprep1_coeff_var_indices=get_circuit_inputs(lambda x,y,z:\
213
+ (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))
214
+ 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))
215
+ print("Generated angles")
216
+ v=np.pad([1/4, 1/4, 0, 1/4, 0, 1/4, 0],(0,2**num_anc - self.ndir))
217
+ v=v**0.5
218
+ v[0]+=1
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
+ def collisionOp(dirs):
223
+ dirs_i_list=[]
224
+ for dir_ in dirs:
225
+ dirs_i=[(int(c)) for c in dir_]
226
+ dirs_i_list+=dirs_i[::-1]
227
+ return dirs_i_list
228
+ self.dirs_i_list=collisionOp(self.dirs)
229
+ print("Generated dirs_i_list")
230
+ @cudaq.kernel
231
+ def rshift(q: cudaq.qview, n: int):
232
+ for i in range(n):
233
+ if i == n-1:
234
+ x(q[n-1-i])
235
+ elif i == n-2:
236
+ x.ctrl(q[n-1-(i+1)], q[n-1-i])
237
+ else:
238
+ x.ctrl(q[0:n-1-i], q[n-1-i])
239
+ @cudaq.kernel
240
+ def lshift(q: cudaq.qview, n: int):
241
+ for i in range(n):
242
+ if i == 0:
243
+ x(q[0])
244
+ elif i == 1:
245
+ x.ctrl(q[0], q[1])
246
+ else:
247
+ x.ctrl(q[0:i], q[i])
248
+ @cudaq.kernel
249
+ def d2q5_tstep(q: cudaq.qview, nqx: int, nqy: int, nqz: int, nq_dir: int, dirs_i: list[int]):
250
+ qx=q[0:nqx]
251
+ qy=q[nqx:nqx+nqy]
252
+ qz=q[nqx+nqy:nqx+nqy+nqz]
253
+ qdir=q[nqx+nqy+nqz:nqx+nqy+nqz+nq_dir]
254
+ i=2
255
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
256
+ for j in range(nq_dir):
257
+ b=b_list[j]
258
+ if b==0:
259
+ x(qdir[j])
260
+ cudaq.control(lshift,qdir,qx,nqx)
261
+ for j in range(nq_dir):
262
+ b=b_list[j]
263
+ if b==0:
264
+ x(qdir[j])
265
+ i=1
266
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
267
+ for j in range(nq_dir):
268
+ b=b_list[j]
269
+ if b==0:
270
+ x(qdir[j])
271
+ cudaq.control(rshift,qdir,qx,nqx)
272
+ for j in range(nq_dir):
273
+ b=b_list[j]
274
+ if b==0:
275
+ x(qdir[j])
276
+ i=4
277
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
278
+ for j in range(nq_dir):
279
+ b=b_list[j]
280
+ if b==0:
281
+ x(qdir[j])
282
+ cudaq.control(lshift,qdir,qy,nqy)
283
+ for j in range(nq_dir):
284
+ b=b_list[j]
285
+ if b==0:
286
+ x(qdir[j])
287
+ i=3
288
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
289
+ for j in range(nq_dir):
290
+ b=b_list[j]
291
+ if b==0:
292
+ x(qdir[j])
293
+ cudaq.control(rshift,qdir,qy,nqy)
294
+ for j in range(nq_dir):
295
+ b=b_list[j]
296
+ if b==0:
297
+ x(qdir[j])
298
+ i=6
299
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
300
+ for j in range(nq_dir):
301
+ b=b_list[j]
302
+ if b==0:
303
+ x(qdir[j])
304
+ cudaq.control(lshift,qdir,qz,nqz)
305
+ for j in range(nq_dir):
306
+ b=b_list[j]
307
+ if b==0:
308
+ x(qdir[j])
309
+ i=5
310
+ b_list=dirs_i[i*nq_dir:(i+1)*nq_dir]
311
+ for j in range(nq_dir):
312
+ b=b_list[j]
313
+ if b==0:
314
+ x(qdir[j])
315
+ cudaq.control(rshift,qdir,qz,nqz)
316
+ for j in range(nq_dir):
317
+ b=b_list[j]
318
+ if b==0:
319
+ x(qdir[j])
320
+ @cudaq.kernel
321
+ def d2q5_tstep_wrapper(state: cudaq.State,nqx:int,nqy:int,nqz:int,nq_dir:int,dirs_i:list[int],\
322
+ x_coeff_var_indices:list[int],x_coeffs:list[float],\
323
+ y_coeff_var_indices:list[int],y_coeffs:list[float],\
324
+ z_coeff_var_indices:list[int],z_coeffs:list[float],\
325
+ x_coeff_var_indices_:list[int],x_coeffs_:list[float],\
326
+ y_coeff_var_indices_:list[int],y_coeffs_:list[float],\
327
+ z_coeff_var_indices_:list[int],z_coeffs_:list[float],\
328
+ unprep1_coeff_var_indices:list[int],unprep1_coeffs:list[float],\
329
+ unprep2_coeff_var_indices:list[int],unprep2_coeffs:list[float]):
330
+ q=cudaq.qvector(state)
331
+ qdir=q[nqx+nqy+nqz:nqx+nqy+nqz+nq_dir]
332
+ prep_op(qdir[2],qdir[1],qdir[0])
333
+ x.ctrl(qdir[0],qdir[1])
334
+ ind=0
335
+ coeff_ind=0
336
+ x(qdir[2])
337
+ while ind<len(x_coeff_var_indices):
338
+ tuple_length=x_coeff_var_indices[ind]
339
+ for sub_ind in range(ind+1, ind+1+tuple_length):
340
+ x.ctrl(q[nqx+nqy+nqz-1-x_coeff_var_indices[sub_ind]],qdir[0])
341
+ ry.ctrl(-x_coeffs[coeff_ind],[qdir[2],qdir[1]],qdir[0])
342
+ coeff_ind+=1
343
+ ind+=(1+tuple_length)
344
+ x(qdir[2])
345
+ ind=0
346
+ coeff_ind=0
347
+ while ind<len(z_coeff_var_indices):
348
+ tuple_length=z_coeff_var_indices[ind]
349
+ for sub_ind in range(ind+1,ind+1+tuple_length):
350
+ x.ctrl(q[nqx+nqy+nqz-1-z_coeff_var_indices[sub_ind]],qdir[0])
351
+ ry.ctrl(-z_coeffs[coeff_ind],[qdir[2],qdir[1]],qdir[0])
352
+ coeff_ind+=1
353
+ ind+=(1+tuple_length)
354
+ x.ctrl(qdir[0],qdir[1])
355
+ ind=0
356
+ coeff_ind=0
357
+ while ind<len(y_coeff_var_indices):
358
+ tuple_length=y_coeff_var_indices[ind]
359
+ for sub_ind in range(ind+1,ind+1+tuple_length):
360
+ x.ctrl(q[nqx+nqy+nqz-1-y_coeff_var_indices[sub_ind]],qdir[2])
361
+ ry.ctrl(y_coeffs[coeff_ind],[qdir[0],qdir[1]],qdir[2])
362
+ coeff_ind+=1
363
+ ind+=(1+tuple_length)
364
+ d2q5_tstep(q,nqx,nqy,nqz,nq_dir,dirs_i)
365
+ ind=0
366
+ coeff_ind=0
367
+ while ind<len(y_coeff_var_indices_):
368
+ tuple_length=y_coeff_var_indices_[ind]
369
+ for sub_ind in range(ind+1,ind+1+tuple_length):
370
+ x.ctrl(q[nqx+nqy+nqz-1-y_coeff_var_indices_[sub_ind]],qdir[2])
371
+ ry.ctrl(-y_coeffs_[coeff_ind],[qdir[0],qdir[1]],qdir[2])
372
+ coeff_ind+=1
373
+ ind+=(1+tuple_length)
374
+ x.ctrl(qdir[0],qdir[1])
375
+ ind=0
376
+ coeff_ind=0
377
+ x(qdir[2])
378
+ while ind<len(x_coeff_var_indices_):
379
+ tuple_length=x_coeff_var_indices_[ind]
380
+ for sub_ind in range(ind+1,ind+1+tuple_length):
381
+ x.ctrl(q[nqx+nqy+nqz-1-x_coeff_var_indices_[sub_ind]],qdir[0])
382
+ ry.ctrl(x_coeffs_[coeff_ind],[qdir[1],qdir[2]],qdir[0])
383
+ coeff_ind+=1
384
+ ind+=(1+tuple_length)
385
+ x(qdir[2])
386
+ ind=0
387
+ coeff_ind=0
388
+ while ind<len(z_coeff_var_indices_):
389
+ tuple_length=z_coeff_var_indices_[ind]
390
+ for sub_ind in range(ind+1,ind+1+tuple_length):
391
+ x.ctrl(q[nqx+nqy+nqz-1-z_coeff_var_indices_[sub_ind]],qdir[0])
392
+ ry.ctrl(z_coeffs_[coeff_ind],[qdir[1],qdir[2]],qdir[0])
393
+ coeff_ind+=1
394
+ ind+=(1+tuple_length)
395
+ x.ctrl(qdir[0],qdir[1])
396
+ ind=0
397
+ coeff_ind=0
398
+ x.ctrl(qdir[1],qdir[2])
399
+ while ind<len(unprep2_coeff_var_indices):
400
+ tuple_length=unprep2_coeff_var_indices[ind]
401
+ for sub_ind in range(ind+1,ind+1+tuple_length):
402
+ x.ctrl(q[nqx+nqy+nqz-1-unprep2_coeff_var_indices[sub_ind]],qdir[1])
403
+ ry.ctrl(unprep2_coeffs[coeff_ind],qdir[2],qdir[1])
404
+ coeff_ind+=1
405
+ ind+=(1+tuple_length)
406
+ x.ctrl(qdir[1],qdir[2])
407
+ ind=0
408
+ coeff_ind=0
409
+ while ind<len(unprep1_coeff_var_indices):
410
+ tuple_length=unprep1_coeff_var_indices[ind]
411
+ for sub_ind in range(ind+1,ind+1+tuple_length):
412
+ x.ctrl(q[nqx+nqy+nqz-1-unprep1_coeff_var_indices[sub_ind]],qdir[1])
413
+ ry.ctrl(-unprep1_coeffs[coeff_ind],qdir[0],qdir[1])
414
+ coeff_ind+=1
415
+ ind+=(1+tuple_length)
416
+ ry(-2*np.pi/3,qdir[0])
417
+ print("Kernels defined")
418
+ def run_timestep_func(vec_arg, hadamard=False):
419
+ 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,\
420
+ x_coeff_var_indices,x_coeffs,\
421
+ y_coeff_var_indices,y_coeffs,\
422
+ z_coeff_var_indices,z_coeffs,\
423
+ x_coeff_var_indices_,x_coeffs_,\
424
+ y_coeff_var_indices_,y_coeffs_,\
425
+ z_coeff_var_indices_,z_coeffs_,\
426
+ unprep1_coeff_var_indices,unprep1_coeffs,\
427
+ unprep2_coeff_var_indices,unprep2_coeffs)
428
+ num_nonzero_ranks = num_ranks / (2**num_anc)
429
+ rank_slice_cupy = to_cupy_array(result)
430
+ if rank >= num_nonzero_ranks and num_nonzero_ranks > 0:
431
+ sub_sv_zeros = np.zeros(N_sub_per_rank, dtype=np.complex128)
432
+ cp.cuda.runtime.memcpy(rank_slice_cupy.data.ptr, sub_sv_zeros.ctypes.data, sub_sv_zeros.nbytes, cp.cuda.runtime.memcpyHostToDevice)
433
+ if rank == 0 and num_nonzero_ranks < 1 and N_sub_per_rank > 0:
434
+ limit_idx = int(N_tot_state_vector / (2**num_anc))
435
+ if limit_idx < rank_slice_cupy.size:
436
+ rank_slice_cupy[limit_idx:] = 0
437
+ return result
438
+ self.run_timestep = run_timestep_func
439
+ print("Circuit created")
440
+ def write_state(self, state_to_write, t_step_str_val):
441
+ rank_slice_cupy = to_cupy_array(state_to_write)
442
+ num_nonzero_ranks = num_ranks / (2**num_anc)
443
+ if rank < num_nonzero_ranks or (rank == 0 and num_nonzero_ranks <= 0):
444
+ save_path = intermediate_folder_path / f"{t_step_str_val}_{rank}.npy"
445
+ with open(save_path, 'wb') as f:
446
+ arr_to_save = None
447
+ data_limit = N_sub_per_rank
448
+ if num_nonzero_ranks < 1 and rank == 0:
449
+ data_limit = int(N_tot_state_vector / (2**num_anc))
450
+ if data_limit > 0:
451
+ relevant_part_cupy = cp.real(rank_slice_cupy[:data_limit])
452
+ else:
453
+ relevant_part_cupy = cp.array([], dtype=cp.float64)
454
+ if relevant_part_cupy.size >= current_N * current_N * current_N:
455
+ arr_flat = relevant_part_cupy[:current_N * current_N * current_N]
456
+ if downsampling_factor > 1 and current_N > 0:
457
+ arr_reshaped = arr_flat.reshape((current_N, current_N, current_N))
458
+ arr_downsampled = arr_reshaped[::downsampling_factor, ::downsampling_factor, ::downsampling_factor]
459
+ arr_to_save = arr_downsampled.flatten()
460
+ else:
461
+ arr_to_save = arr_flat
462
+ elif relevant_part_cupy.size > 0:
463
+ if downsampling_factor > 1:
464
+ arr_to_save = relevant_part_cupy[::downsampling_factor]
465
+ else:
466
+ arr_to_save = relevant_part_cupy
467
+ if arr_to_save is not None and arr_to_save.size > 0:
468
+ np.save(f, arr_to_save.get() if isinstance(arr_to_save, cp.ndarray) else arr_to_save)
469
+ print("Write state defined")
470
+ def run_evolution(self, initial_state_arg, total_timesteps, time_steps_to_save, observable=False):
471
+ current_state_val = initial_state_arg
472
+ save_times = set(time_steps_to_save)
473
+ if 0 in save_times:
474
+ print("Writing first state")
475
+ self.write_state(current_state_val, '0')
476
+ for t_iter in range(total_timesteps):
477
+ print("Running timestep")
478
+ next_state_val = self.run_timestep(current_state_val)
479
+ if (t_iter + 1) in save_times:
480
+ print("Writing next state")
481
+ self.write_state(next_state_val, str(t_iter + 1))
482
+ cp.get_default_memory_pool().free_all_blocks()
483
+ current_state_val = next_state_val
484
+ if rank == 0:
485
+ print(f"Timestep: {total_timesteps}/{total_timesteps} (Evolution complete)")
486
+ cp.get_default_memory_pool().free_all_blocks()
487
+ self.final_state = current_state_val
488
+
489
+ if boundary_condition == "Periodic":
490
+ pass
491
+ elif boundary_condition == "Dirichlet":
492
+ pass
493
+ elif boundary_condition == "Neumann":
494
+ pass
495
+
496
+ downsampling_factor = 1
497
+ if current_N == 0:
498
+ print("Error: current_N is zero. num_reg_qubits likely too small.")
499
+ return None, None # Modified return
500
+ if current_N < downsampling_factor:
501
+ downsampling_factor = current_N if current_N > 0 else 1
502
+
503
+ qlbm_obj = QLBMAdvecDiffD3Q7_new(vx=vx_input, vy=vy_input, vz=vz_input)
504
+ initial_state_val = cudaq.get_state(alloc_kernel, num_qubits_total)
505
+
506
+ sub_sv_init_flat = initial_state_func_eval(np.arange(N_sub_per_rank)).astype(np.complex128)
507
+
508
+ norm = np.linalg.norm(sub_sv_init_flat)
509
+ if norm > 0:
510
+ sub_sv_init_flat /= norm
511
+ else:
512
+ print("Error: Initial state norm is zero.")
513
+ return None, None # Modified return
514
+ full_initial_sv_host = np.zeros(N_sub_per_rank, dtype=np.complex128)
515
+ num_computational_states = current_N ** 3
516
+ if len(sub_sv_init_flat) == num_computational_states:
517
+ if num_computational_states <= N_sub_per_rank:
518
+ full_initial_sv_host[:num_computational_states] = sub_sv_init_flat
519
+ else:
520
+ print(f"Error: Grid data {num_computational_states} > N_sub_per_rank {N_sub_per_rank}")
521
+ return None, None # Modified return
522
+ else:
523
+ print(f"Warning: Initial state size {len(sub_sv_init_flat)} != expected {num_computational_states}")
524
+ fill_len = min(len(sub_sv_init_flat), num_computational_states, N_sub_per_rank)
525
+ full_initial_sv_host[:fill_len] = sub_sv_init_flat[:fill_len]
526
+
527
+ rank_slice_init = to_cupy_array(initial_state_val)
528
+ print(f'Rank {rank}: Initializing state with {distribution_type} (vx={vx_input}, vy={vy_input})...')
529
+ cp.cuda.runtime.memcpy(rank_slice_init.data.ptr, full_initial_sv_host.ctypes.data, full_initial_sv_host.nbytes, cp.cuda.runtime.memcpyHostToDevice)
530
+ print(f'Rank {rank}: Initial state copied. Size: {len(sub_sv_init_flat)}. N_sub_per_rank: {N_sub_per_rank}')
531
+
532
+ print("Starting QLBM evolution...")
533
+ qlbm_obj.run_evolution(initial_state_val, T, time_steps)
534
+ print("QLBM evolution complete.")
535
+
536
+ print("Generating interactive plot with Plotly...")
537
+ downsampled_N = current_N // downsampling_factor
538
+ if downsampled_N == 0 and current_N > 0:
539
+ downsampled_N = 1
540
+ elif current_N == 0:
541
+ print("Error: current_N is zero before Plotly stage.")
542
+ return None, None # Modified return
543
+
544
+ data_frames = []
545
+ actual_timesteps = []
546
+ for t in time_steps:
547
+ file_path = intermediate_folder_path / f"{t}_{rank}.npy"
548
+ if file_path.exists():
549
+ sol_loaded = np.load(file_path)
550
+ if sol_loaded.size == downsampled_N * downsampled_N* downsampled_N:
551
+ data = np.reshape(sol_loaded, (downsampled_N, downsampled_N, downsampled_N))
552
+ data_frames.append(data)
553
+ actual_timesteps.append(t)
554
+ print(f"Time {t}: Min={np.min(data)}, Max={np.max(data)}, Mean={np.mean(data)}")
555
+ else:
556
+ print(f"Warning: File {file_path} size {sol_loaded.size} != expected {downsampled_N*downsampled_N*downsampled_N}. Skipping.")
557
+ else:
558
+ print(f"Warning: File {file_path} not found. Skipping.")
559
+
560
+ if not data_frames:
561
+ print("Error: No data frames loaded for plotting.")
562
+ return None, None # Modified return
563
+
564
+ x_coords_plot = np.linspace(0, 1, downsampled_N)
565
+ y_coords_plot = np.linspace(0, 1, downsampled_N)
566
+ z_coords_plot = np.linspace(0, 1, downsampled_N)
567
+ Z_grid_mesh, Y_grid_mesh, X_grid_mesh = np.meshgrid(x_coords_plot, y_coords_plot, z_coords_plot, indexing='ij')
568
+
569
+ data_min = min([np.min(data) for data in data_frames])
570
+ data_max = max([np.max(data) for data in data_frames])
571
+ if data_max == data_min:
572
+ data_max += 1e-9
573
+
574
+ fig = go.Figure()
575
+
576
+ # Store individual frames for download
577
+ plotly_json_frames = []
578
+
579
+ for i, output_data in enumerate(data_frames):
580
+ frame_trace = go.Isosurface(
581
+ x=X_grid_mesh.flatten(),
582
+ y=Y_grid_mesh.flatten(),
583
+ z=Z_grid_mesh.flatten(),
584
+ value=output_data.flatten(),
585
+ isomin=data_min,
586
+ isomax=data_max,
587
+ opacity=0.4, # needs to be small to see through all surfaces
588
+ surface_count=7, # needs to be a large number for good volume rendering,
589
+ caps=dict(x_show=False, y_show=False, z_show=False)
590
+ )
591
+ fig.add_trace(frame_trace)
592
+
593
+ # Create a figure for the individual frame and convert to JSON
594
+ single_frame_fig = go.Figure(data=[frame_trace], layout=fig.layout)
595
+ single_frame_fig.update_layout(
596
+ title=f"Time: {actual_timesteps[i]}",
597
+ scene=dict(
598
+ xaxis_title='X',
599
+ yaxis_title='Y',
600
+ zaxis_title='Z',
601
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
602
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
603
+ zaxis=dict(range=[z_coords_plot[0], z_coords_plot[-1]]),
604
+ )
605
+ )
606
+ plotly_json_frames.append(single_frame_fig.to_json())
607
+
608
+ for trace in fig.data[1:]:
609
+ trace.visible = False
610
+
611
+ steps = []
612
+ for i in range(len(data_frames)):
613
+ step = dict(
614
+ method="update",
615
+ args=[{"visible": [False] * len(data_frames)}],
616
+ label=f"Time: {actual_timesteps[i]}"
617
+ )
618
+ step["args"][0]["visible"][i] = True
619
+ steps.append(step)
620
+
621
+ sliders = [dict(active=0, currentvalue={"prefix": "Time: "}, pad={"t": 50}, steps=steps)]
622
+
623
+ fig.update_layout(
624
+ title='', # Removed graph title
625
+ scene=dict(
626
+ xaxis_title='X',
627
+ yaxis_title='Y',
628
+ zaxis_title='Z',
629
+ xaxis=dict(range=[x_coords_plot[0], x_coords_plot[-1]]),
630
+ yaxis=dict(range=[y_coords_plot[0], y_coords_plot[-1]]),
631
+ zaxis=dict(range=[z_coords_plot[0], z_coords_plot[-1]]),
632
+ ),
633
+ sliders=sliders,
634
+ width=800,
635
+ height=700
636
+ )
637
+
638
+ return fig, plotly_json_frames # Modified return
pages/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (222 Bytes). View file
 
pages/__pycache__/em_page.cpython-310.pyc ADDED
Binary file (1.93 kB). View file
 
pages/__pycache__/qlbm_page.cpython-310.pyc ADDED
Binary file (2.18 kB). View file
 
pages/em_page.py CHANGED
@@ -7,6 +7,7 @@ import atexit
7
 
8
  # Keep a single child process for the EM app
9
  _em_proc = None
 
10
 
11
 
12
  def _kill_em_process():
@@ -38,6 +39,7 @@ def _ensure_em_process_started():
38
  env = os.environ.copy()
39
  # Port used by iframe
40
  env.setdefault("EM_APP_PORT", env.get("PORT_EM", "8701"))
 
41
  # Start em_trame.py in a separate process
42
  python_exe = sys.executable or "python"
43
  _em_proc = subprocess.Popen([python_exe, em_path], cwd=base_dir, env=env)
@@ -51,9 +53,10 @@ def build(server):
51
  """Render the EM app via iframe and ensure its process is running."""
52
  _ensure_em_process_started()
53
  port = os.environ.get("EM_APP_PORT", os.environ.get("PORT_EM", "8701"))
 
54
  with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
55
  trame_html.Iframe(
56
- src=("em_iframe_src", f"http://localhost:{port}/"),
57
  style="border:0; width:100%; height: calc(100vh - 64px);",
58
  )
59
  trame_html.Div(
 
7
 
8
  # Keep a single child process for the EM app
9
  _em_proc = None
10
+ _EM_HOST = os.environ.get("EM_HOST", "127.0.0.1")
11
 
12
 
13
  def _kill_em_process():
 
39
  env = os.environ.copy()
40
  # Port used by iframe
41
  env.setdefault("EM_APP_PORT", env.get("PORT_EM", "8701"))
42
+ env.setdefault("EM_HOST", _EM_HOST)
43
  # Start em_trame.py in a separate process
44
  python_exe = sys.executable or "python"
45
  _em_proc = subprocess.Popen([python_exe, em_path], cwd=base_dir, env=env)
 
53
  """Render the EM app via iframe and ensure its process is running."""
54
  _ensure_em_process_started()
55
  port = os.environ.get("EM_APP_PORT", os.environ.get("PORT_EM", "8701"))
56
+ host = os.environ.get("EM_HOST", _EM_HOST)
57
  with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
58
  trame_html.Iframe(
59
+ src=("em_iframe_src", f"http://{host}:{port}/"),
60
  style="border:0; width:100%; height: calc(100vh - 64px);",
61
  )
62
  trame_html.Div(
pages/qlbm_page.py CHANGED
@@ -1,203 +1,62 @@
 
 
 
 
 
 
 
 
 
 
1
  from trame_vuetify.widgets import vuetify3
2
  from trame.widgets import html as trame_html
3
- from trame_plotly.widgets import plotly as plotly_widgets
4
- import plotly.graph_objects as go
5
- import os
6
 
7
- GRID_SIZES = [16, 32, 64, 128, 256, 512]
 
8
 
9
 
10
- def _ensure_state_defaults(state):
11
- try:
12
- state.update({
13
- "qlbm_initialized": True,
14
- "logo_src": getattr(state, "logo_src", None),
15
- "is_running": False,
16
- "simulation_has_run": False,
17
- "error_message": "",
18
- "geometry_selection": getattr(state, "geometry_selection", None),
19
- "lbm_dim": getattr(state, "lbm_dim", "2D"),
20
- "domain_L": getattr(state, "domain_L", 1.0),
21
- "domain_W": getattr(state, "domain_W", 1.0),
22
- "domain_H": getattr(state, "domain_H", 1.0),
23
- "dist_type": getattr(state, "dist_type", None),
24
- "advecting_field": getattr(state, "advecting_field", None),
25
- "inlet_velocity": getattr(state, "inlet_velocity", 1.0),
26
- "inlet_temperature": getattr(state, "inlet_temperature", 300.0),
27
- "nx_slider_index": getattr(state, "nx_slider_index", None),
28
- "nx": getattr(state, "nx", None),
29
- "output_type": getattr(state, "output_type", "Surface Plot"),
30
- })
31
- except Exception:
32
- pass
33
-
34
-
35
- def _build_placeholder_figure(state):
36
- try:
37
- if state.geometry_selection == "Rectangular domain with a heated box (2D/3D)" and str(state.lbm_dim) == "3D":
38
- fig = go.Figure(data=[go.Scatter3d(x=[0, 1], y=[0, 1], z=[0, 1], mode="markers")])
39
- fig.update_layout(height=560, margin=dict(l=10, r=10, t=30, b=10))
40
- return fig
41
- fig = go.Figure(data=go.Heatmap(z=[[0, 1], [1, 0]], colorscale="RdBu"))
42
- fig.update_layout(height=560, margin=dict(l=10, r=10, t=30, b=10))
43
- return fig
44
- except Exception:
45
- return go.Figure()
46
-
47
-
48
- def build(server):
49
- state, ctrl = server.state, server.controller
50
- _ensure_state_defaults(state)
51
-
52
- def run_simulation():
53
- state.is_running = True
54
- state.error_message = ""
55
- try:
56
- fig = _build_placeholder_figure(state)
57
- try:
58
- ctrl.qlbm_plot_update(fig)
59
- except Exception:
60
- pass
61
- state.simulation_has_run = True
62
- except Exception as e:
63
- state.error_message = f"Run failed: {e}"
64
- finally:
65
- state.is_running = False
66
-
67
- def reset_all():
68
  try:
69
- state.update({
70
- "geometry_selection": None,
71
- "lbm_dim": "2D",
72
- "domain_L": 1.0,
73
- "domain_W": 1.0,
74
- "domain_H": 1.0,
75
- "dist_type": None,
76
- "advecting_field": None,
77
- "inlet_velocity": 1.0,
78
- "inlet_temperature": 300.0,
79
- "nx_slider_index": None,
80
- "nx": None,
81
- "output_type": "Surface Plot",
82
- "is_running": False,
83
- "simulation_has_run": False,
84
- "error_message": "",
85
- })
86
  try:
87
- ctrl.qlbm_plot_update(go.Figure())
88
  except Exception:
89
  pass
90
- except Exception:
91
- pass
92
 
93
- @state.change("nx_slider_index")
94
- def _on_nx_index_change(nx_slider_index, **_):
95
- try:
96
- state.nx = GRID_SIZES[int(nx_slider_index)] if nx_slider_index is not None else None
97
- except Exception:
98
- state.nx = None
99
 
100
- with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
101
- with vuetify3.VRow(no_gutters=True, classes="fill-height"):
102
- with vuetify3.VCol(cols=5, classes="pa-4 d-flex flex-column"):
103
- with vuetify3.VCard(classes="mb-4"):
104
- vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary")
105
- with vuetify3.VCardText():
106
- vuetify3.VDivider(classes="my-2")
107
- vuetify3.VCardSubtitle("Problems", classes="text-subtitle-1 font-weight-bold mt-2")
108
- vuetify3.VList(density="compact", lines="one", items=(
109
- "qlbm_problems",
110
- [
111
- {"title": "1. Scalar advection-diffusion in a box"},
112
- {"title": "2. Laminar flow & heat transfer for a heated body in water."},
113
- ],
114
- ))
115
- vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
116
- vuetify3.VListItemTitle("Laminar Navier-Stokes including energy", classes="text-body-2")
117
- vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
118
- vuetify3.VListItemTitle("Geometry, Boundary conditions - temperature and flow", classes="text-body-2")
119
- vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
120
- vuetify3.VListItemTitle("Surface plots on sections OR sampling through a line in 3D domain", classes="text-body-2")
121
-
122
- with vuetify3.VCard(classes="mb-4"):
123
- vuetify3.VCardTitle("Geometry", classes="text-primary")
124
- with vuetify3.VCardText():
125
- vuetify3.VSelect(
126
- label="Select",
127
- v_model=("geometry_selection", None),
128
- items=(
129
- "geometry_options",
130
- [
131
- "None",
132
- "Free space",
133
- "Rectangular domain with a heated box (2D/3D)",
134
- ],
135
- ),
136
- placeholder="Select",
137
- density="compact",
138
- color="primary",
139
- )
140
- with vuetify3.VContainer(v_if="geometry_selection === 'Rectangular domain with a heated box (2D/3D)'", classes="pa-0 mt-2"):
141
- vuetify3.VRadioGroup(v_model=("lbm_dim", "2D"), row=True, density="compact", color="primary", children=[
142
- vuetify3.VRadio(label="2D", value="2D"),
143
- vuetify3.VRadio(label="3D", value="3D"),
144
- ])
145
- with vuetify3.VRow(dense=True):
146
- vuetify3.VCol(children=[vuetify3.VTextField(label="Length (L)", v_model=("domain_L", 1.0), type="number", step="0.1", density="compact", color="primary")])
147
- vuetify3.VCol(children=[vuetify3.VTextField(label="Width (W)", v_model=("domain_W", 1.0), type="number", step="0.1", density="compact", color="primary")])
148
- vuetify3.VTextField(v_if="lbm_dim === '3D'", label="Height (H)", v_model=("domain_H", 1.0), type="number", step="0.1", density="compact", color="primary")
149
 
150
- with vuetify3.VCard(v_if="geometry_selection === 'Rectangular domain with a heated box (2D/3D)'", classes="mb-4"):
151
- vuetify3.VCardTitle("Initial & Boundary Conditions", classes="text-primary")
152
- with vuetify3.VCardText():
153
- vuetify3.VSelect(label="Initial Condition", v_model=("dist_type", None), items=("dist_type_opts", ["None", "Delta", "Gaussian"]), density="compact", color="primary")
154
- vuetify3.VSelect(label="Advecting field", v_model=("advecting_field", None), items=("advect_fields", ["Uniform", "Swirl", "Shear", "TGV"]), density="compact", color="primary", classes="mt-2")
155
- with vuetify3.VRow(dense=True, classes="mt-2"):
156
- vuetify3.VCol(children=[vuetify3.VTextField(label="Inlet flow velocity", v_model=("inlet_velocity", 1.0), type="number", step="0.1", density="compact", color="primary")])
157
- vuetify3.VCol(children=[vuetify3.VTextField(label="Inlet temperature (K)", v_model=("inlet_temperature", 300.0), type="number", step="1", density="compact", color="primary")])
158
 
159
- with vuetify3.VCard(classes="mb-4"):
160
- vuetify3.VCardTitle("Meshing", classes="text-primary")
161
- with vuetify3.VCardText():
162
- with vuetify3.VSlider(
163
- v_model=("nx_slider_index", None),
164
- label="No. of points per direction:",
165
- min=0,
166
- max=len(GRID_SIZES) - 1,
167
- step=1,
168
- show_ticks="always",
169
- thumb_label="always",
170
- density="compact",
171
- color="primary",
172
- ):
173
- vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ modelValue === null ? 'Select' : [16, 32, 64, 128, 256, 512][modelValue] }}"])
174
 
175
- with vuetify3.VCard(classes="mb-4"):
176
- vuetify3.VCardTitle("Backends", classes="text-primary")
177
- with vuetify3.VCardText():
178
- vuetify3.VAlert(type="info", color="primary", variant="tonal", density="compact", children=["Simulator only (placeholder)"])
179
 
180
- with vuetify3.VRow(dense=True, classes="mb-2"):
181
- vuetify3.VCol(children=[
182
- vuetify3.VBtn(text=("Run Simulation"), color="primary", block=True, disabled=("is_running || !geometry_selection || (geometry_selection === 'Rectangular domain with a heated box (2D/3D)' && nx === null)", False), click=run_simulation)
183
- ])
184
- vuetify3.VCol(children=[
185
- vuetify3.VBtn(text=("Reset"), color="secondary", variant="tonal", block=True, click=reset_all)
186
- ])
187
-
188
- with vuetify3.VCol(cols=7, classes="pa-4 d-flex flex-column"):
189
- with vuetify3.VCard(v_if="simulation_has_run", classes="mb-4"):
190
- vuetify3.VCardSubtitle("Output Configuration", classes="text-primary")
191
- with vuetify3.VCardText():
192
- with vuetify3.VRadioGroup(v_model=("output_type", "Surface Plot"), row=True, density="compact", color="primary"):
193
- vuetify3.VRadio(label="Surface", value="Surface Plot")
194
- vuetify3.VRadio(label="Line Sampling", value="Line Sampling")
195
 
196
- with vuetify3.VCard(classes="flex-grow-1", style="min-height: 0;"):
197
- with vuetify3.VContainer(v_if="is_running", fluid=True, classes="fill-height d-flex flex-column align-center justify-center"):
198
- vuetify3.VProgressCircular(indeterminate=True, size=64, color="primary")
199
- vuetify3.VCardSubtitle("Running simulation...", classes="mt-4")
200
- with vuetify3.VContainer(v_if="!is_running", fluid=True, classes="fill-height pa-2"):
201
- fig = plotly_widgets.Figure(figure=go.Figure(), responsive=True, style="width: 100%; min-height: 560px;")
202
- ctrl.qlbm_plot_update = fig.update
203
- vuetify3.VContainer(v_if="!simulation_has_run", classes="d-flex align-center justify-center", style="height: 360px; color: rgba(0,0,0,.6);", children=["Configure inputs and run to display results."])
 
1
+ """Embedded QLBM fluids page wrapper.
2
+ Starts the standalone qlbm.py server in a background subprocess so the main
3
+ multi-page app only needs `python app.py`.
4
+
5
+ Environment variables:
6
+ QLBM_APP_PORT / PORT_QLBM -> port (default 8702)
7
+ QLBM_HOST -> host interface (default 127.0.0.1)
8
+ """
9
+ from __future__ import annotations
10
+ import os, sys, subprocess, atexit
11
  from trame_vuetify.widgets import vuetify3
12
  from trame.widgets import html as trame_html
 
 
 
13
 
14
+ _qlbm_proc = None
15
+ _QLBM_HOST = os.environ.get("QLBM_HOST", "127.0.0.1")
16
 
17
 
18
+ def _kill_qlbm_process():
19
+ global _qlbm_proc
20
+ if _qlbm_proc and _qlbm_proc.poll() is None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  try:
22
+ _qlbm_proc.terminate()
23
+ _qlbm_proc.wait(timeout=2)
24
+ except Exception:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  try:
26
+ _qlbm_proc.kill()
27
  except Exception:
28
  pass
29
+ _qlbm_proc = None
 
30
 
 
 
 
 
 
 
31
 
32
+ def _ensure_qlbm_process_started():
33
+ global _qlbm_proc
34
+ if _qlbm_proc and _qlbm_proc.poll() is None:
35
+ return
36
+ _kill_qlbm_process()
37
+ base_dir = os.path.dirname(os.path.dirname(__file__))
38
+ qlbm_path = os.path.join(base_dir, "qlbm.py")
39
+ env = os.environ.copy()
40
+ env.setdefault("QLBM_APP_PORT", env.get("PORT_QLBM", "8702"))
41
+ env.setdefault("QLBM_HOST", _QLBM_HOST)
42
+ py = sys.executable or "python"
43
+ _qlbm_proc = subprocess.Popen([py, qlbm_path], cwd=base_dir, env=env)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
 
 
 
 
 
 
 
45
 
46
+ atexit.register(_kill_qlbm_process)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
 
 
 
 
48
 
49
+ def build(server): # signature matches app.py expectation
50
+ _ensure_qlbm_process_started()
51
+ port = os.environ.get("QLBM_APP_PORT", os.environ.get("PORT_QLBM", "8702"))
52
+ host = os.environ.get("QLBM_HOST", _QLBM_HOST)
53
+ with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
54
+ trame_html.Iframe(
55
+ src=("qlbm_iframe_src", f"http://{host}:{port}/"),
56
+ style="border:0;width:100%;height:calc(100vh - 64px);",
57
+ )
58
+ trame_html.Div(
59
+ "If the QLBM view is blank, wait a few seconds for the subprocess to start.",
60
+ style="color:rgba(0,0,0,.6);padding:6px;",
61
+ )
 
 
62
 
 
 
 
 
 
 
 
 
qlbm.py CHANGED
@@ -1,613 +1,669 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
- import numpy as np
3
  import math
 
 
 
 
 
 
 
 
 
 
 
 
4
  from trame.app import get_server
5
  from trame_vuetify.ui.vuetify3 import SinglePageLayout
6
  from trame_vuetify.widgets import vuetify3
7
  from trame.widgets import html as trame_html
8
  from trame_plotly.widgets import plotly as plotly_widgets
9
- import plotly.graph_objects as go
10
-
11
- # --- Server and State ---
12
- server = get_server(name="qlbm") # kept for standalone use; embedding will rebind to host server
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  state, ctrl = server.state, server.controller
14
 
15
- # --- App State Defaults ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  state.update({
17
- # App/page
18
- "logo_src": None,
19
- "is_running": False,
20
- "simulation_has_run": False,
21
- "error_message": "",
22
- # Overview selection
23
- "problems_selection": None, # Dropdown in Overview (dummy)
24
- # Geometry
25
- "geometry_selection": None, # Free space | Rectangular domain with a heated box (3D)
26
- "domain_L": 1.0,
27
- "domain_W": 1.0,
28
- "domain_H": 1.0, # 3D
29
- # Initial & BC (Rectangular domain)
30
- "dist_type": None, # None | Delta | Gaussian
31
- # Delta/Gaussian positions (copied style from EM)
32
- "impulse_x": 0.5, "impulse_y": 0.5,
33
- "peak_pair": "(0.5, 0.5)",
34
- "mu_x": 0.5, "mu_y": 0.5, "sigma_x": 0.25, "sigma_y": 0.15,
35
- "mu_pair": "(0.5, 0.5)",
36
- "excitation_error_message": "",
37
- # Advection / inlet
38
- "advecting_field": None, # Uniform | Swirl | Shear | TGV
39
- "inlet_velocity": 1.0,
40
- "inlet_temperature": 300.0,
41
- # Meshing
42
- "nx_slider_index": None, # index into grid sizes
43
- "nx": None, # resolved grid points per direction
44
- # Backends (dummy like EM)
45
- "backend_type": "Simulator",
46
- "selected_simulator": "IBM Qiskit simulator",
47
- "selected_qpu": "IBM QPU",
48
- # Outputs
49
- "output_type": "Surface Plot", # Surface Plot | Line Sampling
50
- "qubit_grid_info": "",
51
- "qubit_warning": "",
52
  })
53
 
54
- GRID_SIZES = [16, 32, 64, 128, 256, 512, 1024, 2048]
55
-
56
- # --- Helpers ---
57
- def load_logo_data_uri():
58
- try:
59
- base_dir = os.path.dirname(__file__)
60
- candidates = [
61
- os.path.join(base_dir, "ansys-part-of-synopsys-logo.svg"),
62
- os.path.join(base_dir, "synopsys-logo-color-rgb.svg"),
63
- os.path.join(base_dir, "synopsys-logo-color-rgb.png"),
64
- os.path.join(base_dir, "synopsys-logo-color-rgb.jpg"),
65
- ]
66
- for p in candidates:
67
- if os.path.exists(p):
68
- ext = os.path.splitext(p)[1].lower()
69
- mime = "image/svg+xml" if ext == ".svg" else ("image/png" if ext == ".png" else "image/jpeg")
70
- with open(p, "rb") as f:
71
- import base64
72
- b64 = base64.b64encode(f.read()).decode("ascii")
73
- return f"data:{mime};base64,{b64}"
74
- except Exception:
75
- pass
76
- return None
77
-
78
- state.logo_src = load_logo_data_uri()
79
-
80
- # --- 3D Qubit Info Function ---
81
  def update_qubit_3D_info(grid_size: int):
82
- """Generate 3D qubit requirement plot and info message."""
83
- try:
84
- num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
85
- total_qubits = 3 * num_reg_qubits + 3
86
- grid_display = f"Grid Size: {grid_size} × {grid_size} × {grid_size}"
87
-
88
- x = np.array([16, 32, 64, 128, 256])
89
- y = np.log2(x).astype(int)
90
- fig = go.Figure()
91
- fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction', line=dict(color='#7A3DB5', width=3)))
92
- fig.add_trace(go.Scatter(x=[grid_size], y=[num_reg_qubits], mode='markers',
93
- marker=dict(size=12, color='red'), name='Current Selection'))
94
- fig.update_layout(
95
- xaxis_title="Grid Size (Points/Direction)",
96
- yaxis_title="Qubits/Direction",
97
- width=616,
98
- height=320,
99
- margin=dict(l=40, r=20, t=20, b=40)
100
- )
101
- warning = "⚠️ Warning: Grid sizes > 64 may exceed simulator/memory limits!" if grid_size > 64 else ""
102
- return fig, grid_display, total_qubits, warning
103
- except Exception:
104
- return go.Figure(), "Grid Size: N/A", 0, ""
105
-
106
- # --- Controllers ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  def run_simulation():
108
- state.is_running = True
109
- state.error_message = ""
110
- try:
111
- # Placeholder for compute
112
- state.simulation_has_run = True
113
- except Exception as e:
114
- state.error_message = f"Run failed: {e}"
115
- finally:
116
- state.is_running = False
117
-
118
-
119
- def reset_all():
120
- state.update({
121
- "problems_selection": None,
122
- "geometry_selection": None,
123
- "domain_L": 1.0,
124
- "domain_W": 1.0,
125
- "domain_H": 1.0,
126
- "dist_type": None,
127
- "impulse_x": 0.5,
128
- "impulse_y": 0.5,
129
- "peak_pair": "(0.5, 0.5)",
130
- "mu_x": 0.5,
131
- "mu_y": 0.5,
132
- "sigma_x": 0.25,
133
- "sigma_y": 0.15,
134
- "mu_pair": "(0.5, 0.5)",
135
- "excitation_error_message": "",
136
- "advecting_field": None,
137
- "inlet_velocity": 1.0,
138
- "inlet_temperature": 300.0,
139
- "nx_slider_index": None,
140
- "nx": None,
141
- "output_type": "Surface Plot",
142
- "is_running": False,
143
- "simulation_has_run": False,
144
- "error_message": "",
145
- "backend_type": "Simulator",
146
- "selected_simulator": "IBM Qiskit simulator",
147
- "selected_qpu": "IBM QPU",
148
- "qubit_grid_info": "",
149
- "qubit_warning": "",
150
- })
151
- # No plotting on reset
152
-
153
-
154
- @state.change("nx_slider_index")
155
- def on_nx_index_change(nx_slider_index, **_):
156
- try:
157
- state.nx = GRID_SIZES[int(nx_slider_index)] if nx_slider_index is not None else None
158
- except Exception:
159
- state.nx = None
160
-
161
- # Pair parsing like EM page
162
- @state.change("peak_pair")
163
- def sync_peak_pair(peak_pair, **_):
164
- import re
165
- try:
166
- m = re.match(r"\(\s*([0-9]*\.?[0-9]+)\s*,\s*([0-9]*\.?[0-9]+)\s*\)", str(peak_pair))
167
- if not m:
168
- raise ValueError("Invalid format")
169
- x = max(0.0, min(1.0, float(m.group(1))))
170
- y = max(0.0, min(1.0, float(m.group(2))))
171
- state.impulse_x = x
172
- state.impulse_y = y
173
- state.excitation_error_message = ""
174
- except Exception:
175
- state.excitation_error_message = "Invalid Peak. Use (x, y) in [0,1]."
176
- # No preview plotting
177
-
178
-
179
- @state.change("mu_pair")
180
- def sync_mu_pair(mu_pair, **_):
181
- import re
182
- try:
183
- m = re.match(r"\(\s*([-+]?[0-9]*\.?[0-9]+)\s*,\s*([-+]?[0-9]*\.?[0-9]+)\s*\)", str(mu_pair))
184
- if not m:
185
- raise ValueError("Invalid format")
186
- x = max(0.0, min(1.0, float(m.group(1))))
187
- y = max(0.0, min(1.0, float(m.group(2))))
188
- state.mu_x = x
189
- state.mu_y = y
190
- state.excitation_error_message = ""
191
- except Exception:
192
- state.excitation_error_message = "Invalid Mu. Use (x, y) in [0,1]."
193
- # No preview plotting
194
-
195
-
196
- @state.change("dist_type", "mu_x", "mu_y", "sigma_x", "sigma_y", "impulse_x", "impulse_y", "nx")
197
- def on_preview_params_change(**_):
198
- # No preview plotting on the right side
199
- return
200
-
201
- # --- UI ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  with SinglePageLayout(server) as layout:
203
- layout.title.set_text("QLBM: Lattice Boltzmann")
204
- layout.title.style = "color: #5f259f; font-weight: 600;"
205
- layout.toolbar.classes = "pl-2 pr-1 py-1 elevation-0"
206
- layout.toolbar.style = "background-color: #ffffff; border-bottom: 3px solid #5f259f;"
207
-
208
- # Theme tweaks to match EM page
209
- trame_html.Style(
210
- """
211
- :root{
212
- --v-theme-primary: 95, 37, 159; /* #5F259F */
213
- --v-theme-secondary: 122, 61, 181; /* #7A3DB5 */
214
- --v-theme-accent: 174, 139, 216; /* #AE8BD8 */
215
- --v-theme-surface: 255, 255, 255; /* #FFFFFF */
216
- --v-theme-background: 255, 255, 255; /* #FFFFFF */
217
- --v-theme-on-primary: 255, 255, 255; /* #FFFFFF */
218
- --v-theme-on-surface: 26, 26, 26; /* #1A1A1A */
219
- }
220
- .syn-title{ color:#5f259f !important; }
221
- .syn-border-bottom{ border-bottom:3px solid #5f259f !important; }
222
- .syn-bg-white{ background:#ffffff !important; }
223
- .v-list .v-list-item:hover { background-color: rgba(95,37,159,.08) !important; }
224
- .v-list-item--active { background-color: rgba(95,37,159,.16) !important; color: #5f259f !important; }
225
- """
226
- )
227
-
228
- with layout.toolbar:
229
- vuetify3.VSpacer()
230
- vuetify3.VImg(v_if="logo_src", src=("logo_src", None), style="height: 56px; width: auto; margin-right: 0px;", classes="mr-0")
231
-
232
- with layout.content:
233
- with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
234
- with vuetify3.VRow(no_gutters=True, classes="fill-height"):
235
- # Left column
236
- with vuetify3.VCol(cols=5, classes="pa-4 d-flex flex-column"):
237
- # Cell 1: Overview
238
- with vuetify3.VCard(classes="mb-4"):
239
- vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary")
240
- with vuetify3.VCardText():
241
- vuetify3.VDivider(classes="my-2")
242
- vuetify3.VCardSubtitle("Problems!", classes="text-subtitle-1 font-weight-bold mt-2")
243
- vuetify3.VSelect(
244
- key="overview_problems",
245
- label="Select a problem",
246
- v_model=("problems_selection", None),
247
- items=(
248
- "qlbm_problems",
249
- [
250
- "1. Scalar advection-diffusion in a box",
251
- "2. Laminar flow & heat transfer for a heated body in water.",
252
- ],
253
- ),
254
- placeholder="Select",
255
- density="compact",
256
- color="primary",
257
- )
258
- vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
259
- vuetify3.VListItemTitle("Laminar Navier-Stokes including energy", classes="text-body-2")
260
- vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
261
- vuetify3.VListItemTitle("Geometry, Boundary conditions - temperature and flow", classes="text-body-2")
262
- vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
263
- vuetify3.VListItemTitle("Surface plots on sections OR sampling through a line in 3D domain", classes="text-body-2")
264
-
265
- # Cell 2: Geometry
266
- with vuetify3.VCard(classes="mb-4"):
267
- vuetify3.VCardTitle("Geometry", classes="text-primary")
268
- with vuetify3.VCardText():
269
- vuetify3.VSelect(
270
- key="geom_select",
271
- label="Select",
272
- v_model=("geometry_selection", None),
273
- items=(
274
- "geometry_options",
275
- [
276
- "Free space",
277
- "Rectangular domain with a heated box (3D)",
278
- ],
279
- ),
280
- placeholder="Select",
281
- density="compact",
282
- color="primary",
283
- )
284
- # Domain dimensions - NO DUPLICATES
285
- with vuetify3.VContainer(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="pa-0 mt-2"):
286
- with vuetify3.VRow(dense=True):
287
- vuetify3.VCol(children=[vuetify3.VTextField(key="geom_len", label="Length (L)", v_model=("domain_L", 1.0), type="number", step="0.1", density="compact", color="primary")])
288
- vuetify3.VCol(children=[vuetify3.VTextField(key="geom_breadth", label="Breadth (B)", v_model=("domain_W", 1.0), type="number", step="0.1", density="compact", color="primary")])
289
- vuetify3.VTextField(key="geom_height", label="Height (H)", v_model=("domain_H", 1.0), type="number", step="0.1", density="compact", color="primary")
290
-
291
- # Cell 3: Initial & Boundary Conditions (only when Rectangular selected)
292
- with vuetify3.VCard(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="mb-4"):
293
- vuetify3.VCardTitle("Initial & Boundary Conditions", classes="text-primary")
294
- with vuetify3.VCardText():
295
- # Initial distribution (Gaussian only) - NO DUPLICATES
296
- vuetify3.VTextField(key="gauss_mu", v_model=("mu_pair", "(0.5, 0.5)"), label="Gaussian μ (x, y) in [0,1]", density="compact", color="primary")
297
- with vuetify3.VRow(dense=True, classes="mt-1"):
298
- vuetify3.VCol(children=[vuetify3.VTextField(key="sigma_x", label="Sigma X (0–1)", v_model=("sigma_x", 0.25), type="number", step="0.01", density="compact", color="primary")])
299
- vuetify3.VCol(children=[vuetify3.VTextField(key="sigma_y", label="Sigma Y (0–1)", v_model=("sigma_y", 0.15), type="number", step="0.01", density="compact", color="primary")])
300
- vuetify3.VAlert(v_if="excitation_error_message", type="error", variant="tonal", density="compact", children=["{{ excitation_error_message }}"], classes="mt-2")
301
-
302
- # Advecting field
303
- vuetify3.VSelect(key="adv_field", label="Advecting field", v_model=("advecting_field", None), items=("advect_fields", ["Uniform", "Swirl", "Shear", "TGV"]), density="compact", color="primary", classes="mt-2")
304
-
305
- # Inlet (single set) - NO DUPLICATES
306
- with vuetify3.VRow(dense=True, classes="mt-2"):
307
- vuetify3.VCol(children=[vuetify3.VTextField(key="inlet_vel", label="Inlet flow velocity", v_model=("inlet_velocity", 1.0), type="number", step="0.1", density="compact", color="primary")])
308
- vuetify3.VCol(children=[vuetify3.VTextField(key="inlet_temp", label="Inlet temperature (K)", v_model=("inlet_temperature", 300.0), type="number", step="1", density="compact", color="primary")])
309
-
310
- # Cell 4: Meshing
311
- with vuetify3.VCard(classes="mb-4"):
312
- vuetify3.VCardTitle("Meshing", classes="text-primary")
313
- with vuetify3.VCardText():
314
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=False, location="end"):
315
- with vuetify3.Template(v_slot_activator="{ props }"):
316
- with vuetify3.VSlider(
317
- key="mesh_slider",
318
- v_bind="props",
319
- v_model=("nx_slider_index", None),
320
- label="No. of points per direction:",
321
- min=0,
322
- max=len(GRID_SIZES) - 1,
323
- step=1,
324
- show_ticks="always",
325
- thumb_label="always",
326
- density="compact",
327
- color="primary",
328
- ):
329
- # Keep labels in sync with GRID_SIZES
330
- vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ modelValue === null ? 'Select' : [16, 32, 64, 128, 256, 512, 1024, 2048][modelValue] }}"])
331
- with vuetify3.VSheet(classes="pa-2", elevation=6, rounded=True, style="width: 700px;"):
332
- with vuetify3.VContainer(fluid=True, classes="pa-0"):
333
- qubit_fig_widget = plotly_widgets.Figure(
334
- figure=go.Figure(),
335
- responsive=True,
336
- style="width: 616px; height: 320px; min-height: 320px;",
337
- )
338
- vuetify3.VCardText(children=["{{ qubit_grid_info }}", "{{ qubit_warning }}"], classes="text-caption mt-2")
339
-
340
- # Cell 5: Backends (dummy like EM)
341
- with vuetify3.VCard(classes="mb-4"):
342
- vuetify3.VCardTitle("Backends", classes="text-primary")
343
- with vuetify3.VCardText():
344
- with vuetify3.VRow(dense=True, classes="mb-2"):
345
- with vuetify3.VCol():
346
- vuetify3.VAlert(
347
- type="info",
348
- color="primary",
349
- variant="tonal",
350
- density="compact",
351
- children=[
352
- "Selected: ",
353
- "{{ backend_type || '—' }}",
354
- " - ",
355
- "{{ backend_type === 'Simulator' ? selected_simulator : (backend_type === 'QPU' ? selected_qpu : '—') }}",
356
- ],
357
- )
358
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
359
- with vuetify3.Template(v_slot_activator="{ props }"):
360
- vuetify3.VBtn(v_bind="props", text="Choose Backend", color="primary", variant="tonal", block=True)
361
- with vuetify3.VList(density="compact"):
362
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
363
- with vuetify3.Template(v_slot_activator="{ props }"):
364
- vuetify3.VListItem(v_bind="props", title="Simulator", prepend_icon="mdi-robot-outline", append_icon="mdi-chevron-right")
365
- with vuetify3.VList(density="compact"):
366
- vuetify3.VListItem(title="IBM Qiskit simulator", click="backend_type = 'Simulator'; selected_simulator = 'IBM Qiskit simulator'")
367
- vuetify3.VListItem(title="IonQ simulator", click="backend_type = 'Simulator'; selected_simulator = 'IonQ simulator'")
368
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
369
- with vuetify3.Template(v_slot_activator="{ props }"):
370
- vuetify3.VListItem(v_bind="props", title="QPU", prepend_icon="mdi-chip", append_icon="mdi-chevron-right")
371
- with vuetify3.VList(density="compact"):
372
- vuetify3.VListItem(title="IBM QPU", click="backend_type = 'QPU'; selected_qpu = 'IBM QPU'")
373
- vuetify3.VListItem(title="IonQ QPU", click="backend_type = 'QPU'; selected_qpu = 'IonQ QPU'")
374
-
375
- # Actions - SINGLE ROW WITH TWO BUTTONS
376
- with vuetify3.VRow(dense=True, classes="gap-2 mt-4"):
377
- with vuetify3.VCol():
378
- vuetify3.VBtn(
379
- text="Run Simulation",
380
- color="primary",
381
- block=True,
382
- disabled=("is_running || !geometry_selection || (geometry_selection === 'Rectangular domain with a heated box (3D)' && nx === null)", False),
383
- click=run_simulation
384
- )
385
- with vuetify3.VCol():
386
- vuetify3.VBtn(
387
- text="Reset",
388
- color="secondary",
389
- variant="tonal",
390
- block=True,
391
- click=reset_all
392
- )
393
-
394
- # Right column: Output area
395
- with vuetify3.VCol(cols=7, classes="pa-4 d-flex flex-column"):
396
- with vuetify3.VContainer(fluid=True, classes="fill-height d-flex align-center justify-center"):
397
- vuetify3.VCardText("Select a geometry and configure inputs to display results.", classes="text-center text-medium-emphasis")
398
-
399
- # Update qubit hover plot when nx changes
400
- ctrl.qubit_plot_update = qubit_fig_widget.update
401
-
402
- @state.change("nx")
403
- def _update_qubit_plot(nx, **_):
404
- try:
405
- fig = None
406
- if nx is None:
407
- state.qubit_grid_info = ""
408
- state.qubit_warning = ""
409
- else:
410
- fig, grid_info, total_qubits, warning = update_qubit_3D_info(nx)
411
- state.qubit_grid_info = grid_info
412
- state.qubit_warning = warning
413
- if hasattr(ctrl, 'qubit_plot_update') and fig is not None:
414
- ctrl.qubit_plot_update(fig)
415
- except Exception:
416
- pass
417
-
418
- # --- Entry point helpers for embedding ---
419
- _q_lb_started = False
 
 
 
420
 
421
  def build(host_server):
422
- """Embed QLBM UI directly (no iframe, no separate server)."""
423
- global state, ctrl, _q_lb_started, qubit_fig_widget
424
- state, ctrl = host_server.state, host_server.controller
425
-
426
- # Initialize defaults only if missing (avoid overwriting existing host state)
427
- required_keys = ["is_running","simulation_has_run","qubit_grid_info","qubit_warning"]
428
- if any(not hasattr(state, k) for k in required_keys):
429
- state.update({
430
- "logo_src": getattr(state, "logo_src", None),
431
- "is_running": False,
432
- "simulation_has_run": False,
433
- "error_message": "",
434
- "problems_selection": None,
435
- "geometry_selection": None,
436
- "domain_L": 1.0,
437
- "domain_W": 1.0,
438
- "domain_H": 1.0,
439
- "dist_type": None,
440
- "impulse_x": 0.5, "impulse_y": 0.5, "peak_pair": "(0.5, 0.5)",
441
- "mu_x": 0.5, "mu_y": 0.5, "sigma_x": 0.25, "sigma_y": 0.15, "mu_pair": "(0.5, 0.5)",
442
- "excitation_error_message": "",
443
- "advecting_field": None,
444
- "inlet_velocity": 1.0,
445
- "inlet_temperature": 300.0,
446
- "nx_slider_index": None,
447
- "nx": None,
448
- "backend_type": "Simulator",
449
- "selected_simulator": "IBM Qiskit simulator",
450
- "selected_qpu": "IBM QPU",
451
- "output_type": "Surface Plot",
452
- "qubit_grid_info": "",
453
- "qubit_warning": "",
454
- })
455
-
456
- # UI (previously inside SinglePageLayout.content)
457
- with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
458
- with vuetify3.VRow(no_gutters=True, classes="fill-height"):
459
- # Left column
460
- with vuetify3.VCol(cols=5, classes="pa-4 d-flex flex-column"):
461
- # Overview
462
- with vuetify3.VCard(classes="mb-4"):
463
- vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary")
464
- with vuetify3.VCardText():
465
- vuetify3.VDivider(classes="my-2")
466
- vuetify3.VCardSubtitle("Problems!", classes="text-subtitle-1 font-weight-bold mt-2")
467
- vuetify3.VSelect(
468
- key="overview_problems",
469
- label="Select a problem",
470
- v_model=("problems_selection", None),
471
- items=("qlbm_problems", [
472
- "1. Scalar advection-diffusion in a box",
473
- "2. Laminar flow & heat transfer for a heated body in water.",
474
- ]),
475
- placeholder="Select",
476
- density="compact",
477
- color="primary",
478
- )
479
- vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
480
- vuetify3.VListItemTitle("Laminar Navier-Stokes including energy", classes="text-body-2")
481
- vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
482
- vuetify3.VListItemTitle("Geometry, Boundary conditions - temperature and flow", classes="text-body-2")
483
- vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
484
- vuetify3.VListItemTitle("Surface plots on sections OR sampling through a line in 3D domain", classes="text-body-2")
485
- # Geometry
486
- with vuetify3.VCard(classes="mb-4"):
487
- vuetify3.VCardTitle("Geometry", classes="text-primary")
488
- with vuetify3.VCardText():
489
- vuetify3.VSelect(
490
- key="geom_select",
491
- label="Select",
492
- v_model=("geometry_selection", None),
493
- items=("geometry_options", [
494
- "Free space",
495
- "Rectangular domain with a heated box (3D)",
496
- ]),
497
- placeholder="Select",
498
- density="compact",
499
- color="primary",
500
- )
501
- with vuetify3.VContainer(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="pa-0 mt-2"):
502
- with vuetify3.VRow(dense=True):
503
- vuetify3.VCol(children=[vuetify3.VTextField(key="geom_len", label="Length (L)", v_model=("domain_L", 1.0), type="number", step="0.1", density="compact", color="primary")])
504
- vuetify3.VCol(children=[vuetify3.VTextField(key="geom_breadth", label="Breadth (B)", v_model=("domain_W", 1.0), type="number", step="0.1", density="compact", color="primary")])
505
- vuetify3.VTextField(key="geom_height", label="Height (H)", v_model=("domain_H", 1.0), type="number", step="0.1", density="compact", color="primary")
506
- # Initial & BC
507
- with vuetify3.VCard(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="mb-4"):
508
- vuetify3.VCardTitle("Initial & Boundary Conditions", classes="text-primary")
509
- with vuetify3.VCardText():
510
- vuetify3.VTextField(key="gauss_mu", v_model=("mu_pair", "(0.5, 0.5)"), label="Gaussian μ (x, y) in [0,1]", density="compact", color="primary")
511
- with vuetify3.VRow(dense=True, classes="mt-1"):
512
- vuetify3.VCol(children=[vuetify3.VTextField(key="sigma_x", label="Sigma X (0–1)", v_model=("sigma_x", 0.25), type="number", step="0.01", density="compact", color="primary")])
513
- vuetify3.VCol(children=[vuetify3.VTextField(key="sigma_y", label="Sigma Y (0–1)", v_model=("sigma_y", 0.15), type="number", step="0.01", density="compact", color="primary")])
514
- vuetify3.VAlert(v_if="excitation_error_message", type="error", variant="tonal", density="compact", children=["{{ excitation_error_message }}"], classes="mt-2")
515
- vuetify3.VSelect(key="adv_field", label="Advecting field", v_model=("advecting_field", None), items=("advect_fields", ["Uniform", "Swirl", "Shear", "TGV"]), density="compact", color="primary", classes="mt-2")
516
- with vuetify3.VRow(dense=True, classes="mt-2"):
517
- vuetify3.VCol(children=[vuetify3.VTextField(key="inlet_vel", label="Inlet flow velocity", v_model=("inlet_velocity", 1.0), type="number", step="0.1", density="compact", color="primary")])
518
- vuetify3.VCol(children=[vuetify3.VTextField(key="inlet_temp", label="Inlet temperature (K)", v_model=("inlet_temperature", 300.0), type="number", step="1", density="compact", color="primary")])
519
- # Meshing
520
- with vuetify3.VCard(classes="mb-4"):
521
- vuetify3.VCardTitle("Meshing", classes="text-primary")
522
- with vuetify3.VCardText():
523
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=False, location="end"):
524
- with vuetify3.Template(v_slot_activator="{ props }"):
525
- with vuetify3.VSlider(
526
- key="mesh_slider",
527
- v_bind="props",
528
- v_model=("nx_slider_index", None),
529
- label="No. of points per direction:",
530
- min=0,
531
- max=len(GRID_SIZES) - 1,
532
- step=1,
533
- show_ticks="always",
534
- thumb_label="always",
535
- density="compact",
536
- color="primary",
537
- ):
538
- vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ modelValue === null ? 'Select' : [16, 32, 64, 128, 256, 512, 1024, 2048][modelValue] }}"])
539
- with vuetify3.VSheet(classes="pa-2", elevation=6, rounded=True, style="width: 700px;"):
540
- with vuetify3.VContainer(fluid=True, classes="pa-0"):
541
- qubit_fig_widget = plotly_widgets.Figure(
542
- figure=go.Figure(),
543
- responsive=True,
544
- style="width: 616px; height: 320px; min-height: 320px;",
545
- )
546
- vuetify3.VCardText(children=["{{ qubit_grid_info }}", "{{ qubit_warning }}"], classes="text-caption mt-2")
547
- # Backends
548
- with vuetify3.VCard(classes="mb-4"):
549
- vuetify3.VCardTitle("Backends", classes="text-primary")
550
- with vuetify3.VCardText():
551
- with vuetify3.VRow(dense=True, classes="mb-2"):
552
- with vuetify3.VCol():
553
- vuetify3.VAlert(
554
- type="info",
555
- color="primary",
556
- variant="tonal",
557
- density="compact",
558
- children=[
559
- "Selected: ",
560
- "{{ backend_type || '—' }}",
561
- " - ",
562
- "{{ backend_type === 'Simulator' ? selected_simulator : (backend_type === 'QPU' ? selected_qpu : '—') }}",
563
- ],
564
- )
565
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
566
- with vuetify3.Template(v_slot_activator="{ props }"):
567
- vuetify3.VBtn(v_bind="props", text="Choose Backend", color="primary", variant="tonal", block=True)
568
- with vuetify3.VList(density="compact"):
569
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
570
- with vuetify3.Template(v_slot_activator="{ props }"):
571
- vuetify3.VListItem(v_bind="props", title="Simulator", prepend_icon="mdi-robot-outline", append_icon="mdi-chevron-right")
572
- with vuetify3.VList(density="compact"):
573
- vuetify3.VListItem(title="IBM Qiskit simulator", click="backend_type = 'Simulator'; selected_simulator = 'IBM Qiskit simulator'")
574
- vuetify3.VListItem(title="IonQ simulator", click="backend_type = 'Simulator'; selected_simulator = 'IonQ simulator'")
575
- with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
576
- with vuetify3.Template(v_slot_activator="{ props }"):
577
- vuetify3.VListItem(v_bind="props", title="QPU", prepend_icon="mdi-chip", append_icon="mdi-chevron-right")
578
- with vuetify3.VList(density="compact"):
579
- vuetify3.VListItem(title="IBM QPU", click="backend_type = 'QPU'; selected_qpu = 'IBM QPU'")
580
- vuetify3.VListItem(title="IonQ QPU", click="backend_type = 'QPU'; selected_qpu = 'IonQ QPU'")
581
- with vuetify3.VRow(dense=True, classes="gap-2 mt-4"):
582
- with vuetify3.VCol():
583
- vuetify3.VBtn(
584
- text="Run Simulation",
585
- color="primary",
586
- block=True,
587
- disabled=("is_running || !geometry_selection || (geometry_selection === 'Rectangular domain with a heated box (3D)' && nx === null)", False),
588
- click=run_simulation
589
- )
590
- with vuetify3.VCol():
591
- vuetify3.VBtn(
592
- text="Reset",
593
- color="secondary",
594
- variant="tonal",
595
- block=True,
596
- click=reset_all
597
- )
598
- # Right column
599
- with vuetify3.VCol(cols=7, classes="pa-4 d-flex flex-column"):
600
- with vuetify3.VContainer(fluid=True, classes="fill-height d-flex align-center justify-center"):
601
- vuetify3.VCardText("Select a geometry and configure inputs to display results.", classes="text-center text-medium-emphasis")
602
-
603
- # Register qubit figure update hook
604
- ctrl.qubit_plot_update = qubit_fig_widget.update
605
-
606
- # Re-register state change callbacks on host server
607
- host_server.state.change("nx")(_update_qubit_plot)
608
- host_server.state.change("peak_pair")(sync_peak_pair)
609
- host_server.state.change("mu_pair")(sync_mu_pair)
610
- host_server.state.change("dist_type", "mu_x", "mu_y", "sigma_x", "sigma_y", "impulse_x", "impulse_y", "nx")(on_preview_params_change)
611
 
612
  # --- Entry point ---
613
  if __name__ == "__main__":
 
1
+ """trame3d.py
2
+ An equivalent Trame application replicating the functionality of `gradio3d.py`.
3
+
4
+ Features ported from Gradio version:
5
+ - Example initial distributions (Sinusoidal / Gaussian) with buttons to load presets
6
+ - Simulation parameter controls (grid size slider, time steps slider)
7
+ - Dynamic qubit requirement plot vs. grid size (log2 relation)
8
+ - Velocity field presets (Uniform / Swirl / Shear / TGV) updating velocity expression text boxes
9
+ - Custom velocity expression inputs parsed via SymPy (x, y, z)
10
+ - Initial distribution type radio (Gaussian / Sinusoidal)
11
+ - Boundary condition radio (Periodic / Dirichlet / Neumann)
12
+ - Run simulation button producing a mock 3D Plotly scatter visualization
13
+ - Download JSON frames button (zip of Plotly figure JSON) enabled post-run
14
+
15
+ This is a standalone Trame app and can also be embedded via the `build(host_server)` helper.
16
+ """
17
+
18
  import os
 
19
  import math
20
+ import tempfile
21
+ import zipfile
22
+ import numpy as np
23
+ import plotly.graph_objects as go
24
+ import importlib
25
+ import sys
26
+ import types
27
+ import time
28
+ import multiprocessing as mp
29
+ import plotly.io as pio
30
+ symbols = sympify = lambdify = None # Deferred SymPy import handled at runtime
31
+
32
  from trame.app import get_server
33
  from trame_vuetify.ui.vuetify3 import SinglePageLayout
34
  from trame_vuetify.widgets import vuetify3
35
  from trame.widgets import html as trame_html
36
  from trame_plotly.widgets import plotly as plotly_widgets
37
+
38
+
39
+ # Local simulation module (lazy with optional shim for gradio_litmodel3d)
40
+ qfluid = None
41
+
42
+ def _ensure_qfluid_import():
43
+ """Attempt to import fluid.py as qfluid.
44
+ If gradio_litmodel3d is missing, temporarily stub it to avoid import failure.
45
+ Return imported module or None.
46
+ """
47
+ global qfluid
48
+ if qfluid is not None:
49
+ return qfluid
50
+ # Try plain import first
51
+ try:
52
+ import fluid as _qf
53
+ qfluid = _qf
54
+ return qfluid
55
+ except Exception:
56
+ pass
57
+ # Retry with a lightweight stub for gradio_litmodel3d
58
+ if 'gradio_litmodel3d' not in sys.modules:
59
+ sys.modules['gradio_litmodel3d'] = types.ModuleType('gradio_litmodel3d')
60
+ try:
61
+ import importlib as _il
62
+ qfluid = _il.import_module('fluid3d_pyvista')
63
+ return qfluid
64
+ except Exception:
65
+ return None
66
+
67
+ server = get_server(name="trame3d")
68
  state, ctrl = server.state, server.controller
69
 
70
+ # Register static assets (PNG previews)
71
+ _assets_dir = os.path.join(os.path.dirname(__file__), "Placeholder_Images")
72
+ if os.path.isdir(_assets_dir):
73
+ try:
74
+ server.assets.add(path=_assets_dir, prefix="assets")
75
+ except Exception:
76
+ pass
77
+
78
+ # Throttle settings for live plot updates
79
+ _last_qubit_update = 0.0
80
+ _qubit_update_min_interval = 0.05 # seconds
81
+
82
+ # --------------------------------------------------------------------------------------
83
+ # State Defaults
84
+ # --------------------------------------------------------------------------------------
85
  state.update({
86
+ # Core inputs
87
+ "grid_size": 32,
88
+ "grid_index": 2, # 0..5 -> [8,16,32,64,128,256]
89
+ "time_steps": 100,
90
+ "distribution_type": "Sinusoidal", # Sinusoidal | Gaussian
91
+ "vx_expr": "0.2",
92
+ "vy_expr": "-0.15",
93
+ "vz_expr": "0.3",
94
+ "boundary_condition": "Periodic", # Periodic | Dirichlet | Neumann
95
+ # Velocity preset selection
96
+ "advecting_field": "Uniform", # Uniform | Swirl | Shear | TGV
97
+ # Simulation / outputs
98
+ "is_running": False,
99
+ "run_error": "",
100
+ "has_frames": False,
101
+ "download_ready": False,
102
+ "download_path": None,
103
+ # Qubit plot info
104
+ "qubit_grid_info": "Grid Size: 32 × 32 × 32",
105
+ "qubit_warning": "",
106
+ # Overview / Backends (ported from qlbm)
107
+ "problems_selection": None,
108
+ "backend_type": "Simulator",
109
+ "selected_simulator": "IBM Qiskit simulator",
110
+ "selected_qpu": "IBM QPU",
111
+ # Geometry (for left-side Geometry cell)
112
+ "geometry_selection": None,
113
+ "domain_L": 1.0,
114
+ "domain_W": 1.0,
115
+ "domain_H": 1.0,
 
 
 
 
 
116
  })
117
 
118
+ # --------------------------------------------------------------------------------------
119
+ # Helpers
120
+ # --------------------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  def update_qubit_3D_info(grid_size: int):
122
+ """Generate qubit requirement plot and info strings.
123
+ For a cubic grid: qubits/direction = log2(grid_size).
124
+ """
125
+ try:
126
+ num_reg_qubits = int(math.log2(grid_size)) if grid_size > 0 else 3
127
+ x = np.array([16, 32, 64, 128, 256])
128
+ y = np.log2(x).astype(int)
129
+ fig = go.Figure()
130
+ fig.add_trace(go.Scatter(x=x, y=y, mode='lines', name='Qubits/Direction', line=dict(color='#7A3DB5', width=3)))
131
+ fig.add_trace(go.Scatter(x=[grid_size], y=[num_reg_qubits], mode='markers',
132
+ marker=dict(size=12, color='red'), name='Current Selection'))
133
+ fig.update_layout(
134
+ xaxis_title="Grid Size (Points/Direction)",
135
+ yaxis_title="Qubits/Direction",
136
+ width=616,
137
+ height=320,
138
+ margin=dict(l=40, r=20, t=20, b=40)
139
+ )
140
+ grid_display = f"Grid Size: {grid_size} × {grid_size} × {grid_size}"
141
+ warning = "⚠️ Warning: Grid sizes > 64 may exceed simulator/memory limits!" if grid_size > 64 else ""
142
+ return fig, grid_display, warning
143
+ except Exception:
144
+ return go.Figure(), "Grid Size: N/A", ""
145
+
146
+
147
+ def _mock_simulation(num_reg_qubits, time_steps, distribution_type, vx, vy, vz, boundary_condition):
148
+ """Produce a mock 3D scatter figure and JSON frames (placeholder)."""
149
+ # Random 3D scatter sized by 50 points.
150
+ x, y, z = np.random.rand(3, 50)
151
+ fig = go.Figure(data=[go.Scatter3d(
152
+ x=x, y=y, z=z,
153
+ mode='markers',
154
+ marker=dict(size=5, color=z, colorscale='Viridis', opacity=0.85),
155
+ text=[f"({xi:.2f},{yi:.2f},{zi:.2f})" for xi, yi, zi in zip(x, y, z)],
156
+ hovertemplate="<b>Point</b><br>X=%{x:.2f}<br>Y=%{y:.2f}<br>Z=%{z:.2f}<extra></extra>",
157
+ )])
158
+ fig.update_layout(
159
+ title=f"Mock 3D Simulation Result ({distribution_type})",
160
+ scene=dict(xaxis_title='X', yaxis_title='Y', zaxis_title='Z'),
161
+ margin=dict(l=0, r=0, t=40, b=0),
162
+ showlegend=False,
163
+ )
164
+ return fig, [fig.to_json()]
165
+
166
+
167
+ def create_zip_from_frames(frames_json_list):
168
+ """Create a temporary zip archive containing frame JSON strings."""
169
+ if not frames_json_list:
170
+ return None
171
+ tmp = tempfile.NamedTemporaryFile(suffix="_frames.zip", delete=False)
172
+ tmp.close()
173
+ with zipfile.ZipFile(tmp.name, 'w', zipfile.ZIP_DEFLATED) as zf:
174
+ for i, frame_json in enumerate(frames_json_list):
175
+ zf.writestr(f"frame_{i}.json", frame_json)
176
+ return tmp.name
177
+
178
+
179
+ def _ensure_sympy_import():
180
+ global symbols, sympify, lambdify
181
+ if symbols is None or sympify is None or lambdify is None:
182
+ try:
183
+ sym = importlib.import_module("sympy")
184
+ symbols = sym.symbols
185
+ sympify = sym.sympify
186
+ lambdify = sym.lambdify
187
+ except Exception:
188
+ symbols = sympify = lambdify = None
189
+
190
+
191
+ def _build_velocity_functions(vx_expr: str, vy_expr: str, vz_expr: str):
192
+ """Return callables vx(x,y,z), vy(x,y,z), vz(x,y,z) from user expressions.
193
+ Fallback to constants if SymPy is unavailable or parsing fails."""
194
+ _ensure_sympy_import()
195
+ if symbols and sympify and lambdify:
196
+ x_sym, y_sym, z_sym = symbols('x y z')
197
+ try:
198
+ vx_sym = sympify(vx_expr)
199
+ vy_sym = sympify(vy_expr)
200
+ vz_sym = sympify(vz_expr)
201
+ vx_fn = lambdify((x_sym, y_sym, z_sym), vx_sym, modules="numpy")
202
+ vy_fn = lambdify((x_sym, y_sym, z_sym), vy_sym, modules="numpy")
203
+ vz_fn = lambdify((x_sym, y_sym, z_sym), vz_sym, modules="numpy")
204
+ return vx_fn, vy_fn, vz_fn
205
+ except Exception:
206
+ pass
207
+ # Fallback to constant-valued functions
208
+ try:
209
+ cx = float(vx_expr)
210
+ except Exception:
211
+ cx = 0.0
212
+ try:
213
+ cy = float(vy_expr)
214
+ except Exception:
215
+ cy = 0.0
216
+ try:
217
+ cz = float(vz_expr)
218
+ except Exception:
219
+ cz = 0.0
220
+ return (
221
+ lambda x, y, z, _cx=cx: _cx,
222
+ lambda x, y, z, _cy=cy: _cy,
223
+ lambda x, y, z, _cz=cz: _cz,
224
+ )
225
+
226
+
227
+
228
+
229
+
230
+ # --------------------------------------------------------------------------------------
231
+ # Simulation Controller
232
+ # --------------------------------------------------------------------------------------
233
  def run_simulation():
234
+ if state.is_running:
235
+ return
236
+ state.is_running = True
237
+ state.run_error = ""
238
+ state.download_ready = False
239
+ state.download_path = None
240
+ try:
241
+ # Determine qubits from grid size
242
+ num_reg_qubits = int(math.log2(state.grid_size)) if state.grid_size and state.grid_size > 0 else 3
243
+ fig = None
244
+ frames = []
245
+ try:
246
+ # Lazy import like trial.py (single-process behavior)
247
+ fluid = importlib.import_module('fluid')
248
+ # Build constant velocity functions to avoid SymPy in critical path
249
+ def _const(s, d=0.0):
250
+ try:
251
+ return float(s)
252
+ except Exception:
253
+ return d
254
+ cx, cy, cz = _const(state.vx_expr), _const(state.vy_expr), _const(state.vz_expr)
255
+ vx = lambda x, y, z, _cx=cx: _cx
256
+ vy = lambda x, y, z, _cy=cy: _cy
257
+ vz = lambda x, y, z, _cz=cz: _cz
258
+ fig, frames = fluid.simulate_qlbm_3D_and_animate(
259
+ num_reg_qubits=num_reg_qubits,
260
+ T=state.time_steps,
261
+ distribution_type=state.distribution_type,
262
+ vx_input=vx,
263
+ vy_input=vy,
264
+ vz_input=vz,
265
+ boundary_condition=state.boundary_condition,
266
+ )
267
+ except Exception as e:
268
+ state.run_error = f"Simulation failed: {e}. Falling back to mock visualization."
269
+ if fig is None:
270
+ fig, frames = _mock_simulation(
271
+ num_reg_qubits=num_reg_qubits,
272
+ time_steps=state.time_steps,
273
+ distribution_type=state.distribution_type,
274
+ vx=state.vx_expr,
275
+ vy=state.vy_expr,
276
+ vz=state.vz_expr,
277
+ boundary_condition=state.boundary_condition,
278
+ )
279
+ if fig is None:
280
+ state.run_error = "Simulation returned no figure. Check GPU/driver and parameters."
281
+ return
282
+ if hasattr(ctrl, "sim_plot_update"):
283
+ ctrl.sim_plot_update(fig)
284
+ # Prepare frames for download (if provided)
285
+ state.has_frames = bool(frames)
286
+ state._frames_cache = frames or [] # private cache
287
+ # Enable download button
288
+ state.download_ready = bool(frames)
289
+ except Exception as e:
290
+ state.run_error = f"Simulation failed: {e}"
291
+ finally:
292
+ state.is_running = False
293
+
294
+
295
+ def download_frames():
296
+ if not state.has_frames:
297
+ return
298
+ path = create_zip_from_frames(getattr(state, "_frames_cache", []))
299
+ if path:
300
+ state.download_path = path
301
+ # Trigger file download
302
+ return server.file(path, "plotly_frames.zip")
303
+
304
+
305
+ def download_sinusoidal_stl():
306
+ path = os.path.abspath(os.path.join(os.path.dirname(__file__), "Placeholder_Images", "sinusoidal_surface.stl"))
307
+ if os.path.exists(path):
308
+ return server.file(path, filename="sinusoidal_surface.stl")
309
+
310
+
311
+ def download_gaussian_stl():
312
+ path = os.path.abspath(os.path.join(os.path.dirname(__file__), "Placeholder_Images", "gaussian_surface.stl"))
313
+ if os.path.exists(path):
314
+ return server.file(path, filename="gaussian_surface.stl")
315
+
316
+
317
+ def set_example(dist_type):
318
+ """Load an example initial distribution configuration."""
319
+ state.grid_size = 32
320
+ state.time_steps = 100
321
+ state.distribution_type = dist_type
322
+ state.vx_expr = "0.2"
323
+ state.vy_expr = "-0.15"
324
+ state.vz_expr = "0.3"
325
+ state.boundary_condition = "Periodic"
326
+
327
+
328
+ def set_velocity_preset(preset_name):
329
+ """Map velocity preset buttons to expression triplets."""
330
+ mapping = {
331
+ "Uniform": ("0.2", "-0.15", "0.3"),
332
+ "Swirl": ("0.3*sin(-2*pi*z)", "0.2", "0.3*sin(2*pi*x)"),
333
+ "Shear": ("abs(z-0.5)*1.2-0.3", "0", "0"),
334
+ "TGV": ("0.15*cos(2*pi*x)*sin(2*pi*y)*sin(2*pi*z)", "-0.3*sin(2*pi*x)*cos(2*pi*y)*sin(2*pi*z)", "0.15*sin(2*pi*x)*sin(2*pi*y)*cos(2*pi*z)"),
335
+ }
336
+ vx, vy, vz = mapping.get(preset_name, mapping["Uniform"])
337
+ state.advecting_field = preset_name
338
+ state.vx_expr = vx
339
+ state.vy_expr = vy
340
+ state.vz_expr = vz
341
+
342
+ @state.change("advecting_field")
343
+ def _on_advect_dropdown_change(advecting_field, **_):
344
+ if advecting_field:
345
+ set_velocity_preset(advecting_field)
346
+
347
+
348
+ # --------------------------------------------------------------------------------------
349
+ # Reactive updates
350
+ # --------------------------------------------------------------------------------------
351
+ @state.change("grid_size")
352
+ def _on_grid_size_change(grid_size, **_):
353
+ try:
354
+ # Restrict to powers of two in [8, 16, 32, 64, 128, 256]
355
+ allowed = [8, 16, 32, 64, 128, 256]
356
+ if grid_size not in allowed:
357
+ # snap to nearest allowed value
358
+ nearest = min(allowed, key=lambda v: abs(v - (grid_size or 0)))
359
+ state.grid_size = nearest
360
+ return
361
+ # Throttle frequent updates while dragging
362
+ global _last_qubit_update
363
+ now = time.time()
364
+ if (now - _last_qubit_update) < _qubit_update_min_interval:
365
+ return
366
+ _last_qubit_update = now
367
+ fig, info, warn = update_qubit_3D_info(grid_size)
368
+ state.qubit_grid_info = info
369
+ state.qubit_warning = warn
370
+ if hasattr(ctrl, "qubit_plot_update"):
371
+ ctrl.qubit_plot_update(fig)
372
+ except Exception:
373
+ pass
374
+
375
+
376
+ @state.change("boundary_condition")
377
+ def _force_periodic(boundary_condition, **_):
378
+ """Ensure boundary condition remains 'Periodic' regardless of user selection."""
379
+ try:
380
+ if boundary_condition != "Periodic":
381
+ state.boundary_condition = "Periodic"
382
+ except Exception:
383
+ state.boundary_condition = "Periodic"
384
+
385
+
386
+ @state.change("problems_selection")
387
+ def _on_problem_selection_change(problems_selection, **_):
388
+ """Auto-select geometry based on the chosen problem and show read-only."""
389
+ try:
390
+ if not problems_selection:
391
+ state.geometry_selection = None
392
+ return
393
+ if isinstance(problems_selection, str) and problems_selection.startswith("1."):
394
+ # Scalar advection-diffusion in a box
395
+ state.geometry_selection = "Cube"
396
+ elif isinstance(problems_selection, str) and problems_selection.startswith("2."):
397
+ # Laminar flow & heat transfer for a heated body in water.
398
+ state.geometry_selection = "Rectangular domain with a heated box (3D)"
399
+ else:
400
+ state.geometry_selection = None
401
+ except Exception:
402
+ state.geometry_selection = None
403
+
404
+
405
+ @state.change("grid_index")
406
+ def _on_grid_index_change(grid_index, **_):
407
+ """Map discrete slider index to allowed grid sizes [8,16,32,64,128,256]."""
408
+ try:
409
+ allowed = [8, 16, 32, 64, 128, 256]
410
+ if grid_index is None:
411
+ return
412
+ if isinstance(grid_index, (int, float)):
413
+ idx = int(grid_index)
414
+ idx = max(0, min(idx, len(allowed) - 1))
415
+ val = allowed[idx]
416
+ if state.grid_size != val:
417
+ state.grid_size = val
418
+ except Exception:
419
+ pass
420
+
421
+
422
+ # --------------------------------------------------------------------------------------
423
+ # UI Construction
424
+ # --------------------------------------------------------------------------------------
425
  with SinglePageLayout(server) as layout:
426
+ # Remove top toolbar/title per request
427
+ layout.toolbar.style = "display:none" # Hide toolbar entirely
428
+
429
+ trame_html.Style("""
430
+ :root{ --v-theme-primary:95,37,159; }
431
+ .example-img{ max-width:100%; border-radius:4px; }
432
+ .warn-text{ color:#b71c1c; font-size:0.85rem; }
433
+ """)
434
+
435
+ # No toolbar content
436
+
437
+ with layout.content:
438
+ with vuetify3.VContainer(fluid=True, classes="pa-4"):
439
+ # Row 1: Overview + Geometry + Examples (left) and Visualization (right)
440
+ with vuetify3.VRow(classes="mb-4"):
441
+ # Left column (Overview, Geometry, Examples)
442
+ with vuetify3.VCol(cols=5):
443
+ # Overview cell (from qlbm)
444
+ with vuetify3.VCard(classes="mb-4"):
445
+ vuetify3.VCardTitle("Overview", classes="text-h5 font-weight-bold text-primary")
446
+ with vuetify3.VCardText():
447
+ vuetify3.VDivider(classes="my-2")
448
+ vuetify3.VCardSubtitle("Problems", classes="text-subtitle-1 font-weight-bold mt-2")
449
+ vuetify3.VSelect(
450
+ key="overview_problems",
451
+ label="Select a problem",
452
+ v_model=("problems_selection", None),
453
+ items=(
454
+ "qlbm_problems",
455
+ [
456
+ "1. Scalar advection-diffusion in a box",
457
+ "2. Laminar flow & heat transfer for a heated body in water.",
458
+ ],
459
+ ),
460
+ placeholder="Select",
461
+ density="compact",
462
+ color="primary",
463
+ )
464
+ vuetify3.VCardSubtitle("Governing Equations", classes="text-subtitle-1 font-weight-bold mt-2")
465
+ vuetify3.VListItemTitle("Laminar Navier-Stokes including energy", classes="text-body-2")
466
+ vuetify3.VCardSubtitle("Inputs", classes="text-subtitle-1 font-weight-bold mt-2")
467
+ vuetify3.VListItemTitle("Geometry, Boundary conditions - temperature and flow", classes="text-body-2")
468
+ vuetify3.VCardSubtitle("Outputs", classes="text-subtitle-1 font-weight-bold mt-2")
469
+ vuetify3.VListItemTitle("Surface plots on sections OR sampling through a line in 3D domain", classes="text-body-2")
470
+
471
+ # Geometry cell
472
+ with vuetify3.VCard(classes="mb-4"):
473
+ vuetify3.VCardTitle("Geometry", classes="text-primary")
474
+ with vuetify3.VCardText():
475
+ # Read-only geometry display auto-set by problem selection
476
+ vuetify3.VAlert(
477
+ v_if="geometry_selection",
478
+ type="info",
479
+ variant="tonal",
480
+ density="compact",
481
+ color="primary",
482
+ children=["Selected Geometry: ", "{{ geometry_selection }}"],
483
+ )
484
+ vuetify3.VAlert(
485
+ v_if="!geometry_selection",
486
+ type="info",
487
+ variant="tonal",
488
+ density="compact",
489
+ color="primary",
490
+ children=["No geometry selected. Choose a problem to auto-set."],
491
+ )
492
+ with vuetify3.VContainer(v_if="geometry_selection === 'Rectangular domain with a heated box (3D)'", classes="pa-0 mt-4"):
493
+ vuetify3.VCardSubtitle("Domain dimensions", classes="text-subtitle-1 font-weight-bold mb-2")
494
+ with vuetify3.VRow(dense=True):
495
+ vuetify3.VCol(children=[vuetify3.VTextField(label="Length (L)", v_model=("domain_L", 1.0), type="number", step="0.1", density="compact", color="primary")])
496
+ vuetify3.VCol(children=[vuetify3.VTextField(label="Width (W)", v_model=("domain_W", 1.0), type="number", step="0.1", density="compact", color="primary")])
497
+ vuetify3.VCol(children=[vuetify3.VTextField(label="Height (H)", v_model=("domain_H", 1.0), type="number", step="0.1", density="compact", color="primary")])
498
+
499
+ # Initial Distribution Examples card removed per latest UI request
500
+
501
+ # (Simulation Parameters: moved here to avoid vertical gap)
502
+
503
+ # Combined Initial & Boundary Conditions
504
+ with vuetify3.VCard(classes="mb-4"):
505
+ vuetify3.VCardTitle("Initial & Boundary Conditions", classes="text-primary")
506
+ with vuetify3.VCardText():
507
+ vuetify3.VSelect(label="Initial Distribution Type", v_model=("distribution_type", "Sinusoidal"), items=("dist_types", ["Gaussian", "Sinusoidal"]), density="compact", color="primary")
508
+ # Boundary condition restricted to Periodic only
509
+ vuetify3.VSelect(label="Boundary Condition", v_model=("boundary_condition", "Periodic"), items=("bc_types", ["Periodic"]), density="compact", color="primary", classes="mt-2")
510
+
511
+ # Advecting Fields card
512
+ with vuetify3.VCard(classes="mb-4"):
513
+ vuetify3.VCardTitle("Advecting Fields", classes="text-primary")
514
+ with vuetify3.VCardText():
515
+ vuetify3.VSelect(
516
+ label="Select Advecting field",
517
+ v_model=("advecting_field", "Uniform"),
518
+ items=("adv_presets", ["Uniform", "Swirl", "Shear", "TGV"]),
519
+ density="compact",
520
+ color="primary"
521
+ )
522
+ trame_html.Div("Velocity Components", classes="text-body-2 mt-4")
523
+ vuetify3.VTextField(label="Velocity vx", v_model=("vx_expr", "0.2"), density="compact", color="primary")
524
+ vuetify3.VTextField(label="Velocity vy", v_model=("vy_expr", "-0.15"), density="compact", color="primary")
525
+ vuetify3.VTextField(label="Velocity vz", v_model=("vz_expr", "0.3"), density="compact", color="primary")
526
+
527
+ # Meshing
528
+ with vuetify3.VCard(classes="mb-4"):
529
+ vuetify3.VCardTitle("Meshing", classes="text-primary")
530
+ with vuetify3.VCardText():
531
+ with vuetify3.VMenu(open_on_hover=True, close_on_content_click=False, location="end"):
532
+ with vuetify3.Template(v_slot_activator="{ props }"):
533
+ with vuetify3.VSlider(
534
+ v_bind="props",
535
+ label="Number of Points / Direction",
536
+ v_model=("grid_index", 2),
537
+ min=0, max=5, step=1,
538
+ thumb_label="always",
539
+ show_ticks="always",
540
+ color="primary",
541
+ density="compact"
542
+ ):
543
+ # Map index -> power-of-two label
544
+ vuetify3.Template(v_slot_thumb_label="{ modelValue }", children=["{{ ['8','16','32','64','128','256'][modelValue] }}"])
545
+ with vuetify3.VSheet(classes="pa-2", elevation=6, rounded=True, style="width: 700px;"):
546
+ with vuetify3.VContainer(fluid=True, classes="pa-0"):
547
+ qubit_fig = plotly_widgets.Figure(figure=go.Figure(), style="width: 616px; height: 320px; min-height: 320px;", responsive=True)
548
+ trame_html.Div("{{ qubit_grid_info }}", classes="mt-2 text-caption")
549
+ trame_html.Div("{{ qubit_warning }}", classes="warn-text")
550
+ vuetify3.VAlert(v_if="grid_size > 32", type="warning", variant="tonal", density="compact", children=["Warning: High grid size may impact performance."], classes="mt-2")
551
+
552
+ # Time
553
+ with vuetify3.VCard(classes="mb-4"):
554
+ vuetify3.VCardTitle("Time", classes="text-primary")
555
+ with vuetify3.VCardText():
556
+ vuetify3.VSlider(label="Time Steps", v_model=("time_steps", 100), min=0, max=2000, step=10, thumb_label="always", show_ticks="always", color="primary", density="compact")
557
+ vuetify3.VAlert(v_if="time_steps > 100", type="warning", variant="tonal", density="compact", children=["Warning: High time steps may increase runtime."], classes="mt-2")
558
+
559
+ # Backends cell (from qlbm) with Run button beneath
560
+ with vuetify3.VCard(classes="mb-4"):
561
+ vuetify3.VCardTitle("Backends", classes="text-primary")
562
+ with vuetify3.VCardText():
563
+ with vuetify3.VRow(dense=True, classes="mb-2"):
564
+ with vuetify3.VCol():
565
+ vuetify3.VAlert(
566
+ type="info",
567
+ color="primary",
568
+ variant="tonal",
569
+ density="compact",
570
+ children=[
571
+ "Selected: ",
572
+ "{{ backend_type || '—' }}",
573
+ " - ",
574
+ "{{ backend_type === 'Simulator' ? selected_simulator : (backend_type === 'QPU' ? selected_qpu : '—') }}",
575
+ ],
576
+ )
577
+ with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end"):
578
+ with vuetify3.Template(v_slot_activator="{ props }"):
579
+ vuetify3.VBtn(v_bind="props", text="Choose Backend", color="primary", variant="tonal", block=True)
580
+ with vuetify3.VList(density="compact"):
581
+ with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
582
+ with vuetify3.Template(v_slot_activator="{ props }"):
583
+ vuetify3.VListItem(v_bind="props", title="Simulator", prepend_icon="mdi-robot-outline", append_icon="mdi-chevron-right")
584
+ with vuetify3.VList(density="compact"):
585
+ vuetify3.VListItem(title="IBM Qiskit simulator", click="backend_type = 'Simulator'; selected_simulator = 'IBM Qiskit simulator'")
586
+ vuetify3.VListItem(title="IonQ simulator", click="backend_type = 'Simulator'; selected_simulator = 'IonQ simulator'")
587
+ with vuetify3.VMenu(open_on_hover=True, close_on_content_click=True, location="end", offset=8):
588
+ with vuetify3.Template(v_slot_activator="{ props }"):
589
+ vuetify3.VListItem(v_bind="props", title="QPU", prepend_icon="mdi-chip", append_icon="mdi-chevron-right")
590
+ with vuetify3.VList(density="compact"):
591
+ vuetify3.VListItem(title="IBM QPU", click="backend_type = 'QPU'; selected_qpu = 'IBM QPU'")
592
+ vuetify3.VListItem(title="IonQ QPU", click="backend_type = 'QPU'; selected_qpu = 'IonQ QPU'")
593
+ vuetify3.VDivider(classes="my-3")
594
+ vuetify3.VBtn(text="Run Simulation", color="primary", block=True, disabled=("is_running", False), click=run_simulation)
595
+
596
+ # Right column (Visualization)
597
+ with vuetify3.VCol(cols=7):
598
+ with vuetify3.VCard(classes="fill-height"):
599
+ vuetify3.VCardTitle("QLBM 3D Visualization", classes="text-primary")
600
+ with vuetify3.VCardText(classes="pb-2", style="min-height:760px;display:flex;flex-direction:column;"):
601
+ vuetify3.VProgressCircular(v_if="is_running", indeterminate=True, color="primary", size=64, classes="my-4 mx-auto d-flex")
602
+ sim_fig = plotly_widgets.Figure(figure=go.Figure(), style="width:100%;height:700px;", responsive=True)
603
+ vuetify3.VAlert(v_if="run_error", type="error", variant="tonal", density="compact", children=["{{ run_error }}"], classes="mt-2")
604
+ # Removed Download Plot Data button per request
605
+
606
+ # Row 2 removed to keep left panel contiguous with Geometry (no vertical gap)
607
+
608
+ # Controller hooks for plot updates
609
+ ctrl.qubit_plot_update = qubit_fig.update
610
+ ctrl.sim_plot_update = sim_fig.update
611
+
612
+ # --------------------------------------------------------------------------------------
613
+ # Embedding helper
614
+ # --------------------------------------------------------------------------------------
615
+ _started = False
616
+
617
+ def build(host_server):
618
+ """Embed this app into another Trame server via an iframe, auto-starting its own server."""
619
+ global _started
620
+ if not _started:
621
+ import threading
622
+ def _run():
623
+ try:
624
+ port = int(os.environ.get("TRAME3D_PORT", "8811"))
625
+ server.start(host="0.0.0.0", port=port, open_browser=False)
626
+ except Exception as e:
627
+ print(f"trame3d server failed to start: {e}")
628
+ threading.Thread(target=_run, daemon=True).start()
629
+ _started = True
630
+ h_state = host_server.state
631
+ default_port = os.environ.get("TRAME3D_PORT", "8811")
632
+ if not getattr(h_state, "trame3d_iframe_src", None):
633
+ h_state.trame3d_iframe_src = f"http://localhost:{default_port}/"
634
+ with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
635
+ trame_html.Iframe(src=("trame3d_iframe_src", f"http://localhost:{default_port}/"), style="border:0; width:100%; height: calc(100vh - 64px);")
636
+ trame_html.Div("If the 3D QLBM view is blank, wait a few seconds or verify TRAME3D_PORT.", style="color: rgba(0,0,0,.6); padding: 6px;")
637
+
638
+ # Controller hooks for plot updates
639
+ ctrl.qubit_plot_update = qubit_fig.update
640
+ ctrl.sim_plot_update = sim_fig.update
641
+
642
+ # --------------------------------------------------------------------------------------
643
+ # Embedding helper
644
+ # --------------------------------------------------------------------------------------
645
+ _started = False
646
 
647
  def build(host_server):
648
+ """Embed this app into another Trame server via an iframe, auto-starting its own server."""
649
+ global _started
650
+ if not _started:
651
+ import threading
652
+ def _run():
653
+ try:
654
+ port = int(os.environ.get("TRAME3D_PORT", "8811"))
655
+ server.start(host="0.0.0.0", port=port, open_browser=False)
656
+ except Exception as e:
657
+ print(f"trame3d server failed to start: {e}")
658
+ threading.Thread(target=_run, daemon=True).start()
659
+ _started = True
660
+ h_state = host_server.state
661
+ default_port = os.environ.get("TRAME3D_PORT", "8811")
662
+ if not getattr(h_state, "trame3d_iframe_src", None):
663
+ h_state.trame3d_iframe_src = f"http://localhost:{default_port}/"
664
+ with vuetify3.VContainer(fluid=True, classes="pa-0 fill-height"):
665
+ trame_html.Iframe(src=("trame3d_iframe_src", f"http://localhost:{default_port}/"), style="border:0; width:100%; height: calc(100vh - 64px);")
666
+ trame_html.Div("If the 3D QLBM view is blank, wait a few seconds or verify TRAME3D_PORT.", style="color: rgba(0,0,0,.6); padding: 6px;")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
 
668
  # --- Entry point ---
669
  if __name__ == "__main__":
utils/__pycache__/base_functions.cpython-310.pyc ADDED
Binary file (9.58 kB). View file
 
utils/__pycache__/delta_impulse_generator.cpython-310.pyc ADDED
Binary file (13.8 kB). View file
 
utils/base_ionq.py ADDED
@@ -0,0 +1,458 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import scipy.sparse as sp
3
+ import math
4
+ import random
5
+ import matplotlib.pyplot as plt
6
+ from scipy.special import jn
7
+ from scipy.sparse import identity, csr_matrix, kron, diags, eye
8
+ from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
9
+ from qiskit.circuit.library import MCXGate, MCPhaseGate, RXGate, CRXGate, QFTGate, StatePreparation, PauliEvolutionGate, RZGate
10
+ from qiskit.quantum_info import SparsePauliOp, Statevector, Operator, Pauli
11
+ from scipy.linalg import expm
12
+ # from tools import *
13
+ from qiskit.qasm3 import dumps # QASM 3 exporter
14
+ from qiskit.qasm3 import loads
15
+ from qiskit.circuit.library import QFT
16
+ from qiskit.primitives import StatevectorEstimator
17
+ from qiskit import transpile
18
+ from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit, apply_circuit_to_state, compute_overlap
19
+ from qiskit_aer import AerSimulator
20
+ from qiskit_ionq import IonQProvider
21
+ import os
22
+ my_api_key = os.getenv("IONQ_API_KEY")
23
+ from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
24
+
25
+
26
+
27
+
28
+ # provider = IonQProvider()
29
+
30
+ api_token = "SgUkiDq1r2bVEadyiUfvtuxQ03Qci6UW"
31
+ provider = IonQProvider(api_token)
32
+ ionq_backend = provider.get_backend("simulator")
33
+
34
+
35
+
36
+
37
+ simulator_settings = AerSimulator(
38
+ method="matrix_product_state",
39
+ matrix_product_state_max_bond_dimension=100,
40
+ )
41
+
42
+ def Wj(j, theta, lam, name='Wj', xgate=False):
43
+ if not xgate:
44
+ name = f' $W_{j}$ '
45
+ qc=QuantumCircuit(j, name=name)
46
+
47
+ if j > 1:
48
+ qc.cx(j-1, range(j-1))
49
+ if lam != 0:
50
+ qc.p(lam, j-1)
51
+ qc.h(j-1)
52
+ if xgate:
53
+ qc.x(range(j-1))
54
+
55
+ # the multicontrolled rz gate
56
+ # it will be decomposed in qiskit
57
+ if j > 1:
58
+ qc.mcrz(theta, range(j-1), j-1)
59
+ else:
60
+ qc.rz(theta, j-1)
61
+
62
+ if xgate:
63
+ qc.x(range(j-1))
64
+ qc.h(j-1)
65
+ if lam != 0:
66
+ qc.p(-lam, j-1)
67
+ if j > 1:
68
+ qc.cx(j-1, range(j-1))
69
+
70
+ return qc
71
+
72
+ def Wj_block(j, n, ctrl_state, theta, lam, name='Wj_block', xgate=False):
73
+ if not xgate:
74
+ name = f' $W_{j}_block$ '
75
+ qc=QuantumCircuit(n + j, name=name)
76
+
77
+ if j > 1:
78
+ qc.cx(n + j-1, range(n, n+j-1))
79
+ if lam != 0:
80
+ qc.p(lam, n + j -1)
81
+ qc.h(n + j -1)
82
+
83
+ if xgate and j>1:
84
+ if isinstance(xgate, (list, tuple)): # selective application
85
+ for idx, flag in enumerate(xgate):
86
+ if flag: # only apply where flag == 1
87
+ qc.x(n + idx)
88
+ elif xgate is True: # apply to all
89
+ qc.x(range(n, n+j-1))
90
+
91
+ # the multicontrolled rz gate
92
+ # it will be decomposed in qiskit
93
+ if j > 1:
94
+ mcrz = RZGate(theta).control(len(ctrl_state) + j-1, ctrl_state = "1"*(j-1)+ctrl_state)
95
+ qc.append(mcrz, range(0, n + j))
96
+ else:
97
+ mcrz = RZGate(theta).control(len(ctrl_state), ctrl_state = ctrl_state)
98
+ qc.append(mcrz, range(0, n+j))
99
+
100
+ if xgate and j>1:
101
+ if isinstance(xgate, (list, tuple)): # selective application
102
+ for idx, flag in enumerate(xgate):
103
+ if flag: # only apply where flag == 1
104
+ qc.x(n + idx)
105
+ elif xgate is True: # apply to all
106
+ qc.x(range(n, n+j-1))
107
+
108
+ qc.h(n+ j-1)
109
+ if lam != 0:
110
+ qc.p(-lam, n + j-1)
111
+ if j > 1:
112
+ qc.cx(n + j-1, range(n, n +j-1))
113
+
114
+ return qc.to_gate(label=name)
115
+
116
+ def V1(nx, dt, name = "V1"):
117
+ n = int(np.ceil(np.log2(nx)))
118
+
119
+ derivatives = QuantumRegister(2*n)
120
+ blocks = QuantumRegister(2)
121
+
122
+ qc = QuantumCircuit(derivatives, blocks)
123
+
124
+ W1 = Wj_block(2, n, "0"*n, -dt , 0, xgate=True)
125
+ qc.append(W1, list(derivatives[0:n])+list(blocks[:]))
126
+
127
+ # qc.barrier()
128
+
129
+ W2 = Wj_block(3, n-1, "1"*(n-1), dt , 0, xgate=[0,1])
130
+ qc.append(W2, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
131
+
132
+ # qc.barrier()
133
+
134
+ W3 = Wj_block(1, n+1, "0"*(n+1), dt , 0, xgate=False)
135
+ qc.append(W3, list(derivatives[n:2*n])+list(blocks[:]))
136
+
137
+ # qc.barrier()
138
+
139
+ W4 = Wj_block(2, n, "0"+"1"*(n-1), -dt , 0, xgate=False)
140
+ qc.append(W4, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
141
+
142
+ return qc
143
+
144
+ def V2(nx, dt, name = "V2"):
145
+ n = int(np.ceil(np.log2(nx)))
146
+
147
+ derivatives = QuantumRegister(2*n)
148
+ blocks = QuantumRegister(2)
149
+
150
+ qc = QuantumCircuit(derivatives, blocks)
151
+
152
+ W1 = Wj_block(2, 0, "", -2*dt , -np.pi/2, xgate=True)
153
+ qc.append(W1, list(blocks[:]))
154
+
155
+ # qc.barrier()
156
+
157
+ for j in range(1, n+1):
158
+ W2 = Wj_block(2+j, 0, "", 2*dt , -np.pi/2, xgate=[1]*(j-1)+[0,1])
159
+ qc.append(W2, list(derivatives[0:j])+list(blocks[:]))
160
+
161
+ # qc.barrier()
162
+
163
+ W3 = Wj_block(2, n, "0"*n, -dt , -np.pi/2, xgate=True)
164
+ qc.append(W3, list(derivatives[0:n])+list(blocks[:]))
165
+
166
+ # qc.barrier()
167
+
168
+ W4 = Wj_block(2, n, "1"*n, 2*dt , -np.pi/2, xgate=True)
169
+ qc.append(W4, list(derivatives[0:n])+list(blocks[:]))
170
+
171
+ # qc.barrier()
172
+
173
+ W5 = Wj_block(3, n-1, "1"*(n-1), dt , -np.pi/2, xgate=[0,1])
174
+ qc.append(W5, list(derivatives[1:n])+[derivatives[0]]+list(blocks[:]))
175
+
176
+ # qc.barrier()
177
+
178
+ W6 = Wj_block(1, 1, "0", 2*dt , -np.pi/2, xgate=False)
179
+ qc.append(W6, list(blocks[:]))
180
+
181
+ # qc.barrier()
182
+
183
+ for j in range(1, n+1):
184
+ W7 = Wj_block(1+j, 1, "0", -2*dt , -np.pi/2, xgate=[1]*(j-1))
185
+ qc.append(W7, [blocks[0]]+list(derivatives[n:n+j])+[blocks[1]])
186
+
187
+ # qc.barrier()
188
+
189
+ W8 = Wj_block(1, n+1, "0"*(n+1), dt , -np.pi/2, xgate=False)
190
+ qc.append(W8, list(derivatives[n:2*n])+list(blocks[:]))
191
+
192
+ # qc.barrier()
193
+
194
+ W9 = Wj_block(1, n+1, "0"+"1"*(n), -2*dt , -np.pi/2, xgate=False)
195
+ qc.append(W9, list(derivatives[n:2*n])+list(blocks[:]))
196
+
197
+ # qc.barrier()
198
+
199
+ W10 = Wj_block(2, n, "0"+"1"*(n-1), -dt , -np.pi/2, xgate=False)
200
+ qc.append(W10, list(derivatives[n+1:2*n]) + [blocks[0]] + [derivatives[n]] + [blocks[1]])
201
+
202
+ # qc.barrier()
203
+
204
+ return qc
205
+
206
+ def schro(nx, na, R, dt, initial_state, steps):
207
+
208
+ nq = int(np.ceil(np.log2(nx)))
209
+
210
+ # warped phase transformation
211
+ dp = 2 * R * np.pi / 2**na
212
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
213
+ fp = np.exp(-np.abs(p))
214
+ norm1 = np.linalg.norm(fp[2**(na-1):]) # norm of p>=0
215
+
216
+ # construct quantum circuit
217
+ system = QuantumRegister(2*nq+2, name='system')
218
+ ancilla = QuantumRegister(na, name='ancilla')
219
+ qc = QuantumCircuit(system, ancilla)
220
+
221
+ # initialization
222
+ prep = StatePreparation(initial_state)
223
+ anc_prep = StatePreparation(fp / np.linalg.norm(fp))
224
+
225
+ qc.append(prep, system)
226
+ # qc.append(anc_prep, ancilla)
227
+ qc.initialize(fp / np.linalg.norm(fp), ancilla)
228
+
229
+
230
+ # QFT
231
+ qc.append(QFTGate(na), ancilla)
232
+ qc.x(ancilla[-1])
233
+
234
+ A1 = V1(nx, dt, name = "V1").to_gate()
235
+ A2 = V2(nx, dt, name = "V2")
236
+
237
+
238
+ # Hamiltonian simulation for Nt steps
239
+ for i in range(steps):
240
+ # circuit for one step
241
+ for j in range(na):
242
+ # repeat controlled H1 for 2**j times
243
+ qc.append(A1.control().repeat(2**j), [ancilla[j]] + system[:])
244
+
245
+ # qc.append(A1.inverse().control(ctrl_state = "0").repeat(2**(na-1)), [ancilla[na-1]] + system[:])
246
+ qc.append(A1.inverse().repeat(2**(na-1)), system[:])
247
+ qc.append(A2, system[:])
248
+
249
+ # rearrange eta
250
+ qc.x(ancilla[-1])
251
+ qc.append(QFTGate(na).inverse(), ancilla)
252
+
253
+ return qc
254
+
255
+
256
+
257
+ def circ_for_magnitude(field, x, y, nx, na, R, dt, initial_state, steps):
258
+
259
+ qc = schro(nx, na, R, dt, initial_state, steps)
260
+ naimark = QuantumRegister(1, name='Naimark')
261
+ qc.add_register(naimark)
262
+
263
+ if field == 'Ez':
264
+ index = nx * y + x
265
+ elif field == 'Hx':
266
+ index = 2*nx*nx + nx * y + x
267
+ else:
268
+ index = 3*nx*nx + nx * y + x
269
+
270
+ index_bin = format(index, f'0{qc.num_qubits-2}b')
271
+ ctrl_state = '1' + index_bin
272
+ ctrl_qubits = qc.qubits[:-1]
273
+ qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state)
274
+
275
+ return qc
276
+
277
+ def circuits_for_sign(field, x, y, nx, na, dt, R, initial_state, steps, xref, yref, field_ref = 'Ez'):
278
+ qc = schro(nx, na, R, dt, initial_state, steps)
279
+
280
+ naimark = QuantumRegister(1, name='Naimark')
281
+ qc.add_register(naimark)
282
+
283
+ if field == 'Ez':
284
+ index = nx * y + x
285
+ elif field == 'Hx':
286
+ index = 2*nx*nx + nx * y + x
287
+ else:
288
+ index = 3*nx*nx + nx * y + x
289
+
290
+ if field_ref == 'Ez':
291
+ index_ref = nx * yref + xref
292
+ elif field_ref == 'Hx':
293
+ index_ref = 2*nx*nx + nx * yref + xref
294
+ else:
295
+ index_ref = 3*nx*nx + nx * yref + xref
296
+
297
+ index_bin = [(index >> i) & 1 for i in range(qc.num_qubits-2)]
298
+ index_ref_bin = [(index_ref >> i) & 1 for i in range(qc.num_qubits-2)]
299
+ index_bin.append(1)
300
+ index_ref_bin.append(1)
301
+
302
+ #Convert reference bitstring to 00000
303
+ for i, bit in enumerate(index_ref_bin):
304
+ if bit == 1:
305
+ qc.x(i)
306
+
307
+ d_bits = [b ^ r for b, r in zip(index_ref_bin, index_bin)]
308
+ control = d_bits.index(1)
309
+
310
+ #Convert the other bitstring to 0001000
311
+ for target, bit in enumerate(d_bits):
312
+ if bit == 1 and target != control:
313
+ qc.cx(control, target)
314
+ qc.h(control)
315
+
316
+ ctrl_state_sum = '0'*(qc.num_qubits-1)
317
+ ctrl_state_diff = '0'*(qc.num_qubits-1-control-1)+'1'+'0'*(control)
318
+
319
+ qcdiff = qc.copy()
320
+
321
+ ctrl_qubits = qc.qubits[:-1]
322
+
323
+ qc.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_sum)
324
+ qcdiff.mcx(ctrl_qubits, naimark[0], ctrl_state=ctrl_state_diff)
325
+
326
+ return qc, qcdiff
327
+
328
+ def get_absolute_field_value(qc, nq, na, offset, norm):
329
+
330
+ pauli_label = 'Z'+'I'*(2*nq+2+na)
331
+ observable = SparsePauliOp(Pauli(pauli_label))
332
+ ########################################################################################
333
+ estimator = StatevectorEstimator()
334
+
335
+ # === Run Estimator (no parameters needed) ===
336
+ pub = (qc, observable)
337
+ job = estimator.run([pub])
338
+ result = job.result()[0]
339
+ z_exp = result.data.evs.item()
340
+ #########################################################################################
341
+ # === Compute projector expectation ===
342
+ pi_expect = (1 - z_exp) / 2
343
+
344
+ Absolute_value = norm*np.sqrt(pi_expect)-offset
345
+
346
+ return Absolute_value
347
+
348
+ def get_relative_sign(qc, qcdiff, nq, na):
349
+
350
+ pauli_label = 'Z'+'I'*(2*nq+2+na)
351
+ observable = SparsePauliOp(Pauli(pauli_label))
352
+ ########################################################################################
353
+ estimator = StatevectorEstimator()
354
+
355
+ # === Run Estimator ===
356
+ pub = (qc, observable)
357
+ job = estimator.run([pub])
358
+ result = job.result()[0]
359
+ z_exp = result.data.evs.item()
360
+
361
+ pub_diff = (qcdiff, observable)
362
+ job_diff = estimator.run([pub_diff])
363
+ result_diff = job_diff.result()[0]
364
+ z_exp_diff = result_diff.data.evs.item()
365
+ #########################################################################################
366
+ # === Compute projector expectation ===
367
+ pi_expect_sum = (1 - z_exp) / 2
368
+ pi_expect_diff = (1 - z_exp_diff) / 2
369
+
370
+ relative_sign = 'same' if pi_expect_sum >= pi_expect_diff else 'different'
371
+
372
+ return relative_sign
373
+
374
+ def Eref_value(nx, nq, R, dt, na, steps, xref, yref, field_ref = 'Ez'):
375
+ if steps < 31:
376
+ offset = 1
377
+ else :
378
+ offset = 0.15
379
+ deltastate = np.zeros(4*nx*nx)
380
+ # deltastate[nx*nx//2+nx//2:nx*nx//2+nx//2+1] = 1
381
+ deltastate[nx*yref+xref] = 1
382
+ deltastate[0:nx*nx] = deltastate[0:nx*nx] + offset
383
+ norm1 = np.linalg.norm(deltastate)
384
+ initial_state = deltastate/norm1
385
+
386
+ dp = 2 * R * np.pi / 2**na
387
+ p = np.arange(- R * np.pi, R * np.pi, step=dp)
388
+ fp = np.exp(-np.abs(p))
389
+ norm2 = np.linalg.norm(fp)
390
+ norm = norm1 * norm2
391
+
392
+ qc = circ_for_magnitude(field_ref, xref, yref, nx, na, R, dt, initial_state, steps)
393
+
394
+ Ezref = get_absolute_field_value(qc, nq, na, offset, norm)
395
+
396
+ return Ezref
397
+
398
+
399
+ def transpile_circ(circ, basis_gates=None):
400
+ """
401
+ Transpile the circuit to the specified basis gates.
402
+ """
403
+ if basis_gates is None:
404
+ basis_gates = ['z', 'y', 'x', 'sdg', 's', 'h', 'rz', 'ry', 'rx', 'ecr', 'cz', 'cx']
405
+
406
+ transpiled_circ = transpile(circ, basis_gates=basis_gates)
407
+ return transpiled_circ
408
+
409
+ def compute_fidelity(circ1, circ2):
410
+
411
+ circ_1 = tensornetwork_from_circuit(transpile_circ(circ1), simulator_settings)
412
+ circ_2 = tensornetwork_from_circuit(transpile_circ(circ2), simulator_settings)
413
+ fidelity = abs(compute_overlap(circ_1, circ_2))**2
414
+
415
+ return fidelity
416
+
417
+ # def create_impulse_state(grid_dims, impulse_pos):
418
+ # """
419
+ # Creates an initial state vector with a single delta impulse at a specified grid position.
420
+
421
+ # The 2D grid is flattened into a 1D vector in row-major order, and this
422
+ # vector is then padded to match the full simulation state space size (4x).
423
+
424
+ # Args:
425
+ # grid_dims (tuple): A tuple (width, height) defining the simulation grid dimensions.
426
+ # For your original code, this would be (nx, nx).
427
+ # impulse_pos (tuple): A tuple (x, y) for the position of the impulse.
428
+ # Coordinates are 0-indexed.
429
+
430
+ # Returns:
431
+ # numpy.ndarray: The full, padded initial state vector with a single 1.
432
+
433
+ # Raises:
434
+ # ValueError: If the impulse position is outside the grid dimensions.
435
+ # """
436
+ # grid_width, grid_height = grid_dims
437
+ # impulse_x, impulse_y = impulse_pos
438
+
439
+ # # --- Input Validation ---
440
+ # # Ensure the requested impulse position is actually on the grid.
441
+ # if not (0 <= impulse_x < grid_width and 0 <= impulse_y < grid_height):
442
+ # raise ValueError(f"Impulse position ({impulse_x}, {impulse_y}) is outside the "
443
+ # f"grid dimensions ({grid_width}x{grid_height}).")
444
+
445
+ # # --- 1. Calculate the 1D Array Index ---
446
+ # # Convert the (x, y) coordinate to a single index in a flattened 1D array.
447
+ # # The formula for row-major order is: index = y_coord * width + x_coord
448
+ # flat_index = impulse_y * grid_width + impulse_x
449
+
450
+ # # --- 2. Create the Full, Padded State Vector ---
451
+ # grid_size = grid_width * grid_height
452
+ # total_size = 4 * grid_size # The simulation space is 4x the grid size.
453
+ # initial_state = np.zeros(total_size)
454
+
455
+ # # --- 3. Set the Delta Impulse ---
456
+ # initial_state[flat_index] = 1
457
+
458
+ # return initial_state