Skip to content

Commit fc01dcd

Browse files
authored
Merge pull request #1 from SpeedyWeather/mk/init
initial StochasticStirring and JetDrag
2 parents c55af7a + f72e99c commit fc01dcd

File tree

10 files changed

+371
-0
lines changed

10 files changed

+371
-0
lines changed

.github/dependabot.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
2+
version: 2
3+
updates:
4+
- package-ecosystem: "github-actions"
5+
directory: "/" # Location of package manifests
6+
schedule:
7+
interval: "weekly"

.github/workflows/CI.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: CI
2+
on:
3+
push:
4+
branches:
5+
- main
6+
tags: ['*']
7+
pull_request:
8+
workflow_dispatch:
9+
concurrency:
10+
# Skip intermediate builds: always.
11+
# Cancel intermediate builds: only if it is a pull request build.
12+
group: ${{ github.workflow }}-${{ github.ref }}
13+
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
14+
jobs:
15+
test:
16+
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
17+
runs-on: ${{ matrix.os }}
18+
timeout-minutes: 60
19+
permissions: # needed to allow julia-actions/cache to proactively delete old caches that it has created
20+
actions: write
21+
contents: read
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
version:
26+
- '1.10'
27+
- '1.8'
28+
- 'nightly'
29+
os:
30+
- ubuntu-latest
31+
arch:
32+
- x64
33+
steps:
34+
- uses: actions/checkout@v4
35+
- uses: julia-actions/setup-julia@v1
36+
with:
37+
version: ${{ matrix.version }}
38+
arch: ${{ matrix.arch }}
39+
- uses: julia-actions/cache@v1
40+
- uses: julia-actions/julia-buildpkg@v1
41+
- uses: julia-actions/julia-runtest@v1

.github/workflows/CompatHelper.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
name: CompatHelper
2+
on:
3+
schedule:
4+
- cron: 0 0 * * *
5+
workflow_dispatch:
6+
jobs:
7+
CompatHelper:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- name: Pkg.add("CompatHelper")
11+
run: julia -e 'using Pkg; Pkg.add("CompatHelper")'
12+
- name: CompatHelper.main()
13+
env:
14+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15+
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
16+
run: julia -e 'using CompatHelper; CompatHelper.main()'

.github/workflows/TagBot.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: TagBot
2+
on:
3+
issue_comment:
4+
types:
5+
- created
6+
workflow_dispatch:
7+
inputs:
8+
lookback:
9+
default: 3
10+
permissions:
11+
actions: read
12+
checks: read
13+
contents: write
14+
deployments: read
15+
issues: read
16+
discussions: read
17+
packages: read
18+
pages: read
19+
pull-requests: read
20+
repository-projects: read
21+
security-events: read
22+
statuses: read
23+
jobs:
24+
TagBot:
25+
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
26+
runs-on: ubuntu-latest
27+
steps:
28+
- uses: JuliaRegistries/TagBot@v1
29+
with:
30+
token: ${{ secrets.GITHUB_TOKEN }}
31+
ssh: ${{ secrets.DOCUMENTER_KEY }}

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.DS_Store
2+
3+
# Files generated by invoking Julia with --code-coverage
4+
*.jl.cov
5+
*.jl.*.cov
6+
7+
# Files generated by invoking Julia with --track-allocation
8+
*.jl.mem
9+
10+
# System-specific files and directories generated by the BinaryProvider and BinDeps packages
11+
# They contain absolute paths specific to the host computer, and so should not be committed
12+
deps/deps.jl
13+
deps/build.log
14+
deps/downloads/
15+
deps/usr/
16+
deps/src/
17+
18+
# Build artifacts for creating documentation generated by the Documenter package
19+
docs/build/
20+
docs/site/
21+
22+
# PythonCall added .CondaPkg to /docs
23+
docs/.CondaPkg
24+
25+
# File generated by Pkg, the package manager, based on a corresponding Project.toml
26+
# It records a fixed state of all packages used by the project. As such, it should not be
27+
# committed for packages, but should be committed for applications that require a static
28+
# environment.
29+
Manifest.toml
30+
31+
# no jupyter notebook checkpoints
32+
.ipynb_*
33+
34+
# no model output folders
35+
run_*/
36+
37+
# no video outputs
38+
*.mp4

Project.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name = "StochasticStir"
2+
uuid = "c3541e7a-ece5-4a49-9931-82ce8f5cb0be"
3+
authors = ["Milan Klöwer <[email protected]> and contributors"]
4+
version = "0.1"
5+
6+
[deps]
7+
SpeedyWeather = "9e226e20-d153-4fed-8a5b-493def4f21a9"
8+
9+
[compat]
10+
julia = "1.8"
11+
SpeedyWeather = "0.8"
12+
13+
[extras]
14+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
15+
16+
[targets]
17+
test = ["Test"]

