-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathelixir_euler_potential_temperature_baroclinic_instability.jl
More file actions
247 lines (198 loc) · 9.77 KB
/
elixir_euler_potential_temperature_baroclinic_instability.jl
File metadata and controls
247 lines (198 loc) · 9.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# An idealized baroclinic instability test case
# For optimal results consider increasing the resolution to 16x16x8 trees per cube face.
#
# This elixir takes about 8 hours, using 16 threads of an AMD Ryzen 7 7800X3D.
#
# References:
# - Paul A. Ullrich, Thomas Melvin, Christiane Jablonowski, Andrew Staniforth (2013)
# A proposed baroclinic wave test case for deep- and shallow-atmosphere dynamical cores
# https://doi.org/10.1002/qj.2241
using OrdinaryDiffEqSSPRK
using Trixi, TrixiAtmo
using LinearAlgebra: norm
# Unperturbed balanced steady-state.
# Returns primitive variables with only the velocity in longitudinal direction (rho, u, p).
# The other velocity components are zero.
function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)
# Parameters from Table 1 in the paper
# Corresponding names in the paper are commented
radius_earth = EARTH_RADIUS # a
half_width_parameter = 2 # b
gravitational_acceleration = EARTH_GRAVITATIONAL_ACCELERATION # g
k = 3 # k
surface_pressure = 1.0f5 # p₀
gas_constant = 287 # R
surface_equatorial_temperature = 310 # T₀ᴱ
surface_polar_temperature = 240 # T₀ᴾ
lapse_rate = 0.005 # Γ
angular_velocity = EARTH_ROTATION_RATE # Ω
# Distance to the center of the Earth
r = z + radius_earth
# In the paper: T₀
temperature0 = 0.5 * (surface_equatorial_temperature + surface_polar_temperature)
# In the paper: A, B, C, H
const_a = 1 / lapse_rate
const_b = (temperature0 - surface_polar_temperature) /
(temperature0 * surface_polar_temperature)
const_c = 0.5 * (k + 2) * (surface_equatorial_temperature - surface_polar_temperature) /
(surface_equatorial_temperature * surface_polar_temperature)
const_h = gas_constant * temperature0 / gravitational_acceleration
# In the paper: (r - a) / bH
scaled_z = z / (half_width_parameter * const_h)
# Temporary variables
temp1 = exp(lapse_rate / temperature0 * z)
temp2 = exp(-scaled_z^2)
# In the paper: ̃τ₁, ̃τ₂
tau1 = const_a * lapse_rate / temperature0 * temp1 +
const_b * (1 - 2 * scaled_z^2) * temp2
tau2 = const_c * (1 - 2 * scaled_z^2) * temp2
# In the paper: ∫τ₁(r') dr', ∫τ₂(r') dr'
inttau1 = const_a * (temp1 - 1) + const_b * z * temp2
inttau2 = const_c * z * temp2
# Temporary variables
temp3 = r / radius_earth * cos(lat)
temp4 = temp3^k - k / (k + 2) * temp3^(k + 2)
# In the paper: T
temperature = 1 / ((r / radius_earth)^2 * (tau1 - tau2 * temp4))
# In the paper: U, u (zonal wind, first component of spherical velocity)
big_u = gravitational_acceleration / radius_earth * k * temperature * inttau2 *
(temp3^(k - 1) - temp3^(k + 1))
temp5 = radius_earth * cos(lat)
u = -angular_velocity * temp5 + sqrt(angular_velocity^2 * temp5^2 + temp5 * big_u)
# Hydrostatic pressure
p = surface_pressure *
exp(-gravitational_acceleration / gas_constant * (inttau1 - inttau2 * temp4))
# Density (via ideal gas law)
rho = p / (gas_constant * temperature)
return rho, u, p
end
# Perturbation as in Equations 25 and 26 of the paper (analytical derivative)
function perturbation_stream_function(lon, lat, z)
# Parameters from Table 1 in the paper
# Corresponding names in the paper are commented
perturbation_radius = 1 / 6 # d₀ / a
perturbed_wind_amplitude = 1 # Vₚ
perturbation_lon = pi / 9 # Longitude of perturbation location
perturbation_lat = 2 * pi / 9 # Latitude of perturbation location
pertz = 15000 # Perturbation height cap
# Great circle distance (d in the paper) divided by a (radius of the Earth)
# because we never actually need d without dividing by a
great_circle_distance_by_a = acos(sin(perturbation_lat) * sin(lat) +
cos(perturbation_lat) * cos(lat) *
cos(lon - perturbation_lon))
# In the first case, the vertical taper function is per definition zero.
# In the second case, the stream function is per definition zero.
if z > pertz || great_circle_distance_by_a > perturbation_radius
return 0, 0
end
# Vertical tapering of stream function
perttaper = 1 - 3 * z^2 / pertz^2 + 2 * z^3 / pertz^3
# sin/cos(pi * d / (2 * d_0)) in the paper
sin_, cos_ = sincos(0.5f0 * pi * great_circle_distance_by_a / perturbation_radius)
# Common factor for both u and v
factor = 16 / (3 * sqrt(3)) * perturbed_wind_amplitude * perttaper * cos_^3 * sin_
u_perturbation = -factor * (-sin(perturbation_lat) * cos(lat) +
cos(perturbation_lat) * sin(lat) * cos(lon - perturbation_lon)) /
sin(great_circle_distance_by_a)
v_perturbation = factor * cos(perturbation_lat) * sin(lon - perturbation_lon) /
sin(great_circle_distance_by_a)
return u_perturbation, v_perturbation
end
function cartesian_to_sphere(x)
r = norm(x)
lambda = atan(x[2], x[1])
if lambda < 0
lambda += 2 * pi
end
phi = asin(x[3] / r)
return lambda, phi, r
end
# Initial condition for an idealized baroclinic instability test
# https://doi.org/10.1002/qj.2241, Section 3.2 and Appendix A
function initial_condition_baroclinic_instability(x, t,
equations::CompressibleEulerPotentialTemperatureEquationsWithGravity3D)
lon, lat, r = cartesian_to_sphere(x)
radius_earth = EARTH_RADIUS
# Make sure that the r is not smaller than radius_earth
z = max(r - radius_earth, 0.0)
# Unperturbed basic state
rho, u, p = basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)
# Stream function type perturbation
u_perturbation, v_perturbation = perturbation_stream_function(lon, lat, z)
u += u_perturbation
v = v_perturbation
# Convert spherical velocity to Cartesian
v1 = -sin(lon) * u - sin(lat) * cos(lon) * v
v2 = cos(lon) * u - sin(lat) * sin(lon) * v
v3 = cos(lat) * v
gravitational_acceleration = EARTH_GRAVITATIONAL_ACCELERATION # g
r = norm(x)
# Make sure that r is not smaller than radius_earth
z = max(r - radius_earth, 0)
if z > 0
r = norm(x)
else
r = -(2 * radius_earth^3) / (x[1]^2 + x[2]^2 + x[3]^2)
end
r = -norm(x)
phi = radius_earth^2 * gravitational_acceleration / r
return prim2cons(SVector(rho, v1, v2, v3, p, phi), equations)
end
@inline function source_terms_baroclinic_instability(u, x, t,
equations::CompressibleEulerPotentialTemperatureEquationsWithGravity3D)
radius_earth = EARTH_RADIUS # a
angular_velocity = EARTH_ROTATION_RATE # Ω
r = norm(x)
# Make sure that r is not smaller than radius_earth
z = max(r - radius_earth, 0)
r = z + radius_earth
du1 = zero(eltype(u))
du2 = zero(eltype(u))
du3 = zero(eltype(u))
du4 = zero(eltype(u))
du5 = zero(eltype(u))
# Coriolis term, -2Ω × ρv = -2 * angular_velocity * (0, 0, 1) × u[2:4]
du2 -= -2 * angular_velocity * u[3]
du3 -= 2 * angular_velocity * u[2]
return SVector(du1, du2, du3, du4, du5, zero(eltype(u)))
end
equations = CompressibleEulerPotentialTemperatureEquationsWithGravity3D(c_p = 1004,
c_v = 717,
gravity = EARTH_GRAVITATIONAL_ACCELERATION)
initial_condition = initial_condition_baroclinic_instability
boundary_conditions = Dict(:inside => boundary_condition_slip_wall,
:outside => boundary_condition_slip_wall)
polydeg = 5
surface_flux = (FluxLMARS(340), flux_zero)
volume_flux = (flux_tec, flux_nonconservative_souza_etal)
solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux,
volume_integral = VolumeIntegralFluxDifferencing(volume_flux))
trees_per_cube_face = (8, 4)
mesh = P4estMeshCubedSphere(trees_per_cube_face..., EARTH_RADIUS, 30000,
polydeg = polydeg, initial_refinement_level = 0)
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
source_terms = source_terms_baroclinic_instability,
boundary_conditions = boundary_conditions)
###############################################################################
# ODE solvers, callbacks etc.
T = 10 # 10 days
tspan = (0.0, T * SECONDS_PER_DAY) # time in seconds for 10 days
ode = semidiscretize(semi, tspan)
summary_callback = SummaryCallback()
analysis_interval = 1000
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
alive_callback = AliveCallback(analysis_interval = analysis_interval)
save_solution = SaveSolutionCallback(dt = 100, save_initial_solution = true,
save_final_solution = true)
callbacks = CallbackSet(summary_callback,
analysis_callback,
alive_callback,
save_solution)
###############################################################################
# Use a Runge-Kutta method with automatic (error based) time step size control
# Enable threading of the RK method for better performance on multiple threads
tol = 1e-6
sol = solve(ode,
SSPRK43(thread = Trixi.True());
abstol = tol, reltol = tol, ode_default_options()...,
callback = callbacks)