Skip to content

Commit f52fdd8

Browse files
committed
ouch #2
1 parent fd85ad4 commit f52fdd8

25 files changed

+5729
-9
lines changed
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
# Held-Suarez test case
2+
# Following Souza et al.:
3+
# The Flux-Differencing Discontinuous Galerkin Method Applied to an Idealized Fully
4+
# Compressible Nonhydrostatic Dry Atmosphere
5+
6+
using OrdinaryDiffEqLowStorageRK
7+
using Trixi
8+
using TrixiAtmo
9+
using LinearAlgebra
10+
11+
function cartesian_to_sphere(x)
12+
r = norm(x)
13+
lambda = atan(x[2], x[1])
14+
phi = asin(x[3] / r)
15+
16+
return lambda, phi, r
17+
end
18+
19+
function initial_condition_isothermal(x, t, equations::PassiveTracerEquations)
20+
# equation (60) in the paper
21+
temperature = 285 # T_I
22+
gas_constant = 287 # R_d
23+
surface_pressure = 1e5 # p_0
24+
radius_earth = 6.371229e6 # r_planet
25+
gravitational_acceleration = 9.80616 # g
26+
c_v = 717.5 # specific heat capacity at constant volume
27+
28+
r = norm(x)
29+
# Make sure that r is not smaller than radius_earth
30+
z = max(r - radius_earth, 0.0)
31+
r = z + radius_earth
32+
33+
# pressure
34+
# geopotential formulation?
35+
p = surface_pressure *
36+
exp(gravitational_acceleration *
37+
(radius_earth^2 / r - radius_earth) /
38+
(gas_constant * temperature))
39+
40+
# density (via ideal gas law)
41+
rho = p / (gas_constant * temperature)
42+
43+
# geopotential
44+
phi = gravitational_acceleration * (radius_earth - radius_earth^2 / r)
45+
46+
E = c_v * temperature + phi
47+
48+
tracer = SVector(ntuple(@inline(v->0.0001), Val(Trixi.ntracers(equations))))
49+
50+
return vcat(SVector(rho, 0, 0, 0, rho * E, phi),
51+
rho * tracer, rho * tracer)
52+
end
53+
54+
@inline function source_terms_coriolis(u, x, t,
55+
equations::CompressibleEulerEquationsWithGravity3D)
56+
radius_earth = 6.371229e6 # r_planet
57+
angular_velocity = 7.29212e-5 # Ω
58+
# 7.27220521664e-05 (Giraldo)
59+
60+
r = norm(x)
61+
# Make sure that r is not smaller than radius_earth
62+
z = max(r - radius_earth, 0.0)
63+
r = z + radius_earth
64+
65+
du0 = zero(eltype(u))
66+
67+
# Coriolis term, -2Ω × ρv = -2 * angular_velocity * (0, 0, 1) × u[2:4]
68+
du2 = 2 * angular_velocity * u[3]
69+
du3 = -2 * angular_velocity * u[2]
70+
71+
return SVector(du0, du2, du3, du0, du0, du0, du0)
72+
end
73+
74+
@inline function source_terms_hs_relaxation(u, x, t,
75+
equations::CompressibleEulerEquationsWithGravity3D)
76+
# equations (55)-(58) in the paper
77+
secondsperday = 60 * 60 * 24
78+
radius_earth = 6.371229e6 # r_planet
79+
k_f = 1 / secondsperday # Damping scale for momentum
80+
k_a = 1 / (40 * secondsperday) # Polar relaxation scale
81+
k_s = 1 / (4 * secondsperday) # Equatorial relaxation scale
82+
T_min = 200 # Minimum equilibrium temperature
83+
T_equator = 315 # Equatorial equilibrium temperature
84+
surface_pressure = 1e5 # p_0
85+
deltaT = 60 # Latitudinal temperature difference
86+
deltaTheta = 10 # Vertical temperature difference
87+
sigma_b = 0.7 # Dimensionless damping height
88+
gas_constant = 287 # R_d
89+
c_v = 717.5 # Specific heat capacity of dry air at constant volume
90+
c_p = 1004.5 # Specific heat capacity of dry air at constant pressure
91+
92+
_, _, _, _, pressure = cons2prim(u, equations)
93+
lon, lat, r = cartesian_to_sphere(x)
94+
temperature = pressure / (u[1] * gas_constant)
95+
96+
sigma = pressure / surface_pressure # "p_0 instead of instantaneous surface pressure"
97+
delta_sigma = max(0, (sigma - sigma_b) / (1 - sigma_b)) # "height factor"
98+
k_v = k_f * delta_sigma
99+
k_T = k_a + (k_s - k_a) * delta_sigma * cos(lat)^4
100+
101+
T_equi = max(T_min,
102+
(T_equator - deltaT * sin(lat)^2 - deltaTheta * log(sigma) * cos(lat)^2) *
103+
sigma^(gas_constant / c_p))
104+
105+
# project onto r, normalize! @. Yₜ.c.uₕ -= k_v * Y.c.uₕ
106+
# Make sure that r is not smaller than radius_earth
107+
z = max(r - radius_earth, 0.0)
108+
r = z + radius_earth
109+
dotprod = (u[2] * x[1] + u[3] * x[2] + u[4] * x[3]) / (r * r)
110+
111+
du2 = -k_v * (u[2] - dotprod * x[1])
112+
du3 = -k_v * (u[3] - dotprod * x[2])
113+
du4 = -k_v * (u[4] - dotprod * x[3])
114+
115+
du5 = -k_T * u[1] * c_v * (temperature - T_equi)
116+
117+
du0 = zero(eltype(u))
118+
119+
return SVector(du0, du2, du3, du4, du5, du0, du0)
120+
end
121+
122+
# setup
123+
struct RadonDecaySetup{NPRODUCTS}
124+
dt::Float64
125+
lambda::Vector{Float64}
126+
pr1::Array{Float64, 2}
127+
pr2::Array{Float64, 3}
128+
129+
function RadonDecaySetup(; nproducts = 1, dt = 1.0)
130+
secondsperday = 60 * 60 * 24
131+
half_lifes = [3.8 * secondsperday, # 222Rn -> 218Po
132+
3.0 * 60, # 218Po -> 214Pb
133+
27.0 * 60, # 214Pb -> 214Bi
134+
20.0 * 60, # 214Bi -> 214Po -> 210Pb
135+
22.0 * 365 * secondsperday] # 210Pb -> ... -> 206Pb
136+
137+
lambda = log(2) ./ half_lifes[1:nproducts]
138+
139+
pr1 = fill(1.0, nproducts, nproducts)
140+
for i in 1:nproducts
141+
for m in 1:(i - 1)
142+
for q in m:(i - 1)
143+
pr1[i, m] = pr1[i, m] * lambda[q]
144+
end
145+
end
146+
end
147+
148+
pr2 = fill(1.0, nproducts, nproducts, nproducts)
149+
for i in 1:nproducts
150+
for m in 1:(i - 1)
151+
for k in m:i
152+
for j in m:i
153+
# TODO if (j==k) CYCLE
154+
pr2[i, m, k] = pr2[i, m, k] * (lambda[j] - lambda[k])
155+
end
156+
end
157+
end
158+
end
159+
new{nproducts}(dt, lambda, pr1, pr2)
160+
end
161+
end
162+
163+
@inline function source_terms_radon_decay(u, tracer_equations::PassiveTracerEquations,
164+
setup::RadonDecaySetup{5})
165+
@unpack dt, lambda, pr1, pr2 = setup
166+
tracer = tracer_variables(u, tracer_equations)
167+
168+
tracer_new = zeros(eltype(u), 5)
169+
for i in 1:5
170+
tracer_new[i] = tracer[i] * exp(-lambda[i] * dt)
171+
for m in 1:(i - 1)
172+
tmp = 0.0
173+
for k in m:i
174+
tmp = tmp + exp(-lambda[k] * dt) / pr2[i, m, k]
175+
end
176+
tracer_new[i] = tracer_new[i] + tracer[m] * pr1[i, m] * tmp
177+
end
178+
end
179+
180+
return SVector((tracer_new .- tracer) ./ dt)
181+
end
182+
183+
@inline function source_terms_radon_decay(u, tracer_equations::PassiveTracerEquations,
184+
setup::RadonDecaySetup{1})
185+
@unpack dt, lambda = setup
186+
tau = inv(lambda[1])
187+
tracer = Trixi.tracers(u, tracer_equations)
188+
y = tracer .* ((exp(-dt / tau) - 1) / dt)
189+
return y
190+
end
191+
192+
@inline function source_terms_radon_init(u, x, setup::RadonDecaySetup{1})
193+
radius_earth = 6.371229e6 # r_planet
194+
lon, lat, r = cartesian_to_sphere(x)
195+
lon_pos = 0.889036791765
196+
lon_dist = min(abs(lon - lon_pos), abs(lon + 2 * pi - lon_pos),
197+
abs(lon - 2 * pi - lon_pos))
198+
tracer1 = 0.01 * exp(-500 * (lon_dist / pi)^2
199+
-
200+
400 * ((lat - 0.889036791765) / pi)^2
201+
-
202+
100 * ((r - radius_earth) / 30_000)^2)
203+
tracer2 = 0.009 * exp(-300 * (lon_dist / pi)^2
204+
-
205+
600 * ((lat - 0.4) / pi)^2
206+
-
207+
100 * ((r - radius_earth) / 30_000)^2)
208+
return SVector(u[1] * tracer1, u[1] * tracer2)
209+
end
210+
211+
# source terms
212+
@inline function (setup::RadonDecaySetup)(u, x, t,
213+
tracer_equations::PassiveTracerEquations)
214+
@unpack flow_equations = tracer_equations
215+
216+
u_flow = Trixi.flow_variables(u, tracer_equations)
217+
source_terms_flow = source_terms_coriolis(u_flow, x, t, flow_equations) +
218+
source_terms_hs_relaxation(u_flow, x, t, flow_equations)
219+
source_terms_tracer = source_terms_radon_init(u, x, setup) +
220+
source_terms_radon_decay(u, tracer_equations, setup)
221+
@info source_terms_tracer
222+
return vcat(source_terms_flow, source_terms_tracer)
223+
end
224+
225+
###############################################################################
226+
# semidiscretization of the compressible Euler equations
227+
gamma = 1.4
228+
flow_equations = CompressibleEulerEquationsWithGravity3D(gamma)
229+
230+
#nproducts = 5
231+
nproducts = 1
232+
equations = PassiveTracerEquations(flow_equations, n_tracers = 2)
233+
radon_setup = RadonDecaySetup(; nproducts = nproducts, dt = 6.0) # rough estimate for timestep
234+
235+
initial_condition = initial_condition_isothermal
236+
237+
boundary_conditions = Dict(:inside => TrixiAtmo.boundary_condition_slip_wall_noncons,
238+
:outside => TrixiAtmo.boundary_condition_slip_wall_noncons)
239+
240+
volume_flux = (FluxTracerEquationsCentral(flux_kennedy_gruber),
241+
TrixiAtmo.FluxTracerEquationsZero(flux_nonconservative_waruszewski))
242+
#surface_flux = (FluxTracerEquationsCentral(FluxLMARS(340.0)),
243+
surface_flux = (flux_lax_friedrichs,
244+
TrixiAtmo.FluxTracerEquationsZero(flux_nonconservative_waruszewski))
245+
246+
# Giraldo: (10,8), polydeg 4
247+
lat_lon_trees_per_dim = 4
248+
layers = 2
249+
polydeg = 3
250+
251+
solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux,
252+
volume_integral = VolumeIntegralFluxDifferencing(volume_flux))
253+
254+
mesh = Trixi.T8codeMeshCubedSphere(lat_lon_trees_per_dim, layers, 6.371229e6, 30000.0,
255+
polydeg = polydeg, initial_refinement_level = 0)
256+
257+
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
258+
source_terms = radon_setup,
259+
boundary_conditions = boundary_conditions)
260+
261+
###############################################################################
262+
# ODE solvers, callbacks etc.
263+
264+
tspan = (0.0, 500 * 24 * 60 * 60) # time in seconds for 1 year
265+
ode = semidiscretize(semi, tspan)
266+
267+
summary_callback = SummaryCallback()
268+
269+
analysis_interval = 5000
270+
#analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
271+
272+
alive_callback = AliveCallback(analysis_interval = analysis_interval)
273+
274+
save_solution = SaveSolutionCallback(interval = 20000,
275+
save_initial_solution = true,
276+
save_final_solution = true,
277+
solution_variables = cons2prim,
278+
output_directory = "out_heldsuarez_tracer_small_llf")
279+
280+
callbacks = CallbackSet(summary_callback,
281+
# analysis_callback,
282+
alive_callback,
283+
save_solution)
284+
285+
###############################################################################
286+
# run the simulation
287+
288+
# Use a Runge-Kutta method with automatic (error based) time step size control
289+
# Enable threading of the RK method for better performance on multiple threads
290+
sol = solve(ode,
291+
RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8,
292+
ode_default_options()..., maxiters = 1e8,
293+
callback = callbacks);

0 commit comments

Comments
 (0)