src/StochasticStir.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module StochasticStir
2+
3+
using SpeedyWeather
4+
5+
include("stochasitc_stirring.jl")
6+
include("jet_drag.jl")
7+
8+
end

src/jet_drag.jl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
export JetDrag
2+
Base.@kwdef struct JetDrag{NF} <: SpeedyWeather.AbstractDrag{NF}
3+
4+
# DIMENSIONS from SpectralGrid
5+
"Spectral resolution as max degree of spherical harmonics"
6+
trunc::Int
7+
8+
# OPTIONS
9+
"Relaxation time scale τ"
10+
time_scale::Second = Day(6)
11+
12+
"Jet strength [m/s]"
13+
u₀::Float64 = 20
14+
15+
"latitude of Gaussian jet [˚N]"
16+
latitude::Float64 = 30
17+
18+
"Width of Gaussian jet [˚]"
19+
width::Float64 = 6
20+
21+
# TO BE INITIALISED
22+
"Relaxation back to reference vorticity"
23+
ζ₀::LowerTriangularMatrix{Complex{NF}} = zeros(LowerTriangularMatrix{Complex{NF}},trunc+2,trunc+1)
24+
end
25+
26+
function JetDrag(SG::SpectralGrid;kwargs...)
27+
return JetDrag{SG.NF}(;SG.trunc,kwargs...)
28+
end
29+
30+
function SpeedyWeather.initialize!( drag::JetDrag,
31+
model::ModelSetup)
32+
33+
(;spectral_grid, geometry) = model
34+
(;Grid,NF,nlat_half) = spectral_grid
35+
u = zeros(Grid{NF},nlat_half)
36+
37+
lat = geometry.latds
38+
39+
for ij in eachindex(u)
40+
u[ij] = drag.u₀ * exp(-(lat[ij]-drag.latitude)^2/(2*drag.width^2))
41+
end
42+
43+
û = SpeedyTransforms.spectral(u,one_more_degree=true)
44+
= zero(û)
45+
SpeedyTransforms.curl!(drag.ζ₀,û,v̂,model.spectral_transform)
46+
return nothing
47+
end
48+
49+
function SpeedyWeather.drag!( diagn::DiagnosticVariablesLayer,
50+
progn::PrognosticVariablesLayer,
51+
drag::JetDrag,
52+
time::DateTime,
53+
model::ModelSetup)
54+
55+
(;vor) = progn
56+
(;vor_tend) = diagn.tendencies
57+
(;ζ₀) = drag
58+
59+
(;radius) = model.spectral_grid
60+
r = radius/drag.time_scale.value
61+
for lm in eachindex(vor,vor_tend,ζ₀)
62+
vor_tend[lm] -= r*(vor[lm] - ζ₀[lm])
63+
end
64+
end

