7This is a particle tracing convergence test for linear plume rise.
9This simulation implements a linear velocity field v = (0, ay) where particles experience
10exponential vertical acceleration, designed to test the convergence properties of different
11interpolation and integration schemes. In this one-way coupling setup, particles are advected
12through the analytical velocity field, allowing for precise comparison between numerical and
15The linear plume rise provides an ideal test case because:
17- It has a known analytical solution: y(t) = y₀ * e^(at)
18- It tests interpolation accuracy in a simple gradient field
19- It validates integration scheme performance under exponential growth
20- Particles maintain constant x-position while rising exponentially in y
22Particles are initialised along a vertical line and their trajectories are compared
23against the analytical solution to measure interpolation and integration errors.
28 file_path, num_particles=100, x_fixed=0.5, y_min=0.1, y_max=0.9
31 Generates a .dat file with particles uniformly spaced along a vertical line at x = x_fixed.
34 file_path (str): Path to save the .dat file.
35 num_particles (int): Number of particles to generate.
36 x_fixed (float): Fixed x-coordinate for all particles.
37 y_min (float): Minimum y-coordinate.
38 y_max (float): Maximum y-coordinate.
43 step = (y_max - y_min) / (num_particles - 1)
44 y_values = [round(y_min + i * step, 4)
for i
in range(num_particles)]
46 with open(file_path,
"w")
as file:
48 file.write(f
"{x_fixed:.4f} {y:.4f}\n")
51 f
"Generated {num_particles} particles along vertical line at x = {x_fixed} and saved to {file_path}"
55parser = exahype2.ArgumentParser(
56 "ExaHyPE 2 - Finite Volumes Particle Tracing Testing Script"
59interpolation_methods = {
65 "-interp",
"--interpolation", default=
"Linear", help=
"|".join(interpolation_methods)
68integration_schemes = {
78 "-integ",
"--integration", default=
"SemiEuler", help=
"|".join(integration_schemes)
87 "-grav",
"--gravity", default=
"Central", help=
"|".join(gravity_models)
92 degrees_of_freedom=64,
96args = parser.parse_args()
100max_h = 1.1 * min(size) / (3.0**args.min_depth)
101min_h = max_h * 3.0 ** (-args.amr_levels)
103project = exahype2.Project(
104 namespace=[
"tests",
"exahype2",
"fv"],
107 executable=
"ExaHyPE",
110if args.number_of_snapshots <= 0:
111 time_in_between_plots = 0.0
113 time_in_between_plots = args.end_time / args.number_of_snapshots
114 project.set_output_path(args.output)
116project.set_global_simulation_parameters(
120 min_end_time=args.end_time,
121 max_end_time=args.end_time,
122 first_plot_time_stamp=0.0,
123 time_in_between_plots=time_in_between_plots,
125 args.periodic_boundary_conditions_x,
126 args.periodic_boundary_conditions_y,
130initial_conditions =
"""
131 Q[Shortcuts::rho] = 1.0;
132 Q[Shortcuts::rhoU + 0] = 0.0; // rho * u_x
133 Q[Shortcuts::rhoU + 1] = 0.5 * x(1); // rho * u_y
134 Q[Shortcuts::rhoE] = 1.0; // Simple energy (rho*e)
137boundary_conditions =
"""
138 // Reflective boundary conditions
139 Qoutside[Shortcuts::rho] = Qinside[Shortcuts::rho];
140 Qoutside[Shortcuts::rhoU + 0] = -Qinside[Shortcuts::rhoU + 0];
141 Qoutside[Shortcuts::rhoU + 1] = -Qinside[Shortcuts::rhoU + 1];
142 Qoutside[Shortcuts::rhoE] = Qinside[Shortcuts::rhoE];
146 for (int i = 0; i < NumberOfUnknowns; i++) {
155analytical_solution =
"""
156 // Analytical solution for linear plume rise with exponential velocity growth in y-direction
157 constexpr double a = 0.5; // Velocity gradient coefficient
159 // Initial particle position (x0, y0)
160 const double x0 = particle->getData(1); // x doesn't change
161 const double y0 = particle->getData(2); // Used to reconstruct y(t) analytically
163 // Time-dependent solution
164 solution[0] = x0; // x(t) = x0 (no x motion)
165 solution[1] = y0 * exp(a * t); // y(t) = y0 * e^(a t)
167 // Fluid velocity field at particle position
168 solution[3] = 0.0; // fluid_u (x-direction)
169 solution[4] = a * solution[1]; // fluid_v = a * y
171 // Particle velocity (matches fluid)
172 solution[5] = 0.0; // u = dx/dt
173 solution[6] = a * solution[1]; // v = dy/dt
175 // Particle acceleration
176 solution[7] = 0.0; // a_x
177 solution[8] = a * a * solution[1]; // a_y = d^2y/dt^2 = a^2 * y(t)
179 solution[2] = 1.0; // rho
180 solution[9] = 1.0; // mass
183fv_solver = exahype2.solvers.fv.godunov.GlobalFixedTimeStep(
185 patch_size=args.degrees_of_freedom,
186 unknowns={
"rho": 1,
"rhoU": 2,
"rhoE": 1},
187 auxiliary_variables=0,
190 normalised_time_step_size=0.01,
193fv_solver.set_implementation(
194 initial_conditions=initial_conditions,
195 boundary_conditions=boundary_conditions,
196 max_eigenvalue=max_eigenvalue,
200project.add_solver(fv_solver)
215tracer_particles = fv_solver.add_tracer(
216 name=
"Tracers", particle_attributes=tracer_attributes
220 "InitTracers.dat", num_particles=1000, x_fixed=1.5, y_min=0.1, y_max=0.7
222init_tracers = exahype2.tracer.InsertParticlesFromFile(
223 particle_set=tracer_particles,
224 filename=
"InitTracers.dat",
226 save_initialpos=
True,
229init_tracers.descend_invocation_order = 0
230project.add_action_set_to_initialisation(init_tracers)
232tracing_action_set = exahype2.tracer.FiniteVolumesTracingWithForce(
236 integration_scheme=integration_schemes[args.integration],
237 interpolation_scheme=interpolation_methods[args.interpolation],
240tracing_action_set.descend_invocation_order = (
241 fv_solver._action_set_update_patch.descend_invocation_order + 1
244project.add_action_set_to_timestepping(tracing_action_set)
246from pathlib
import Path
248error_measurement = exahype2.solvers.fv.ErrorMeasurementParticles(
250 error_measurement_implementation=analytical_solution,
251 output_file_name=args.output +
"/Error_" + Path(__file__).name,
252 particle_set=tracer_particles,
253 descend_invocation_order=65536,
256project.set_load_balancer(
"new ::exahype2::LoadBalancingConfiguration")
257project.set_build_mode(mode=peano4.output.string_to_mode(args.build_mode))
258project = project.generate_Peano4_project(verbose=
False)
259project.build(make=
True, make_clean_first=
True, throw_away_data_after_build=
True)
generate_particles_on_vertical_line(file_path, num_particles=100, x_fixed=0.5, y_min=0.1, y_max=0.9)
Generates a .dat file with particles uniformly spaced along a vertical line at x = x_fixed.