Peano
Loading...
Searching...
No Matches
fv-tracers-drag-force-validation.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 settling simulation with buoyancy and drag forces.
8
9This simulation models the dynamics of spherical particles settling under the combined
10influence of gravity, buoyancy, and Stokes drag forces. The particles experience:
11
12- Gravitational force: F_g = mg (downward)
13- Buoyancy force: F_b = ρ_fluid * V * g (upward)
14- Stokes drag: F_d = 6πμRv (opposing motion)
15
16The analytical solution describes the exponential approach to terminal velocity:
17v(t) = -v_s * (1 - e^(-t/τ)),
18where v_s is terminal velocity and τ is the relaxation time.
19
20This provides a validation test for:
21
22- Multi-physics force integration (gravity + buoyancy + drag)
23- Particle-fluid interaction through interpolated density fields
24- Transient dynamics approaching steady-state terminal velocity
25
26Particles with different masses and radii are initialised to test the scaling
27of settling behavior with particle properties.
28"""
29
30parser = exahype2.ArgumentParser(
31 "ExaHyPE 2 - Finite Volumes Particle Tracing Testing Script"
32)
33
34interpolation_methods = {
35 "None": 0, # No interpolation (use raw values)
36 "Constant": 1, # Piecewise constant interpolation (nearest neighbor)
37 "Linear": 2, # Piecewise linear interpolation (more accurate)
38}
39parser.add_argument(
40 "-interp", "--interpolation", default="Linear", help="|".join(interpolation_methods)
41)
42
43integration_schemes = {
44 "Static": 0, # No integration (static particles)
45 "Euler": 1, # Explicit Euler method (simple but less accurate)
46 "SemiEuler": 2, # Semi-implicit Euler method (better stability)
47 "Verlet": 3, # Velocity Verlet algorithm (good for orbital mechanics)
48 "Midpoint": 4, # Midpoint method (2nd order Runge-Kutta)
49 "RK3": 5, # 3rd order Runge-Kutta method (high accuracy)
50 "RK4": 6, # 4th order Runge-Kutta method (higher accuracy)
51}
52parser.add_argument(
53 "-integ", "--integration", default="SemiEuler", help="|".join(integration_schemes)
54)
55
56parser.set_defaults(
57 min_depth=3,
58 degrees_of_freedom=64,
59 end_time=2.0,
60)
61
62args = parser.parse_args()
63
64size = [3.0, 3.0]
65offset = [0.0, 0.0]
66max_h = 1.1 * min(size) / (3.0**args.min_depth)
67min_h = max_h * 3.0 ** (-args.amr_levels)
68
69project = exahype2.Project(
70 namespace=["tests", "exahype2", "fv"],
71 project_name=".",
72 directory=".",
73 executable="ExaHyPE",
74)
75
76if args.number_of_snapshots <= 0:
77 time_in_between_plots = 0.0
78else:
79 time_in_between_plots = args.end_time / args.number_of_snapshots
80 project.set_output_path(args.output)
81
82project.set_global_simulation_parameters(
83 dimensions=2,
84 size=size,
85 offset=offset,
86 min_end_time=args.end_time,
87 max_end_time=args.end_time,
88 first_plot_time_stamp=0.0,
89 time_in_between_plots=time_in_between_plots,
90 periodic_BC=[
91 args.periodic_boundary_conditions_x,
92 args.periodic_boundary_conditions_y,
93 ],
94)
95
96initial_conditions = """
97 for (int i = 0; i < NumberOfUnknowns + NumberOfAuxiliaryVariables; i++) {
98 Q[i] = 0.0;
99 }
100 Q[0] = 1.0;
101"""
102
103boundary_conditions = """
104 for (int i = 0; i < NumberOfUnknowns + NumberOfAuxiliaryVariables; i++) {
105 Qoutside[i] = Qinside[i];
106 }
107"""
108
109flux = """
110 for (int i = 0; i < NumberOfUnknowns; i++) {
111 F[i] = 0.0;
112 }
113"""
114
115max_eigenvalue = """
116 return 1.0;
117"""
118
119analytical_solution = """
120 constexpr double g = 1.0; // Gravitational acceleration
121 constexpr double mu = 1.0; // Dynamic viscosity
122
123 // Pull particle properties
124 const double mass = particle->getData(7);
125 const double radius = particle->getData(8);
126
127 // Compute volume & densities
128 double volume = (4.0/3.0) * M_PI * radius*radius*radius;
129 double rho_particle = mass / volume;
130 double rho_fluid = 1.0;
131 double density_difference = rho_particle - rho_fluid;
132
133 // Buoyant acceleration (effective g)
134 const double a_eff = density_difference * g / rho_particle;
135 // Stokes drag relaxation time
136 const double tau = mass / (6.0 * M_PI * mu * radius);
137 // Terminal speed (buoyancy + drag)
138 const double vs = a_eff * tau;
139
140 // Transient solution
141 const double expTerm = std::exp(- t / tau);
142 const double vy = -vs * (1.0 - expTerm);
143 const double y = 2.5 + (
144 - vs * t // Long-term drift
145 + vs * tau // Offset so y(0)=2.5
146 * (1.0 - expTerm)
147 );
148
149 solution[0] = 1.5; // x(t)=const
150 solution[1] = y; // y(t)
151
152 solution[2] = rho_fluid;
153 solution[3] = 0.0; // Fluid u
154 solution[4] = 0.0; // Fluid v
155
156 solution[5] = 0.0; // Particle u
157 solution[6] = vy; // Particle v(t)
158
159 solution[7] = mass; // Keep mass at index 7
160 solution[8] = radius; // Keep radius at index 8
161"""
162
163fv_solver = exahype2.solvers.fv.godunov.GlobalFixedTimeStep(
164 name="FVSolver",
165 patch_size=args.degrees_of_freedom,
166 unknowns=3,
167 auxiliary_variables=0,
168 min_volume_h=min_h,
169 max_volume_h=max_h,
170 normalised_time_step_size=0.001,
171)
172
173fv_solver.set_implementation(
174 initial_conditions=initial_conditions,
175 boundary_conditions=boundary_conditions,
176 max_eigenvalue=max_eigenvalue,
177 flux=flux,
178)
179
180project.add_solver(fv_solver)
181
182tracer_attributes = {
183 "unknowns": {
184 "rho": 1,
185 "fluid_u": 1,
186 "fluid_v": 1,
187 "u": 1,
188 "v": 1,
189 "a_x": 1,
190 "a_y": 1,
191 "m": 1,
192 "d": 1,
193 }
194}
195
196tracer_particles = fv_solver.add_tracer(
197 name="Tracers", particle_attributes=tracer_attributes
198)
199
200init_tracers = exahype2.tracer.InsertParticlesByCoordinatesAndData(
201 particle_set=tracer_particles,
202 unknowns_per_particle=len(tracer_attributes["unknowns"]),
203 initial_data="""
204 {
205 2.0, 2.5, 0.0, 0.0, 0.0, 0, 0, 0, 0.0, 0.01, 0.1,
206 1.5, 2.5, 0.0, 0.0, 0.0, 0, 0, 0, 0.0, 0.1, 0.1,
207 1.0, 2.5, 0.0, 0.0, 0.0, 0, 0, 0, 0.0, 0.5, 0.1,
208 0.5, 2.5, 0.0, 0.0, 0.0, 0, 0, 0, 0.0, 1.0, 0.1
209 }
210""",
211)
212
213init_tracers.descend_invocation_order = 0
214project.add_action_set_to_initialisation(init_tracers)
215
216tracing_action_set = exahype2.tracer.FiniteVolumesTracingWithForce(
217 tracer_particles,
218 fv_solver,
219 project,
220 integration_scheme=integration_schemes[args.integration],
221 # Force method bitmask: 14 = 2 (Buoyancy) + 4 (Drag) + 8 (ConstantGravity)
222 # This enables three forces acting on particles:
223 # - Buoyancy: upward force based on fluid density
224 # - Drag: resistance based on relative velocity
225 # - Gravity: constant downward force
226 force_method=14,
227 interpolation_scheme=interpolation_methods[args.interpolation],
228 # Field interpolation from fluid solver to particle data:
229 # "0,1,2" maps solver fields to particle storage as follows:
230 # Solver Q[0] → particle.getData(0): fluid density (ρ)
231 # - Used for buoyancy force calculation: F_b = ρ*V*g
232 # Solver Q[1] → particle.getData(1): fluid velocity x-component (u_x)
233 # - Used for drag force calculation
234 # Solver Q[2] → particle.getData(2): fluid velocity y-component (u_y)
235 # - Used for drag force calculation
236 # These values are interpolated at each particle position from the
237 # surrounding fluid cells to enable accurate force calculations
238 fields_to_interpolate="0,1,2",
239)
240
241tracing_action_set.descend_invocation_order = (
242 fv_solver._action_set_update_patch.descend_invocation_order + 1
243)
244
245project.add_action_set_to_timestepping(tracing_action_set)
246project.add_action_set_to_initialisation(tracing_action_set)
247
248from pathlib import Path
249
250error_measurement = exahype2.solvers.fv.ErrorMeasurementParticles(
251 fv_solver,
252 error_measurement_implementation=analytical_solution,
253 output_file_name=args.output + "/Error_" + Path(__file__).name,
254 particle_set=tracer_particles,
255 descend_invocation_order=65536, # After all other action sets
256)
257
258project.set_load_balancer("new ::exahype2::LoadBalancingConfiguration")
259project.set_build_mode(mode=peano4.output.string_to_mode(args.build_mode))
260project = project.generate_Peano4_project(verbose=False)
261project.build(make=True, make_clean_first=True, throw_away_data_after_build=True)