src/stochastic_stirring.jl

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
export StochasticStirring
2+
Base.@kwdef struct StochasticStirring{NF} <: SpeedyWeather.AbstractForcing{NF}
3+
4+
# DIMENSIONS from SpectralGrid
5+
"Spectral resolution as max degree of spherical harmonics"
6+
trunc::Int
7+
8+
"Number of latitude rings, used for latitudinal mask"
9+
nlat::Int
10+
11+
12+
# OPTIONS
13+
"Decorrelation time scale τ [days]"
14+
decorrelation_time::Second = Day(2)
15+
16+
"Stirring strength A [1/s²]"
17+
strength::Float64 = 1e-11
18+
19+
"Stirring latitude [˚N]"
20+
latitude::Float64 = 45
21+
22+
"Stirring width [˚]"
23+
width::Float64 = 24
24+
25+
"Minimum degree of spherical harmonics to force"
26+
lmin::Int = 8
27+
28+
"Maximum degree of spherical harmonics to force"
29+
lmax::Int = 40
30+
31+
"Minimum order of spherical harmonics to force"
32+
mmin::Int = 4
33+
34+
"Maximum order of spherical harmonics to force"
35+
mmax::Int = lmax
36+
37+
# TO BE INITIALISED
38+
"Stochastic stirring term S"
39+
S::LowerTriangularMatrix{Complex{NF}} = zeros(LowerTriangularMatrix{Complex{NF}},trunc+2,trunc+1)
40+
41+
"a = A*sqrt(1 - exp(-2dt/τ)), the noise factor times the stirring strength [1/s²]"
42+
a::Base.RefValue{NF} = Ref(zero(NF))
43+
44+
"b = exp(-dt/τ), the auto-regressive factor [1]"
45+
b::Base.RefValue{NF} = Ref(zero(NF))
46+
47+
"Latitudinal mask, confined to mid-latitude storm track by default [1]"
48+
lat_mask::Vector{NF} = zeros(NF,nlat)
49+
end
50+
51+
function StochasticStirring(SG::SpectralGrid;kwargs...)
52+
(;trunc,Grid,nlat_half) = SG
53+
nlat = RingGrids.get_nlat(Grid,nlat_half)
54+
return StochasticStirring{SG.NF}(;trunc,nlat,kwargs...)
55+
end
56+
57+
function SpeedyWeather.initialize!( forcing::StochasticStirring,
58+
model::ModelSetup)
59+
60+
# precompute forcing strength, scale with radius^2 as is the vorticity equation
61+
(;radius) = model.spectral_grid
62+
A = radius^2 * forcing.strength
63+
64+
# precompute noise and auto-regressive factor, packed in RefValue for mutability
65+
dt = model.time_stepping.Δt_sec
66+
τ = forcing.decorrelation_time.value # in seconds
67+
forcing.a[] = A*sqrt(1 - exp(-2dt/τ))
68+
forcing.b[] = exp(-dt/τ)
69+
70+
# precompute the latitudinal mask
71+
(;Grid,nlat_half) = model.spectral_grid
72+
latd = RingGrids.get_latd(Grid,nlat_half)
73+
74+
for j in eachindex(forcing.lat_mask)
75+
# Gaussian centred at forcing.latitude of width forcing.width
76+
forcing.lat_mask[j] = exp(-(forcing.latitude-latd[j])^2/forcing.width^2*2)
77+
end
78+
79+
return nothing
80+
end
81+
82+
function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer,
83+
progn::PrognosticVariablesLayer,
84+
forcing::StochasticStirring,
85+
time::DateTime,
86+
model::ModelSetup)
87+
SpeedyWeather.forcing!(diagn,forcing,model.spectral_transform)
88+
end
89+
90+
function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer,
91+
forcing::StochasticStirring{NF},
92+
spectral_transform::SpectralTransform) where NF
93+
94+
# noise and auto-regressive factors
95+
a = forcing.a[] # = sqrt(1 - exp(-2dt/τ))
96+
b = forcing.b[] # = exp(-dt/τ)
97+
98+
(;S) = forcing
99+
lmax,mmax = size(S)
100+
@inbounds for m in 1:mmax
101+
for l in m:lmax
102+
if (forcing.mmin <= m <= forcing.mmax) &&
103+
(forcing.lmin <= l <= forcing.lmax)
104+
# Barnes and Hartmann, 2011 Eq. 2
105+
Qi = 2rand(Complex{NF}) - (1 + im) # ~ [-1,1] in complex
106+
S[l,m] = a*Qi + b*S[l,m]
107+
end
108+
end
109+
end
110+
111+
# to grid-point space
112+
S_grid = diagn.dynamics_variables.a_grid
113+
SpeedyTransforms.gridded!(S_grid,S,spectral_transform)
114+
115+
# mask everything but mid-latitudes
116+
RingGrids._scale_lat!(S_grid,forcing.lat_mask)
117+
118+
# back to spectral space
119+
(;vor_tend) = diagn.tendencies
120+
SpeedyTransforms.spectral!(vor_tend,S_grid,spectral_transform)
121+
SpeedyTransforms.spectral_truncation!(vor_tend) # set lmax+1 to zero
122+
123+
return nothing
124+
end

test/runtests.jl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using SpeedyWeather
2+
using StochasticStir
3+
using Test
4+
5+
@testset "StochasticStir.jl" begin
6+
spectral_grid = SpectralGrid(trunc=31,nlev=1)
7+
8+
drag = JetDrag(spectral_grid,time_scale=Day(6))
9+
forcing = StochasticStirring(spectral_grid)
10+
initial_conditions = StartFromRest()
11+
12+
# with barotropic model
13+
model = BarotropicModel(;spectral_grid,initial_conditions,forcing,drag)
14+
simulation = initialize!(model)
15+
16+
run!(simulation,period=Day(5))
17+
@test simulation.model.feedback.nars_detected == false
18+
19+
# with shallow water model
20+
model = ShallowWaterModel(;spectral_grid,initial_conditions,forcing,drag)
21+
simulation = initialize!(model)
22+
23+
run!(simulation,period=Day(5))
24+
@test simulation.model.feedback.nars_detected == false
25+
end

0 commit comments

Comments
 (0)