Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/examples/t8code_3d_dgsem/elixir_euler_baroclinic_instability.jl
2055 views
1
# An idealized baroclinic instability test case
2
# For optimal results consider increasing the resolution to 16x16x8 trees per cube face.
3
#
4
# Note that this elixir can take several hours to run.
5
# Using 24 threads of an AMD Ryzen Threadripper 3990X (more threads don't speed it up further)
6
# and `check-bounds=no`, this elixirs takes about one hour to run.
7
# With 16x16x8 trees per cube face on the same machine, it takes about 28 hours.
8
#
9
# References:
10
# - Paul A. Ullrich, Thomas Melvin, Christiane Jablonowski, Andrew Staniforth (2013)
11
# A proposed baroclinic wave test case for deep- and shallow-atmosphere dynamical cores
12
# https://doi.org/10.1002/qj.2241
13
14
using OrdinaryDiffEqLowStorageRK
15
using Trixi
16
using LinearAlgebra
17
18
###############################################################################
19
# Setup for the baroclinic instability test
20
gamma = 1.4
21
equations = CompressibleEulerEquations3D(gamma)
22
23
# Initial condition for an idealized baroclinic instability test
24
# https://doi.org/10.1002/qj.2241, Section 3.2 and Appendix A
25
function initial_condition_baroclinic_instability(x, t,
26
equations::CompressibleEulerEquations3D)
27
lon, lat, r = cartesian_to_sphere(x)
28
radius_earth = 6.371229e6
29
# Make sure that the r is not smaller than radius_earth
30
z = max(r - radius_earth, 0.0)
31
32
# Unperturbed basic state
33
rho, u, p = basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)
34
35
# Stream function type perturbation
36
u_perturbation, v_perturbation = perturbation_stream_function(lon, lat, z)
37
38
u += u_perturbation
39
v = v_perturbation
40
41
# Convert spherical velocity to Cartesian
42
v1 = -sin(lon) * u - sin(lat) * cos(lon) * v
43
v2 = cos(lon) * u - sin(lat) * sin(lon) * v
44
v3 = cos(lat) * v
45
46
return prim2cons(SVector(rho, v1, v2, v3, p), equations)
47
end
48
49
# Steady state for RHS correction below
50
function steady_state_baroclinic_instability(x, t, equations::CompressibleEulerEquations3D)
51
lon, lat, r = cartesian_to_sphere(x)
52
radius_earth = 6.371229e6
53
# Make sure that the r is not smaller than radius_earth
54
z = max(r - radius_earth, 0.0)
55
56
# Unperturbed basic state
57
rho, u, p = basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)
58
59
# Convert spherical velocity to Cartesian
60
v1 = -sin(lon) * u
61
v2 = cos(lon) * u
62
v3 = 0.0
63
64
return prim2cons(SVector(rho, v1, v2, v3, p), equations)
65
end
66
67
function cartesian_to_sphere(x)
68
r = norm(x)
69
lambda = atan(x[2], x[1])
70
if lambda < 0
71
lambda += 2 * pi
72
end
73
phi = asin(x[3] / r)
74
75
return lambda, phi, r
76
end
77
78
# Unperturbed balanced steady-state.
79
# Returns primitive variables with only the velocity in longitudinal direction (rho, u, p).
80
# The other velocity components are zero.
81
function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)
82
# Parameters from Table 1 in the paper
83
# Corresponding names in the paper are commented
84
radius_earth = 6.371229e6 # a
85
half_width_parameter = 2 # b
86
gravitational_acceleration = 9.80616 # g
87
k = 3 # k
88
surface_pressure = 1e5 # p₀
89
gas_constant = 287 # R
90
surface_equatorial_temperature = 310.0 # T₀ᴱ
91
surface_polar_temperature = 240.0 # T₀ᴾ
92
lapse_rate = 0.005 # Γ
93
angular_velocity = 7.29212e-5 # Ω
94
95
# Distance to the center of the Earth
96
r = z + radius_earth
97
98
# In the paper: T₀
99
temperature0 = 0.5 * (surface_equatorial_temperature + surface_polar_temperature)
100
# In the paper: A, B, C, H
101
const_a = 1 / lapse_rate
102
const_b = (temperature0 - surface_polar_temperature) /
103
(temperature0 * surface_polar_temperature)
104
const_c = 0.5 * (k + 2) * (surface_equatorial_temperature - surface_polar_temperature) /
105
(surface_equatorial_temperature * surface_polar_temperature)
106
const_h = gas_constant * temperature0 / gravitational_acceleration
107
108
# In the paper: (r - a) / bH
109
scaled_z = z / (half_width_parameter * const_h)
110
111
# Temporary variables
112
temp1 = exp(lapse_rate / temperature0 * z)
113
temp2 = exp(-scaled_z^2)
114
115
# In the paper: ̃τ₁, ̃τ₂
116
tau1 = const_a * lapse_rate / temperature0 * temp1 +
117
const_b * (1 - 2 * scaled_z^2) * temp2
118
tau2 = const_c * (1 - 2 * scaled_z^2) * temp2
119
120
# In the paper: ∫τ₁(r') dr', ∫τ₂(r') dr'
121
inttau1 = const_a * (temp1 - 1) + const_b * z * temp2
122
inttau2 = const_c * z * temp2
123
124
# Temporary variables
125
temp3 = r / radius_earth * cos(lat)
126
temp4 = temp3^k - k / (k + 2) * temp3^(k + 2)
127
128
# In the paper: T
129
temperature = 1 / ((r / radius_earth)^2 * (tau1 - tau2 * temp4))
130
131
# In the paper: U, u (zonal wind, first component of spherical velocity)
132
big_u = gravitational_acceleration / radius_earth * k * temperature * inttau2 *
133
(temp3^(k - 1) - temp3^(k + 1))
134
temp5 = radius_earth * cos(lat)
135
u = -angular_velocity * temp5 + sqrt(angular_velocity^2 * temp5^2 + temp5 * big_u)
136
137
# Hydrostatic pressure
138
p = surface_pressure *
139
exp(-gravitational_acceleration / gas_constant * (inttau1 - inttau2 * temp4))
140
141
# Density (via ideal gas law)
142
rho = p / (gas_constant * temperature)
143
144
return rho, u, p
145
end
146
147
# Perturbation as in Equations 25 and 26 of the paper (analytical derivative)
148
function perturbation_stream_function(lon, lat, z)
149
# Parameters from Table 1 in the paper
150
# Corresponding names in the paper are commented
151
perturbation_radius = 1 / 6 # d₀ / a
152
perturbed_wind_amplitude = 1.0 # Vₚ
153
perturbation_lon = pi / 9 # Longitude of perturbation location
154
perturbation_lat = 2 * pi / 9 # Latitude of perturbation location
155
pertz = 15000 # Perturbation height cap
156
157
# Great circle distance (d in the paper) divided by a (radius of the Earth)
158
# because we never actually need d without dividing by a
159
great_circle_distance_by_a = acos(sin(perturbation_lat) * sin(lat) +
160
cos(perturbation_lat) * cos(lat) *
161
cos(lon - perturbation_lon))
162
163
# In the first case, the vertical taper function is per definition zero.
164
# In the second case, the stream function is per definition zero.
165
if z > pertz || great_circle_distance_by_a > perturbation_radius
166
return 0.0, 0.0
167
end
168
169
# Vertical tapering of stream function
170
perttaper = 1.0 - 3 * z^2 / pertz^2 + 2 * z^3 / pertz^3
171
172
# sin/cos(pi * d / (2 * d_0)) in the paper
173
sin_, cos_ = sincos(0.5 * pi * great_circle_distance_by_a / perturbation_radius)
174
175
# Common factor for both u and v
176
factor = 16 / (3 * sqrt(3)) * perturbed_wind_amplitude * perttaper * cos_^3 * sin_
177
178
u_perturbation = -factor * (-sin(perturbation_lat) * cos(lat) +
179
cos(perturbation_lat) * sin(lat) * cos(lon - perturbation_lon)) /
180
sin(great_circle_distance_by_a)
181
182
v_perturbation = factor * cos(perturbation_lat) * sin(lon - perturbation_lon) /
183
sin(great_circle_distance_by_a)
184
185
return u_perturbation, v_perturbation
186
end
187
188
@inline function source_terms_baroclinic_instability(u, x, t,
189
equations::CompressibleEulerEquations3D)
190
radius_earth = 6.371229e6 # a
191
gravitational_acceleration = 9.80616 # g
192
angular_velocity = 7.29212e-5 # Ω
193
194
r = norm(x)
195
# Make sure that r is not smaller than radius_earth
196
z = max(r - radius_earth, 0.0)
197
r = z + radius_earth
198
199
du1 = zero(eltype(u))
200
201
# Gravity term
202
temp = -gravitational_acceleration * radius_earth^2 / r^3
203
du2 = temp * u[1] * x[1]
204
du3 = temp * u[1] * x[2]
205
du4 = temp * u[1] * x[3]
206
du5 = temp * (u[2] * x[1] + u[3] * x[2] + u[4] * x[3])
207
208
# Coriolis term, -2Ω × ρv = -2 * angular_velocity * (0, 0, 1) × u[2:4]
209
du2 -= -2 * angular_velocity * u[3]
210
du3 -= 2 * angular_velocity * u[2]
211
212
return SVector(du1, du2, du3, du4, du5)
213
end
214
215
###############################################################################
216
# Start of the actual elixir, semidiscretization of the problem
217
218
initial_condition = initial_condition_baroclinic_instability
219
220
boundary_conditions = Dict(:inside => boundary_condition_slip_wall,
221
:outside => boundary_condition_slip_wall)
222
223
# This is a good estimate for the speed of sound in this example.
224
# Other values between 300 and 400 should work as well.
225
surface_flux = FluxLMARS(340)
226
volume_flux = flux_kennedy_gruber
227
solver = DGSEM(polydeg = 5, surface_flux = surface_flux,
228
volume_integral = VolumeIntegralFluxDifferencing(volume_flux))
229
230
# For optimal results, use (16, 8) here
231
trees_per_cube_face = (8, 4)
232
inner_radius = 6.371229e6 # Radius of the inner side of the shell
233
thickness = 30000.0 # Thickness of the shell
234
mesh = Trixi.T8codeMeshCubedSphere(trees_per_cube_face..., inner_radius, thickness,
235
polydeg = 5, initial_refinement_level = 0)
236
237
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
238
source_terms = source_terms_baroclinic_instability,
239
boundary_conditions = boundary_conditions)
240
241
###############################################################################
242
# ODE solvers, callbacks etc.
243
244
tspan = (0.0, 10 * 24 * 60 * 60.0) # time in seconds for 10 days
245
246
# Save RHS of the steady state and subtract it in every RHS evaluation.
247
# This trick preserves the steady state exactly (to machine rounding errors, of course).
248
# Otherwise, this elixir produces entirely unusable results for a resolution of 8x8x4 cells
249
# per cube face with a polydeg of 3.
250
# With this trick, even the polydeg 3 simulation produces usable (although badly resolved) results,
251
# and most of the grid imprinting in higher polydeg simulation is eliminated.
252
#
253
# See https://github.com/trixi-framework/Trixi.jl/issues/980 for more information.
254
u_steady_state = compute_coefficients(steady_state_baroclinic_instability, tspan[1], semi)
255
# Use a `let` block for performance (otherwise du_steady_state will be a global variable)
256
let du_steady_state = similar(u_steady_state)
257
# Save RHS of the steady state
258
Trixi.rhs!(du_steady_state, u_steady_state, semi, tspan[1])
259
260
global function corrected_rhs!(du, u, semi, t)
261
# Normal RHS evaluation
262
Trixi.rhs!(du, u, semi, t)
263
# Correct by subtracting the steady-state RHS
264
Trixi.@trixi_timeit Trixi.timer() "rhs correction" begin
265
# Use Trixi.@threaded for threaded performance
266
Trixi.@threaded for i in eachindex(du)
267
du[i] -= du_steady_state[i]
268
end
269
end
270
end
271
end
272
u0 = compute_coefficients(tspan[1], semi)
273
ode = ODEProblem(corrected_rhs!, u0, tspan, semi)
274
275
summary_callback = SummaryCallback()
276
277
analysis_interval = 5000
278
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
279
280
alive_callback = AliveCallback(analysis_interval = analysis_interval)
281
282
save_solution = SaveSolutionCallback(interval = 5000,
283
save_initial_solution = true,
284
save_final_solution = true,
285
solution_variables = cons2prim)
286
287
callbacks = CallbackSet(summary_callback,
288
analysis_callback,
289
alive_callback,
290
save_solution)
291
292
###############################################################################
293
# run the simulation
294
295
# Use a Runge-Kutta method with automatic (error based) time step size control
296
# Enable threading of the RK method for better performance on multiple threads
297
sol = solve(ode, RDPK3SpFSAL49(thread = Trixi.True());
298
abstol = 1.0e-6, reltol = 1.0e-6,
299
ode_default_options()..., callback = callbacks);
300
301