Skip to content

Commit 695bad3

Browse files
committed
Visualize: added 2D/3D plotting/animation scripts
1 parent e6268a1 commit 695bad3

4 files changed

Lines changed: 1898 additions & 0 deletions

File tree

‎visualize/plot2D.py‎

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Fast 2D Voronoi-like visualization using nearest-neighbor rasterization.
4+
Instead of computing Voronoi polygons, each pixel is assigned to the nearest
5+
seed point and colored by its hydro quantity. Handles ~1M cells efficiently.
6+
"""
7+
8+
import argparse
9+
import time
10+
11+
import h5py
12+
import matplotlib.pyplot as plt
13+
import numpy as np
14+
from scipy.spatial import cKDTree
15+
16+
17+
def plot_2d_fast(hdf5_file, quantity='rho', output_file=None, vmin=None, vmax=None,
18+
resolution=1024, cmap_name='viridis', dpi=150, show_seeds=False):
19+
"""
20+
Fast rasterized nearest-neighbor plot of 2D Voronoi mesh.
21+
22+
Parameters
23+
----------
24+
hdf5_file : str
25+
Path to mesh_output HDF5 file.
26+
quantity : str
27+
Hydro quantity to plot: 'rho', 'vel_mag', 'Energy', 'pressure'.
28+
output_file : str or None
29+
Save path; if None, displays interactively.
30+
vmin, vmax : float or None
31+
Colorbar limits (defaults to data range).
32+
resolution : int
33+
Pixel resolution per axis (default 1024 → 1024x1024 image).
34+
cmap_name : str
35+
Matplotlib colormap name.
36+
dpi : int
37+
Output DPI when saving.
38+
show_seeds : bool
39+
Overlay seed points on the image.
40+
"""
41+
t_start = time.perf_counter()
42+
43+
with h5py.File(hdf5_file, 'r') as f:
44+
seeds = f['cells/seeds'][:]
45+
header = f['header']
46+
dimension = header.attrs['dimension']
47+
extent = header.attrs['extent']
48+
49+
if dimension != 2:
50+
print(f"Error: This script is for 2D data only (got {dimension}D).")
51+
return False
52+
53+
rho = f['hydro/rho'][:]
54+
vel = f['hydro/vel'][:]
55+
energy = f['hydro/Energy'][:]
56+
57+
# Compute quantity
58+
if quantity == 'rho':
59+
values = rho
60+
label = 'Density (ρ)'
61+
elif quantity == 'vel_mag':
62+
values = np.sqrt(vel[:, 0]**2 + vel[:, 1]**2)
63+
label = 'Velocity Magnitude |v|'
64+
elif quantity == 'vel_x':
65+
values = vel[:, 0]
66+
label = 'Velocity x'
67+
elif quantity == 'vel_y':
68+
values = vel[:, 1]
69+
label = 'Velocity y'
70+
elif quantity == 'energy':
71+
values = energy
72+
label = 'Energy'
73+
elif quantity == 'pressure':
74+
gamma = 5.0 / 3.0
75+
# Energy is the *total* energy density (internal + kinetic), so
76+
# pressure = (γ-1) * (E - 0.5 ρ |v|²); without the kinetic term we'd
77+
# just be plotting a scaled E.
78+
kinetic = 0.5 * rho * (vel[:, 0] ** 2 + vel[:, 1] ** 2)
79+
values = (gamma - 1.0) * (energy - kinetic)
80+
label = 'Pressure'
81+
else:
82+
raise ValueError(f"Unknown quantity: {quantity}")
83+
84+
if vmin is None:
85+
vmin = values.min()
86+
if vmax is None:
87+
vmax = values.max()
88+
89+
n_cells = len(seeds)
90+
print(f"Cells: {n_cells}")
91+
print(f"Domain: [0, {extent}]²")
92+
print(f"Plotting: {label}")
93+
print(f"Value range: [{values.min():.4e}, {values.max():.4e}]")
94+
print(f"Color range: [{vmin:.4e}, {vmax:.4e}]")
95+
print(f"Resolution: {resolution}x{resolution}")
96+
97+
# --- Build KDTree from seed positions (2D) ---
98+
t0 = time.perf_counter()
99+
tree = cKDTree(seeds[:, :2])
100+
t1 = time.perf_counter()
101+
print(f"KDTree built in {t1 - t0:.3f}s")
102+
103+
# --- Create pixel grid centers ---
104+
pixel_size = extent / resolution
105+
x_centers = np.linspace(pixel_size / 2, extent - pixel_size / 2, resolution)
106+
y_centers = np.linspace(pixel_size / 2, extent - pixel_size / 2, resolution)
107+
xv, yv = np.meshgrid(x_centers, y_centers)
108+
grid_points = np.column_stack([xv.ravel(), yv.ravel()])
109+
110+
# --- Query nearest seed for every pixel ---
111+
t0 = time.perf_counter()
112+
_, indices = tree.query(grid_points, workers=-1)
113+
t1 = time.perf_counter()
114+
print(f"Nearest-neighbor query in {t1 - t0:.3f}s")
115+
116+
# --- Map values onto grid ---
117+
image = values[indices].reshape(resolution, resolution)
118+
119+
# --- Plot ---
120+
fig, ax = plt.subplots(figsize=(10, 9))
121+
122+
cmap = plt.get_cmap(cmap_name)
123+
im = ax.imshow(image, origin='lower', extent=[0, extent, 0, extent],
124+
cmap=cmap, vmin=vmin, vmax=vmax, interpolation='nearest',
125+
aspect='equal')
126+
127+
if show_seeds:
128+
ax.scatter(seeds[:, 0], seeds[:, 1], s=0.1, c='white', edgecolors='none',
129+
alpha=0.3, zorder=5)
130+
131+
ax.set_xlabel('x')
132+
ax.set_ylabel('y')
133+
ax.set_xlim(0, extent)
134+
ax.set_ylim(0, extent)
135+
136+
cbar = plt.colorbar(im, ax=ax, label=label)
137+
plt.title(f'{label} ({n_cells} cells, {resolution}×{resolution} px)')
138+
139+
t_total = time.perf_counter() - t_start
140+
print(f"Total time: {t_total:.3f}s")
141+
142+
plt.tight_layout()
143+
if output_file:
144+
plt.savefig(output_file, dpi=dpi, bbox_inches='tight')
145+
print(f"Saved to: {output_file}")
146+
else:
147+
plt.show()
148+
plt.close()
149+
return True
150+
151+
152+
if __name__ == "__main__":
153+
parser = argparse.ArgumentParser(
154+
description='Fast 2D Voronoi-like plot via nearest-neighbor rasterization')
155+
parser.add_argument('-i', '--input', type=str, default='../output/mesh_output.hdf5',
156+
help='Input HDF5 file (default: ../output/mesh_output.hdf5)')
157+
parser.add_argument('-o', '--output', type=str, default=None,
158+
help='Output image file (if not specified, displays plot)')
159+
parser.add_argument('-q', '--quantity', type=str, default='rho',
160+
choices=['rho', 'vel_mag', 'vel_x', 'vel_y', 'energy', 'pressure'],
161+
help='Quantity to plot (default: rho)')
162+
parser.add_argument('-r', '--resolution', type=int, default=1024,
163+
help='Pixel resolution per axis (default: 1024)')
164+
parser.add_argument('--vmin', type=float, default=None,
165+
help='Minimum value for colorbar')
166+
parser.add_argument('--vmax', type=float, default=None,
167+
help='Maximum value for colorbar')
168+
parser.add_argument('--cmap', type=str, default='viridis',
169+
help='Colormap name (default: viridis)')
170+
parser.add_argument('--dpi', type=int, default=150,
171+
help='Output DPI (default: 150)')
172+
parser.add_argument('--seeds', action='store_true',
173+
help='Overlay seed points on the image')
174+
175+
args = parser.parse_args()
176+
177+
plot_2d_fast(args.input, args.quantity, args.output, args.vmin, args.vmax,
178+
args.resolution, args.cmap, args.dpi, args.seeds)

0 commit comments

Comments
 (0)