Peano
Loading...
Searching...
No Matches
isothermal-stratification.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 os
4import sys
5
6import peano4
7import exahype2
8
9sys.path.insert(0, os.path.abspath(".."))
10from PDE import max_eigenvalue, flux, source_term
11from FWave import fWave
12
13# TODO: Should be moved into tests!
14"""
15@article{WellBalancedness,
16 author = {{Käppeli, R.} and {Mishra, S.}},
17 title = {A well-balanced finite volume scheme for the Euler equations with gravitation - The exact preservation of hydrostatic equilibrium with arbitrary entropy stratification},
18 DOI= "10.1051/0004-6361/201527815",
19 url= "https://doi.org/10.1051/0004-6361/201527815",
20 journal = {A&A},
21 year = 2016,
22 volume = 587,
23 pages = "A94",
24 month = "",
25}
26
27This artificial scenario describes a stratified atmosphere by ensuring that an exponentially decreasing density profile
28is exactly balanced with an exponentially decreasing pressure gradient.
29Therefore, no updates should occur over the duration of the simulation and nothing should change in the density
30and pressure profiles. This scenario can be used to test the well-balancedness of the underlying numerical scheme
31such that it accurately captures the initial steady state. This is particularly difficult for higher order methods,
32such as the MUSCL-Hancock scheme.
33
34The reconstruction proposed by Käppeli and Mishra aims at retaining an exponentially decaying pressure gradient
35within the MUSCL-Hancock boundary extrapolation phase. To this end, they detail first- and second-order
36pressure reconstruction techniques, which they find to capture the initial stratified steady state up to
37machine precision in a Runge-Kutta scheme. While the first-order accurate pressure reconstruction is found to work
38in this scenario, the necessary second-order reconstruction, which limits pressure perturbations after the initial
39time step t = 0 to t != 0, results in unphysical updates.
40In general, any sort of custom reconstruction of the volume boundary values using the limited slopes and original
41values can be implemented with the newly introduced reconstruction argument of the MUSCL-Hancock solver.
42"""
43
44initial_conditions = """
45 const auto scaleHeight = (GAS_CONSTANT * TEMPERATURE) / GRAVITY / MEAN_MOLECULAR_MASS;
46 const auto exponentialDistribution = tarch::la::pow(tarch::la::E, -x(1) / scaleHeight);
47
48 Q[Shortcuts::rho] = GROUND_DENSITY * exponentialDistribution;
49 Q[Shortcuts::rhoU + 0] = 0.0;
50 Q[Shortcuts::rhoU + 1] = 0.0;
51 #if DIMENSIONS == 3
52 Q[Shortcuts::rhoU + 2] = 0.0;
53 #endif
54
55 const auto pressure = Q[Shortcuts::rho] * GAS_CONSTANT * TEMPERATURE / MEAN_MOLECULAR_MASS;
56 Q[Shortcuts::rhoE] = pressure / (GAMMA - 1.0);
57"""
58
59boundary_conditions = """
60 // normal == 0 => left
61 // normal == 1 => bottom
62 // normal == 2 => right
63 // normal == 3 => top
64
65 if (normal == 1 || normal == 3) {
66 // positive dy upwards, negative dy downwards
67 const auto dy = (normal == 3 ? h(1) : -h(1));
68
69 const auto scaleHeight = GAS_CONSTANT * TEMPERATURE / GRAVITY / MEAN_MOLECULAR_MASS;
70 const auto exponentialDistribution = tarch::la::pow(tarch::la::E, -dy / scaleHeight);
71
72 const auto densityOutside = Qinside[Shortcuts::rho] * exponentialDistribution;
73 const auto pressureOutside = densityOutside * GAS_CONSTANT * TEMPERATURE / MEAN_MOLECULAR_MASS;
74
75 Qoutside[Shortcuts::rho] = densityOutside;
76 const auto u = Qinside[Shortcuts::rhoU + 0] / Qinside[Shortcuts::rho];
77 const auto v = Qinside[Shortcuts::rhoU + 1] / Qinside[Shortcuts::rho];
78 #if DIMENSIONS == 3
79 const auto w = Qinside[Shortcuts::rhoU + 2] / Qinside[Shortcuts::rho];
80 const auto uSq = u * u + v * v + w * w;
81 #else
82 const auto uSq = u * u + v * v;
83 #endif
84
85 Qoutside[Shortcuts::rhoE] = pressureOutside / (GAMMA - 1) + 0.5 * densityOutside * uSq;
86
87 Qoutside[Shortcuts::rhoU + 0] = u * densityOutside;
88 #if DIMENSIONS == 3
89 Qoutside[Shortcuts::rhoU + 2] = w * densityOutside;
90 #endif
91
92 if (normal == 1) {
93 // Reflective at bottom to keep atmosphere
94 // from escaping the domain through the Earth's surface
95 Qoutside[Shortcuts::rhoU + 1] = -v * densityOutside;
96 } else {
97 // Outflow at top
98 Qoutside[Shortcuts::rhoU + 1] = v * densityOutside;
99 }
100 } else {
101 // outflow in other directions than stratified
102 for (int unknown = 0; unknown < NumberOfUnknowns; ++unknown) {
103 Qoutside[unknown] = Qinside[unknown];
104 }
105 }
106"""
107
108compute_usq = """
109 auto computeUSq = [] (
110 const {{COMPUTE_PRECISION}}* const NOALIAS Q,
111 {{COMPUTE_PRECISION}}& uSq
112 ) {
113 const int ShortcutRho = 0;
114 const int ShortcutRhoU = 1;
115
116 const auto u = Q[ShortcutRhoU + 0] / Q[ShortcutRho];
117 const auto v = Q[ShortcutRhoU + 1] / Q[ShortcutRho];
118 #if DIMENSIONS == 3
119 const auto w = Q[ShortcutRhoU + 2] / Q[ShortcutRho];
120 uSq = u * u + v * v + w * w;
121 #else
122 uSq = u * u + v * v;
123 #endif
124 };
125"""
126
127calculate_pressure = (
128 """
129 auto calculatePressure = [] (
130 const {{COMPUTE_PRECISION}}* const NOALIAS Q,
131 {{COMPUTE_PRECISION}}& pressure,
132 {{COMPUTE_PRECISION}}& uSq
133 ) {
134 const int ShortcutRho = 0;
135 const int ShortcutRhoE = DIMENSIONS + 1;
136
137 """
138 + compute_usq
139 + """
140
141 computeUSq(Q, uSq);
142 pressure = (GAMMA - 1.0) * (Q[ShortcutRhoE] - 0.5 * Q[ShortcutRho] * uSq);
143 };
144"""
145)
146
147calculate_pressure_perturbations = (
148 """
149 auto pressurePerturbations = [] (
150 const {{COMPUTE_PRECISION}}* const NOALIAS QL,
151 const {{COMPUTE_PRECISION}}* const NOALIAS Q,
152 const {{COMPUTE_PRECISION}}* const NOALIAS QR,
153 const tarch::la::Vector<DIMENSIONS, double> h,
154 const int normal,
155 {{COMPUTE_PRECISION}}& pressurePerturbationLeft,
156 {{COMPUTE_PRECISION}}& pressurePerturbationRight
157 ) {
158 /**
159 * Following "A well-balanced finite volume scheme for the Euler equations with gravitation", R. Käppeli and S. Mishra, https://doi.org/10.1051/0004-6361/201527815
160 */
161 const auto densityLeft = QL[0];
162 const auto density = Q[0];
163 const auto densityRight = QR[0];
164
165 """
166 + calculate_pressure
167 + """
168
169 {{COMPUTE_PRECISION}} pressure;
170 {{COMPUTE_PRECISION}} uSq;
171 calculatePressure(Q, pressure, uSq);
172
173 {{COMPUTE_PRECISION}} pressureLeft;
174 {{COMPUTE_PRECISION}} uSqLeft;
175 calculatePressure(QL, pressureLeft, uSqLeft);
176
177 {{COMPUTE_PRECISION}} pressureRight;
178 {{COMPUTE_PRECISION}} uSqRight;
179 calculatePressure(QR, pressureRight, uSqRight);
180
181 // Eq. 26
182 const auto reconstructedPressureLeft = pressure + 0.5 * (densityLeft + density) * -GRAVITY * h(normal);
183 const auto reconstructedPressureRight = pressure - 0.5 * (densityRight + density) * -GRAVITY * h(normal);
184
185 // Eq. 25
186 pressurePerturbationLeft = pressureLeft - reconstructedPressureLeft;
187 pressurePerturbationRight = pressureRight - reconstructedPressureRight;
188 };
189"""
190)
191
192limiter = (
193 calculate_pressure_perturbations
194 + """
195 {{COMPUTE_PRECISION}} a;
196 {{COMPUTE_PRECISION}} b;
197 if (unknown == 3 && normal == 1) {
198 pressurePerturbations(QL, Q, QR, h, normal, b, a);
199 } else {
200 a = QR[unknown] - Q[unknown];
201 b = Q[unknown] - QL[unknown];
202 }
203
204 // Minmod
205 if (a * b <= 0.0) {
206 return 0.0;
207 } else {
208 if (tarch::la::smaller(tarch::la::abs(a), tarch::la::abs(b))) {
209 return a;
210 } else {
211 return b;
212 }
213 }
214"""
215)
216
217"""
218Required in MUSCL-Hancock before we solve the Riemann Problem.
219-> Not the same as a simple postprocessing step
220"""
221reconstruction = (
222 """
223 auto reconstructEnergyAtBoundary = [] (
224 const {{COMPUTE_PRECISION}} * const NOALIAS Q,
225 const tarch::la::Vector<DIMENSIONS, double> h,
226 const int normal,
227 const {{COMPUTE_PRECISION}} slope,
228 {{COMPUTE_PRECISION}}* const NOALIAS extrapolatedBoundaryLeft,
229 {{COMPUTE_PRECISION}}* const NOALIAS extrapolatedBoundaryRight
230 ) {
231
232 """
233 + calculate_pressure
234 + """
235
236 {{COMPUTE_PRECISION}} pressure;
237 {{COMPUTE_PRECISION}} uSq;
238 calculatePressure(Q, pressure, uSq);
239
240 const int ShortcutRho = 0;
241 const int ShortcutE = DIMENSIONS + 1;
242
243 const {{COMPUTE_PRECISION}} density = Q[ShortcutRho];
244
245 // Eq. 16 Käppeli and Mishra
246 const auto reconstructedPressureLeftBoundary = pressure + 0.5 * density * -GRAVITY * h(normal);
247 const auto reconstructedPressureRightBoundary = pressure - 0.5 * density * -GRAVITY * h(normal);
248
249 // Eq. 27 Käppeli and Mishra
250 // Following p_{1,i}(x_i) = p_i - p_{0,i}(x_i) = 0, all pressure perturbations are zero,
251 // so we just add the perturbation slopes to the reconstructed pressure
252 const auto pressureLeftBoundary = reconstructedPressureLeftBoundary - 0.5 * h(normal) * slope;
253 const auto pressureRightBoundary = reconstructedPressureRightBoundary + 0.5 * h(normal) * slope;
254
255 // compute boundary uSqs
256 {{COMPUTE_PRECISION}} uSqLeftBoundary;
257 {{COMPUTE_PRECISION}} uSqRightBoundary;
258
259 """
260 + compute_usq
261 + """
262
263 computeUSq(extrapolatedBoundaryLeft, uSqLeftBoundary);
264 computeUSq(extrapolatedBoundaryRight, uSqRightBoundary);
265
266 // convert pressure back to energy
267 extrapolatedBoundaryLeft[ShortcutE] = pressureLeftBoundary / (GAMMA - 1.0) + 0.5 * extrapolatedBoundaryLeft[ShortcutRho] * uSqLeftBoundary;
268 extrapolatedBoundaryRight[ShortcutE] = pressureRightBoundary / (GAMMA - 1.0) + 0.5 * extrapolatedBoundaryRight[ShortcutRho] * uSqRightBoundary;
269 };
270
271 if (unknown == 3 && normal == 1) {
272 reconstructEnergyAtBoundary(QInGathered, volumeSize, normal, slopeGathered[normal][unknown], extrapolatedBoundaryLeft, extrapolatedBoundaryRight);
273 reconstructEnergyAtBoundary(QInGatheredLeft, volumeSize, normal, slopeGatheredLeft[normal][unknown], extrapolatedBoundaryLeftNeighborLeftBoundary, extrapolatedBoundaryLeftNeighborRightBoundary);
274 reconstructEnergyAtBoundary(QInGatheredRight, volumeSize, normal, slopeGatheredRight[normal][unknown], extrapolatedBoundaryRightNeighborLeftBoundary, extrapolatedBoundaryRightNeighborRightBoundary);
275 } else {
276 extrapolatedBoundaryLeft[unknown] = QInGathered[unknown] - 0.5 * volumeSize(normal) * slopeGathered[normal][unknown];
277 extrapolatedBoundaryRight[unknown] = QInGathered[unknown] + 0.5 * volumeSize(normal) * slopeGathered[normal][unknown];
278 extrapolatedBoundaryLeftNeighborLeftBoundary[unknown] = QInGatheredLeft[unknown] - 0.5 * volumeSize(normal) * slopeGatheredLeft[normal][unknown];
279 extrapolatedBoundaryLeftNeighborRightBoundary[unknown] = QInGatheredLeft[unknown] + 0.5 * volumeSize(normal) * slopeGatheredLeft[normal][unknown];
280 extrapolatedBoundaryRightNeighborLeftBoundary[unknown] = QInGatheredRight[unknown] - 0.5 * volumeSize(normal) * slopeGatheredRight[normal][unknown];
281 extrapolatedBoundaryRightNeighborRightBoundary[unknown] = QInGatheredRight[unknown] + 0.5 * volumeSize(normal) * slopeGatheredRight[normal][unknown];
282 }
283"""
284)
285
286parser = exahype2.ArgumentParser(
287 "ExaHyPE 2 - Euler Isothermal Stratification Argument Parser"
288)
289parser.set_defaults(
290 min_depth=6,
291 degrees_of_freedom=16,
292)
293args = parser.parse_args()
294
295size = [40_000, 40_000, 40_000]
296max_h = 1.1 * min(size) / (3.0**args.min_depth)
297min_h = max_h * 3.0 ** (-args.amr_levels)
298
299riemann_solver = exahype2.solvers.fv.musclhancock.GlobalAdaptiveTimeStep(
300 name="FVSolver",
301 patch_size=args.degrees_of_freedom,
302 unknowns={"rho": 1, "rhoU": args.dimensions, "rhoE": 1},
303 auxiliary_variables=0,
304 min_volume_h=min_h,
305 max_volume_h=max_h,
306 time_step_relaxation=0.5,
307 use_enclave_tasking=args.enclave_tasking,
308 number_of_enclave_tasks=args.ntasks,
309 reconstruction=reconstruction, # Specific to MUSCL-Hancock
310)
311
312riemann_solver.set_implementation(
313 initial_conditions=initial_conditions,
314 boundary_conditions=boundary_conditions,
315 max_eigenvalue=max_eigenvalue,
316 flux=flux,
317 limiter=limiter,
318 riemann_solver=fWave,
319)
320
321
327riemann_solver.set_implementation(
328 source_term=source_term,
329)
330
331riemann_solver.set_plotter(args.plotter)
332
333project = exahype2.Project(
334 namespace=["applications", "exahype2", "euler"],
335 project_name="IsothermalStratification",
336 directory=".",
337 executable="Euler",
338)
339project.add_solver(riemann_solver)
340
341if args.number_of_snapshots <= 0:
342 time_in_between_plots = 0.0
343else:
344 time_in_between_plots = args.end_time / args.number_of_snapshots
345 project.set_output_path(args.output)
346
347project.set_global_simulation_parameters(
348 dimensions=args.dimensions,
349 size=size[0 : args.dimensions],
350 offset=[0.0, 0.0, 0.0][0 : args.dimensions],
351 min_end_time=args.end_time,
352 max_end_time=args.end_time,
353 first_plot_time_stamp=90.0,
354 time_in_between_plots=time_in_between_plots,
355 periodic_BC=[
356 args.periodic_boundary_conditions_x,
357 args.periodic_boundary_conditions_y,
358 args.periodic_boundary_conditions_z,
359 ],
360)
361
362project.set_load_balancer(
363 f"new ::exahype2::LoadBalancingConfiguration({args.load_balancing_quality}, 1, {args.trees}, {args.trees})"
364)
365project.set_Peano4_installation(
366 "../../../../", mode=peano4.output.string_to_mode(args.build_mode)
367)
368project = project.generate_Peano4_project(verbose=False)
369project.set_fenv_handler(args.fpe)
370project.output.makefile.set_target_device(args.target_device)
371project.output.makefile.add_CXX_flag("-DGAMMA=1.4")
372project.output.makefile.add_CXX_flag("-DGRAVITY=9.81")
373project.output.makefile.add_CXX_flag("-DGAS_CONSTANT=8.31")
374project.output.makefile.add_CXX_flag("-DTEMPERATURE=299")
375project.output.makefile.add_CXX_flag("-DGROUND_DENSITY=1.166")
376project.output.makefile.add_CXX_flag("-DMEAN_MOLECULAR_MASS=0.0289647")
377project.build(make=True, make_clean_first=True, throw_away_data_after_build=True)