harishaseebat92 commited on
Commit
2409ff1
·
1 Parent(s): bffdcd9

QLBM : IBM QPU version integrated

Browse files
qlbm/qlbm_sample_app.py CHANGED
@@ -423,7 +423,10 @@ from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
423
  from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
424
  import pprint
425
  # import mthree
426
- # from ...Quantum_LBM_AdvecDiff.qlbm.visualize_counts import load_samples, estimate_density, plot_density_isosurface
 
 
 
427
 
428
 
429
  def run_sampling_hw_ibm(
@@ -436,6 +439,7 @@ def run_sampling_hw_ibm(
436
  shots=2**19,
437
  vel_resolution=32,
438
  output_resolution=40,
 
439
  ):
440
  """
441
  Run QLBM simulation on IBM quantum hardware.
@@ -456,15 +460,23 @@ def run_sampling_hw_ibm(
456
  Resolution for velocity field discretization
457
  output_resolution : int
458
  Grid resolution for density estimation output
 
 
459
 
460
  Returns
461
  -------
462
  job : IBMJob
463
  The submitted job object
464
  get_job_result : callable
465
- Callback function to retrieve and process results
466
  """
467
 
 
 
 
 
 
 
468
  if type(ux)==str:
469
  ux,uy,uz=str_to_lambda(ux,uy,uz)
470
 
@@ -479,10 +491,10 @@ def run_sampling_hw_ibm(
479
 
480
  for qc in qc_list:
481
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
482
- print("Generating ISA circuit via PassManager (preserves measurements/conditionals).")
483
  qc_compiled = pm.run(qc) # this is the recommended replacement for transpile(..., backend=backend)
484
- print("Compiled circuit qubits/clbits:", qc_compiled.num_qubits, qc_compiled.num_clbits)
485
- print("Depth: ", qc_compiled.depth())
486
  qc_compiled_list+=[qc_compiled]
487
 
488
  # Create Sampler primitive bound to the backend
@@ -490,43 +502,44 @@ def run_sampling_hw_ibm(
490
 
491
  # Submit job: pass a list of PUBs (we send one PUB [qc_compiled])
492
  job = sampler.run(qc_compiled_list, shots=shots)
493
- print("Job submitted; waiting for result...")
494
 
495
  def get_job_result(j):
496
  result = j.result() # PrimitiveResult (a container of PubResults)
497
- print(result)
498
 
499
  output=[]
500
 
501
  for T_total,pub in zip(T_list,result):
502
 
503
  # We'll inspect the first PUB result
504
- print("PUB metadata:", pub.metadata if hasattr(pub, "metadata") else "<no metadata>")
505
 
506
  # 1) Try to obtain counts via the recommended API
507
  try:
508
  counts = pub.data.meas.get_counts()
509
- print("\nCounts (pub.data.meas.get_counts()) sample:")
510
- pprint.pprint({k: counts[k] for k in list(counts)[:10]})
511
  except Exception as e:
512
- print("Couldn't call pub.data.meas.get_counts():", e)
513
  counts = None
514
 
515
  # 2) Try join_data() (to combine multiple regs) and get_counts() on it
516
  try:
517
  joined = pub.join_data() # join_data concatenates registers along bits axis
518
  joined_counts = joined.get_counts()
519
- print("\nJoined counts (pub.join_data().get_counts()) sample:")
520
- pprint.pprint({k: joined_counts[k] for k in list(joined_counts)[:10]})
521
  except Exception as e:
522
- print("join_data()/joined.get_counts() not available or failed:", e)
523
  joined_counts = None
524
 
525
 
526
  pts, counts = load_samples(joined_counts,T_total)
527
  output+=[estimate_density(pts, counts, bandwidth=0.05, grid_size=output_resolution)]
528
 
529
- return output
 
530
 
531
  return job,get_job_result
532
 
 
423
  from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
424
  import pprint
425
  # import mthree
426
+ try:
427
+ from qlbm.visualize_counts import load_samples, estimate_density, plot_density_isosurface, plot_density_isosurface_slider
428
+ except ImportError:
429
+ from visualize_counts import load_samples, estimate_density, plot_density_isosurface, plot_density_isosurface_slider
430
 
431
 
432
  def run_sampling_hw_ibm(
 
439
  shots=2**19,
440
  vel_resolution=32,
441
  output_resolution=40,
442
+ logger=None,
443
  ):
444
  """
445
  Run QLBM simulation on IBM quantum hardware.
 
460
  Resolution for velocity field discretization
461
  output_resolution : int
462
  Grid resolution for density estimation output
463
+ logger : callable, optional
464
+ Function to log messages (e.g. print to console)
465
 
466
  Returns
467
  -------
468
  job : IBMJob
469
  The submitted job object
470
  get_job_result : callable
471
+ Callback function to retrieve and process results. Returns (output, fig).
472
  """
473
 
474
+ def log(msg):
475
+ if logger:
476
+ logger(str(msg))
477
+ else:
478
+ print(msg)
479
+
480
  if type(ux)==str:
481
  ux,uy,uz=str_to_lambda(ux,uy,uz)
482
 
 
491
 
492
  for qc in qc_list:
493
  pm = generate_preset_pass_manager(backend=backend, optimization_level=pm_optimization_level)
494
+ log("Generating ISA circuit via PassManager (preserves measurements/conditionals).")
495
  qc_compiled = pm.run(qc) # this is the recommended replacement for transpile(..., backend=backend)
496
+ log(f"Compiled circuit qubits/clbits: {qc_compiled.num_qubits} {qc_compiled.num_clbits}")
497
+ log(f"Depth: {qc_compiled.depth()}")
498
  qc_compiled_list+=[qc_compiled]
499
 
500
  # Create Sampler primitive bound to the backend
 
502
 
503
  # Submit job: pass a list of PUBs (we send one PUB [qc_compiled])
504
  job = sampler.run(qc_compiled_list, shots=shots)
505
+ log("Job submitted; waiting for result...")
506
 
507
  def get_job_result(j):
508
  result = j.result() # PrimitiveResult (a container of PubResults)
509
+ log(str(result))
510
 
511
  output=[]
512
 
513
  for T_total,pub in zip(T_list,result):
514
 
515
  # We'll inspect the first PUB result
516
+ log(f"PUB metadata: {pub.metadata if hasattr(pub, 'metadata') else '<no metadata>'}")
517
 
518
  # 1) Try to obtain counts via the recommended API
519
  try:
520
  counts = pub.data.meas.get_counts()
521
+ log("\nCounts (pub.data.meas.get_counts()) sample:")
522
+ log(str({k: counts[k] for k in list(counts)[:10]}))
523
  except Exception as e:
524
+ log(f"Couldn't call pub.data.meas.get_counts(): {e}")
525
  counts = None
526
 
527
  # 2) Try join_data() (to combine multiple regs) and get_counts() on it
528
  try:
529
  joined = pub.join_data() # join_data concatenates registers along bits axis
530
  joined_counts = joined.get_counts()
531
+ log("\nJoined counts (pub.join_data().get_counts()) sample:")
532
+ log(str({k: joined_counts[k] for k in list(joined_counts)[:10]}))
533
  except Exception as e:
534
+ log(f"join_data()/joined.get_counts() not available or failed: {e}")
535
  joined_counts = None
536
 
537
 
538
  pts, counts = load_samples(joined_counts,T_total)
539
  output+=[estimate_density(pts, counts, bandwidth=0.05, grid_size=output_resolution)]
540
 
541
+ fig = plot_density_isosurface_slider(output, T_list)
542
+ return output, fig
543
 
544
  return job,get_job_result
545
 
qlbm/visualize_counts.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from sklearn.neighbors import KernelDensity
3
+ import plotly.graph_objects as go
4
+
5
+ def bitstring_to_xyz(bs):
6
+ """
7
+ Interpret the bitstring `bs` by dividing it into three equal thirds,
8
+ and converting each third to an integer or normalized float.
9
+ Returns (x, y, z).
10
+ """
11
+ n = len(bs)
12
+ assert n % 3 == 0, "Bitstring length must be divisible by 3"
13
+ t = n // 3
14
+ bx = bs[0:t]
15
+ by = bs[t:2*t]
16
+ bz = bs[2*t:3*t]
17
+ # convert each to integer (or normalized in [0,1])
18
+ ix = int(bx, 2)
19
+ iy = int(by, 2)
20
+ iz = int(bz, 2)
21
+ # Optionally normalize by 2^t
22
+ maxv = (1 << t)
23
+ return ix / maxv, iy / maxv, iz / maxv
24
+
25
+ def load_samples(d,T_total):
26
+ pts = []
27
+ counts = []
28
+ for bs, cnt in d.items():
29
+ if bs[:6*T_total] == "0" * 6*T_total:
30
+ if cnt<0:
31
+ continue
32
+ x, y, z = bitstring_to_xyz(bs[6*T_total:])
33
+ pts.append([x, y, z])
34
+ counts.append(cnt)
35
+ pts = np.array(pts)
36
+ counts = np.array(counts)
37
+ print("Number of valid counts:", np.sum(counts))
38
+ print("Number of valid bitstrings:", len(counts))
39
+ # counts=counts*len(counts)*10/np.sum(counts)
40
+ # print(counts)
41
+ return pts, counts
42
+
43
+ def estimate_density(pts, counts, bandwidth=0.05, grid_size=64):
44
+ """
45
+ Fit KDE weighted by counts, and evaluate on a grid.
46
+ Returns (grid_x, grid_y, grid_z, grid_density) where
47
+ grid_x, etc. are 3D mesh arrays, and grid_density has same shape.
48
+ """
49
+ # Expand points by weights: we can replicate points (if counts small),
50
+ # or directly use weights in log-likelihood calculation.
51
+ # sklearn’s KernelDensity doesn’t support sample weights in .fit,
52
+ # but you can approximate by replication or by customizing the KDE.
53
+ # Here, for simplicity, replicate (careful of explosion):
54
+ pts_rep = np.repeat(pts, counts.astype(int), axis=0)
55
+ kde = KernelDensity(bandwidth=bandwidth, kernel='gaussian')
56
+ kde.fit(pts_rep)
57
+
58
+ # create a 3D grid over the bounding box
59
+ # mins = pts.min(axis=0)
60
+ # maxs = pts.max(axis=0)
61
+ mins=[0,0,0]
62
+ maxs=[1,1,1]
63
+ xs = np.linspace(mins[0], maxs[0], grid_size)
64
+ ys = np.linspace(mins[1], maxs[1], grid_size)
65
+ zs = np.linspace(mins[2], maxs[2], grid_size)
66
+ xx, yy, zz = np.meshgrid(xs, ys, zs, indexing='ij')
67
+ grid_coords = np.vstack([xx.ravel(), yy.ravel(), zz.ravel()]).T
68
+
69
+ logdens = kde.score_samples(grid_coords) # log density
70
+ dens = np.exp(logdens)
71
+ dens = dens.reshape(xx.shape)
72
+
73
+ print("Mins:", mins)
74
+ print("Maxs:", maxs)
75
+ print("dens:", dens)
76
+
77
+ return xx, yy, zz, dens
78
+
79
+ def plot_density_isosurface(xx, yy, zz, dens, level=None):
80
+ """
81
+ Plot an isosurface of density using Plotly.
82
+ If level is None, choose a percentile.
83
+ """
84
+ if level is None:
85
+ level = np.percentile(dens, 90) # for example, top 10% density
86
+
87
+ fig = go.Figure(
88
+ data=go.Isosurface(
89
+ x=xx.ravel(),
90
+ y=yy.ravel(),
91
+ z=zz.ravel(),
92
+ value=dens.ravel(),
93
+ isomin=level,
94
+ isomax= dens.max(),
95
+ # surface_count=1, # one isosurface
96
+ opacity=0.4, # needs to be small to see through all surfaces
97
+ surface_count=5, # needs to be a large number for good volume rendering,
98
+ # caps=dict(x_show=False, y_show=False, z_show=False)
99
+ caps=dict(x_show=False, y_show=False, z_show=False),
100
+ showscale=True,
101
+ )
102
+ )
103
+ fig.update_layout(
104
+ scene=dict(
105
+ xaxis_title="x",
106
+ yaxis_title="y",
107
+ zaxis_title="z",
108
+ ),
109
+ title="3D Density Isosurface"
110
+ )
111
+ fig.show(renderer="browser")
112
+
113
+ def plot_density_isosurface_slider(outputs, T_list=None):
114
+ """
115
+ Plot an isosurface of density using Plotly with a slider for timesteps.
116
+ outputs: list of (xx, yy, zz, dens) tuples.
117
+ T_list: list of timestep values corresponding to outputs.
118
+ """
119
+ if not outputs:
120
+ print("No output to plot.")
121
+ return
122
+
123
+ # If T_list is not provided, generate indices
124
+ if T_list is None:
125
+ T_list = list(range(len(outputs)))
126
+
127
+ # Compute global min/max for consistent color scaling
128
+ # dens is the 4th element (index 3) in the tuple (xx, yy, zz, dens)
129
+ all_dens = [out[3] for out in outputs]
130
+ global_min = min(np.min(d) for d in all_dens)
131
+ global_max = max(np.max(d) for d in all_dens)
132
+
133
+ fig = go.Figure()
134
+
135
+ # Add a trace for each timestep
136
+ for i, (xx, yy, zz, dens) in enumerate(outputs):
137
+ visible = (i == 0) # Only the first trace is visible initially
138
+
139
+ fig.add_trace(go.Isosurface(
140
+ x=xx.ravel(),
141
+ y=yy.ravel(),
142
+ z=zz.ravel(),
143
+ value=dens.ravel(),
144
+ isomin=global_min,
145
+ isomax=global_max,
146
+ opacity=0.4,
147
+ surface_count=5,
148
+ caps=dict(x_show=False, y_show=False, z_show=False),
149
+ colorscale='Blues',
150
+ colorbar=dict(title="Density"),
151
+ visible=visible,
152
+ name=f"T={T_list[i]}"
153
+ ))
154
+
155
+ # Create slider steps
156
+ steps = []
157
+ for i, T in enumerate(T_list):
158
+ step = dict(
159
+ method="update",
160
+ args=[{"visible": [False] * len(outputs)},
161
+ {"title": f"QLBM Simulation - Timestep T={T}"}],
162
+ label=str(T)
163
+ )
164
+ step["args"][0]["visible"][i] = True # Toggle i-th trace to True
165
+ steps.append(step)
166
+
167
+ sliders = [dict(
168
+ active=0,
169
+ currentvalue={"prefix": "Timestep: "},
170
+ pad={"t": 50},
171
+ steps=steps
172
+ )]
173
+
174
+ fig.update_layout(
175
+ title=f"QLBM Simulation - Timestep T={T_list[0]}",
176
+ scene=dict(
177
+ xaxis_title="X",
178
+ yaxis_title="Y",
179
+ zaxis_title="Z",
180
+ aspectmode='cube',
181
+ ),
182
+ sliders=sliders
183
+ )
184
+
185
+ # fig.show(renderer="browser")
186
+ return fig
187
+
188
+ # if __name__ == '__main__':
189
+ # pts, counts = load_samples('counts_7_3.json')
190
+ # # pts, counts = load_samples('quasis_8_3.txt')
191
+ # xx, yy, zz, dens = estimate_density(pts, counts, bandwidth=0.05, grid_size=40)
192
+ # plot_density_isosurface(xx, yy, zz, dens)
qlbm_embedded.py CHANGED
@@ -29,6 +29,7 @@ _QISKIT_IMPORT_ERROR = None
29
  try:
30
  from qlbm.qlbm_sample_app import (
31
  run_sampling_sim,
 
32
  get_named_init_state_circuit,
33
  str_to_lambda,
34
  _create_slider_figure,
@@ -995,6 +996,11 @@ def run_simulation():
995
  _state.qlbm_selected_simulator == "IBM Qiskit simulator" and
996
  _QISKIT_BACKEND_AVAILABLE
997
  )
 
 
 
 
 
998
 
999
  # Log initial configuration
1000
  backend_info = f"{_state.qlbm_backend_type}"
@@ -1049,6 +1055,62 @@ def run_simulation():
1049
  log_to_console("Qiskit simulation completed successfully.")
1050
  _state.qlbm_status_message = "Simulation completed successfully."
1051
  _state.qlbm_status_type = "success"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1052
 
1053
  # === CUDA-Q Backend ===
1054
  elif _state.qlbm_backend_type == "Simulator" and _state.qlbm_selected_simulator == "CUDA-Q simulator":
 
29
  try:
30
  from qlbm.qlbm_sample_app import (
31
  run_sampling_sim,
32
+ run_sampling_hw_ibm,
33
  get_named_init_state_circuit,
34
  str_to_lambda,
35
  _create_slider_figure,
 
996
  _state.qlbm_selected_simulator == "IBM Qiskit simulator" and
997
  _QISKIT_BACKEND_AVAILABLE
998
  )
999
+ use_ibm_qpu = (
1000
+ _state.qlbm_backend_type == "QPU" and
1001
+ _state.qlbm_selected_qpu == "IBM QPU" and
1002
+ _QISKIT_BACKEND_AVAILABLE
1003
+ )
1004
 
1005
  # Log initial configuration
1006
  backend_info = f"{_state.qlbm_backend_type}"
 
1055
  log_to_console("Qiskit simulation completed successfully.")
1056
  _state.qlbm_status_message = "Simulation completed successfully."
1057
  _state.qlbm_status_type = "success"
1058
+
1059
+ # === IBM QPU Backend ===
1060
+ elif use_ibm_qpu:
1061
+ log_to_console("Using IBM QPU backend...")
1062
+
1063
+ params = _map_state_to_qiskit_params()
1064
+ if params is None:
1065
+ raise RuntimeError("Failed to map state parameters")
1066
+
1067
+ # Create initial state circuit
1068
+ log_to_console("Creating initial state circuit...")
1069
+ init_state_prep_circ = get_named_init_state_circuit(
1070
+ n=params["n"],
1071
+ init_state_name=params["init_state_name"],
1072
+ sine_k_x=params["sine_k_x"],
1073
+ sine_k_y=params["sine_k_y"],
1074
+ sine_k_z=params["sine_k_z"],
1075
+ gauss_cx=params["gauss_cx"],
1076
+ gauss_cy=params["gauss_cy"],
1077
+ gauss_cz=params["gauss_cz"],
1078
+ gauss_sigma=params["gauss_sigma"],
1079
+ )
1080
+
1081
+ log_to_console("Submitting job to IBM Quantum...")
1082
+
1083
+ # Run HW simulation
1084
+ job, get_result = run_sampling_hw_ibm(
1085
+ n=params["n"],
1086
+ ux=params["vx_expr"],
1087
+ uy=params["vy_expr"],
1088
+ uz=params["vz_expr"],
1089
+ init_state_prep_circ=init_state_prep_circ,
1090
+ T_list=params["T_list"],
1091
+ shots=2**14, # Reduced shots for responsiveness/quota
1092
+ vel_resolution=min(params['grid_size'], 32),
1093
+ output_resolution=40,
1094
+ logger=log_to_console
1095
+ )
1096
+
1097
+ log_to_console("Waiting for job results (this may take time)...")
1098
+ output, plotly_fig = get_result(job)
1099
+
1100
+ # Update UI
1101
+ if hasattr(_ctrl, "qlbm_qiskit_result_update"):
1102
+ _ctrl.qlbm_qiskit_result_update(plotly_fig)
1103
+
1104
+ _state.qlbm_max_time_step = len(output) - 1
1105
+ _state.qlbm_time_val = 0
1106
+ _state.qlbm_time_slider_labels = [f"T={t}" for t in params["T_list"]]
1107
+ _state.qlbm_simulation_has_run = True
1108
+ _state.qlbm_qiskit_mode = True
1109
+
1110
+ _progress_callback(100)
1111
+ log_to_console("IBM QPU simulation completed successfully.")
1112
+ _state.qlbm_status_message = "Simulation completed successfully."
1113
+ _state.qlbm_status_type = "success"
1114
 
1115
  # === CUDA-Q Backend ===
1116
  elif _state.qlbm_backend_type == "Simulator" and _state.qlbm_selected_simulator == "CUDA-Q simulator":