Skip to content

Commit 02b5199

Browse files
Guo-astrotdavidcl
andauthored
[GSPH] Add GSPH solver and VTK I/O (#1453)
Co-authored-by: David--Cléris Timothée <timothee.davidcleris@proton.me>
1 parent 999dbb1 commit 02b5199

13 files changed

Lines changed: 4755 additions & 1 deletion

File tree

exemples/common/visualization/animate_sedov_csv.py

Lines changed: 591 additions & 0 deletions
Large diffs are not rendered by default.

exemples/common/visualization/animate_sod_csv.py

Lines changed: 485 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate GIF animation from Sod Shock Tube VTK files.
4+
5+
Works with both SPH and GSPH solver outputs.
6+
Uses shamrock.phys.SodTube for analytical solution (no custom Python implementation).
7+
8+
Usage:
9+
python animate_sod_vtk.py <vtk_dir> [output_dir] [--solver SPH|GSPH]
10+
11+
Examples:
12+
python animate_sod_vtk.py simulations_data/gsph_sod/vtk --solver GSPH
13+
python animate_sod_vtk.py simulations_data/sph_sod/vtk --solver SPH
14+
"""
15+
16+
import argparse
17+
import glob
18+
import os
19+
import sys
20+
21+
import matplotlib
22+
23+
matplotlib.use("Agg")
24+
import matplotlib.pyplot as plt
25+
import numpy as np
26+
import pyvista as pv
27+
from matplotlib.animation import FuncAnimation, PillowWriter
28+
29+
# Import shamrock for analytical solution
30+
try:
31+
import shamrock
32+
33+
HAS_SHAMROCK = True
34+
except ImportError:
35+
HAS_SHAMROCK = False
36+
print("Warning: shamrock module not found. Analytical solution will not be shown.")
37+
38+
39+
def parse_args():
40+
parser = argparse.ArgumentParser(description="Animate Sod shock tube VTK results")
41+
parser.add_argument("vtk_dir", help="Directory containing VTK files")
42+
parser.add_argument(
43+
"output_dir",
44+
nargs="?",
45+
default=None,
46+
help="Output directory (defaults to parent of vtk_dir)",
47+
)
48+
parser.add_argument(
49+
"--solver",
50+
choices=["SPH", "GSPH"],
51+
default="GSPH",
52+
help="Solver type (affects file naming)",
53+
)
54+
parser.add_argument("--gamma", type=float, default=1.4, help="Adiabatic index (default: 1.4)")
55+
parser.add_argument(
56+
"--t-final",
57+
type=float,
58+
default=0.245,
59+
help="Final simulation time (default: 0.245)",
60+
)
61+
parser.add_argument(
62+
"--fps", type=int, default=10, help="Animation frames per second (default: 10)"
63+
)
64+
return parser.parse_args()
65+
66+
67+
def get_analytical_solution(sod, t, x_array):
68+
"""Get analytical solution at multiple x positions using shamrock.phys.SodTube."""
69+
rho = np.zeros(len(x_array))
70+
vel = np.zeros(len(x_array))
71+
pres = np.zeros(len(x_array))
72+
for i, x in enumerate(x_array):
73+
rho[i], vel[i], pres[i] = sod.get_value(t, x)
74+
return x_array, rho, vel, pres
75+
76+
77+
def read_vtk(filename):
78+
"""Read VTK file using pyvista."""
79+
mesh = pv.read(filename)
80+
points = np.array(mesh.points)
81+
velocities = np.array(mesh["v"])
82+
hpart = np.array(mesh["h"])
83+
rho = np.array(mesh["rho"])
84+
P = np.array(mesh["P"])
85+
return points, velocities, hpart, rho, P
86+
87+
88+
def main():
89+
args = parse_args()
90+
91+
vtk_dir = args.vtk_dir
92+
output_dir = args.output_dir or os.path.dirname(vtk_dir)
93+
solver_name = args.solver
94+
gamma = args.gamma
95+
t_final = args.t_final
96+
97+
# Find VTK files
98+
vtk_pattern = os.path.join(vtk_dir, "*.vtk")
99+
vtk_files = sorted(glob.glob(vtk_pattern))
100+
101+
print(f"{'=' * 70}")
102+
print(f"Sod Shock Tube Animation ({solver_name})")
103+
print(f"{'=' * 70}")
104+
print(f"VTK directory: {vtk_dir}")
105+
print(f"Output directory: {output_dir}")
106+
print(f"Found {len(vtk_files)} VTK files")
107+
print()
108+
109+
if len(vtk_files) == 0:
110+
print(f"ERROR: No VTK files found in {vtk_dir}")
111+
sys.exit(1)
112+
113+
n_frames = len(vtk_files)
114+
dt_dump = t_final / n_frames
115+
116+
# Create analytical solver using shamrock.phys.SodTube
117+
sod_solver = None
118+
if HAS_SHAMROCK:
119+
# Standard Sod problem: left state (rho=1, P=1), right state (rho=0.125, P=0.1)
120+
sod_solver = shamrock.phys.SodTube(
121+
gamma=gamma,
122+
rho_1=1.0, # Left density
123+
P_1=1.0, # Left pressure
124+
rho_5=0.125, # Right density
125+
P_5=0.1, # Right pressure
126+
)
127+
print(f"Analytical solution: shamrock.phys.SodTube (gamma={gamma})")
128+
else:
129+
print("Analytical solution: not available")
130+
print()
131+
132+
# Set up figure
133+
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
134+
135+
def update(frame):
136+
vtk_file = vtk_files[frame]
137+
t = frame * dt_dump
138+
139+
# Read data
140+
points, velocities, h, rho, P = read_vtk(vtk_file)
141+
142+
x = points[:, 0]
143+
vx = velocities[:, 0]
144+
145+
# Sort by x
146+
idx = np.argsort(x)
147+
x_sort = x[idx]
148+
rho_sort = rho[idx]
149+
vx_sort = vx[idx]
150+
P_sort = P[idx]
151+
h_sort = h[idx]
152+
153+
# Clear and redraw
154+
for ax in axes.flat:
155+
ax.clear()
156+
157+
# Plot analytical solution if available
158+
if sod_solver is not None and t > 0:
159+
x_ana = np.linspace(-1.0, 1.0, 500)
160+
_, rho_ana, vx_ana, P_ana = get_analytical_solution(sod_solver, t, x_ana)
161+
162+
axes[0, 0].plot(x_ana, rho_ana, "r-", lw=2, label="Analytical")
163+
axes[0, 1].plot(x_ana, vx_ana, "r-", lw=2, label="Analytical")
164+
axes[1, 0].plot(x_ana, P_ana, "r-", lw=2, label="Analytical")
165+
166+
# Density
167+
axes[0, 0].scatter(x_sort, rho_sort, s=1, alpha=0.5, label=solver_name)
168+
axes[0, 0].set_ylabel("Density")
169+
axes[0, 0].set_title("Density")
170+
axes[0, 0].legend()
171+
axes[0, 0].set_xlim(-1.1, 1.1)
172+
axes[0, 0].set_ylim(0, 1.2)
173+
174+
# Velocity
175+
axes[0, 1].scatter(x_sort, vx_sort, s=1, alpha=0.5, label=solver_name)
176+
axes[0, 1].set_ylabel("Velocity")
177+
axes[0, 1].set_title("Velocity")
178+
axes[0, 1].legend()
179+
axes[0, 1].set_xlim(-1.1, 1.1)
180+
axes[0, 1].set_ylim(-0.1, 1.1)
181+
182+
# Pressure
183+
axes[1, 0].scatter(x_sort, P_sort, s=1, alpha=0.5, label=solver_name)
184+
axes[1, 0].set_ylabel("Pressure")
185+
axes[1, 0].set_xlabel("x")
186+
axes[1, 0].set_title("Pressure")
187+
axes[1, 0].legend()
188+
axes[1, 0].set_xlim(-1.1, 1.1)
189+
axes[1, 0].set_ylim(0, 1.2)
190+
191+
# Smoothing length
192+
axes[1, 1].scatter(x_sort, h_sort, s=1, alpha=0.5)
193+
axes[1, 1].set_ylabel("h")
194+
axes[1, 1].set_xlabel("x")
195+
axes[1, 1].set_title("Smoothing Length h")
196+
axes[1, 1].set_xlim(-1.1, 1.1)
197+
198+
fig.suptitle(
199+
f"{solver_name} Sod Shock Tube (t = {t:.3f})",
200+
fontsize=14,
201+
fontweight="bold",
202+
)
203+
plt.tight_layout()
204+
205+
return axes.flat
206+
207+
# Create animation
208+
print("Creating animation...")
209+
anim = FuncAnimation(fig, update, frames=len(vtk_files), interval=100)
210+
211+
# Save as GIF
212+
solver_lower = solver_name.lower()
213+
gif_path = os.path.join(output_dir, f"{solver_lower}_sod_animation.gif")
214+
os.makedirs(output_dir, exist_ok=True)
215+
print(f"Saving to {gif_path}...")
216+
anim.save(gif_path, writer=PillowWriter(fps=args.fps))
217+
print(f"Animation saved to {gif_path}")
218+
219+
# Save final frame as PNG
220+
print("Saving final frame...")
221+
update(len(vtk_files) - 1)
222+
final_path = os.path.join(output_dir, f"{solver_lower}_sod_final.png")
223+
plt.savefig(final_path, dpi=150)
224+
print(f"Final frame saved to {final_path}")
225+
226+
print()
227+
print(f"{'=' * 70}")
228+
print("Done!")
229+
print(f"{'=' * 70}")
230+
231+
232+
if __name__ == "__main__":
233+
main()
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
"""
2+
GSPH Sod Shock Tube Simulation with VTK Output
3+
===============================================
4+
5+
Runs the Sod shock tube test using Godunov SPH (GSPH) with HLLC Riemann solver
6+
and outputs VTK files for visualization.
7+
8+
Uses the same initial conditions as the SPH Sod test for direct comparison.
9+
10+
Output: VTK files in output/ directory with simulation time metadata
11+
"""
12+
13+
import json
14+
import os
15+
16+
import shamrock
17+
18+
# Physical parameters (same as SPH test)
19+
gamma = 1.4
20+
21+
rho_L = 1.0 # Left density
22+
rho_R = 0.125 # Right density
23+
24+
P_L = 1.0 # Left pressure
25+
P_R = 0.1 # Right pressure
26+
27+
# Derived quantities
28+
fact = (rho_L / rho_R) ** (1.0 / 3.0)
29+
u_L = P_L / ((gamma - 1) * rho_L) # Left internal energy
30+
u_R = P_R / ((gamma - 1) * rho_R) # Right internal energy
31+
32+
# Resolution (same as SPH test)
33+
resol = 128
34+
35+
# Initialize context and model
36+
ctx = shamrock.Context()
37+
ctx.pdata_layout_new()
38+
39+
# Use GSPH model with M6 kernel (same as SPH test)
40+
model = shamrock.get_Model_GSPH(context=ctx, vector_type="f64_3", sph_kernel="M6")
41+
42+
# Configure solver
43+
cfg = model.gen_default_config()
44+
45+
# Set HLLC Riemann solver
46+
cfg.set_riemann_hllc()
47+
48+
# Set piecewise constant reconstruction (first-order, most stable)
49+
cfg.set_reconstruct_piecewise_constant()
50+
51+
# Set periodic boundaries (with wall particles for shock tube)
52+
cfg.set_boundary_periodic()
53+
54+
# Set adiabatic EOS
55+
cfg.set_eos_adiabatic(gamma)
56+
57+
# Print configuration
58+
cfg.print_status()
59+
model.set_solver_config(cfg)
60+
61+
model.init_scheduler(int(1e8), 1)
62+
63+
# Setup domain (same as SPH test)
64+
(xs, ys, zs) = model.get_box_dim_fcc_3d(1, resol, 24, 24)
65+
dr = 1 / xs
66+
(xs, ys, zs) = model.get_box_dim_fcc_3d(dr, resol, 24, 24)
67+
68+
model.resize_simulation_box((-xs, -ys / 2, -zs / 2), (xs, ys / 2, zs / 2))
69+
70+
# Setup initial conditions using HCP lattice (same as SPH test)
71+
# Left side: high density (smaller spacing)
72+
model.add_cube_hcp_3d(dr, (-xs, -ys / 2, -zs / 2), (0, ys / 2, zs / 2))
73+
# Right side: low density (larger spacing)
74+
model.add_cube_hcp_3d(dr * fact, (0, -ys / 2, -zs / 2), (xs, ys / 2, zs / 2))
75+
76+
# Set internal energy for left and right states (discontinuity at x=0)
77+
model.set_field_in_box("uint", "f64", u_L, (-xs, -ys / 2, -zs / 2), (0, ys / 2, zs / 2))
78+
model.set_field_in_box("uint", "f64", u_R, (0, -ys / 2, -zs / 2), (xs, ys / 2, zs / 2))
79+
80+
# Set particle mass (same as SPH test)
81+
vol_b = xs * ys * zs
82+
totmass = (rho_R * vol_b) + (rho_L * vol_b)
83+
pmass = model.total_mass_to_part_mass(totmass)
84+
model.set_particle_mass(pmass)
85+
86+
print(f"Total mass: {totmass}")
87+
print(f"Particle mass: {pmass}")
88+
89+
# Set CFL conditions (same as SPH test)
90+
model.set_cfl_cour(0.1)
91+
model.set_cfl_force(0.1)
92+
93+
# Simulation parameters (same as SPH test)
94+
t_final = 0.245
95+
n_outputs = 50
96+
dt_output = t_final / n_outputs
97+
98+
# Track output times
99+
times = []
100+
output_count = 0
101+
102+
# Create output directory
103+
output_dir = "simulations_data/gsph_sod/vtk"
104+
os.makedirs(output_dir, exist_ok=True)
105+
106+
# Initial output
107+
filename = f"{output_dir}/gsph_sod_{output_count:04d}.vtk"
108+
model.do_vtk_dump(filename, True)
109+
times.append({"index": output_count, "time": 0.0, "file": filename})
110+
print(f"Saved: {filename} (t = 0.0)")
111+
output_count += 1
112+
113+
# Time evolution with outputs
114+
t_current = 0.0
115+
t_next_output = dt_output
116+
117+
while t_current < t_final:
118+
# Evolve to next output time or final time
119+
t_target = min(t_next_output, t_final)
120+
model.evolve_until(t_target)
121+
t_current = t_target
122+
123+
# Output VTK
124+
filename = f"{output_dir}/gsph_sod_{output_count:04d}.vtk"
125+
model.do_vtk_dump(filename, True)
126+
times.append({"index": output_count, "time": t_current, "file": filename})
127+
print(f"Saved: {filename} (t = {t_current:.6f})")
128+
output_count += 1
129+
130+
t_next_output += dt_output
131+
132+
# Save times metadata
133+
with open("simulations_data/gsph_sod/times_gsph_sod.json", "w") as f:
134+
json.dump(
135+
{
136+
"method": "GSPH",
137+
"riemann_solver": "HLLC",
138+
"kernel": "M6",
139+
"gamma": gamma,
140+
"rho_L": rho_L,
141+
"rho_R": rho_R,
142+
"P_L": P_L,
143+
"P_R": P_R,
144+
"t_final": t_final,
145+
"outputs": times,
146+
},
147+
f,
148+
indent=2,
149+
)
150+
151+
print(f"\nSimulation complete! {output_count} VTK files saved to {output_dir}/")
152+
print("\nNote: L2 error analysis not available for GSPH model.")
153+
print("Use post-processing scripts for comparison with analytical solution.")

0 commit comments

Comments
 (0)