• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Atmospheric-Dynamics-GUF / PinCFlow.jl / 18561934460

16 Oct 2025 12:54PM UTC coverage: 65.961% (-0.03%) from 65.988%
18561934460

push

github

web-flow
Removed `test_case` and made several dispatches more precise (#104)

* Removed `test_case`.

* Made several dispatches more precise.

* Removed `examples.md` (was comitted by accident).

* Renamed `AbstractVariable` to `AbstractPredictand` and removed some of its subtypes (these are direct subtypes of `Any` now).

* Some simplifications and additional comments in initialize_rays!.

* Corrected a few typos.

* Moved the namelist parameter "model" to AtmosphereNamelist and removed SettingNamelist. Reformatted the code.

104 of 119 new or added lines in 44 files covered. (87.39%)

4 existing lines in 4 files now uncovered.

4951 of 7506 relevant lines covered (65.96%)

1423740.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.33
/src/MSGWaM/RayUpdate/propagate_rays!.jl
1
"""
2
```julia
3
propagate_rays!(state::State, dt::AbstractFloat, rkstage::Integer)
4
```
5

6
Integrate the wave-action-density and ray equations by dispatching to a WKB-mode-specific method.
7

8
```julia
9
propagate_rays!(
10
    state::State,
11
    dt::AbstractFloat,
12
    rkstage::Integer,
13
    wkb_mode::NoWKB,
14
)
15
```
16

17
Return for non-WKB configurations.
18

19
```julia
20
propagate_rays!(
21
    state::State,
22
    dt::AbstractFloat,
23
    rkstage::Integer,
24
    wkb_mode::Union{SingleColumn, MultiColumn},
25
)
26
```
27

28
Integrate the wave-action-density and ray equations derived from 1D or 3D transient WKB theory.
29

30
The updates of the RK tendencies for the phase-space position of each ray volume are given by
31

32
```math
33
\\begin{align*}
34
    q_r^x & \\rightarrow \\Delta t \\left(u_{\\mathrm{b}, r} + k_r \\frac{N_r^2 - \\widehat{\\omega}_r^2}{\\widehat{\\omega}_r \\left|\\boldsymbol{k}_r\\right|^2}\\right) + \\alpha_\\mathrm{RK} q_r^x,\\\\
35
    q_r^y & \\rightarrow \\Delta t \\left(v_{\\mathrm{b}, r} + l_r \\frac{N_r^2 - \\widehat{\\omega}_r^2}{\\widehat{\\omega}_r \\left|\\boldsymbol{k}_r\\right|^2}\\right) + \\alpha_\\mathrm{RK} q_r^y,\\\\
36
    q_r^z & \\rightarrow - \\Delta t \\frac{m_r \\left(\\widehat{\\omega}_r^2 - f^2\\right)}{\\widehat{\\omega}_r \\left|\\boldsymbol{k}_r\\right|^2} + \\alpha_\\mathrm{RK} q_r^z,\\\\
37
    q_r^k & \\rightarrow - \\Delta t \\left[k_r \\left(\\frac{\\partial u_\\mathrm{b}}{\\partial x}\\right)_r - l_r \\left(\\frac{\\partial v_\\mathrm{b}}{\\partial x}\\right)_r\\right] + \\alpha_\\mathrm{RK} q_r^k,\\\\
38
    q_r^l & \\rightarrow - \\Delta t \\left[k_r \\left(\\frac{\\partial u_\\mathrm{b}}{\\partial y}\\right)_r - l_r \\left(\\frac{\\partial v_\\mathrm{b}}{\\partial y}\\right)_r\\right] + \\alpha_\\mathrm{RK} q_r^l,\\\\
39
    q_r^m & \\rightarrow - \\Delta t \\left[k_r \\left(\\frac{\\partial u_\\mathrm{b}}{\\partial z}\\right)_r - l_r \\left(\\frac{\\partial v_\\mathrm{b}}{\\partial z}\\right)_r - \\frac{k_r^2 + l_r^2}{2 \\widehat{\\omega}_r \\left|\\boldsymbol{k}_r\\right|^2} \\left(\\frac{\\partial N^2}{\\partial z}\\right)_r\\right] + \\alpha_\\mathrm{RK} q_r^m
40
\\end{align*}
41
```
42

43
and the position update is
44

45
```math
46
\\begin{align*}
47
    x_r & = x_r + \\beta_\\mathrm{RK} q_r^x, & y_r & \\rightarrow y_r + \\beta_\\mathrm{RK} q_r^y, & z_r & \\rightarrow z_r + \\beta_\\mathrm{RK} q_r^z,\\\\
48
    k_r & \\rightarrow k_r + \\beta_\\mathrm{RK} q_r^k, & l_r & \\rightarrow l_r + \\beta_\\mathrm{RK} q_r^l, & m_r & \\rightarrow m_r + \\beta_\\mathrm{RK} q_r^m,
49
\\end{align*}
50
```
51

52
where the subscript ``r`` indicates either a ray-volume property or a mean-flow property interpolated to the ray-volume position, via `interpolate_mean_flow` and `interpolate_stratification`. In addition, MSGWaM updates the ray-volume extents, following
53

54
```math
55
\\begin{align*}
56
    q_r^{\\Delta x} & \\rightarrow \\Delta t \\left(u_{\\mathrm{b}, r, +} - u_{\\mathrm{b}, r, -}\\right) + \\alpha_\\mathrm{RK} q_r^{\\Delta x}, & \\Delta x_r & \\rightarrow \\Delta x_r + \\beta_\\mathrm{RK} q_r^{\\Delta x},\\\\
57
    q_r^{\\Delta y} & \\rightarrow \\Delta t \\left(v_{\\mathrm{b}, r, +} - v_{\\mathrm{b}, r, -}\\right) + \\alpha_\\mathrm{RK} q_r^{\\Delta y}, & \\Delta y_r & \\rightarrow \\Delta y_r + \\beta_\\mathrm{RK} q_r^{\\Delta y},\\\\
58
    q_r^{\\Delta z} & \\rightarrow \\Delta t \\left(c_{\\mathrm{g} z, r, +} - c_{\\mathrm{g} z, r, -}\\right) + \\alpha_\\mathrm{RK} q_r^{\\Delta z}, & \\Delta z_r & \\rightarrow \\Delta z_r + \\beta_\\mathrm{RK} q_r^{\\Delta z},
59
\\end{align*}
60
```
61

62
where ``u_{\\mathrm{b}, r, \\pm}`` is the interpolation of ``u_\\mathrm{b}`` to ``x_{r, \\pm} = x_r \\pm \\Delta x_r / 2`` (from before the position update) and ``v_{\\mathrm{b}, r, \\pm}`` is the equivalent for ``v_\\mathrm{b}`` in ``y``-direction. In the computation of ``c_{\\mathrm{g} z, r, \\pm}``, the intrinsic frequency and squared buoyancy frequency are interpolated to ``z_{r, \\pm} = z_r \\pm \\Delta z_r / 2`` (also from before the position update). The update of the spectral ray-volume extents uses the fact that the surfaces in the ``x``-``k``, ``y``-``l`` and ``z``-``m`` subspaces are conserved. Finally, the update of the phase-space wave-action density reads
63

64
```math
65
\\mathcal{N}_r \\rightarrow \\left(1 + 2 \\alpha_{\\mathrm{R}, r} f_\\mathrm{RK} \\Delta t\\right)^{- 1} \\mathcal{N}_r,
66
```
67

68
where ``\\alpha_{\\mathrm{R}, r}`` is the interpolation of the Rayleigh-damping coefficient to the updated ray-volume position, obtained from `interpolate_sponge`.
69

70
The group velocities that are calculated for the propagation in physical space are also used to determine the maxima needed for the WKB-CFL condition used in the time-step computation.
71

72
```julia
73
propagate_rays!(
74
    state::State,
75
    dt::AbstractFloat,
76
    rkstage::Integer,
77
    wkb_mode::SteadyState,
78
)
79
```
80

81
Update the vertical wavenumber and wave-action density, using steady-state WKB theory.
82

83
In steady-state mode, the ray volumes are stationary in physical space. In mountain-wave simulations, this method first updates the ray volumes in the launch layer by calling `activate_orographic_source!`. Subsequently, it performs a vertical sweep to update all other ray volumes. Therein, the vertical wavenumber is set to
84

85
```math
86
m_r = - \\sigma \\sqrt{\\frac{\\left(k_r^2 + l_r^2\\right) \\left(N_r^2 - \\widehat{\\omega}_r^2\\right)}{\\widehat{\\omega}_r^2 - f^2}},
87
```
88

89
where ``N_r^2`` is the squared buoyancy frequency interpolated to the ray-volume position (with `interpolate_stratification`) and ``\\widehat{\\omega}_r = - k_r u_\\mathrm{b} - l_r v_\\mathrm{b}`` (in the case of mountain waves, for which ``\\omega_r = 0``). The new wave-action-density field is obtained by integrating
90

91
```math
92
\\frac{\\partial}{\\partial z} \\left(c_{\\mathrm{g} z, r} \\mathcal{A}_r\\right) = - 2 \\alpha_{\\mathrm{R}, r} \\mathcal{A}_r - 2 K \\left|\\boldsymbol{k}_r\\right|^2 \\mathcal{A}_r,
93
```
94

95
where ``\\alpha_{\\mathrm{R}, r}`` is the Rayleigh-damping coefficient interpolated to the ray-volume position (using `interpolate_sponge`) and
96

97
```math
98
K = \\left[2 \\sum\\limits_r \\frac{J \\Delta \\widehat{z}}{c_{\\mathrm{g}, z, r}} \\left(m_r \\left|b_{\\mathrm{w}, r}\\right| \\left|\\boldsymbol{k}_r\\right|\\right)^2 f_r\\right]^{- 1} \\max \\left[0, \\sum_r \\left(m_r \\left|b_{\\mathrm{w}, r}\\right|\\right)^2 f_r - \\alpha_\\mathrm{s}^2 N^4\\right]
99
```
100

101
is the turbulent viscosity and diffusivity due to wave breaking (see [`PinCFlow.MSGWaM.RayUpdate.apply_saturation_scheme!`](@ref) for more details). After the first right-hand-side term has been integrated with the implicit step
102

103
```math
104
\\mathcal{A}_r = \\left[1 + \\frac{2 \\alpha_{\\mathrm{R}, r}}{c_{\\mathrm{g} z, r}} \\left(z_r - z_{r, k - 1}\\right)\\right]^{- 1} \\frac{c_{\\mathrm{g} z, r, k - 1}}{c_{\\mathrm{g} z, r}} \\mathcal{A}_{r, k - 1},
105
```
106

107
the second term is integrated with the pseudo-time step ``J \\Delta \\widehat{z} / c_{\\mathrm{g} z, r}``, which corresponds to the substitution ``\\mathcal{A}_r \\rightarrow \\left(1 - 2 J \\Delta \\widehat{z} / c_{\\mathrm{g} z, r} K \\left|\\boldsymbol{k}_r\\right|^2\\right) \\mathcal{A}_r``.
108

109
If the domain is parallelized in the vertical, the integration in vertical subdomains is performed sequentially, with one-way communication providing boundary conditions.
110

111
# Arguments
112

113
  - `state`: Model state.
114

115
  - `dt`: Time step.
116

117
  - `rkstage`: Runge-Kutta-stage index.
118

119
  - `wkb_mode`: Approximations used by MSGWaM.
120

121
# See also
122

123
  - [`PinCFlow.MSGWaM.RayOperations.get_physical_position`](@ref)
124

125
  - [`PinCFlow.MSGWaM.RayOperations.get_spectral_position`](@ref)
126

127
  - [`PinCFlow.MSGWaM.RayOperations.get_physical_extent`](@ref)
128

129
  - [`PinCFlow.MSGWaM.RayOperations.get_spectral_extent`](@ref)
130

131
  - [`PinCFlow.MSGWaM.Interpolation.interpolate_stratification`](@ref)
132

133
  - [`PinCFlow.MSGWaM.Interpolation.interpolate_mean_flow`](@ref)
134

135
  - [`PinCFlow.MSGWaM.Interpolation.interpolate_sponge`](@ref)
136

137
  - [`PinCFlow.MSGWaM.RaySources.activate_orographic_source!`](@ref)
138

139
  - [`PinCFlow.MSGWaM.RayOperations.copy_rays!`](@ref)
140
"""
141
function propagate_rays! end
142

143
function propagate_rays!(state::State, dt::AbstractFloat, rkstage::Integer)
522✔
144
    (; wkb_mode) = state.namelists.wkb
522✔
145
    propagate_rays!(state, dt, rkstage, wkb_mode)
522✔
146
    return
522✔
147
end
148

149
function propagate_rays!(
444✔
150
    state::State,
151
    dt::AbstractFloat,
152
    rkstage::Integer,
153
    wkb_mode::NoWKB,
154
)
155
    return
444✔
156
end
157

158
function propagate_rays!(
78✔
159
    state::State,
160
    dt::AbstractFloat,
161
    rkstage::Integer,
162
    wkb_mode::Union{SingleColumn, MultiColumn},
163
)
164
    (; branch, impact_altitude) = state.namelists.wkb
78✔
165
    (; x_size, y_size) = state.namelists.domain
78✔
166
    (; coriolis_frequency) = state.namelists.atmosphere
78✔
167
    (; lref, tref) = state.constants
78✔
168
    (; nray_max, nray, cgx_max, cgy_max, cgz_max, rays) = state.wkb
78✔
169
    (; dxray, dyray, dzray, dkray, dlray, dmray, ddxray, ddyray, ddzray) =
78✔
170
        state.wkb.increments
171
    (; alphark, betark, stepfrac, nstages) = state.time
78✔
172
    (; lz, zctilde) = state.grid
78✔
173
    (; ko, k0, k1, j0, j1, i0, i1) = state.domain
78✔
174

175
    # Set Coriolis parameter.
176
    fc = coriolis_frequency * tref
78✔
177

178
    kmin = ko == 0 ? k0 - 1 : k0
78✔
179
    kmax = k1
78✔
180

181
    # Initialize WKB increments at the first RK stage.
182
    @ivy if rkstage == 1
338✔
183
        for k in kmin:kmax, j in j0:j1, i in i0:i1
26✔
184
            for r in 1:nray[i, j, k]
28,600✔
185
                dxray[r, i, j, k] = 0.0
4,640✔
186
                dyray[r, i, j, k] = 0.0
4,640✔
187
                dzray[r, i, j, k] = 0.0
4,640✔
188
                dkray[r, i, j, k] = 0.0
4,640✔
189
                dlray[r, i, j, k] = 0.0
4,640✔
190
                dmray[r, i, j, k] = 0.0
4,640✔
191
                ddxray[r, i, j, k] = 0.0
4,640✔
192
                ddyray[r, i, j, k] = 0.0
4,640✔
193
                ddzray[r, i, j, k] = 0.0
4,640✔
194
            end
8,840✔
195
        end
28,860✔
196
    end
197

198
    cgx_max[] = 0.0
78✔
199
    cgy_max[] = 0.0
78✔
200
    @ivy cgz_max[i0:i1, j0:j1, kmin:kmax] .= 0.0
78✔
201

202
    @ivy for k in kmin:kmax, j in j0:j1, i in i0:i1
858✔
203
        nskip = 0
85,800✔
204
        for r in 1:nray[i, j, k]
85,800✔
205
            (xr, yr, zr) = get_physical_position(rays, r, i, j, k)
14,232✔
206
            (kr, lr, mr) = get_spectral_position(rays, r, i, j, k)
14,232✔
207
            (dxr, dyr, dzr) = get_physical_extent(rays, r, i, j, k)
14,232✔
208
            (axk, ayl, azm) = get_surfaces(rays, r, i, j, k)
14,232✔
209

210
            xr1 = xr - dxr / 2
14,232✔
211
            xr2 = xr + dxr / 2
14,232✔
212
            yr1 = yr - dyr / 2
14,232✔
213
            yr2 = yr + dyr / 2
14,232✔
214
            zr1 = zr - dzr / 2
14,232✔
215
            zr2 = zr + dzr / 2
14,232✔
216

217
            khr = sqrt(kr^2 + lr^2)
14,232✔
218

219
            n2r1 = interpolate_stratification(zr1, state, N2())
28,464✔
220
            n2r = interpolate_stratification(zr, state, N2())
28,464✔
221
            n2r2 = interpolate_stratification(zr2, state, N2())
28,464✔
222

223
            omir1 =
14,232✔
224
                branch * sqrt(n2r1 * khr^2 + fc^2 * mr^2) / sqrt(khr^2 + mr^2)
225

226
            omir = branch * sqrt(n2r * khr^2 + fc^2 * mr^2) / sqrt(khr^2 + mr^2)
14,232✔
227

228
            omir2 =
14,232✔
229
                branch * sqrt(n2r2 * khr^2 + fc^2 * mr^2) / sqrt(khr^2 + mr^2)
230

231
            if any((n2r1, n2r, n2r2) .<= 0)
14,232✔
232
                error(
×
233
                    "Error in propagate_rays!: Interpolated stratification is negative!",
234
                )
235
            end
236

237
            if khr <= 0
14,232✔
238
                error(
×
239
                    "Error in propagate_rays!: Horizontal wavenumber is negative!",
240
                )
241
            end
242

243
            # Compute intrinsic zonal group velocity.
244
            if x_size > 1
14,232✔
245
                cgirx = kr * (n2r - omir^2) / (omir * (khr^2 + mr^2))
14,232✔
246
            end
247

248
            # Compute intrinsic meridional group velocity.
249
            if y_size > 1
14,232✔
250
                cgiry = lr * (n2r - omir^2) / (omir * (khr^2 + mr^2))
14,232✔
251
            end
252

253
            # Compute intrinsic vertical group velocities at the vertical edges.
254
            cgirz1 = -mr * (omir1^2 - fc^2) / (omir1 * (khr^2 + mr^2))
14,232✔
255
            cgirz2 = -mr * (omir2^2 - fc^2) / (omir2 * (khr^2 + mr^2))
14,232✔
256

257
            #-------------------------------
258
            #      Change of position
259
            #-------------------------------
260

261
            # Update zonal position.
262

263
            if x_size > 1 && k >= k0 && wkb_mode != SingleColumn()
14,232✔
264
                uxr1 = interpolate_mean_flow(xr1, yr, zr, state, U())
13,920✔
265
                uxr2 = interpolate_mean_flow(xr2, yr, zr, state, U())
13,920✔
266

267
                cgrx1 = cgirx + uxr1
13,920✔
268
                cgrx2 = cgirx + uxr2
13,920✔
269

270
                cgrx = (cgrx1 + cgrx2) / 2
13,920✔
271

272
                f = cgrx
13,920✔
273
                dxray[r, i, j, k] =
13,920✔
274
                    dt * f + alphark[rkstage] * dxray[r, i, j, k]
275
                rays.x[r, i, j, k] += betark[rkstage] * dxray[r, i, j, k]
13,920✔
276

277
                cgx_max[] = max(cgx_max[], abs(cgrx))
13,920✔
278
            end
279

280
            # Update meridional position.
281

282
            if y_size > 1 && k >= k0 && wkb_mode != SingleColumn()
14,232✔
283
                vyr1 = interpolate_mean_flow(xr, yr1, zr, state, V())
13,920✔
284
                vyr2 = interpolate_mean_flow(xr, yr2, zr, state, V())
13,920✔
285

286
                cgry1 = cgiry + vyr1
13,920✔
287
                cgry2 = cgiry + vyr2
13,920✔
288

289
                cgry = (cgry1 + cgry2) / 2
13,920✔
290

291
                f = cgry
13,920✔
292
                dyray[r, i, j, k] =
13,920✔
293
                    dt * f + alphark[rkstage] * dyray[r, i, j, k]
294
                rays.y[r, i, j, k] += betark[rkstage] * dyray[r, i, j, k]
13,920✔
295

296
                cgy_max[] = max(cgy_max[], abs(cgry))
13,920✔
297
            end
298

299
            # Update vertical position.
300

301
            cgrz1 = cgirz1
14,232✔
302
            cgrz2 = cgirz2
14,232✔
303

304
            cgrz = (cgrz1 + cgrz2) / 2
14,232✔
305

306
            f = cgrz
14,232✔
307
            dzray[r, i, j, k] = dt * f + alphark[rkstage] * dzray[r, i, j, k]
14,232✔
308
            rays.z[r, i, j, k] += betark[rkstage] * dzray[r, i, j, k]
14,232✔
309

310
            cgz_max[i, j, k] = max(cgz_max[i, j, k], abs(cgrz))
14,232✔
311

312
            # Refraction is only allowed above impact_altitude / lref.
313

314
            if zr > impact_altitude / lref
14,232✔
315

316
                #-------------------------------
317
                #      Change of wavenumber
318
                #-------------------------------
319

320
                dudxr = interpolate_mean_flow(xr, yr, zr, state, DUDX())
13,700✔
321
                dudyr = interpolate_mean_flow(xr, yr, zr, state, DUDY())
13,700✔
322
                dudzr = interpolate_mean_flow(xr, yr, zr, state, DUDZ())
13,700✔
323

324
                dvdxr = interpolate_mean_flow(xr, yr, zr, state, DVDX())
13,700✔
325
                dvdyr = interpolate_mean_flow(xr, yr, zr, state, DVDY())
13,700✔
326
                dvdzr = interpolate_mean_flow(xr, yr, zr, state, DVDZ())
13,700✔
327

328
                dn2dzr = interpolate_stratification(zr, state, DN2DZ())
13,700✔
329

330
                dkdt = -dudxr * kr - dvdxr * lr
13,700✔
331
                dldt = -dudyr * kr - dvdyr * lr
13,700✔
332
                dmdt =
13,700✔
333
                    -dudzr * kr - dvdzr * lr -
334
                    khr^2 * dn2dzr / (2 * omir * (khr^2 + mr^2))
335

336
                dkray[r, i, j, k] =
13,700✔
337
                    dt * dkdt + alphark[rkstage] * dkray[r, i, j, k]
338
                dlray[r, i, j, k] =
13,700✔
339
                    dt * dldt + alphark[rkstage] * dlray[r, i, j, k]
340
                dmray[r, i, j, k] =
13,700✔
341
                    dt * dmdt + alphark[rkstage] * dmray[r, i, j, k]
342

343
                rays.k[r, i, j, k] += betark[rkstage] * dkray[r, i, j, k]
13,700✔
344
                rays.l[r, i, j, k] += betark[rkstage] * dlray[r, i, j, k]
13,700✔
345
                rays.m[r, i, j, k] += betark[rkstage] * dmray[r, i, j, k]
13,700✔
346

347
                #-------------------------------
348
                #      Change of extents
349
                #-------------------------------
350

351
                # Update extents in x and k.
352

353
                if x_size > 1 && k >= k0 && wkb_mode != SingleColumn()
13,700✔
354
                    ddxdt = cgrx2 - cgrx1
13,700✔
355

356
                    ddxray[r, i, j, k] =
13,700✔
357
                        dt * ddxdt + alphark[rkstage] * ddxray[r, i, j, k]
358

359
                    rays.dxray[r, i, j, k] +=
13,700✔
360
                        betark[rkstage] * ddxray[r, i, j, k]
361

362
                    if rays.dxray[r, i, j, k] <= 0
13,700✔
363
                        rays.dxray[r, i, j, k] *= -1
×
364
                    end
365

366
                    rays.dkray[r, i, j, k] = axk / rays.dxray[r, i, j, k]
13,700✔
367
                end
368

369
                # Update extents in y and l.
370

371
                if y_size > 1 && k >= k0 && wkb_mode != SingleColumn()
13,700✔
372
                    ddydt = cgry2 - cgry1
13,700✔
373

374
                    ddyray[r, i, j, k] =
13,700✔
375
                        dt * ddydt + alphark[rkstage] * ddyray[r, i, j, k]
376

377
                    rays.dyray[r, i, j, k] +=
13,700✔
378
                        betark[rkstage] * ddyray[r, i, j, k]
379

380
                    if rays.dyray[r, i, j, k] <= 0
13,700✔
381
                        rays.dyray[r, i, j, k] *= -1
×
382
                    end
383

384
                    rays.dlray[r, i, j, k] = ayl / rays.dyray[r, i, j, k]
13,700✔
385
                end
386

387
                # Update extents in z and m.
388

389
                ddzdt = cgrz2 - cgrz1
13,700✔
390

391
                ddzray[r, i, j, k] =
13,700✔
392
                    dt * ddzdt + alphark[rkstage] * ddzray[r, i, j, k]
393

394
                rays.dzray[r, i, j, k] += betark[rkstage] * ddzray[r, i, j, k]
13,700✔
395

396
                if rays.dzray[r, i, j, k] <= 0
13,700✔
397
                    rays.dzray[r, i, j, k] *= -1
×
398
                end
399

400
                rays.dmray[r, i, j, k] = azm / rays.dzray[r, i, j, k]
13,700✔
401
            end
402
        end
27,128✔
403

404
        if nskip > 0
85,800✔
405
            println(
×
406
                nskip,
407
                " out of ",
408
                nray[i, j, k],
409
                " ray volumes have been skipped in propagate_rays!!",
410
            )
411
            println("")
×
412
        end
413
    end
×
414

415
    #-------------------------------
416
    #     Change of wave action
417
    #-------------------------------
418

419
    @ivy for k in k0:k1, j in j0:j1, i in i0:i1
780✔
420
        for r in 1:nray[i, j, k]
78,000✔
421
            (xr, yr, zr) = get_physical_position(rays, r, i, j, k)
13,920✔
422
            alphasponge = 2 * interpolate_sponge(xr, yr, zr, state)
13,920✔
423
            betasponge = 1 / (1 + alphasponge * stepfrac[rkstage] * dt)
13,920✔
424
            rays.dens[r, i, j, k] *= betasponge
13,920✔
425
        end
26,816✔
426
    end
×
427

428
    activate_orographic_source!(state)
78✔
429

430
    return
78✔
431
end
432

433
function propagate_rays!(
×
434
    state::State,
435
    dt::AbstractFloat,
436
    rkstage::Integer,
437
    wkb_mode::SteadyState,
438
)
439
    (; x_size, y_size) = state.namelists.domain
×
440
    (; coriolis_frequency) = state.namelists.atmosphere
×
441
    (; branch, use_saturation, saturation_threshold) = state.namelists.wkb
×
442
    (; stepfrac) = state.time
×
443
    (; tref) = state.constants
×
444
    (; comm, zz_size, nzz, nx, ny, ko, k0, k1, j0, j1, i0, i1, down, up) =
×
445
        state.domain
446
    (; dx, dy, dz, zctilde, zc, jac) = state.grid
×
447
    (; rhobar) = state.atmosphere
×
448
    (; u, v) = state.variables.predictands
×
449
    (; nray, rays) = state.wkb
×
450

451
    # Set Coriolis parameter.
452
    fc = coriolis_frequency * tref
×
453

NEW
454
    activate_orographic_source!(state)
×
455

456
    @ivy if ko != 0
×
457
        nray_down = zeros(Int, nx, ny)
×
458
        MPI.Recv!(nray_down, comm; source = down)
×
459
        nray[i0:i1, j0:j1, k0 - 1] .= nray_down
×
460

461
        local_count = maximum(nray[i0:i1, j0:j1, k0 - 1])
×
462
        if local_count > 0
×
463
            fields = fieldcount(Rays)
×
464
            rays_down = zeros(fields, local_count, nx, ny)
×
465
            MPI.Recv!(rays_down, comm; source = down)
×
466
            for field in 1:fields
×
467
                getfield(rays, field)[1:local_count, i0:i1, j0:j1, k0 - 1] .=
×
468
                    rays_down[field, :, :, :]
469
            end
×
470
        end
471
    end
472

473
    # Loop over grid cells.
474
    @ivy for k in k0:k1, j in j0:j1, i in i0:i1
×
475

476
        # Set the ray-volume count.
477
        nray[i, j, k] = nray[i, j, k - 1]
×
478

479
        # Set up the saturation integrals.
480
        integral1 = 0.0
×
481
        integral2 = 0.0
×
482
        m2b2 = 0.0
×
483
        m2b2k2 = 0.0
×
484

485
        # Loop over ray volumes.
486
        for r in 1:nray[i, j, k]
×
487

488
            # Prepare the ray volume.
489
            copy_rays!(rays, r => r, i => i, j => j, k - 1 => k)
×
490

491
            # Skip modes with zero wave-action density.
492
            if rays.dens[r, i, j, k - 1] == 0
×
493
                continue
×
494
            end
495

496
            # Set the vertical position (and extent).
497
            rays.z[r, i, j, k] =
×
498
                zctilde[i, j, k - 1] +
499
                (rays.z[r, i, j, k - 1] - zctilde[i, j, k - 2]) /
500
                jac[i, j, k - 1] * jac[i, j, k]
501
            rays.dzray[r, i, j, k] =
×
502
                rays.dzray[r, i, j, k - 1] * jac[i, j, k] / jac[i, j, k - 1]
503

504
            # Get the horizontal wavenumbers.
505
            (kr, lr, mr) = get_spectral_position(rays, r, i, j, k)
×
506
            khr = sqrt(kr^2 + lr^2)
×
507

508
            # Set the reference level.
509
            kref = ko == 0 ? max(k0, k - 1) : k - 1
×
510

511
            # Compute the vertical group velocity at the level below.
512
            n2r = interpolate_stratification(rays.z[r, i, j, kref], state, N2())
×
513
            omir =
×
514
                -(u[i, j, kref] + u[i - 1, j, kref]) / 2 * kr -
515
                (v[i, j, kref] + v[i, j - 1, kref]) / 2 * lr
516
            if fc < branch * omir < sqrt(n2r)
×
517
                mr = rays.m[r, i, j, kref]
×
518
                cgirz0 = mr * (fc^2 - n2r) * khr^2 / omir / (khr^2 + mr^2)^2
×
519
            else
520
                rays.dens[r, i, j, kref] = 0.0
×
521
                rays.dens[r, i, j, k] = 0.0
×
522
                continue
×
523
            end
524

525
            # Compute the local vertical wavenumber and vertical group velocity.
526
            n2r = interpolate_stratification(rays.z[r, i, j, k], state, N2())
×
527
            omir =
×
528
                -(u[i, j, k] + u[i - 1, j, k]) / 2 * kr -
529
                (v[i, j, k] + v[i, j - 1, k]) / 2 * lr
530
            if fc < branch * omir < sqrt(n2r)
×
531
                mr = -branch * sqrt(khr^2 * (n2r - omir^2) / (omir^2 - fc^2))
×
532
                cgirz = mr * (fc^2 - n2r) * khr^2 / omir / (khr^2 + mr^2)^2
×
533
            else
534
                rays.dens[r, i, j, kref] = 0.0
×
535
                rays.dens[r, i, j, k] = 0.0
×
536
                continue
×
537
            end
538

539
            # Set the local vertical wavenumber.
540
            rays.m[r, i, j, k] = mr
×
541

542
            # Set the local wave action density.
543
            (xr, yr, zr) = get_physical_position(rays, r, i, j, k)
×
544
            alphasponge = 2 * interpolate_sponge(xr, yr, zr, state)
×
545
            rays.dens[r, i, j, k] =
×
546
                1 / (
547
                    1 +
548
                    alphasponge / cgirz *
549
                    (rays.z[r, i, j, k] - rays.z[r, i, j, kref])
550
                ) *
551
                cgirz0 *
552
                rays.dens[r, i, j, kref] / cgirz
553

554
            # Cycle if the saturation scheme is turned off.
555
            if !use_saturation
×
556
                continue
×
557
            end
558

559
            # Get the ray volume extents.
560
            (dxr, dyr, dzr) = get_physical_extent(rays, r, i, j, k)
×
561
            (dkr, dlr, dmr) = get_spectral_extent(rays, r, i, j, k)
×
562

563
            # Compute the phase space factor.
564
            dzi = min(dzr, jac[i, j, k] * dz)
×
565
            facpsp = dzi / jac[i, j, k] / dz * dmr
×
566
            if x_size > 1
×
567
                dxi = min(dxr, dx)
×
568
                facpsp *= dxi / dx * dkr
×
569
            end
570
            if y_size > 1
×
571
                dyi = min(dyr, dy)
×
572
                facpsp *= dyi / dy * dlr
×
573
            end
574

575
            # Compute the saturation integrals.
576
            integral1 = khr^2 * mr^2 / ((khr^2 + mr^2) * omir) * facpsp
×
577
            m2b2 +=
×
578
                2 * n2r^2 / rhobar[i, j, k] * integral1 * rays.dens[r, i, j, k]
579
            integral2 = khr^2 * mr^2 / omir * facpsp
×
580
            m2b2k2 +=
×
581
                2 * n2r^2 / rhobar[i, j, k] *
582
                integral2 *
583
                rays.dens[r, i, j, k] *
584
                jac[i, j, k] *
585
                dz / cgirz
586
        end
×
587

588
        # Compute the diffusion coefficient
589
        n2r = interpolate_stratification(zc[i, j, k], state, N2())
×
590
        if m2b2k2 == 0 || m2b2 < saturation_threshold^2 * n2r^2
×
591
            diffusion = 0.0
×
592
        else
593
            diffusion = (m2b2 - saturation_threshold^2 * n2r^2) / (2 * m2b2k2)
×
594
        end
595

596
        # Reduce the wave action density.
597
        for r in 1:nray[i, j, k]
×
598
            if !use_saturation
×
599
                continue
×
600
            end
601
            if rays.dens[r, i, j, k] == 0
×
602
                continue
×
603
            end
604
            (kr, lr, mr) = get_spectral_position(rays, r, i, j, k)
×
605
            khr = sqrt(kr^2 + lr^2)
×
606
            n2r = interpolate_stratification(rays.z[r, i, j, k], state, N2())
×
607
            omir =
×
608
                -(u[i, j, k] + u[i - 1, j, k]) / 2 * kr -
609
                (v[i, j, k] + v[i, j - 1, k]) / 2 * lr
610
            if fc < branch * omir < sqrt(n2r)
×
611
                cgirz = mr * (fc^2 - n2r) * khr^2 / omir / (khr^2 + mr^2)^2
×
612
            else
613
                rays.dens[r, i, j, kref] = 0.0
×
614
                rays.dens[r, i, j, k] = 0.0
×
615
                continue
×
616
            end
617
            rays.dens[r, i, j, k] *= max(
×
618
                0,
619
                1 - jac[i, j, k] * dz / cgirz * 2 * diffusion * (khr^2 + mr^2),
620
            )
621
        end
×
622
    end
×
623

624
    @ivy if ko + nzz != zz_size
×
625
        nray_up = nray[i0:i1, j0:j1, k1]
×
626
        MPI.Send(nray_up, comm; dest = up)
×
627

628
        local_count = maximum(nray[i0:i1, j0:j1, k1])
×
629
        if local_count > 0
×
630
            fields = fieldcount(Rays)
×
631
            rays_up = zeros(fields, local_count, nx, ny)
×
632
            for field in 1:fields
×
633
                rays_up[field, :, :, :] .=
×
634
                    getfield(rays, field)[1:local_count, i0:i1, j0:j1, k1]
635
            end
×
636
            MPI.Send(rays_up, comm; dest = up)
×
637
        end
638
    end
639

640
    return
×
641
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc