File size: 9,002 Bytes
2409ff1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3cd4a37
fd3d804
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2409ff1
 
5d45402
 
3cd4a37
5d45402
3cd4a37
 
 
 
 
fd3d804
 
3cd4a37
 
fd3d804
 
 
 
 
3cd4a37
 
fd3d804
2409ff1
fd3d804
3cd4a37
5d45402
 
fd3d804
 
 
2409ff1
5d45402
fd3d804
 
 
 
 
2409ff1
 
fd3d804
2409ff1
 
fd3d804
 
 
 
 
 
 
 
2409ff1
 
 
 
 
 
 
 
fd3d804
 
 
 
 
 
 
 
 
 
 
 
2409ff1
 
fd3d804
2409ff1
 
 
fd3d804
 
 
 
 
 
 
 
 
 
 
 
 
2409ff1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d45402
 
2409ff1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d45402
2409ff1
b0f5437
2409ff1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5d45402
2409ff1
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
import numpy as np
from sklearn.neighbors import KernelDensity
import plotly.graph_objects as go

def bitstring_to_xyz(bs):
    """
    Interpret the bitstring `bs` by dividing it into three equal thirds,
    and converting each third to an integer or normalized float.
    Returns (x, y, z).
    """
    n = len(bs)
    assert n % 3 == 0, "Bitstring length must be divisible by 3"
    t = n // 3
    bx = bs[0:t]
    by = bs[t:2*t]
    bz = bs[2*t:3*t]
    # convert each to integer (or normalized in [0,1])
    ix = int(bx, 2)
    iy = int(by, 2)
    iz = int(bz, 2)
    # Optionally normalize by 2^t
    maxv = (1 << t)
    return ix / maxv, iy / maxv, iz / maxv

def load_samples(d, T_total, logger=None, flag_qubits=False, midcircuit_meas=True):
  """
  Load samples from measurement counts dictionary.
  
  Parameters
  ----------
  d : dict
      Dictionary mapping bitstrings to counts
  T_total : int
      Total number of timesteps (used to determine how many direction bits to check)
  logger : callable, optional
      Function to log messages
  
  Returns
  -------
  pts : ndarray
      Array of (x, y, z) points
  counts : ndarray
      Array of counts for each point
  """
  def log(msg):
      if logger:
          logger(str(msg))
      else:
          print(msg)
  
  pts = []
  counts = []

  if flag_qubits:
    if midcircuit_meas:
      pref_length=12*T_total
    else:
      pref_length=6*(T_total+1)
  else:
    pref_length=6*T_total
      
  
  if d is None or len(d) == 0:
    log("Warning: Empty counts dictionary")
    return np.array(pts), np.array(counts)
  
  # Debug: show sample bitstrings
  sample_keys = list(d.keys())[:3]
  log(f"Sample bitstrings (first 3): {sample_keys}")
  if sample_keys:
    log(f"Bitstring length: {len(sample_keys[0])}")
    log(f"Expected prefix length: {pref_length}")
  
  for bs, cnt in d.items():
    # Check if the direction qubits (first 6*T_total bits) are all zeros
    bs=bs.replace(" ","")
    prefix = bs[:pref_length]
    expected_prefix = "0" * pref_length
    
    if prefix == expected_prefix:
      if cnt < 0:
         continue
      remaining_bits = bs[pref_length:]
      # Check if remaining bits are divisible by 3
      if len(remaining_bits) % 3 != 0:
          log(f"Warning: Remaining bitstring length {len(remaining_bits)} not divisible by 3")
          continue
      x, y, z = bitstring_to_xyz(remaining_bits)
      pts.append([x, y, z])
      counts.append(cnt)
  
  pts = np.array(pts)
  counts = np.array(counts)
  log(f"Number of valid counts: {np.sum(counts)}")
  log(f"Number of valid bitstrings: {len(counts)}")
  
  if len(counts) == 0:
      log("Warning: No bitstrings matched the zero-prefix filter. This may indicate:")
      log("  - All measurement outcomes had non-zero direction qubits")
      log("  - Bitstring format mismatch")
  
  return pts, counts

