Peano
Loading...
Searching...
No Matches
fv-tracers-vertical-plume-rise.py
Go to the documentation of this file.
1# This file is part of the ExaHyPE2 project. For conditions of distribution and
2# use, please see the copyright notice at www.peano-framework.org
3import peano4
4import exahype2
5
6"""
7This is a particle tracing convergence test for linear plume rise.
8
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
13analytical solutions.
14
15The linear plume rise provides an ideal test case because:
16
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
21
22Particles are initialised along a vertical line and their trajectories are compared
23against the analytical solution to measure interpolation and integration errors.
24"""
25
26
28 file_path, num_particles=100, x_fixed=0.5, y_min=0.1, y_max=0.9
29):
30 """
31 Generates a .dat file with particles uniformly spaced along a vertical line at x = x_fixed.
32
33 Parameters:
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.
39 """
40 if num_particles < 2:
41 y_values = [0.5] # Fallback: single particle at center
42 else:
43 step = (y_max - y_min) / (num_particles - 1)
44 y_values = [round(y_min + i * step, 4) for i in range(num_particles)]
45
46 with open(file_path, "w") as file:
47 for y in y_values:
48 file.write(f"{x_fixed:.4f} {y:.4f}\n")
49
50 print(
51 f"Generated {num_particles} particles along vertical line at x = {x_fixed} and saved to {file_path}"
52 )
53
54
55parser = exahype2.ArgumentParser(
56 "ExaHyPE 2 - Finite Volumes Particle Tracing Testing Script"
57)
58
59interpolation_methods = {
60 "None": 0, # No interpolation (use raw values)
61 "Constant": 1, # Piecewise constant interpolation (nearest neighbor)
62 "Linear": 2, # Piecewise linear interpolation (more accurate)
63}
64parser.add_argument(
65 "-interp", "--interpolation", default="Linear", help="|".join(interpolation_methods)
66)
67
68integration_schemes = {
69 "Static": 0, # No integration (static particles)
70 "Euler": 1, # Explicit Euler method (simple but less accurate)
71 "SemiEuler": 2, # Semi-implicit Euler method (better stability)
72 "Verlet": 3, # Velocity Verlet algorithm (good for orbital mechanics)
73 "Midpoint": 4, # Midpoint method (2nd order Runge-Kutta)
74 "RK3": 5, # 3rd order Runge-Kutta method (high accuracy)
75 "RK4": 6, # 4th order Runge-Kutta method (higher accuracy)
76}
77parser.add_argument(
78 "-integ", "--integration", default="SemiEuler", help="|".join(integration_schemes)
79)
80
81gravity_models = {
82 "None": 0, # No gravity (straight-line motion)
83 "Central": 1, # Central gravitational force (1/r²) from a fixed point
84 "Custom": 2, # User-defined gravitational field
85}
86parser.add_argument(
87 "-grav", "--gravity", default="Central", help="|".join(gravity_models)
88)
89
90parser.set_defaults(
91 min_depth=3,
92 degrees_of_freedom=64,
93 end_time=1.0,
94)
95
96args = parser.parse_args()
97
98size = [3.0, 3.0]
99offset = [0.0, 0.0]
100max_h = 1.1 * min(size) / (3.0**args.min_depth)
101min_h = max_h * 3.0 ** (-args.amr_levels)
102
103project = exahype2.Project(
104 namespace=["tests", "exahype2", "fv"],
105 project_name=".",
106 directory=".",
107 executable="ExaHyPE",
108)
109
110if args.number_of_snapshots <= 0:
111 time_in_between_plots = 0.0
112else:
113 time_in_between_plots = args.end_time / args.number_of_snapshots
114 project.set_output_path(args.output)
115
116project.set_global_simulation_parameters(
117 dimensions=2,
118 size=size,
119 offset=offset,
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,
124 periodic_BC=[
125 args.periodic_boundary_conditions_x,
126 args.periodic_boundary_conditions_y,
127 ],
128)
129
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)
135"""
136
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];
143"""
144
145flux = """
146 for (int i = 0; i < NumberOfUnknowns; i++) {
147 F[i] = 0.0;
148 }
149"""
150
151max_eigenvalue = """
152 return 1.0;
153"""
154
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
158
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
162
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)
166
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
170
171 // Particle velocity (matches fluid)
172 solution[5] = 0.0; // u = dx/dt
173 solution[6] = a * solution[1]; // v = dy/dt
174
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)
178
179 solution[2] = 1.0; // rho
180 solution[9] = 1.0; // mass
181"""
182
183fv_solver = exahype2.solvers.fv.godunov.GlobalFixedTimeStep(
184 name="FVSolver",
185 patch_size=args.degrees_of_freedom,
186 unknowns={"rho": 1, "rhoU": 2, "rhoE": 1},
187 auxiliary_variables=0,
188 min_volume_h=min_h,
189 max_volume_h=max_h,
190 normalised_time_step_size=0.01,
191)
192
193fv_solver.set_implementation(
194 initial_conditions=initial_conditions,
195 boundary_conditions=boundary_conditions,
196 max_eigenvalue=max_eigenvalue,
197 flux=flux,
198)
199
200project.add_solver(fv_solver)
201
202tracer_attributes = {
203 "unknowns": {
204 "rho": 1,
205 "init_x": 1,
206 "init_y": 1, # Interpolated fluid velocity
207 "u": 1,
208 "v": 1, # Particle's own velocity
209 "a_x": 1,
210 "a_y": 1,
211 "d": 1,
212 }
213}
214
215tracer_particles = fv_solver.add_tracer(
216 name="Tracers", particle_attributes=tracer_attributes
217)
218
220 "InitTracers.dat", num_particles=1000, x_fixed=1.5, y_min=0.1, y_max=0.7
221)
222init_tracers = exahype2.tracer.InsertParticlesFromFile(
223 particle_set=tracer_particles,
224 filename="InitTracers.dat",
225 scale_factor=1.0,
226 save_initialpos=True,
227)
228
229init_tracers.descend_invocation_order = 0
230project.add_action_set_to_initialisation(init_tracers)
231
232tracing_action_set = exahype2.tracer.FiniteVolumesTracingWithForce(
233 tracer_particles,
234 fv_solver,
235 project,
236 integration_scheme=integration_schemes[args.integration],
237 interpolation_scheme=interpolation_methods[args.interpolation],
238)
239
240tracing_action_set.descend_invocation_order = (
241 fv_solver._action_set_update_patch.descend_invocation_order + 1
242)
243
244project.add_action_set_to_timestepping(tracing_action_set)
245
246from pathlib import Path
247
248error_measurement = exahype2.solvers.fv.ErrorMeasurementParticles(
249 fv_solver,
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, # After all other action sets
254)
255
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.