← back to index

src/user/MOM_wave_interface.F90

portedportable, not yet portedexecuted, not portableexecutable, not hit by this run

1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4
5!> Interface for surface waves
6module MOM_wave_interface
7
8use MOM_data_override, only : data_override_init, data_override
9use MOM_diag_mediator, only : post_data, register_diag_field, safe_alloc_alloc
10use MOM_diag_mediator, only : diag_ctrl
11use MOM_domains, only : pass_var, pass_vector, AGRID
12use MOM_domains, only : To_South, To_West, To_All
13use MOM_error_handler, only : MOM_error, FATAL, WARNING
14use MOM_file_parser, only : get_param, log_version, param_file_type
15use MOM_forcing_type, only : mech_forcing
16use MOM_grid, only : ocean_grid_type
17use MOM_hor_index, only : hor_index_type
18use MOM_io, only : file_exists, get_var_sizes, read_variable
19use MOM_io, only : vardesc, var_desc
20use MOM_safe_alloc, only : safe_alloc_ptr
21use MOM_spatial_means, only : global_area_mean
22use MOM_time_manager, only : time_type, operator(+), operator(/)
23use MOM_unit_scaling, only : unit_scale_type
24use MOM_variables, only : thermo_var_ptrs, surface
25use MOM_verticalgrid, only : verticalGrid_type
26use MOM_restart, only : register_restart_pair, MOM_restart_CS
27
28implicit none ; private
29
30#include <MOM_memory.h>
31
32public MOM_wave_interface_init ! Public interface to fully initialize the wave routines.
33public query_wave_properties ! Public interface to obtain information from the waves control structure.
34public Update_Surface_Waves ! Public interface to update wave information at the
35 ! coupler/driver level.
36public Update_Stokes_Drift ! Public interface to update the Stokes drift profiles
37 ! called in step_mom.
38public get_Langmuir_Number ! Public interface to compute Langmuir number called from
39 ! ePBL or KPP routines.
40public Stokes_PGF ! Public interface to compute Stokes-shear induced pressure gradient force anomaly
41public StokesMixing ! NOT READY - Public interface to add down-Stokes gradient
42 ! momentum mixing (e.g. the approach of Harcourt 2013/2015)
43public CoriolisStokes ! NOT READY - Public interface to add Coriolis-Stokes acceleration
44 ! of the mean currents, needed for comparison with LES. It is
45 ! presently advised against implementing in non-1d settings without
46 ! serious consideration of the full 3d wave-averaged Navier-Stokes
47 ! CL2 effects.
48public Waves_end ! public interface to deallocate and free wave related memory.
49public get_wave_method ! public interface to obtain the wave method string
50public waves_register_restarts ! public interface to register wave restart fields
51
52! A note on unit descriptions in comments: MOM6 uses units that can be rescaled for dimensional
53! consistency testing. These are noted in comments with units like Z, H, L, and T, along with
54! their mks counterparts with notation like "a velocity [Z T-1 ~> m s-1]". If the units
55! vary with the Boussinesq approximation, the Boussinesq variant is given first.
56
57!> Container for all surface wave related parameters
58type, public :: wave_parameters_CS ; private
59
60 ! Main surface wave options and publicly visible variables
61 logical, public :: UseWaves = .false. !< Flag to enable surface gravity wave feature
62 logical, public :: Stokes_VF = .false. !< True if Stokes vortex force is used
63 logical, public :: Passive_Stokes_VF = .false. !< Computes Stokes VF, but doesn't affect dynamics
64 logical, public :: Stokes_PGF = .false. !< True if Stokes shear pressure Gradient force is used
65 logical, public :: robust_Stokes_PGF = .false. !< If true, use expressions to calculate the
66 !! Stokes-induced pressure gradient anomalies that are
67 !! more accurate in the limit of thin layers.
68 logical, public :: Passive_Stokes_PGF = .false. !< Keeps Stokes_PGF on, but doesn't affect dynamics
69 logical, public :: Stokes_DDT = .false. !< Developmental:
70 !! True if Stokes d/dt is used
71 logical, public :: Passive_Stokes_DDT = .false. !< Keeps Stokes_DDT on, but doesn't affect dynamics
72 logical :: Homogenize_Surfbands !< True to homogenize surface band Stokes drift in the horizontal
73
74 real, allocatable, dimension(:,:,:), public :: &
75 Us_x !< 3d zonal Stokes drift profile [L T-1 ~> m s-1]
76 !! Horizontal -> U points
77 !! Vertical -> Mid-points
78 real, allocatable, dimension(:,:,:), public :: &
79 Us_y !< 3d meridional Stokes drift profile [L T-1 ~> m s-1]
80 !! Horizontal -> V points
81 !! Vertical -> Mid-points
82 real, allocatable, dimension(:,:,:), public :: &
83 ddt_Us_x !< 3d time tendency of zonal Stokes drift profile [L T-2 ~> m s-2]
84 !! Horizontal -> U points
85 !! Vertical -> Mid-points
86 real, allocatable, dimension(:,:,:), public :: &
87 ddt_Us_y !< 3d time tendency of meridional Stokes drift profile [L T-2 ~> m s-2]
88 !! Horizontal -> V points
89 !! Vertical -> Mid-points
90 real, allocatable, dimension(:,:,:), public :: &
91 Us_x_from_ddt !< Check of 3d zonal Stokes drift profile [L T-1 ~> m s-1]
92 !! Horizontal -> U points
93 !! Vertical -> Mid-points
94 real, allocatable, dimension(:,:,:), public :: &
95 Us_y_from_ddt !< Check of 3d meridional Stokes drift profile [L T-1 ~> m s-1]
96 !! Horizontal -> V points
97 !! Vertical -> Mid-points
98 real, allocatable, dimension(:,:,:), public :: &
99 Us_x_prev !< 3d zonal Stokes drift profile, previous dynamics call [L T-1 ~> m s-1]
100 !! Horizontal -> U points
101 !! Vertical -> Mid-points
102 real, allocatable, dimension(:,:,:), public :: &
103 Us_y_prev !< 3d meridional Stokes drift profile, previous dynamics call [L T-1 ~> m s-1]
104 !! Horizontal -> V points
105 !! Vertical -> Mid-points
106 real, allocatable, dimension(:,:,:), public :: &
107 KvS !< Viscosity for Stokes Drift shear [H Z T-1 ~> m2 s-1 or Pa s]
108 real, allocatable, dimension(:), public :: &
109 WaveNum_Cen !< Wavenumber bands for read/coupled [Z-1 ~> m-1]
110 real, allocatable, dimension(:,:,:), public :: &
111 UStk_Hb !< Surface Stokes Drift spectrum (zonal) [L T-1 ~> m s-1]
112 !! Horizontal -> H-points
113 !! 3rd dimension -> Freq/Wavenumber
114 real, allocatable, dimension(:,:,:), public :: &
115 VStk_Hb !< Surface Stokes Drift spectrum (meridional) [L T-1 ~> m s-1]
116 !! Horizontal -> H-points
117 !! 3rd dimension -> Freq/Wavenumber
118 real, allocatable, dimension(:,:), public :: &
119 Omega_w2x !< wind direction ccw from model x- axis [nondim radians]
120 integer, public :: NumBands = 0 !< Number of wavenumber/frequency partitions
121 !! Must match the number of bands provided
122 !! via either coupling or file.
123
124 ! The remainder of this control structure is private
125 integer :: WaveMethod = -99 !< Options for including wave information
126 !! Valid (tested) choices are:
127 !! 0 - Test Profile
128 !! 1 - Surface Stokes Drift Bands
129 !! 2 - DHH85
130 !! 3 - LF17
131 !! -99 - No waves computed, but empirical Langmuir number used.
132 logical :: LagrangianMixing !< This feature is in development and not ready
133 !! True if Stokes drift is present and mixing
134 !! should be applied to Lagrangian current
135 !! (mean current + Stokes drift).
136 !! See Reichl et al., 2016 KPP-LT approach
137 logical :: StokesMixing !< This feature is in development and not ready.
138 !! True if vertical mixing of momentum
139 !! should be applied directly to Stokes current
140 !! (with separate mixing parameter for Eulerian
141 !! mixing contribution).
142 !! See Harcourt 2013, 2015 Second-Moment approach
143 logical :: CoriolisStokes !< This feature is in development and not ready.
144 ! True if Coriolis-Stokes acceleration should be applied.
145 real :: Stokes_min_thick_avg !< A layer thickness below which the cell-center Stokes drift is
146 !! used instead of the cell average [Z ~> m]. This is only used if
147 !! WAVE_INTERFACE_ANSWER_DATE < 20230101.
148 integer :: answer_date !< The vintage of the order of arithmetic and expressions in the
149 !! surface wave calculations. Values below 20230101 recover the
150 !! answers from the end of 2022, while higher values use updated
151 !! and more robust forms of the same expressions.
152
153 ! Options if WaveMethod is Surface Stokes Drift Bands (1)
154 integer :: PartitionMode !< Method for partition mode (meant to check input)
155 !! 0 - wavenumbers
156 !! 1 - frequencies
157 integer :: DataSource !< Integer that specifies where the model Looks for data
158 !! Valid choices are:
159 !! 1 - FMS DataOverride Routine
160 !! 2 - Reserved For Coupler
161 !! 3 - User input (fixed values, useful for 1d testing)
162
163 ! Options if using FMS DataOverride Routine
164 character(len=40) :: SurfBandFileName !< Filename if using DataOverride
165 real :: land_speed !< A large Stokes velocity that can be used to indicate land values in
166 !! a data override file [L T-1 ~> m s-1]. Stokes drift components larger
167 !! than this are set to zero in data override calls for the Stokes drift.
168 logical :: DataOver_initialized !< Flag for DataOverride Initialization
169
170 ! Options for computing Langmuir number
171 real :: LA_FracHBL !< Fraction of OSBL for averaging Langmuir number [nondim]
172 real :: LA_HBL_min !< Minimum boundary layer depth for averaging Langmuir number [Z ~> m]
173 logical :: LA_Misalignment = .false. !< Flag to use misalignment in Langmuir number
174 logical :: LA_misalign_bug = .false. !< Flag to use code with a sign error when calculating the
175 !! misalignment between the shear and waves in the Langmuir number calculation.
176 real :: g_Earth !< The gravitational acceleration, equivalent to GV%g_Earth but with
177 !! different dimensional rescaling appropriate for deep-water gravity
178 !! waves [Z T-2 ~> m s-2]
179 real :: I_g_Earth !< The inverse of the gravitational acceleration, with dimensional rescaling
180 !! appropriate for deep-water gravity waves [T2 Z-1 ~> s2 m-1]
181 ! Surface Wave Dependent 1d/2d/3d vars
182 real, allocatable, dimension(:) :: &
183 Freq_Cen !< Central frequency for wave bands, including a factor of 2*pi [T-1 ~> s-1]
184 real, allocatable, dimension(:) :: &
185 PrescribedSurfStkX !< Surface Stokes drift if prescribed [L T-1 ~> m s-1]
186 real, allocatable, dimension(:) :: &
187 PrescribedSurfStkY !< Surface Stokes drift if prescribed [L T-1 ~> m s-1]
188 real, allocatable, dimension(:,:) :: &
189 La_Turb !< Aligned Turbulent Langmuir number [nondim]
190 !! Horizontal -> H points
191 real, allocatable, dimension(:,:) :: &
192 US0_x !< Surface Stokes Drift (zonal) [L T-1 ~> m s-1]
193 !! Horizontal -> U points
194 real, allocatable, dimension(:,:) :: &
195 US0_y !< Surface Stokes Drift (meridional) [L T-1 ~> m s-1]
196 !! Horizontal -> V points
197 real, allocatable, dimension(:,:,:) :: &
198 STKx0 !< Stokes Drift spectrum (zonal) [L T-1 ~> m s-1]
199 !! Horizontal -> U points
200 !! 3rd dimension -> Freq/Wavenumber
201 real, allocatable, dimension(:,:,:) :: &
202 STKy0 !< Stokes Drift spectrum (meridional) [L T-1 ~> m s-1]
203 !! Horizontal -> V points
204 !! 3rd dimension -> Freq/Wavenumber
205
206 real :: La_min !< An arbitrary lower-bound on the Langmuir number [nondim].
207 !! Langmuir number is sqrt(u_star/u_stokes). When both are small
208 !! but u_star is orders of magnitude smaller, the Langmuir number could
209 !! have unintended consequences. Since both are small it can be safely
210 !! capped to avoid such consequences.
211 real :: La_Stk_backgnd !< A small background Stokes velocity used in the denominator of
212 !! some expressions for the Langmuir number [L T-1 ~> m s-1]
213
214 ! Parameters used in estimating the wind speed or wave properties from the friction velocity
215 real :: VonKar = -1.0 !< The von Karman coefficient as used in the MOM_wave_interface module [nondim]
216 real :: rho_air !< A typical density of air at sea level, as used in wave calculations [R ~> kg m-3]
217 real :: nu_air !< The viscosity of air, as used in wave calculations [Z2 T-1 ~> m2 s-1]
218 real :: rho_ocn !< A typical surface density of seawater, as used in wave calculations in
219 !! comparison with the density of air [R ~> kg m-3]. The default is RHO_0.
220 real :: SWH_from_u10sq !< A factor for converting the square of the 10 m wind speed to the
221 !! significant wave height [Z T2 L-2 ~> s2 m-1]
222 real :: Charnock_min !< The minimum value of the Charnock coefficient, which relates the square of
223 !! the air friction velocity divided by the gravitational acceleration to the
224 !! wave roughness length [nondim]
225 real :: Charnock_slope_U10 !< The partial derivative of the Charnock coefficient with the 10 m wind
226 !! speed [T L-1 ~> s m-1]. Note that in eq. 13 of the Edson et al. 2013 describing
227 !! the COARE 3.5 bulk flux algorithm, this slope is given as 0.017. However, 0.0017
228 !! reproduces the curve in their figure 6, so that is the default value used in MOM6.
229 real :: Charnock_intercept !< The intercept of the fit for the Charnock coefficient in the limit of
230 !! no wind [nondim]. Note that this can be negative because CHARNOCK_MIN will keep
231 !! the final value for the Charnock coefficient from being from being negative.
232
233 ! Options used with the test profile
234 real :: TP_STKX0 !< Test profile x-stokes drift amplitude [L T-1 ~> m s-1]
235 real :: TP_STKY0 !< Test profile y-stokes drift amplitude [L T-1 ~> m s-1]
236 real :: TP_WVL !< Test profile wavelength [Z ~> m]
237
238 ! Options for use with the Donelan et al., 1985 (DHH85) spectrum
239 logical :: WaveAgePeakFreq !< Flag to use wave age to determine the peak frequency with DHH85
240 logical :: StaticWaves !< Flag to disable updating DHH85 Stokes drift
241 logical :: DHH85_is_set !< The if the wave properties have been set when WaveMethod = DHH85.
242 real :: WaveAge !< The fixed wave age used with the DHH85 spectrum [nondim]
243 real :: WaveWind !< Wind speed for the DHH85 spectrum [L T-1 ~> m s-1]
244 real :: omega_min !< Minimum wave frequency with the DHH85 spectrum [T-1 ~> s-1]
245 real :: omega_max !< Maximum wave frequency with the DHH85 spectrum [T-1 ~> s-1]
246
247 type(time_type), pointer :: Time !< A pointer to the ocean model's clock.
248 type(diag_ctrl), pointer :: diag !< A structure that is used to regulate the
249 !! timing of diagnostic output.
250
251 !>@{ Diagnostic handles
252 integer, public :: id_PFu_Stokes = -1 , id_PFv_Stokes = -1
253 integer, public :: id_3dstokes_x_from_ddt = -1 , id_3dstokes_y_from_ddt = -1
254 integer :: id_P_deltaStokes_L = -1, id_P_deltaStokes_i = -1
255 integer :: id_surfacestokes_x = -1 , id_surfacestokes_y = -1
256 integer :: id_3dstokes_x = -1 , id_3dstokes_y = -1
257 integer :: id_ddt_3dstokes_x = -1 , id_ddt_3dstokes_y = -1
258 integer :: id_La_turb = -1
259 !>@}
260
261end type wave_parameters_CS
262
263! Switches needed in import_stokes_drift
264!>@{ Enumeration values for the wave method
265integer, parameter :: TESTPROF = 0, SURFBANDS = 1, DHH85 = 2, LF17 = 3, EFACTOR = 4, NULL_WaveMethod = -99
266!>@}
267!>@{ Enumeration values for the wave data source
268integer, parameter :: DATAOVR = 1, COUPLER = 2, INPUT = 3
269!>@}
270
271! Strings for the wave method
272character*(5), parameter :: NULL_STRING = "EMPTY" !< null wave method string
273character*(12), parameter :: TESTPROF_STRING = "TEST_PROFILE" !< test profile string
274character*(13), parameter :: SURFBANDS_STRING = "SURFACE_BANDS" !< surface bands string
275character*(5), parameter :: DHH85_STRING = "DHH85" !< DHH85 wave method string
276character*(4), parameter :: LF17_STRING = "LF17" !< LF17 wave method string
277character*(7), parameter :: EFACTOR_STRING = "EFACTOR" !< EFACTOR (based on vr12-ma) wave method string
278
279contains
280
281!> Initializes parameters related to MOM_wave_interface
2821subroutine MOM_wave_interface_init(time, G, GV, US, param_file, CS, diag)
283 type(time_type), target, intent(in) :: Time !< Model time
284 type(ocean_grid_type), intent(inout) :: G !< Grid structure
285 type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure
286 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
287 type(param_file_type), intent(in) :: param_file !< Input parameter structure
288 type(wave_parameters_CS), pointer :: CS !< Wave parameter control structure
289 type(diag_ctrl), target, intent(inout) :: diag !< Diagnostic Pointer
290
291 ! Local variables
292 character(len=40) :: mdl = "MOM_wave_interface" !< This module's name.
293 ! This include declares and sets the variable "version".
294# include "version_variable.h"
295 character*(13) :: TMPSTRING1, TMPSTRING2
296 character*(12), parameter :: DATAOVR_STRING = "DATAOVERRIDE"
297 character*(7), parameter :: COUPLER_STRING = "COUPLER"
298 character*(5), parameter :: INPUT_STRING = "INPUT"
299 integer :: default_answer_date ! The default setting for the various ANSWER_DATE flags
300 logical :: use_waves
301 logical :: StatisticalWaves
302
303 ! Dummy Check
3041 if (.not. associated(CS)) then
3050 call MOM_error(FATAL, "wave_interface_init called without an associated control structure.")
3061 return
307 endif
308
309 call get_param(param_file, mdl, "USE_WAVES", use_waves, &
3101 "If true, enables surface wave modules.", default=.false.)
311
312 ! Check if using LA_LI2016
313 call get_param(param_file, mdl, "USE_LA_LI2016", StatisticalWaves, &
3141 do_not_log=.true.,default=.false.)
315
3161 if (.not.(use_waves .or. StatisticalWaves)) return
317
3181 CS%UseWaves = use_waves
3191 CS%diag => diag
3201 CS%Time => Time
321
3221 CS%g_Earth = GV%g_Earth_Z_T2
3231 CS%I_g_Earth = 1.0 / CS%g_Earth
324
325 ! Add any initializations needed here
3261 CS%DataOver_initialized = .false.
327
3281 call log_version(param_file, mdl, version)
329
330 call get_param(param_file, mdl, "DEFAULT_ANSWER_DATE", default_answer_date, &
331 "This sets the default value for the various _ANSWER_DATE parameters.", &
3321 default=99991231)
333
334 call get_param(param_file, mdl, "WAVE_INTERFACE_ANSWER_DATE", CS%answer_date, &
335 "The vintage of the order of arithmetic and expressions in the surface wave "//&
336 "calculations. Values below 20230101 recover the answers from the end of 2022, "//&
337 "while higher values use updated and more robust forms of the same expressions:\n"//&
338 "\t < 20230101 - Original answers for wave interface routines\n"//&
339 "\t >= 20230101 - More robust expressions for Update_Stokes_Drift\n"//&
340 "\t >= 20230102 - More robust expressions for get_StokesSL_LiFoxKemper\n"//&
341 "\t >= 20230103 - More robust expressions for ust_2_u10_coare3p5", &
3421 default=default_answer_date, do_not_log=.not.GV%Boussinesq)
3431 if (.not.GV%Boussinesq) CS%answer_date = max(CS%answer_date, 20230701)
344
345 ! Langmuir number Options
346 call get_param(param_file, mdl, "LA_DEPTH_RATIO", CS%LA_FracHBL, &
347 "The depth (normalized by BLD) to average Stokes drift over in "//&
348 "Langmuir number calculation, where La = sqrt(ust/Stokes).", &
3491 units="nondim", default=0.04)
350 call get_param(param_file, mdl, "LA_DEPTH_MIN", CS%LA_HBL_min, &
351 "The minimum depth over which to average the Stokes drift in the Langmuir "//&
3521 "number calculation.", units="m", default=0.1, scale=US%m_to_Z)
353
3541 if (StatisticalWaves) then
3551 CS%WaveMethod = LF17
3561 call set_LF17_wave_params(param_file, mdl, GV, US, CS)
3571 if (.not.use_waves) return
358 else
3590 CS%WaveMethod = NULL_WaveMethod
360 endif
361
362 ! Wave modified physics
363 ! Presently these are all in research mode
364 call get_param(param_file, mdl, "LAGRANGIAN_MIXING", CS%LagrangianMixing, &
365 "Flag to use Lagrangian Mixing of momentum", default=.false., &
3660 do_not_log=.not.use_waves)
3670 if (CS%LagrangianMixing) then
368 ! Force Code Intervention
3690 call MOM_error(FATAL,"Should you be enabling Lagrangian Mixing? Code not ready.")
370 endif
371 call get_param(param_file, mdl, "STOKES_MIXING", CS%StokesMixing, &
372 "Flag to use Stokes Mixing of momentum", default=.false., &
3730 do_not_log=.not.use_waves)
3740 if (CS%StokesMixing) then
375 ! Force Code Intervention
3760 call MOM_error(FATAL, "Should you be enabling Stokes Mixing? Code not ready.")
377 endif
378 call get_param(param_file, mdl, "CORIOLIS_STOKES", CS%CoriolisStokes, &
379 "Flag to use Coriolis Stokes acceleration", default=.false., &
3800 do_not_log=.not.use_waves)
3810 if (CS%CoriolisStokes) then
382 ! Force Code Intervention
3830 call MOM_error(FATAL, "Should you be enabling Coriolis-Stokes? Code not ready.")
384 endif
385
386 call get_param(param_file, mdl, "STOKES_VF", CS%Stokes_VF, &
387 "Flag to use Stokes vortex force", &
3880 default=.false.)
389 call get_param(param_file, mdl, "PASSIVE_STOKES_VF", CS%Passive_Stokes_VF, &
390 "Flag to make Stokes vortex force diagnostic only.", &
3910 default=.false.)
392 call get_param(param_file, mdl, "STOKES_PGF", CS%Stokes_PGF, &
393 "Flag to use Stokes-induced pressure gradient anomaly", &
3940 default=.false.)
395 call get_param(param_file, mdl, "ROBUST_STOKES_PGF", CS%robust_Stokes_PGF, &
396 "If true, use expressions to calculate the Stokes-induced pressure gradient "//&
397 "anomalies that are more accurate in the limit of thin layers.", &
3980 default=.false., do_not_log=.not.CS%Stokes_PGF)
399 !### Change the default for ROBUST_STOKES_PGF to True.
400 call get_param(param_file, mdl, "PASSIVE_STOKES_PGF", CS%Passive_Stokes_PGF, &
401 "Flag to make Stokes-induced pressure gradient anomaly diagnostic only.", &
4020 default=.false.)
403 call get_param(param_file, mdl, "STOKES_DDT", CS%Stokes_DDT, &
404 "Flag to use Stokes d/dt", &
4050 default=.false.)
406 call get_param(param_file, mdl, "PASSIVE_STOKES_DDT", CS%Passive_Stokes_DDT, &
407 "Flag to make Stokes d/dt diagnostic only", &
4080 default=.false.)
409
410 ! Get Wave Method and write to integer WaveMethod
411 call get_param(param_file,mdl,"WAVE_METHOD",TMPSTRING1, &
412 "Choice of wave method, valid options include: \n"// &
413 " TEST_PROFILE - Prescribed from surface Stokes drift \n"// &
414 " and a decay wavelength.\n"// &
415 " SURFACE_BANDS - Computed from multiple surface values \n"// &
416 " and decay wavelengths.\n"// &
417 " DHH85 - Uses Donelan et al. 1985 empirical \n"// &
418 " wave spectrum with prescribed values. \n"// &
419 " LF17 - Infers Stokes drift profile from wind \n"// &
420 " speed following Li and Fox-Kemper 2017.\n"// &
421 " EFACTOR - Applies an enhancement factor to the KPP\n"//&
422 " turbulent velocity scale received \n"// &
423 " directly from WW3 and is based on the \n"// &
424 " surface layer and projected Langmuir \n"// &
425 " number (Li 2016)\n", &
4260 default=NULL_STRING)
4270 select case (TRIM(TMPSTRING1))
428 case (NULL_STRING)! No Waves
429 call MOM_error(FATAL, "wave_interface_init called with no specified "//&
4300 "WAVE_METHOD.")
431 case (TESTPROF_STRING)! Test Profile
4320 CS%WaveMethod = TESTPROF
433 call get_param(param_file, mdl, "TP_STKX_SURF", CS%TP_STKX0, &
434 'Surface Stokes (x) for test profile', &
4350 units='m/s', default=0.1, scale=US%m_s_to_L_T)
436 call get_param(param_file, mdl, "TP_STKY_SURF", CS%TP_STKY0, &
437 'Surface Stokes (y) for test profile', &
4380 units='m/s', default=0.0, scale=US%m_s_to_L_T)
439 call get_param(param_file,mdl, "TP_WVL", CS%TP_WVL, &
440 'Wavelength for test profile', &
4410 units='m', default=50.0, scale=US%m_to_Z)
442 case (SURFBANDS_STRING)! Surface Stokes Drift Bands
4430 CS%WaveMethod = SURFBANDS
444 call get_param(param_file, mdl, "SURFBAND_MIN_THICK_AVG", CS%Stokes_min_thick_avg, &
445 "A layer thickness below which the cell-center Stokes drift is used instead of "//&
446 "the cell average. This is only used if WAVE_INTERFACE_ANSWER_DATE < 20230101.", &
4470 units="m", default=0.1, scale=US%m_to_Z, do_not_log=(CS%answer_date>=20230101))
448 call get_param(param_file, mdl, "HOMOGENIZE_SURFBANDS", CS%Homogenize_Surfbands, &
449 "A logical which causes the code to horizontally homogenize the surface band "//&
450 "Stokes drift, which is needed in column mode to avoid round-off differences. "//&
451 "At present it only works with DATAOVERRIDE, and is not coded for COUPLER.",&
4520 default=.false.)
453 call get_param(param_file, mdl, "SURFBAND_SOURCE", TMPSTRING2, &
454 "Choice of SURFACE_BANDS data mode, valid options include: \n"//&
455 " DATAOVERRIDE - Read from NetCDF using FMS DataOverride. \n"//&
456 " COUPLER - Look for variables from coupler pass \n"//&
4570 " INPUT - Testing with fixed values.", default=NULL_STRING)
4580 select case (TRIM(TMPSTRING2))
459 case (NULL_STRING)! Default
460 call MOM_error(FATAL, "wave_interface_init called with SURFACE_BANDS "//&
4610 "but no SURFBAND_SOURCE.")
462 case (DATAOVR_STRING)! Using Data Override
4630 CS%DataSource = DATAOVR
464 call get_param(param_file, mdl, "SURFBAND_FILENAME", CS%SurfBandFileName, &
4650 "Filename of surface Stokes drift input band data.", default="StkSpec.nc")
466 call get_param(param_file, mdl, "SURFBAND_OVERRIDE_LAND_SPEED", CS%land_speed, &
467 "A large Stokes velocity that can be used to indicate land values in "//&
468 "a data override file. Stokes drift components larger than this are "//&
469 "set to zero in data override calls for the Stokes drift.", &
4700 units="m s-1", default=10.0, scale=US%m_s_to_L_T)
471 case (COUPLER_STRING)! Reserved for coupling
4720 CS%DataSource = COUPLER
473 ! This is just to make something work, but it needs to be read from the wavemodel.
474 call get_param(param_file, mdl, "STK_BAND_COUPLER",CS%NumBands, &
475 "STK_BAND_COUPLER is the number of Stokes drift bands in the coupler. "//&
476 "This has to be consistent with the number of Stokes drift bands in WW3, "//&
4770 "or the model will fail.", default=1)
4780 allocate( CS%WaveNum_Cen(CS%NumBands), source=0.0 )
4790 allocate( CS%STKx0(G%isdB:G%iedB,G%jsd:G%jed,CS%NumBands), source=0.0 )
4800 allocate( CS%STKy0(G%isd:G%ied,G%jsdB:G%jedB,CS%NumBands), source=0.0 )
4810 allocate( CS%UStk_Hb(G%isc:G%iec,G%jsc:G%jec,CS%NumBands), source=0.0 )
4820 allocate( CS%VStk_Hb(G%isc:G%iec,G%jsc:G%jec,CS%NumBands), source=0.0 )
4830 allocate( CS%Omega_w2x(G%isc:G%iec,G%jsc:G%jec) , source=0.0 )
4840 CS%PartitionMode = 0
485 call get_param(param_file, mdl, "SURFBAND_WAVENUMBERS", CS%WaveNum_Cen, &
486 "Central wavenumbers for surface Stokes drift bands.", &
4870 units='rad/m', default=0.12566, scale=US%Z_to_m)
488 case (INPUT_STRING)! A method to input the Stokes band (globally uniform)
4890 CS%DataSource = INPUT
490 call get_param(param_file, mdl, "SURFBAND_NB", CS%NumBands, &
491 "Prescribe number of wavenumber bands for Stokes drift. "//&
492 "Make sure this is consistnet w/ WAVENUMBERS, STOKES_X, and "//&
4930 "STOKES_Y, there are no safety checks in the code.", default=1)
4940 allocate( CS%WaveNum_Cen(1:CS%NumBands), source=0.0 )
4950 allocate( CS%PrescribedSurfStkX(1:CS%NumBands), source=0.0 )
4960 allocate( CS%PrescribedSurfStkY(1:CS%NumBands), source=0.0 )
4970 allocate( CS%STKx0(G%isdB:G%iedB,G%jsd:G%jed,1:CS%NumBands), source=0.0 )
4980 allocate( CS%STKy0(G%isd:G%ied,G%jsdB:G%jedB,1:CS%NumBands), source=0.0 )
499
5000 CS%PartitionMode = 0
501 call get_param(param_file, mdl, "SURFBAND_WAVENUMBERS", CS%WaveNum_Cen, &
502 "Central wavenumbers for surface Stokes drift bands.", &
5030 units='rad/m', default=0.12566, scale=US%Z_to_m)
504 call get_param(param_file, mdl, "SURFBAND_STOKES_X", CS%PrescribedSurfStkX, &
505 "X-direction surface Stokes drift for bands.", &
5060 units='m/s', default=0.15, scale=US%m_s_to_L_T)
507 call get_param(param_file, mdl, "SURFBAND_STOKES_Y", CS%PrescribedSurfStkY, &
508 "Y-direction surface Stokes drift for bands.", &
5090 units='m/s', default=0.0, scale=US%m_s_to_L_T)
510 case default! No method provided
5110 call MOM_error(FATAL,'Check WAVE_METHOD.')
512 end select
513
514 case (DHH85_STRING) !Donelan et al., 1985 spectrum
5150 CS%WaveMethod = DHH85
516 call MOM_error(WARNING,"DHH85 only ever set-up for uniform cases w/ "//&
5170 "Stokes drift in x-direction.")
518 call get_param(param_file, mdl, "DHH85_AGE_FP", CS%WaveAgePeakFreq, &
5190 "Choose true to use waveage in peak frequency.", default=.false.)
520 call get_param(param_file, mdl, "DHH85_AGE", CS%WaveAge, &
521 "Wave Age for DHH85 spectrum.", &
5220 units='nondim', default=1.2)
523 call get_param(param_file, mdl, "DHH85_WIND", CS%WaveWind, &
524 "Wind speed for DHH85 spectrum.", &
5250 units='m s-1', default=10.0, scale=US%m_s_to_L_T)
526 call get_param(param_file, mdl, "DHH85_MIN_WAVE_FREQ", CS%omega_min, &
527 "Minimum wave frequency for the DHH85 spectrum.", &
5280 units='s-1', default=0.1, scale=US%T_to_s)
529 call get_param(param_file, mdl, "DHH85_MAX_WAVE_FREQ", CS%omega_max, &
530 "Maximum wave frequency for the DHH85 spectrum.", &
5310 units='s-1', default=10.0, scale=US%T_to_s) ! The default is about a 30 cm cutoff wavelength.
532 call get_param(param_file, mdl, "STATIC_DHH85", CS%StaticWaves, &
5330 "Flag to disable updating DHH85 Stokes drift.", default=.false.)
534 case (LF17_STRING) !Li and Fox-Kemper 17 wind-sea Langmuir number
5350 CS%WaveMethod = LF17
5360 call set_LF17_wave_params(param_file, mdl, GV, US, CS)
537 case (EFACTOR_STRING) !Li and Fox-Kemper 16
5380 CS%WaveMethod = EFACTOR
539 case default
5400 call MOM_error(FATAL,'Check WAVE_METHOD.')
541 end select
542
543 ! Langmuir number Options (Note that CS%LA_FracHBL is set above.)
544 call get_param(param_file, mdl, "LA_MISALIGNMENT", CS%LA_Misalignment, &
545 "Flag (logical) if using misalignment between shear and waves in LA", &
5460 default=.false.)
547 call get_param(param_file, mdl, "LA_MISALIGNMENT_BUG", CS%LA_misalign_bug, &
548 "If true, use a code with a sign error when calculating the misalignment between "//&
549 "the shear and waves when LA_MISALIGNMENT is true.", &
5500 default=.false., do_not_log=.not.CS%LA_Misalignment)
551 call get_param(param_file, mdl, "MIN_LANGMUIR", CS%La_min, &
552 "A minimum value for all Langmuir numbers that is not physical, "//&
553 "but is likely only encountered when the wind is very small and "//&
554 "therefore its effects should be mostly benign.", &
5550 units="nondim", default=0.05)
556 call get_param(param_file, mdl, "LANGMUIR_STOKES_BACKGROUND", CS%La_Stk_backgnd, &
557 "A small background Stokes velocity used in the denominator of some "//&
558 "expressions for the Langmuir number.", &
5590 units="m s-1", default=1.0e-10, scale=US%m_s_to_L_T, do_not_log=(CS%WaveMethod==LF17))
560
561 ! Allocate and initialize
562 ! a. Stokes driftProfiles
5630 allocate(CS%Us_x(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0)
5640 allocate(CS%Us_y(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0)
5650 if (CS%Stokes_DDT) then
566 !allocate(CS%Us_x_prev(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0)
567 !allocate(CS%Us_y_prev(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0)
5680 allocate(CS%ddt_Us_x(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0)
5690 allocate(CS%ddt_Us_y(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0)
5700 allocate(CS%Us_x_from_ddt(G%isdB:G%IedB,G%jsd:G%jed,G%ke), source=0.0)
5710 allocate(CS%Us_y_from_ddt(G%isd:G%Ied,G%jsdB:G%jedB,G%ke), source=0.0)
572 endif
573 ! b. Surface Values
5740 allocate(CS%US0_x(G%isdB:G%iedB,G%jsd:G%jed), source=0.0)
5750 allocate(CS%US0_y(G%isd:G%ied,G%jsdB:G%jedB), source=0.0)
576 ! c. Langmuir number
5770 allocate(CS%La_turb(G%isc:G%iec,G%jsc:G%jec), source=0.0)
578 ! d. Viscosity for Stokes drift
5790 if (CS%StokesMixing) then
5800 allocate(CS%KvS(G%isd:G%Ied,G%jsd:G%jed,GV%ke), source=0.0)
581 endif
582
583 ! Initialize Wave related outputs
584 CS%id_surfacestokes_y = register_diag_field('ocean_model','surface_stokes_y', &
5850 CS%diag%axesCv1,Time,'Surface Stokes drift (y)', 'm s-1', conversion=US%L_T_to_m_s)
586 CS%id_surfacestokes_x = register_diag_field('ocean_model','surface_stokes_x', &
5870 CS%diag%axesCu1,Time,'Surface Stokes drift (x)', 'm s-1', conversion=US%L_T_to_m_s)
588 CS%id_3dstokes_y = register_diag_field('ocean_model','3d_stokes_y', &
5890 CS%diag%axesCvL,Time,'3d Stokes drift (y)', 'm s-1', conversion=US%L_T_to_m_s)
590 CS%id_3dstokes_x = register_diag_field('ocean_model','3d_stokes_x', &
5910 CS%diag%axesCuL,Time,'3d Stokes drift (x)', 'm s-1', conversion=US%L_T_to_m_s)
5920 if (CS%Stokes_DDT) then
593 CS%id_ddt_3dstokes_y = register_diag_field('ocean_model','dvdt_Stokes', &
5940 CS%diag%axesCvL,Time,'d/dt Stokes drift (meridional)', 'm s-2', conversion=US%L_T2_to_m_s2)
595 CS%id_ddt_3dstokes_x = register_diag_field('ocean_model','dudt_Stokes', &
5960 CS%diag%axesCuL,Time,'d/dt Stokes drift (zonal)', 'm s-2', conversion=US%L_T2_to_m_s2)
597 CS%id_3dstokes_y_from_ddt = register_diag_field('ocean_model','3d_stokes_y_from_ddt', &
5980 CS%diag%axesCvL,Time,'3d Stokes drift from ddt (y)', 'm s-1', conversion=US%L_T_to_m_s)
599 CS%id_3dstokes_x_from_ddt = register_diag_field('ocean_model','3d_stokes_x_from_ddt', &
6000 CS%diag%axesCuL,Time,'3d Stokes drift from ddt (x)', 'm s-1', conversion=US%L_T_to_m_s)
601 endif
602 CS%id_PFv_Stokes = register_diag_field('ocean_model','PFv_Stokes', &
6030 CS%diag%axesCvL,Time,'PF from Stokes drift (meridional)','m s-2',conversion=US%L_T2_to_m_s2)
604 CS%id_PFu_Stokes = register_diag_field('ocean_model','PFu_Stokes', &
6050 CS%diag%axesCuL,Time,'PF from Stokes drift (zonal)','m s-2',conversion=US%L_T2_to_m_s2)
606 CS%id_P_deltaStokes_i = register_diag_field('ocean_model','P_deltaStokes_i', &
607 CS%diag%axesTi,Time,'Interfacial pressure anomaly from Stokes drift used in PFu_Stokes',&
6080 'm2 s-2',conversion=US%L_T_to_m_s**2)
609 CS%id_P_deltaStokes_L = register_diag_field('ocean_model','P_deltaStokes_L', &
610 CS%diag%axesTL,Time,'Layer averaged pressure anomaly from Stokes drift used in PFu_Stokes',&
6110 'm2 s-2',conversion=US%L_T_to_m_s**2)
612 CS%id_La_turb = register_diag_field('ocean_model','La_turbulent', &
6130 CS%diag%axesT1,Time,'Surface (turbulent) Langmuir number','nondim')
614
615end subroutine MOM_wave_interface_init
616
617!> Set the parameters that are used to determine the averaged Stokes drift and Langmuir numbers
6181subroutine set_LF17_wave_params(param_file, mdl, GV, US, CS)
619 type(param_file_type), intent(in) :: param_file !< Input parameter structure
620 character(len=*), intent(in) :: mdl !< A module name to use in the get_param calls
621 type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure
622 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
623 type(wave_parameters_CS), pointer :: CS !< Wave parameter control structure
624
625 ! A separate routine is used to set these parameters because there are multiple ways that the
626 ! underlying parameterizations are enabled.
627
628 call get_param(param_file, mdl, "VISCOSITY_AIR", CS%nu_air, &
629 "A typical viscosity of air at sea level, as used in wave calculations", &
6301 units="m2 s-1", default=1.0e-6, scale=US%m2_s_to_Z2_T)
631 call get_param(param_file, mdl, "VON_KARMAN_WAVES", CS%vonKar, &
632 "The value the von Karman constant as used for surface wave calculations.", &
6331 units="nondim", default=0.40) ! The default elsewhere in MOM6 is usually 0.41.
634 call get_param(param_file, mdl, "RHO_AIR", CS%rho_air, &
635 "A typical density of air at sea level, as used in wave calculations", &
6361 units="kg m-3", default=1.225, scale=US%kg_m3_to_R)
637 call get_param(param_file, mdl, "RHO_SFC_WAVES", CS%Rho_ocn, &
638 "A typical surface density of seawater, as used in wave calculations in "//&
639 "comparison with the density of air. The default is RHO_0.", &
6401 units="kg m-3", default=GV%Rho0*US%R_to_kg_m3, scale=US%kg_m3_to_R)
641 call get_param(param_file, mdl, "WAVE_HEIGHT_SCALE_FACTOR", CS%SWH_from_u10sq, &
642 "A factor relating the square of the 10 m wind speed to the significant "//&
643 "wave height, with a default value based on the Pierson-Moskowitz spectrum.", &
6441 units="s2 m-1", default=0.0246, scale=US%m_to_Z*US%L_T_to_m_s**2)
645 call get_param(param_file, mdl, "CHARNOCK_MIN", CS%Charnock_min, &
646 "The minimum value of the Charnock coefficient, which relates the square of "//&
647 "the air friction velocity divided by the gravitational acceleration to the "//&
6481 "wave roughness length.", units="nondim", default=0.028)
649 call get_param(param_file, mdl, "CHARNOCK_SLOPE_U10", CS%Charnock_slope_U10, &
650 "The partial derivative of the Charnock coefficient with the 10 m wind speed. "//&
651 "Note that in eq. 13 of the Edson et al. 2013 describing the COARE 3.5 bulk "//&
652 "flux algorithm, this slope is given as 0.017. However, 0.0017 reproduces "//&
653 "the curve in their figure 6, so that is the default value used in MOM6.", &
6541 units="s m-1", default=0.0017, scale=US%L_T_to_m_s)
655 call get_param(param_file, mdl, "CHARNOCK_0_WIND_INTERCEPT", CS%Charnock_intercept, &
656 "The intercept of the fit for the Charnock coefficient in the limit of no wind. "//&
657 "Note that this can be negative because CHARNOCK_MIN will keep the final "//&
658 "value for the Charnock coefficient from being from being negative.", &
6591 units="nondim", default=-0.005)
660
6611end subroutine set_LF17_wave_params
662
663!> This interface provides the caller with information from the waves control structure.
6640subroutine query_wave_properties(CS, NumBands, WaveNumbers, US)
665 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
666 integer, optional, intent(out) :: NumBands !< If present, this returns the number of
667 !!< wavenumber partitions in the wave discretization
668 real, dimension(:), optional, intent(out) :: Wavenumbers !< If present this returns the characteristic
669 !! wavenumbers of the wave discretization [m-1] or [Z-1 ~> m-1]
670 type(unit_scale_type), optional, intent(in) :: US !< A dimensional unit scaling type that is used to undo
671 !! the dimensional scaling of the output variables, if present
672 integer :: n
673
6740 if (present(NumBands)) NumBands = CS%NumBands
6750 if (present(Wavenumbers)) then
6760 if (size(Wavenumbers) < CS%NumBands) call MOM_error(FATAL, "query_wave_properties called "//&
6770 "with a Wavenumbers array that is smaller than the number of bands.")
6780 if (present(US)) then
6790 do n=1,CS%NumBands ; Wavenumbers(n) = US%m_to_Z * CS%WaveNum_Cen(n) ; enddo
680 else
6810 do n=1,CS%NumBands ; Wavenumbers(n) = CS%WaveNum_Cen(n) ; enddo
682 endif
683 endif
684
6850end subroutine query_wave_properties
686
687!> Subroutine that handles updating of surface wave/Stokes drift related properties
6880subroutine Update_Surface_Waves(G, GV, US, Time_present, dt, CS, forces)
689 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
690 type(ocean_grid_type), intent(inout) :: G !< Grid structure
691 type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure
692 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
693 type(time_type), intent(in) :: Time_present !< Model Time
694 type(time_type), intent(in) :: dt !< Time increment as a time-type
695 type(mech_forcing), intent(in), optional :: forces !< MOM_forcing_type
696 ! Local variables
697 type(time_type) :: Stokes_Time
698 integer :: i, j, b
699
7000 if (CS%WaveMethod == TESTPROF) then
701 ! Do nothing
7020 elseif (CS%WaveMethod == SURFBANDS) then
7030 if (CS%DataSource == DATAOVR) then
704 ! Updating Stokes drift time to center of time increment.
705 ! This choice makes sense for the thermodynamics, but for the
706 ! dynamics it may be more useful to update to the end of the
707 ! time increment.
7080 Stokes_Time = Time_present + dt/2
7090 call Surface_Bands_by_data_override(Stokes_Time, G, GV, US, CS)
7100 elseif (CS%DataSource == COUPLER) then
7110 if (.not.present(FORCES)) then
712 call MOM_error(FATAL,"The option SURFBAND = COUPLER can not be used with "//&
713 "this driver. If you are using a coupled driver with a wave model then "//&
714 "check the arguments in the subroutine call to Update_Surface_Waves, "//&
7150 "otherwise select another option for SURFBAND_SOURCE.")
716 endif
7170 if (size(CS%WaveNum_Cen) /= size(forces%stk_wavenumbers)) then
718 call MOM_error(FATAL, "Number of wavenumber bands in WW3 does not match that in MOM6. "//&
719 "Make sure that STK_BAND_COUPLER in MOM6 input is equal to the number of bands in "//&
7200 "ww3_grid.inp, and that your mod_def.ww3 is up to date.")
721 endif
722
7230 do b=1,CS%NumBands
7240 CS%WaveNum_Cen(b) = forces%stk_wavenumbers(b)
725 !Interpolate from a grid to c grid
7260 do j=G%jsc,G%jec
7270 do I=G%iscB,G%iecB
7280 CS%STKx0(I,j,b) = 0.5*(forces%UStkb(i,j,b)+forces%UStkb(i+1,j,b))
729 enddo
730 enddo
7310 do J=G%jscB,G%jecB
7320 do i=G%isc,G%iec
7330 CS%STKY0(i,J,b) = 0.5*(forces%VStkb(i,j,b)+forces%VStkb(i,j+1,b))
734 enddo
735 enddo
7360 call pass_vector(CS%STKx0(:,:,b),CS%STKy0(:,:,b), G%Domain)
737 enddo
7380 do j=G%jsc,G%jec
7390 do i=G%isc,G%iec
7400 CS%Omega_w2x(i,j) = forces%omega_w2x(i,j)
7410 do b=1,CS%NumBands
7420 CS%UStk_Hb(i,j,b) = forces%UStkb(i,j,b)
7430 CS%VStk_Hb(i,j,b) = forces%VStkb(i,j,b)
744 enddo
745 enddo
746 enddo
7470 elseif (CS%DataSource == INPUT) then
7480 do b=1,CS%NumBands
7490 do j=G%jsd,G%jed
7500 do I=G%isdB,G%iedB
7510 CS%STKx0(I,j,b) = CS%PrescribedSurfStkX(b)
752 enddo
753 enddo
7540 do J=G%jsdB, G%jedB
7550 do i=G%isd,G%ied
7560 CS%STKY0(i,J,b) = CS%PrescribedSurfStkY(b)
757 enddo
758 enddo
759 enddo
760 endif
761 endif
762
7630end subroutine Update_Surface_Waves
764
765!> Constructs the Stokes Drift profile on the model grid based on
766!! desired coupling options
7670subroutine Update_Stokes_Drift(G, GV, US, CS, dz, ustar, dt, dynamics_step)
768 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
769 type(ocean_grid_type), intent(inout) :: G !< Grid structure
770 type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure
771 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
772 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), &
773 intent(in) :: dz !< Thickness in height units [Z ~> m]
774 real, dimension(SZI_(G),SZJ_(G)), &
775 intent(in) :: ustar !< Wind friction velocity [Z T-1 ~> m s-1].
776 real, intent(in) :: dt !< Time-step for computing Stokes-tendency [T ~> s]
777 logical, intent(in) :: dynamics_step !< True if this call is on a dynamics step
778
779 ! Local Variables
780 real :: Top, MidPoint, Bottom ! Positions within the layer [Z ~> m]
781 real :: level_thick ! The thickness of each layer [Z ~> m]
782 real :: DecayScale ! A vertical decay scale in the test profile [Z-1 ~> m-1]
783 real :: CMN_FAC ! A nondimensional factor [nondim]
784 real :: WN ! Model wavenumber [Z-1 ~> m-1]
785 real :: UStokes ! A Stokes drift velocity [L T-1 ~> m s-1]
786 real :: PI ! 3.1415926535... [nondim]
787 real :: La ! The local Langmuir number [nondim]
788 integer :: i, j, k, b
789 real :: I_dt ! The inverse of the time step [T-1 ~> s-1]
790
7910 if (CS%WaveMethod==EFACTOR) return
792
7930 if (allocated(CS%US_x) .and. allocated(CS%US_y)) then
7940 call pass_vector(CS%US_x(:,:,:),CS%US_y(:,:,:), G%Domain)
795 endif
796
797 ! 1. If Test Profile Option is chosen
798 ! Computing mid-point value from surface value and decay wavelength
7990 if (CS%WaveMethod==TESTPROF) then
8000 PI = 4.0*atan(1.0)
8010 DecayScale = 4.*PI / CS%TP_WVL !4pi
8020 do j=G%jsc,G%jec
8030 do I=G%iscB,G%iecB
8040 Bottom = 0.0
8050 MidPoint = 0.0
8060 do k = 1,GV%ke
8070 Top = Bottom
8080 MidPoint = Bottom - 0.25*(dz(i,j,k)+dz(i+1,j,k))
8090 Bottom = Bottom - 0.5*(dz(i,j,k)+dz(i+1,j,k))
8100 CS%Us_x(I,j,k) = CS%TP_STKX0*exp(MidPoint*DecayScale)
811 enddo
812 enddo
813 enddo
8140 do J=G%jscB,G%jecB
8150 do i=G%isc,G%iec
8160 Bottom = 0.0
8170 MidPoint = 0.0
8180 do k = 1,GV%ke
8190 Top = Bottom
8200 MidPoint = Bottom - 0.25*(dz(i,j,k)+dz(i,j+1,k))
8210 Bottom = Bottom - 0.5*(dz(i,j,k)+dz(i,j+1,k))
8220 CS%Us_y(i,J,k) = CS%TP_STKY0*exp(MidPoint*DecayScale)
823 enddo
824 enddo
825 enddo
8260 call pass_vector(CS%Us_x(:,:,:),CS%Us_y(:,:,:), G%Domain, To_All)
827 ! 2. If Surface Bands is chosen
828 ! In wavenumber mode compute integral for layer averaged Stokes drift.
829 ! In frequency mode compuate value at midpoint.
8300 elseif (CS%WaveMethod==SURFBANDS) then
8310 CS%Us_x(:,:,:) = 0.0
8320 CS%Us_y(:,:,:) = 0.0
8330 CS%Us0_x(:,:) = 0.0
8340 CS%Us0_y(:,:) = 0.0
835 ! Computing X direction Stokes drift
8360 do j=G%jsc,G%jec
8370 do I=G%iscB,G%iecB
838 ! 1. First compute the surface Stokes drift
839 ! by summing over the partitions.
8400 do b = 1,CS%NumBands
8410 CS%US0_x(I,j) = CS%US0_x(I,j) + CS%STKx0(I,j,b)
842 enddo
843 ! 2. Second compute the level averaged Stokes drift
8440 bottom = 0.0
8450 do k = 1,GV%ke
8460 Top = Bottom
8470 level_thick = 0.5*(dz(i,j,k)+dz(i+1,j,k))
8480 MidPoint = Top - 0.5*level_thick
8490 Bottom = Top - level_thick
850
8510 if (CS%answer_date >= 20230101) then
852 ! Use more accurate and numerically stable expressions that work even for vanished layers.
8530 do b = 1,CS%NumBands
8540 if (CS%PartitionMode == 0) then
855 ! Average over a layer using the bin's central wavenumber.
8560 CMN_FAC = exp(2.*CS%WaveNum_Cen(b)*Top) * one_minus_exp_x(2.*CS%WaveNum_Cen(b)*level_thick)
857 else
858 ! Use an analytic expression for the average of an exponential over a layer
8590 WN = CS%Freq_Cen(b)**2 * CS%I_g_Earth
8600 CMN_FAC = exp(2.*WN*Top) * one_minus_exp_x(2.*WN*level_thick)
861 endif
8620 CS%US_x(I,j,k) = CS%US_x(I,j,k) + CS%STKx0(I,j,b)*CMN_FAC
863 enddo
864
8650 elseif (level_thick > CS%Stokes_min_thick_avg) then
866 ! -> Stokes drift in thin layers not averaged.
8670 do b = 1,CS%NumBands
8680 if (CS%PartitionMode == 0) then
869 ! In wavenumber we are averaging over level
870 CMN_FAC = (exp(Top*2.*CS%WaveNum_Cen(b))-exp(Bottom*2.*CS%WaveNum_Cen(b))) &
8710 / ((Top-Bottom)*(2.*CS%WaveNum_Cen(b)))
872 else
873 ! Use a numerical integration and then divide by layer thickness
8740 WN = CS%Freq_Cen(b)**2 / CS%g_Earth !bgr bug-fix missing g
8750 CMN_FAC = (exp(2.*WN*Top)-exp(2.*WN*Bottom)) / (2.*WN*(Top-Bottom))
876 endif
8770 CS%US_x(I,j,k) = CS%US_x(I,j,k) + CS%STKx0(I,j,b)*CMN_FAC
878 enddo
879 else ! Take the value at the midpoint
8800 do b = 1,CS%NumBands
8810 if (CS%PartitionMode == 0) then
8820 CMN_FAC = exp(MidPoint * 2. * CS%WaveNum_Cen(b))
883 else
8840 CMN_FAC = exp(MidPoint * 2. * CS%Freq_Cen(b)**2 / CS%g_Earth)
885 endif
8860 CS%US_x(I,j,k) = CS%US_x(I,j,k) + CS%STKx0(I,j,b)*CMN_FAC
887 enddo
888 endif
889 enddo
890 enddo
891 enddo
892
893 ! Computing Y direction Stokes drift
8940 do J=G%jscB,G%jecB
8950 do i=G%isc,G%iec
896 ! Set the surface value to that at z=0
8970 do b = 1,CS%NumBands
8980 CS%US0_y(i,J) = CS%US0_y(i,J) + CS%STKy0(i,J,b)
899 enddo
900 ! Compute the level averages.
9010 bottom = 0.0
9020 do k = 1,GV%ke
9030 Top = Bottom
9040 level_thick = 0.5*(dz(i,j,k)+dz(i,j+1,k))
9050 MidPoint = Top - 0.5*level_thick
9060 Bottom = Top - level_thick
907
9080 if (CS%answer_date >= 20230101) then
909 ! Use more accurate and numerically stable expressions that work even for vanished layers.
9100 do b = 1,CS%NumBands
9110 if (CS%PartitionMode == 0) then
912 ! Average over a layer using the bin's central wavenumber.
9130 CMN_FAC = exp(2.*CS%WaveNum_Cen(b)*Top) * one_minus_exp_x(2.*CS%WaveNum_Cen(b)*level_thick)
914 else
915 ! Use an analytic expression for the average of an exponential over a layer
9160 WN = CS%Freq_Cen(b)**2 * CS%I_g_Earth
9170 CMN_FAC = exp(2.*WN*Top) * one_minus_exp_x(2.*WN*level_thick)
918 endif
9190 CS%US_y(i,J,k) = CS%US_y(i,J,k) + CS%STKy0(i,J,b)*CMN_FAC
920 enddo
9210 elseif (level_thick > CS%Stokes_min_thick_avg) then
922 ! -> Stokes drift in thin layers not averaged.
9230 do b = 1,CS%NumBands
9240 if (CS%PartitionMode == 0) then
925 ! In wavenumber we are averaging over level
926 CMN_FAC = (exp(Top*2.*CS%WaveNum_Cen(b))-exp(Bottom*2.*CS%WaveNum_Cen(b))) &
9270 / ((Top-Bottom)*(2.*CS%WaveNum_Cen(b)))
928 else
929 ! Use a numerical integration and then divide by layer thickness
9300 WN = CS%Freq_Cen(b)**2 / CS%g_Earth !bgr bug-fix missing g
9310 CMN_FAC = (exp(2.*WN*Top)-exp(2.*WN*Bottom)) / (2.*WN*(Top-Bottom))
932 endif
9330 CS%US_y(i,J,k) = CS%US_y(i,J,k) + CS%STKy0(i,J,b)*CMN_FAC
934 enddo
935 else ! Take the value at the midpoint
9360 do b = 1,CS%NumBands
9370 if (CS%PartitionMode == 0) then
9380 CMN_FAC = exp(MidPoint*2.*CS%WaveNum_Cen(b))
939 else
9400 CMN_FAC = exp(MidPoint * 2. * CS%Freq_Cen(b)**2 / CS%g_Earth)
941 endif
9420 CS%US_y(i,J,k) = CS%US_y(i,J,k) + CS%STKy0(i,J,b)*CMN_FAC
943 enddo
944 endif
945 enddo
946 enddo
947 enddo
9480 call pass_vector(CS%Us_x(:,:,:),CS%Us_y(:,:,:), G%Domain, To_All)
9490 call pass_vector(CS%Us0_x(:,:),CS%Us0_y(:,:), G%Domain)
9500 elseif (CS%WaveMethod == DHH85) then
9510 if (.not.(CS%StaticWaves .and. CS%DHH85_is_set)) then
9520 do j=G%jsc,G%jec
9530 do I=G%iscB,G%iecB
9540 bottom = 0.0
9550 do k = 1,GV%ke
9560 Top = Bottom
9570 MidPoint = Top - 0.25*(dz(i,j,k)+dz(i+1,j,k))
9580 Bottom = Top - 0.5*(dz(i,j,k)+dz(i+1,j,k))
959 !bgr note that this is using a u-point I on h-point ustar
960 ! this code has only been previous used for uniform
961 ! grid cases. This needs fixed if DHH85 is used for non
962 ! uniform cases.
9630 call DHH85_mid(GV, US, CS, MidPoint, UStokes)
964 ! Putting into x-direction (no option for direction
9650 CS%US_x(I,j,k) = UStokes
966 enddo
967 enddo
968 enddo
9690 do J=G%jscB,G%jecB
9700 do i=G%isc,G%iec
9710 Bottom = 0.0
9720 do k = 1,GV%ke
9730 Top = Bottom
9740 MidPoint = Bottom - 0.25*(dz(i,j,k)+dz(i,j+1,k))
9750 Bottom = Bottom - 0.5*(dz(i,j,k)+dz(i,j+1,k))
976 !bgr note that this is using a v-point J on h-point ustar
977 ! this code has only been previous used for uniform
978 ! grid cases. This needs fixed if DHH85 is used for non
979 ! uniform cases.
980 ! call DHH85_mid(GV, US, CS, Midpoint, UStokes)
981 ! Putting into x-direction, so setting y direction to 0
9820 CS%US_y(i,J,k) = 0.0
983 ! For rotational symmetry there should be the option for this to become = UStokes
984 ! bgr - see note above, but this is true
985 ! if this is used for anything
986 ! other than simple LES comparison
987 enddo
988 enddo
989 enddo
9900 CS%DHH85_is_set = .true.
991 endif
9920 call pass_vector(CS%Us_x(:,:,:),CS%Us_y(:,:,:), G%Domain)
993 else! Keep this else, fallback to 0 Stokes drift
9940 CS%Us_x(:,:,:) = 0.
9950 CS%Us_y(:,:,:) = 0.
996 endif
997
998 ! Turbulent Langmuir number is computed here and available to use anywhere.
999 ! SL Langmuir number requires mixing layer depth, and therefore is computed
1000 ! in the routine it is needed by (e.g. KPP or ePBL).
10010 do j=G%jsc, G%jec
10020 do i=G%isc,G%iec
1003 call get_Langmuir_Number( La, G, GV, US, dz(i,j,1), ustar(i,j), i, j, &
10040 dz(i,j,:), CS, Override_MA=.false.)
10050 CS%La_turb(i,j) = La
1006 enddo
1007 enddo
1008
1009 ! Finding tendency of Stokes drift over the time step to apply
1010 ! as an acceleration to the models current.
10110 if ( dynamics_step .and. CS%Stokes_DDT ) then
10120 I_dt = 1.0 / dt
10130 CS%ddt_us_x(:,:,:) = (CS%US_x(:,:,:) - CS%US_x_prev(:,:,:)) * I_dt
10140 CS%ddt_us_y(:,:,:) = (CS%US_y(:,:,:) - CS%US_y_prev(:,:,:)) * I_dt
10150 CS%US_x_prev(:,:,:) = CS%US_x(:,:,:)
10160 CS%US_y_prev(:,:,:) = CS%US_y(:,:,:)
1017 endif
1018
1019 ! Output any desired quantities
10200 if (CS%id_surfacestokes_y>0) &
10210 call post_data(CS%id_surfacestokes_y, CS%us0_y, CS%diag)
10220 if (CS%id_surfacestokes_x>0) &
10230 call post_data(CS%id_surfacestokes_x, CS%us0_x, CS%diag)
10240 if (CS%id_3dstokes_y>0) &
10250 call post_data(CS%id_3dstokes_y, CS%us_y, CS%diag)
10260 if (CS%id_3dstokes_x>0) &
10270 call post_data(CS%id_3dstokes_x, CS%us_x, CS%diag)
10280 if (CS%Stokes_DDT) then
10290 if (CS%id_ddt_3dstokes_x>0) &
10300 call post_data(CS%id_ddt_3dstokes_x, CS%ddt_us_x, CS%diag)
10310 if (CS%id_ddt_3dstokes_y>0) &
10320 call post_data(CS%id_ddt_3dstokes_y, CS%ddt_us_y, CS%diag)
10330 if (CS%id_3dstokes_x_from_ddt>0) &
10340 call post_data(CS%id_3dstokes_x_from_ddt, CS%us_x_from_ddt, CS%diag)
10350 if (CS%id_3dstokes_y_from_ddt>0) &
10360 call post_data(CS%id_3dstokes_y_from_ddt, CS%us_y_from_ddt, CS%diag)
1037 endif
10380 if (CS%id_La_turb>0) &
10390 call post_data(CS%id_La_turb, CS%La_turb, CS%diag)
1040
1041end subroutine Update_Stokes_Drift
1042
1043!> Return the value of (1 - exp(-x))/x [nondim], using an accurate expression for small values of x.
1044626926real function one_minus_exp_x(x)
1045 real, intent(in) :: x !< The argument of the function ((1 - exp(-x))/x) [nondim]
1046 real, parameter :: C1_6 = 1.0/6.0 ! A rational fraction [nondim]
1047626926 if (abs(x) <= 2.0e-5) then
1048 ! The Taylor series expression for exp(-x) gives a more accurate expression for 64-bit reals.
10490 one_minus_exp_x = 1.0 - x * (0.5 - C1_6*x)
1050 else
1051626926 one_minus_exp_x = (1.0 - exp(-x)) / x
1052 endif
1053626926end function one_minus_exp_x
1054
1055!> Return the value of (1 - exp(-x)) [nondim], using an accurate expression for small values of x.
10560real function one_minus_exp(x)
1057 real, intent(in) :: x !< The argument of the function ((1 - exp(-x))/x) [nondim]
1058 real, parameter :: C1_6 = 1.0/6.0 ! A rational fraction [nondim]
10590 if (abs(x) <= 2.0e-5) then
1060 ! The Taylor series expression for exp(-x) gives a more accurate expression for 64-bit reals.
10610 one_minus_exp = x * (1.0 - x * (0.5 - C1_6*x))
1062 else
10630 one_minus_exp = 1.0 - exp(-x)
1064 endif
10650end function one_minus_exp
1066
1067!> A subroutine to fill the Stokes drift from a NetCDF file
1068!! using the data_override procedures.
10690subroutine Surface_Bands_by_data_override(Time, G, GV, US, CS)
1070 type(time_type), intent(in) :: Time !< Time to get Stokes drift bands
1071 type(wave_parameters_CS), pointer :: CS !< Wave structure
1072 type(ocean_grid_type), intent(inout) :: G !< Grid structure
1073 type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure
1074 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1075
1076 ! Local variables
10770 real :: temp_x(SZI_(G),SZJ_(G)) ! Pseudo-zonal Stokes drift of band at h-points [L T-1 ~> m s-1]
10780 real :: temp_y(SZI_(G),SZJ_(G)) ! Pseudo-meridional Stokes drift of band at h-points [L T-1 ~> m s-1]
1079 integer, dimension(4) :: sizes ! The sizes of the various dimensions of the variable.
1080 character(len=48) :: dim_name(4) ! The names of the dimensions of the variable.
1081 character(len=20) :: varname ! The name of an input variable for data override.
1082 real :: PI ! 3.1415926535... [nondim]
1083 real :: avgx, avgy ! The global averages of temp_x and temp_y [L T-1 ~> m s-1]
1084 logical :: wavenumber_exists
1085 integer :: ndims, b, i, j
1086
10870 if (.not.CS%DataOver_initialized) then
10880 call data_override_init(G%Domain)
10890 CS%DataOver_initialized = .true.
1090
10910 if (.not.file_exists(CS%SurfBandFileName)) &
10920 call MOM_error(FATAL, "MOM_wave_interface is unable to find file "//trim(CS%SurfBandFileName))
1093
1094 ! Check if input has wavenumber or frequency variables.
1095
1096 ! Read the number of wavenumber bands in the file, if the variable 'wavenumber' exists.
10970 call get_var_sizes(CS%SurfBandFileName, 'wavenumber', ndims, sizes, dim_names=dim_name)
10980 wavenumber_exists = (ndims > -1)
1099
11000 if (.not.wavenumber_exists) then
1101 ! Read the number of frequency bands in the file, if the variable 'frequency' exists.
11020 call get_var_sizes(CS%SurfBandFileName, 'frequency', ndims, sizes, dim_names=dim_name)
11030 if (ndims < 0) &
1104 call MOM_error(FATAL, "error finding variable 'wavenumber' or 'frequency' in file "//&
11050 trim(CS%SurfBandFileName)//" in MOM_wave_interface.")
1106 endif
1107
11080 CS%NUMBANDS = sizes(1)
1109 ! Allocate the wavenumber bins
11100 allocate( CS%WaveNum_Cen(CS%NUMBANDS), source=0.0 )
1111
11120 if (wavenumber_exists) then
1113 ! Wavenumbers found, so this file uses the old method:
11140 CS%PartitionMode = 0
1115
1116 ! Reading wavenumber bins
11170 call read_variable(CS%SurfBandFileName, dim_name(1), CS%WaveNum_Cen, scale=US%Z_to_m)
1118
1119 else
1120 ! Frequencies found, so this file uses the newer method:
11210 CS%PartitionMode = 1
1122
1123 ! Allocate the frequency bins
11240 allocate( CS%Freq_Cen(CS%NUMBANDS), source=0.0 )
1125
1126 ! Reading frequencies
11270 PI = 4.0*atan(1.0)
11280 call read_variable(CS%SurfBandFileName, dim_name(1), CS%Freq_Cen, scale=2.*PI*US%T_to_s)
1129
11300 do b = 1,CS%NumBands
11310 CS%WaveNum_Cen(b) = CS%Freq_Cen(b)**2 / CS%g_Earth
1132 enddo
1133 endif
1134
11350 if (.not.allocated(CS%STKx0)) then
11360 allocate( CS%STKx0(G%isdB:G%iedB,G%jsd:G%jed,CS%NUMBANDS), source=0.0 )
1137 endif
11380 if (.not.allocated(CS%STKy0)) then
11390 allocate( CS%STKy0(G%isd:G%ied,G%jsdB:G%jedB,CS%NUMBANDS), source=0.0 )
1140 endif
1141 endif
1142
11430 do b = 1,CS%NumBands
11440 temp_x(:,:) = 0.0
11450 temp_y(:,:) = 0.0
11460 varname = ' '
11470 write(varname, "(A3,I0)") 'Usx', b
11480 call data_override(G%Domain, trim(varname), temp_x, Time, scale=US%m_s_to_L_T)
11490 varname = ' '
11500 write(varname, "(A3,I0)") 'Usy', b
11510 call data_override(G%Domain, trim(varname), temp_y, Time, scale=US%m_s_to_L_T)
1152 ! Update halo on h-grid
11530 call pass_vector(temp_x, temp_y, G%Domain, To_All, AGRID)
1154 ! Filter land values
11550 do j = G%jsd,G%jed
11560 do i = G%Isd,G%Ied
11570 if ((abs(temp_x(i,j)) > CS%land_speed) .or. (abs(temp_y(i,j)) > CS%land_speed)) then
1158 ! Assume land-mask and zero out
11590 temp_x(i,j) = 0.0
11600 temp_y(i,j) = 0.0
1161 endif
1162 enddo
1163 enddo
11640 if (CS%Homogenize_Surfbands) then
11650 avgx = global_area_mean(temp_x, G)
11660 avgy = global_area_mean(temp_y, G)
11670 do j = G%jsd,G%jed ; do i = G%Isd,G%Ied ; if (G%mask2dT(i,j) > 0.0) then
11680 temp_y(i,j) = avgy
11690 temp_x(i,j) = avgx
1170 endif ; enddo ; enddo
1171 endif
1172
1173 ! Interpolate to u/v grids
11740 do j = G%jsc,G%jec
11750 do I = G%IscB,G%IecB
11760 CS%STKx0(I,j,b) = 0.5 * (temp_x(i,j) + temp_x(i+1,j))
1177 enddo
1178 enddo
11790 do J = G%JscB,G%JecB
11800 do i = G%isc,G%iec
11810 CS%STKy0(i,J,b) = 0.5 * (temp_y(i,j) + temp_y(i,j+1))
1182 enddo
1183 enddo
1184 enddo !Closes b-loop
1185
1186 ! Update halo on u/v grids
11870 call pass_vector(CS%STKx0(:,:,:), CS%STKy0(:,:,:), G%Domain, To_ALL)
1188
11890end subroutine Surface_Bands_by_data_override
1190
1191!> Interface to get Langmuir number based on options stored in wave structure
1192!!
1193!! Note this can be called with an unallocated Waves pointer, which is okay if we
1194!! want the wind-speed only dependent Langmuir number. Therefore, we need to be
1195!! careful about what we try to access here.
1196626926subroutine get_Langmuir_Number( LA, G, GV, US, HBL, ustar, i, j, dz, Waves, &
1197626926 U_H, V_H, Override_MA )
1198 type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
1199 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
1200 real, intent(out) :: LA !< Langmuir number [nondim]
1201 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1202 real, intent(in) :: HBL !< (Positive) thickness of boundary layer [Z ~> m]
1203 real, intent(in) :: ustar !< Friction velocity [Z T-1 ~> m s-1]
1204 integer, intent(in) :: i !< Meridional index of h-point
1205 integer, intent(in) :: j !< Zonal index of h-point
1206 real, dimension(SZK_(GV)), intent(in) :: dz !< Grid layer thickness [Z ~> m]
1207 type(Wave_parameters_CS), pointer :: Waves !< Surface wave control structure.
1208 real, dimension(SZK_(GV)), &
1209 optional, intent(in) :: U_H !< Zonal velocity at H point [L T-1 ~> m s-1] or [m s-1]
1210 real, dimension(SZK_(GV)), &
1211 optional, intent(in) :: V_H !< Meridional velocity at H point [L T-1 ~> m s-1] or [m s-1]
1212 logical, optional, intent(in) :: Override_MA !< Override to use misalignment in LA
1213 !! calculation. This can be used if diagnostic
1214 !! LA outputs are desired that are different than
1215 !! those used by the dynamical model.
1216
1217
1218!Local Variables
1219 real :: Top, Bottom, MidPoint ! Positions within each layer [Z ~> m]
1220 real :: Dpt_LASL ! Averaging depth for Stokes drift [Z ~> m]
1221 real :: ShearDirection ! Shear angular direction from atan2 [radians]
1222 real :: WaveDirection ! Wave angular direction from atan2 [radians]
1223 real :: LA_STKx, LA_STKy, LA_STK ! Stokes velocities in [L T-1 ~> m s-1]
1224 logical :: ContinueLoop, USE_MA
1225626926 real, dimension(SZK_(GV)) :: US_H, VS_H ! Profiles of Stokes velocities [L T-1 ~> m s-1]
1226313463 real, allocatable :: StkBand_X(:), StkBand_Y(:) ! Stokes drifts by band [L T-1 ~> m s-1]
1227 integer :: k, BB
1228
1229 ! Compute averaging depth for Stokes drift (negative)
1230313463 Dpt_LASL = -1.0*max(Waves%LA_FracHBL*HBL, Waves%LA_HBL_min)
1231
1232313463 USE_MA = Waves%LA_Misalignment
1233313463 if (present(Override_MA)) USE_MA = Override_MA
1234
1235 ! If requesting to use misalignment in the Langmuir number compute the Shear Direction
1236313463 if (USE_MA) then
12370 if (.not.(present(U_H).and.present(V_H))) call MOM_error(FATAL, &
12380 "Get_LA_waves requested to consider misalignment, but velocities were not provided.")
12390 ContinueLoop = .true.
12400 bottom = 0.0
12410 do k = 1,GV%ke
12420 Top = Bottom
12430 MidPoint = Bottom + 0.5*dz(k)
12440 Bottom = Bottom + dz(k)
1245
12460 if (Waves%LA_Misalign_bug) then
1247 ! Given the sign convention that Dpt_LASL is negative, the next line has a bug.
12480 if (MidPoint > Dpt_LASL .and. k > 1 .and. ContinueLoop) then
12490 ShearDirection = atan2(V_H(1)-V_H(k), U_H(1)-U_H(k))
12500 ContinueLoop = .false.
1251 endif
1252 else ! This version avoids the bug in the version above.
12530 if (MidPoint > abs(Dpt_LASL) .and. (k > 1) .and. ContinueLoop) then
12540 ShearDirection = atan2(V_H(1)-V_H(k), U_H(1)-U_H(k))
12550 ContinueLoop = .false.
1256 endif
1257 endif
1258 enddo
1259 endif
1260
1261313463 if (Waves%WaveMethod==TESTPROF) then
12620 do k = 1,GV%ke
12630 US_H(k) = 0.5*(Waves%US_X(I,j,k)+Waves%US_X(I-1,j,k))
12640 VS_H(k) = 0.5*(Waves%US_Y(i,J,k)+Waves%US_Y(i,J-1,k))
1265 enddo
12660 call Get_SL_Average_Prof( GV, Dpt_LASL, dz, US_H, LA_STKx)
12670 call Get_SL_Average_Prof( GV, Dpt_LASL, dz, VS_H, LA_STKy)
12680 LA_STK = sqrt((LA_STKX*LA_STKX) + (LA_STKY*LA_STKY))
1269313463 elseif (Waves%WaveMethod==SURFBANDS) then
12700 allocate(StkBand_X(Waves%NumBands), StkBand_Y(Waves%NumBands))
12710 do bb = 1,Waves%NumBands
12720 StkBand_X(bb) = 0.5*(Waves%STKx0(I,j,bb)+Waves%STKx0(I-1,j,bb))
12730 StkBand_Y(bb) = 0.5*(Waves%STKy0(i,J,bb)+Waves%STKy0(i,J-1,bb))
1274 enddo
12750 call Get_SL_Average_Band(GV, Dpt_LASL, Waves%NumBands, Waves%WaveNum_Cen, StkBand_X, LA_STKx )
12760 call Get_SL_Average_Band(GV, Dpt_LASL, Waves%NumBands, Waves%WaveNum_Cen, StkBand_Y, LA_STKy )
12770 LA_STK = sqrt((LA_STKX**2) + (LA_STKY**2))
12780 deallocate(StkBand_X, StkBand_Y)
1279313463 elseif (Waves%WaveMethod==DHH85) then
1280 ! Temporarily integrating profile rather than spectrum for simplicity
12810 do k = 1,GV%ke
12820 US_H(k) = 0.5*(Waves%US_X(I,j,k)+Waves%US_X(I-1,j,k))
12830 VS_H(k) = 0.5*(Waves%US_Y(i,J,k)+Waves%US_Y(i,J-1,k))
1284 enddo
12850 call Get_SL_Average_Prof( GV, Dpt_LASL, dz, US_H, LA_STKx)
12860 call Get_SL_Average_Prof( GV, Dpt_LASL, dz, VS_H, LA_STKy)
12870 LA_STK = sqrt((LA_STKX**2) + (LA_STKY**2))
1288313463 elseif (Waves%WaveMethod==LF17) then
1289313463 call get_StokesSL_LiFoxKemper(ustar, HBL*Waves%LA_FracHBL, GV, US, Waves, LA_STK, LA)
12900 elseif (Waves%WaveMethod==Null_WaveMethod) then
1291 call MOM_error(FATAL, "Get_Langmuir_number called without defining a WaveMethod. "//&
1292 "Suggest to make sure USE_LT is set/overridden to False or choose "//&
12930 "a wave method (or set USE_LA_LI2016 to use statistical waves).")
1294 endif
1295
1296313463 if (.not.(Waves%WaveMethod==LF17)) then
1297 ! This expression uses an arbitrary lower bound on Langmuir number.
1298 ! We shouldn't expect values lower than this, but there is also no good reason to cap it here
1299 ! other than to prevent large enhancements in unconstrained parts of the curve fit parameterizations.
13000 LA = max(Waves%La_min, sqrt(US%Z_to_L*ustar / (LA_STK + Waves%La_Stk_backgnd)))
1301 endif
1302
1303313463 if (Use_MA) then
13040 WaveDirection = atan2(LA_STKy, LA_STKx)
13050 LA = LA / sqrt(max(1.e-8, cos( WaveDirection - ShearDirection)))
1306 endif
1307
1308940389end subroutine get_Langmuir_Number
1309
1310!> function to return the wave method string set in the param file
13110function get_wave_method(CS)
1312 character(:), allocatable :: get_wave_method
1313 type(wave_parameters_CS), pointer :: CS !< Control structure
1314
13150 if (associated(CS)) then
13160 select case(CS%WaveMethod)
1317 case (Null_WaveMethod)
13180 get_wave_method = NULL_STRING
1319 case (TESTPROF)
13200 get_wave_method = TESTPROF_STRING
1321 case (SURFBANDS)
13220 get_wave_method = SURFBANDS_STRING
1323 case (DHH85)
13240 get_wave_method = DHH85_STRING
1325 case (LF17)
13260 get_wave_method = LF17_STRING
1327 case (EFACTOR)
13280 get_wave_method = EFACTOR_STRING
1329 end select
1330 else
13310 get_wave_method = NULL_STRING
1332 endif
13330end function get_wave_method
1334
1335!> Get SL averaged Stokes drift from Li/FK 17 method
1336!!
1337!! Original description:
1338!! - This function returns the enhancement factor, given the 10-meter
1339!! wind [m s-1], friction velocity [m s-1] and the boundary layer depth [m].
1340!!
1341!! Update (Jan/25):
1342!! - Converted from function to subroutine, now returns Langmuir number.
1343!! - Compute 10m wind internally, so only ustar and hbl need passed to
1344!! subroutine.
1345!!
1346!! Qing Li, 160606
1347!! - BGR port from CVMix to MOM6 Jan/25/2017
1348!! - BGR change output to LA from Efactor
1349!! - BGR remove u10 input
1350!! - BGR note: fixed parameter values should be changed to "get_params"
1351313463subroutine get_StokesSL_LiFoxKemper(ustar, hbl, GV, US, CS, UStokes_SL, LA)
1352 real, intent(in) :: ustar !< water-side surface friction velocity [Z T-1 ~> m s-1].
1353 real, intent(in) :: hbl !< boundary layer depth [Z ~> m].
1354 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid structure
1355 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1356 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
1357 real, intent(out) :: UStokes_SL !< Surface layer averaged Stokes drift [L T-1 ~> m s-1]
1358 real, intent(out) :: LA !< Langmuir number [nondim]
1359 ! Local variables
1360 ! parameters
1361 real, parameter :: u19p5_to_u10 = 1.075 ! ratio of U19.5 to U10 (Holthuijsen, 2007) [nondim]
1362 real, parameter :: fm_into_fp = 1.296 ! ratio of mean frequency to peak frequency for
1363 ! Pierson-Moskowitz spectrum (Webb, 2011) [nondim]
1364 real, parameter :: us_to_u10 = 0.0162 ! ratio of surface Stokes drift to U10 [nondim]
1365 real, parameter :: r_loss = 0.667 ! loss ratio of Stokes transport [nondim]
1366 real :: UStokes ! The surface Stokes drift [L T-1 ~> m s-1]
1367 real :: hm0 ! The significant wave height [Z ~> m]
1368 real :: fm ! The mean wave frequency [T-1 ~> s-1]
1369 real :: fp ! The peak wave frequency [T-1 ~> s-1]
1370 real :: kphil ! A peak wavenumber in the Phillips spectrum [Z-1 ~> m-1]
1371 real :: kstar ! A rescaled wavenumber? [Z-1 ~> m-1]
1372 real :: vstokes ! The total Stokes transport [Z L T-1 ~> m2 s-1]
1373 real :: z0 ! The boundary layer depth [Z ~> m]
1374 real :: z0i ! The inverse of the boundary layer depth [Z-1 ~> m-1]
1375 real :: r1, r2, r3, r4 ! Nondimensional ratios [nondim]
1376 real :: r5 ! A single expression that combines r2 and r4 [nondim]
1377 real :: root_2kz ! The square root of twice the peak wavenumber times the
1378 ! boundary layer depth [nondim]
1379 real :: u10 ! The 10 m wind speed [L T-1 ~> m s-1]
1380 real :: PI ! 3.1415926535... [nondim]
1381
1382313463 PI = 4.0*atan(1.0)
1383313463 UStokes_sl = 0.0
1384313463 LA = 1.e8
1385313463 if (ustar > 0.0) then
1386 ! This code should be revised to minimize the number of divisions and cancel out common factors.
1387
1388 ! Computing u10 based on u_star and COARE 3.5 relationships
1389313463 call ust_2_u10_coare3p5(ustar*sqrt(CS%rho_ocn/CS%rho_air), u10, GV, US, CS)
1390 ! surface Stokes drift
1391313463 UStokes = us_to_u10*u10
1392 !
1393 ! significant wave height from Pierson-Moskowitz spectrum (Bouws, 1998)
1394313463 hm0 = CS%SWH_from_u10sq * u10**2
1395 !
1396 ! peak frequency (PM, Bouws, 1998)
1397313463 fp = 0.877 * (US%L_to_Z*GV%g_Earth) / (2.0 * PI * u19p5_to_u10 * u10)
1398 !
1399 ! mean frequency
1400313463 fm = fm_into_fp * fp
1401 !
1402 ! total Stokes transport (a factor r_loss is applied to account
1403 ! for the effect of directional spreading, multidirectional waves
1404 ! and the use of PM peak frequency and PM significant wave height
1405 ! on estimating the Stokes transport)
1406313463 vstokes = 0.125 * PI * r_loss * US%Z_to_L * fm * hm0**2
1407 !
1408 ! the general peak wavenumber for Phillips' spectrum
1409 ! (Breivik et al., 2016) with correction of directional spreading
1410313463 kphil = 0.176 * UStokes / vstokes
1411
1412 ! Combining all of the expressions above gives kPhil as the following
1413 ! where the first two lines are just a constant:
1414 ! kphil = ((0.176 * us_to_u10 * u19p5_to_u10) / &
1415 ! (0.5*0.125 * r_loss * fm_into_fp * 0.877 * CS%SWH_from_u10sq**2)) / &
1416 ! (GV%g_Earth * u10**2)
1417
1418 ! surface layer
1419313463 z0 = abs(hbl)
1420
1421313463 if (CS%answer_date < 20230102) then
14220 z0i = 1.0 / z0
1423
1424 ! Surface layer averaged Stokes drift with Stokes drift profile
1425 ! estimated from Phillips' spectrum (Breivik et al., 2016)
1426 ! The directional spreading effect from Webb and Fox-Kemper, 2015 is also included.
14270 kstar = kphil * 2.56
1428
1429 ! Terms 1 to 4, as written in the appendix of Li et al. (2017)
1430 r1 = ( 0.151 / kphil * z0i - 0.84 ) * &
14310 ( 1.0 - exp(-2.0 * kphil * z0) )
1432 r2 = -( 0.84 + 0.0591 / kphil * z0i ) * &
1433 sqrt( 2.0 * PI * kphil * z0 ) * &
14340 erfc( sqrt( 2.0 * kphil * z0 ) )
1435 r3 = ( 0.0632 / kstar * z0i + 0.125 ) * &
14360 (1.0 - exp(-2.0 * kstar * z0) )
1437 r4 = ( 0.125 + 0.0946 / kstar * z0i ) * &
1438 sqrt( 2.0 * PI * kstar * z0) * &
14390 erfc( sqrt( 2.0 * kstar * z0 ) )
14400 UStokes_sl = UStokes * (0.715 + r1 + r2 + r3 + r4)
1441 else
1442 ! The following is equivalent to the code above, but avoids singularities
1443313463 r1 = ( 0.302 - 1.68*(kphil*z0) ) * one_minus_exp_x(2.0 * (kphil * z0))
1444313463 r3 = ( 0.1264 + 0.64*(kphil*z0) ) * one_minus_exp_x(5.12 * (kphil * z0))
1445
1446313463 root_2kz = sqrt(2.0 * kphil * z0)
1447 ! r2 = -( 0.84 + 0.0591*2.0 / (root_2kz**2) ) * sqrt(PI) * root_2kz * erfc( root_2kz )
1448 ! r4 = ( 0.2 + 0.059125*2.0 / (root_2kz**2) ) * sqrt(PI) * root_2kz * erfc( 1.6 * root_2kz )
1449
1450 ! r5 = r2 + r4 (with a small correction to one coefficient to avoid a singularity when z0 = 0):
1451 ! The correction leads to <1% relative differences in (r2+r4) for root_2kz > 0.05, but without
1452 ! it the values of r2 + r4 are qualitatively wrong (>50% errors) for root_2kz < 0.0015 .
1453 ! It has been verified that these two expressions for r5 are the same to 6 decimal places for
1454 ! root_2kz between 1e-10 and 1e-3, but that the first one degrades for smaller values.
1455313463 if (root_2kz > 1e-3) then
1456 r5 = sqrt(PI) * (root_2kz * (-0.84 * erfc(root_2kz) + 0.2 * erfc(1.6*root_2kz)) + &
1457313463 0.1182 * (erfc(1.6*root_2kz) - erfc(root_2kz)) / root_2kz)
1458 else
1459 ! It is more accurate to replace erf with the first two terms of its Taylor series
1460 ! erf(z) = (2/sqrt(pi)) * z * (1. - (1/3)*z**2 + (1/10)*z**4 - (1/42)*z**6 + ...)
1461 ! and then cancel or combine common terms and drop negligibly small terms.
14620 r5 = -0.64*sqrt(PI)*root_2kz + (-0.14184 + 1.0839648 * root_2kz**2)
1463 endif
1464313463 UStokes_sl = UStokes * (0.715 + ((r1 + r3) + r5))
1465 endif
1466
1467313463 if (UStokes_sl /= 0.0) LA = sqrt(US%Z_to_L*ustar / UStokes_sl)
1468 endif
1469
1470313463end subroutine Get_StokesSL_LiFoxKemper
1471
1472!> Get SL Averaged Stokes drift from a Stokes drift Profile
14730subroutine Get_SL_Average_Prof( GV, AvgDepth, dz, Profile, Average )
1474 type(verticalGrid_type), &
1475 intent(in) :: GV !< Ocean vertical grid structure
1476 real, intent(in) :: AvgDepth !< Depth to average over (negative) [Z ~> m]
1477 real, dimension(SZK_(GV)), &
1478 intent(in) :: dz !< Grid thickness [Z ~> m]
1479 real, dimension(SZK_(GV)), &
1480 intent(in) :: Profile !< Profile of quantity to be averaged in arbitrary units [A]
1481 !! (used here for Stokes drift)
1482 real, intent(out) :: Average !< Output quantity averaged over depth AvgDepth [A]
1483 !! (used here for Stokes drift)
1484 !Local variables
1485 real :: Top, Bottom ! Depths, negative downward [Z ~> m]
1486 real :: Sum ! The depth weighted vertical sum of a quantity [A Z ~> A m]
1487 integer :: k
1488
1489 ! Initializing sum
14900 Sum = 0.0
1491
1492 ! Integrate
14930 bottom = 0.0
14940 do k = 1, GV%ke
14950 Top = Bottom
14960 Bottom = Bottom - dz(k)
14970 if (AvgDepth < Bottom) then ! The whole cell is within H_LA
14980 Sum = Sum + Profile(k) * dz(k)
14990 elseif (AvgDepth < Top) then ! A partial cell is within H_LA
15000 Sum = Sum + Profile(k) * (Top-AvgDepth)
15010 exit
1502 else
15030 exit
1504 endif
1505 enddo
1506
1507 ! Divide by AvgDepth or the depth in the column, whichever is smaller.
15080 if (abs(AvgDepth) <= abs(Bottom)) then
15090 Average = Sum / abs(AvgDepth)
15100 elseif (abs(Bottom) > 0.0) then
15110 Average = Sum / abs(Bottom)
1512 else
15130 Average = 0.0
1514 endif
1515
15160end subroutine Get_SL_Average_Prof
1517
1518!> Get SL averaged Stokes drift from the banded Spectrum method
15190subroutine Get_SL_Average_Band( GV, AvgDepth, NB, WaveNumbers, SurfStokes, Average )
1520 type(verticalGrid_type), &
1521 intent(in) :: GV !< Ocean vertical grid
1522 real, intent(in) :: AvgDepth !< Depth to average over [Z ~> m].
1523 integer, intent(in) :: NB !< Number of bands used
1524 real, dimension(NB), &
1525 intent(in) :: WaveNumbers !< Wavenumber corresponding to each band [Z-1 ~> m-1]
1526 real, dimension(NB), &
1527 intent(in) :: SurfStokes !< Surface Stokes drift for each band [L T-1 ~> m s-1]
1528 real, intent(out) :: Average !< Output average Stokes drift over depth AvgDepth [L T-1 ~> m s-1]
1529
1530 ! Local variables
1531 integer :: bb
1532
1533 ! Loop over bands
15340 Average = 0.0
15350 do bb = 1, NB
1536 ! Factor includes analytical integration of e(2kz)
1537 ! - divided by (-H_LA) to get average from integral.
1538 Average = Average + SurfStokes(BB) * &
1539 (1.-EXP(-abs(AvgDepth * 2.0 * WaveNumbers(BB)))) / &
15400 abs(AvgDepth * 2.0 * WaveNumbers(BB))
1541
1542 ! For accuracy when AvgDepth is small change the above to:
1543 ! Average = Average + SurfStokes(BB) * one_minus_exp_x(abs(AvgDepth * 2.0 * WaveNumbers(BB)))
1544 enddo
1545
15460end subroutine Get_SL_Average_Band
1547
1548!> Compute the Stokes drift at a given depth
1549!!
1550!! Taken from Qing Li (Brown)
1551!! use for comparing MOM6 simulation to his LES
1552!! computed at z mid point (I think) and not depth averaged.
1553!! Should be fine to integrate in frequency from 0.1 to sqrt(-0.2*grav*2pi/dz
15540subroutine DHH85_mid(GV, US, CS, zpt, UStokes)
1555 type(verticalGrid_type), intent(in) :: GV !< Ocean vertical grid
1556 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1557 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
1558 real, intent(in) :: zpt !< Depth to get Stokes drift [Z ~> m].
1559 real, intent(out) :: UStokes !< Stokes drift [L T-1 ~> m s-1]
1560 !
1561 real :: ann, Bnn, Snn, Cnn, Dnn ! Nondimensional factors [nondim]
1562 real :: omega_peak ! The peak wave frequency [T-1 ~> s-1]
1563 real :: omega ! The average frequency in the band [T-1 ~> s-1]
1564 real :: domega ! The width in frequency of the band [T-1 ~> s-1]
1565 real :: u10 ! The wind speed for this spectrum [Z T-1 ~> m s-1]
1566 real :: wavespec ! The wave spectrum [L Z T ~> m2 s]
1567 real :: Stokes ! The Stokes displacement per cycle [L ~> m]
1568 real :: PI ! 3.1415926535... [nondim]
1569 integer :: Nomega ! The number of wavenumber bands
1570 integer :: OI
1571
15720 u10 = CS%WaveWind*US%L_to_Z
1573
1574 !/
15750 NOmega = 1000
15760 domega = (CS%omega_max - CS%omega_min) / real(NOmega)
1577
1578 !
15790 if (CS%WaveAgePeakFreq) then
15800 omega_peak = CS%g_Earth / (CS%WaveAge * u10)
1581 else
15820 PI = 4.0*atan(1.0)
15830 omega_peak = 2. * PI * 0.13 * CS%g_Earth / u10
1584 endif
1585 !/
15860 Ann = 0.006 * CS%WaveAge**(-0.55)
15870 Bnn = 1.0
15880 Snn = 0.08 * (1.0 + 4.0 * CS%WaveAge**3)
15890 Cnn = 1.7
15900 if (CS%WaveAge < 1.) then
15910 Cnn = Cnn - 6.0*log10(CS%WaveAge)
1592 endif
1593 !/
15940 UStokes = 0.0
15950 omega = CS%omega_min + 0.5*domega
15960 do oi = 1,nomega-1
15970 Dnn = exp ( -0.5 * (omega-omega_peak)**2 / (Snn**2 * omega_peak**2) )
1598 ! wavespec units [L Z T ~> m2 s]
1599 wavespec = US%Z_to_L * (Ann * CS%g_Earth**2 / (omega_peak*omega**4 ) ) * &
16000 exp(-bnn*(omega_peak/omega)**4)*Cnn**Dnn
1601 ! Stokes units [L ~> m] (multiply by frequency range for units of [L T-1 ~> m s-1])
1602 Stokes = 2.0 * wavespec * omega**3 * &
16030 exp( 2.0 * omega**2 * zpt / CS%g_Earth) / CS%g_Earth
16040 UStokes = UStokes + Stokes*domega
16050 omega = omega + domega
1606 enddo
1607
16080end subroutine DHH85_mid
1609
1610!> Explicit solver for Stokes mixing.
1611!! Still in development do not use.
16120subroutine StokesMixing(G, GV, dt, h, dz, u, v, Waves )
1613 type(ocean_grid_type), &
1614 intent(in) :: G !< Ocean grid
1615 type(verticalGrid_type), &
1616 intent(in) :: GV !< Ocean vertical grid
1617 real, intent(in) :: dt !< Time step of MOM6 [T ~> s] for explicit solver
1618 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), &
1619 intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2]
1620 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), &
1621 intent(in) :: dz !< Vertical distance between interfaces around a layer [Z ~> m]
1622 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), &
1623 intent(inout) :: u !< Velocity i-component [L T-1 ~> m s-1]
1624 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), &
1625 intent(inout) :: v !< Velocity j-component [L T-1 ~> m s-1]
1626 type(Wave_parameters_CS), &
1627 pointer :: Waves !< Surface wave related control structure.
1628 ! Local variables
1629 real :: dTauUp, dTauDn ! Vertical momentum fluxes [H L T-2 ~> m2 s-2 or Pa]
1630 real :: h_lay ! The layer thickness at a velocity point [H ~> m or kg m-2]
1631 real :: dz_lay ! The distance between interfaces at a velocity point [Z ~> m]
1632 integer :: i, j, k
1633
1634! This is a template to think about down-Stokes mixing.
1635! This is not ready for use...
1636
16370 do k = 1, GV%ke
16380 do j = G%jsc, G%jec
16390 do I = G%iscB, G%iecB
16400 h_lay = 0.5*(h(i,j,k)+h(i+1,j,k))
16410 dz_lay = 0.5*(dz(i,j,k)+dz(i+1,j,k))
16420 dTauUp = 0.0
16430 if (k > 1) &
1644 dTauUp = (0.5*(waves%Kvs(i,j,k)+waves%Kvs(i+1,j,k))) * &
1645 (waves%us_x(i,j,k-1)-waves%us_x(i,j,k)) / &
16460 (0.5*(dz_lay + 0.5*(dz(i,j,k-1)+dz(i+1,j,k-1)) ))
16470 dTauDn = 0.0
16480 if (k < GV%ke-1) &
1649 dTauDn = (0.5*(waves%Kvs(i,j,k+1)+waves%Kvs(i+1,j,k+1))) * &
1650 (waves%us_x(i,j,k)-waves%us_x(i,j,k+1)) / &
16510 (0.5*(dz_lay + 0.5*(dz(i,j,k+1)+dz(i+1,j,k+1)) ))
16520 u(i,j,k) = u(i,j,k) + dt * (dTauUp-dTauDn) / h_lay
1653 enddo
1654 enddo
1655 enddo
1656
16570 do k = 1, GV%ke
16580 do J = G%jscB, G%jecB
16590 do i = G%isc, G%iec
16600 h_lay = 0.5*(h(i,j,k)+h(i,j+1,k))
16610 dz_lay = 0.5*(dz(i,j,k)+dz(i,j+1,k))
16620 dTauUp = 0.
16630 if (k > 1) &
1664 dTauUp = (0.5*(waves%Kvs(i,j,k)+waves%Kvs(i,j+1,k))) * &
1665 (waves%us_y(i,j,k-1)-waves%us_y(i,j,k)) / &
16660 (0.5*(dz_lay + 0.5*(dz(i,j,k-1)+dz(i,j+1,k-1)) ))
16670 dTauDn = 0.0
16680 if (k < GV%ke-1) &
1669 dTauDn = (0.5*(waves%Kvs(i,j,k+1)+waves%Kvs(i,j+1,k+1))) * &
1670 (waves%us_y(i,j,k)-waves%us_y(i,j,k+1)) / &
16710 (0.5*(dz_lay + 0.5*(dz(i,j,k+1)+dz(i,j+1,k+1)) ))
16720 v(i,J,k) = v(i,J,k) + dt * (dTauUp-dTauDn) / h_lay
1673 enddo
1674 enddo
1675 enddo
1676
16770end subroutine StokesMixing
1678
1679!> Solver to add Coriolis-Stokes to model
1680!! Still in development and not meant for general use.
1681!! Can be activated (with code intervention) for LES comparison
1682!! CHECK THAT RIGHT TIMESTEP IS PASSED IF YOU USE THIS**
1683!!
1684!! Not accessed in the standard code.
16850subroutine CoriolisStokes(G, GV, dt, h, u, v, Waves)
1686 type(ocean_grid_type), &
1687 intent(in) :: G !< Ocean grid
1688 type(verticalGrid_type), &
1689 intent(in) :: GV !< Ocean vertical grid
1690 real, intent(in) :: dt !< Time step of MOM6 [T ~> s]
1691 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), &
1692 intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2]
1693 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)), &
1694 intent(inout) :: u !< Velocity i-component [L T-1 ~> m s-1]
1695 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)), &
1696 intent(inout) :: v !< Velocity j-component [L T-1 ~> m s-1]
1697 type(Wave_parameters_CS), &
1698 pointer :: Waves !< Surface wave related control structure.
1699
1700 ! Local variables
1701 real :: DVel ! A rescaled velocity change [L T-2 ~> m s-2]
1702 integer :: i, j, k
1703
17040 do k = 1, GV%ke
17050 do j = G%jsc, G%jec
17060 do I = G%iscB, G%iecB
1707 DVel = 0.25*((Waves%us_y(i,J-1,k)+Waves%us_y(i+1,J-1,k)) * G%CoriolisBu(I,J-1)) + &
17080 0.25*((Waves%us_y(i,J,k)+Waves%us_y(i+1,J,k)) * G%CoriolisBu(I,J))
17090 u(I,j,k) = u(I,j,k) + DVEL*dt
1710 enddo
1711 enddo
1712 enddo
1713
17140 do k = 1, GV%ke
17150 do J = G%jscB, G%jecB
17160 do i = G%isc, G%iec
1717 DVel = 0.25*((Waves%us_x(I-1,j,k)+Waves%us_x(I-1,j+1,k)) * G%CoriolisBu(I-1,j)) + &
17180 0.25*((Waves%us_x(I,j,k)+Waves%us_x(I,j+1,k)) * G%CoriolisBu(I,J))
17190 v(i,J,k) = v(i,j,k) - DVEL*dt
1720 enddo
1721 enddo
1722 enddo
17230end subroutine CoriolisStokes
1724
1725!> Computes tendency due to Stokes pressure gradient force anomaly
1726!! including analytical integration of Stokes shear using multiple-exponential decay
1727!! Stokes drift profile and vertical integration of the resulting pressure
1728!! anomaly to the total pressure gradient force
17290subroutine Stokes_PGF(G, GV, US, dz, u, v, PFu_Stokes, PFv_Stokes, CS )
1730 type(ocean_grid_type), &
1731 intent(in) :: G !< Ocean grid
1732 type(verticalGrid_type), &
1733 intent(in) :: GV !< Ocean vertical grid
1734 type(unit_scale_type), &
1735 intent(in) :: US !< A dimensional unit scaling type
1736 real, dimension(SZI_(G),SZJ_(G),SZK_(G)),&
1737 intent(in) :: dz !< Layer thicknesses in height units [Z ~> m]
1738 real, dimension(SZIB_(G),SZJ_(G),SZK_(G)), &
1739 intent(in) :: u !< Lagrangian Velocity i-component [L T-1 ~> m s-1]
1740 real, dimension(SZI_(G),SZJB_(G),SZK_(G)), &
1741 intent(in) :: v !< Lagrangian Velocity j-component [L T-1 ~> m s-1]
1742 real, dimension(SZIB_(G),SZJ_(G),SZK_(G)), &
1743 intent(out) :: PFu_Stokes !< PGF Stokes-shear i-component [L T-2 ~> m s-2]
1744 real, dimension(SZI_(G),SZJB_(G),SZK_(G)), &
1745 intent(out) :: PFv_Stokes !< PGF Stokes-shear j-component [L T-2 ~> m s-2]
1746 type(Wave_parameters_CS), &
1747 pointer :: CS !< Surface wave related control structure.
1748
1749 ! Local variables
17500 real, dimension(SZI_(G),SZJ_(G),SZK_(G)) :: P_deltaStokes_L ! The Stokes induced pressure anomaly,
1751 ! layer averaged [L2 T-2 ~> m2 s-2]
17520 real, dimension(SZI_(G),SZJ_(G),SZK_(G)+1) :: P_deltaStokes_i ! The Stokes induced pressure anomaly
1753 ! at interfaces [L2 T-2 ~> m2 s-2]
1754 real :: P_Stokes_l, P_Stokes_r ! Stokes-induced pressure anomaly over layer (left/right of point) [L2 T-2 ~> m2 s-2]
1755 real :: P_Stokes_l0, P_Stokes_r0 ! Stokes-induced pressure anomaly at interface
1756 ! (left/right of point) [L2 T-2 ~> m2 s-2]
1757 real :: dP_Stokes_l_dz, dP_Stokes_r_dz ! Contribution of layer to integrated Stokes pressure anomaly for summation
1758 ! (left/right of point) [Z L2 T-2 ~> m3 s-2]
1759 real :: dP_lay_Stokes_l, dP_lay_Stokes_r ! Contribution of layer to integrated Stokes pressure anomaly for summation
1760 ! (left/right of point) [L2 T-2 ~> m2 s-2]
1761 real :: dP_Stokes_l, dP_Stokes_r ! Net increment of Stokes pressure anomaly across layer for summation
1762 ! (left/right of point) [L2 T-2 ~> m2 s-2]
1763 real :: uE_l, uE_r, vE_l, vE_r ! Eulerian velocity components (left/right of point) [L T-1 ~> m s-1]
1764 real :: uS0_l, uS0_r, vS0_l, vS0_r ! Surface Stokes velocity components (left/right of point) [L T-1 ~> m s-1]
17650 real :: zi_l(SZK_(G)+1), zi_r(SZK_(G)+1) ! The height of the edges of the cells (left/right of point) [Z ~> m].
17660 real :: idz_l(SZK_(G)), idz_r(SZK_(G)) ! The inverse thickness of the cells (left/right of point) [Z-1 ~> m-1]
1767 real :: h_l, h_r ! The thickness of the cell (left/right of point) [Z ~> m].
1768 real :: exp_top ! The decay of the surface stokes drift to the interface atop a layer [nondim]
1769 real :: dexp2kzL, dexp4kzL, dexp2kzR, dexp4kzR ! Analytical evaluation of multi-exponential decay
1770 ! contribution to Stokes pressure anomalies [nondim].
1771 real :: TwoK, FourK ! Wavenumbers multiplied by a factor [Z-1 ~> m-1]
1772 real :: iTwoK, iFourK ! Inverses of wavenumbers [Z ~> m]
1773
1774 integer :: i, j, k, l
1775
1776 !---------------------------------------------------------------
1777 ! Compute the Stokes contribution to the pressure gradient force
1778 !---------------------------------------------------------------
1779 ! Notes on the algorithm/code:
1780 ! This code requires computing velocities at bounding h points
1781 ! of the u/v points to get the pressure-gradient. In this
1782 ! implementation there are several redundant calculations as the
1783 ! left/right points are computed at each cell while integrating
1784 ! in the vertical, requiring about twice the calculations. The
1785 ! velocities at the tracer points could be precomputed and
1786 ! stored, but this would require more memory and cycling through
1787 ! large 3d arrays while computing the pressures. This could be
1788 ! explored as a way to speed up this code.
1789 !---------------------------------------------------------------
1790
17910 PFu_Stokes(:,:,:) = 0.0
17920 PFv_Stokes(:,:,:) = 0.0
17930 if (CS%id_P_deltaStokes_i > 0) P_deltaStokes_i(:,:,:) = 0.0
17940 if (CS%id_P_deltaStokes_L > 0) P_deltaStokes_L(:,:,:) = 0.0
1795
1796 ! First compute PGFu. The Stokes-induced pressure anomaly diagnostic is stored from this calculation.
1797 ! > Seeking PGFx at (I,j), meaning we need to compute pressure at h-points (i,j) and (i+1,j).
1798 ! UL(i,j) -> found as average of I-1 & I on j
1799 ! UR(i+1,j) -> found as average of I & I+1 on j
1800 ! VL(i,j) -> found on i as average of J-1 & J
1801 ! VR(i+1,j) -> found on i+1 as average of J-1 & J
1802 !
18030 do j = G%jsc, G%jec ; do I = G%iscB, G%iecB
18040 if (G%mask2dCu(I,j)>0.5) then
18050 P_Stokes_l0 = 0.0
18060 P_Stokes_r0 = 0.0
1807 ! We don't need to precompute the grid in physical space arrays and could have done this during
1808 ! the next loop, but this gives flexibility if the loop directions (integrations) are performed
1809 ! upwards instead of downwards (it seems downwards is the better approach).
18100 zi_l(1) = 0.0
18110 zi_r(1) = 0.0
18120 do k = 1, G%ke
18130 h_l = dz(i,j,k)
18140 h_r = dz(i+1,j,k)
18150 zi_l(k+1) = zi_l(k) - h_l
18160 zi_r(k+1) = zi_r(k) - h_r
18170 if (.not.CS%robust_Stokes_PGF) then
1818 ! When the code is properly refactored, the following hard-coded constants are unnecessary.
18190 Idz_l(k) = 1./max(0.1*US%m_to_Z, h_l)
18200 Idz_r(k) = 1./max(0.1*US%m_to_Z, h_r)
1821 endif
1822 enddo
18230 do k = 1,G%ke
1824 ! Computing (left/right) Eulerian velocities assuming the velocity passed to this routine is the
1825 ! Lagrangian velocity. This requires the wave acceleration terms to be activated together.
1826 uE_l = 0.5*((u(I-1,j,k)-CS%Us_x(I-1,j,k))*G%mask2dCu(I-1,j) + &
18270 (u(I,j,k)-CS%Us_x(I,j,k))*G%mask2dCu(I,j))
1828 uE_r = 0.5*((u(I,j,k)-CS%Us_x(I,j,k))*G%mask2dCu(I,j) + &
18290 (u(I+1,j,k)-CS%Us_x(I+1,j,k))*G%mask2dCu(I+1,j))
1830 vE_l = 0.5*((v(i,J-1,k)-CS%Us_y(i,J-1,k))*G%mask2dCv(i,J-1) + &
18310 (v(i,J,k)-CS%Us_y(i,J,k))*G%mask2dCv(i,J))
1832 vE_r = 0.5*((v(i+1,J-1,k)-CS%Us_y(i+1,J-1,k))*G%mask2dCv(i+1,J-1) + &
18330 (v(i+1,J,k)-CS%Us_y(i+1,J,k))*G%mask2dCv(i+1,J))
1834
18350 dP_Stokes_l_dz = 0.0
18360 dP_Stokes_r_dz = 0.0
18370 dP_Stokes_l = 0.0
18380 dP_Stokes_r = 0.0
18390 dP_lay_Stokes_l=0.0
18400 dP_lay_Stokes_r=0.0
1841
18420 do l = 1, CS%numbands
1843
1844 ! Computing (left/right) surface Stokes drift velocities at wavenumber band
1845 uS0_l = 0.5*(CS%Stkx0(I-1,j,l)*G%mask2dCu(I-1,j) + &
18460 CS%Stkx0(I,j,l)*G%mask2dCu(I,j))
1847 uS0_r = 0.5*(CS%Stkx0(I,j,l)*G%mask2dCu(I,j) + &
18480 CS%Stkx0(I+1,j,l)*G%mask2dCu(I+1,j))
1849 vS0_l = 0.5*(CS%Stky0(i,J-1,l)*G%mask2dCv(i,J-1) + &
18500 CS%Stky0(i,J,l)*G%mask2dCv(i,J))
1851 vS0_r = 0.5*(CS%Stky0(i+1,J-1,l)*G%mask2dCv(i+1,J-1) + &
18520 CS%Stky0(i+1,J,l)*G%mask2dCv(i+1,J))
1853
1854 ! Wavenumber terms that are useful to simplify the pressure calculations
18550 TwoK = 2.*CS%WaveNum_Cen(l)
18560 FourK = 2.*TwoK
18570 if (.not.CS%robust_Stokes_PGF) then
18580 iTwoK = 1. / TwoK
18590 iFourK = 1. / FourK
1860 endif
1861
1862 ! Compute Pressure at interface and integrated over layer on left/right bounding points.
1863 ! These are summed over wavenumber bands.
18640 if (G%mask2dT(i,j)>0.5) then
18650 if (.not.CS%robust_Stokes_PGF) then
18660 dexp2kzL = exp(TwoK*zi_l(k))-exp(TwoK*zi_l(k+1))
18670 dexp4kzL = exp(FourK*zi_l(k))-exp(FourK*zi_l(k+1))
1868 dP_Stokes_l_dz = dP_Stokes_l_dz + &
18690 ((uE_l*uS0_l+vE_l*vS0_l)*iTwoK*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*iFourK*dexp4kzL)
18700 dP_Stokes_l = dP_Stokes_l + (uE_l*uS0_l+vE_l*vS0_l)*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*dexp4kzL
1871 else ! These expressions are equivalent to those above for thick layers, but more accurate for thin layers.
18720 exp_top = exp(TwoK*zi_l(k))
1873 dP_lay_Stokes_l = dP_lay_Stokes_l + &
1874 ((((uE_l*uS0_l)+(vE_l*vS0_l)) * exp_top) * one_minus_exp_x(TwoK*dz(i,j,k)) + &
18750 (0.5*((uS0_l**2)+(vS0_l**2)) * exp_top**2) * one_minus_exp_x(FourK*dz(i,j,k)) )
1876 dP_Stokes_l = dP_Stokes_l + &
1877 ((((uE_l*uS0_l)+(vE_l*vS0_l)) * exp_top) * one_minus_exp(TwoK*dz(i,j,k)) + &
18780 (0.5*((uS0_l**2)+(vS0_l**2)) * exp_top**2) * one_minus_exp(FourK*dz(i,j,k)) )
1879 endif
1880 endif
18810 if (G%mask2dT(i+1,j)>0.5) then
18820 if (.not.CS%robust_Stokes_PGF) then
18830 dexp2kzR = exp(TwoK*zi_r(k))-exp(TwoK*zi_r(k+1))
18840 dexp4kzR = exp(FourK*zi_r(k))-exp(FourK*zi_r(k+1))
1885 dP_Stokes_r_dz = dP_Stokes_r_dz + &
18860 ((uE_r*uS0_r+vE_r*vS0_r)*iTwoK*dexp2kzR + 0.5*(uS0_r*uS0_r+vS0_r*vS0_r)*iFourK*dexp4kzR)
18870 dP_Stokes_r = dP_Stokes_r + (uE_r*uS0_r+vE_r*vS0_r)*dexp2kzR + 0.5*(uS0_r*uS0_r+vS0_r*vS0_r)*dexp4kzR
1888 else ! These expressions are equivalent to those above for thick layers, but more accurate for thin layers.
18890 exp_top = exp(TwoK*zi_r(k))
1890 dP_lay_Stokes_r = dP_lay_Stokes_r + &
1891 ((((uE_r*uS0_r)+(vE_r*vS0_r)) * exp_top) * one_minus_exp_x(TwoK*dz(i+1,j,k)) + &
18920 (0.5*((uS0_r**2)+(vS0_r**2)) * exp_top**2) * one_minus_exp_x(FourK*dz(i+1,j,k)) )
1893 dP_Stokes_r = dP_Stokes_r + &
1894 ((((uE_r*uS0_r)+(vE_r*vS0_r)) * exp_top) * one_minus_exp(TwoK*dz(i+1,j,k)) + &
18950 (0.5*((uS0_r**2)+(vS0_r**2)) * exp_top**2) * one_minus_exp(FourK*dz(i+1,j,k)) )
1896 endif
1897 endif
1898 enddo
1899
1900 ! Summing PF over bands
1901 ! > Increment the Layer averaged pressure
19020 if (.not.CS%robust_Stokes_PGF) then
19030 P_Stokes_l = P_Stokes_l0 + dP_Stokes_l_dz*Idz_l(k)
19040 P_Stokes_r = P_Stokes_r0 + dP_Stokes_r_dz*Idz_r(k)
1905 else
19060 P_Stokes_l = P_Stokes_l0 + dP_lay_Stokes_l
19070 P_Stokes_r = P_Stokes_r0 + dP_lay_Stokes_r
1908 endif
1909
1910 ! > Increment the Interface pressure
19110 P_Stokes_l0 = P_Stokes_l0 + dP_Stokes_l
19120 P_Stokes_r0 = P_Stokes_r0 + dP_Stokes_r
1913
1914 ! Pressure force anomaly is finite difference across the cell.
19150 PFu_Stokes(I,j,k) = (P_Stokes_r - P_Stokes_l)*G%IdxCu(I,j)
1916
1917 ! Choose to output the pressure delta on the h-points from the PFu calculation.
19180 if (G%mask2dT(i,j)>0.5 .and. CS%id_P_deltaStokes_L > 0) P_deltaStokes_L(i,j,k) = P_Stokes_l
19190 if (G%mask2dT(i,j)>0.5 .and. CS%id_P_deltaStokes_i > 0) P_deltaStokes_i(i,j,k+1) = P_Stokes_l0
1920
1921 enddo
1922 endif
1923 enddo ; enddo
1924
1925 ! Next compute PGFv. The Stokes-induced pressure anomaly diagnostic is stored from this calculation.
1926 ! > Seeking PGFy at (i,J), meaning we need to compute pressure at h-points (i,j) and (i,j+1).
1927 ! UL(i,j) -> found as average of I-1 & I on j
1928 ! UR(i,j+1) -> found as average of I-1 & I on j+1
1929 ! VL(i,j) -> found on i as average of J-1 & J
1930 ! VR(i,j+1) -> found on i as average of J & J+1
1931 !
19320 do J = G%jscB, G%jecB ; do i = G%isc, G%iec
19330 if (G%mask2dCv(i,J)>0.5) then
19340 P_Stokes_l0 = 0.0
19350 P_Stokes_r0 = 0.0
19360 zi_l(1) = 0.0
19370 zi_r(1) = 0.0
19380 do k = 1, G%ke
19390 h_l = dz(i,j,k)
19400 h_r = dz(i,j+1,k)
19410 zi_l(k+1) = zi_l(k) - h_l
19420 zi_r(k+1) = zi_r(k) - h_r
19430 if (.not.CS%robust_Stokes_PGF) then
1944 ! When the code is properly refactored, the following hard-coded constants are unnecessary.
19450 Idz_l(k) = 1. / max(0.1*US%m_to_Z, h_l)
19460 Idz_r(k) = 1. / max(0.1*US%m_to_Z, h_r)
1947 endif
1948 enddo
19490 do k = 1,G%ke
1950 ! Computing (left/right) Eulerian velocities assuming the velocity passed to this routine is the
1951 ! Lagrangian velocity. This requires the wave acceleration terms to be activated together.
1952 uE_l = 0.5*((u(I-1,j,k)-CS%Us_x(I-1,j,k))*G%mask2dCu(I-1,j) + &
19530 (u(I,j,k)-CS%Us_x(I,j,k))*G%mask2dCu(I,j))
1954 uE_r = 0.5*((u(I-1,j+1,k)-CS%Us_x(I-1,j+1,k))*G%mask2dCu(I-1,j+1) + &
19550 (u(I,j+1,k)-CS%Us_x(I,j+1,k))*G%mask2dCu(I,j+1))
1956 vE_l = 0.5*((v(i,J-1,k)-CS%Us_y(i,J-1,k))*G%mask2dCv(i,J-1) + &
19570 (v(i,J,k)-CS%Us_y(i,J,k))*G%mask2dCv(i,J))
1958 vE_r = 0.5*((v(i,J,k)-CS%Us_y(i,J,k))*G%mask2dCv(i,J) + &
19590 (v(i,J+1,k)-CS%Us_y(i,J+1,k))*G%mask2dCv(i,J+1))
1960
19610 dP_Stokes_l_dz = 0.0
19620 dP_Stokes_r_dz = 0.0
19630 dP_Stokes_l = 0.0
19640 dP_Stokes_r = 0.0
19650 dP_lay_Stokes_l=0.0
19660 dP_lay_Stokes_r=0.0
1967
19680 do l = 1, CS%numbands
1969
1970 ! Computing (left/right) surface Stokes drift velocities at wavenumber band
1971 uS0_l = 0.5*(CS%Stkx0(I-1,j,l)*G%mask2dCu(I-1,j) + &
19720 CS%Stkx0(I,j,l)*G%mask2dCu(I,j))
1973 uS0_r = 0.5*(CS%Stkx0(I-1,j+1,l)*G%mask2dCu(I-1,j+1) + &
19740 CS%Stkx0(I,j+1,l)*G%mask2dCu(I,j+1))
1975 vS0_l = 0.5*(CS%Stky0(i,J-1,l)*G%mask2dCv(i,J-1) + &
19760 CS%Stky0(i,J,l)*G%mask2dCv(i,J))
1977 vS0_r = 0.5*(CS%Stky0(i,J,l)*G%mask2dCv(i,J) + &
19780 CS%Stky0(i,J+1,l)*G%mask2dCv(i,J+1))
1979
1980 ! Wavenumber terms that are useful to simplify the pressure calculations
19810 TwoK = 2.*CS%WaveNum_Cen(l)
19820 FourK = 2.*TwoK
19830 if (.not.CS%robust_Stokes_PGF) then
19840 iTwoK = 1. / TwoK
19850 iFourK = 1. / FourK
1986 endif
1987
1988 ! Compute Pressure at interface and integrated over layer on left/right bounding points.
1989 ! These are summed over wavenumber bands.
19900 if (G%mask2dT(i,j)>0.5) then
19910 if (.not.CS%robust_Stokes_PGF) then
19920 dexp2kzL = exp(TwoK*zi_l(k))-exp(TwoK*zi_l(k+1))
19930 dexp4kzL = exp(FourK*zi_l(k))-exp(FourK*zi_l(k+1))
1994 dP_Stokes_l_dz = dP_Stokes_l_dz + &
19950 ((uE_l*uS0_l+vE_l*vS0_l)*iTwoK*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*iFourK*dexp4kzL)
19960 dP_Stokes_l = dP_Stokes_l + (uE_l*uS0_l+vE_l*vS0_l)*dexp2kzL + 0.5*(uS0_l*uS0_l+vS0_l*vS0_l)*dexp4kzL
1997 else ! These expressions are equivalent to those above for thick layers, but more accurate for thin layers.
19980 exp_top = exp(TwoK*zi_l(k))
1999 dP_lay_Stokes_l = dP_lay_Stokes_l + &
2000 ((((uE_l*uS0_l)+(vE_l*vS0_l)) * exp_top) * one_minus_exp_x(TwoK*dz(i,j,k)) + &
20010 (0.5*((uS0_l**2)+(vS0_l**2)) * exp_top**2) * one_minus_exp_x(FourK*dz(i,j,k)) )
2002 dP_Stokes_l = dP_Stokes_l + &
2003 ((((uE_l*uS0_l)+(vE_l*vS0_l)) * exp_top) * one_minus_exp(TwoK*dz(i,j,k)) + &
20040 (0.5*((uS0_l**2)+(vS0_l**2)) * exp_top**2) * one_minus_exp(FourK*dz(i,j,k)) )
2005 endif
2006 endif
20070 if (G%mask2dT(i,j+1)>0.5) then
20080 if (.not.CS%robust_Stokes_PGF) then
20090 dexp2kzR = exp(TwoK*zi_r(k))-exp(TwoK*zi_r(k+1))
20100 dexp4kzR = exp(FourK*zi_r(k))-exp(FourK*zi_r(k+1))
2011 dP_Stokes_r_dz = dP_Stokes_r_dz + &
20120 ((uE_r*uS0_r+vE_r*vS0_r)*iTwoK*dexp2kzR + 0.5*(uS0_r*uS0_r+vS0_r*vS0_r)*iFourK*dexp4kzR)
20130 dP_Stokes_r = dP_Stokes_r + (uE_r*uS0_r+vE_r*vS0_r)*dexp2kzR + 0.5*(uS0_r*uS0_r+vS0_r*vS0_r)*dexp4kzR
2014 else ! These expressions are equivalent to those above for thick layers, but more accurate for thin layers.
20150 exp_top = exp(TwoK*zi_r(k))
2016 dP_lay_Stokes_r = dP_lay_Stokes_r + &
2017 ((((uE_r*uS0_r)+(vE_r*vS0_r)) * exp_top) * one_minus_exp_x(TwoK*dz(i,j+1,k)) + &
20180 (0.5*((uS0_r**2)+(vS0_r**2)) * exp_top**2) * one_minus_exp_x(FourK*dz(i,j+1,k)) )
2019 dP_Stokes_r = dP_Stokes_r + &
2020 ((((uE_r*uS0_r)+(vE_r*vS0_r)) * exp_top) * one_minus_exp(TwoK*dz(i,j+1,k)) + &
20210 (0.5*((uS0_r**2)+(vS0_r**2)) * exp_top**2) * one_minus_exp(FourK*dz(i,j+1,k)) )
2022 endif
2023 endif
2024 enddo
2025
2026 ! Summing PF over bands
2027 ! > Increment the Layer averaged pressure
20280 if (.not.CS%robust_Stokes_PGF) then
20290 P_Stokes_l = P_Stokes_l0 + dP_Stokes_l_dz*Idz_l(k)
20300 P_Stokes_r = P_Stokes_r0 + dP_Stokes_r_dz*Idz_r(k)
2031 else
20320 P_Stokes_l = P_Stokes_l0 + dP_lay_Stokes_l
20330 P_Stokes_r = P_Stokes_r0 + dP_lay_Stokes_r
2034 endif
2035
2036 ! > Increment the Interface pressure
20370 P_Stokes_l0 = P_Stokes_l0 + dP_Stokes_l
20380 P_Stokes_r0 = P_Stokes_r0 + dP_Stokes_r
2039
2040 ! Pressure force anomaly is finite difference across the cell.
20410 PFv_Stokes(I,j,k) = (P_Stokes_r - P_Stokes_l)*G%IdyCv(i,J)
2042
2043 enddo
2044 endif
2045 enddo ; enddo
2046
20470 if (CS%id_PFv_Stokes>0) &
20480 call post_data(CS%id_PFv_Stokes, PFv_Stokes, CS%diag)
20490 if (CS%id_PFu_Stokes>0) &
20500 call post_data(CS%id_PFu_Stokes, PFu_Stokes, CS%diag)
20510 if (CS%id_P_deltaStokes_L>0) &
20520 call post_data(CS%id_P_deltaStokes_L, P_deltaStokes_L, CS%diag)
20530 if (CS%id_P_deltaStokes_i>0) &
20540 call post_data(CS%id_P_deltaStokes_i, P_deltaStokes_i, CS%diag)
2055
20560end subroutine Stokes_PGF
2057
2058!> Computes wind speed from ustar_air based on COARE 3.5 Cd relationship
2059!! Probably doesn't belong in this module, but it is used here to estimate
2060!! wind speed for wind-wave relationships. Should be a fine way to estimate
2061!! the neutral wind-speed as written here.
2062313463subroutine ust_2_u10_coare3p5(USTair, U10, GV, US, CS)
2063 real, intent(in) :: USTair !< Wind friction velocity [Z T-1 ~> m s-1]
2064 real, intent(out) :: U10 !< 10-m neutral wind speed [L T-1 ~> m s-1]
2065 type(verticalGrid_type), intent(in) :: GV !< vertical grid type
2066 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
2067 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
2068
2069 ! Local variables
2070 real :: z0sm, z0, z0rough ! Roughness lengths [Z ~> m]
2071 real :: ten_m_scale ! The 10 m reference height, in rescaled units [Z ~> m]
2072 real :: I_ten_m_scale ! The inverse of the 10 m reference height, in rescaled units [Z-1 ~> m-1]
2073 real :: u10a ! The previous guess for u10 [L T-1 ~> m s-1]
2074 real :: alpha ! The Charnock coeffient relating the wind friction velocity squared to the
2075 ! roughness length [nondim]
2076 real :: Cd ! The drag coefficient [nondim]
2077 real :: I_sqrtCd ! The inverse of the square root of the drag coefficient [nondim]
2078 real :: I_vonKar ! The inverse of the von Karman coefficient [nondim]
2079 integer :: CT
2080
2081 ! Uses empirical formula for z0 to convert ustar_air to u10 based on the
2082 ! COARE 3.5 paper (Edson et al., 2013)
2083 ! alpha=m*U10+b
2084 ! Note in Edson et al. 2013, eq. 13 m is given as 0.017. However,
2085 ! m=0.0017 reproduces the curve in their figure 6.
2086
2087313463 if (CS%vonKar < 0.0) call MOM_error(FATAL, &
20880 "ust_2_u10_coare3p5 called with a negative value of Waves%vonKar")
2089
2090313463 z0sm = 0.11 * CS%nu_air / USTair ! Compute z0smooth from ustar guess
2091313463 u10a = 1000.0*US%m_s_to_L_T ! An insanely large upper bound for u10.
2092
2093313463 if (CS%answer_date < 20230103) then
20940 u10 = US%Z_to_L*USTair / sqrt(0.001) ! Guess for u10
20950 ten_m_scale = 10.0*US%m_to_Z
20960 CT=0
20970 do while (abs(u10a/u10 - 1.) > 0.001)
20980 CT=CT+1
20990 u10a = u10
21000 alpha = min(CS%Charnock_min, CS%Charnock_slope_U10 * u10 + CS%Charnock_intercept)
21010 z0rough = alpha * (US%Z_to_L*USTair)**2 / GV%g_Earth ! Compute z0rough from ustar guess
21020 z0 = z0sm + z0rough
21030 Cd = ( CS%vonKar / log(ten_m_scale / z0) )**2 ! Compute Cd from derived roughness
21040 u10 = US%Z_to_L*USTair/sqrt(Cd) ! Compute new u10 from derived Cd, while loop
2105 ! ends and checks for convergence...CT counter
2106 ! makes sure loop doesn't run away if function
2107 ! doesn't converge. This code was produced offline
2108 ! and converged rapidly (e.g. 2 cycles)
2109 ! for ustar=0.0001:0.0001:10.
21100 if (CT>20) then
21110 u10 = US%Z_to_L*USTair/sqrt(0.0015) ! I don't expect to get here, but just
2112 ! in case it will output a reasonable value.
21130 exit
2114 endif
2115 enddo
2116
2117 else ! Use more efficient expressions that are mathematically equivalent to those above.
2118313463 u10 = US%Z_to_L*USTair * sqrt(1000.0) ! First guess for u10.
2119 ! In the line above 1000 is the inverse of a plausible first guess of the drag coefficient.
2120313463 I_vonKar = 1.0 / CS%vonKar
2121313463 I_ten_m_scale = 0.1*US%Z_to_m
2122
21231440143 do CT=1,20
21241440143 if (abs(u10a - u10) <= 0.001*u10) exit ! Check for convergence.
21251126680 u10a = u10
21261126680 alpha = min(CS%Charnock_min, CS%Charnock_slope_U10 * u10 + CS%Charnock_intercept)
21271126680 z0rough = alpha * (CS%I_g_Earth * USTair**2) ! Compute z0rough from ustar guess
21281126680 z0 = z0sm + z0rough
21291126680 I_sqrtCd = abs(log(z0 * I_ten_m_scale)) * I_vonKar ! Compute Cd from derived roughness
21301126680 u10 = US%Z_to_L*USTair * I_sqrtCd ! Compute new u10 from the derived Cd.
2131 enddo
2132
2133 ! Output a reasonable estimate of u10 if the iteration has not converged. The hard-coded
2134 ! number 25.82 is 1/sqrt(0.0015) to 4 decimal places, but the exact value should not matter.
2135313463 if (abs(u10a - u10) > 0.001*u10) u10 = US%Z_to_L*USTair * 25.82
2136 endif
2137
2138313463end subroutine ust_2_u10_coare3p5
2139
2140!> Clear pointers, deallocate memory
21410subroutine Waves_end(CS)
2142 type(wave_parameters_CS), pointer :: CS !< Control structure
2143
21440 if (allocated(CS%WaveNum_Cen)) deallocate( CS%WaveNum_Cen )
21450 if (allocated(CS%Freq_Cen)) deallocate( CS%Freq_Cen )
21460 if (allocated(CS%Us_x)) deallocate( CS%Us_x )
21470 if (allocated(CS%Us_y)) deallocate( CS%Us_y )
21480 if (allocated(CS%La_turb)) deallocate( CS%La_turb )
21490 if (allocated(CS%STKx0)) deallocate( CS%STKx0 )
21500 if (allocated(CS%STKy0)) deallocate( CS%STKy0 )
21510 if (allocated(CS%UStk_Hb)) deallocate( CS%UStk_Hb )
21520 if (allocated(CS%VStk_Hb)) deallocate( CS%VStk_Hb )
21530 if (allocated(CS%Omega_w2x)) deallocate( CS%Omega_w2x )
21540 if (allocated(CS%KvS)) deallocate( CS%KvS )
21550 if (allocated(CS%Us0_y)) deallocate( CS%Us0_y )
21560 if (allocated(CS%Us0_x)) deallocate( CS%Us0_x )
2157
21580 deallocate( CS )
2159
21600end subroutine Waves_end
2161
2162!> Register wave restart fields. To be called before MOM_wave_interface_init
21631subroutine waves_register_restarts(CS, HI, GV, US, param_file, restart_CSp)
2164 type(wave_parameters_CS), pointer :: CS !< Wave parameter Control structure
2165 type(hor_index_type), intent(inout) :: HI !< Grid structure
2166 type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure
2167 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
2168 type(param_file_type), intent(in) :: param_file !< Input parameter structure
2169 type(MOM_restart_CS), pointer :: restart_CSp !< Restart structure, data intent(inout)
2170 ! Local variables
217131 type(vardesc) :: vd(2)
2172 logical :: use_waves
2173 logical :: StatisticalWaves
2174 logical :: time_tendency_term
2175 character(len=40) :: mdl = "MOM_wave_interface" !< This module's name.
2176
21771 if (associated(CS)) then
21780 call MOM_error(FATAL, "waves_register_restarts: Called with initialized waves control structure")
2179 endif
21801 allocate(CS)
2181
2182 call get_param(param_file, mdl, "USE_WAVES", use_waves, &
21831 "If true, enables surface wave modules.", do_not_log=.true., default=.false.)
2184
2185 ! Check if using LA_LI2016
2186 call get_param(param_file,mdl,"USE_LA_LI2016",StatisticalWaves, &
21871 do_not_log=.true.,default=.false.)
2188
21891 if (.not.(use_waves .or. StatisticalWaves)) return
2190
21911 call get_param(param_file, mdl, "STOKES_DDT", time_tendency_term, do_not_log=.true., default=.false.)
2192
21931 if (time_tendency_term) then
2194 ! Allocate wave fields needed for restart file
21950 allocate(CS%Us_x_prev(HI%isdB:HI%IedB,HI%jsd:HI%jed,GV%ke), source=0.0)
21960 allocate(CS%Us_y_prev(HI%isd:HI%Ied,HI%jsdB:HI%jedB,GV%ke), source=0.0)
2197
2198 ! Register to restart files. If these are not found in a restart file, they stay 0.
2199 vd(1) = var_desc("Us_x_prev", "m s-1", "3d zonal Stokes drift profile",&
22000 hor_grid='u', z_grid='L')
2201 vd(2) = var_desc("Us_y_prev", "m s-1", "3d meridional Stokes drift profile",&
22020 hor_grid='v', z_grid='L')
2203 call register_restart_pair(CS%US_x_prev, CS%US_y_prev, vd(1), vd(2), .false., &
22040 restart_CSp, conversion=US%L_T_to_m_s)
2205 endif
2206
2207end subroutine waves_register_restarts
2208
2209!> \namespace mom_wave_interface
2210!!
2211!! \author Brandon Reichl, 2018.
2212!!
2213!! This module should be moved as wave coupling progresses and
2214!! likely will should mirror the iceberg or sea-ice model set-up.
2215!!
2216!! This module is meant to contain the routines to read in and
2217!! interpret surface wave data for MOM6. In its original form, the
2218!! capabilities include setting the Stokes drift in the model (from a
2219!! variety of sources including prescribed, empirical, and input
2220!! files). In short order, the plan is to also amend the subroutine
2221!! to accept Stokes drift information from an external coupler.
2222!! Eventually, it will be necessary to break this file apart so that
2223!! general wave information may be stored in the control structure
2224!! and the Stokes drift effect can be isolated from processes such as
2225!! sea-state dependent momentum fluxes, gas fluxes, and other wave
2226!! related air-sea interaction and boundary layer phenomenon.
2227!!
2228!! The Stokes drift are stored on the C-grid with the conventional
2229!! protocol to interpolate to the h-grid to compute Langmuir number,
2230!! the primary quantity needed for Langmuir turbulence
2231!! parameterizations in both the ePBL and KPP approach. This module
2232!! also computes full 3d Stokes drift profiles, which will be useful
2233!! if second-order type boundary layer parameterizations are
2234!! implemented (perhaps via GOTM, work in progress).
2235
22360end module MOM_wave_interface