Skip to content

Commit 4bdb933

Browse files
authored
Stratified Couette flow verification (CliMA#381)
Stratified Couette flow verification Former-commit-id: 211e18c
2 parents 84337a2 + cb25a45 commit 4bdb933

File tree

5 files changed

+564
-0
lines changed

5 files changed

+564
-0
lines changed

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,5 @@ EquationsOfState = (LinearEquationOfState, RoquetIdealizedNonlinearEquationOfSta
6565
include("test_output_writers.jl")
6666
include("test_regression.jl")
6767
include("test_examples.jl")
68+
include("test_verification.jl")
6869
end

test/test_verification.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
VERIFICATION_DIR = "../verification/"
2+
EXPERIMENTS = ["stratified_couette_flow"]
3+
4+
for exp in EXPERIMENTS
5+
script_filepath = joinpath(VERIFICATION_DIR, exp, exp * ".jl")
6+
try
7+
include(script_filepath)
8+
catch err
9+
@error sprint(showerror, err)
10+
end
11+
end
12+
13+
function run_stratified_couette_flow_verification(arch)
14+
simulate_stratified_couette_flow(Nxy=16, Nz=8, arch=arch, Ri=0.01, Ni=1, end_time=1e-15)
15+
return true
16+
end
17+
18+
19+
@testset "Verification" begin
20+
println("Testing verification scripts...")
21+
22+
for arch in archs
23+
@testset "Stratified Couette flow verification [$(typeof(arch))]" begin
24+
println(" Testing stratified Couette flow verification [$(typeof(arch))]")
25+
@test run_stratified_couette_flow_verification(arch)
26+
end
27+
end
28+
end
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
include("stratified_couette_flow.jl")
2+
3+
simulate_stratified_couette_flow(Nxy=128, Nz=128, Ri=0)
4+
simulate_stratified_couette_flow(Nxy=128, Nz=128, Ri=0.01)
5+
simulate_stratified_couette_flow(Nxy=128, Nz=128, Ri=0.04)
6+
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
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 (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 = (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

Comments
 (0)