|
| 1 | +using LibTrixi |
| 2 | +using OrdinaryDiffEq |
| 3 | +using Trixi |
| 4 | + |
| 5 | +# Warm bubble test case from |
| 6 | +# - Wicker, L. J., and Skamarock, W. C. (1998) |
| 7 | +# A time-splitting scheme for the elastic equations incorporating |
| 8 | +# second-order Runge–Kutta time differencing |
| 9 | +# [DOI: 10.1175/1520-0493(1998)126%3C1992:ATSSFT%3E2.0.CO;2](https://doi.org/10.1175/1520-0493(1998)126%3C1992:ATSSFT%3E2.0.CO;2) |
| 10 | +# See also |
| 11 | +# - Bryan and Fritsch (2002) |
| 12 | +# A Benchmark Simulation for Moist Nonhydrostatic Numerical Models |
| 13 | +# [DOI: 10.1175/1520-0493(2002)130<2917:ABSFMN>2.0.CO;2](https://doi.org/10.1175/1520-0493(2002)130<2917:ABSFMN>2.0.CO;2) |
| 14 | +# - Carpenter, Droegemeier, Woodward, Hane (1990) |
| 15 | +# Application of the Piecewise Parabolic Method (PPM) to |
| 16 | +# Meteorological Modeling |
| 17 | +# [DOI: 10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2](https://doi.org/10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2) |
| 18 | +struct WarmBubbleSetup |
| 19 | + # Physical constants |
| 20 | + g::Float64 # gravity of earth |
| 21 | + c_p::Float64 # heat capacity for constant pressure (dry air) |
| 22 | + c_v::Float64 # heat capacity for constant volume (dry air) |
| 23 | + gamma::Float64 # heat capacity ratio (dry air) |
| 24 | + |
| 25 | + function WarmBubbleSetup(; g = 9.81, c_p = 1004.0, c_v = 717.0, gamma = c_p / c_v) |
| 26 | + new(g, c_p, c_v, gamma) |
| 27 | + end |
| 28 | +end |
| 29 | + |
| 30 | +# Initial condition |
| 31 | +function (setup::WarmBubbleSetup)(x, t, equations::CompressibleEulerEquations2D) |
| 32 | + @unpack g, c_p, c_v = setup |
| 33 | + |
| 34 | + # center of perturbation |
| 35 | + center_x = 10000.0 |
| 36 | + center_z = 2000.0 |
| 37 | + # radius of perturbation |
| 38 | + radius = 2000.0 |
| 39 | + # distance of current x to center of perturbation |
| 40 | + r = sqrt((x[1] - center_x)^2 + (x[2] - center_z)^2) |
| 41 | + |
| 42 | + # perturbation in potential temperature |
| 43 | + potential_temperature_ref = 300.0 |
| 44 | + potential_temperature_perturbation = 0.0 |
| 45 | + if r <= radius |
| 46 | + potential_temperature_perturbation = 2 * cospi(0.5 * r / radius)^2 |
| 47 | + end |
| 48 | + potential_temperature = potential_temperature_ref + potential_temperature_perturbation |
| 49 | + |
| 50 | + # Exner pressure, solves hydrostatic equation for x[2] |
| 51 | + exner = 1 - g / (c_p * potential_temperature) * x[2] |
| 52 | + |
| 53 | + # pressure |
| 54 | + p_0 = 100_000.0 # reference pressure |
| 55 | + R = c_p - c_v # gas constant (dry air) |
| 56 | + p = p_0 * exner^(c_p / R) |
| 57 | + |
| 58 | + # temperature |
| 59 | + T = potential_temperature * exner |
| 60 | + |
| 61 | + # density |
| 62 | + rho = p / (R * T) |
| 63 | + |
| 64 | + v1 = 20.0 |
| 65 | + v2 = 0.0 |
| 66 | + E = c_v * T + 0.5 * (v1^2 + v2^2) |
| 67 | + return SVector(rho, rho * v1, rho * v2, rho * E) |
| 68 | +end |
| 69 | + |
| 70 | +# Source terms |
| 71 | +@inline function (setup::WarmBubbleSetup)(u, x, t, equations::CompressibleEulerEquations2D) |
| 72 | + @unpack g = setup |
| 73 | + rho, _, rho_v2, _ = u |
| 74 | + return SVector(zero(eltype(u)), zero(eltype(u)), -g * rho, -g * rho_v2) |
| 75 | +end |
| 76 | + |
| 77 | + |
| 78 | +# The function to create the simulation state needs to be named `init_simstate` |
| 79 | +function init_simstate() |
| 80 | + |
| 81 | + ############################################################################### |
| 82 | + # semidiscretization of the compressible Euler equations |
| 83 | + warm_bubble_setup = WarmBubbleSetup() |
| 84 | + |
| 85 | + equations = CompressibleEulerEquations2D(warm_bubble_setup.gamma) |
| 86 | + |
| 87 | + boundary_conditions = (x_neg = boundary_condition_periodic, |
| 88 | + x_pos = boundary_condition_periodic, |
| 89 | + y_neg = boundary_condition_slip_wall, |
| 90 | + y_pos = boundary_condition_slip_wall) |
| 91 | + |
| 92 | + polydeg = 3 |
| 93 | + basis = LobattoLegendreBasis(polydeg) |
| 94 | + |
| 95 | + # This is a good estimate for the speed of sound in this example. |
| 96 | + # Other values between 300 and 400 should work as well. |
| 97 | + surface_flux = FluxLMARS(340.0) |
| 98 | + |
| 99 | + volume_flux = flux_kennedy_gruber |
| 100 | + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) |
| 101 | + |
| 102 | + solver = DGSEM(basis, surface_flux, volume_integral) |
| 103 | + |
| 104 | + coordinates_min = (0.0, 0.0) |
| 105 | + coordinates_max = (20_000.0, 10_000.0) |
| 106 | + |
| 107 | + # Same coordinates as in examples/structured_2d_dgsem/elixir_euler_warm_bubble.jl |
| 108 | + # However TreeMesh will generate a 20_000 x 20_000 square domain instead |
| 109 | + mesh = TreeMesh(coordinates_min, coordinates_max, |
| 110 | + initial_refinement_level = 6, |
| 111 | + n_cells_max = 100_000, |
| 112 | + periodicity = (true, false)) |
| 113 | + |
| 114 | + semi = SemidiscretizationHyperbolic(mesh, equations, warm_bubble_setup, solver, |
| 115 | + source_terms = warm_bubble_setup, |
| 116 | + boundary_conditions = boundary_conditions) |
| 117 | + |
| 118 | + ############################################################################### |
| 119 | + # ODE solvers, callbacks etc. |
| 120 | + |
| 121 | + tspan = (0.0, 1000.0) # 1000 seconds final time |
| 122 | + |
| 123 | + ode = semidiscretize(semi, tspan) |
| 124 | + |
| 125 | + summary_callback = SummaryCallback() |
| 126 | + |
| 127 | + analysis_interval = 1000 |
| 128 | + |
| 129 | + analysis_callback = AnalysisCallback(semi, interval = analysis_interval, |
| 130 | + extra_analysis_errors = (:entropy_conservation_error,)) |
| 131 | + |
| 132 | + alive_callback = AliveCallback(analysis_interval = analysis_interval) |
| 133 | + |
| 134 | + save_solution = SaveSolutionCallback(interval = analysis_interval, |
| 135 | + save_initial_solution = true, |
| 136 | + save_final_solution = true, |
| 137 | + output_directory = "out_bubble", |
| 138 | + solution_variables = cons2prim) |
| 139 | + |
| 140 | + @inline function Tpot(u, equations::CompressibleEulerEquations2D) |
| 141 | + rho, _, _, p = cons2prim(u, equations) |
| 142 | + exner = (p / 100_000)^(1-inv(warm_bubble_setup.gamma)) |
| 143 | + T = p / rho / (warm_bubble_setup.c_p - warm_bubble_setup.c_v) |
| 144 | + return T / exner |
| 145 | + end |
| 146 | + amr_indicator = IndicatorLöhner(semi, variable = Tpot) |
| 147 | + amr_controller = ControllerThreeLevel(semi, amr_indicator, |
| 148 | + base_level = 4, |
| 149 | + med_level = 6, med_threshold = 0.0002, |
| 150 | + max_level = 8, max_threshold = 0.0005) |
| 151 | + amr_callback = AMRCallback(semi, amr_controller, |
| 152 | + interval = 50, |
| 153 | + adapt_initial_condition = true, |
| 154 | + adapt_initial_condition_only_refine = true) |
| 155 | + |
| 156 | + stepsize_callback = StepsizeCallback(cfl = 1.0) |
| 157 | + |
| 158 | + callbacks = CallbackSet(summary_callback, |
| 159 | + analysis_callback, |
| 160 | + alive_callback, |
| 161 | + save_solution, |
| 162 | + amr_callback, |
| 163 | + stepsize_callback) |
| 164 | + |
| 165 | + ############################################################################### |
| 166 | + # create OrdinaryDiffEq's time integrator |
| 167 | + integrator = init(ode, |
| 168 | + CarpenterKennedy2N54(williamson_condition=false), |
| 169 | + dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback |
| 170 | + save_everystep=false, |
| 171 | + callback=callbacks); |
| 172 | + |
| 173 | + ############################################################################### |
| 174 | + # Create simulation state |
| 175 | + simstate = SimulationState(semi, integrator) |
| 176 | + |
| 177 | + return simstate |
| 178 | +end |
0 commit comments