def estimate_density(pts, counts, bandwidth=0.05, grid_size=64):
    """
    Fit KDE weighted by counts, and evaluate on a grid.
    Returns (grid_x, grid_y, grid_z, grid_density) where
    grid_x, etc. are 3D mesh arrays, and grid_density has same shape.
    """
    # Handle empty input
    if len(pts) == 0 or len(counts) == 0 or np.sum(counts) == 0:
        print("Warning: No valid samples to estimate density. Returning uniform distribution.")
        mins = [0, 0, 0]
        maxs = [1, 1, 1]
        xs = np.linspace(mins[0], maxs[0], grid_size)
        ys = np.linspace(mins[1], maxs[1], grid_size)
        zs = np.linspace(mins[2], maxs[2], grid_size)
        xx, yy, zz = np.meshgrid(xs, ys, zs, indexing='ij')
        dens = np.ones(xx.shape) * 0.001  # Small uniform density
        return xx, yy, zz, dens
    
    # Expand points by weights: we can replicate points (if counts small),
    # or directly use weights in log-likelihood calculation.
    # sklearn's KernelDensity doesn't support sample weights in .fit,
    # but you can approximate by replication or by customizing the KDE.
    # Here, for simplicity, replicate (careful of explosion):
    pts_rep = np.repeat(pts, counts.astype(int), axis=0)
    
    # Handle case where all counts are zero after conversion
    if len(pts_rep) == 0:
        print("Warning: No points after replication. Returning uniform distribution.")
        mins = [0, 0, 0]
        maxs = [1, 1, 1]
        xs = np.linspace(mins[0], maxs[0], grid_size)
        ys = np.linspace(mins[1], maxs[1], grid_size)
        zs = np.linspace(mins[2], maxs[2], grid_size)
        xx, yy, zz = np.meshgrid(xs, ys, zs, indexing='ij')
        dens = np.ones(xx.shape) * 0.001  # Small uniform density
        return xx, yy, zz, dens
    
    kde = KernelDensity(bandwidth=bandwidth, kernel='gaussian')
    kde.fit(pts_rep)

    # create a 3D grid over the bounding box
    # mins = pts.min(axis=0)
    # maxs = pts.max(axis=0)
    mins=[0,0,0]
    maxs=[1,1,1]
    xs = np.linspace(mins[0], maxs[0], grid_size)
    ys = np.linspace(mins[1], maxs[1], grid_size)
    zs = np.linspace(mins[2], maxs[2], grid_size)
    xx, yy, zz = np.meshgrid(xs, ys, zs, indexing='ij')
    grid_coords = np.vstack([xx.ravel(), yy.ravel(), zz.ravel()]).T

    logdens = kde.score_samples(grid_coords)  # log density
    dens = np.exp(logdens)
    dens = dens.reshape(xx.shape)

    dens = (grid_size**3)*dens/np.sum(dens.flatten())

    print("Mins:", mins)
    print("Maxs:", maxs)
    print("dens:", dens)

    return xx, yy, zz, dens

def plot_density_isosurface(xx, yy, zz, dens, level=None):
    """
    Plot an isosurface of density using Plotly.
    If level is None, choose a percentile.
    """
    if level is None:
        level = np.percentile(dens, 90)  # for example, top 10% density

    fig = go.Figure(
        data=go.Isosurface(
            x=xx.ravel(),
            y=yy.ravel(),
            z=zz.ravel(),
            value=dens.ravel(),
            isomin=level,
            isomax= dens.max(),
            # surface_count=1,  # one isosurface
            opacity=0.4, # needs to be small to see through all surfaces
            surface_count=5, # needs to be a large number for good volume rendering,    
            # caps=dict(x_show=False, y_show=False, z_show=False)
            caps=dict(x_show=False, y_show=False, z_show=False),
            showscale=True,
        )
    )
    fig.update_layout(
        scene=dict(
            xaxis_title="x",
            yaxis_title="y",
            zaxis_title="z",
        ),
        title="3D Density Isosurface"
    )
    fig.show(renderer="browser")

def plot_density_isosurface_slider(outputs, T_list=None):
    """
    Plot an isosurface of density using Plotly with a slider for timesteps.
    outputs: list of (xx, yy, zz, dens) tuples.
    T_list: list of timestep values corresponding to outputs.
    """
    if not outputs:
        print("No output to plot.")
        return

    # If T_list is not provided, generate indices
    if T_list is None:
        T_list = list(range(len(outputs)))

    # Compute global min/max for consistent color scaling
    # dens is the 4th element (index 3) in the tuple (xx, yy, zz, dens)
    all_dens = [out[3] for out in outputs]
    global_min = min(np.min(d) for d in all_dens)
    global_max = max(np.max(d) for d in all_dens)
    
    fig = go.Figure()

    # Add a trace for each timestep
    for i, (xx, yy, zz, dens) in enumerate(outputs):
        visible = (i == 0)  # Only the first trace is visible initially
        
        fig.add_trace(go.Isosurface(
            x=xx.ravel(),
            y=yy.ravel(),
            z=zz.ravel(),
            value=dens.ravel(),
            isomin=global_min,
            isomax=global_max,
            opacity=0.4,
            surface_count=10,
            caps=dict(x_show=False, y_show=False, z_show=False),
            colorscale='Turbo',
            colorbar=dict(title="Density"),
            visible=visible,
            name=f"T={T_list[i]}"
        ))

    # Create slider steps
    steps = []
    for i, T in enumerate(T_list):
        step = dict(
            method="update",
            args=[{"visible": [False] * len(outputs)},
                  {"title": f"QLBM Simulation - Timestep T={T}"}],
            label=str(T)
        )
        step["args"][0]["visible"][i] = True  # Toggle i-th trace to True
        steps.append(step)

    sliders = [dict(
        active=0,
        currentvalue={"prefix": "Timestep: "},
        pad={"t": 50},
        steps=steps
    )]

    fig.update_layout(
        title=f"QLBM Simulation - Timestep T={T_list[0]}",
        scene=dict(
            xaxis_title="X",
            yaxis_title="Y",
            zaxis_title="Z",
            aspectmode='cube',
        ),
        sliders=sliders
    )

    # fig.show(renderer="browser")
    return fig

# if __name__ == '__main__':
#     pts, counts = load_samples('counts_7_3.json')
#     # pts, counts = load_samples('quasis_8_3.txt')
#     xx, yy, zz, dens = estimate_density(pts, counts, bandwidth=0.05, grid_size=40)
#     plot_density_isosurface(xx, yy, zz, dens)