Peano
Loading...
Searching...
No Matches
fv-tracers-radial-jet.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 radial jet flow.
8
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.
13
14The radial jet provides an ideal test case because:
15
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
19
20Particles are initialised radially around the jet center and their trajectories are compared
21against the analytical solution to measure interpolation and integration errors.
22"""
23
24
26 file_path,
27 num_rays=20,
28 particles_per_ray=50,
29 r_min=0.05,
30 r_max=1.0,
31 center_x=1.5,
32 center_y=1.5,
33):
34 """
35 Generates a .dat file with particles arranged radially from the domain center.
36
37 Parameters:
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.
45 """
46 import math
47
48 with open(file_path, "w") as file:
49 for i in range(num_rays):
50 theta = 2 * math.pi * i / num_rays # Equally spaced angles
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")
56
57 total = num_rays * particles_per_ray
58 print(
59 f"Generated {total} radial particles in a 3x3 domain centered at ({center_x},{center_y}) and saved to {file_path}"
60 )
61
62
63parser = exahype2.ArgumentParser(
64 "ExaHyPE 2 - Finite Volumes Particle Tracing Testing Script"
65)
66
67interpolation_methods = {
68 "None": 0, # No interpolation (use raw values)
69 "Constant": 1, # Piecewise constant interpolation (nearest neighbor)
70 "Linear": 2, # Piecewise linear interpolation (more accurate)
71}
72parser.add_argument(
73 "-interp", "--interpolation", default="Linear", help="|".join(interpolation_methods)
74)
75
76integration_schemes = {
77 "Static": 0, # No integration (static particles)
78 "Euler": 1, # Explicit Euler method (simple but less accurate)
79 "SemiEuler": 2, # Semi-implicit Euler method (better stability)
80 "Verlet": 3, # Velocity Verlet algorithm (good for orbital mechanics)
81 "Midpoint": 4, # Midpoint method (2nd order Runge-Kutta)
82 "RK3": 5, # 3rd order Runge-Kutta method (high accuracy)
83 "RK4": 6, # 4th order Runge-Kutta method (higher accuracy)
84}
85parser.add_argument(
86 "-integ", "--integration", default="RK4", help="|".join(integration_schemes)
87)
88
89parser.set_defaults(
90 min_depth=3,
91 degrees_of_freedom=64,
92 end_time=0.8,
93)
94
95args = parser.parse_args()
96
97size = [3.0, 3.0]
98offset = [0.0, 0.0]
99max_h = 1.1 * min(size) / (3.0**args.min_depth)
100min_h = max_h * 3.0 ** (-args.amr_levels)
101
102project = exahype2.Project(
103 namespace=["tests", "exahype2", "fv"],
104 project_name=".",
105 directory=".",
106 executable="ExaHyPE",
107)
108
109if args.number_of_snapshots <= 0:
110 time_in_between_plots = 0.0
111else:
112 time_in_between_plots = args.end_time / args.number_of_snapshots
113 project.set_output_path(args.output)
114
115project.set_global_simulation_parameters(
116 dimensions=2,
117 size=size,
118 offset=offset,
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,
123 periodic_BC=[
124 False,
125 False,
126 ],
127)
128
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;
136
137 Q[0] = 1.0; // rho
138 Q[1] = dx / r2; // rho * u
139 Q[2] = dy / r2; // rho * v
140 Q[3] = 1.0; // rho * e (just constant baseline)
141"""
142
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];
149"""
150
151flux = """
152 for (int i = 0; i < NumberOfUnknowns; i++) {
153 F[i] = 0.0;
154 }
155"""
156
157max_eigenvalue = """
158 return 1.0;
159"""
160
161analytical_solution = """
162 // Analytical solution for radial jet with velocity field v = (x/r^2, y/r^2)
163 constexpr double Epsilon = 1e-3;
164
165 // Jet center
166 const double x_c = 1.5;
167 const double y_c = 1.5;
168
169 // Initial position relative to center
170 const double dx0 = particle->getData(1) - x_c;
171 const double dy0 = particle->getData(2) - y_c;
172
173 // Initial squared distance
174 const double r0_2 = dx0 * dx0 + dy0 * dy0 + Epsilon * Epsilon;
175
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
179
180 // Updated position
181 solution[0] = x_c + dx0 * scale; // x(t)
182 solution[1] = y_c + dy0 * scale; // y(t)
183
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))
188
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)
192
193 // Acceleration
194 solution[7] = 0.0;
195 solution[8] = 0.0;
196
197 solution[2] = 1.0; // rho
198 solution[9] = 1.0; // mass
199"""
200
201fv_solver = exahype2.solvers.fv.godunov.GlobalFixedTimeStep(
202 name="FVSolver",
203 patch_size=args.degrees_of_freedom,
204 unknowns={"rho": 1, "u": 1, "v": 1, "e": 1},
205 auxiliary_variables=0,
206 min_volume_h=min_h,
207 max_volume_h=max_h,
208 normalised_time_step_size=0.0025,
209)
210
211fv_solver.set_implementation(
212 initial_conditions=initial_conditions,
213 boundary_conditions=boundary_conditions,
214 max_eigenvalue=max_eigenvalue,
215 flux=flux,
216)
217
218project.add_solver(fv_solver)
219
220tracer_attributes = {
221 "unknowns": {
222 "rho": 1,
223 "init_x": 1,
224 "init_y": 1, # Interpolated fluid velocity
225 "u": 1,
226 "v": 1, # Particle's own velocity
227 "a_x": 1,
228 "a_y": 1,
229 "d": 1,
230 }
231}
232
233tracer_particles = fv_solver.add_tracer(
234 name="Tracers", particle_attributes=tracer_attributes
235)
236
238 "InitTracers.dat", num_rays=36, particles_per_ray=5, r_min=0.1, r_max=0.5
239)
240init_action_set = exahype2.tracer.InsertParticlesFromFile(
241 particle_set=tracer_particles,
242 filename="InitTracers.dat",
243 scale_factor=1.0,
244 save_initialpos=True,
245)
246
247init_action_set.descend_invocation_order = 0
248project.add_action_set_to_initialisation(init_action_set)
249
250tracing_action_set = exahype2.tracer.FiniteVolumesTracingWithForce(
251 tracer_particles,
252 fv_solver,
253 project,
254 integration_scheme=integration_schemes[args.integration],
255 interpolation_scheme=interpolation_methods[args.interpolation],
256 force_method=0,
257 fields_to_interpolate="0,3,4",
258)
259
260tracing_action_set.descend_invocation_order = (
261 fv_solver._action_set_update_patch.descend_invocation_order + 1
262)
263
264project.add_action_set_to_timestepping(tracing_action_set)
265
266from pathlib import Path
267
268error_measurement = exahype2.solvers.fv.ErrorMeasurementParticles(
269 fv_solver,
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, # After all other action sets
274)
275
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.