Path: blob/main/examples/t8code_3d_dgsem/elixir_euler_baroclinic_instability.jl
2055 views
# An idealized baroclinic instability test case1# For optimal results consider increasing the resolution to 16x16x8 trees per cube face.2#3# Note that this elixir can take several hours to run.4# Using 24 threads of an AMD Ryzen Threadripper 3990X (more threads don't speed it up further)5# and `check-bounds=no`, this elixirs takes about one hour to run.6# With 16x16x8 trees per cube face on the same machine, it takes about 28 hours.7#8# References:9# - Paul A. Ullrich, Thomas Melvin, Christiane Jablonowski, Andrew Staniforth (2013)10# A proposed baroclinic wave test case for deep- and shallow-atmosphere dynamical cores11# https://doi.org/10.1002/qj.22411213using OrdinaryDiffEqLowStorageRK14using Trixi15using LinearAlgebra1617###############################################################################18# Setup for the baroclinic instability test19gamma = 1.420equations = CompressibleEulerEquations3D(gamma)2122# Initial condition for an idealized baroclinic instability test23# https://doi.org/10.1002/qj.2241, Section 3.2 and Appendix A24function initial_condition_baroclinic_instability(x, t,25equations::CompressibleEulerEquations3D)26lon, lat, r = cartesian_to_sphere(x)27radius_earth = 6.371229e628# Make sure that the r is not smaller than radius_earth29z = max(r - radius_earth, 0.0)3031# Unperturbed basic state32rho, u, p = basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)3334# Stream function type perturbation35u_perturbation, v_perturbation = perturbation_stream_function(lon, lat, z)3637u += u_perturbation38v = v_perturbation3940# Convert spherical velocity to Cartesian41v1 = -sin(lon) * u - sin(lat) * cos(lon) * v42v2 = cos(lon) * u - sin(lat) * sin(lon) * v43v3 = cos(lat) * v4445return prim2cons(SVector(rho, v1, v2, v3, p), equations)46end4748# Steady state for RHS correction below49function steady_state_baroclinic_instability(x, t, equations::CompressibleEulerEquations3D)50lon, lat, r = cartesian_to_sphere(x)51radius_earth = 6.371229e652# Make sure that the r is not smaller than radius_earth53z = max(r - radius_earth, 0.0)5455# Unperturbed basic state56rho, u, p = basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)5758# Convert spherical velocity to Cartesian59v1 = -sin(lon) * u60v2 = cos(lon) * u61v3 = 0.06263return prim2cons(SVector(rho, v1, v2, v3, p), equations)64end6566function cartesian_to_sphere(x)67r = norm(x)68lambda = atan(x[2], x[1])69if lambda < 070lambda += 2 * pi71end72phi = asin(x[3] / r)7374return lambda, phi, r75end7677# Unperturbed balanced steady-state.78# Returns primitive variables with only the velocity in longitudinal direction (rho, u, p).79# The other velocity components are zero.80function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z)81# Parameters from Table 1 in the paper82# Corresponding names in the paper are commented83radius_earth = 6.371229e6 # a84half_width_parameter = 2 # b85gravitational_acceleration = 9.80616 # g86k = 3 # k87surface_pressure = 1e5 # p₀88gas_constant = 287 # R89surface_equatorial_temperature = 310.0 # T₀ᴱ90surface_polar_temperature = 240.0 # T₀ᴾ91lapse_rate = 0.005 # Γ92angular_velocity = 7.29212e-5 # Ω9394# Distance to the center of the Earth95r = z + radius_earth9697# In the paper: T₀98temperature0 = 0.5 * (surface_equatorial_temperature + surface_polar_temperature)99# In the paper: A, B, C, H100const_a = 1 / lapse_rate101const_b = (temperature0 - surface_polar_temperature) /102(temperature0 * surface_polar_temperature)103const_c = 0.5 * (k + 2) * (surface_equatorial_temperature - surface_polar_temperature) /104(surface_equatorial_temperature * surface_polar_temperature)105const_h = gas_constant * temperature0 / gravitational_acceleration106107# In the paper: (r - a) / bH108scaled_z = z / (half_width_parameter * const_h)109110# Temporary variables111temp1 = exp(lapse_rate / temperature0 * z)112temp2 = exp(-scaled_z^2)113114# In the paper: ̃τ₁, ̃τ₂115tau1 = const_a * lapse_rate / temperature0 * temp1 +116const_b * (1 - 2 * scaled_z^2) * temp2117tau2 = const_c * (1 - 2 * scaled_z^2) * temp2118119# In the paper: ∫τ₁(r') dr', ∫τ₂(r') dr'120inttau1 = const_a * (temp1 - 1) + const_b * z * temp2121inttau2 = const_c * z * temp2122123# Temporary variables124temp3 = r / radius_earth * cos(lat)125temp4 = temp3^k - k / (k + 2) * temp3^(k + 2)126127# In the paper: T128temperature = 1 / ((r / radius_earth)^2 * (tau1 - tau2 * temp4))129130# In the paper: U, u (zonal wind, first component of spherical velocity)131big_u = gravitational_acceleration / radius_earth * k * temperature * inttau2 *132(temp3^(k - 1) - temp3^(k + 1))133temp5 = radius_earth * cos(lat)134u = -angular_velocity * temp5 + sqrt(angular_velocity^2 * temp5^2 + temp5 * big_u)135136# Hydrostatic pressure137p = surface_pressure *138exp(-gravitational_acceleration / gas_constant * (inttau1 - inttau2 * temp4))139140# Density (via ideal gas law)141rho = p / (gas_constant * temperature)142143return rho, u, p144end145146# Perturbation as in Equations 25 and 26 of the paper (analytical derivative)147function perturbation_stream_function(lon, lat, z)148# Parameters from Table 1 in the paper149# Corresponding names in the paper are commented150perturbation_radius = 1 / 6 # d₀ / a151perturbed_wind_amplitude = 1.0 # Vₚ152perturbation_lon = pi / 9 # Longitude of perturbation location153perturbation_lat = 2 * pi / 9 # Latitude of perturbation location154pertz = 15000 # Perturbation height cap155156# Great circle distance (d in the paper) divided by a (radius of the Earth)157# because we never actually need d without dividing by a158great_circle_distance_by_a = acos(sin(perturbation_lat) * sin(lat) +159cos(perturbation_lat) * cos(lat) *160cos(lon - perturbation_lon))161162# In the first case, the vertical taper function is per definition zero.163# In the second case, the stream function is per definition zero.164if z > pertz || great_circle_distance_by_a > perturbation_radius165return 0.0, 0.0166end167168# Vertical tapering of stream function169perttaper = 1.0 - 3 * z^2 / pertz^2 + 2 * z^3 / pertz^3170171# sin/cos(pi * d / (2 * d_0)) in the paper172sin_, cos_ = sincos(0.5 * pi * great_circle_distance_by_a / perturbation_radius)173174# Common factor for both u and v175factor = 16 / (3 * sqrt(3)) * perturbed_wind_amplitude * perttaper * cos_^3 * sin_176177u_perturbation = -factor * (-sin(perturbation_lat) * cos(lat) +178cos(perturbation_lat) * sin(lat) * cos(lon - perturbation_lon)) /179sin(great_circle_distance_by_a)180181v_perturbation = factor * cos(perturbation_lat) * sin(lon - perturbation_lon) /182sin(great_circle_distance_by_a)183184return u_perturbation, v_perturbation185end186187@inline function source_terms_baroclinic_instability(u, x, t,188equations::CompressibleEulerEquations3D)189radius_earth = 6.371229e6 # a190gravitational_acceleration = 9.80616 # g191angular_velocity = 7.29212e-5 # Ω192193r = norm(x)194# Make sure that r is not smaller than radius_earth195z = max(r - radius_earth, 0.0)196r = z + radius_earth197198du1 = zero(eltype(u))199200# Gravity term201temp = -gravitational_acceleration * radius_earth^2 / r^3202du2 = temp * u[1] * x[1]203du3 = temp * u[1] * x[2]204du4 = temp * u[1] * x[3]205du5 = temp * (u[2] * x[1] + u[3] * x[2] + u[4] * x[3])206207# Coriolis term, -2Ω × ρv = -2 * angular_velocity * (0, 0, 1) × u[2:4]208du2 -= -2 * angular_velocity * u[3]209du3 -= 2 * angular_velocity * u[2]210211return SVector(du1, du2, du3, du4, du5)212end213214###############################################################################215# Start of the actual elixir, semidiscretization of the problem216217initial_condition = initial_condition_baroclinic_instability218219boundary_conditions = Dict(:inside => boundary_condition_slip_wall,220:outside => boundary_condition_slip_wall)221222# This is a good estimate for the speed of sound in this example.223# Other values between 300 and 400 should work as well.224surface_flux = FluxLMARS(340)225volume_flux = flux_kennedy_gruber226solver = DGSEM(polydeg = 5, surface_flux = surface_flux,227volume_integral = VolumeIntegralFluxDifferencing(volume_flux))228229# For optimal results, use (16, 8) here230trees_per_cube_face = (8, 4)231inner_radius = 6.371229e6 # Radius of the inner side of the shell232thickness = 30000.0 # Thickness of the shell233mesh = Trixi.T8codeMeshCubedSphere(trees_per_cube_face..., inner_radius, thickness,234polydeg = 5, initial_refinement_level = 0)235236semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,237source_terms = source_terms_baroclinic_instability,238boundary_conditions = boundary_conditions)239240###############################################################################241# ODE solvers, callbacks etc.242243tspan = (0.0, 10 * 24 * 60 * 60.0) # time in seconds for 10 days244245# Save RHS of the steady state and subtract it in every RHS evaluation.246# This trick preserves the steady state exactly (to machine rounding errors, of course).247# Otherwise, this elixir produces entirely unusable results for a resolution of 8x8x4 cells248# per cube face with a polydeg of 3.249# With this trick, even the polydeg 3 simulation produces usable (although badly resolved) results,250# and most of the grid imprinting in higher polydeg simulation is eliminated.251#252# See https://github.com/trixi-framework/Trixi.jl/issues/980 for more information.253u_steady_state = compute_coefficients(steady_state_baroclinic_instability, tspan[1], semi)254# Use a `let` block for performance (otherwise du_steady_state will be a global variable)255let du_steady_state = similar(u_steady_state)256# Save RHS of the steady state257Trixi.rhs!(du_steady_state, u_steady_state, semi, tspan[1])258259global function corrected_rhs!(du, u, semi, t)260# Normal RHS evaluation261Trixi.rhs!(du, u, semi, t)262# Correct by subtracting the steady-state RHS263Trixi.@trixi_timeit Trixi.timer() "rhs correction" begin264# Use Trixi.@threaded for threaded performance265Trixi.@threaded for i in eachindex(du)266du[i] -= du_steady_state[i]267end268end269end270end271u0 = compute_coefficients(tspan[1], semi)272ode = ODEProblem(corrected_rhs!, u0, tspan, semi)273274summary_callback = SummaryCallback()275276analysis_interval = 5000277analysis_callback = AnalysisCallback(semi, interval = analysis_interval)278279alive_callback = AliveCallback(analysis_interval = analysis_interval)280281save_solution = SaveSolutionCallback(interval = 5000,282save_initial_solution = true,283save_final_solution = true,284solution_variables = cons2prim)285286callbacks = CallbackSet(summary_callback,287analysis_callback,288alive_callback,289save_solution)290291###############################################################################292# run the simulation293294# Use a Runge-Kutta method with automatic (error based) time step size control295# Enable threading of the RK method for better performance on multiple threads296sol = solve(ode, RDPK3SpFSAL49(thread = Trixi.True());297abstol = 1.0e-6, reltol = 1.0e-6,298ode_default_options()..., callback = callbacks);299300301