-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add finite depth functionality to SurfaceQG
#375
base: main
Are you sure you want to change the base?
Changes from 11 commits
b494935
d850f33
22e2bd4
e34c677
b061230
ee89d90
61f965c
166e3a9
32d3876
7de8a99
6da9ee1
974d139
a833b5e
1648288
9b00fe3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
Problem, | ||
set_b!, | ||
updatevars!, | ||
streamfunctionfromb!, | ||
|
||
kinetic_energy, | ||
buoyancy_variance, | ||
|
@@ -28,6 +29,7 @@ | |
Lx = 2π, | ||
ny = nx, | ||
Ly = Lx, | ||
H = Inf, | ||
ν = 0, | ||
nν = 1, | ||
dt = 0.01, | ||
|
@@ -49,6 +51,7 @@ | |
- `ny`: Number of grid points in ``y``-domain. | ||
- `Lx`: Extent of the ``x``-domain. | ||
- `Ly`: Extent of the ``y``-domain. | ||
- `H`: Layer depth, set Inf for standard SQG. | ||
- `ν`: Small-scale (hyper)-viscosity coefficient. | ||
- `nν`: (Hyper)-viscosity order, `nν```≥ 1``. | ||
- `dt`: Time-step. | ||
|
@@ -64,6 +67,7 @@ | |
ny = nx, | ||
Lx = 2π, | ||
Ly = Lx, | ||
H = Inf, | ||
# Hyper-viscosity parameters | ||
ν = 0, | ||
nν = 1, | ||
|
@@ -78,8 +82,8 @@ | |
|
||
grid = TwoDGrid(dev; nx, Lx, ny, Ly, aliased_fraction, T) | ||
|
||
params = Params{T}(ν, nν, calcF) | ||
|
||
params = Params(T(H), T(ν), nν, calcF, grid) | ||
vars = calcF == nothingfunction ? DecayingVars(grid) : (stochastic ? StochasticForcedVars(grid) : ForcedVars(grid)) | ||
|
||
equation = Equation(params, grid) | ||
|
@@ -92,24 +96,34 @@ | |
# Parameters | ||
# ---------- | ||
|
||
abstract type SurfaceQGParams <: AbstractParams end | ||
|
||
""" | ||
Params{T}(ν, nν, calcF!) | ||
struct Params{T, Atrans}(H, ν, nν, calcF!, DirNeu) <: SurfaceQGParams | ||
|
||
A struct containing the parameters for Surface QG dynamics. Included are: | ||
|
||
$(TYPEDFIELDS) | ||
""" | ||
struct Params{T} <: AbstractParams | ||
struct Params{T, Atrans <: AbstractArray} <: SurfaceQGParams | ||
"layer depth" | ||
H :: T | ||
"buoyancy (hyper)-viscosity coefficient" | ||
ν :: T | ||
ν :: T | ||
"buoyancy (hyper)-viscosity order" | ||
nν :: Int | ||
nν :: Int | ||
"function that calculates the Fourier transform of the forcing, ``F̂``" | ||
calcF! :: Function | ||
calcF! :: Function | ||
"array containing Dirichlet-to-Neumann operator for buoyancy-streamfunction relation" | ||
ψhfrombh :: Atrans | ||
end | ||
|
||
Params(ν, nν) = Params(ν, nν, nothingfunction) | ||
function Params(H, ν, nν, calcF!, grid::AbstractGrid) | ||
ψhfrombh = @. sqrt(grid.invKrsq) * coth(H / sqrt(grid.invKrsq)) | ||
return Params(H, ν, nν, calcF!, ψhfrombh) | ||
end | ||
|
||
Params(ν, nν, grid) = Params(Inf, ν, nν, nothingfunction, grid) | ||
|
||
# --------- | ||
# Equations | ||
|
@@ -119,7 +133,7 @@ | |
Equation(params, grid) | ||
|
||
Return the `equation` for surface QG dynamics with `params` and `grid`. The linear | ||
opeartor ``L`` includes (hyper)-viscosity of order ``n_ν`` with coefficient ``ν``, | ||
operator ``L`` includes (hyper)-viscosity of order ``n_ν`` with coefficient ``ν``, | ||
|
||
```math | ||
L = - ν |𝐤|^{2 n_ν} . | ||
|
@@ -129,10 +143,10 @@ | |
|
||
The nonlinear term is computed via function `calcN!()`. | ||
""" | ||
function Equation(params::Params, grid::AbstractGrid) | ||
function Equation(params::SurfaceQGParams, grid::AbstractGrid) | ||
L = @. - params.ν * grid.Krsq^params.nν | ||
CUDA.@allowscalar L[1, 1] = 0 | ||
|
||
return FourierFlows.Equation(L, calcN!, grid) | ||
end | ||
|
||
|
@@ -199,7 +213,7 @@ | |
|
||
@devzeros Dev T (grid.nx, grid.ny) b u v | ||
@devzeros Dev Complex{T} (grid.nkr, grid.nl) bh uh vh Fh | ||
|
||
return Vars(b, u, v, bh, uh, vh, Fh, nothing) | ||
end | ||
|
||
|
@@ -214,7 +228,7 @@ | |
|
||
@devzeros Dev T (grid.nx, grid.ny) b u v | ||
@devzeros Dev Complex{T} (grid.nkr, grid.nl) bh uh vh Fh prevsol | ||
|
||
return Vars(b, u, v, bh, uh, vh, Fh, prevsol) | ||
end | ||
|
||
|
@@ -235,24 +249,24 @@ | |
""" | ||
function calcN_advection!(N, sol, t, clock, vars, params, grid) | ||
@. vars.bh = sol | ||
@. vars.uh = im * grid.l * sqrt(grid.invKrsq) * sol | ||
@. vars.vh = - im * grid.kr * sqrt(grid.invKrsq) * sol | ||
@. vars.uh = im * grid.l * params.ψhfrombh * sol | ||
@. vars.vh = - im * grid.kr * params.ψhfrombh * sol | ||
|
||
ldiv!(vars.u, grid.rfftplan, vars.uh) | ||
ldiv!(vars.v, grid.rfftplan, vars.vh) | ||
ldiv!(vars.b, grid.rfftplan, vars.bh) | ||
|
||
ub, ubh = vars.u, vars.uh # use vars.u, vars.uh as scratch variables | ||
vb, vbh = vars.v, vars.vh # use vars.v, vars.vh as scratch variables | ||
|
||
@. ub *= vars.b # u*b | ||
@. vb *= vars.b # v*b | ||
|
||
mul!(ubh, grid.rfftplan, ub) # \hat{u*b} | ||
mul!(vbh, grid.rfftplan, vb) # \hat{v*b} | ||
|
||
@. N = - im * grid.kr * ubh - im * grid.l * vbh | ||
|
||
return nothing | ||
end | ||
|
||
|
@@ -267,11 +281,11 @@ | |
""" | ||
function calcN!(N, sol, t, clock, vars, params, grid) | ||
dealias!(sol, grid) | ||
|
||
calcN_advection!(N, sol, t, clock, vars, params, grid) | ||
|
||
addforcing!(N, sol, t, clock, vars, params, grid) | ||
|
||
return nothing | ||
end | ||
|
||
|
@@ -286,7 +300,7 @@ | |
function addforcing!(N, sol, t, clock, vars::ForcedVars, params, grid) | ||
params.calcF!(vars.Fh, sol, t, clock, vars, params, grid) | ||
@. N += vars.Fh | ||
|
||
return nothing | ||
end | ||
|
||
|
@@ -295,9 +309,9 @@ | |
@. vars.prevsol = sol # sol at previous time-step is needed to compute budgets for stochastic forcing | ||
params.calcF!(vars.Fh, sol, t, clock, vars, params, grid) | ||
end | ||
|
||
@. N += vars.Fh | ||
|
||
return nothing | ||
end | ||
|
||
|
@@ -307,26 +321,28 @@ | |
# ---------------- | ||
|
||
""" | ||
updatevars!(sol, vars, params, grid) | ||
updatevars!(prob) | ||
|
||
Update variables in `vars` with solution in `sol`. | ||
""" | ||
function updatevars!(prob) | ||
vars, grid, sol = prob.vars, prob.grid, prob.sol | ||
|
||
function updatevars!(sol, vars, params, grid) | ||
dealias!(sol, grid) | ||
|
||
@. vars.bh = sol | ||
@. vars.uh = im * grid.l * sqrt(grid.invKrsq) * sol | ||
@. vars.vh = - im * grid.kr * sqrt(grid.invKrsq) * sol | ||
@. vars.uh = im * grid.l * params.ψhfrombh * sol | ||
@. vars.vh = - im * grid.kr * params.ψhfrombh * sol | ||
|
||
ldiv!(vars.b, grid.rfftplan, deepcopy(vars.bh)) | ||
ldiv!(vars.u, grid.rfftplan, deepcopy(vars.uh)) | ||
ldiv!(vars.v, grid.rfftplan, deepcopy(vars.vh)) | ||
|
||
return nothing | ||
end | ||
|
||
updatevars!(prob) = updatevars!(prob.sol, prob.vars, prob.params, prob.grid) | ||
|
||
|
||
""" | ||
set_b!(prob, b) | ||
|
||
|
@@ -335,33 +351,48 @@ | |
function set_b!(prob, b) | ||
mul!(prob.sol, prob.grid.rfftplan, b) | ||
CUDA.@allowscalar prob.sol[1, 1] = 0 # zero domain average | ||
|
||
updatevars!(prob) | ||
|
||
|
||
return nothing | ||
end | ||
|
||
""" | ||
streamfunctionfromb!(ψ, bh, params, grid) | ||
streamfunctionfromb!(ψ, prob) | ||
|
||
Compute the streamfunction `ψ` from the buoyancy `bh` in Fourier space. | ||
(Note that `bh = prob.sol`.) | ||
""" | ||
function streamfunctionfromb!(ψ, bh, params, grid) | ||
ldiv!(ψ, grid.rfftplan, @. params.ψhfrombh * bh) | ||
return nothing | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Delete since it's not used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the method anyway allocates memory, wouldn't it be simpler if it was something like: function get_streamfunction(prob)
ψh = @. prob.params.ψhfrombh * prob.sol
return irfft(ψh, prob.grid.nx)
end There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, looks good, this is what I was getting at with 'non-! version' above. Also, is there a reason (other than the !) why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use ldiv! and mul! to avoid allocations when already the arrays we’ll put the values in are pre-existing. |
||
|
||
streamfunctionfromb!(ψ, prob) = streamfunctionfromb!(ψ, prob.sol, prob.params, prob.grid) | ||
|
||
""" | ||
kinetic_energy(prob) | ||
kinetic_energy(sol, vars, params, grid) | ||
|
||
Return the domain-averaged surface kinetic energy. Since ``u² + v² = |{\\bf ∇} ψ|²``, we get | ||
```math | ||
\\int \\frac1{2} |{\\bf ∇} ψ|² \\frac{𝖽x 𝖽y}{L_x L_y} = \\sum_{𝐤} \\frac1{2} |𝐤|² |ψ̂|² . | ||
``` | ||
In SQG, this is identical to half the domain-averaged surface buoyancy variance. | ||
In SQG with infinite depth, this is identical to half the domain-averaged surface buoyancy variance. | ||
""" | ||
@inline function kinetic_energy(prob) | ||
sol, vars, grid = prob.sol, prob.vars, prob.grid | ||
|
||
@inline function kinetic_energy(sol, vars, params, grid) | ||
ψh = vars.uh # use vars.uh as scratch variable | ||
kinetic_energyh = vars.bh # use vars.bh as scratch variable | ||
@. ψh = sqrt(grid.invKrsq) * sol | ||
|
||
@. ψh = params.ψhfrombh * sol | ||
@. kinetic_energyh = 1 / 2 * grid.Krsq * abs2(ψh) | ||
|
||
return 1 / (grid.Lx * grid.Ly) * parsevalsum(kinetic_energyh, grid) | ||
end | ||
|
||
@inline kinetic_energy(prob) = kinetic_energy(prob.sol, prob.vars, prob.params, prob.grid) | ||
|
||
""" | ||
buoyancy_variance(prob) | ||
|
||
|
@@ -370,11 +401,11 @@ | |
\\int b² \\frac{𝖽x 𝖽y}{L_x L_y} = \\sum_{𝐤} |b̂|² . | ||
``` | ||
In SQG, this is identical to the velocity variance (i.e., twice the domain-averaged kinetic | ||
energy). | ||
energy in the infinite-depth case). | ||
""" | ||
@inline function buoyancy_variance(prob) | ||
sol, grid = prob.sol, prob.grid | ||
|
||
return 1 / (grid.Lx * grid.Ly) * parsevalsum(abs2.(sol), grid) | ||
end | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the end this is not used, right? Delete?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not used. However, it might still be useful as the streamfunction is not available in
vars
and often it's very useful to know. Removing it would be ok as we have the inversion relation saved, though it's perhaps more user-friendly to have a function that calculates it rather than expecting users to do the Fourier transforms themselves?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that "exporting" is really about choosing a stable user-API (not necessarily defining all functions that users need). Its ok to require users to qualify their names or write
using GeophysicalFlows: streamfunctionfromb!
. Not expressing an opinion about exporting or not here, just want to make sure that the goals are clear.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this reply was intended to answer this and the question below in one go so I didn't separate removing the function and removing the export. I'll leave the choice of exporting vs not to you but I think including it (or perhaps a non-! version) would be useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I was confused about @navidcy's suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, sorry if I created confusion.
I think I understand now the point of the method -- thanks @mncrowe
I agree with @glwagner, we can include the method (if we also test it) but we don't necessarily need to export it.