diff --git a/examples/moist_bubble/comparison_elixirs/elixir_moist_euler_moist_bubble_lucas.jl b/examples/moist_bubble/comparison_elixirs/elixir_moist_euler_moist_bubble_lucas.jl new file mode 100644 index 00000000..8fe7690d --- /dev/null +++ b/examples/moist_bubble/comparison_elixirs/elixir_moist_euler_moist_bubble_lucas.jl @@ -0,0 +1,300 @@ +using OrdinaryDiffEqLowStorageRK +using Trixi, TrixiAtmo +using TrixiAtmo: cons2aeqpot, saturation_pressure, source_terms_moist_bubble, + flux_LMARS +using NLsolve: nlsolve + +############################################################################### +# semidiscretization of the compressible moist Euler equations + +equations = CompressibleMoistEulerEquations2D() + +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, equations::CompressibleMoistEulerEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql = perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + + v1 = 0.0 + v2 = 0.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho, rho_v1, rho_v2, rho_E, rho_qv, rho_ql) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + p_v = rho_qv * R_v * T_loc + p_d = p_loc - p_v + T_C = T_loc - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + H = p_v / p_vs + r_v = rho_qv / rho_d + r_l = rho_ql / rho_d + r_t = r_v + r_l + + # equivalent potential temperature + a = T_loc * (p_0 / p_d)^(R_d / (c_pd + r_t * c_pl)) + b = H^(-r_v * R_v / c_pd) + L_v = L_00 + (c_pv - c_pl) * T_loc + c = exp(L_v * r_v / ((c_pd + r_t * c_pl) * T_loc)) + aeq_pot = (a * b * c) # TODO: this is not used. remove? + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(equations) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations) + return initial_condition_moist_bubble(x, t, equations, atmosphere_data) +end + +initial_condition = initial_condition_moist + +boundary_condition = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +source_term = source_terms_moist_bubble + +############################################################################### +# Get the DG approximation space + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_LMARS +solver = DGSEM(basis, surface_flux) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20000.0, 10000.0) + +cells_per_dimension = (128, 64) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false)) + +############################################################################### +# create the semi discretization object + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition, + source_terms = source_term) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +solution_variables = cons2aeqpot + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,), + extra_analysis_integrals = (entropy, energy_total, + saturation_pressure)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = solution_variables) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy.jl b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy.jl new file mode 100644 index 00000000..25bdacd9 --- /dev/null +++ b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy.jl @@ -0,0 +1,224 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: saturation_vapour_pressure, saturation_vapour_pressure_derivative, + saturation_residual, saturation_residual_jacobian, + terminal_velocity_rain + +function initial_condition_convergence_test_rainy(x, t, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + + # define rho like in dry convergence test + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + rho = c + A * sin(ω * (x[1] + x[2] - t)) + + # define variables of rho + temperature = rho + 250.0 + rho_vapour = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + rho_cloud = rho / c_l * 3000 + rho_rain = rho / c_l * 1000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist - rho_rain + + # define matching energydensity with v1 := 1 and v2 := 1 , initially + energy = (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * + temperature + energy += rho_vapour * ref_L + rho + + return SVector(rho_dry, rho_moist, rho_rain, rho, rho, energy, rho_vapour, rho_cloud, + temperature) +end + +function source_terms_convergence_test_rainy(u, x, t, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + N_0 = equations.rain_water_distr + v_0 = equations.v_mean_rain + + # help constant for terminal rain velocity derivative ( \Gamma(4.5) / 6 ~= 1.9386213994279082 ) + c_help = v_0 * 1.9386213994279082 * (pi * N_0)^(-0.125) + + # define rho like initial condition + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + si, co = sincos(ω * (x[1] + x[2] - t)) + rho = c + A * si + rho_x = ω * A * co + rho_t = -rho_x + + # define variables of rho + temperature = rho + 250.0 + sat_vap_p = saturation_vapour_pressure(temperature, equations) + rho_vapour = sat_vap_p / (R_v * temperature) + rho_cloud = rho / c_l * 3000 + rho_rain = rho / c_l * 1000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist - rho_rain + vr = terminal_velocity_rain(rho_moist, rho_rain, equations) + + # define needed derivatives + sat_vap_p_t = rho_t * saturation_vapour_pressure_derivative(temperature, equations) + sat_vap_p_x = rho_x * saturation_vapour_pressure_derivative(temperature, equations) + + rho_vapour_t = (sat_vap_p_t * temperature - rho_t * sat_vap_p) / (R_v * temperature^2) + rho_vapour_x = (sat_vap_p_x * temperature - rho_x * sat_vap_p) / (R_v * temperature^2) + + rho_cloud_t = rho_t / c_l * 3000 + rho_cloud_x = rho_x / c_l * 3000 + + rho_rain_t = rho_t / c_l * 1000 + rho_rain_x = rho_x / c_l * 1000 + + rho_moist_t = rho_vapour_t + rho_cloud_t + rho_moist_x = rho_vapour_x + rho_cloud_x + + rho_dry_t = rho_t - rho_moist_t - rho_rain_t + rho_dry_x = rho_x - rho_moist_x - rho_rain_x + + energy_t = (c_vd * rho_dry_t + c_vv * rho_vapour_t + c_l * (rho_cloud_t + rho_rain_t)) * + temperature + energy_t += (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * rho_t + energy_t += rho_vapour_t * ref_L + rho_t + + energy_x = (c_vd * rho_dry_x + c_vv * rho_vapour_x + c_l * (rho_cloud_x + rho_rain_x)) * + temperature + energy_x += (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * rho_x + energy_x += rho_vapour_x * ref_L + rho_x + + pressure_x = (rho_dry_x * R_d + rho_vapour_x * R_v) * temperature + pressure_x += (rho_dry * R_d + rho_vapour * R_v) * rho_x # temperature_x = rho_x + + vr_x = c_help * 0.125 * + ((rho_rain_x * rho_moist - rho_rain * rho_moist_x) / (rho_moist + rho_rain)^2) + vr_x *= (rho_rain / (rho_moist + rho_rain))^(-0.875) + + rhor_vr__x = rho_rain_x * vr + rho_rain * vr_x + + # calculate source terms for manufactured solution + # density + S_rho_dry = rho_dry_t + 2.0 * rho_dry_x + S_rho_moist = rho_moist_t + 2.0 * rho_moist_x + S_rho_rain = rho_rain_t + 2.0 * rho_rain_x - rhor_vr__x + + # "momentum" + S_rho_v1 = rho_x + pressure_x - rhor_vr__x + S_rho_v2 = rho_x + pressure_x - rhor_vr__x + + # "energy" + S_energy = energy_t + 2.0 * (energy_x + pressure_x) - (c_l * rho_x * rho_rain * vr) + S_energy -= (c_l * temperature + 1) * rhor_vr__x + + return SVector(S_rho_dry, S_rho_moist, S_rho_rain, S_rho_v1, S_rho_v2, S_energy, 0.0, + 0.0, 0.0) +end + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerEquations2D() + +initial_condition = initial_condition_convergence_test_rainy + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (6, 6) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test_rainy) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 10000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +stage_limiter! = NonlinearSolveDG(saturation_residual, saturation_residual_jacobian, + SVector(7, 8, 9), 1e-9) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false, stage_limiter!), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary + +# For copy-paste convenience: +#convergence_test("TrixiAtmo.jl/examples/convergence_test_elixirs/convergence_test_rainy.jl", 5) + +#= +#################################################################################################### +l2 +rho_dry rho_moist rho_rain rho_v1 rho_v2 energy_density rho_vapour rho_cloud temperature + +error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC +2.40e-06 - 3.89e-05 - 1.52e-05 - 7.53e-03 - 2.09e-02 - 5.20e+01 - 1.75e-08 - 3.89e-05 - 2.16e-04 - + +1.43e-07 4.07 2.32e-06 4.07 9.72e-07 3.97 2.90e-04 4.70 1.39e-03 3.91 3.14e+00 4.05 1.15e-09 3.93 2.32e-06 4.07 1.42e-05 3.93 +8.48e-09 4.08 1.37e-07 4.08 1.20e-07 3.02 1.13e-05 4.68 3.10e-05 5.49 1.78e-01 4.14 2.69e-11 5.42 1.37e-07 4.08 3.32e-07 5.41 +5.29e-10 4.00 8.57e-09 4.00 5.27e-09 4.51 6.34e-07 4.16 9.54e-07 5.02 1.39e-02 3.68 1.24e-12 4.43 8.57e-09 4.00 1.54e-08 4.43 +3.31e-11 4.00 5.34e-10 4.00 2.02e-10 4.70 3.85e-08 4.04 3.80e-08 4.65 7.79e-04 4.16 7.09e-14 4.13 5.34e-10 4.00 8.78e-10 4.13 + +mean 4.04 mean 4.04 mean 4.05 mean 4.39 mean 4.77 mean 4.01 mean 4.48 mean 4.04 mean 4.48 +---------------------------------------------------------------------------------------------------- +linf +rho_dry rho_moist rho_rain rho_v1 rho_v2 energy_density rho_vapour rho_cloud temperature + +error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC +1.68e-05 - 2.74e-04 - 8.69e-05 - 2.34e-02 - 6.25e-02 - 3.85e+02 - 6.00e-08 - 2.74e-04 - 7.43e-04 - + +1.07e-06 3.97 1.73e-05 3.98 4.36e-06 4.32 1.13e-03 4.38 3.11e-03 4.33 2.29e+01 4.07 5.72e-09 3.39 1.73e-05 3.98 7.09e-05 3.39 +6.01e-08 4.15 9.73e-07 4.16 3.46e-07 3.66 4.43e-05 4.67 6.53e-05 5.57 1.08e+00 4.40 1.63e-10 5.13 9.73e-07 4.16 2.00e-06 5.15 +3.69e-09 4.02 5.98e-08 4.03 2.91e-08 3.57 2.47e-06 4.16 2.37e-06 4.79 9.44e-02 3.52 9.40e-12 4.12 5.97e-08 4.03 1.15e-07 4.12 +2.30e-10 4.01 3.71e-09 4.01 1.47e-09 4.31 1.50e-07 4.04 1.16e-07 4.35 5.51e-03 4.10 5.70e-13 4.04 3.71e-09 4.01 6.97e-09 4.04 + +mean 4.04 mean 4.04 mean 3.96 mean 4.31 mean 4.76 mean 4.02 mean 4.17 mean 4.04 mean 4.18 +---------------------------------------------------------------------------------------------------- +Dict{Symbol, Any} with 3 entries: + :variables => ("rho_dry", "rho_moist", "rho_rain", "rho_v1", "rho_v2", "energy_density", "rho_vapour", "rho_cloud", "temperature") + :l2 => [4.03641, 4.03834, 4.04901, 4.39454, 4.7665, 4.00677, 4.47752, 4.03837, 4.47639] + :linf => [4.03824, 4.04306, 3.96283, 4.31327, 4.76078, 4.023, 4.17127, 4.04306, 4.17577] +=# diff --git a/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_dry.jl b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_dry.jl new file mode 100644 index 00000000..06a5f8a0 --- /dev/null +++ b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_dry.jl @@ -0,0 +1,151 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo + +# adapted from compressible_euler_2d.jl +function initial_condition_convergence_test(x, t, + equations::CompressibleRainyEulerEquations2D) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + ini = c + A * sin(ω * (x[1] + x[2] - t)) + + rho = ini + rho_v1 = ini + rho_v2 = ini + rho_e = ini^2 + + return SVector(rho, 0.0, 0.0, rho_v1, rho_v2, rho_e, 0.0, 0.0, 0.0) +end + +# adapted from compressible_euler_2d.jl +function source_terms_convergence_test(u, x, t, + equations::CompressibleRainyEulerEquations2D) + c_pd = equations.c_dry_air_const_pressure + c_vd = equations.c_dry_air_const_volume + + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + γ = c_pd / c_vd + + x1, x2 = x + si, co = sincos(ω * (x1 + x2 - t)) + rho = c + A * si + rho_x = ω * A * co + # Notice that d/dt rho = -d/dx rho = -d/dy rho. + + tmp = (2 * rho - 1) * (γ - 1) #+ R_d * ref_temp + + du1 = rho_x + du2 = rho_x * (1 + tmp) + du3 = du2 + du4 = 2 * rho_x * (rho + tmp) + + return SVector(du1, 0.0, 0.0, du2, du3, du4, 0.0, 0.0, 0.0) +end + +# from elixir_euler_source_terms.jl +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerEquations2D() + +initial_condition = initial_condition_convergence_test + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (8, 8) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = analysis_interval, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary + +#= For copy-paste convenience: +#convergence_test("TrixiAtmo.jl/examples/convergence_test_elixirs/convergence_test_rainy_dry.jl", 5) + +#################################################################################################### +l2 +rho_dry rho_moist rho_rain rho_v1 rho_v2 energy_density rho_vapour rho_cloud temperature +error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC error +EOC +2.80e-05 - 0.00e+00 - 0.00e+00 - 2.90e-05 - 2.90e-05 - 9.66e-05 - 0.00e+00 - 0.00e+00 - 0.00e+00 +- +9.30e-07 4.91 0.00e+00 NaN 0.00e+00 NaN 1.42e-06 4.36 1.42e-06 4.36 4.82e-06 4.32 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN +7.00e-08 3.73 0.00e+00 NaN 0.00e+00 NaN 9.51e-08 3.90 9.51e-08 3.90 3.29e-07 3.87 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN +4.64e-09 3.92 0.00e+00 NaN 0.00e+00 NaN 6.08e-09 3.97 6.08e-09 3.97 2.12e-08 3.95 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN +2.93e-10 3.98 0.00e+00 NaN 0.00e+00 NaN 3.82e-10 3.99 3.82e-10 3.99 1.33e-09 3.99 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN + +mean 4.14 mean NaN mean NaN mean 4.05 mean 4.05 mean 4.04 mean NaN mean NaN mean +NaN +---------------------------------------------------------------------------------------------------- +linf +rho_dry rho_moist rho_rain rho_v1 rho_v2 energy_density rho_vapour rho_cloud temperature +error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC error +EOC +1.97e-04 - 0.00e+00 - 0.00e+00 - 2.02e-04 - 2.02e-04 - 8.91e-04 - 0.00e+00 - 0.00e+00 - 0.00e+00 +- +9.62e-06 4.36 0.00e+00 NaN 0.00e+00 NaN 1.17e-05 4.10 1.17e-05 4.10 4.89e-05 4.19 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN +6.24e-07 3.95 0.00e+00 NaN 0.00e+00 NaN 7.49e-07 3.97 7.49e-07 3.97 3.23e-06 3.92 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN +4.06e-08 3.94 0.00e+00 NaN 0.00e+00 NaN 4.97e-08 3.91 4.97e-08 3.91 2.10e-07 3.94 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN +2.55e-09 3.99 0.00e+00 NaN 0.00e+00 NaN 3.19e-09 3.96 3.19e-09 3.96 1.34e-08 3.97 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 +NaN + +mean 4.06 mean NaN mean NaN mean 3.99 mean 3.99 mean 4.00 mean NaN mean NaN mean +NaN +---------------------------------------------------------------------------------------------------- +Dict{Symbol, Any} with 3 entries: + :variables => ("rho_dry", "rho_moist", "rho_rain", "rho_v1", "rho_v2", "energy_density", "rho_vapour", "rho_cloud", "temperature") + :l2 => [4.13577, NaN, NaN, 4.05385, 4.05385, 4.03601, NaN, NaN, NaN] + :linf => [4.05911, NaN, NaN, 3.9867, 3.98669, 4.00476, NaN, NaN, NaN]=# diff --git a/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit.jl b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit.jl new file mode 100644 index 00000000..9f1b0155 --- /dev/null +++ b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit.jl @@ -0,0 +1,218 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: saturation_vapour_pressure, saturation_vapour_pressure_derivative, + terminal_velocity_rain, RainLimiterDG + +function initial_condition_convergence_test_rainy(x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + + # define rho like in dry convergence test + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + rho = c + A * sin(ω * (x[1] + x[2] - t)) + + # define variables of rho + temperature = rho + 250.0 + rho_vapour = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + rho_cloud = rho / c_l * 3000 + rho_rain = rho / c_l * 1000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist - rho_rain + + # define matching energydensity with v1 := 1 and v2 := 1 , initially + energy = (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * + temperature + energy += rho_vapour * ref_L + rho + + return SVector(rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho, energy) +end + +function source_terms_convergence_test_rainy(u, x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + N_0 = equations.rain_water_distr + v_0 = equations.v_mean_rain + + # help constant for terminal rain velocity derivative ( \Gamma(4.5) / 6 ~= 1.9386213994279082 ) + c_help = v_0 * 1.9386213994279082 * (pi * N_0)^(-0.125) + + # define rho like initial condition + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + si, co = sincos(ω * (x[1] + x[2] - t)) + rho = c + A * si + rho_x = ω * A * co + rho_t = -rho_x + + # define variables of rho + temperature = rho + 250.0 + sat_vap_p = saturation_vapour_pressure(temperature, equations) + rho_vapour = sat_vap_p / (R_v * temperature) + rho_cloud = rho / c_l * 3000 + rho_rain = rho / c_l * 1000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist - rho_rain + vr = terminal_velocity_rain(rho_moist, rho_rain, equations) + + # define needed derivatives + sat_vap_p_t = rho_t * saturation_vapour_pressure_derivative(temperature, equations) + sat_vap_p_x = rho_x * saturation_vapour_pressure_derivative(temperature, equations) + + rho_vapour_t = (sat_vap_p_t * temperature - rho_t * sat_vap_p) / (R_v * temperature^2) + rho_vapour_x = (sat_vap_p_x * temperature - rho_x * sat_vap_p) / (R_v * temperature^2) + + rho_cloud_t = rho_t / c_l * 3000 + rho_cloud_x = rho_x / c_l * 3000 + + rho_rain_t = rho_t / c_l * 1000 + rho_rain_x = rho_x / c_l * 1000 + + rho_moist_t = rho_vapour_t + rho_cloud_t + rho_moist_x = rho_vapour_x + rho_cloud_x + + rho_dry_t = rho_t - rho_moist_t - rho_rain_t + rho_dry_x = rho_x - rho_moist_x - rho_rain_x + + energy_t = (c_vd * rho_dry_t + c_vv * rho_vapour_t + c_l * (rho_cloud_t + rho_rain_t)) * + temperature + energy_t += (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * rho_t + energy_t += rho_vapour_t * ref_L + rho_t + + energy_x = (c_vd * rho_dry_x + c_vv * rho_vapour_x + c_l * (rho_cloud_x + rho_rain_x)) * + temperature + energy_x += (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * rho_x + energy_x += rho_vapour_x * ref_L + rho_x + + pressure_x = (rho_dry_x * R_d + rho_vapour_x * R_v) * temperature + pressure_x += (rho_dry * R_d + rho_vapour * R_v) * rho_x # temperature_x = rho_x + + vr_x = c_help * 0.125 * + ((rho_rain_x * rho_moist - rho_rain * rho_moist_x) / (rho_moist + rho_rain)^2) + vr_x *= (rho_rain / (rho_moist + rho_rain))^(-0.875) + + rhor_vr__x = rho_rain_x * vr + rho_rain * vr_x + + # calculate source terms for manufactured solution + # density + S_rho_dry = rho_dry_t + 2.0 * rho_dry_x + S_rho_vapour = rho_vapour_t + 2.0 * rho_vapour_x + S_rho_cloud = rho_cloud_t + 2.0 * rho_cloud_x + S_rho_rain = rho_rain_t + 2.0 * rho_rain_x - rhor_vr__x + + # "momentum" + S_rho_v1 = rho_x + pressure_x - rhor_vr__x + S_rho_v2 = rho_x + pressure_x - rhor_vr__x + + # "energy" + S_energy = energy_t + 2.0 * (energy_x + pressure_x) - (c_l * rho_x * rho_rain * vr) + S_energy -= (c_l * temperature + 1) * rhor_vr__x + + return SVector(S_rho_dry, S_rho_vapour, S_rho_cloud, S_rho_rain, S_rho_v1, S_rho_v2, + S_energy) +end + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +initial_condition = initial_condition_convergence_test_rainy + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (6, 6) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test_rainy) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 10000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +stage_limiter! = RainLimiterDG() + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false, stage_limiter!), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary + +# For copy-paste convenience: +#convergence_test("TrixiAtmo.jl/examples/convergence_test_elixirs/convergence_test_rainy_explicit.jl", 5) + +#= +#################################################################################################### +l2 +rho_dry rho_vapour rho_cloud rho_rain rho_v1 rho_v2 energy_density +error EOC error EOC error EOC error EOC error EOC error EOC error EOC +2.39e-06 - 1.19e-07 - 3.86e-05 - 1.51e-05 - 7.49e-03 - 2.06e-02 - 5.17e+01 - +1.43e-07 4.06 3.43e-09 5.12 2.32e-06 4.06 9.69e-07 3.96 2.89e-04 4.70 1.38e-03 3.90 3.13e+00 4.04 +8.44e-09 4.09 8.83e-11 5.28 1.37e-07 4.08 1.20e-07 3.02 1.13e-05 4.68 3.09e-05 5.48 1.78e-01 4.14 +5.28e-10 4.00 3.28e-12 4.75 8.55e-09 4.00 5.28e-09 4.51 6.34e-07 4.16 9.54e-07 5.02 1.39e-02 3.68 +3.32e-11 3.99 7.50e-14 5.45 5.35e-10 4.00 2.02e-10 4.71 3.85e-08 4.04 3.80e-08 4.65 7.80e-04 4.16 + +mean 4.03 mean 5.15 mean 4.03 mean 4.05 mean 4.39 mean 4.76 mean 4.00 +---------------------------------------------------------------------------------------------------- +linf +rho_dry rho_vapour rho_cloud rho_rain rho_v1 rho_v2 energy_density +error EOC error EOC error EOC error EOC error EOC error EOC error EOC +1.68e-05 - 5.92e-07 - 2.69e-04 - 8.52e-05 - 2.33e-02 - 6.19e-02 - 3.71e+02 - +1.06e-06 3.99 1.25e-08 5.57 1.72e-05 3.97 4.33e-06 4.30 1.12e-03 4.37 3.08e-03 4.33 2.27e+01 4.03 +5.98e-08 4.15 2.97e-10 5.39 9.68e-07 4.15 3.45e-07 3.65 4.43e-05 4.67 6.53e-05 5.56 1.08e+00 4.39 +3.68e-09 4.02 1.28e-11 4.53 5.96e-08 4.02 2.91e-08 3.57 2.47e-06 4.16 2.37e-06 4.79 9.43e-02 3.52 +2.30e-10 4.00 3.03e-13 5.40 3.71e-09 4.00 1.47e-09 4.31 1.50e-07 4.04 1.16e-07 4.36 5.52e-03 4.09 + +mean 4.04 mean 5.22 mean 4.04 mean 3.96 mean 4.31 mean 4.76 mean 4.01 +---------------------------------------------------------------------------------------------------- +Dict{Symbol, Any} with 3 entries: + :variables => ("rho_dry", "rho_vapour", "rho_cloud", "rho_rain", "rho_v1", "rho_v2", "energy_density") + :l2 => [4.03455, 5.14974, 4.0349, 4.04611, 4.39262, 4.76286, 4.0041] + :linf => [4.03983, 5.22416, 4.03676, 3.95607, 4.31093, 4.75739, 4.00916] +=# diff --git a/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit_dry.jl b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit_dry.jl new file mode 100644 index 00000000..14602092 --- /dev/null +++ b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit_dry.jl @@ -0,0 +1,136 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo + +# adapted from compressible_euler_2d.jl +function initial_condition_convergence_test(x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + ini = c + A * sin(ω * (x[1] + x[2] - t)) + + rho = ini + rho_v1 = ini + rho_v2 = ini + rho_e = ini^2 + + return SVector(rho, 0.0, 0.0, 0.0, rho_v1, rho_v2, rho_e) +end + +# adapted from compressible_euler_2d.jl +function source_terms_convergence_test(u, x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + c_pd = equations.c_dry_air_const_pressure + c_vd = equations.c_dry_air_const_volume + + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + γ = c_pd / c_vd + + x1, x2 = x + si, co = sincos(ω * (x1 + x2 - t)) + rho = c + A * si + rho_x = ω * A * co + # Notice that d/dt rho = -d/dx rho = -d/dy rho. + + tmp = (2 * rho - 1) * (γ - 1) + du1 = rho_x + du2 = rho_x * (1 + tmp) + du3 = du2 + du4 = 2 * rho_x * (rho + tmp) + + return SVector(du1, 0.0, 0.0, 0.0, du2, du3, du4) +end + +# from elixir_euler_source_terms.jl +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +initial_condition = initial_condition_convergence_test + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (8, 8) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = analysis_interval, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary + +#= For copy-paste convenience: +#convergence_test("TrixiAtmo.jl/examples/convergence_test_elixirs/convergence_test_rainy_dry_explicit.jl", 5) + +#################################################################################################### +l2 +rho_dry rho_vapour rho_cloud rho_rain rho_v1 rho_v2 energy_density +error EOC error EOC error EOC error EOC error EOC error EOC error EOC +2.80e-05 - 0.00e+00 - 0.00e+00 - 0.00e+00 - 2.90e-05 - 2.90e-05 - 9.66e-05 - +9.30e-07 4.91 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 1.42e-06 4.36 1.42e-06 4.36 4.82e-06 4.32 +7.00e-08 3.73 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 9.51e-08 3.90 9.51e-08 3.90 3.29e-07 3.87 +4.64e-09 3.92 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 6.08e-09 3.97 6.08e-09 3.97 2.12e-08 3.95 +2.93e-10 3.98 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 3.82e-10 3.99 3.82e-10 3.99 1.33e-09 3.99 + +mean 4.14 mean NaN mean NaN mean NaN mean 4.05 mean 4.05 mean 4.04 +---------------------------------------------------------------------------------------------------- +linf +rho_dry rho_vapour rho_cloud rho_rain rho_v1 rho_v2 energy_density +error EOC error EOC error EOC error EOC error EOC error EOC error EOC +1.97e-04 - 0.00e+00 - 0.00e+00 - 0.00e+00 - 2.02e-04 - 2.02e-04 - 8.91e-04 - +9.62e-06 4.36 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 1.17e-05 4.10 1.17e-05 4.10 4.89e-05 4.19 +6.24e-07 3.95 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 7.49e-07 3.97 7.49e-07 3.97 3.23e-06 3.92 +4.06e-08 3.94 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 4.97e-08 3.91 4.97e-08 3.91 2.10e-07 3.94 +2.55e-09 3.99 0.00e+00 NaN 0.00e+00 NaN 0.00e+00 NaN 3.19e-09 3.96 3.19e-09 3.96 1.34e-08 3.97 + +mean 4.06 mean NaN mean NaN mean NaN mean 3.99 mean 3.99 mean 4.00 +---------------------------------------------------------------------------------------------------- +Dict{Symbol, Any} with 3 entries: + :variables => ("rho_dry", "rho_vapour", "rho_cloud", "rho_rain", "rho_v1", "rho_v2", "energy_density") + :l2 => [4.13577, NaN, NaN, NaN, 4.05385, 4.05385, 4.03601] + :linf => [4.05911, NaN, NaN, NaN, 3.9867, 3.9867, 4.00476]=# diff --git a/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit_moist.jl b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit_moist.jl new file mode 100644 index 00000000..55791844 --- /dev/null +++ b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_explicit_moist.jl @@ -0,0 +1,189 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: saturation_vapour_pressure, saturation_vapour_pressure_derivative, + saturation_residual, saturation_residual_jacobian + +function initial_condition_convergence_test_rainy_no_rain(x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # needed constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + + # define rho like in dry convergence test + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + rho = c + A * sin(ω * (x[1] + x[2] - t)) + + # define variables of rho + temperature = rho + 250.0 + rho_vapour = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + rho_cloud = rho / c_l * 4000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist + + # define matching energydensity with v1 := 1 and v2 := 1 , initially + energy = (c_vd * rho_dry + c_vv * rho_vapour + c_l * rho_cloud) * temperature + energy += rho_vapour * ref_L + rho + + return SVector(rho_dry, rho_vapour, rho_cloud, 0.0, rho, rho, energy) +end + +function source_terms_convergence_test_rainy_no_rain(u, x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # needed constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + + # define rho like initial condition + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + si, co = sincos(ω * (x[1] + x[2] - t)) + rho = c + A * si + rho_x = ω * A * co + rho_t = -rho_x + + # define variables of rho + temperature = rho + 250.0 + sat_vap_p = saturation_vapour_pressure(temperature, equations) + rho_vapour = sat_vap_p / (R_v * temperature) + rho_cloud = rho / c_l * 4000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist + + # define needed derivatives + sat_vap_p_t = rho_t * saturation_vapour_pressure_derivative(temperature, equations) + sat_vap_p_x = rho_x * saturation_vapour_pressure_derivative(temperature, equations) + + rho_vapour_t = (sat_vap_p_t * temperature - rho_t * sat_vap_p) / (R_v * temperature^2) + rho_vapour_x = (sat_vap_p_x * temperature - rho_x * sat_vap_p) / (R_v * temperature^2) + + rho_cloud_t = rho_t / c_l * 4000 + rho_cloud_x = rho_x / c_l * 4000 + + rho_moist_t = rho_vapour_t + rho_cloud_t + rho_moist_x = rho_vapour_x + rho_cloud_x + + rho_dry_t = rho_t - rho_moist_t + rho_dry_x = rho_x - rho_moist_x + + energy_t = (c_vd * rho_dry_t + c_vv * rho_vapour_t + c_l * rho_cloud_t) * temperature + energy_t += (c_vd * rho_dry + c_vv * rho_vapour + c_l * rho_cloud) * rho_t + energy_t += rho_vapour_t * ref_L + rho_t + + energy_x = (c_vd * rho_dry_x + c_vv * rho_vapour_x + c_l * rho_cloud_x) * temperature + energy_x += (c_vd * rho_dry + c_vv * rho_vapour + c_l * rho_cloud) * rho_x + energy_x += rho_vapour_x * ref_L + rho_x + + pressure_x = (rho_dry_x * R_d + rho_vapour_x * R_v) * temperature + pressure_x += (rho_dry * R_d + rho_vapour * R_v) * rho_x # temperature_x = rho_x + + # calculate source terms for manufactured solution + # density + S_rho_dry = rho_dry_t + 2.0 * rho_dry_x + S_rho_vapour = rho_vapour_t + 2.0 * rho_vapour_x + S_rho_cloud = rho_cloud_t + 2.0 * rho_cloud_x + + # "momentum" + S_rho_v1 = rho_x + pressure_x + S_rho_v2 = rho_x + pressure_x + + # "energy" + S_energy = energy_t + 2.0 * (energy_x + pressure_x) + + return SVector(S_rho_dry, S_rho_vapour, S_rho_cloud, 0.0, S_rho_v1, S_rho_v2, S_energy) +end + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +initial_condition = initial_condition_convergence_test_rainy_no_rain + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (8, 8) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test_rainy_no_rain) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 10000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary + +# For copy-paste convenience: +#convergence_test("TrixiAtmo.jl/examples/convergence_test_elixirs/convergence_test_rainy_no_rain_explicit.jl", 4) +#= +#################################################################################################### +l2 +rho_dry rho_vapour rho_cloud rho_rain rho_v1 rho_v2 energy_density +error EOC error EOC error EOC error EOC error EOC error EOC error EOC +8.21e-07 - 1.47e-07 - 1.76e-05 - 0.00e+00 - 7.48e-03 - 7.48e-03 - 1.88e+01 - +4.37e-08 4.23 4.86e-09 4.92 9.38e-07 4.23 0.00e+00 NaN 2.13e-04 5.13 2.13e-04 5.13 1.01e+00 4.23 +2.69e-09 4.02 1.91e-10 4.67 5.78e-08 4.02 0.00e+00 NaN 7.04e-06 4.92 7.04e-06 4.92 6.20e-02 4.02 +1.68e-10 4.00 5.06e-12 5.24 3.61e-09 4.00 0.00e+00 NaN 2.78e-07 4.66 2.78e-07 4.66 3.87e-03 4.00 + +mean 4.09 mean 4.94 mean 4.08 mean NaN mean 4.90 mean 4.90 mean 4.08 +---------------------------------------------------------------------------------------------------- +linf +rho_dry rho_vapour rho_cloud rho_rain rho_v1 rho_v2 energy_density +error EOC error EOC error EOC error EOC error EOC error EOC error EOC +6.66e-06 - 6.69e-07 - 1.42e-04 - 0.00e+00 - 1.55e-02 - 1.55e-02 - 1.54e+02 - +3.44e-07 4.28 1.96e-08 5.09 7.25e-06 4.30 0.00e+00 NaN 4.91e-04 4.98 4.91e-04 4.98 7.81e+00 4.30 +1.93e-08 4.16 7.70e-10 4.67 4.13e-07 4.13 0.00e+00 NaN 1.81e-05 4.76 1.81e-05 4.76 4.43e-01 4.14 +1.18e-09 4.03 2.47e-11 4.96 2.53e-08 4.03 0.00e+00 NaN 8.48e-07 4.42 8.48e-07 4.42 2.72e-02 4.03 + +mean 4.16 mean 4.91 mean 4.15 mean NaN mean 4.72 mean 4.72 mean 4.16 +---------------------------------------------------------------------------------------------------- +Dict{Symbol, Any} with 3 entries: + :variables => ("rho_dry", "rho_vapour", "rho_cloud", "rho_rain", "rho_v1", "rho_v2", "energy_density") + :l2 => [4.08558, 4.94267, 4.08233, NaN, 4.90466, 4.90466, 4.08271] + :linf => [4.1557, 4.90799, 4.15355, NaN, 4.72056, 4.72056, 4.15543]=# diff --git a/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_moist.jl b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_moist.jl new file mode 100644 index 00000000..b2b38a1b --- /dev/null +++ b/examples/moist_bubble/convergence_test_elixirs/convergence_test_rainy_moist.jl @@ -0,0 +1,193 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: saturation_vapour_pressure, saturation_vapour_pressure_derivative, + saturation_residual, saturation_residual_jacobian + +function initial_condition_convergence_test_rainy_no_rain(x, t, + equations::CompressibleRainyEulerEquations2D) + # needed constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + + # define rho like in dry convergence test + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + rho = c + A * sin(ω * (x[1] + x[2] - t)) + + # define variables of rho + temperature = rho + 250.0 + rho_vapour = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + rho_cloud = rho / c_l * 4000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist + + # define matching energydensity with v1 := 1 and v2 := 1 , initially + energy = (c_vd * rho_dry + c_vv * rho_vapour + c_l * rho_cloud) * temperature + energy += rho_vapour * ref_L + rho + + return SVector(rho_dry, rho_moist, 0.0, rho, rho, energy, rho_vapour, rho_cloud, + temperature) +end + +function source_terms_convergence_test_rainy_no_rain(u, x, t, + equations::CompressibleRainyEulerEquations2D) + # needed constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + + # define rho like initial condition + c = 2.0 + A = 0.1 + L = 2.0 + f = 1.0 / L + ω = 2 * pi * f + si, co = sincos(ω * (x[1] + x[2] - t)) + rho = c + A * si + rho_x = ω * A * co + rho_t = -rho_x + + # define variables of rho + temperature = rho + 250.0 + sat_vap_p = saturation_vapour_pressure(temperature, equations) + rho_vapour = sat_vap_p / (R_v * temperature) + rho_cloud = rho / c_l * 4000 + rho_moist = rho_vapour + rho_cloud + rho_dry = rho - rho_moist + + # define needed derivatives + sat_vap_p_t = rho_t * saturation_vapour_pressure_derivative(temperature, equations) + sat_vap_p_x = rho_x * saturation_vapour_pressure_derivative(temperature, equations) + + rho_vapour_t = (sat_vap_p_t * temperature - rho_t * sat_vap_p) / (R_v * temperature^2) + rho_vapour_x = (sat_vap_p_x * temperature - rho_x * sat_vap_p) / (R_v * temperature^2) + + rho_cloud_t = rho_t / c_l * 4000 + rho_cloud_x = rho_x / c_l * 4000 + + rho_moist_t = rho_vapour_t + rho_cloud_t + rho_moist_x = rho_vapour_x + rho_cloud_x + + rho_dry_t = rho_t - rho_moist_t + rho_dry_x = rho_x - rho_moist_x + + energy_t = (c_vd * rho_dry_t + c_vv * rho_vapour_t + c_l * rho_cloud_t) * temperature + energy_t += (c_vd * rho_dry + c_vv * rho_vapour + c_l * rho_cloud) * rho_t + energy_t += rho_vapour_t * ref_L + rho_t + + energy_x = (c_vd * rho_dry_x + c_vv * rho_vapour_x + c_l * rho_cloud_x) * temperature + energy_x += (c_vd * rho_dry + c_vv * rho_vapour + c_l * rho_cloud) * rho_x + energy_x += rho_vapour_x * ref_L + rho_x + + pressure_x = (rho_dry_x * R_d + rho_vapour_x * R_v) * temperature + pressure_x += (rho_dry * R_d + rho_vapour * R_v) * rho_x # temperature_x = rho_x + + # calculate source terms for manufactured solution + # density + S_rho_dry = rho_dry_t + 2.0 * rho_dry_x + S_rho_moist = rho_moist_t + 2.0 * rho_moist_x + + # "momentum" + S_rho_v1 = rho_x + pressure_x + S_rho_v2 = rho_x + pressure_x + + # "energy" + S_energy = energy_t + 2.0 * (energy_x + pressure_x) + + return SVector(S_rho_dry, S_rho_moist, 0.0, S_rho_v1, S_rho_v2, S_energy, 0.0, 0.0, 0.0) +end + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerEquations2D() + +initial_condition = initial_condition_convergence_test_rainy_no_rain + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) + +coordinates_min = (0.0, 0.0) +coordinates_max = (2.0, 2.0) + +cells_per_dimension = (8, 8) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test_rainy_no_rain) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 10000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback) + +stage_limiter! = NonlinearSolveDG(saturation_residual, saturation_residual_jacobian, + SVector(7, 8, 9), 1e-9) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false, stage_limiter!), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary + +# For copy-paste convenience: +#convergence_test("TrixiAtmo.jl/examples/convergence_test_elixirs/convergence_test_rainy_no_rain.jl", 4) +#= +#################################################################################################### +l2 +rho_dry rho_moist rho_rain rho_v1 rho_v2 energy_density rho_vapour rho_cloud temperature +error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC +8.26e-07 - 1.87e-05 - 0.00e+00 - 7.47e-03 - 7.47e-03 - 1.92e+01 - 2.62e-08 - 1.87e-05 - 3.23e-04 - +4.38e-08 4.24 9.63e-07 4.28 0.00e+00 NaN 2.13e-04 5.13 2.13e-04 5.13 1.01e+00 4.25 8.68e-10 4.91 9.63e-07 4.28 1.07e-05 4.91 +2.69e-09 4.02 5.86e-08 4.04 0.00e+00 NaN 7.04e-06 4.92 7.04e-06 4.92 6.23e-02 4.03 3.61e-11 4.59 5.86e-08 4.04 4.46e-07 4.58 +1.68e-10 4.01 3.61e-09 4.02 0.00e+00 NaN 2.78e-07 4.66 2.78e-07 4.66 3.87e-03 4.01 1.06e-12 5.09 3.61e-09 4.02 1.32e-08 5.08 + +mean 4.09 mean 4.11 mean NaN mean 4.90 mean 4.90 mean 4.09 mean 4.86 mean 4.11 mean +4.86 +---------------------------------------------------------------------------------------------------- +linf +rho_dry rho_moist rho_rain rho_v1 rho_v2 energy_density rho_vapour rho_cloud temperature +error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC error EOC +6.66e-06 - 1.45e-04 - 0.00e+00 - 1.55e-02 - 1.55e-02 - 1.54e+02 - 1.24e-07 - 1.45e-04 - 1.54e-03 - +3.51e-07 4.25 8.00e-06 4.18 0.00e+00 NaN 4.91e-04 4.98 4.91e-04 4.98 8.24e+00 4.23 3.85e-09 5.01 8.00e-06 4.18 4.78e-05 5.01 +1.92e-08 4.19 4.16e-07 4.26 0.00e+00 NaN 1.81e-05 4.76 1.81e-05 4.76 4.46e-01 4.21 1.58e-10 4.61 4.16e-07 4.26 1.94e-06 4.62 +1.17e-09 4.03 2.53e-08 4.04 0.00e+00 NaN 8.48e-07 4.42 8.48e-07 4.42 2.72e-02 4.04 5.63e-12 4.81 2.53e-08 4.04 6.94e-08 4.81 + +mean 4.16 mean 4.16 mean NaN mean 4.72 mean 4.72 mean 4.16 mean 4.81 mean 4.16 mean 4.81 +---------------------------------------------------------------------------------------------------- +Dict{Symbol, Any} with 3 entries: + :variables => ("rho_dry", "rho_moist", "rho_rain", "rho_v1", "rho_v2", "energy_density", "rho_vapour", "rho_cloud", "temperature") + :l2 => [4.08874, 4.11293, NaN, 4.90375, 4.90375, 4.09297, 4.86169, 4.11317, 4.85982] + :linf => [4.1567, 4.161, NaN, 4.71912, 4.71912, 4.15581, 4.8092, 4.16105, 4.81099]=# diff --git a/examples/moist_bubble/dry_bubble_elixirs/energy/elixir_rainy_euler_dry_bubble.jl b/examples/moist_bubble/dry_bubble_elixirs/energy/elixir_rainy_euler_dry_bubble.jl new file mode 100644 index 00000000..f766f65a --- /dev/null +++ b/examples/moist_bubble/dry_bubble_elixirs/energy/elixir_rainy_euler_dry_bubble.jl @@ -0,0 +1,120 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_no_phase_change + +# copied from elixir_euler_warm_bubble.jl for quick tests +function initial_condition_bubble_dry(x, t, equations::CompressibleRainyEulerEquations2D) + g = equations.gravity + c_p = equations.c_dry_air_const_pressure + c_v = equations.c_dry_air_const_volume + + # center of perturbation + center_x = 10000.0 + center_z = 2000.0 + # radius of perturbation + radius = 2000.0 + # distance of current x to center of perturbation + r = sqrt((x[1] - center_x)^2 + (x[2] - center_z)^2) + + # perturbation in potential temperature + potential_temperature_ref = 300.0 + potential_temperature_perturbation = 0.0 + if r <= radius + potential_temperature_perturbation = 2 * cospi(0.5 * r / radius)^2 + end + potential_temperature = potential_temperature_ref + potential_temperature_perturbation + + # Exner pressure, solves hydrostatic equation for x[2] + exner = 1 - g / (c_p * potential_temperature) * x[2] + + # pressure + p_0 = 100_000.0 # reference pressure + R = c_p - c_v # gas constant (dry air) + p = p_0 * exner^(c_p / R) + + # temperature + T = potential_temperature * exner + + # density + rho = p / (R * T) + + v1 = 0.0 + v2 = 0.0 + E = c_v * T + 0.5 * (v1^2 + v2^2) + + return SVector(rho, 0.0, 0.0, rho * v1, rho * v2, rho * E, 0.0, 0.0, 0.0) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +equations = CompressibleRainyEulerEquations2D() + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_lax_friedrichs + +solver = DGSEM(basis, surface_flux) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20_000.0, 10_000.0) + +cells_per_dimension = (64, 32) + +periodicity = (true, false) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = periodicity) + +initial_condition = initial_condition_bubble_dry +source_terms = source_terms_no_phase_change + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 100, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2prim) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + maxiters = 1.0e7, + dt = 1.0, + save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/entropy_conservation_tests/moist_bubble/elixir_rainy_euler_explicit_moist_bubble_ec_test.jl b/examples/moist_bubble/entropy_conservation_tests/moist_bubble/elixir_rainy_euler_explicit_moist_bubble_ec_test.jl new file mode 100644 index 00000000..282775ed --- /dev/null +++ b/examples/moist_bubble/entropy_conservation_tests/moist_bubble/elixir_rainy_euler_explicit_moist_bubble_ec_test.jl @@ -0,0 +1,282 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_moist, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2eq_pot_temp, flux_LMARS, flux_ec_rain +using NLsolve: nlsolve + +# Initial condition from elixir_moist_euler_bubble.jl +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + g = 0.0 + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, equations::CompressibleMoistEulerEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql, T_loc = perturb_moist_profile!(x, rho, rho_theta, rho_qv, + rho_ql, + equations::CompressibleMoistEulerEquations2D) + + v1 = 1.0 + v2 = 1.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho - rho_qv - rho_ql, rho_qv, rho_ql, 0.0, rho_v1, rho_v2, rho_E) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql, T_loc) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(CompressibleMoistEulerEquations2D()) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations::CompressibleRainyEulerExplicitEquations2D) + return initial_condition_moist_bubble(x, t, CompressibleMoistEulerEquations2D(), + atmosphere_data) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +volume_flux = flux_ec_rain +surface_flux = volume_flux +volume_integral = VolumeIntegralFluxDifferencing(volume_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20_000.0, 10_000.0) + +cells_per_dimension = (64, 32) +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, true)) + +semi = SemidiscretizationHyperbolic(mesh, equations, + initial_condition_moist, solver, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = 1000) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + maxiters = 1.0e7, + dt = 1.0, + save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/entropy_conservation_tests/weak_blast_wave/rainy_euler_explicit_weak_blast_wave_dry.jl b/examples/moist_bubble/entropy_conservation_tests/weak_blast_wave/rainy_euler_explicit_weak_blast_wave_dry.jl new file mode 100644 index 00000000..3ab66a02 --- /dev/null +++ b/examples/moist_bubble/entropy_conservation_tests/weak_blast_wave/rainy_euler_explicit_weak_blast_wave_dry.jl @@ -0,0 +1,93 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: flux_ec_rain, flux_chandrashekar, + RainLimiterDG, cons2eq_pot_temp + +function initial_condition_weak_blast_wave_dry(x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_vd = equations.c_dry_air_const_volume + R_d = equations.R_dry_air + + if sqrt(x[1]^2 + x[2]^2) > 0.5 + rho = 1.0e-4 + v1 = 0.0 + v2 = 0.0 + p = 1.0 + else + phi = atan(x[2], x[1]) + rho = 1.1691e-4 + v1 = 0.1882 * cos(phi) + v2 = 0.1882 * sin(phi) + p = 1.245 + end + + rho_dry = rho + T = p / (rho_dry * R_d) + + energy_density = c_vd * rho_dry * T + energy_density += 0.5 * rho * (v1^2 + v2^2) + + return SVector(rho_dry, 0.0, 0.0, 0.0, rho * v1, rho * v2, energy_density) +end + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +initial_condition = initial_condition_weak_blast_wave_dry + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +volume_flux = flux_ec_rain +surface_flux = volume_flux +solver = DGSEM(basis, surface_flux, VolumeIntegralFluxDifferencing(volume_flux)) + +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 1_000_000, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_periodic) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.4) +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(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/moist_bubble/entropy_conservation_tests/weak_blast_wave/rainy_euler_explicit_weak_blast_wave_rainy.jl b/examples/moist_bubble/entropy_conservation_tests/weak_blast_wave/rainy_euler_explicit_weak_blast_wave_rainy.jl new file mode 100644 index 00000000..35a31c5e --- /dev/null +++ b/examples/moist_bubble/entropy_conservation_tests/weak_blast_wave/rainy_euler_explicit_weak_blast_wave_rainy.jl @@ -0,0 +1,105 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: saturation_residual, saturation_residual_jacobian, + flux_ec_rain, saturation_vapour_pressure, flux_chandrashekar, + RainLimiterDG, cons2eq_pot_temp, entropy + +function initial_condition_weak_blast_wave_rainy(x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + if sqrt(x[1]^2 + x[2]^2) > 0.5 + rho = 1.0e-4 + v1 = 0.0 + v2 = 0.0 + p = 1.0 + else + phi = atan(x[2], x[1]) + rho = 1.1691e-4 + v1 = 0.1882 * cos(phi) + v2 = 0.1882 * sin(phi) + p = 1.245 + end + + rho_dry = 0.25 * rho + rho_vapour = 0.25 * rho + rho_cloud = 0.25 * rho + rho_rain = 0.25 * rho + + T = p / (rho_dry * R_d + rho_vapour * R_v) + + energy_density = (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) * T + energy_density += L_ref * rho_vapour + 0.5 * rho * (v1^2 + v2^2) + + return SVector(rho_dry, rho_vapour, rho_cloud, rho_rain, rho * v1, rho * v2, + energy_density) +end + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +initial_condition = initial_condition_weak_blast_wave_rainy + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +volume_flux = flux_ec_rain +surface_flux = volume_flux +solver = DGSEM(basis, surface_flux, VolumeIntegralFluxDifferencing(volume_flux)) + +coordinates_min = (-2.0, -2.0) +coordinates_max = (2.0, 2.0) + +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 1_000_000, + periodicity = true) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_periodic) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 0.4) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = (entropy,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +stepsize_callback = StepsizeCallback(cfl = 0.1) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_moist_euler_moist_bubble_dgmulti.jl b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_moist_euler_moist_bubble_dgmulti.jl new file mode 100644 index 00000000..4cd5edaf --- /dev/null +++ b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_moist_euler_moist_bubble_dgmulti.jl @@ -0,0 +1,300 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_moist_bubble, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2aeqpot, flux_LMARS, flux_chandrashekar +using NLsolve: nlsolve +using Plots +using StartUpDG + +equations = CompressibleMoistEulerEquations2D() + +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, equations::CompressibleMoistEulerEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql = perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + + v1 = 0.0 + v2 = 0.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho, rho_v1, rho_v2, rho_E, rho_qv, rho_ql) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + p_v = rho_qv * R_v * T_loc + p_d = p_loc - p_v + T_C = T_loc - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + H = p_v / p_vs + r_v = rho_qv / rho_d + r_l = rho_ql / rho_d + r_t = r_v + r_l + + # equivalent potential temperature + a = T_loc * (p_0 / p_d)^(R_d / (c_pd + r_t * c_pl)) + b = H^(-r_v * R_v / c_pd) + L_v = L_00 + (c_pv - c_pl) * T_loc + c = exp(L_v * r_v / ((c_pd + r_t * c_pl) * T_loc)) + aeq_pot = (a * b * c) # TODO: this is not used. remove? + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(equations) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations) + return initial_condition_moist_bubble(x, t, equations, atmosphere_data) +end + +############################################################################### +# semidiscretization of the compressible moist Euler equations + +initial_condition = initial_condition_moist + +# tag different boundary segments +left(x, tol = 50 * eps()) = abs(x[1] - coordinates_min[1]) < tol +right(x, tol = 50 * eps()) = abs(x[1] - coordinates_max[1]) < tol +bottom(x, tol = 50 * eps()) = abs(x[2] - coordinates_min[2]) < tol +top(x, tol = 50 * eps()) = abs(x[2] - coordinates_max[2]) < tol + +is_on_boundary = Dict(:left => left, :right => right, :top => top, :bottom => bottom) + +boundary_conditions = (; :left => boundary_condition_periodic, + :top => boundary_condition_slip_wall, + :bottom => boundary_condition_slip_wall, + :right => boundary_condition_periodic) + +solver = DGMulti(polydeg = 3, element_type = Quad(), + approximation_type = StartUpDG.Polynomial{MultidimensionalQuadrature}(), + surface_integral = SurfaceIntegralWeakForm(flux_LMARS), + volume_integral = VolumeIntegralWeakForm(), + quad_rule_vol = StartUpDG.quad_nodes(Quad(), 6)) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20_000.0, 10_000.0) + +cells_per_dimension = (200, 100) +mesh = DGMultiMesh(solver, cells_per_dimension; coordinates_min, coordinates_max, + is_on_boundary, periodicity = (true, false)) + +semi = SemidiscretizationHyperbolic(mesh, equations, + initial_condition, solver; + source_terms = source_terms_moist_bubble, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + uEltype = real(solver)) + +alive_callback = AliveCallback(analysis_interval = 1000) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + stepsize_callback) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + maxiters = 1.0e7, + dt = 1.0, + save_everystep = false, callback = callbacks); + +summary_callback() + +pd = PlotData2D(sol; solution_variables = cons2aeqpot); +plot(pd["aeqpottemp"]) diff --git a/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_explicit_moist_bubble.jl b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_explicit_moist_bubble.jl new file mode 100644 index 00000000..e2a221d0 --- /dev/null +++ b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_explicit_moist_bubble.jl @@ -0,0 +1,285 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_moist, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2eq_pot_temp, flux_LMARS, flux_ec_rain +using NLsolve: nlsolve + +# Initial condition from elixir_moist_euler_bubble.jl +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, equations::CompressibleMoistEulerEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql, T_loc = perturb_moist_profile!(x, rho, rho_theta, rho_qv, + rho_ql, + equations::CompressibleMoistEulerEquations2D) + + v1 = 0.0 + v2 = 0.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho - rho_qv - rho_ql, rho_qv, rho_ql, 0.0, rho_v1, rho_v2, rho_E) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql, T_loc) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(CompressibleMoistEulerEquations2D()) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations::CompressibleRainyEulerExplicitEquations2D) + return initial_condition_moist_bubble(x, t, CompressibleMoistEulerEquations2D(), + atmosphere_data) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +equations = CompressibleRainyEulerExplicitEquations2D() + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_LMARS +volume_flux = flux_ec_rain + +volume_integral = VolumeIntegralFluxDifferencing(volume_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20_000.0, 10_000.0) + +cells_per_dimension = (128, 64) +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false)) + +semi = SemidiscretizationHyperbolic(mesh, equations, + initial_condition_moist, solver, + source_terms = source_terms_moist, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = 1000) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + maxiters = 1.0e7, + dt = 1.0, + save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_moist_bubble.jl b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_moist_bubble.jl new file mode 100644 index 00000000..287b5685 --- /dev/null +++ b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_moist_bubble.jl @@ -0,0 +1,289 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_no_phase_change, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2eq_pot_temp, flux_LMARS, flux_chandrashekar, + flux_ec_rain, boundary_condition_simple_slip_wall +using NLsolve: nlsolve + +# Initial condition from elixir_moist_euler_bubble.jl +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, equations::CompressibleMoistEulerEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql, T_loc = perturb_moist_profile!(x, rho, rho_theta, rho_qv, + rho_ql, + equations::CompressibleMoistEulerEquations2D) + + v1 = 0.0 + v2 = 0.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho - rho_qv - rho_ql, rho_qv + rho_ql, 0.0, rho_v1, rho_v2, rho_E, + rho_qv, rho_ql, T_loc) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql, T_loc) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(CompressibleMoistEulerEquations2D()) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations::CompressibleRainyEulerEquations2D) + return initial_condition_moist_bubble(x, t, CompressibleMoistEulerEquations2D(), + atmosphere_data) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +equations = CompressibleRainyEulerEquations2D() + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_LMARS +volume_flux = flux_ec_rain + +volume_integral = VolumeIntegralFluxDifferencing(volume_flux) + +solver = DGSEM(basis, surface_flux, volume_integral) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20_000.0, 10_000.0) + +cells_per_dimension = (128, 64) +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false)) + +semi = SemidiscretizationHyperbolic(mesh, equations, + initial_condition_moist, solver, + source_terms = source_terms_no_phase_change, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = 1000) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +stage_limiter! = NonlinearSolveDG(saturation_residual, saturation_residual_jacobian, + SVector(7, 8, 9), 1e-9) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false, stage_limiter!), + maxiters = 1.0e7, + dt = 1.0, + save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_moist_bubble_dgmulti.jl b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_moist_bubble_dgmulti.jl new file mode 100644 index 00000000..7c1e30a8 --- /dev/null +++ b/examples/moist_bubble/moist_bubble_elixirs/energy/elixir_rainy_euler_moist_bubble_dgmulti.jl @@ -0,0 +1,290 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_no_phase_change, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2eq_pot_temp, flux_LMARS, flux_chandrashekar +using NLsolve: nlsolve +using Plots + +# Initial condition from elixir_moist_euler_bubble.jl +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, equations::CompressibleMoistEulerEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql, T_loc = perturb_moist_profile!(x, rho, rho_theta, rho_qv, + rho_ql, + equations::CompressibleMoistEulerEquations2D) + + v1 = 0.0 + v2 = 0.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho - rho_qv - rho_ql, rho_qv + rho_ql, 0.0, rho_v1, rho_v2, rho_E, + rho_qv, rho_ql, T_loc) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerEquations2D) + @unpack kappa, p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql, T_loc) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(CompressibleMoistEulerEquations2D()) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations::CompressibleRainyEulerEquations2D) + return initial_condition_moist_bubble(x, t, CompressibleMoistEulerEquations2D(), + atmosphere_data) +end + +############################################################################### +# semidiscretization of the compressible moist Euler equations + +equations = CompressibleRainyEulerEquations2D() + +initial_condition = initial_condition_moist + +# tag different boundary segments +left(x, tol = 50 * eps()) = abs(x[1] - coordinates_min[1]) < tol +right(x, tol = 50 * eps()) = abs(x[1] - coordinates_max[1]) < tol +bottom(x, tol = 50 * eps()) = abs(x[2] - coordinates_min[2]) < tol +top(x, tol = 50 * eps()) = abs(x[2] - coordinates_max[2]) < tol + +is_on_boundary = Dict(:left => left, :right => right, :top => top, :bottom => bottom) + +boundary_conditions = (; :left => boundary_condition_periodic, + :top => boundary_condition_slip_wall, + :bottom => boundary_condition_slip_wall, + :right => boundary_condition_periodic) + +solver = DGMulti(polydeg = 1, element_type = Quad(), approximation_type = GaussSBP(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm()) + +coordinates_min = (0.0, 0.0) +coordinates_max = (20_000.0, 10_000.0) + +cells_per_dimension = (200, 100) +mesh = DGMultiMesh(solver, cells_per_dimension; coordinates_min, coordinates_max, + is_on_boundary, periodicity = (true, false)) + +semi = SemidiscretizationHyperbolic(mesh, equations, + initial_condition, solver; + source_terms = source_terms_no_phase_change, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + uEltype = real(solver)) + +alive_callback = AliveCallback(analysis_interval = 1000) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + stepsize_callback) + +stage_limiter! = NonlinearSolveDG(saturation_residual, saturation_residual_jacobian, + SVector(7, 8, 9), 1e-9) + +############################################################################### +# run the simulation +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false, stage_limiter!), + maxiters = 1.0e7, + dt = 1.0, + save_everystep = false, callback = callbacks); + +summary_callback() + +pd = PlotData2D(sol; solution_variables = cons2eq_pot_temp); +plot(pd["eq_pot_temp"]) diff --git a/examples/moist_bubble/moist_bubble_elixirs/potential_temperature/elixir_moist_euler_potential_temperature_moist_bubble.jl b/examples/moist_bubble/moist_bubble_elixirs/potential_temperature/elixir_moist_euler_potential_temperature_moist_bubble.jl new file mode 100644 index 00000000..a0e2d00a --- /dev/null +++ b/examples/moist_bubble/moist_bubble_elixirs/potential_temperature/elixir_moist_euler_potential_temperature_moist_bubble.jl @@ -0,0 +1,301 @@ +using OrdinaryDiffEq +using Trixi, TrixiAtmo +using Plots +using TrixiAtmo: cons2aeqpot, saturation_pressure, source_terms_moist_bubble, + flux_LMARS +using NLsolve: nlsolve + +############################################################################### +# semidiscretization of the compressible moist Euler equations + +equations = CompressibleMoistEulerPotentialTemperatureEquations2D() + +function moist_state(y, dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack p_0, g, c_pd, c_pv, c_vd, c_vv, R_d, R_v, c_pl, L_00 = equations + (p, rho, T, r_t, r_v, rho_qv, theta_e) = y + p0 = y0[1] + + F = zeros(7, 1) + rho_d = rho / (1 + r_t) + p_d = R_d * rho_d * T + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + L = L_00 - (c_pl - c_pv) * T + + F[1] = (p - p0) / dz + g * rho + F[2] = p - (R_d * rho_d + R_v * rho_qv) * T + # H = 1 is assumed + F[3] = (theta_e - + T * (p_d / p_0)^(-R_d / (c_pd + c_pl * r_t)) * + exp(L * r_v / ((c_pd + c_pl * r_t) * T))) + F[4] = r_t - r_t0 + F[5] = rho_qv - rho_d * r_v + F[6] = theta_e - theta_e0 + a = p_vs / (R_v * T) - rho_qv + b = rho - rho_qv - rho_d + # H=1 => phi=0 + F[7] = a + b - sqrt(a * a + b * b) + + return F +end + +function generate_function_of_y(dz, y0, r_t0, theta_e0, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + function function_of_y(y) + return moist_state(y, dz, y0, r_t0, theta_e0, equations) + end +end + +struct AtmosphereLayers{RealT <: Real} + equations::CompressibleMoistEulerPotentialTemperatureEquations2D + # structure: 1--> i-layer (z = total_height/precision *(i-1)), 2--> rho, rho_theta, rho_qv, rho_ql + layer_data::Matrix{RealT} + total_height::RealT + preciseness::Int + layers::Int + ground_state::NTuple{2, RealT} + equivalent_potential_temperature::RealT + mixing_ratios::NTuple{2, RealT} +end + +function AtmosphereLayers(equations; total_height = 10010.0, preciseness = 10, + ground_state = (1.4, 100000.0), + equivalent_potential_temperature = 320, + mixing_ratios = (0.02, 0.02), RealT = Float64) + @unpack p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl = equations + kappa = 1 - inv(equations.gamma) + rho0, p0 = ground_state + r_t0, r_v0 = mixing_ratios + theta_e0 = equivalent_potential_temperature + + rho_qv0 = rho0 * r_v0 + T0 = theta_e0 + y0 = [p0, rho0, T0, r_t0, r_v0, rho_qv0, theta_e0] + + n = convert(Int, total_height / preciseness) + dz = 0.01 + layer_data = zeros(RealT, n + 1, 4) + + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[1, :] = [rho, rho_theta, rho_qv, rho_ql] + for i in (1:n) + y0 = deepcopy(sol.zero) + dz = preciseness + F = generate_function_of_y(dz, y0, r_t0, theta_e0, equations) + sol = nlsolve(F, y0) + p, rho, T, r_t, r_v, rho_qv, theta_e = sol.zero + + rho_d = rho / (1 + r_t) + rho_ql = rho - rho_d - rho_qv + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * (p0 / p)^kappa_M * T * (1 + (R_v / R_d) * r_v) / (1 + r_t) + + layer_data[i + 1, :] = [rho, rho_theta, rho_qv, rho_ql] + end + + return AtmosphereLayers{RealT}(equations, layer_data, total_height, dz, n, ground_state, + theta_e0, mixing_ratios) +end + +# Moist bubble test case from paper: +# G.H. Bryan, J.M. Fritsch, A Benchmark Simulation for Moist Nonhydrostatic Numerical +# Models, MonthlyWeather Review Vol.130, 2917–2928, 2002, +# https://journals.ametsoc.org/view/journals/mwre/130/12/1520-0493_2002_130_2917_absfmn_2.0.co_2.xml. +function initial_condition_moist_bubble(x, t, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D, + atmosphere_layers::AtmosphereLayers) + @unpack layer_data, preciseness, total_height = atmosphere_layers + dz = preciseness + z = x[2] + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_l = (n - 1) * dz + (rho_l, rho_theta_l, rho_qv_l, rho_ql_l) = layer_data[n, :] + z_r = n * dz + if (z_l == total_height) + z_r = z_l + dz + n = n - 1 + end + (rho_r, rho_theta_r, rho_qv_r, rho_ql_r) = layer_data[n + 1, :] + rho = (rho_r * (z - z_l) + rho_l * (z_r - z)) / dz + rho_theta = rho * (rho_theta_r / rho_r * (z - z_l) + rho_theta_l / rho_l * (z_r - z)) / + dz + rho_qv = rho * (rho_qv_r / rho_r * (z - z_l) + rho_qv_l / rho_l * (z_r - z)) / dz + rho_ql = rho * (rho_ql_r / rho_r * (z - z_l) + rho_ql_l / rho_l * (z_r - z)) / dz + + rho, rho_e, rho_qv, rho_ql = perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + + v1 = 0.0 + v2 = 0.0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_E = rho_e + 1 / 2 * rho * (v1^2 + v2^2) + + return SVector(rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_ql) +end + +function perturb_moist_profile!(x, rho, rho_theta, rho_qv, rho_ql, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack p_0, c_pd, c_vd, c_pv, c_vv, R_d, R_v, c_pl, L_00 = equations + kappa = 1 - inv(equations.gamma) + xc = 10000.0 + zc = 2000.0 + rc = 2000.0 + Δθ = 2.0 + + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + p_loc = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + T_loc = p_loc / (R_d * rho_d + R_v * rho_qv) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + + p_v = rho_qv * R_v * T_loc + p_d = p_loc - p_v + T_C = T_loc - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + H = p_v / p_vs + r_v = rho_qv / rho_d + r_l = rho_ql / rho_d + r_t = r_v + r_l + + # equivalent potential temperature + a = T_loc * (p_0 / p_d)^(R_d / (c_pd + r_t * c_pl)) + b = H^(-r_v * R_v / c_pd) + L_v = L_00 + (c_pv - c_pl) * T_loc + c = exp(L_v * r_v / ((c_pd + r_t * c_pl) * T_loc)) + aeq_pot = (a * b * c) # TODO: this is not used. remove? + + # Assume pressure stays constant + if (r < rc && Δθ > 0) + # Calculate background density potential temperature + θ_dens = rho_theta / rho * (p_loc / p_0)^(kappa_M - kappa) + # Calculate perturbed density potential temperature + θ_dens_new = θ_dens * (1 + Δθ * cospi(0.5 * r / rc)^2 / 300) + rt = (rho_qv + rho_ql) / rho_d + rv = rho_qv / rho_d + # Calculate moist potential temperature + θ_loc = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rv) + # Adjust varuables until the temperature is met + if rt > 0 + while true + T_loc = θ_loc * (p_loc / p_0)^kappa + T_C = T_loc - 273.15 + # SaturVapor + pvs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + rho_d_new = (p_loc - pvs) / (R_d * T_loc) + rvs = pvs / (R_v * rho_d_new * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + if abs(θ_new - θ_loc) <= θ_loc * 1.0e-12 + break + else + θ_loc = θ_new + end + end + else + rvs = 0 + T_loc = θ_loc * (p_loc / p_0)^kappa + rho_d_new = p_loc / (R_d * T_loc) + θ_new = θ_dens_new * (1 + rt) / (1 + (R_v / R_d) * rvs) + end + rho_qv = rvs * rho_d_new + rho_ql = (rt - rvs) * rho_d_new + rho = rho_d_new * (1 + rt) + rho_d = rho - rho_qv - rho_ql + kappa_M = (R_d * rho_d + R_v * rho_qv) / + (c_pd * rho_d + c_pv * rho_qv + c_pl * rho_ql) + rho_theta = rho * θ_dens_new * (p_loc / p_0)^(kappa - kappa_M) + rho_e = (c_vd * rho_d + c_vv * rho_qv + c_pl * rho_ql) * T_loc + L_00 * rho_qv + end + return SVector(rho, rho_e, rho_qv, rho_ql) +end + +# Create background atmosphere data set +atmosphere_data = AtmosphereLayers(equations) + +# Create the initial condition with the initial data set +function initial_condition_moist(x, t, equations) + return initial_condition_moist_bubble(x, t, equations, atmosphere_data) +end + +initial_condition = initial_condition_moist + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall) + +source_term = source_terms_moist_bubble + +############################################################################### +# Get the DG approximation space + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_LMARS + +solver = DGSEM(basis, surface_flux) +coordinates_min = (0.0, 0.0) +coordinates_max = (20000.0, 10000.0) + +cells_per_dimension = (128, 64) + +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false)) + +############################################################################### +# create the semi discretization object + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions, + source_terms = source_term) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1000.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +solution_variables = cons2aeqpot + +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = solution_variables) + +stepsize_callback = StepsizeCallback(cfl = 1.0) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback) + +############################################################################### +# run the simulation + +sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks); +summary_callback() # print the timer summary diff --git a/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_explicit_rainy_bubble.jl b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_explicit_rainy_bubble.jl new file mode 100644 index 00000000..9c513a62 --- /dev/null +++ b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_explicit_rainy_bubble.jl @@ -0,0 +1,279 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_rainy, saturation_residual, + saturation_residual_jacobian, RainLimiterDG, + cons2eq_pot_temp, saturation_vapour_pressure, + flux_chandrashekar, flux_LMARS, flux_ec_rain, + source_terms_no_phase_change, + boundary_condition_simple_slip_wall +using NLsolve: nlsolve + +# domain +coordinates_min = (0.0, 0.0) +coordinates_max = (2400.0, 2400.0) + +# hydrostatic dry potential temperature +function theta_d(z, equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + ref_pressure = equations.ref_pressure + + # problem specific constants + surface_temperature = 283.0 + surface_pressure = 8.5e4 + stratification = 1.3e-5 + + # dry potential temperature at surface + Theta0 = surface_temperature * (ref_pressure / surface_pressure)^(R_d / c_pd) + # at height z + theta_d = Theta0 * exp(stratification * z) + + return theta_d +end + +# hydrostatic base state residual +function generate_hydrostatic_residual(pressure_lower, humidity_rel0, z, dz, + equations::CompressibleRainyEulerExplicitEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + g = equations.gravity + + function hydrostatic_residual!(residual, guess) + # variables + pressure, rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + + # pressure derivative residual approximation + residual[1] = (pressure - pressure_lower) / dz + (rho_dry + rho_vapour) * g + + # pressure residual + residual[2] = pressure - temperature * (rho_dry * R_d + rho_vapour * R_v) + + # hydrostatic dry potential temperature residual + residual[3] = theta_d(z, equations) - + temperature * (ref_pressure / pressure)^(R_d / c_pd) + + # humidity residual + residual[4] = rho_vs * (rho_dry + rho_vapour / eps) * humidity_rel0 + residual[4] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[4] *= 1000.0 + end + + return hydrostatic_residual! +end + +function generate_perturbation_residual(pressure_hydrostatic, H_init, z, + equations::CompressibleRainyEulerExplicitEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + + function perturbation_residual!(residual, guess) + # variables + rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + pressure = (rho_dry * R_d + rho_vapour * R_v) * temperature + + # humidity residual + residual[1] = rho_vs * (rho_dry + rho_vapour / eps) * H_init + residual[1] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[1] *= 30.0 + + # hydrostatic dry potential temperature residual + residual[2] = theta_d(z, equations) - + temperature * (ref_pressure / pressure_hydrostatic)^(R_d / c_pd) + + # pressure residual + residual[3] = pressure_hydrostatic - pressure + end + + return perturbation_residual! +end + +# for approximating the dz pressure gradient +struct AtmosphereLayersRainyBubble{RealT <: Real} + layer_data :: Matrix{RealT} + total_height :: RealT + precision :: RealT +end + +function AtmosphereLayersRainyBubble(equations::CompressibleRainyEulerExplicitEquations2D; + total_height = coordinates_max[2] + 1.0, + precision = 1.0, RealT = Float64) + # constants + humidity_rel0 = 0.2 # hydrostatic relative humidity + surface_pressure = 8.5e4 + + # surface layer with initial guesses for rho_dry, rho_vapour and temperature + surface_layer = [surface_pressure, 1.4, 0.04, 300.0] + + # allocate layer_data + n = convert(Int, total_height / precision) + layer_data = zeros(RealT, n + 1, 4) + + # solve (slightly above) surface layer + dz = 0.01 + z = 0.01 + residual_function! = generate_hydrostatic_residual(surface_pressure, humidity_rel0, z, + dz, equations) + layer_data[1, :] .= nlsolve(residual_function!, surface_layer).zero + + # adjust to chosen precision + dz = precision + + # iterate up the atmosphere + for i in (1:n) + z += dz + residual_function! = generate_hydrostatic_residual(layer_data[i, 1], humidity_rel0, + z, dz, equations) + guess = deepcopy(layer_data[i, :]) + layer_data[i + 1, :] .= nlsolve(residual_function!, guess, ftol = 1e-10, + iterations = 20).zero + end + + return AtmosphereLayersRainyBubble{RealT}(layer_data, total_height, precision) +end + +# create layers for initial condition +equations = CompressibleRainyEulerExplicitEquations2D() +layers = AtmosphereLayersRainyBubble(equations) + +function initial_condition_bubble_rainy(x, t, + equations::CompressibleRainyEulerExplicitEquations2D; + atmosphere_layers = layers) + # equations constants + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + ref_L = equations.ref_latent_heat_vap_temp + + # problem specific constants + humidity_rel_bar = 0.2 # background relative humidity field + humidity_max = 1.0 + + # bubble parameters + radius_outer, radius_inner = 300.0, 200.0 # radii of humidity bubble + x_center, z_center = 1200.0, 800.0 # center of humidity bubble + + # radius relative to bubble center + r = sqrt((x[1] - x_center)^2 + (x[2] - z_center)^2) + + # humidity definition + if (r > radius_outer) + # outside the bubble + humidity = humidity_rel_bar + elseif (r > radius_inner) + # outer layers of the bubble + humidity = humidity_rel_bar + + (humidity_max - humidity_rel_bar) * + cos(pi * (r - radius_inner) / (2.0 * (radius_outer - radius_inner)))^2 + else + # inner layer + humidity = humidity_max + end + + # get atmosphere layer and height information + @unpack layer_data, total_height, precision = atmosphere_layers + dz = precision + z = x[2] + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_lower = (n - 1) * dz + z_upper = n * dz + + if (z_lower == total_height) + z_upper = z_lower + dz + n = n - 1 + end + + # check height consistency + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + + # get hydrostatic pressures and approximate between lower and upper data point + pressure_hydrostatic_lower = layer_data[n, 1] + pressure_hydrostatic_upper = layer_data[n + 1, 1] + pressure_hydrostatic = (pressure_hydrostatic_upper * (z - z_lower) + + pressure_hydrostatic_lower * (z_upper - z)) / dz + + # solve perturbation + residual_function! = generate_perturbation_residual(pressure_hydrostatic, humidity, z, + equations) + rho_dry, rho_vapour, temperature = nlsolve(residual_function!, layer_data[n, 2:4], + ftol = 1e-9, iterations = 20).zero + + energy_density = (c_vd * rho_dry + c_vv * rho_vapour) * temperature + rho_vapour * ref_L + + return SVector(rho_dry, rho_vapour, 0.0, 0.0, 0.0, 0.0, energy_density) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_simple_slip_wall, + y_pos = boundary_condition_simple_slip_wall) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_lax_friedrichs +volume_integral = VolumeIntegralFluxDifferencing(flux_ec_rain) + +solver = DGSEM(basis, surface_flux, volume_integral) + +cells_per_dimension = (64, 64) +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false)) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_bubble_rainy, solver, + source_terms = source_terms_rainy, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 600.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +# entropy +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution) + +stage_limiter! = RainLimiterDG() + +############################################################################### +# run the simulation +sol = solve(ode, SSPRK43(stage_limiter!); ode_default_options()..., + maxiters = 1.0e7, save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_explicit_rainy_bubble_diffusion.jl b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_explicit_rainy_bubble_diffusion.jl new file mode 100644 index 00000000..97a97744 --- /dev/null +++ b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_explicit_rainy_bubble_diffusion.jl @@ -0,0 +1,296 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_rainy, saturation_residual, + saturation_residual_jacobian, RainLimiterDG, + cons2eq_pot_temp, saturation_vapour_pressure, + flux_chandrashekar, flux_LMARS, + source_terms_no_phase_change, + boundary_condition_laplace, + flux_ec_rain, + boundary_condition_simple_slip_wall +using NLsolve: nlsolve + +# domain +coordinates_min = (0.0, 0.0) +coordinates_max = (2400.0, 2400.0) + +# hydrostatic dry potential temperature +function theta_d(z, equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + ref_pressure = equations.ref_pressure + + # problem specific constants + surface_temperature = 283.0 + surface_pressure = 8.5e4 + stratification = 1.3e-5 + + # dry potential temperature at surface + Theta0 = surface_temperature * (ref_pressure / surface_pressure)^(R_d / c_pd) + # at height z + theta_d = Theta0 * exp(stratification * z) + + return theta_d +end + +# hydrostatic base state residual +function generate_hydrostatic_residual(pressure_lower, humidity_rel0, z, dz, + equations::CompressibleRainyEulerExplicitEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + g = equations.gravity + + function hydrostatic_residual!(residual, guess) + # variables + pressure, rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + + # pressure derivative residual approximation + residual[1] = (pressure - pressure_lower) / dz + (rho_dry + rho_vapour) * g + + # pressure residual + residual[2] = pressure - temperature * (rho_dry * R_d + rho_vapour * R_v) + + # hydrostatic dry potential temperature residual + residual[3] = theta_d(z, equations) - + temperature * (ref_pressure / pressure)^(R_d / c_pd) + + # humidity residual + residual[4] = rho_vs * (rho_dry + rho_vapour / eps) * humidity_rel0 + residual[4] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[4] *= 1000.0 + end + + return hydrostatic_residual! +end + +function generate_perturbation_residual(pressure_hydrostatic, H_init, z, + equations::CompressibleRainyEulerExplicitEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + + function perturbation_residual!(residual, guess) + # variables + rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + pressure = (rho_dry * R_d + rho_vapour * R_v) * temperature + + # humidity residual + residual[1] = rho_vs * (rho_dry + rho_vapour / eps) * H_init + residual[1] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[1] *= 30.0 + + # hydrostatic dry potential temperature residual + residual[2] = theta_d(z, equations) - + temperature * (ref_pressure / pressure_hydrostatic)^(R_d / c_pd) + + # pressure residual + residual[3] = pressure_hydrostatic - pressure + end + + return perturbation_residual! +end + +# for approximating the dz pressure gradient +struct AtmosphereLayersRainyBubble{RealT <: Real} + layer_data :: Matrix{RealT} + total_height :: RealT + precision :: RealT +end + +function AtmosphereLayersRainyBubble(equations::CompressibleRainyEulerExplicitEquations2D; + total_height = coordinates_max[2] + 1.0, + precision = 1.0, RealT = Float64) + # constants + humidity_rel0 = 0.2 # hydrostatic relative humidity + surface_pressure = 8.5e4 + + # surface layer with initial guesses for rho_dry, rho_vapour and temperature + surface_layer = [surface_pressure, 1.4, 0.04, 300.0] + + # allocate layer_data + n = convert(Int, total_height / precision) + layer_data = zeros(RealT, n + 1, 4) + + # solve (slightly above) surface layer + dz = 0.01 + z = 0.01 + residual_function! = generate_hydrostatic_residual(surface_pressure, humidity_rel0, z, + dz, equations) + layer_data[1, :] .= nlsolve(residual_function!, surface_layer).zero + + # adjust to chosen precision + dz = precision + + # iterate up the atmosphere + for i in (1:n) + z += dz + residual_function! = generate_hydrostatic_residual(layer_data[i, 1], humidity_rel0, + z, dz, equations) + guess = deepcopy(layer_data[i, :]) + layer_data[i + 1, :] .= nlsolve(residual_function!, guess, ftol = 1e-10, + iterations = 20).zero + end + + return AtmosphereLayersRainyBubble{RealT}(layer_data, total_height, precision) +end + +# create layers for initial condition +equations = CompressibleRainyEulerExplicitEquations2D() +layers = AtmosphereLayersRainyBubble(equations) + +function initial_condition_bubble_rainy(x, t, + equations::CompressibleRainyEulerExplicitEquations2D; + atmosphere_layers = layers) + # equations constants + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + ref_L = equations.ref_latent_heat_vap_temp + + # problem specific constants + humidity_rel_bar = 0.2 # background relative humidity field + humidity_max = 1.0 + + # bubble parameters + radius_outer, radius_inner = 300.0, 200.0 # radii of humidity bubble + x_center, z_center = 1200.0, 800.0 # center of humidity bubble + + # radius relative to bubble center + r = sqrt((x[1] - x_center)^2 + (x[2] - z_center)^2) + + # humidity definition + if (r > radius_outer) + # outside the bubble + humidity = humidity_rel_bar + elseif (r > radius_inner) + # outer layers of the bubble + humidity = humidity_rel_bar + + (humidity_max - humidity_rel_bar) * + cos(pi * (r - radius_inner) / (2.0 * (radius_outer - radius_inner)))^2 + else + # inner layer + humidity = humidity_max + end + + # get atmosphere layer and height information + @unpack layer_data, total_height, precision = atmosphere_layers + dz = precision + z = x[2] + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_lower = (n - 1) * dz + z_upper = n * dz + + if (z_lower == total_height) + z_upper = z_lower + dz + n = n - 1 + end + + if (n == 0) + n = 1 + end + + # check height consistency + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + + # get hydrostatic pressures and approximate between lower and upper data point + pressure_hydrostatic_lower = layer_data[n, 1] + pressure_hydrostatic_upper = layer_data[n + 1, 1] + pressure_hydrostatic = (pressure_hydrostatic_upper * (z - z_lower) + + pressure_hydrostatic_lower * (z_upper - z)) / dz + + # solve perturbation + residual_function! = generate_perturbation_residual(pressure_hydrostatic, humidity, z, + equations) + rho_dry, rho_vapour, temperature = nlsolve(residual_function!, layer_data[n, 2:4], + ftol = 1e-9, iterations = 20).zero + + energy_density = (c_vd * rho_dry + c_vv * rho_vapour) * temperature + rho_vapour * ref_L + + return SVector(rho_dry, rho_vapour, 0.0, 0.0, 0.0, 0.0, energy_density) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +diffusivity = 0.5 +equations_parabolic = LaplaceDiffusion2D(diffusivity, equations) + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_simple_slip_wall, + y_pos = boundary_condition_simple_slip_wall) + +boundary_conditions_parabolic = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_laplace, + y_pos = boundary_condition_laplace) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_lax_friedrichs +volume_integral = VolumeIntegralFluxDifferencing(flux_ec_rain) + +solver = DGSEM(basis, surface_flux, volume_integral) + +initial_condition = initial_condition_bubble_rainy +source_terms = source_terms_rainy + +mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 6, + periodicity = (true, false), n_cells_max = 1_000_000) + +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), + initial_condition, solver; source_terms, + boundary_conditions = (boundary_conditions, + boundary_conditions_parabolic)) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 600.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +# entropy +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution) + +stage_limiter! = RainLimiterDG() + +############################################################################### +# run the simulation +sol = solve(ode, SSPRK43(stage_limiter!); ode_default_options()..., + maxiters = 1.0e7, save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_rainy_bubble.jl b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_rainy_bubble.jl new file mode 100644 index 00000000..58c58ece --- /dev/null +++ b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_rainy_bubble.jl @@ -0,0 +1,280 @@ +using OrdinaryDiffEq +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_rainy, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2eq_pot_temp, saturation_vapour_pressure, + flux_chandrashekar, flux_LMARS, flux_ec_rain, + source_terms_no_phase_change, + boundary_condition_simple_slip_wall +using NLsolve: nlsolve + +# domain +coordinates_min = (0.0, 0.0) +coordinates_max = (2400.0, 2400.0) + +# hydrostatic dry potential temperature +function theta_d(z, equations::CompressibleRainyEulerEquations2D) + # constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + ref_pressure = equations.ref_pressure + + # problem specific constants + surface_temperature = 283.0 + surface_pressure = 8.5e4 + stratification = 1.3e-5 + + # dry potential temperature at surface + Theta0 = surface_temperature * (ref_pressure / surface_pressure)^(R_d / c_pd) + # at height z + theta_d = Theta0 * exp(stratification * z) + + return theta_d +end + +# hydrostatic base state residual +function generate_hydrostatic_residual(pressure_lower, humidity_rel0, z, dz, + equations::CompressibleRainyEulerEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + g = equations.gravity + + function hydrostatic_residual!(residual, guess) + # variables + pressure, rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + + # pressure derivative residual approximation + residual[1] = (pressure - pressure_lower) / dz + (rho_dry + rho_vapour) * g + + # pressure residual + residual[2] = pressure - temperature * (rho_dry * R_d + rho_vapour * R_v) + + # hydrostatic dry potential temperature residual + residual[3] = theta_d(z, equations) - + temperature * (ref_pressure / pressure)^(R_d / c_pd) + + # humidity residual + residual[4] = rho_vs * (rho_dry + rho_vapour / eps) * humidity_rel0 + residual[4] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[4] *= 1000.0 + end + + return hydrostatic_residual! +end + +function generate_perturbation_residual(pressure_hydrostatic, H_init, z, + equations::CompressibleRainyEulerEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + + function perturbation_residual!(residual, guess) + # variables + rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + pressure = (rho_dry * R_d + rho_vapour * R_v) * temperature + + # humidity residual + residual[1] = rho_vs * (rho_dry + rho_vapour / eps) * H_init + residual[1] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[1] *= 30.0 + + # hydrostatic dry potential temperature residual + residual[2] = theta_d(z, equations) - + temperature * (ref_pressure / pressure_hydrostatic)^(R_d / c_pd) + + # pressure residual + residual[3] = pressure_hydrostatic - pressure + end + + return perturbation_residual! +end + +# for approximating the dz pressure gradient +struct AtmosphereLayersRainyBubble{RealT <: Real} + layer_data :: Matrix{RealT} + total_height :: RealT + precision :: RealT +end + +function AtmosphereLayersRainyBubble(equations::CompressibleRainyEulerEquations2D; + total_height = coordinates_max[2] + 1.0, + precision = 1.0, RealT = Float64) + # constants + humidity_rel0 = 0.2 # hydrostatic relative humidity + surface_pressure = 8.5e4 + + # surface layer with initial guesses for rho_dry, rho_vapour and temperature + surface_layer = [surface_pressure, 1.4, 0.04, 300.0] + + # allocate layer_data + n = convert(Int, total_height / precision) + layer_data = zeros(RealT, n + 1, 4) + + # solve (slightly above) surface layer + dz = 0.01 + z = 0.01 + residual_function! = generate_hydrostatic_residual(surface_pressure, humidity_rel0, z, + dz, equations) + layer_data[1, :] .= nlsolve(residual_function!, surface_layer).zero + + # adjust to chosen precision + dz = precision + + # iterate up the atmosphere + for i in (1:n) + z += dz + residual_function! = generate_hydrostatic_residual(layer_data[i, 1], humidity_rel0, + z, dz, equations) + guess = deepcopy(layer_data[i, :]) + layer_data[i + 1, :] .= nlsolve(residual_function!, guess, ftol = 1e-10, + iterations = 20).zero + end + + return AtmosphereLayersRainyBubble{RealT}(layer_data, total_height, precision) +end + +# create layers for initial condition +equations = CompressibleRainyEulerEquations2D() +layers = AtmosphereLayersRainyBubble(equations) + +function initial_condition_bubble_rainy(x, t, equations::CompressibleRainyEulerEquations2D; + atmosphere_layers = layers) + # equations constants + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + ref_L = equations.ref_latent_heat_vap_temp + + # problem specific constants + humidity_rel_bar = 0.2 # background relative humidity field + humidity_max = 1.0 + + # bubble parameters + radius_outer, radius_inner = 300.0, 200.0 # radii of humidity bubble + x_center, z_center = 1200.0, 800.0 # center of humidity bubble + + # radius relative to bubble center + r = sqrt((x[1] - x_center)^2 + (x[2] - z_center)^2) + + # humidity definition + if (r > radius_outer) + # outside the bubble + humidity = humidity_rel_bar + elseif (r > radius_inner) + # outer layers of the bubble + humidity = humidity_rel_bar + + (humidity_max - humidity_rel_bar) * + cos(pi * (r - radius_inner) / (2.0 * (radius_outer - radius_inner)))^2 + else + # inner layer + humidity = humidity_max + end + + # get atmosphere layer and height information + @unpack layer_data, total_height, precision = atmosphere_layers + dz = precision + z = x[2] + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_lower = (n - 1) * dz + z_upper = n * dz + + if (z_lower == total_height) + z_upper = z_lower + dz + n = n - 1 + end + + # check height consistency + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + + # get hydrostatic pressures and approximate between lower and upper data point + pressure_hydrostatic_lower = layer_data[n, 1] + pressure_hydrostatic_upper = layer_data[n + 1, 1] + pressure_hydrostatic = (pressure_hydrostatic_upper * (z - z_lower) + + pressure_hydrostatic_lower * (z_upper - z)) / dz + + # solve perturbation + residual_function! = generate_perturbation_residual(pressure_hydrostatic, humidity, z, + equations) + rho_dry, rho_vapour, temperature = nlsolve(residual_function!, layer_data[n, 2:4], + ftol = 1e-9, iterations = 20).zero + + energy_density = (c_vd * rho_dry + c_vv * rho_vapour) * temperature + rho_vapour * ref_L + + return SVector(rho_dry, rho_vapour, 0.0, 0.0, 0.0, energy_density, rho_vapour, 0.0, + temperature) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_simple_slip_wall, + y_pos = boundary_condition_simple_slip_wall) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_lax_friedrichs +volume_integral = VolumeIntegralFluxDifferencing(flux_ec_rain) + +solver = DGSEM(basis, surface_flux, volume_integral) + +cells_per_dimension = (64, 64) +mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false)) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_bubble_rainy, solver, + source_terms = source_terms_rainy, + boundary_conditions = boundary_conditions) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 600.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +# entropy +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution) + +stage_limiter! = NonlinearSolveDG(saturation_residual, saturation_residual_jacobian, + SVector(7, 8, 9), 1e-9) + +############################################################################### +# run the simulation +sol = solve(ode, SSPRK43(stage_limiter!); ode_default_options()..., + maxiters = 1.0e7, save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_rainy_bubble_diffusion.jl b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_rainy_bubble_diffusion.jl new file mode 100644 index 00000000..aefe38c6 --- /dev/null +++ b/examples/moist_bubble/rainy_bubble_elixirs/elixir_rainy_euler_rainy_bubble_diffusion.jl @@ -0,0 +1,299 @@ +using OrdinaryDiffEqSSPRK +using Trixi +using TrixiAtmo +using TrixiAtmo: source_terms_rainy, saturation_residual, + saturation_residual_jacobian, NonlinearSolveDG, + cons2eq_pot_temp, saturation_vapour_pressure, + flux_chandrashekar, flux_LMARS, + source_terms_no_phase_change, + boundary_condition_laplace, + boundary_condition_simple_slip_wall, + flux_ec_rain +using NLsolve: nlsolve + +# domain +coordinates_min = (0.0, 0.0) +coordinates_max = (2400.0, 2400.0) + +# hydrostatic dry potential temperature +function theta_d(z, equations::CompressibleRainyEulerEquations2D) + # constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + ref_pressure = equations.ref_pressure + + # problem specific constants + surface_temperature = 283.0 + surface_pressure = 8.5e4 + stratification = 1.3e-5 + + # dry potential temperature at surface + Theta0 = surface_temperature * (ref_pressure / surface_pressure)^(R_d / c_pd) + # at height z + theta_d = Theta0 * exp(stratification * z) + + return theta_d +end + +# hydrostatic base state residual +function generate_hydrostatic_residual(pressure_lower, humidity_rel0, z, dz, + equations::CompressibleRainyEulerEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + g = equations.gravity + + function hydrostatic_residual!(residual, guess) + # variables + pressure, rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + + # pressure derivative residual approximation + residual[1] = (pressure - pressure_lower) / dz + (rho_dry + rho_vapour) * g + + # pressure residual + residual[2] = pressure - temperature * (rho_dry * R_d + rho_vapour * R_v) + + # hydrostatic dry potential temperature residual + residual[3] = theta_d(z, equations) - + temperature * (ref_pressure / pressure)^(R_d / c_pd) + + # humidity residual + residual[4] = rho_vs * (rho_dry + rho_vapour / eps) * humidity_rel0 + residual[4] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[4] *= 1000.0 + end + + return hydrostatic_residual! +end + +function generate_perturbation_residual(pressure_hydrostatic, H_init, z, + equations::CompressibleRainyEulerEquations2D) + # equations constants + c_pd = equations.c_dry_air_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + eps = equations.eps + ref_pressure = equations.ref_pressure + + function perturbation_residual!(residual, guess) + # variables + rho_dry, rho_vapour, temperature = guess + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + pressure = (rho_dry * R_d + rho_vapour * R_v) * temperature + + # humidity residual + residual[1] = rho_vs * (rho_dry + rho_vapour / eps) * H_init + residual[1] -= rho_vapour * (rho_dry + rho_vs / eps) + residual[1] *= 30.0 + + # hydrostatic dry potential temperature residual + residual[2] = theta_d(z, equations) - + temperature * (ref_pressure / pressure_hydrostatic)^(R_d / c_pd) + + # pressure residual + residual[3] = pressure_hydrostatic - pressure + end + + return perturbation_residual! +end + +# for approximating the dz pressure gradient +struct AtmosphereLayersRainyBubble{RealT <: Real} + layer_data :: Matrix{RealT} + total_height :: RealT + precision :: RealT +end + +function AtmosphereLayersRainyBubble(equations::CompressibleRainyEulerEquations2D; + total_height = coordinates_max[2] + 1.0, + precision = 1.0, RealT = Float64) + # constants + humidity_rel0 = 0.2 # hydrostatic relative humidity + surface_pressure = 8.5e4 + + # surface layer with initial guesses for rho_dry, rho_vapour and temperature + surface_layer = [surface_pressure, 1.4, 0.04, 300.0] + + # allocate layer_data + n = convert(Int, total_height / precision) + layer_data = zeros(RealT, n + 1, 4) + + # solve (slightly above) surface layer + dz = 0.01 + z = 0.01 + residual_function! = generate_hydrostatic_residual(surface_pressure, humidity_rel0, z, + dz, equations) + layer_data[1, :] .= nlsolve(residual_function!, surface_layer).zero + + # adjust to chosen precision + dz = precision + + # iterate up the atmosphere + for i in (1:n) + z += dz + residual_function! = generate_hydrostatic_residual(layer_data[i, 1], humidity_rel0, + z, dz, equations) + guess = deepcopy(layer_data[i, :]) + layer_data[i + 1, :] .= nlsolve(residual_function!, guess, ftol = 1e-10, + iterations = 20).zero + end + + return AtmosphereLayersRainyBubble{RealT}(layer_data, total_height, precision) +end + +# create layers for initial condition +equations = CompressibleRainyEulerEquations2D() +layers = AtmosphereLayersRainyBubble(equations) + +function initial_condition_bubble_rainy(x, t, equations::CompressibleRainyEulerEquations2D; + atmosphere_layers = layers) + # equations constants + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + ref_L = equations.ref_latent_heat_vap_temp + + # problem specific constants + humidity_rel_bar = 0.2 # background relative humidity field + humidity_max = 1.0 + + # bubble parameters + radius_outer, radius_inner = 300.0, 200.0 # radii of humidity bubble + x_center, z_center = 1200.0, 800.0 # center of humidity bubble + + # radius relative to bubble center + r = sqrt((x[1] - x_center)^2 + (x[2] - z_center)^2) + + # humidity definition + if (r > radius_outer) + # outside the bubble + humidity = humidity_rel_bar + elseif (r > radius_inner) + # outer layers of the bubble + humidity = humidity_rel_bar + + (humidity_max - humidity_rel_bar) * + cos(pi * (r - radius_inner) / (2.0 * (radius_outer - radius_inner)))^2 + else + # inner layer + humidity = humidity_max + end + + # get atmosphere layer and height information + @unpack layer_data, total_height, precision = atmosphere_layers + dz = precision + z = x[2] + n = convert(Int, floor((z + eps()) / dz)) + 1 + z_lower = (n - 1) * dz + z_upper = n * dz + + if (z_lower == total_height) + z_upper = z_lower + dz + n = n - 1 + end + + if (n == 0) + n = 1 + end + + # check height consistency + if (z > total_height && !(isapprox(z, total_height))) + error("The atmosphere does not match the simulation domain") + end + + # get hydrostatic pressures and approximate between lower and upper data point + pressure_hydrostatic_lower = layer_data[n, 1] + pressure_hydrostatic_upper = layer_data[n + 1, 1] + pressure_hydrostatic = (pressure_hydrostatic_upper * (z - z_lower) + + pressure_hydrostatic_lower * (z_upper - z)) / dz + + # solve perturbation + residual_function! = generate_perturbation_residual(pressure_hydrostatic, humidity, z, + equations) + rho_dry, rho_vapour, temperature = nlsolve(residual_function!, layer_data[n, 2:4], + ftol = 1e-9, iterations = 20).zero + + energy_density = (c_vd * rho_dry + c_vv * rho_vapour) * temperature + rho_vapour * ref_L + + return SVector(rho_dry, rho_vapour, 0.0, 0.0, 0.0, energy_density, rho_vapour, 0.0, + temperature) +end + +############################################################################### +# semidiscretization of the compressible rainy Euler equations + +diffusivity = 0.5 +equations_parabolic = LaplaceDiffusion2D(diffusivity, equations) + +boundary_conditions = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_simple_slip_wall, + y_pos = boundary_condition_simple_slip_wall) + +boundary_conditions_parabolic = (x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_laplace, + y_pos = boundary_condition_laplace) + +polydeg = 3 +basis = LobattoLegendreBasis(polydeg) + +surface_flux = flux_lax_friedrichs +volume_integral = VolumeIntegralFluxDifferencing(flux_ec_rain) + +solver = DGSEM(basis, surface_flux, volume_integral) + +initial_condition = initial_condition_bubble_rainy +source_terms = source_terms_rainy + +initial_refinement_level = 6 +mesh = TreeMesh(coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + periodicity = (true, false), n_cells_max = 1_000_000) + +semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), + initial_condition, solver; source_terms, + boundary_conditions = (boundary_conditions, + boundary_conditions_parabolic)) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 600.0) + +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 1000 + +# entropy +analysis_callback = AnalysisCallback(semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,)) + +alive_callback = AliveCallback(analysis_interval = analysis_interval) + +save_solution = SaveSolutionCallback(interval = 1000, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2eq_pot_temp) + +callbacks = CallbackSet(summary_callback, + analysis_callback, + alive_callback, + save_solution) + +stage_limiter! = NonlinearSolveDG(saturation_residual, saturation_residual_jacobian, + SVector(7, 8, 9), 1e-9) + +############################################################################### +# run the simulation +sol = solve(ode, SSPRK43(stage_limiter!); ode_default_options()..., + maxiters = 1.0e7, save_everystep = false, callback = callbacks); + +summary_callback() diff --git a/src/TrixiAtmo.jl b/src/TrixiAtmo.jl index 84743594..ca9b4b2e 100644 --- a/src/TrixiAtmo.jl +++ b/src/TrixiAtmo.jl @@ -26,6 +26,7 @@ using HDF5: HDF5, h5open, attributes, create_dataset, datatype, dataspace include("auxiliary/auxiliary.jl") include("equations/equations.jl") +include("callbacks_stage/callbacks_stage.jl") include("meshes/meshes.jl") include("semidiscretization/semidiscretization.jl") include("solvers/solvers.jl") @@ -34,10 +35,14 @@ include("callbacks_step/callbacks_step.jl") export CompressibleMoistEulerEquations2D, ShallowWaterEquations3D, CovariantLinearAdvectionEquation2D, CovariantShallowWaterEquations2D, - SplitCovariantShallowWaterEquations2D + SplitCovariantShallowWaterEquations2D, CompressibleRainyEulerEquations2D, + CompressibleMoistEulerPotentialTemperatureEquations2D, + CompressibleRainyEulerExplicitEquations2D export GlobalCartesianCoordinates, GlobalSphericalCoordinates +export NonlinearSolveDG + export flux_chandrashekar, FluxLMARS export flux_nonconservative_zeros, flux_nonconservative_ec, diff --git a/src/callbacks_stage/callbacks_stage.jl b/src/callbacks_stage/callbacks_stage.jl new file mode 100644 index 00000000..b7cfd68f --- /dev/null +++ b/src/callbacks_stage/callbacks_stage.jl @@ -0,0 +1,4 @@ +@muladd begin + include("nonlinear_solve_dg.jl") + include("rain_limiter_dg.jl") +end diff --git a/src/callbacks_stage/nonlinear_solve_dg.jl b/src/callbacks_stage/nonlinear_solve_dg.jl new file mode 100644 index 00000000..2fe7b01e --- /dev/null +++ b/src/callbacks_stage/nonlinear_solve_dg.jl @@ -0,0 +1,24 @@ +using Trixi: wrap_array, AbstractSemidiscretization, TimerOutputs, @trixi_timeit, timer + +@muladd begin + struct NonlinearSolveDG + residual :: Function + jacobian :: Function + variables_index_vector :: Vector{Int} + tolerance :: Real + end + + function (limiter!::NonlinearSolveDG)(u_ode, integrator, + semi::AbstractSemidiscretization, t) + u = wrap_array(u_ode, semi) + + @trixi_timeit timer() "nonlinear system solver" begin + nonlinear_solve_dg_2d!(u, limiter!.residual, limiter!.jacobian, + limiter!.variables_index_vector, + limiter!.tolerance, semi.equations, semi.solver, + semi.cache, semi.mesh) + end + end + + include("nonlinear_solve_dg2d.jl") +end diff --git a/src/callbacks_stage/nonlinear_solve_dg2d.jl b/src/callbacks_stage/nonlinear_solve_dg2d.jl new file mode 100644 index 00000000..7140454c --- /dev/null +++ b/src/callbacks_stage/nonlinear_solve_dg2d.jl @@ -0,0 +1,89 @@ +using Trixi: get_node_vars, @batch, get_inverse_jacobian, set_node_vars!, get_node_coords, + each_quad_node +using LinearAlgebra +using StaticArrays + +@muladd begin + function nonlinear_solve_dg_2d!(u, residual, jacobian, variables_index_vector, + tolerance, + equations::AbstractCompressibleRainyEulerEquations, + dg::DGSEM, cache, mesh) + max_iterations = 20 + + # iterate over every DGSEM element + @batch for element in eachelement(dg, cache) + # iterate over every node + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + guess = SVector(u_node[7], u_node[8], u_node[9]) + + # keep rain positive + if (u_node[3] < 0.0) + u[3, i, j, element] = 0.0 + end + + # newton method + for iteration in range(1, max_iterations) + res_vector = residual(u_node, guess, equations) + + if (maximum(abs.(res_vector)) < tolerance) + break + end + + jac_matrix = jacobian(u_node, guess, equations) + guess += -jac_matrix \ res_vector + + #= warnings seem to have allocations... + if iteration == max_iterations + @warn "newton method: tolerance not met" + end + =# + end + + # similar to set_node_vars! + for index in eachindex(variables_index_vector) + u[variables_index_vector[index], i, j, element] = guess[index] + end + end + end + end + + function nonlinear_solve_dg_2d!(u, residual, jacobian, variables_index_vector, + tolerance, + equations::AbstractCompressibleRainyEulerEquations, + dg::DGMulti, cache, mesh::DGMultiMesh) + max_iterations = 20 + + # iterate over every node + @batch for element in eachelement(mesh, dg) + for j in each_quad_node(mesh, dg) + u_node = u[j, element] + + # keep rain positive + if (u_node[3] < 0.0) + u_node = SVector(u_node[1], u_node[2], 0.0, u_node[4], u_node[5], + u_node[6], + u_node[7], u_node[8], u_node[9]) + end + + guess = SVector(u_node[7], u_node[8], u_node[9]) + + # newton method + for iteration in range(1, max_iterations) + res_vector = residual(u_node, guess, equations) + + if (maximum(abs.(res_vector)) < tolerance) + break + end + + jac_matrix = jacobian(u_node, guess, equations) + guess += -jac_matrix \ res_vector + end + + u[j, element] = SVector(u_node[1], u_node[2], u_node[3], u_node[4], + u_node[5], u_node[6], + guess[1], guess[2], guess[3]) + end + end + end +end # muladd end diff --git a/src/callbacks_stage/rain_limiter_dg.jl b/src/callbacks_stage/rain_limiter_dg.jl new file mode 100644 index 00000000..409b0d46 --- /dev/null +++ b/src/callbacks_stage/rain_limiter_dg.jl @@ -0,0 +1,17 @@ +using Trixi: wrap_array, TimerOutputs, @trixi_timeit, timer + +@muladd begin + struct RainLimiterDG + end + + function (limiter!::RainLimiterDG)(u_ode, integrator, semi::AbstractSemidiscretization, + t) + u = wrap_array(u_ode, semi) + + @trixi_timeit timer() "rain limiter" begin + rain_limiter_dg2d!(u, semi.equations, semi.solver, semi.cache, semi.mesh) + end + end + + include("rain_limiter_dg2d.jl") +end diff --git a/src/callbacks_stage/rain_limiter_dg2d.jl b/src/callbacks_stage/rain_limiter_dg2d.jl new file mode 100644 index 00000000..097ab1e1 --- /dev/null +++ b/src/callbacks_stage/rain_limiter_dg2d.jl @@ -0,0 +1,27 @@ +using Trixi: get_node_vars, @batch, set_node_vars!, get_node_coords, each_quad_node +using LinearAlgebra +using StaticArrays + +@muladd begin + function rain_limiter_dg2d!(u, equations::AbstractCompressibleRainyEulerEquations, + dg::DGSEM, cache, mesh) + + # iterate over every DGSEM element + @batch for element in eachelement(dg, cache) + # iterate over every node + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # keep rho_rain positive + if (u_node[4] < 0.0) + u[4, i, j, element] = 0.0 + end + + # keep rho_cloud positive + if (u_node[3] < 0.0) + u[3, i, j, element] = 0.0 + end + end + end + end +end #muladd end diff --git a/src/equations/compressible_moist_euler_potential_temperature_2d.jl b/src/equations/compressible_moist_euler_potential_temperature_2d.jl new file mode 100644 index 00000000..09d8ea34 --- /dev/null +++ b/src/equations/compressible_moist_euler_potential_temperature_2d.jl @@ -0,0 +1,544 @@ +# Implemented by Lucas Gemein +# https://github.com/NichtLucas/Trixi.jl/tree/thesis_gemein_2022 + +using Trixi +using Trixi: ln_mean, inv_ln_mean +import Trixi: varnames, flux_chandrashekar, boundary_condition_slip_wall, + cons2prim, cons2entropy, max_abs_speed_naive, max_abs_speeds, + entropy, energy_total, flux, stolarsky_mean + +@muladd begin +#! format: noindent +struct CompressibleMoistEulerPotentialTemperatureEquations2D{RealT <: Real} <: + AbstractCompressibleMoistEulerEquations{2, 6} + p_0::RealT # constant reference pressure 1000 hPa(100000 Pa) + c_pd::RealT # dry air constant + c_vd::RealT # dry air constant + R_d::RealT # dry air gas constant + c_pv::RealT # moist air constant + c_vv::RealT # moist air constant + R_v::RealT # moist air gas constant + c_pl::RealT # liqid water constant + g::RealT # gravitation constant + inv_gamma_minus_one::RealT # ratio of the gas constant R_d + gamma::RealT # = inv(kappa- 1); can be used to write slow divisions as fast multiplications + L_00::RealT # latent heat of evaporation at 0 K + K::RealT +end + +function CompressibleMoistEulerPotentialTemperatureEquations2D(; g = 9.81, + RealT = Float64) + p_0 = 100000.0 + c_pd = 1004.0 + c_vd = 717.0 + R_d = c_pd - c_vd + c_pv = 1885.0 + c_vv = 1424.0 + R_v = c_pv - c_vv + c_pl = 4186.0 + gamma = c_pd / c_vd # = 1/(1 - kappa) + inv_gamma_minus_one = inv(1 - gamma) + K = L_00 = 2.5e6 #+ (c_pl - c_pv)*273.15 + K = p_0 * (R_d / p_0)^gamma + return CompressibleMoistEulerPotentialTemperatureEquations2D{RealT}(p_0, c_pd, c_vd, + R_d, c_pv, c_vv, + R_v, c_pl, g, + inv_gamma_minus_one, + gamma, L_00, K) +end + +@inline function pressure(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack p_0, R_d, R_v, c_pd, c_pv, c_pl, c_vd, c_vv = equations + rho, rho_v1, rho_v2, rho_theta, rho_v, rho_l = u + + rho_d = rho - (rho_v + rho_l) + + kappa_M = (R_d * rho_d + R_v * rho_v) / (c_pd * rho_d + c_pv * rho_v + c_pl * rho_l) + p = p_0 * (R_d * rho_theta / p_0)^(1 / (1 - kappa_M)) + + return p +end + +# Calculate 1D flux for a single point. +@inline function flux(u, orientation::Integer, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_qc = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + qv = rho_qv / rho + qc = rho_qc / rho + p = pressure(u, equations) + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = rho_v1 * v2 + f4 = rho_theta * v1 + f5 = rho_v1 * qv + f6 = rho_v1 * qc + else + f1 = rho_v2 + f2 = rho_v2 * v1 + f3 = rho_v2 * v2 + p + f4 = rho_theta * v2 + f5 = rho_v2 * qv + f6 = rho_v2 * qc + end + return SVector(f1, f2, f3, f4, f5, f6) +end + +# Calculate 1D flux for a single point in the normal direction. +# Note, this directional vector is not normalized. +@inline function flux(u, normal_direction::AbstractVector, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_qc = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + qv = rho_qv / rho + qc = rho_qc / rho + p = pressure(u, equations) + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + rho_v_normal = rho * v_normal + f1 = rho_v_normal + f2 = (rho_v_normal) * v1 + p * normal_direction[1] + f3 = (rho_v_normal) * v2 + p * normal_direction[2] + f4 = (rho_theta) * v_normal + f5 = rho_v_normal * qv + f6 = (rho_v_normal) * qc + return SVector(f1, f2, f3, f4, f5, f6) +end + +# Slip-wall boundary condition +# Determine the boundary numerical surface flux for a slip wall condition. +# Imposes a zero normal velocity at the wall. +@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack c_pd, c_pv, c_pl, c_vd, c_vv = equations + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal = normal_direction / norm_ + + # rotate the internal solution state + u_local = rotate_to_x(u_inner, normal, equations) + + # compute the primitive variables + rho_local, v_normal, v_tangent, p_local, qv_local, ql_local = cons2prim(u_local, + equations) + qd_local = 1 - qv_local - ql_local + gamma = (qd_local * c_pd + qv_local * c_pv + ql_local * c_pl) * + inv(qd_local * c_vd + qv_local * c_vv + ql_local * c_pl) + # Get the solution of the pressure Riemann problem + # See Section 6.3.3 of + # Eleuterio F. Toro (2009) + # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + if v_normal <= 0.0 + sound_speed = sqrt(gamma * p_local / rho_local) # local sound speed + p_star = p_local * + (1.0 + 0.5 * (gamma - 1) * v_normal / sound_speed)^(2.0 * gamma * + inv(gamma - 1)) + else # v_normal > 0.0 + A = 2.0 / ((gamma + 1) * rho_local) + B = p_local * (gamma - 1) / (gamma + 1) + p_star = p_local + + 0.5 * v_normal / A * + (v_normal + sqrt(v_normal^2 + 4.0 * A * (p_local + B))) + end + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector(zero(eltype(u_inner)), + p_star * normal[1], + p_star * normal[2], + zero(eltype(u_inner)), + zero(eltype(u_inner)), + zero(eltype(u_inner))) * norm_ +end + +# Fix sign for structured mesh. +@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + direction, x, t, + surface_flux_function, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back + # to be inward pointing on the -x and -y sides due to the orientation convention used by StructuredMesh + if isodd(direction) + boundary_flux = -boundary_condition_slip_wall(u_inner, -normal_direction, + x, t, surface_flux_function, + equations) + else + boundary_flux = boundary_condition_slip_wall(u_inner, normal_direction, + x, t, surface_flux_function, + equations) + end + + return boundary_flux +end + +# Rotate momentum flux. The same as in compressible Euler. +@inline function rotate_to_x(u, normal_vector::AbstractVector, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D rotation matrix with normal and tangent directions of the form + # [ 1 0 0 0; + # 0 n_1 n_2 0; + # 0 t_1 t_2 0; + # 0 0 0 1 ] + # where t_1 = -n_2 and t_2 = n_1 + + return SVector(u[1], + c * u[2] + s * u[3], + -s * u[2] + c * u[3], + u[4], u[5], u[6]) +end + +# Recreates the convergence test initial condition from compressible euler 2D. +function initial_condition_warm_bubble(x, t, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack R_d, R_v, c_vd, c_vv, c_pl, L_00 = equations + xc = 0 + zc = 2000 + r = sqrt((x[1] - xc)^2 + (x[2] - zc)^2) + rc = 2000 + theta_ref = 300 + qv_ref = 0 + qc_ref = 0 + Δtheta = 0 + Δqv = 0 + Δqc = 0 + + if r <= rc + Δtheta = 2 * cospi(0.5 * r / rc)^2 + end + + #Perturbed state: + theta = theta_ref + Δtheta # potential temperature + π_exner = 1 - equation._grav / (equation.c_pd * theta) * x[2] # exner pressure + rho = equation.p_0 / (equation.R_d * theta) * + (π_exner)^(equation.c_vd / equation.R_d) # density + + v1 = 20 + v2 = 0 + qv = 0 + qc = 0 + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_theta = rho * theta + rho_qv = rho * qv + rho_qc = rho * qc + + return SVector(rho, rho_v1, rho_v2, rho_e, rho_qv, rho_qc) +end + +# Gravity source term +@inline function source_terms_gravity(u, x, t, + equations::CompressibleMoistEulerEquations2D) + @unpack g = equations + rho, _, _, _, _, _ = u + + return SVector(zero(eltype(u)), zero(eltype(u)), + -g * rho, + zero(eltype(u)), zero(eltype(u)), zero(eltype(u))) +end + +# Rayleigh damping sponge source term form A. Sridhar et al., +# Large-eddy simulations with ClimateMachine: a new open-sourcecode for +# atmospheric simulations on GPUs and CPUs, 2 Oct 2021, doi: 10.5194/gmd-15-6259-2022, +# https://arxiv.org/abs/2110.00853 [physics.ao-ph] . +@inline function source_terms_nonhydrostatic_rayleigh_sponge(u, x, t, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_ql = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + z = x[2] + + # relaxed background velocity + vr1, vr2 = (10.0, 0.0) + # damping threshold + z_s = 9000.0 + # boundary top + z_top = 16000.0 + # positive even power with default value 2 + gamma = 2.0 + # relaxation coefficient > 0 + alpha = 0.5 + + tau_s = zero(eltype(u)) + if z > z_s + tau_s = alpha * sin(0.5 * (z - z_s) * inv(z_top - z_s))^(gamma) + end + + return SVector(zero(eltype(u)), + -tau_s * rho * (v1 - vr1), + -tau_s * rho * (v2 - vr2), + zero(eltype(u)), zero(eltype(u)), zero(eltype(u))) +end + +function source_terms_moist_bubble(u, x, t, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_qc = u + RelCloud = 1 + rho_d = rho - rho_qv - rho_qc + c_pml = equations.c_pd * rho_d + equations.c_pv * rho_qv + equations.c_pl * rho_qc + c_vml = equations.c_vd * rho_d + equations.c_vv * rho_qv + equations.c_pl * rho_qc + R_m = equations.R_d * rho_d + equations.R_v * rho_qv + kappa_M = R_m / c_pml + p = pressure(u, equations) + T = p / R_m + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + a = p_vs / (equations.R_v * T) - rho_qv + b = rho_qc + rho_q_cond = RelCloud * (a + b - sqrt(a * a + b * b)) + L = equations.L_00 - (equations.c_pl - equations.c_pv) * T + du4 = rho_theta * ((-L / (c_pml * T) - + log(p / equations.p_0) * kappa_M * + (equations.R_v / R_m - equations.c_pv / c_pml) + + equations.R_v / R_m) * rho_q_cond + + (log(p / equations.p_0) * kappa_M * (equations.c_pl / c_pml)) * + (-rho_q_cond)) + + du5 = rho_q_cond + du6 = -rho_q_cond + + return SVector(zero(eltype(u)), zero(eltype(u)), -equations.g * rho, du4, du5, du6) +end + +# Low Mach number approximate Riemann solver (LMARS) from +# X. Chen, N. Andronova, B. Van Leer, J. E. Penner, J. P. Boyd, C. Jablonowski, S. +# Lin, A Control-Volume Model of the Compressible Euler Equations with a Vertical Lagrangian +# Coordinate Monthly Weather Review Vol. 141.7, pages 2526–2544, 2013, +# https://journals.ametsoc.org/view/journals/mwre/141/7/mwr-d-12-00129.1.xml. +@inline function flux_LMARS(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + a = 360.0 + # Unpack left and right state + rho_ll, rho_v1_ll, rho_v2_ll, rho_theta_ll, rho_qv_ll, rho_ql_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_theta_rr, rho_qv_rr, rho_ql_rr = u_rr + p_ll = pressure(u_ll, equations) + p_rr = pressure(u_rr, equations) + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + + v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # diffusion parameter <= 1 + beta = 1 + + # Compute the necessary interface flux components + norm_ = norm(normal_direction) + + rho = 0.5 * (rho_ll + rho_rr) + p_interface = 0.5 * (p_ll + p_rr) - beta * 0.5 * a * rho * (v_rr - v_ll) / norm_ + v_interface = 0.5 * (v_ll + v_rr) - beta * 1 / (2 * a * rho) * (p_rr - p_ll) * norm_ + + if (v_interface > 0) + f1, f2, f3, f4, f5, f6 = u_ll * v_interface + else + f1, f2, f3, f4, f5, f6 = u_rr * v_interface + end + + return SVector(f1, + f2 + p_interface * normal_direction[1], + f3 + p_interface * normal_direction[2], + f4, f5, f6) +end + +# Convert conservative variables to primitive. +@inline function cons2prim(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_ql = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = pressure(u, equations) + qv = rho_qv / rho + ql = rho_ql / rho + + return SVector(rho, v1, v2, p, qv, ql) +end + +# Convert conservative variables to entropy +@inline function cons2entropy(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack R_d, R_v, c_pd, c_pv, c_pl, L_00 = equations + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_qc = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + p = pressure(u, equations) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) / (equations.gamma - 1) - 0.5 * rho_p * v_square + w2 = rho_p * v1 + w3 = rho_p * v2 + w4 = -rho_p + w5 = rho_p * rho_qv / rho + w6 = rho_p * rho_qc / rho + + return SVector(w1, w2, w3, w4, w5, w6) +end + +# Convert primitive to conservative variables. +@inline function prim2cons(prim, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, v1, v2, p, qv, ql = prim + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_qv = rho * qv + rho_ql = rho * ql + rho_theta = (p / equations.K)^(1 / equations.gamma) # TODO + return SVector(rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_ql) +end + +@inline function density(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho = u[1] + return rho +end + +@inline function density_dry(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho_qd = u[1] - (u[5] + u[6]) + return rho_qd +end + +@inline function density_vapor(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho_qv = u[5] + return rho_qv +end + +# Calculate kinetic energy for a conservative state `cons`. +@inline function energy_kinetic(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_ql = u + return (rho_v1^2 + rho_v2^2) / (2 * rho) +end + +@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack c_pd, c_pv, c_pl, c_vd, c_vv = equations + rho_ll, v1_ll, v2_ll, p_ll, qv_ll, ql_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr, qv_rr, ql_rr = cons2prim(u_rr, equations) + qd_ll = 1 - qv_ll - ql_ll + qd_rr = 1 - qv_rr - ql_rr + # Get the density and gas gamma + gamma_ll = (qd_ll * c_pd + qv_ll * c_pv + ql_ll * c_pl) * + inv(qd_ll * c_vd + qv_ll * c_vv + ql_ll * c_pl) + gamma_rr = (qd_rr * c_pd + qv_rr * c_pv + ql_rr * c_pl) * + inv(qd_rr * c_vd + qv_rr * c_vv + ql_rr * c_pl) + + # Compute the sound speeds on the left and right + v_mag_ll = sqrt(v1_ll^2 + v2_ll^2) + c_ll = sqrt(gamma_ll * p_ll / rho_ll) + v_mag_rr = sqrt(v1_rr^2 + v2_rr^2) + c_rr = sqrt(gamma_rr * p_rr / rho_rr) + + return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) +end + +# Adjusted version of LLF dissipation from compressible euler. +@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack c_pd, c_pv, c_pl, c_vd, c_vv = equations + rho_ll, v1_ll, v2_ll, p_ll, qv_ll, ql_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr, qv_rr, ql_rr = cons2prim(u_rr, equations) + qd_ll = 1 - qv_ll - ql_ll + qd_rr = 1 - qv_rr - ql_rr + # Get the density and gas gamma + gamma_ll = (qd_ll * c_pd + qv_ll * c_pv + ql_ll * c_pl) * + inv(qd_ll * c_vd + qv_ll * c_vv + ql_ll * c_pl) + gamma_rr = (qd_rr * c_pd + qv_rr * c_pv + ql_rr * c_pl) * + inv(qd_rr * c_vd + qv_rr * c_vv + ql_rr * c_pl) + # Calculate normal velocities and sound speed + # left + v_ll = (v1_ll * normal_direction[1] + + + v2_ll * normal_direction[2]) + c_ll = sqrt(gamma_ll * p_ll / rho_ll) + # right + v_rr = (v1_rr * normal_direction[1] + + + v2_rr * normal_direction[2]) + c_rr = sqrt(gamma_rr * p_rr / rho_rr) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) +end + +# Adjusted version of lambda_max from compressible euler. +@inline function max_abs_speeds(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack c_pd, c_pv, c_pl, c_vd, c_vv = equations + rho, v1, v2, p, qv, ql = cons2prim(u, equations) + qd = 1 - qv - ql + + gamma = (qd * c_pd + qv * c_pv + ql * c_pl) * inv(qd * c_vd + qv * c_vv + ql * c_pl) + c = sqrt(gamma * p / rho) + + return (abs(v1) + c, abs(v2) + c) +end + +@inline function cons2aeqpot(u, + equations::CompressibleMoistEulerPotentialTemperatureEquations2D) + @unpack c_pd, c_pv, c_pl, R_d, R_v, p_0, L_00 = equations + rho, rho_v1, rho_v2, rho_theta, rho_qv, rho_ql = u + rho_d = rho - rho_qv - rho_ql + p = pressure(u, equations) + T = p / (equations.R_d * rho_d + equations.R_v * rho_qv) + p_v = rho_qv * R_v * T + p_d = p - p_v + T_C = T - 273.15 + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + H = p_v / p_vs + r_v = rho_qv / rho_d + r_l = rho_ql / rho_d + r_t = r_v + r_l + L_v = L_00 + (c_pv - c_pl) * T + c_p = c_pd + r_t * c_pl + + # equivalent potential temperature + aeq_pot = (T * (p_0 / p_d)^(R_d / c_p) * H^(-r_v * R_v / c_p) * + exp(L_v * r_v * inv(c_p * T))) + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + + pot1 = rho + pot2 = v1 + pot3 = v2 + pot4 = aeq_pot + pot5 = r_v + pot6 = r_t + return SVector(pot1, pot2, pot3, pot4, pot5, pot6) +end + +varnames(::typeof(cons2aeqpot), ::CompressibleMoistEulerPotentialTemperatureEquations2D) = ("rho", + "v1", + "v2", + "aeqpottemp", + "rv", + "rt") + +varnames(::typeof(cons2cons), ::CompressibleMoistEulerPotentialTemperatureEquations2D) = ("rho", + "rho_v1", + "rho_v2", + "rho_theta", + "rho_qv", + "rho_ql") +varnames(::typeof(cons2prim), ::CompressibleMoistEulerPotentialTemperatureEquations2D) = ("rho", + "v1", + "v2", + "p", + "qv", + "ql") +end # @muladd diff --git a/src/equations/compressible_rainy_euler_2d.jl b/src/equations/compressible_rainy_euler_2d.jl new file mode 100644 index 00000000..e085a58d --- /dev/null +++ b/src/equations/compressible_rainy_euler_2d.jl @@ -0,0 +1,1103 @@ +using Trixi +using NLsolve: nlsolve +import Trixi: varnames, + cons2prim, cons2entropy, + flux, flux_chandrashekar, + max_abs_speeds, max_abs_speed_naive, + boundary_condition_slip_wall, + LaplaceDiffusion2D + +### Implementation similar to: +# Sabine Doppler, Philip L. Lederer, Joachim Schöberl, Henry von Wahl, +# A discontinuous Galerkin approach for atmospheric flows with implicit condensation, +# Journal of Computational Physics, +# Volume 499, +# 2024, +# 112713, +# ISSN 0021-9991 + +@muladd begin + + ### equation, parameters and constants ### + + struct CompressibleRainyEulerEquations2D{RealT <: Real} <: + AbstractCompressibleRainyEulerEquations{2, 9} + # Specific heat capacities: + c_liquid_water :: RealT + c_dry_air_const_pressure :: RealT + c_dry_air_const_volume :: RealT + c_vapour_const_pressure :: RealT + c_vapour_const_volume :: RealT + + # Gas constants: + R_dry_air :: RealT + R_vapour :: RealT + eps :: RealT + + # Reference values: + ref_saturation_pressure :: RealT + ref_temperature :: RealT + ref_latent_heat_vap_temp :: RealT + ref_pressure :: RealT + + # Other: + gravity :: RealT + rain_water_distr :: RealT + v_mean_rain :: RealT + end + + function CompressibleRainyEulerEquations2D(; RealT = Float64) + # Specific heat capacities: + c_liquid_water = 4186.0 + c_dry_air_const_pressure = 1004.0 + c_dry_air_const_volume = 717.0 + c_vapour_const_pressure = 1885.0 + c_vapour_const_volume = 1424.0 + + # Gas constants: + R_dry_air = c_dry_air_const_pressure - c_dry_air_const_volume + R_vapour = c_vapour_const_pressure - c_vapour_const_volume + eps = R_dry_air / R_vapour + + # Reference values: + ref_saturation_pressure = 610.7 # This needs to be adjusted if ref_temperature is changed! + ref_temperature = 273.15 + ref_latent_heat_vap_temp = 2.5e6#3147620.0 + ref_pressure = 1e5 + + # Other: + gravity = 9.81 + rain_water_distr = 8e6 + v_mean_rain = 130.0 + + return CompressibleRainyEulerEquations2D{RealT}(c_liquid_water, + c_dry_air_const_pressure, + c_dry_air_const_volume, + c_vapour_const_pressure, + c_vapour_const_volume, R_dry_air, + R_vapour, eps, + ref_saturation_pressure, + ref_temperature, + ref_latent_heat_vap_temp, + ref_pressure, gravity, + rain_water_distr, v_mean_rain) + end + + ### conversion ### + + @inline function cons2prim(u, equations::CompressibleRainyEulerEquations2D) + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # energy density + energy = energy_density(u, equations) + + # nonlinear system + rho_vapour, rho_cloud, temperature = cons2nonlinearsystemsol(u, equations) + + return SVector(rho_dry, rho_moist, rho_rain, v1, v2, energy, rho_vapour, rho_cloud, + temperature) + end + + # converts consverved to entropy variables + @inline function cons2entropy(u, equations::CompressibleRainyEulerEquations2D) + # constants + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + c_l = equations.c_liquid_water + R_d = equations.R_dry_air + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # nonlinear system + rho_vapour, rho_cloud, temperature = cons2nonlinearsystemsol(u, equations) + ln_temperature = log(temperature) + inv_temperature = inv(temperature) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + v_squared_temp = 0.5 * (v1^2 + v2^2) * inv_temperature + + # check for zero density + rho_vapour_log = 0.0 + + if (rho_vapour > 0.0) + rho_vapour_log = log(rho_vapour) + end + + omega_dry = c_vd * ln_temperature - R_d * log(rho_dry) + v_squared_temp - c_vd - R_d + omega_vapour = c_vv * ln_temperature - R_v * rho_vapour_log + v_squared_temp - + c_vv - R_v - L_ref * inv_temperature + omega_liquid = c_l * ln_temperature + v_squared_temp - c_l + omega_momentum_1 = -v1 * inv_temperature + omega_momentum_2 = -v2 * inv_temperature + omega_energy = inv_temperature + + return SVector(omega_dry, omega_vapour + omega_liquid, omega_liquid, + omega_momentum_1, omega_momentum_2, omega_energy, 0.0, 0.0, 0.0) + end + + # adapted from compressible_moist_euler_2d.jl + @inline function cons2eq_pot_temp(u, equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_pd = equations.c_dry_air_const_pressure + c_pv = equations.c_vapour_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_p = equations.ref_pressure + ref_temp = equations.ref_temperature + ref_L = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # energy density + energy = energy_density(u, equations) + + # nonlinear system + rho_vapour, rho_cloud, temperature = cons2nonlinearsystemsol(u, equations) + + # pressure + p = pressure(u, equations) + + p_v = rho_vapour * R_v * temperature + p_d = p - p_v + T_C = temperature - ref_temp + p_vs = 611.2 * exp(17.62 * T_C / (243.12 + T_C)) + H = p_v / p_vs + r_v = rho_vapour / rho_dry + r_c = rho_cloud / rho_dry + r_r = rho_rain / rho_dry + L_v = ref_L + (c_pv - c_l) * temperature + c_p = c_pd + (r_v + r_c + r_r) * c_l + + # equivalent potential temperature + eq_pot = (temperature * (ref_p / p_d)^(R_d / c_p) * H^(-r_v * R_v / c_p) * + exp(L_v * r_v * inv(c_p * temperature))) + + return SVector(rho_dry, rho_vapour, rho_cloud, rho_rain, v1, v2, eq_pot, p) + end + + @inline function cons2nonlinearsystemsol(u, + equations::CompressibleRainyEulerEquations2D) + # constants + c_vd = equations.c_dry_air_const_volume + #ref_temp = equations.ref_temperature + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # energy density + energy = energy_density(u, equations) + + # recover temperature explicitly from energy when other variables are zero + if (rho_moist == 0.0 && rho_rain == 0.0) + # energy density definition without ref_temp for dry case + energy_kinetic = 0.5 * (v1^2 + v2^2) * rho + temperature = (energy - energy_kinetic) / (c_vd * rho) #+ ref_temp + + if (temperature < 0.0) + error("temp negative") + end + + return SVector(0.0, 0.0, temperature) + else + # experimental and overly simple positivity check + rho_vapour = u[7] + rho_cloud = u[8] + temperature = u[9] + + if (rho_vapour < 0.0 && isapprox(rho_vapour, 0.0, atol = 1e-15)) + rho_vapour = 0.0 + end + + if (rho_cloud < 0.0 && isapprox(rho_cloud, 0.0, atol = 1e-15)) + rho_cloud = 0.0 + end + + return SVector(rho_vapour, rho_cloud, temperature) + end + end + + # for convenience TODO rename + @inline function cons2speeds(u, equations::CompressibleRainyEulerEquations2D) + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # get speed of sound + v_sound = speed_of_sound(u, equations)[1] + + # get terminal velocity rain + v_r = terminal_velocity_rain(rho_moist, rho_rain, equations) + + return SVector(v1, v2, v_sound, v_r) + end + + ### varnames ### + + varnames(::typeof(cons2cons), ::CompressibleRainyEulerEquations2D) = ("rho_dry", + "rho_moist", + "rho_rain", + "rho_v1", + "rho_v2", + "energy_density", + "rho_vapour", + "rho_cloud", + "temperature") + + varnames(::typeof(cons2prim), ::CompressibleRainyEulerEquations2D) = ("rho_dry", + "rho_moist", + "rho_rain", + "v1", "v2", + "energy_density", + "rho_vapour", + "rho_cloud", + "temperature") + + varnames(::typeof(cons2eq_pot_temp), ::CompressibleRainyEulerEquations2D) = ("rho_dry", + "rho_vapour", + "rho_cloud", + "rho_rain", + "v1", "v2", + "eq_pot_temp", + "pressure") + + ### physics variables ### + + @inline function densities(u, equations::CompressibleRainyEulerEquations2D) + # densities + rho_dry = u[1] + rho_moist = u[2] + rho_rain = u[3] + rho = rho_dry + rho_moist + rho_rain + rho_inv = inv(rho) + + return SVector(rho_dry, rho_moist, rho_rain, rho, rho_inv) + end + + @inline function rain_density(u, equations::CompressibleRainyEulerEquations2D) + return u[3] + end + + @inline function velocities(u, rho_inv, equations::CompressibleRainyEulerEquations2D) + return SVector(u[4] * rho_inv, u[5] * rho_inv) + end + + @inline function energy_density(u, equations::CompressibleRainyEulerEquations2D) + return u[6] + end + + @inline function pressure(u, equations::CompressibleRainyEulerEquations2D) + # constants + R_d = equations.R_dry_air + R_v = equations.R_vapour + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + rho_vapour, _, temperature = cons2nonlinearsystemsol(u, equations) + + p = (R_d * rho_dry + R_v * rho_vapour) * temperature + + return p + end + + @inline function speed_of_sound(u, equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + + # densities + rho_dry, _, rho_rain, _, rho_inv = densities(u, equations) + + # recover rho_vapour, rho_cloud, temperature from nonlinear system + rho_vapour, rho_cloud, temperature = cons2nonlinearsystemsol(u, equations) + if (rho_vapour < 0.0) + error("rho vapour less than zero") + end + if (rho_cloud < 0.0) + error("rho cloud less than zero") + end + + # formula + p = pressure(u, equations) + q_v = rho_vapour / rho_dry + q_l = (rho_cloud + rho_rain) / rho_dry + gamma_m = 1 + (R_d + R_v * q_v) / (c_vd + c_vv * q_v + c_l * q_l) + + if (rho_inv < 0.0) + error("rho less than zero") + elseif (p < 0.0) + error("pressure less than zero") + end + + v_sound = sqrt(gamma_m * p * rho_inv) + + return SVector(v_sound, gamma_m) + end + + @inline function terminal_velocity_rain(rho_moist, rho_rain, + equations::CompressibleRainyEulerEquations2D) + # constants + N_0 = equations.rain_water_distr + v_0 = equations.v_mean_rain + + # formula ( \Gamma(4.5) / 6 ~= 1.9386213994279082 ) + if (rho_rain > 0.0) + v_terminal_rain = v_0 * 1.9386213994279082 * + (rho_rain / (pi * (rho_moist + rho_rain) * N_0))^(0.125) + else + v_terminal_rain = 0.0 + end + + return v_terminal_rain + end + + @inline function saturation_vapour_pressure(temperature, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_pv = equations.c_vapour_const_pressure + R_v = equations.R_vapour + ref_s_p = equations.ref_saturation_pressure + ref_temp = equations.ref_temperature + ref_L = equations.ref_latent_heat_vap_temp + + # testing + if (temperature < 0.0) + display(temperature) + error("temp less than zero") + end + + # Clausius Clapeyron formula + p_vapour_saturation = ref_s_p * (temperature / ref_temp)^((c_pv - c_l) / R_v) + p_vapour_saturation *= exp(((ref_L - (c_pv - c_l) * ref_temp) / R_v) * + (1 / ref_temp - 1 / temperature)) + + return p_vapour_saturation + end + + @inline function saturation_vapour_pressure_derivative(temperature, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_pv = equations.c_vapour_const_pressure + R_v = equations.R_vapour + ref_s_p = equations.ref_saturation_pressure + ref_temp = equations.ref_temperature + ref_L = equations.ref_latent_heat_vap_temp + + # testing + if (temperature < 0.0) + display(temperature) + error("temp less than zero") + end + + const_1 = (c_pv - c_l) / R_v + const_2 = (ref_L - (c_pv - c_l) * ref_temp) / R_v + + p_vapour_saturation_derivative = ref_s_p / (ref_temp^const_1) + p_vapour_saturation_derivative *= (const_1 * temperature^(const_1 - 1) + + const_2 * temperature^(const_1 - 2)) + p_vapour_saturation_derivative *= exp(const_2 * (1 / ref_temp - 1 / temperature)) + + return p_vapour_saturation_derivative + end + + ### pde discretization ### + + @inline function flux(u, orientation::Integer, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + + #densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # recover rho_vapour, rho_cloud, temperature from nonlinear system + _, _, temperature = cons2nonlinearsystemsol(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + v_r = terminal_velocity_rain(rho_moist, rho_rain, equations) + + # pressure + p = pressure(u, equations) + + # energy density + energy = energy_density(u, equations) + + # flux for orientation cases + if (orientation == 1) + # "mass" + f1 = rho_dry * v1 + f2 = rho_moist * v1 + f3 = rho_rain * v1 + + # "momentum" + f4 = rho * v1 * v1 + p + f5 = rho * v1 * v2 + + # "energy" + f6 = (energy + p) * v1 + + else + # "mass" + f1 = rho_dry * v2 + f2 = rho_moist * v2 + f3 = rho_rain * (v2 - v_r) + + # "momentum" + f4 = rho * v1 * v2 - rho_rain * v_r * v1 + f5 = rho * v2 * v2 + p - rho_rain * v_r * v2 + + # "energy" + f6 = (energy + p) * v2 - + (c_l * temperature + 0.5 * (v1^2 + v2^2)) * rho_rain * v_r + end + + return SVector(f1, f2, f3, f4, f5, f6, + 0.0, 0.0, 0.0) + end + + @inline function flux(u, normal_direction::AbstractVector, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # recover rho_vapour, rho_cloud, temperature from nonlinear system + _, _, temperature = cons2nonlinearsystemsol(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + v_r = terminal_velocity_rain(rho_moist, rho_rain, equations) + + # normal velocities + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + v_r_normal = v_r * normal_direction[2] + + # pressure + p = pressure(u, equations) + + # energy density + energy = energy_density(u, equations) + + # flux + # "mass" + f1 = rho_dry * v_normal + f2 = rho_moist * v_normal + f3 = rho_rain * (v_normal - v_r_normal) + + # "momentum" + f4 = rho * v_normal * v1 + p * normal_direction[1] - rho_rain * v_r_normal * v1 + f5 = rho * v_normal * v2 + p * normal_direction[2] - rho_rain * v_r_normal * v2 + + # "energy" + f6 = (energy + p) * v_normal - + (c_l * temperature + 0.5 * (v1^2 + v2^2)) * rho_rain * v_r_normal + + return SVector(f1, f2, f3, f4, f5, f6, + 0.0, 0.0, 0.0) + end + + # no Coriolis term + @inline function source_terms_rainy(u, x, t, + equations::CompressibleRainyEulerEquations2D) + # constants + R_v = equations.R_vapour + ref_temp = equations.ref_temperature + g = equations.gravity + + # name needed variables + rho_v2 = u[5] + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # recover rho_vapour, rho_cloud, temperature from nonlinear system + rho_vapour, rho_cloud, temperature = cons2nonlinearsystemsol(u, equations) + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + + # source terms phase change + S_evaporation = (3.86e-3 - 9.41e-5 * (temperature - ref_temp)) * + (1 + 9.1 * rho_rain^(0.1875)) + S_evaporation *= (rho_vs - rho_vapour) * rho_rain^(0.5) + S_auto_conversion = 0.001 * rho_cloud + S_accretion = 1.72 * rho_cloud * rho_rain^(0.875) + S_rain = S_auto_conversion + S_accretion - S_evaporation + + return SVector(0.0, -S_rain, S_rain, 0.0, + -rho * g, -rho_v2 * g, 0.0, 0.0, 0.0) + end + + # no phase changes and no Coriolis term + @inline function source_terms_no_phase_change(u, x, t, + equations::CompressibleRainyEulerEquations2D) + # constants + g = equations.gravity + + # name needed variables + rho_v2 = u[5] + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + return SVector(0.0, 0.0, 0.0, 0.0, + -g * rho, -g * rho_v2, 0.0, 0.0, 0.0) + end + + @inline function max_abs_speeds(u, equations::CompressibleRainyEulerEquations2D) + # name needed variables + v1, v2, v_sound, v_r = cons2speeds(u, equations) + + return SVector((abs(v1) + v_sound), (abs(v2) + v_sound + abs(v_r))) + end + + @inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerEquations2D) + # name needed variables + v1_ll, v2_ll, v_sound_ll, v_r_ll = cons2speeds(u_ll, equations) + v1_rr, v2_rr, v_sound_rr, v_r_rr = cons2speeds(u_rr, equations) + + # calculate upper bounds for left and right speed + v_ll_max = abs(v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_ll_max += abs(v_r_ll * normal_direction[2]) + + v_rr_max = abs(v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + v_rr_max += abs(v_r_rr * normal_direction[2]) + + return max(v_ll_max, v_rr_max) + + max(v_sound_ll, v_sound_rr) * norm(normal_direction) + end + + @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::CompressibleRainyEulerEquations2D) + # name needed variables + v1_ll, v2_ll, v_sound_ll, v_r_ll = cons2speeds(u_ll, equations) + v1_rr, v2_rr, v_sound_rr, v_r_rr = cons2speeds(u_rr, equations) + + if (orientation == 1) + v_ll = abs(v1_ll) + v_rr = abs(v1_rr) + else + v_ll = abs(v2_ll) + v_rr = abs(v2_rr) + end + # experimental + return max(v_ll, v_rr) + max(v_sound_ll, v_sound_rr) + max(abs(v_r_ll), abs(v_r_rr)) + end + + ### boundary conditions ### + + # adapted from compressible_moist_euler_2d.jl which was probably adapted from compressible_euler_2d.jl + @inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations::CompressibleRainyEulerEquations2D) + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal = normal_direction / norm_ + + # rotate the internal solution state + u_local = rotate_to_x(u_inner, normal, equations) + + # name needed variables + rho_v1 = u_local[4] + + # densities + rho_dry_local, rho_moist_local, rho_rain_local, rho_local, rho_inv_local = densities(u_local, + equations) + + # velocities + v_normal = rho_v1 * rho_inv_local + v_sound, gamma = speed_of_sound(u_local, equations) + + # pressure + p_local = pressure(u_local, equations) + + # Get the solution of the pressure Riemann problem + # See Section 6.3.3 of + # Eleuterio F. Toro (2009) + # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + if v_normal <= 0.0 + p_star = p_local * + (1.0 + 0.5 * (gamma - 1) * v_normal / v_sound)^(2.0 * gamma * + inv(gamma - 1)) + else # v_normal > 0.0 + A = 2.0 / ((gamma + 1) * rho_local) + B = p_local * (gamma - 1) / (gamma + 1) + p_star = p_local + + 0.5 * v_normal / A * + (v_normal + sqrt(v_normal^2 + 4.0 * A * (p_local + B))) + end + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector(0.0, 0.0, 0.0, + p_star * normal[1] * norm_, + p_star * normal[2] * norm_, + 0.0, 0.0, 0.0, 0.0) + end + + # same as in compressible_euler_2d.jl + @inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + direction, x, t, + surface_flux_function, + equations::CompressibleRainyEulerEquations2D) + # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back + # to be inward pointing on the -x and -y sides due to the orientation convention used by StructuredMesh + if isodd(direction) + boundary_flux = -boundary_condition_slip_wall(u_inner, -normal_direction, + x, t, surface_flux_function, + equations) + else + boundary_flux = boundary_condition_slip_wall(u_inner, normal_direction, + x, t, surface_flux_function, + equations) + end + + return boundary_flux + end + + # same as in compressible_euler_2d.jl + @inline function rotate_to_x(u, normal_vector, + equations::CompressibleRainyEulerEquations2D) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + return SVector(u[1], u[2], u[3], + c * u[4] + s * u[5], + -s * u[4] + c * u[5], + u[6], u[7], u[8], u[9]) + end + + # should be used together with TreeMesh (adapted from compressible_euler_2d.jl) + @inline function boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, + equations::CompressibleRainyEulerEquations2D) + # get the appropriate normal vector from the orientation + RealT = eltype(u_inner) + if orientation == 1 + normal_direction = SVector(one(RealT), zero(RealT)) + else # orientation == 2 + normal_direction = SVector(zero(RealT), one(RealT)) + end + + # compute and return the flux using `boundary_condition_slip_wall` routine above + return boundary_condition_slip_wall(u_inner, normal_direction, direction, + x, t, surface_flux_function, equations) + end + + # for parabolic terms (LaplaceDiffusion2D) + @inline function boundary_condition_laplace(flux_inner, u_inner, normal::AbstractVector, + x, t, operator_type::Trixi.Gradient, + equations_parabolic::LaplaceDiffusion2D) + return u_inner + end + + @inline function boundary_condition_laplace(flux_inner, u_inner, normal::AbstractVector, + x, t, operator_type::Trixi.Divergence, + equations_parabolic::LaplaceDiffusion2D) + return flux_inner + end + + ### Nonlinear System Residual ### + + # in preparation for a callback to solve the nonlinear system + @inline function saturation_residual(u, guess, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # energy density + energy = energy_density(u, equations) + + # define residual + residual1 = (c_vd * rho_dry + c_vv * guess[1] + c_l * (guess[2] + rho_rain)) * + guess[3] + residual1 += guess[1] * L_ref + residual1 -= (energy - rho * 0.5 * (v1^2 + v2^2)) + + residual2 = min(saturation_vapour_pressure(guess[3], equations) / (R_v * guess[3]), + rho_moist) + residual2 -= guess[1] + residual2 *= 1e7 + + residual3 = rho_moist + residual3 -= guess[1] + guess[2] + residual3 *= 1e7 + + return SVector(residual1, residual2, residual3) + end + + @inline function saturation_residual_jacobian(u, guess, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_moist, rho_rain, rho, rho_inv = densities(u, equations) + + # saturation + svp = saturation_vapour_pressure(guess[3], equations) + svp_t = saturation_vapour_pressure_derivative(guess[3], equations) + + # define jacobian + J_11 = c_vv * guess[3] + L_ref + J_12 = c_l * guess[3] + J_13 = c_vd * rho_dry + c_vv * guess[1] + c_l * (guess[2] + rho_rain) + + J_21 = -1e7 + J_22 = 0.0 + + if (svp / (R_v * guess[3]) < rho_moist) + J_23 = (svp_t * guess[3] - svp) / (R_v * guess[3]^2) * 1e7 + else + J_23 = 0.0 + end + + J_31 = -1e7 + J_32 = -1e7 + J_33 = 0.0 + + return SMatrix{3, 3}(J_11, J_21, J_31, J_12, J_22, J_32, J_13, J_23, J_33) + end + + # Low Mach number approximate Riemann solver (LMARS) from + # X. Chen, N. Andronova, B. Van Leer, J. E. Penner, J. P. Boyd, C. Jablonowski, S. + # Lin, A Control-Volume Model of the Compressible Euler Equations with a Vertical Lagrangian + # Coordinate Monthly Weather Review Vol. 141.7, pages 2526–2544, 2013, + # https://journals.ametsoc.org/view/journals/mwre/141/7/mwr-d-12-00129.1.xml. + # adapted from compressible_moist_euler_2d.jl, does NOT work with rain! + @inline function flux_LMARS(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerEquations2D) + # constants + a = 360.0 + + # densities + rho_dry_ll, rho_moist_ll, rho_rain_ll, rho_ll, rho_inv_ll = densities(u_ll, + equations) + rho_dry_rr, rho_moist_rr, rho_rain_rr, rho_rr, rho_inv_rr = densities(u_rr, + equations) + + # pressure + p_ll = pressure(u_ll, equations) + p_rr = pressure(u_rr, equations) + + # velocities + v1_ll, v2_ll = velocities(u_ll, rho_inv_ll, equations) + v1_rr, v2_rr = velocities(u_rr, rho_inv_rr, equations) + + v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + norm_ = norm(normal_direction) + + # diffusion parameter 0.0 < beta <= 1.0 + beta = 1.0 + + # interface flux components + rho = 0.5 * (rho_ll + rho_rr) + p_interface = 0.5 * (p_ll + p_rr) - beta * 0.5 * a * rho * (v_rr - v_ll) / norm_ + v_interface = 0.5 * (v_ll + v_rr) - beta * 1 / (2 * a * rho) * (p_rr - p_ll) * norm_ + + if (v_interface > 0) + f1, f2, _, f4, f5, f6, _, _, _ = u_ll * v_interface + f6 += p_ll * v_interface + else + f1, f2, _, f4, f5, f6, _, _, _ = u_rr * v_interface + f6 += p_rr * v_interface + end + + return SVector(f1, f2, 0.0, + f4 + p_interface * normal_direction[1], + f5 + p_interface * normal_direction[2], + f6, 0.0, 0.0, 0.0) + end + + # Adjusted EC flux in a normal direction with R_q=0. This is based on + # A. Gouasmi, K. Duraisamy, S. M. Murman, Formulation of Entropy-Stable schemes for the + # multicomponent compressible Euler equations, 4 Feb 2020, doi:10.1016/j.cma.2020.112912, + # https://arxiv.org/abs/1904.00972 [math.NA]. + # Adapted from compressible_moist_euler_2d.jl, careful, does NOT work for rain! + @inline function flux_chandrashekar(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + R_q = 0.0 + + # densities and temperatures + rho_dry_ll, rho_moist_ll, rho_rain_ll, rho_ll, rho_inv_ll = densities(u_ll, + equations) + rho_dry_rr, rho_moist_rr, rho_rain_rr, rho_rr, rho_inv_rr = densities(u_rr, + equations) + rho_vapour_ll, rho_cloud_ll, temperature_ll = cons2nonlinearsystemsol(u_ll, + equations) + rho_vapour_rr, rho_cloud_rr, temperature_rr = cons2nonlinearsystemsol(u_rr, + equations) + + # velocities + v1_ll, v2_ll = velocities(u_ll, rho_inv_ll, equations) + v1_rr, v2_rr = velocities(u_rr, rho_inv_rr, equations) + vr_ll = terminal_velocity_rain(rho_moist_ll, rho_rain_ll, equations) + vr_rr = terminal_velocity_rain(rho_moist_rr, rho_rain_rr, equations) + + # mean values + rho_dry_mean = 0.0 + rho_vapour_mean = 0.0 + rho_cloud_mean = 0.0 + rho_rain_mean = 0.0 + inv_temperature_mean = 0.0 + + if (!(rho_dry_ll == 0.0) && !(rho_dry_rr == 0.0)) + rho_dry_mean = ln_mean(rho_dry_ll, rho_dry_rr) + end + + if (!(rho_vapour_ll == 0.0) && !(rho_vapour_rr == 0.0)) + rho_vapour_mean = ln_mean(rho_vapour_ll, rho_vapour_rr) + end + + if (!(rho_cloud_ll == 0.0) && !(rho_cloud_rr == 0.0)) + rho_cloud_mean = ln_mean(rho_cloud_ll, rho_cloud_rr) + end + + if (!(rho_rain_ll == 0.0) && !(rho_rain_rr == 0.0)) + rho_rain_mean = ln_mean(rho_rain_ll, rho_rain_rr) + end + + if (!(inv(temperature_ll) == 0.0) && !(inv(temperature_rr) == 0.0)) + inv_temperature_mean = inv_ln_mean(inv(temperature_ll), inv(temperature_rr)) + end + + v1_avg = 0.5 * (v1_ll + v1_rr) + v2_avg = 0.5 * (v2_ll + v2_rr) + v1_square_avg = 0.5 * (v1_ll^2 + v1_rr^2) + v2_square_avg = 0.5 * (v2_ll^2 + v2_rr^2) + rho_dry_avg = 0.5 * (rho_dry_ll + rho_dry_rr) + rho_vapour_avg = 0.5 * (rho_vapour_ll + rho_vapour_rr) + rho_cloud_avg = 0.5 * (rho_cloud_ll + rho_cloud_rr) + rho_rain_avg = 0.5 * (rho_rain_ll + rho_rain_rr) + inv_temperature_avg = 0.5 * (inv(temperature_ll) + inv(temperature_rr)) + v_dot_n_avg = normal_direction[1] * v1_avg + normal_direction[2] * v2_avg + + p_int = inv(inv_temperature_avg) * (R_d * rho_dry_avg + R_v * rho_vapour_avg + + R_q * (rho_cloud_avg + rho_rain_avg)) + K_avg = 0.5 * (v1_square_avg + v2_square_avg) + vr_normal_avg = 0.5 * normal_direction[2] * (vr_ll + vr_rr) + rho_rain_avg = 0.5 * (rho_rain_ll + rho_rain_rr) + + # assemble the flux + f_dry = rho_dry_mean * v_dot_n_avg + f_rain = rho_rain_avg * (v_dot_n_avg - vr_normal_avg) + f_vapour = rho_vapour_mean * v_dot_n_avg + f_cloud = rho_cloud_mean * v_dot_n_avg + f_moist = (rho_vapour_mean + rho_cloud_mean) * v_dot_n_avg + f_rhov1 = (f_dry + f_moist + f_rain) * v1_avg + normal_direction[1] * p_int + f_rhov2 = (f_dry + f_moist + f_rain) * v2_avg + normal_direction[2] * p_int + f_energy = ((c_vd * inv_temperature_mean - K_avg) * f_dry + + (ref_L + c_vv * inv_temperature_mean - K_avg) * f_vapour + + (c_l * inv_temperature_mean - K_avg) * (f_cloud + f_rain) + + v1_avg * f_rhov1 + v2_avg * f_rhov2) + + return SVector(f_dry, f_moist, f_rain, f_rhov1, f_rhov2, f_energy, 0.0, 0.0, 0.0) + end + + @inline function flux_chandrashekar(u_ll, u_rr, orientation::Int, + equations::CompressibleRainyEulerEquations2D) + if (orientation == 1) + return flux_chandrashekar(u_ll, u_rr, SVector(1, 0), equations) + else + return flux_chandrashekar(u_ll, u_rr, SVector(0, 1), equations) + end + end + + @inline function flux_ec_rain(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + # densities and temperatures + rho_dry_ll, rho_moist_ll, rho_rain_ll, rho_ll, rho_inv_ll = densities(u_ll, + equations) + rho_dry_rr, rho_moist_rr, rho_rain_rr, rho_rr, rho_inv_rr = densities(u_rr, + equations) + rho_vapour_ll, rho_cloud_ll, temperature_ll = cons2nonlinearsystemsol(u_ll, + equations) + rho_vapour_rr, rho_cloud_rr, temperature_rr = cons2nonlinearsystemsol(u_rr, + equations) + inv_temperature_ll = inv(temperature_ll) + inv_temperature_rr = inv(temperature_rr) + + # velocities + v1_ll, v2_ll = velocities(u_ll, rho_inv_ll, equations) + v1_rr, v2_rr = velocities(u_rr, rho_inv_rr, equations) + vr_ll = terminal_velocity_rain(rho_vapour_ll + rho_cloud_ll, rho_rain_ll, equations) + vr_rr = terminal_velocity_rain(rho_vapour_rr + rho_cloud_rr, rho_rain_rr, equations) + + # velocity averages + v1_avg = 0.5 * (v1_ll + v1_rr) + v2_avg = 0.5 * (v2_ll + v2_rr) + v1_square_avg = 0.5 * (v1_ll^2 + v1_rr^2) + v2_square_avg = 0.5 * (v2_ll^2 + v2_rr^2) + K_avg = 0.5 * (v1_square_avg + v2_square_avg) + v_dot_n_avg = normal_direction[1] * v1_avg + normal_direction[2] * v2_avg + vr_normal_avg = 0.5 * normal_direction[2] * (vr_ll + vr_rr) + + # density averages + rho_dry_avg = 0.5 * (rho_dry_ll + rho_dry_rr) + rho_vapour_avg = 0.5 * (rho_vapour_ll + rho_vapour_rr) + rho_cloud_avg = 0.5 * (rho_cloud_ll + rho_cloud_rr) + rho_rain_avg = 0.5 * (rho_rain_ll + rho_rain_rr) + + # density log means + rho_dry_log = 0.0 + rho_vapour_log = 0.0 + rho_cloud_log = 0.0 + rho_rain_log = 0.0 + + if (!(rho_dry_ll == 0.0) && !(rho_dry_rr == 0.0)) + rho_dry_log = ln_mean(rho_dry_ll, rho_dry_rr) + end + + if (!(rho_vapour_ll == 0.0) && !(rho_vapour_rr == 0.0)) + rho_vapour_log = ln_mean(rho_vapour_ll, rho_vapour_rr) + end + + if (!(rho_cloud_ll == 0.0) && !(rho_cloud_rr == 0.0)) + rho_cloud_log = ln_mean(rho_cloud_ll, rho_cloud_rr) + end + + if (!(rho_rain_ll == 0.0) && !(rho_rain_rr == 0.0)) + rho_rain_log = ln_mean(rho_rain_ll, rho_rain_rr) + end + + # other averages + inv_temperature_avg = 0.5 * (inv_temperature_ll + inv_temperature_rr) + inv_temperature_log = inv_ln_mean(inv_temperature_ll, inv_temperature_rr) + p_int = inv(inv_temperature_avg) * (R_d * rho_dry_avg + R_v * rho_vapour_avg) + + # density flux + f_vapour = rho_vapour_log * v_dot_n_avg + f_cloud = rho_cloud_avg * v_dot_n_avg + f_dry = rho_dry_log * v_dot_n_avg + f_rain = rho_rain_avg * (v_dot_n_avg - vr_normal_avg) + f_moist = f_vapour + f_cloud + f_rho = f_dry + f_moist + f_rain + + # momentum flux + f_rhov1 = f_rho * v1_avg + p_int * normal_direction[1] + f_rhov2 = f_rho * v2_avg + p_int * normal_direction[2] + + # energy flux + f_energy = (c_vd * inv_temperature_log - K_avg) * f_dry + + (c_vv * inv_temperature_log - K_avg + L_ref) * f_vapour + + (c_l * inv_temperature_log - K_avg) * (f_cloud + f_rain) + + (v1_avg * f_rhov1 + v2_avg * f_rhov2) + + return SVector(f_dry, f_moist, f_rain, f_rhov1, f_rhov2, f_energy, 0.0, 0.0, 0.0) + end + + @inline function flux_ec_rain(u_ll, u_rr, orientation::Int, + equations::CompressibleRainyEulerEquations2D) + if (orientation == 1) + return flux_ec_rain(u_ll, u_rr, SVector(1, 0), equations) + else + return flux_ec_rain(u_ll, u_rr, SVector(0, 1), equations) + end + end + + # adapted from ShallowWaterEquations2D (Recommended with rain!) + @inline function boundary_condition_simple_slip_wall(u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleRainyEulerEquations2D) + # normalize the outward pointing direction + normal = normal_direction / norm(normal_direction) + + # compute the normal velocity + u_normal = normal[1] * u_inner[4] + normal[2] * u_inner[5] + + # create the "external" boundary solution state + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], + u_inner[4] - 2 * u_normal * normal[1], + u_inner[5] - 2 * u_normal * normal[2], + u_inner[6], u_inner[7], u_inner[8], u_inner[9]) + + # calculate the boundary flux + flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) + + return flux + end + + # adapted from ShallowWaterEquations2D (Recommended with rain!) + @inline function boundary_condition_simple_slip_wall(u_inner, orientation, direction, x, + t, surface_flux_function, + equations::CompressibleRainyEulerEquations2D) + ## get the appropriate normal vector from the orientation + if orientation == 1 + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], -u_inner[4], + u_inner[5], u_inner[6], u_inner[7], u_inner[8], u_inner[9]) + else # orientation == 2 + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], u_inner[4], + -u_inner[5], u_inner[6], u_inner[7], u_inner[8], + u_inner[9]) + end + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end +end # muladd end diff --git a/src/equations/compressible_rainy_euler_explicit_2d.jl b/src/equations/compressible_rainy_euler_explicit_2d.jl new file mode 100644 index 00000000..8a3214f2 --- /dev/null +++ b/src/equations/compressible_rainy_euler_explicit_2d.jl @@ -0,0 +1,1060 @@ +using Trixi +using NLsolve: nlsolve +import Trixi: varnames, + cons2prim, cons2entropy, + flux, flux_chandrashekar, + max_abs_speeds, max_abs_speed_naive, + boundary_condition_slip_wall, entropy + +### Implementation similar to: +# Sabine Doppler, Philip L. Lederer, Joachim Schöberl, Henry von Wahl, +# A discontinuous Galerkin approach for atmospheric flows with implicit condensation, +# Journal of Computational Physics, +# Volume 499, +# 2024, +# 112713, +# ISSN 0021-9991 + +@muladd begin + + ### equation, parameters and constants ### + + struct CompressibleRainyEulerExplicitEquations2D{RealT <: Real} <: + AbstractCompressibleRainyEulerEquations{2, 7} + # Specific heat capacities: + c_liquid_water :: RealT + c_dry_air_const_pressure :: RealT + c_dry_air_const_volume :: RealT + c_vapour_const_pressure :: RealT + c_vapour_const_volume :: RealT + + # Gas constants: + R_dry_air :: RealT + R_vapour :: RealT + eps :: RealT + + # Reference values: + ref_saturation_pressure :: RealT + ref_temperature :: RealT + ref_latent_heat_vap_temp :: RealT + ref_pressure :: RealT + + # Other: + gravity :: RealT + rain_water_distr :: RealT + v_mean_rain :: RealT + end + + function CompressibleRainyEulerExplicitEquations2D(; RealT = Float64) + # Specific heat capacities: + c_liquid_water = 4186.0 + c_dry_air_const_pressure = 1004.0 + c_dry_air_const_volume = 717.0 + c_vapour_const_pressure = 1885.0 + c_vapour_const_volume = 1424.0 + + # Gas constants: + R_dry_air = c_dry_air_const_pressure - c_dry_air_const_volume + R_vapour = c_vapour_const_pressure - c_vapour_const_volume + eps = R_dry_air / R_vapour + + # Reference values: + ref_saturation_pressure = 610.7 # This needs to be adjusted if ref_temperature is changed! + ref_temperature = 273.15 + ref_latent_heat_vap_temp = 2.5e6#3147620.0 + ref_pressure = 1e5 + + # Other: + gravity = 9.81 + rain_water_distr = 8e6 + v_mean_rain = 130.0 + + return CompressibleRainyEulerExplicitEquations2D{RealT}(c_liquid_water, + c_dry_air_const_pressure, + c_dry_air_const_volume, + c_vapour_const_pressure, + c_vapour_const_volume, + R_dry_air, + R_vapour, eps, + ref_saturation_pressure, + ref_temperature, + ref_latent_heat_vap_temp, + ref_pressure, gravity, + rain_water_distr, + v_mean_rain) + end + + ### conversion ### + + @inline function cons2prim(u, equations::CompressibleRainyEulerExplicitEquations2D) + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # energy density + energy = energy_density(u, equations) + + return SVector(rho_dry, rho_vapour, rho_cloud, rho_rain, v1, v2, energy) + end + + # converts consverved to entropy variables + @inline function cons2entropy(u, equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + c_l = equations.c_liquid_water + R_d = equations.R_dry_air + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # nonlinear system + temperature = get_temperature(u, equations) + ln_temperature = log(temperature) + inv_temperature = inv(temperature) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + v_squared_temp = 0.5 * (v1^2 + v2^2) * inv_temperature + + # check for zero density + rho_vapour_log = 0.0 + + if (rho_vapour > 0.0) + rho_vapour_log = log(rho_vapour) + end + + omega_dry = c_vd * ln_temperature - R_d * log(rho_dry) + v_squared_temp - c_vd - R_d + omega_vapour = c_vv * ln_temperature - R_v * rho_vapour_log + v_squared_temp - + c_vv - R_v - L_ref * inv_temperature + omega_liquid = c_l * ln_temperature + v_squared_temp - c_l + omega_momentum_1 = -v1 * inv_temperature + omega_momentum_2 = -v2 * inv_temperature + omega_energy = inv_temperature + + return SVector(omega_dry, omega_vapour, omega_liquid, omega_liquid, + omega_momentum_1, omega_momentum_2, omega_energy) + end + + # adapted from compressible_moist_euler_2d.jl + @inline function cons2eq_pot_temp(u, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_pd = equations.c_dry_air_const_pressure + c_pv = equations.c_vapour_const_pressure + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_p = equations.ref_pressure + ref_temp = equations.ref_temperature + ref_L = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # nonlinear system + temperature = get_temperature(u, equations) + + # pressure + p = pressure(u, equations) + + p_v = rho_vapour * R_v * temperature + p_d = p - p_v + p_vs = saturation_vapour_pressure(temperature, equations) + H = p_v / p_vs + r_v = rho_vapour / rho_dry + r_c = rho_cloud / rho_dry + r_r = rho_rain / rho_dry + L_v = ref_L + (c_pv - c_l) * temperature + c_p = c_pd + (r_v + r_c + r_r) * c_l + + # equivalent potential temperature + eq_pot = (temperature * (ref_p / p_d)^(R_d / c_p) * H^(-r_v * R_v / c_p) * + exp(L_v * r_v * inv(c_p * temperature))) + + return SVector(rho, rho_vapour, rho_cloud, rho_rain, v1, v2, eq_pot, rho) + end + + # for convenience TODO rename + @inline function cons2speeds(u, equations::CompressibleRainyEulerExplicitEquations2D) + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # get speed of sound + v_sound = speed_of_sound(u, equations)[1] + + # get terminal velocity rain + v_r = terminal_velocity_rain(rho_vapour + rho_cloud, rho_rain, equations) + + return SVector(v1, v2, v_sound, v_r) + end + + ### varnames ### + + varnames(::typeof(cons2cons), ::CompressibleRainyEulerExplicitEquations2D) = ("rho_dry", + "rho_vapour", + "rho_cloud", + "rho_rain", + "rho_v1", + "rho_v2", + "energy_density") + + varnames(::typeof(cons2prim), ::CompressibleRainyEulerExplicitEquations2D) = ("rho_dry", + "rho_vapour", + "rho_cloud", + "rho_rain", + "v1", + "v2", + "energy_density") + + varnames(::typeof(cons2eq_pot_temp), ::CompressibleRainyEulerExplicitEquations2D) = ("rho_dry", + "rho_vapour", + "rho_cloud", + "rho_rain", + "v1", + "v2", + "eq_pot_temp", + "rho") + + ### physics variables ### + + @inline function densities(u, equations::CompressibleRainyEulerExplicitEquations2D) + # densities + rho_dry = u[1] + rho_vapour = u[2] + rho_cloud = u[3] + rho_rain = u[4] + rho = rho_dry + rho_vapour + rho_cloud + rho_rain + rho_inv = inv(rho) + + return SVector(rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv) + end + + @inline function rain_density(u, equations::CompressibleRainyEulerExplicitEquations2D) + return u[4] + end + + @inline function velocities(u, rho_inv, + equations::CompressibleRainyEulerExplicitEquations2D) + return SVector(u[5] * rho_inv, u[6] * rho_inv) + end + + @inline function energy_density(u, equations::CompressibleRainyEulerExplicitEquations2D) + return u[7] + end + + @inline function pressure(u, equations::CompressibleRainyEulerExplicitEquations2D) + # constants + R_d = equations.R_dry_air + R_v = equations.R_vapour + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + temperature = get_temperature(u, equations) + + p = (R_d * rho_dry + R_v * rho_vapour) * temperature + + return p + end + + @inline function speed_of_sound(u, equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + if (rho_vapour < 0.0) + error("rho vapour less than zero") + end + if (rho_cloud < 0.0) + error("rho cloud less than zero") + end + + # formula + p = pressure(u, equations) + q_v = rho_vapour / rho_dry + q_l = (rho_cloud + rho_rain) / rho_dry + gamma_m = 1 + (R_d + R_v * q_v) / (c_vd + c_vv * q_v + c_l * q_l) + + if (rho_inv < 0.0) + error("rho less than zero") + elseif (p < 0.0) + error("pressure less than zero") + end + + v_sound = sqrt(gamma_m * p * rho_inv) + + return SVector(v_sound, gamma_m) + end + + @inline function get_temperature(u, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + L_ref = equations.ref_latent_heat_vap_temp + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + + # energy density + energy = energy_density(u, equations::CompressibleRainyEulerExplicitEquations2D) + + return (energy - L_ref * rho_vapour - 0.5 * rho * (v1^2 + v2^2)) / + (c_vd * rho_dry + c_vv * rho_vapour + c_l * (rho_cloud + rho_rain)) + end + + @inline function terminal_velocity_rain(rho_moist, rho_rain, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + N_0 = equations.rain_water_distr + v_0 = equations.v_mean_rain + + # formula ( \Gamma(4.5) / 6 ~= 1.9386213994279082 ) + if (rho_rain > 0.0) + v_terminal_rain = v_0 * 1.9386213994279082 * + (rho_rain / (pi * (rho_moist + rho_rain) * N_0))^(0.125) + else + v_terminal_rain = 0.0 + end + + return v_terminal_rain + end + + @inline function saturation_vapour_pressure(temperature, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_pv = equations.c_vapour_const_pressure + R_v = equations.R_vapour + ref_s_p = equations.ref_saturation_pressure + ref_temp = equations.ref_temperature + ref_L = equations.ref_latent_heat_vap_temp + + # testing + if (temperature < 0.0) + display(temperature) + error("temp less than zero") + end + + # Clausius Clapeyron formula + p_vapour_saturation = ref_s_p * (temperature / ref_temp)^((c_pv - c_l) / R_v) + p_vapour_saturation *= exp(((ref_L - (c_pv - c_l) * ref_temp) / R_v) * + (1 / ref_temp - 1 / temperature)) + + return p_vapour_saturation + end + + @inline function saturation_vapour_pressure_derivative(temperature, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_pv = equations.c_vapour_const_pressure + R_v = equations.R_vapour + ref_s_p = equations.ref_saturation_pressure + ref_temp = equations.ref_temperature + ref_L = equations.ref_latent_heat_vap_temp + + # testing + if (temperature < 0.0) + display(temperature) + error("temp less than zero") + end + + const_1 = (c_pv - c_l) / R_v + const_2 = (ref_L - (c_pv - c_l) * ref_temp) / R_v + + p_vapour_saturation_derivative = ref_s_p / (ref_temp^const_1) + p_vapour_saturation_derivative *= (const_1 * temperature^(const_1 - 1) + + const_2 * temperature^(const_1 - 2)) + p_vapour_saturation_derivative *= exp(const_2 * (1 / ref_temp - 1 / temperature)) + + return p_vapour_saturation_derivative + end + + # adapted from compressible_moist_euler_2d.jl + @inline function moist_air_phase_change(u, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + R_v = equations.R_vapour + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # temperature + temperature = get_temperature(u, equations) + + # saturation vapor pressure + p_vs = saturation_vapour_pressure(temperature, equations) + + # saturation density of vapor + rho_star_qv = p_vs / (R_v * temperature) + + # Fisher-Burgmeister-Function + a = rho_star_qv - rho_vapour + b = rho_cloud + + # saturation control factor + # < 1: stronger saturation effect + # > 1: weaker saturation effect + C = 1.0 + + return (a + b - sqrt(a^2 + b^2)) * C + end + + ### pde discretization ### + + @inline function flux(u, orientation::Integer, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + + #densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # recover rho_vapour, rho_cloud, temperature from nonlinear system + temperature = get_temperature(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + v_r = terminal_velocity_rain(rho_vapour + rho_cloud, rho_rain, equations) + + # pressure + p = pressure(u, equations) + + # energy density + energy = energy_density(u, equations) + + # flux for orientation cases + if (orientation == 1) + # "mass" + f1 = rho_dry * v1 + f2 = rho_vapour * v1 + f3 = rho_cloud * v1 + f4 = rho_rain * v1 + + # "momentum" + f5 = rho * v1 * v1 + p + f6 = rho * v1 * v2 + + # "energy" + f7 = (energy + p) * v1 + + else + # "mass" + f1 = rho_dry * v2 + f2 = rho_vapour * v2 + f3 = rho_cloud * v2 + f4 = rho_rain * (v2 - v_r) + + # "momentum" + f5 = rho * v1 * v2 - rho_rain * v_r * v1 + f6 = rho * v2 * v2 + p - rho_rain * v_r * v2 + + # "energy" + f7 = (energy + p) * v2 - + (c_l * temperature + 0.5 * (v1^2 + v2^2)) * rho_rain * v_r + end + + return SVector(f1, f2, f3, f4, f5, f6, f7) + end + + @inline function flux(u, normal_direction::AbstractVector, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # recover rho_vapour, rho_cloud, temperature from nonlinear system + temperature = get_temperature(u, equations) + + # velocity + v1, v2 = velocities(u, rho_inv, equations) + v_r = terminal_velocity_rain(rho_vapour + rho_cloud, rho_rain, equations) + + # normal velocities + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + v_r_normal = v_r * normal_direction[2] + + # pressure + p = pressure(u, equations) + + # energy density + energy = energy_density(u, equations) + + # flux + # "mass" + f1 = rho_dry * v_normal + f2 = rho_vapour * v_normal + f3 = rho_cloud * v_normal + f4 = rho_rain * (v_normal - v_r_normal) + + # "momentum" + f5 = rho * v_normal * v1 + p * normal_direction[1] - rho_rain * v_r_normal * v1 + f6 = rho * v_normal * v2 + p * normal_direction[2] - rho_rain * v_r_normal * v2 + + # "energy" + f7 = (energy + p) * v_normal - + (c_l * temperature + 0.5 * (v1^2 + v2^2)) * rho_rain * v_r_normal + + return SVector(f1, f2, f3, f4, f5, f6, f7) + end + + # no Coriolis term + @inline function source_terms_rainy(u, x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + R_v = equations.R_vapour + ref_temp = equations.ref_temperature + g = equations.gravity + + # name needed variables + rho_v2 = u[6] + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + # temperature + temperature = get_temperature(u, equations) + + rho_vs = saturation_vapour_pressure(temperature, equations) / (R_v * temperature) + Q_ph = moist_air_phase_change(u, equations) + + # source terms phase change + S_evaporation = (3.86e-3 - 9.41e-5 * (temperature - ref_temp)) * + (1 + 9.1 * rho_rain^(0.1875)) + S_evaporation *= (rho_vs - rho_vapour) * rho_rain^(0.5) + S_auto_conversion = 0.001 * rho_cloud + S_accretion = 1.72 * rho_cloud * rho_rain^(0.875) + S_rain = S_auto_conversion + S_accretion - S_evaporation + + return SVector(0.0, Q_ph + S_evaporation, -Q_ph - S_auto_conversion - S_accretion, + S_rain, 0.0, + -rho * g, -rho_v2 * g) + end + + # no Coriolis term + @inline function source_terms_moist(u, x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + g = equations.gravity + + # name needed variables + rho_v2 = u[6] + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + Q_ph = moist_air_phase_change(u, equations) + + return SVector(0.0, Q_ph, -Q_ph, 0.0, 0.0, + -rho * g, -rho_v2 * g) + end + + # no phase changes and no Coriolis term + @inline function source_terms_no_phase_change(u, x, t, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + g = equations.gravity + + # name needed variables + rho_v2 = u[6] + + # densities + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + + return SVector(0.0, 0.0, 0.0, 0.0, 0.0, -g * rho, -g * rho_v2) + end + + @inline function max_abs_speeds(u, equations::CompressibleRainyEulerExplicitEquations2D) + # name needed variables + v1, v2, v_sound, v_r = cons2speeds(u, equations) + + return SVector((abs(v1) + v_sound), (abs(v2) + v_sound + abs(v_r))) + end + + @inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerExplicitEquations2D) + # name needed variables + v1_ll, v2_ll, v_sound_ll, v_r_ll = cons2speeds(u_ll, equations) + v1_rr, v2_rr, v_sound_rr, v_r_rr = cons2speeds(u_rr, equations) + + # calculate upper bounds for left and right speed + v_ll_max = abs(v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_ll_max += abs(v_r_ll * normal_direction[2]) + + v_rr_max = abs(v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + v_rr_max += abs(v_r_rr * normal_direction[2]) + + return max(v_ll_max, v_rr_max) + + max(v_sound_ll, v_sound_rr) * norm(normal_direction) + end + + @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, + equations::CompressibleRainyEulerExplicitEquations2D) + # name needed variables + v1_ll, v2_ll, v_sound_ll, v_r_ll = cons2speeds(u_ll, equations) + v1_rr, v2_rr, v_sound_rr, v_r_rr = cons2speeds(u_rr, equations) + + if (orientation == 1) + v_ll = abs(v1_ll) + v_rr = abs(v1_rr) + else + v_ll = abs(v2_ll) + v_rr = abs(v2_rr) + end + # experimental + return max(v_ll, v_rr) + max(v_sound_ll, v_sound_rr) + max(abs(v_r_ll), abs(v_r_rr)) + end + + ### boundary conditions ### + + # adapted from compressible_moist_euler_2d.jl which was probably adapted from compressible_euler_2d.jl + @inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations::CompressibleRainyEulerExplicitEquations2D) + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal = normal_direction / norm_ + + # rotate the internal solution state + u_local = rotate_to_x(u_inner, normal, equations) + + # name needed variables + rho_v1 = u_local[5] + + # densities + rho_dry_local, rho_vapour_local, rho_cloud_local, rho_rain_local, rho_local, rho_inv_local = densities(u_local, + equations) + + # velocities + v_normal = rho_v1 * rho_inv_local + v_sound, gamma = speed_of_sound(u_local, equations) + + # pressure + p_local = pressure(u_local, equations) + + # Get the solution of the pressure Riemann problem + # See Section 6.3.3 of + # Eleuterio F. Toro (2009) + # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + if v_normal <= 0.0 + p_star = p_local * + (1.0 + 0.5 * (gamma - 1) * v_normal / v_sound)^(2.0 * gamma * + inv(gamma - 1)) + else # v_normal > 0.0 + A = 2.0 / ((gamma + 1) * rho_local) + B = p_local * (gamma - 1) / (gamma + 1) + p_star = p_local + + 0.5 * v_normal / A * + (v_normal + sqrt(v_normal^2 + 4.0 * A * (p_local + B))) + end + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector(0.0, 0.0, 0.0, 0.0, + p_star * normal[1] * norm_, + p_star * normal[2] * norm_, + 0.0) + end + + # same as in compressible_euler_2d.jl + @inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, + direction, x, t, + surface_flux_function, + equations::CompressibleRainyEulerExplicitEquations2D) + # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back + # to be inward pointing on the -x and -y sides due to the orientation convention used by StructuredMesh + if isodd(direction) + boundary_flux = -boundary_condition_slip_wall(u_inner, -normal_direction, + x, t, surface_flux_function, + equations) + else + boundary_flux = boundary_condition_slip_wall(u_inner, normal_direction, + x, t, surface_flux_function, + equations) + end + + return boundary_flux + end + + # same as in compressible_euler_2d.jl + @inline function rotate_to_x(u, normal_vector, + equations::CompressibleRainyEulerExplicitEquations2D) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + return SVector(u[1], u[2], u[3], u[4], + c * u[5] + s * u[6], + -s * u[5] + c * u[6], + u[7]) + end + + # should be used together with TreeMesh (adapted from compressible_euler_2d.jl) + @inline function boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, + equations::CompressibleRainyEulerExplicitEquations2D) + # get the appropriate normal vector from the orientation + RealT = eltype(u_inner) + if orientation == 1 + normal_direction = SVector(one(RealT), zero(RealT)) + else # orientation == 2 + normal_direction = SVector(zero(RealT), one(RealT)) + end + + # compute and return the flux using `boundary_condition_slip_wall` routine above + return boundary_condition_slip_wall(u_inner, normal_direction, direction, + x, t, surface_flux_function, equations) + end + + #= for parabolic terms (LaplaceDiffusion2D) + @inline function boundary_condition_laplace(flux_inner, u_inner, normal::AbstractVector, x, t, operator_type::Trixi.Gradient, + equations_parabolic::LaplaceDiffusion2D) + return u_inner + end + + @inline function boundary_condition_laplace(flux_inner, u_inner, normal::AbstractVector, x, t, operator_type::Trixi.Divergence, + equations_parabolic::LaplaceDiffusion2D) + return flux_inner + end=# + + # Low Mach number approximate Riemann solver (LMARS) from + # X. Chen, N. Andronova, B. Van Leer, J. E. Penner, J. P. Boyd, C. Jablonowski, S. + # Lin, A Control-Volume Model of the Compressible Euler Equations with a Vertical Lagrangian + # Coordinate Monthly Weather Review Vol. 141.7, pages 2526–2544, 2013, + # https://journals.ametsoc.org/view/journals/mwre/141/7/mwr-d-12-00129.1.xml. + # adapted from compressible_moist_euler_2d.jl, does NOT work with rain! + @inline function flux_LMARS(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + a = 360.0 + + # densities + rho_dry_ll, rho_vapour_ll, rho_cloud_ll, rho_rain_ll, rho_ll, rho_inv_ll = densities(u_ll, + equations) + rho_dry_rr, rho_vapour_rr, rho_cloud_rr, rho_rain_rr, rho_rr, rho_inv_rr = densities(u_rr, + equations) + + # pressure + p_ll = pressure(u_ll, equations) + p_rr = pressure(u_rr, equations) + + # velocities + v1_ll, v2_ll = velocities(u_ll, rho_inv_ll, equations) + v1_rr, v2_rr = velocities(u_rr, rho_inv_rr, equations) + + v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + norm_ = norm(normal_direction) + + # diffusion parameter 0.0 < beta <= 1.0 + beta = 1.0 + + # interface flux components + rho = 0.5 * (rho_ll + rho_rr) + p_interface = 0.5 * (p_ll + p_rr) - beta * 0.5 * a * rho * (v_rr - v_ll) / norm_ + v_interface = 0.5 * (v_ll + v_rr) - beta * 1 / (2 * a * rho) * (p_rr - p_ll) * norm_ + + if (v_interface > 0) + f1, f2, f3, _, f5, f6, f7 = u_ll * v_interface + f7 += p_ll * v_interface + else + f1, f2, f3, _, f5, f6, f7 = u_rr * v_interface + f7 += p_rr * v_interface + end + + return SVector(f1, f2, f3, 0.0, + f5 + p_interface * normal_direction[1], + f6 + p_interface * normal_direction[2], + f7) + end + + # Adjusted EC flux in a normal direction with R_q=0. This is based on + # A. Gouasmi, K. Duraisamy, S. M. Murman, Formulation of Entropy-Stable schemes for the + # multicomponent compressible Euler equations, 4 Feb 2020, doi:10.1016/j.cma.2020.112912, + # https://arxiv.org/abs/1904.00972 [math.NA]. + # Adapted from compressible_moist_euler_2d.jl, careful, does NOT work for rain! + @inline function flux_chandrashekar(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + ref_L = equations.ref_latent_heat_vap_temp + R_q = 0.0 + + # densities and temperatures + rho_dry_ll, rho_vapour_ll, rho_cloud_ll, rho_rain_ll, rho_ll, rho_inv_ll = densities(u_ll, + equations) + rho_dry_rr, rho_vapour_rr, rho_cloud_rr, rho_rain_rr, rho_rr, rho_inv_rr = densities(u_rr, + equations) + temperature_ll = get_temperature(u_ll, equations) + temperature_rr = get_temperature(u_rr, equations) + + # velocities + v1_ll, v2_ll = velocities(u_ll, rho_inv_ll, equations) + v1_rr, v2_rr = velocities(u_rr, rho_inv_rr, equations) + vr_ll = terminal_velocity_rain(rho_vapour_ll + rho_cloud_ll, rho_rain_ll, equations) + vr_rr = terminal_velocity_rain(rho_vapour_rr + rho_cloud_rr, rho_rain_rr, equations) + + # mean values + rho_dry_mean = 0.0 + rho_vapour_mean = 0.0 + rho_cloud_mean = 0.0 + rho_rain_mean = 0.0 + inv_temperature_mean = 0.0 + + if (!(rho_dry_ll == 0.0) && !(rho_dry_rr == 0.0)) + rho_dry_mean = ln_mean(rho_dry_ll, rho_dry_rr) + end + + if (!(rho_vapour_ll == 0.0) && !(rho_vapour_rr == 0.0)) + rho_vapour_mean = ln_mean(rho_vapour_ll, rho_vapour_rr) + end + + if (!(rho_cloud_ll == 0.0) && !(rho_cloud_rr == 0.0)) + rho_cloud_mean = ln_mean(rho_cloud_ll, rho_cloud_rr) + end + + if (!(rho_rain_ll == 0.0) && !(rho_rain_rr == 0.0)) + rho_rain_mean = ln_mean(rho_rain_ll, rho_rain_rr) + end + + if (!(inv(temperature_ll) == 0.0) && !(inv(temperature_rr) == 0.0)) + inv_temperature_mean = inv_ln_mean(inv(temperature_ll), inv(temperature_rr)) + end + + v1_avg = 0.5 * (v1_ll + v1_rr) + v2_avg = 0.5 * (v2_ll + v2_rr) + v1_square_avg = 0.5 * (v1_ll^2 + v1_rr^2) + v2_square_avg = 0.5 * (v2_ll^2 + v2_rr^2) + rho_dry_avg = 0.5 * (rho_dry_ll + rho_dry_rr) + rho_vapour_avg = 0.5 * (rho_vapour_ll + rho_vapour_rr) + rho_cloud_avg = 0.5 * (rho_cloud_ll + rho_cloud_rr) + rho_rain_avg = 0.5 * (rho_rain_ll + rho_rain_rr) + inv_temperature_avg = 0.5 * (inv(temperature_ll) + inv(temperature_rr)) + v_dot_n_avg = normal_direction[1] * v1_avg + normal_direction[2] * v2_avg + + p_int = inv(inv_temperature_avg) * (R_d * rho_dry_avg + R_v * rho_vapour_avg + + R_q * (rho_cloud_avg + rho_rain_avg)) + K_avg = 0.5 * (v1_square_avg + v2_square_avg) + vr_normal_avg = 0.5 * normal_direction[2] * (vr_ll + vr_rr) + rho_rain_avg = 0.5 * (rho_rain_ll + rho_rain_rr) + + # assemble the flux + f_dry = rho_dry_mean * v_dot_n_avg + f_vapour = rho_vapour_mean * v_dot_n_avg + f_cloud = rho_cloud_mean * v_dot_n_avg + f_rain = rho_rain_avg * (v_dot_n_avg - vr_normal_avg) + f_rhov1 = (f_dry + f_vapour + f_cloud + f_rain) * v1_avg + + normal_direction[1] * p_int + f_rhov2 = (f_dry + f_vapour + f_cloud + f_rain) * v2_avg + + normal_direction[2] * p_int + f_energy = ((c_vd * inv_temperature_mean - K_avg) * f_dry + + (ref_L + c_vv * inv_temperature_mean - K_avg) * f_vapour + + (c_l * inv_temperature_mean - K_avg) * (f_cloud + f_rain) + + v1_avg * f_rhov1 + v2_avg * f_rhov2) + + return SVector(f_dry, f_vapour, f_cloud, f_rain, f_rhov1, f_rhov2, f_energy) + end + + @inline function flux_chandrashekar(u_ll, u_rr, orientation::Int, + equations::CompressibleRainyEulerExplicitEquations2D) + if (orientation == 1) + return flux_chandrashekar(u_ll, u_rr, SVector(1, 0), equations) + else + return flux_chandrashekar(u_ll, u_rr, SVector(0, 1), equations) + end + end + + @inline function flux_ec_rain(u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + L_ref = equations.ref_latent_heat_vap_temp + + # densities and temperatures + rho_dry_ll, rho_vapour_ll, rho_cloud_ll, rho_rain_ll, rho_ll, rho_inv_ll = densities(u_ll, + equations) + rho_dry_rr, rho_vapour_rr, rho_cloud_rr, rho_rain_rr, rho_rr, rho_inv_rr = densities(u_rr, + equations) + temperature_ll = get_temperature(u_ll, equations) + temperature_rr = get_temperature(u_rr, equations) + inv_temperature_ll = inv(temperature_ll) + inv_temperature_rr = inv(temperature_rr) + + # velocities + v1_ll, v2_ll = velocities(u_ll, rho_inv_ll, equations) + v1_rr, v2_rr = velocities(u_rr, rho_inv_rr, equations) + vr_ll = terminal_velocity_rain(rho_vapour_ll + rho_cloud_ll, rho_rain_ll, equations) + vr_rr = terminal_velocity_rain(rho_vapour_rr + rho_cloud_rr, rho_rain_rr, equations) + + # velocity averages + v1_avg = 0.5 * (v1_ll + v1_rr) + v2_avg = 0.5 * (v2_ll + v2_rr) + v1_square_avg = 0.5 * (v1_ll^2 + v1_rr^2) + v2_square_avg = 0.5 * (v2_ll^2 + v2_rr^2) + K_avg = 0.5 * (v1_square_avg + v2_square_avg) + v_dot_n_avg = normal_direction[1] * v1_avg + normal_direction[2] * v2_avg + vr_normal_avg = 0.5 * normal_direction[2] * (vr_ll + vr_rr) + + # density averages + rho_dry_avg = 0.5 * (rho_dry_ll + rho_dry_rr) + rho_vapour_avg = 0.5 * (rho_vapour_ll + rho_vapour_rr) + rho_cloud_avg = 0.5 * (rho_cloud_ll + rho_cloud_rr) + rho_rain_avg = 0.5 * (rho_rain_ll + rho_rain_rr) + + # density log means + rho_dry_log = 0.0 + rho_vapour_log = 0.0 + rho_cloud_log = 0.0 + rho_rain_log = 0.0 + + if (!(rho_dry_ll == 0.0) && !(rho_dry_rr == 0.0)) + rho_dry_log = ln_mean(rho_dry_ll, rho_dry_rr) + end + + if (!(rho_vapour_ll == 0.0) && !(rho_vapour_rr == 0.0)) + rho_vapour_log = ln_mean(rho_vapour_ll, rho_vapour_rr) + end + + if (!(rho_cloud_ll == 0.0) && !(rho_cloud_rr == 0.0)) + rho_cloud_log = ln_mean(rho_cloud_ll, rho_cloud_rr) + end + + if (!(rho_rain_ll == 0.0) && !(rho_rain_rr == 0.0)) + rho_rain_log = ln_mean(rho_rain_ll, rho_rain_rr) + end + + # other averages + inv_temperature_avg = 0.5 * (inv_temperature_ll + inv_temperature_rr) + inv_temperature_log = inv_ln_mean(inv_temperature_ll, inv_temperature_rr) + p_int = inv(inv_temperature_avg) * (R_d * rho_dry_avg + R_v * rho_vapour_avg) + + # density flux + f_vapour = rho_vapour_log * v_dot_n_avg + f_cloud = rho_cloud_avg * v_dot_n_avg + f_dry = rho_dry_log * v_dot_n_avg + f_rain = rho_rain_avg * (v_dot_n_avg - vr_normal_avg) + f_moist = f_vapour + f_cloud + f_rho = f_dry + f_moist + f_rain + + # momentum flux + f_rhov1 = f_rho * v1_avg + p_int * normal_direction[1] + f_rhov2 = f_rho * v2_avg + p_int * normal_direction[2] + + # energy flux + f_energy = (c_vd * inv_temperature_log - K_avg) * f_dry + + (c_vv * inv_temperature_log - K_avg + L_ref) * f_vapour + + (c_l * inv_temperature_log - K_avg) * (f_cloud + f_rain) + + (v1_avg * f_rhov1 + v2_avg * f_rhov2) + + return SVector(f_dry, f_vapour, f_cloud, f_rain, f_rhov1, f_rhov2, f_energy) + end + + @inline function flux_ec_rain(u_ll, u_rr, orientation::Int, + equations::CompressibleRainyEulerExplicitEquations2D) + if (orientation == 1) + return flux_ec_rain(u_ll, u_rr, SVector(1, 0), equations) + else + return flux_ec_rain(u_ll, u_rr, SVector(0, 1), equations) + end + end + + # adapted from ShallowWaterEquations2D (Recommended with rain!) + @inline function boundary_condition_simple_slip_wall(u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleRainyEulerExplicitEquations2D) + # normalize the outward pointing direction + normal = normal_direction / norm(normal_direction) + + # compute the normal velocity + u_normal = normal[1] * u_inner[5] + normal[2] * u_inner[6] + + # create the "external" boundary solution state + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], u_inner[4], + u_inner[5] - 2 * u_normal * normal[1], + u_inner[6] - 2 * u_normal * normal[2], + u_inner[7]) + + # calculate the boundary flux + flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) + + return flux + end + + # adapted from ShallowWaterEquations2D (Recommended with rain!) + @inline function boundary_condition_simple_slip_wall(u_inner, orientation, direction, x, + t, surface_flux_function, + equations::CompressibleRainyEulerExplicitEquations2D) + ## get the appropriate normal vector from the orientation + if orientation == 1 + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], u_inner[4], + -u_inner[5], u_inner[6], u_inner[7]) + else # orientation == 2 + u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], u_inner[4], u_inner[5], + -u_inner[6], u_inner[7]) + end + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + @inline function entropy(u, equations::CompressibleRainyEulerExplicitEquations2D) + # constants + c_l = equations.c_liquid_water + c_vd = equations.c_dry_air_const_volume + c_vv = equations.c_vapour_const_volume + R_d = equations.R_dry_air + R_v = equations.R_vapour + + # variables + rho_dry, rho_vapour, rho_cloud, rho_rain, rho, rho_inv = densities(u, equations) + temperature = get_temperature(u, equations) + + # s_k + s_d = c_vd * log(temperature) - R_d * log(rho_dry) + s_v = c_vv * log(temperature) - R_v * log(rho_vapour) + s_l = c_l * log(temperature) + + return rho_dry * s_d + rho_vapour * s_v + (rho_cloud + rho_rain) * s_l + end +end # muladd end diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 064c34b0..da83cb34 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -1,3 +1,5 @@ +using Trixi: AbstractEquations + @muladd begin #! format: noindent @@ -329,10 +331,16 @@ abstract type AbstractCompressibleMoistEulerEquations{NDIMS, NVARS} <: abstract type AbstractCovariantShallowWaterEquations2D{GlobalCoordinateSystem} <: AbstractCovariantEquations{2, 3, GlobalCoordinateSystem, 3} end +abstract type AbstractCompressibleRainyEulerEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("covariant_advection.jl") include("covariant_shallow_water.jl") include("covariant_shallow_water_split.jl") include("compressible_moist_euler_2d_lucas.jl") include("shallow_water_3d.jl") include("reference_data.jl") +include("compressible_rainy_euler_2d.jl") +include("compressible_moist_euler_potential_temperature_2d.jl") +include("compressible_rainy_euler_explicit_2d.jl") end # @muladd diff --git a/test/runtests.jl b/test/runtests.jl index 962e7265..12dc0bfb 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -17,6 +17,10 @@ const TRIXI_NTHREADS = clamp(Sys.CPU_THREADS, 2, 3) include("test_2d_moist_euler.jl") end + @time if TRIXI_TEST == "all" || TRIXI_TEST == "moist_euler" + include("test_2d_rainy_euler.jl") + end + @time if TRIXI_TEST == "all" || TRIXI_TEST == "spherical_advection" include("test_spherical_advection.jl") end diff --git a/test/test_2d_rainy_euler.jl b/test/test_2d_rainy_euler.jl new file mode 100644 index 00000000..9aa55fb4 --- /dev/null +++ b/test/test_2d_rainy_euler.jl @@ -0,0 +1,47 @@ +module TestExamples2DRainyEuler + +using Test +using TrixiAtmo + +include("test_trixiatmo.jl") + +EXAMPLES_DIR = joinpath(TrixiAtmo.examples_dir(), "moist_bubble") + +@trixiatmo_testset "elixir_rainy_euler_rainy_bubble_diffusion" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, + "rainy_bubble_elixirs", + "elixir_rainy_euler_rainy_bubble_diffusion.jl"), + l2=[ + 1.300428671901329e-6, + 2.601090012108739e-5, + 0.0006660124630171347, + 0.008969786054960861, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + linf=[ + 1.0312042909910168e-5, + 0.00020625488871672815, + 0.006392107590872236, + 0.07612038028310053, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + initial_refinement_level=3, + tspan=(0.0, 10.0)) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated TrixiAtmo.Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + end +end +end # module