|
| 1 | +using Statistics, Printf |
| 2 | + |
| 3 | +using Oceananigans |
| 4 | +using Oceananigans.TurbulenceClosures |
| 5 | + |
| 6 | +""" Friction velocity. See equation (16) of Vreugdenhil & Taylor (2018). """ |
| 7 | +function uτ(model, Uavg) |
| 8 | + Nz, Hz, Δz = model.grid.Nz, model.grid.Hz, model.grid.Δz |
| 9 | + U_wall = model.parameters.U_wall |
| 10 | + ν = model.closure.ν |
| 11 | + |
| 12 | + U = Uavg(model)[1+Hz:end-Hz] # Exclude average of halo region. |
| 13 | + |
| 14 | + # Use a finite difference to calculate dU/dz at the top and bottomtom walls. |
| 15 | + # The distance between the center of the cell adjacent to the wall and the |
| 16 | + # wall itself is Δz/2. |
| 17 | + uτ²_top = ν * abs(U[1] - U_wall) / (Δz/2) # Top wall where u = +U_wall |
| 18 | + uτ²_bottom = ν * abs(-U_wall - U[Nz]) / (Δz/2) # Bottom wall where u = -U_wall |
| 19 | + |
| 20 | + uτ_top, uτ_bottom = √uτ²_top, √uτ²_bottom |
| 21 | + |
| 22 | + return uτ_top, uτ_bottom |
| 23 | +end |
| 24 | + |
| 25 | +""" Heat flux at the wall. See equation (16) of Vreugdenhil & Taylor (2018). """ |
| 26 | +function q_wall(model, Tavg) |
| 27 | + Nz, Hz, Δz = model.grid.Nz, model.grid.Hz, model.grid.Δz |
| 28 | + Θ_wall = model.parameters.Θ_wall |
| 29 | + κ = model.closure.κ |
| 30 | + |
| 31 | + Θ = Tavg(model)[1+Hz:end-Hz] # Exclude average of halo region. |
| 32 | + |
| 33 | + # Use a finite difference to calculate dθ/dz at the top and bottomtom walls. |
| 34 | + # The distance between the center of the cell adjacent to the wall and the |
| 35 | + # wall itself is Δz/2. |
| 36 | + q_wall_top = κ * abs(Θ[1] - Θ_wall) / (Δz/2) # Top wall where Θ = +Θ_wall |
| 37 | + q_wall_bottom = κ * abs(-Θ_wall - Θ[Nz]) / (Δz/2) # Bottom wall where Θ = -Θ_wall |
| 38 | + |
| 39 | + return q_wall_top, q_wall_bottom |
| 40 | +end |
| 41 | + |
| 42 | +struct FrictionReynoldsNumber{H} |
| 43 | + Uavg :: H |
| 44 | +end |
| 45 | + |
| 46 | +struct NusseltNumber{H} |
| 47 | + Tavg :: H |
| 48 | +end |
| 49 | + |
| 50 | +""" Friction Reynolds number. See equation (20) of Vreugdenhil & Taylor (2018). """ |
| 51 | +function (Reτ::FrictionReynoldsNumber)(model) |
| 52 | + ν = model.closure.ν |
| 53 | + h = model.grid.Lz / 2 |
| 54 | + uτ_top, uτ_bottom = uτ(model, Reτ.Uavg) |
| 55 | + |
| 56 | + return h * uτ_top / ν, h * uτ_bottom / ν |
| 57 | +end |
| 58 | + |
| 59 | +""" Nusselt number. See equation (20) of Vreugdenhil & Taylor (2018). """ |
| 60 | +function (Nu::NusseltNumber)(model) |
| 61 | + κ = model.closure.κ |
| 62 | + h = model.grid.Lz / 2 |
| 63 | + Θ_wall = model.parameters.Θ_wall |
| 64 | + |
| 65 | + q_wall_top, q_wall_bottom = q_wall(model, Nu.Tavg) |
| 66 | + |
| 67 | + return (q_wall_top * h)/(κ * Θ_wall), (q_wall_bottom * h)/(κ * Θ_wall) |
| 68 | +end |
| 69 | + |
| 70 | +""" |
| 71 | + simulate_stratified_couette_flow(; Nxy, Nz, h=1, U_wall=1, Re=4250, Pr=0.7, Ri, Ni=10, end_time=1000) |
| 72 | +
|
| 73 | +Simulate stratified plane Couette flow with `Nxy` grid cells in each horizontal |
| 74 | +direction, `Nz` grid cells in the vertical, in a domain of size (4πh, 2πh, 2h), |
| 75 | +with wall velocities of `U_wall` at the top and -`U_wall` at the bottom, at a Reynolds |
| 76 | +number `Re, Prandtl number `Pr`, and Richardson number `Ri`. |
| 77 | +
|
| 78 | +`Ni` is the number of "intermediate" time steps taken at a time before printing a progress |
| 79 | +statement and updating the time step. |
| 80 | +""" |
| 81 | +function simulate_stratified_couette_flow(; Nxy, Nz, arch=GPU(), h=1, U_wall=1, Re=4250, Pr=0.7, Ri, Ni=10, end_time=1000) |
| 82 | + #### |
| 83 | + #### Computed parameters |
| 84 | + #### |
| 85 | + |
| 86 | + ν = U_wall * h / Re # From Re = U_wall h / ν |
| 87 | + Θ_wall = Ri * U_wall^2 / h # From Ri = L Θ_wall / U_wall² |
| 88 | + κ = ν / Pr # From Pr = ν / κ |
| 89 | + |
| 90 | + parameters = (U_wall=U_wall, Θ_wall=Θ_wall, Re=Re, Pr=Pr, Ri=Ri) |
| 91 | + |
| 92 | + #### |
| 93 | + #### Impose boundary conditions |
| 94 | + #### |
| 95 | + |
| 96 | + Tbcs = HorizontallyPeriodicBCs( top = BoundaryCondition(Value, Θ_wall), |
| 97 | + bottom = BoundaryCondition(Value, -Θ_wall)) |
| 98 | + |
| 99 | + ubcs = HorizontallyPeriodicBCs( top = BoundaryCondition(Value, U_wall), |
| 100 | + bottom = BoundaryCondition(Value, -U_wall)) |
| 101 | + |
| 102 | + vbcs = HorizontallyPeriodicBCs( top = BoundaryCondition(Value, 0), |
| 103 | + bottom = BoundaryCondition(Value, 0)) |
| 104 | + |
| 105 | + #### |
| 106 | + #### Non-dimensional model setup |
| 107 | + #### |
| 108 | + |
| 109 | + model = Model( |
| 110 | + architecture = arch, |
| 111 | + grid = RegularCartesianGrid(N = (Nxy, Nxy, Nz), L = (4π*h, 2π*h, 2h)), |
| 112 | + buoyancy = SeawaterBuoyancy(equation_of_state=LinearEquationOfState(α=1.0, β=0.0)), |
| 113 | + closure = AnisotropicMinimumDissipation(ν=ν, κ=κ), |
| 114 | +boundary_conditions = HorizontallyPeriodicSolutionBCs(u=ubcs, v=vbcs, T=Tbcs), |
| 115 | + parameters = parameters |
| 116 | + ) |
| 117 | + |
| 118 | + #### |
| 119 | + #### Set initial conditions |
| 120 | + #### |
| 121 | + |
| 122 | + # Add a bit of surface-concentrated noise to the initial condition |
| 123 | + ε(σ, z) = σ * randn() * z/model.grid.Lz * (1 + z/model.grid.Lz) |
| 124 | + |
| 125 | + # We add a sinusoidal initial condition to u to encourage instability. |
| 126 | + T₀(x, y, z) = 2Θ_wall * (1/2 + z/model.grid.Lz) * (1 + ε(5e-1, z)) |
| 127 | + u₀(x, y, z) = 2U_wall * (1/2 + z/model.grid.Lz) * (1 + ε(5e-1, z)) * (1 + 0.5*sin(4π/model.grid.Lx * x)) |
| 128 | + v₀(x, y, z) = ε(5e-1, z) |
| 129 | + w₀(x, y, z) = ε(5e-1, z) |
| 130 | + |
| 131 | + set_ic!(model, u=u₀, v=v₀, w=w₀, T=T₀) |
| 132 | + |
| 133 | + #### |
| 134 | + #### Print simulation banner |
| 135 | + #### |
| 136 | + |
| 137 | + @printf( |
| 138 | + """ |
| 139 | + Simulating stratified plane Couette flow |
| 140 | +
|
| 141 | + N : %d, %d, %d |
| 142 | + L : %.3g, %.3g, %.3g |
| 143 | + Re : %.3f |
| 144 | + Ri : %.3f |
| 145 | + Pr : %.3f |
| 146 | + ν : %.3g |
| 147 | + κ : %.3g |
| 148 | + U_wall : %.3f |
| 149 | + Θ_wall : %.3f |
| 150 | +
|
| 151 | + """, model.grid.Nx, model.grid.Ny, model.grid.Nz, |
| 152 | + model.grid.Lx, model.grid.Ly, model.grid.Lz, |
| 153 | + Re, Ri, Pr, ν, κ, U_wall, Θ_wall) |
| 154 | + |
| 155 | + #### |
| 156 | + #### Set up field output writer |
| 157 | + #### |
| 158 | + |
| 159 | + base_dir = @sprintf("stratified_couette_flow_data_Nxy%d_Nz%d_Ri%.2f", Nxy, Nz, Ri) |
| 160 | + prefix = @sprintf("stratified_couette_flow_Nxy%d_Nz%d_Ri%.2f", Nxy, Nz, Ri) |
| 161 | + |
| 162 | + function init_save_parameters_and_bcs(file, model) |
| 163 | + file["parameters/reynolds_number"] = Re |
| 164 | + file["parameters/richardson_number"] = Ri |
| 165 | + file["parameters/prandtl_number"] = Pr |
| 166 | + file["parameters/viscosity"] = ν |
| 167 | + file["parameters/diffusivity"] = κ |
| 168 | + file["parameters/wall_velocity"] = U_wall |
| 169 | + file["parameters/wall_temperature"] = Θ_wall |
| 170 | + end |
| 171 | + |
| 172 | + fields = Dict( |
| 173 | + :u => model -> Array(model.velocities.u.data.parent), |
| 174 | + :v => model -> Array(model.velocities.v.data.parent), |
| 175 | + :w => model -> Array(model.velocities.w.data.parent), |
| 176 | + :T => model -> Array(model.tracers.T.data.parent), |
| 177 | + :kappaT => model -> Array(model.diffusivities.κₑ.T.data.parent), |
| 178 | + :nu => model -> Array(model.diffusivities.νₑ.data.parent)) |
| 179 | + |
| 180 | + field_writer = JLD2OutputWriter(model, fields; dir=base_dir, prefix=prefix * "_fields", |
| 181 | + init=init_save_parameters_and_bcs, |
| 182 | + interval=10, force=true, verbose=true) |
| 183 | + |
| 184 | + push!(model.output_writers, field_writer) |
| 185 | + |
| 186 | + #### |
| 187 | + #### Set up profile output writer |
| 188 | + #### |
| 189 | + |
| 190 | + Uavg = HorizontalAverage(model, model.velocities.u; return_type=Array) |
| 191 | + Vavg = HorizontalAverage(model, model.velocities.v; return_type=Array) |
| 192 | + Wavg = HorizontalAverage(model, model.velocities.w; return_type=Array) |
| 193 | + Tavg = HorizontalAverage(model, model.tracers.T; return_type=Array) |
| 194 | + νavg = HorizontalAverage(model, model.diffusivities.νₑ; return_type=Array) |
| 195 | + κavg = HorizontalAverage(model, model.diffusivities.κₑ.T; return_type=Array) |
| 196 | + |
| 197 | + profiles = Dict( |
| 198 | + :u => model -> Uavg(model), |
| 199 | + :v => model -> Vavg(model), |
| 200 | + :w => model -> Wavg(model), |
| 201 | + :T => model -> Tavg(model), |
| 202 | + :nu => model -> νavg(model), |
| 203 | + :kappaT => model -> κavg(model)) |
| 204 | + |
| 205 | + profile_writer = JLD2OutputWriter(model, profiles; dir=base_dir, prefix=prefix * "_profiles", |
| 206 | + init=init_save_parameters_and_bcs, interval=1, force=true, verbose=true) |
| 207 | + |
| 208 | + push!(model.output_writers, profile_writer) |
| 209 | + |
| 210 | + #### |
| 211 | + #### Set up statistic output writer |
| 212 | + #### |
| 213 | + |
| 214 | + Reτ = FrictionReynoldsNumber(Uavg) |
| 215 | + Nu = NusseltNumber(Tavg) |
| 216 | + |
| 217 | + statistics = Dict( |
| 218 | + :Re_tau => model -> Reτ(model), |
| 219 | + :Nu => model -> Nu(model)) |
| 220 | + |
| 221 | + statistics_writer = JLD2OutputWriter(model, statistics; dir=base_dir, prefix=prefix * "_statistics", |
| 222 | + init=init_save_parameters_and_bcs, interval=1/2, force=true, verbose=true) |
| 223 | + |
| 224 | + push!(model.output_writers, statistics_writer) |
| 225 | + |
| 226 | + #### |
| 227 | + #### Time stepping |
| 228 | + #### |
| 229 | + |
| 230 | + wizard = TimeStepWizard(cfl=0.02, Δt=0.0001, max_change=1.1, max_Δt=0.02) |
| 231 | + |
| 232 | + cfl(t) = min(0.01*t, 0.1) |
| 233 | + |
| 234 | + while model.clock.time < end_time |
| 235 | + wizard.cfl = cfl(model.clock.time) |
| 236 | + |
| 237 | + walltime = @elapsed time_step!(model; Nt=Ni, Δt=wizard.Δt) |
| 238 | + progress = 100 * (model.clock.time / end_time) |
| 239 | + |
| 240 | + umax = maximum(abs, model.velocities.u.data.parent) |
| 241 | + vmax = maximum(abs, model.velocities.v.data.parent) |
| 242 | + wmax = maximum(abs, model.velocities.w.data.parent) |
| 243 | + CFL = wizard.Δt / Oceananigans.cell_advection_timescale(model) |
| 244 | + |
| 245 | + νmax = maximum(model.diffusivities.νₑ.data.parent) |
| 246 | + κmax = maximum(model.diffusivities.κₑ.T.data.parent) |
| 247 | + |
| 248 | + Δ = min(model.grid.Δx, model.grid.Δy, model.grid.Δz) |
| 249 | + νCFL = wizard.Δt / (Δ^2 / νmax) |
| 250 | + κCFL = wizard.Δt / (Δ^2 / κmax) |
| 251 | + |
| 252 | + update_Δt!(wizard, model) |
| 253 | + |
| 254 | + @printf("[%06.2f%%] i: %d, t: %4.2f, umax: (%6.3g, %6.3g, %6.3g) m/s, CFL: %6.4g, νκmax: (%6.3g, %6.3g), νκCFL: (%6.4g, %6.4g), next Δt: %8.5g, ⟨wall time⟩: %s\n", |
| 255 | + progress, model.clock.iteration, model.clock.time, |
| 256 | + umax, vmax, wmax, CFL, νmax, κmax, νCFL, κCFL, |
| 257 | + wizard.Δt, prettytime(walltime / Ni)) |
| 258 | + end |
| 259 | +end |
| 260 | + |
0 commit comments