Skip to content

Commit b6f9c0b

Browse files
Implicit free surface formulation for NonhydrostaticModel (#4740)
* Add Combination boundary condition classification * adapt regularize_boundary_condition to work with CombinationBoundaryCondition * better names * validate -> regularize * Update test_boundary_conditions.jl * Combination -> Mixed * formatting * add InhomogeneousFormulation to tridiagonal pressure solver * add free_surface property to NonhydrostaticModel * WIP nonhydrostatic free surface pressure solver * fix bug launching internal_work_layout? * Write a routine to compute the RHS of the pressure poisson equation for implicit free surface * persist poisson eigenvalues in FourierTridiagonalPoissonSolver * update tridiagonal term for implicit free surface * fix * bugfix * bugfix * bugfixes * fix rhs + diagonal computation for ifs * add MixedBoundaryCondition to pressure for ifs case * WIP (code runs, but sign errors may remain) * Fixed typos, added w update at surface, commented out no-penetration bc for w in field tuples, commented out subtraction of mean in solver * fix top w bc for non-nothing free surface in nonhydrostaticmodel * Update src/Models/NonhydrostaticModels/nonhydrostatic_model.jl * use InhomogeneousFormulation for pressure solver * fixes * Update src/Fields/field_tuples.jl * Update src/Fields/field_tuples.jl * Update src/Solvers/fourier_tridiagonal_poisson_solver.jl * move surface velocity computation to separate kernel * fix indexing * disambiguate * fix function signatures * Update src/Models/NonhydrostaticModels/pressure_correction.jl * Update src/Models/NonhydrostaticModels/pressure_correction.jl * fix test utility * fix * fix solve_for_pressure! signatures * throw warning when using naive solver properly --------- Co-authored-by: Shriya Fruitwala <[email protected]>
1 parent 40f0697 commit b6f9c0b

15 files changed

+282
-45
lines changed

src/Fields/field_tuples.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function VelocityFields(grid::AbstractGrid, user_bcs = NamedTuple())
136136
u = XFaceField(grid, boundary_conditions=bcs.u)
137137
v = YFaceField(grid, boundary_conditions=bcs.v)
138138
w = ZFaceField(grid, boundary_conditions=bcs.w)
139-
139+
140140
return (u=u, v=v, w=w)
141141
end
142142

src/Models/HydrostaticFreeSurfaceModels/HydrostaticFreeSurfaceModels.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ using DocStringExtensions
1717

1818
import Oceananigans: fields, prognostic_fields, initialize!
1919
import Oceananigans.Advection: cell_advection_timescale
20+
import Oceananigans.Models: materialize_free_surface
2021
import Oceananigans.TimeSteppers: step_lagrangian_particles!
2122
import Oceananigans.Architectures: on_architecture
2223

src/Models/HydrostaticFreeSurfaceModels/hydrostatic_free_surface_model.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,7 @@ function HydrostaticFreeSurfaceModel(; grid,
179179

180180
# Next, we form a list of default boundary conditions:
181181
field_names = constructor_field_names(velocities, tracers, free_surface, auxiliary_fields, biogeochemistry, grid)
182-
default_boundary_conditions = NamedTuple{field_names}(Tuple(FieldBoundaryConditions()
183-
for name in field_names))
182+
default_boundary_conditions = NamedTuple{field_names}(FieldBoundaryConditions() for name in field_names)
184183

185184
# Then we merge specified, embedded, and default boundary conditions. Specified boundary conditions
186185
# have precedence, followed by embedded, followed by default.

src/Models/Models.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ validate_tracer_advection(tracer_advection_tuple::NamedTuple, grid) = Centered()
8989
validate_tracer_advection(tracer_advection::AbstractAdvectionScheme, grid) = tracer_advection, NamedTuple()
9090
validate_tracer_advection(tracer_advection::Nothing, grid) = nothing, NamedTuple()
9191

92+
# Used in both NonhydrostaticModels and HydrostaticFreeSurfaceModels
93+
function materialize_free_surface end
94+
9295
# Communication - Computation overlap in distributed models
9396
include("interleave_communication_and_computation.jl")
9497

src/Models/NonhydrostaticModels/NonhydrostaticModels.jl

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,42 @@ using Oceananigans.DistributedComputations: DistributedFFTBasedPoissonSolver, Di
1616
using Oceananigans.Grids: XYRegularRG, XZRegularRG, YZRegularRG, XYZRegularRG
1717
using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid
1818
using Oceananigans.Solvers: GridWithFFTSolver, GridWithFourierTridiagonalSolver, ConjugateGradientPoissonSolver
19+
using Oceananigans.Solvers: InhomogeneousFormulation, ZDirection
1920
using Oceananigans.Utils: sum_of_velocities
2021

2122
import Oceananigans: fields, prognostic_fields
2223
import Oceananigans.Advection: cell_advection_timescale
2324
import Oceananigans.TimeSteppers: step_lagrangian_particles!
2425

25-
function nonhydrostatic_pressure_solver(::Distributed, local_grid::XYZRegularRG)
26+
function nonhydrostatic_pressure_solver(::Distributed, local_grid::XYZRegularRG, ::Nothing)
2627
global_grid = reconstruct_global_grid(local_grid)
2728
return DistributedFFTBasedPoissonSolver(global_grid, local_grid)
2829
end
2930

30-
function nonhydrostatic_pressure_solver(::Distributed, local_grid::GridWithFourierTridiagonalSolver)
31+
function nonhydrostatic_pressure_solver(::Distributed, local_grid::GridWithFourierTridiagonalSolver, ::Nothing)
3132
global_grid = reconstruct_global_grid(local_grid)
3233
return DistributedFourierTridiagonalPoissonSolver(global_grid, local_grid)
3334
end
3435

35-
nonhydrostatic_pressure_solver(arch, grid::XYZRegularRG) = FFTBasedPoissonSolver(grid)
36-
nonhydrostatic_pressure_solver(arch, grid::GridWithFourierTridiagonalSolver) =
37-
FourierTridiagonalPoissonSolver(grid)
36+
nonhydrostatic_pressure_solver(arch, grid::XYZRegularRG, ::Nothing) = FFTBasedPoissonSolver(grid)
3837

39-
# fallback
40-
nonhydrostatic_pressure_solver(arch, grid) = ConjugateGradientPoissonSolver(grid)
38+
function nonhydrostatic_pressure_solver(arch, grid::GridWithFourierTridiagonalSolver, ::Nothing)
39+
return FourierTridiagonalPoissonSolver(grid)
40+
end
41+
42+
function nonhydrostatic_pressure_solver(arch, grid::Union{GridWithFourierTridiagonalSolver, XYZRegularRG}, free_surface)
43+
tridiagonal_formulation = InhomogeneousFormulation(ZDirection())
44+
return FourierTridiagonalPoissonSolver(grid; tridiagonal_formulation)
45+
end
4146

42-
const IBGWithFFTSolver = ImmersedBoundaryGrid{<:Any, <:Any, <:Any, <:Any, <:GridWithFFTSolver}
47+
# fallback
48+
nonhydrostatic_pressure_solver(arch, grid, ::Nothing) = ConjugateGradientPoissonSolver(grid)
4349

44-
function nonhydrostatic_pressure_solver(arch, ibg::IBGWithFFTSolver)
50+
const IBGWithFFT = ImmersedBoundaryGrid{<:Any, <:Any, <:Any, <:Any, <:GridWithFFTSolver}
51+
nonhydrostatic_pressure_solver(arch, ibg::IBGWithFFT, ::Nothing) = naive_solver_with_warning(arch, ibg, nothing)
52+
nonhydrostatic_pressure_solver(arch, ibg::IBGWithFFT, fs) = naive_solver_with_warning(arch, ibg, fs)
53+
54+
function naive_solver_with_warning(arch, ibg, free_surface)
4555
msg = """The FFT-based pressure_solver for NonhydrostaticModels on ImmersedBoundaryGrid
4656
is approximate and will probably produce velocity fields that are divergent
4757
adjacent to the immersed boundary. An experimental but improved pressure_solver
@@ -54,10 +64,11 @@ function nonhydrostatic_pressure_solver(arch, ibg::IBGWithFFTSolver)
5464
"""
5565
@warn msg
5666

57-
return nonhydrostatic_pressure_solver(arch, ibg.underlying_grid)
67+
return nonhydrostatic_pressure_solver(arch, ibg.underlying_grid, free_surface)
5868
end
5969

60-
nonhydrostatic_pressure_solver(grid) = nonhydrostatic_pressure_solver(architecture(grid), grid)
70+
71+
nonhydrostatic_pressure_solver(grid, free_surface) = nonhydrostatic_pressure_solver(architecture(grid), grid, free_surface)
6172

6273
#####
6374
##### NonhydrostaticModel definition

src/Models/NonhydrostaticModels/nonhydrostatic_model.jl

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ using Oceananigans.Architectures: AbstractArchitecture
44
using Oceananigans.DistributedComputations: Distributed
55
using Oceananigans.Advection: Centered, adapt_advection_order
66
using Oceananigans.BuoyancyFormulations: validate_buoyancy, regularize_buoyancy, SeawaterBuoyancy
7+
using Oceananigans.BoundaryConditions: MixedBoundaryCondition
78
using Oceananigans.Biogeochemistry: validate_biogeochemistry, AbstractBiogeochemistry, biogeochemical_auxiliary_fields
89
using Oceananigans.BoundaryConditions: regularize_field_boundary_conditions
910
using Oceananigans.Fields: Field, tracernames, VelocityFields, TracerFields, CenterField
1011
using Oceananigans.Forcings: model_forcing
1112
using Oceananigans.Grids: inflate_halo_size, with_halo, architecture
1213
using Oceananigans.ImmersedBoundaries: ImmersedBoundaryGrid
13-
using Oceananigans.Models: AbstractModel, extract_boundary_conditions
14+
using Oceananigans.Models: AbstractModel, extract_boundary_conditions, materialize_free_surface
1415
using Oceananigans.Solvers: FFTBasedPoissonSolver
1516
using Oceananigans.TimeSteppers: Clock, TimeStepper, update_state!, AbstractLagrangianParticles
1617
using Oceananigans.TurbulenceClosures: validate_closure, with_tracers, build_diffusivity_fields, time_discretization, implicit_diffusion_solver
@@ -29,7 +30,7 @@ const BFOrNamedTuple = Union{BackgroundFields, NamedTuple}
2930
# but for now we use it only for hydrostatic pressure anomalies for now.
3031
struct DefaultHydrostaticPressureAnomaly end
3132

32-
mutable struct NonhydrostaticModel{TS, E, A<:AbstractArchitecture, G, T, B, R, SD, U, C, Φ, F,
33+
mutable struct NonhydrostaticModel{TS, E, A<:AbstractArchitecture, G, T, B, R, SD, U, C, Φ, F, FS,
3334
V, S, K, BG, P, BGC, AF, BM} <: AbstractModel{TS, A}
3435

3536
architecture :: A # Computer `Architecture` on which `Model` is run
@@ -41,6 +42,7 @@ mutable struct NonhydrostaticModel{TS, E, A<:AbstractArchitecture, G, T, B, R, S
4142
stokes_drift :: SD # Set of parameters for surfaces waves via the Craik-Leibovich approximation
4243
forcing :: F # Container for forcing functions defined by the user
4344
closure :: E # Diffusive 'turbulence closure' for all model fields
45+
free_surface :: FS # Linearized free surface representation (Nothing for rigid lid)
4446
background_fields :: BG # Background velocity and tracer fields
4547
particles :: P # Particle set for Lagrangian tracking
4648
biogeochemistry :: BGC # Biogeochemistry for Oceananigans tracers
@@ -120,6 +122,7 @@ function NonhydrostaticModel(; grid,
120122
stokes_drift = nothing,
121123
forcing::NamedTuple = NamedTuple(),
122124
closure = nothing,
125+
free_surface = nothing,
123126
boundary_conditions::NamedTuple = NamedTuple(),
124127
tracers = (),
125128
timestepper = :RungeKutta3,
@@ -128,7 +131,7 @@ function NonhydrostaticModel(; grid,
128131
biogeochemistry::AbstractBGCOrNothing = nothing,
129132
velocities = nothing,
130133
hydrostatic_pressure_anomaly = DefaultHydrostaticPressureAnomaly(),
131-
nonhydrostatic_pressure = CenterField(grid),
134+
nonhydrostatic_pressure = nothing,
132135
diffusivity_fields = nothing,
133136
pressure_solver = nothing,
134137
auxiliary_fields = NamedTuple())
@@ -137,6 +140,20 @@ function NonhydrostaticModel(; grid,
137140

138141
tracers = tupleit(tracers) # supports tracers=:c keyword argument (for example)
139142

143+
if isnothing(nonhydrostatic_pressure)
144+
if isnothing(free_surface)
145+
nonhydrostatic_pressure = CenterField(grid)
146+
else
147+
# elseif free_surface isa ImplicitFreeSurface
148+
# Use a MixedBoundaryCondition for the top bc for pressure
149+
coefficient = Ref(zero(grid))
150+
combination = Field{Center, Center, Nothing}(grid)
151+
top_bc = MixedBoundaryCondition(coefficient, combination)
152+
pressure_bcs = FieldBoundaryConditions(grid, (Center(), Center(), Center()), top=top_bc)
153+
nonhydrostatic_pressure = CenterField(grid; boundary_conditions=pressure_bcs)
154+
end
155+
end
156+
140157
# Validate pressure fields
141158
nonhydrostatic_pressure isa Field{Center, Center, Center} ||
142159
throw(ArgumentError("nonhydrostatic_pressure must be CenterField(grid)."))
@@ -196,25 +213,43 @@ function NonhydrostaticModel(; grid,
196213

197214
# Next, we form a list of default boundary conditions:
198215
field_names = (:u, :v, :w, tracernames(tracers)..., keys(auxiliary_fields)...)
199-
default_boundary_conditions = NamedTuple{field_names}(FieldBoundaryConditions()
200-
for name in field_names)
216+
default_boundary_conditions = NamedTuple{field_names}(FieldBoundaryConditions() for name in field_names)
201217

202218
# Finally, we merge specified, embedded, and default boundary conditions. Specified boundary conditions
203219
# have precedence, followed by embedded, followed by default.
204220
boundary_conditions = merge(default_boundary_conditions, embedded_boundary_conditions, boundary_conditions)
221+
222+
if !isnothing(free_surface) # replace top boundary condition for `w` with `nothing`
223+
w_bcs = boundary_conditions.w
224+
w_bcs = FieldBoundaryConditions(top = nothing,
225+
bottom = w_bcs.bottom,
226+
east = w_bcs.east,
227+
west = w_bcs.west,
228+
south = w_bcs.south,
229+
north = w_bcs.north,
230+
immersed = w_bcs.immersed)
231+
232+
boundary_conditions = merge(boundary_conditions, (; w = w_bcs))
233+
end
234+
205235
boundary_conditions = regularize_field_boundary_conditions(boundary_conditions, grid, field_names)
206236

207237
# Ensure `closure` describes all tracers
208238
closure = with_tracers(tracernames(tracers), closure)
209239

240+
# TODO: limit free surface to `nothing` (rigid lid) or ImplicitFreeSurface
241+
if !isnothing(free_surface)
242+
free_surface = materialize_free_surface(free_surface, velocities, grid)
243+
end
244+
210245
# Either check grid-correctness, or construct tuples of fields
211246
velocities = VelocityFields(velocities, grid, boundary_conditions)
212247
tracers = TracerFields(tracers, grid, boundary_conditions)
213248
pressures = (pNHS=nonhydrostatic_pressure, pHY′=hydrostatic_pressure_anomaly)
214249
diffusivity_fields = build_diffusivity_fields(diffusivity_fields, grid, clock, tracernames(tracers), boundary_conditions, closure)
215250

216251
if isnothing(pressure_solver)
217-
pressure_solver = nonhydrostatic_pressure_solver(grid)
252+
pressure_solver = nonhydrostatic_pressure_solver(grid, free_surface)
218253
end
219254

220255
# Materialize background fields
@@ -224,7 +259,7 @@ function NonhydrostaticModel(; grid,
224259

225260
# Instantiate timestepper if not already instantiated
226261
implicit_solver = implicit_diffusion_solver(time_discretization(closure), grid)
227-
timestepper = TimeStepper(timestepper, grid, prognostic_fields; implicit_solver=implicit_solver)
262+
timestepper = TimeStepper(timestepper, grid, prognostic_fields; implicit_solver)
228263

229264
# Regularize forcing for model tracer and velocity fields.
230265
forcing = model_forcing(model_fields; forcing...)
@@ -235,7 +270,7 @@ function NonhydrostaticModel(; grid,
235270
!isnothing(particles) && arch isa Distributed && error("LagrangianParticles are not supported on Distributed architectures.")
236271

237272
model = NonhydrostaticModel(arch, grid, clock, advection, buoyancy, coriolis, stokes_drift,
238-
forcing, closure, background_fields, particles, biogeochemistry, velocities, tracers,
273+
forcing, closure, free_surface, background_fields, particles, biogeochemistry, velocities, tracers,
239274
pressures, diffusivity_fields, timestepper, pressure_solver, auxiliary_fields, boundary_mass_fluxes)
240275

241276
update_state!(model; compute_tendencies = false)

src/Models/NonhydrostaticModels/pressure_correction.jl

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,53 @@ function compute_pressure_correction!(model::NonhydrostaticModel, Δt)
1010
# Mask immersed velocities
1111
foreach(mask_immersed_field!, model.velocities)
1212
fill_halo_regions!(model.velocities, model.clock, fields(model))
13-
1413
enforce_open_boundary_mass_conservation!(model, model.boundary_mass_fluxes)
1514

16-
solve_for_pressure!(model.pressures.pNHS, model.pressure_solver, Δt, model.velocities)
17-
fill_halo_regions!(model.pressures.pNHS)
15+
p_Δt = model.pressures.pNHS
16+
solve_for_pressure!(p_Δt, model.pressure_solver, model.free_surface, model.velocities, Δt)
17+
18+
set_top_pressure_boundary_condition!(p_Δt, model.free_surface, model.velocities.w, Δt)
19+
fill_halo_regions!(p_Δt)
20+
21+
return nothing
22+
end
23+
24+
# TODO: make these fallbacks
25+
# Routines for rigid lid
26+
set_top_pressure_boundary_condition!(p_Δt, ::Nothing, w̃, Δt) = nothing
27+
28+
function set_top_pressure_boundary_condition!(p_Δt, free_surface, w̃, Δt)
29+
top_bc = p_Δt.boundary_conditions.top
30+
g = free_surface.gravitational_acceleration
31+
32+
# Set the "coefficient" of the MixedBoundaryCondition
33+
top_bc.condition.coefficient[] = 1 / (g * Δt^2)
1834

35+
# Set the "combination" of the MixedBoundaryCondition
36+
η = free_surface.η
37+
bc_rhs = top_bc.condition.inhomogeneity
38+
grid = p_Δt.grid
39+
arch = grid.architecture
40+
launch!(arch, grid, :xy, _set_top_pressure_boundary_condition!, bc_rhs, grid, w̃, Δt, η, g, p_Δt)
1941
return nothing
2042
end
2143

44+
@kernel function _set_top_pressure_boundary_condition!(bc_rhs, grid, w̃, Δt, η, g, p_Δt)
45+
i, j = @index(Global, NTuple)
46+
Nz = grid.Nz
47+
48+
@inbounds bc_rhs[i, j, 1] = η[i, j, Nz+1] / Δt + w̃[i, j, Nz+1]
49+
50+
# Compute ηⁿ⁺¹
51+
Δzᶠ = Δzᵃᵃᶠ(i, j, Nz+1, grid)
52+
a = 1 / Δzᶠ - 1 / (2g * Δt^2)
53+
b = 1 / Δzᶠ + 1 / (2g * Δt^2)
54+
@inbounds begin
55+
η★ = η[i, j, Nz+1] + Δt * w̃[i, j, Nz+1]
56+
η[i, j, Nz+1] = (p_Δt[i, j, Nz] / Δt + (p_Δt[i, j, Nz] * a / (b * Δt) + η★ / (b * Δt^2))) / 2g
57+
end
58+
end
59+
2260
#####
2361
##### Fractional and time stepping
2462
#####
@@ -36,15 +74,32 @@ Update the predictor velocities u, v, and w with the non-hydrostatic pressure mu
3674
@inbounds U.w[i, j, k] -= ∂zᶜᶜᶠ(i, j, k, grid, pNHSΔt)
3775
end
3876

77+
@kernel function _compute_surface_vertical_velocity!(w, grid, pNHSΔt)
78+
i, j = @index(Global, NTuple)
79+
k = grid.Nz + 1
80+
@inbounds w[i, j, k] -= ∂zᶜᶜᶠ(i, j, k, grid, pNHSΔt)
81+
end
82+
3983
"Update the solution variables (velocities and tracers)."
4084
function make_pressure_correction!(model::NonhydrostaticModel, Δt)
4185

42-
launch!(model.architecture, model.grid, :xyz,
86+
grid = model.grid
87+
arch = grid.architecture
88+
89+
launch!(arch, grid, :xyz,
4390
_make_pressure_correction!,
4491
model.velocities,
4592
model.grid,
4693
model.pressures.pNHS)
4794

95+
if !isnothing(model.free_surface)
96+
launch!(arch, grid, :xy,
97+
_compute_surface_vertical_velocity!,
98+
model.velocities.w,
99+
model.grid,
100+
model.pressures.pNHS)
101+
end
102+
48103
ϵ = eps(eltype(model.pressures.pNHS))
49104
Δt⁺ = max(ϵ, Δt)
50105
model.pressures.pNHS ./= Δt⁺

src/Models/NonhydrostaticModels/set_nonhydrostatic_model.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ function set!(model::NonhydrostaticModel; enforce_incompressibility=true, kwargs
3636
ϕ = getproperty(model.velocities, fldname)
3737
elseif fldname propertynames(model.tracers)
3838
ϕ = getproperty(model.tracers, fldname)
39+
elseif !isnothing(model.free_surface) && fldname propertynames(model.free_surface)
40+
ϕ = getproperty(model.free_surface, fldname)
3941
else
4042
throw(ArgumentError("name $fldname not found in model.velocities or model.tracers."))
4143
end

0 commit comments

Comments
 (0)