Spaces:
Paused
Paused
| 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) | |