7This is a particle tracing convergence test for radial jet flow.
9This simulation implements a radial jet velocity field v = r/r² emanating from a central point,
10designed to test the convergence properties of different interpolation schemes. In this one-way
11coupling setup, particles are advected through the analytical velocity field, allowing for
12precise comparison between numerical and analytical solutions.
14The radial jet provides an ideal test case because:
16- It has a known analytical solution for particle trajectories
17- It tests interpolation accuracy across varying field gradients
18- It validates integration scheme performance in divergent flow fields
20Particles are initialised radially around the jet center and their trajectories are compared
21against the analytical solution to measure interpolation and integration errors.
35 Generates a .dat file with particles arranged radially from the domain center.
38 file_path (str): Path to save the .dat file.
39 num_rays (int): Number of angular directions (rays).
40 particles_per_ray (int): Number of particles along each ray.
41 r_min (float): Minimum distance from center (to avoid singularity).
42 r_max (float): Maximum distance from center.
43 center_x (float): X-coordinate of the radial center.
44 center_y (float): Y-coordinate of the radial center.
48 with open(file_path,
"w")
as file:
49 for i
in range(num_rays):
50 theta = 2 * math.pi * i / num_rays
51 for j
in range(particles_per_ray):
52 r = r_min + j * (r_max - r_min) / (particles_per_ray - 1)
53 x = center_x + r * math.cos(theta)
54 y = center_y + r * math.sin(theta)
55 file.write(f
"{x:.6f} {y:.6f}\n")
57 total = num_rays * particles_per_ray
59 f
"Generated {total} radial particles in a 3x3 domain centered at ({center_x},{center_y}) and saved to {file_path}"
63parser = exahype2.ArgumentParser(
64 "ExaHyPE 2 - Finite Volumes Particle Tracing Testing Script"
67interpolation_methods = {
73 "-interp",
"--interpolation", default=
"Linear", help=
"|".join(interpolation_methods)
76integration_schemes = {
86 "-integ",
"--integration", default=
"RK4", help=
"|".join(integration_schemes)
91 degrees_of_freedom=64,
95args = parser.parse_args()
99max_h = 1.1 * min(size) / (3.0**args.min_depth)
100min_h = max_h * 3.0 ** (-args.amr_levels)
102project = exahype2.Project(
103 namespace=[
"tests",
"exahype2",
"fv"],
106 executable=
"ExaHyPE",
109if args.number_of_snapshots <= 0:
110 time_in_between_plots = 0.0
112 time_in_between_plots = args.end_time / args.number_of_snapshots
113 project.set_output_path(args.output)
115project.set_global_simulation_parameters(
119 min_end_time=args.end_time,
120 max_end_time=args.end_time,
121 first_plot_time_stamp=0.0,
122 time_in_between_plots=time_in_between_plots,
129initial_conditions =
"""
130 constexpr double Epsilon = 1e-3;
131 const double x0 = 1.5; // Jet center x
132 const double y0 = 1.5; // Jet center y
133 const double dx = x(0) - x0;
134 const double dy = x(1) - y0;
135 const double r2 = dx * dx + dy * dy + Epsilon * Epsilon;
138 Q[1] = dx / r2; // rho * u
139 Q[2] = dy / r2; // rho * v
140 Q[3] = 1.0; // rho * e (just constant baseline)
143boundary_conditions =
"""
144 // Reflective boundary conditions
145 Qoutside[0] = Qinside[0];
146 Qoutside[1] = -Qinside[1];
147 Qoutside[2] = -Qinside[2];
148 Qoutside[3] = Qinside[3];
152 for (int i = 0; i < NumberOfUnknowns; i++) {
161analytical_solution =
"""
162 // Analytical solution for radial jet with velocity field v = (x/r^2, y/r^2)
163 constexpr double Epsilon = 1e-3;
166 const double x_c = 1.5;
167 const double y_c = 1.5;
169 // Initial position relative to center
170 const double dx0 = particle->getData(1) - x_c;
171 const double dy0 = particle->getData(2) - y_c;
173 // Initial squared distance
174 const double r0_2 = dx0 * dx0 + dy0 * dy0 + Epsilon * Epsilon;
176 // Current distance scaling
177 const double r_t_2 = r0_2 + 2.0 * t; // r(t)^2 = r0^2 + 2t
178 const double scale = sqrt(r_t_2 / r0_2); // r(t)/r0
181 solution[0] = x_c + dx0 * scale; // x(t)
182 solution[1] = y_c + dy0 * scale; // y(t)
184 // Fluid velocity at particle position at time t
185 const double inv_rt2 = 1.0 / r_t_2;
186 solution[3] = (solution[0] - x_c) * inv_rt2; // u(x(t), y(t))
187 solution[4] = (solution[1] - y_c) * inv_rt2; // v(x(t), y(t))
189 // Particle velocity (same as fluid)
190 solution[5] = dx0 / r_t_2; // u(t) = dx0 / (r0^2 + 2t)
191 solution[6] = dy0 / r_t_2; // v(t) = dy0 / (r0^2 + 2t)
197 solution[2] = 1.0; // rho
198 solution[9] = 1.0; // mass
201fv_solver = exahype2.solvers.fv.godunov.GlobalFixedTimeStep(
203 patch_size=args.degrees_of_freedom,
204 unknowns={
"rho": 1,
"u": 1,
"v": 1,
"e": 1},
205 auxiliary_variables=0,
208 normalised_time_step_size=0.0025,
211fv_solver.set_implementation(
212 initial_conditions=initial_conditions,
213 boundary_conditions=boundary_conditions,
214 max_eigenvalue=max_eigenvalue,
218project.add_solver(fv_solver)
233tracer_particles = fv_solver.add_tracer(
234 name=
"Tracers", particle_attributes=tracer_attributes
238 "InitTracers.dat", num_rays=36, particles_per_ray=5, r_min=0.1, r_max=0.5
240init_action_set = exahype2.tracer.InsertParticlesFromFile(
241 particle_set=tracer_particles,
242 filename=
"InitTracers.dat",
244 save_initialpos=
True,
247init_action_set.descend_invocation_order = 0
248project.add_action_set_to_initialisation(init_action_set)
250tracing_action_set = exahype2.tracer.FiniteVolumesTracingWithForce(
254 integration_scheme=integration_schemes[args.integration],
255 interpolation_scheme=interpolation_methods[args.interpolation],
257 fields_to_interpolate=
"0,3,4",
260tracing_action_set.descend_invocation_order = (
261 fv_solver._action_set_update_patch.descend_invocation_order + 1
264project.add_action_set_to_timestepping(tracing_action_set)
266from pathlib
import Path
268error_measurement = exahype2.solvers.fv.ErrorMeasurementParticles(
270 error_measurement_implementation=analytical_solution,
271 output_file_name=args.output +
"/Error_" + Path(__file__).name,
272 particle_set=tracer_particles,
273 descend_invocation_order=65536,
276project.set_load_balancer(
"new ::exahype2::LoadBalancingConfiguration")
277project.set_build_mode(mode=peano4.output.string_to_mode(args.build_mode))
278project = project.generate_Peano4_project(verbose=
False)
279project.build(make=
True, make_clean_first=
True, throw_away_data_after_build=
True)
generate_radial_particles(file_path, num_rays=20, particles_per_ray=50, r_min=0.05, r_max=1.0, center_x=1.5, center_y=1.5)
Generates a .dat file with particles arranged radially from the domain center.