MOM6
MOM_wave_structure.F90
1 !> Vertical structure functions for first baroclinic mode wave speed
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 ! By Benjamin Mater & Robert Hallberg, 2015
7 
8 ! The subroutine in this module calculates the vertical structure
9 ! functions of the first baroclinic mode internal wave speed.
10 ! Calculation of interface values is the same as done in
11 ! MOM_wave_speed by Hallberg, 2008.
12 
13 use mom_debugging, only : isnan => is_nan
14 use mom_diag_mediator, only : post_data, query_averaging_enabled, diag_ctrl
15 use mom_diag_mediator, only : register_diag_field, safe_alloc_ptr, time_type
17 use mom_error_handler, only : mom_error, fatal, warning
19 use mom_grid, only : ocean_grid_type
23 
24 implicit none ; private
25 
26 #include <MOM_memory.h>
27 
28 public wave_structure, wave_structure_init
29 
30 ! A note on unit descriptions in comments: MOM6 uses units that can be rescaled for dimensional
31 ! consistency testing. These are noted in comments with units like Z, H, L, and T, along with
32 ! their mks counterparts with notation like "a velocity [Z T-1 ~> m s-1]". If the units
33 ! vary with the Boussinesq approximation, the Boussinesq variant is given first.
34 
35 !> The control structure for the MOM_wave_structure module
36 type, public :: wave_structure_cs ; !private
37  type(diag_ctrl), pointer :: diag => null() !< A structure that is used to
38  !! regulate the timing of diagnostic output.
39  real, allocatable, dimension(:,:,:) :: w_strct
40  !< Vertical structure of vertical velocity (normalized) [m s-1].
41  real, allocatable, dimension(:,:,:) :: u_strct
42  !< Vertical structure of horizontal velocity (normalized) [m s-1].
43  real, allocatable, dimension(:,:,:) :: w_profile
44  !< Vertical profile of w_hat(z), where
45  !! w(x,y,z,t) = w_hat(z)*exp(i(kx+ly-freq*t)) is the full time-
46  !! varying vertical velocity with w_hat(z) = W0*w_strct(z) [m s-1].
47  real, allocatable, dimension(:,:,:) :: uavg_profile
48  !< Vertical profile of the magnitude of horizontal velocity,
49  !! (u^2+v^2)^0.5, averaged over a period [m s-1].
50  real, allocatable, dimension(:,:,:) :: z_depths
51  !< Depths of layer interfaces [m].
52  real, allocatable, dimension(:,:,:) :: n2
53  !< Squared buoyancy frequency at each interface [s-2].
54  integer, allocatable, dimension(:,:):: num_intfaces
55  !< Number of layer interfaces (including surface and bottom)
56  real :: int_tide_source_x !< X Location of generation site
57  !! for internal tide for testing (BDM)
58  real :: int_tide_source_y !< Y Location of generation site
59  !! for internal tide for testing (BDM)
60 
61 end type wave_structure_cs
62 
63 contains
64 
65 !> This subroutine determines the internal wave velocity structure for any mode.
66 !!
67 !! This subroutine solves for the eigen vector [vertical structure, e(k)] associated with
68 !! the first baroclinic mode speed [i.e., smallest eigen value (lam = 1/c^2)] of the
69 !! system d2e/dz2 = -(N2/cn2)e, or (A-lam*I)e = 0, where A = -(1/N2)(d2/dz2), lam = 1/c^2,
70 !! and I is the identity matrix. 2nd order discretization in the vertical lets this system
71 !! be represented as
72 !!
73 !! -Igu(k)*e(k-1) + (Igu(k)+Igl(k)-lam)*e(k) - Igl(k)*e(k+1) = 0.0
74 !!
75 !! with rigid lid boundary conditions e(1) = e(nz+1) = 0.0 giving
76 !!
77 !! (Igu(2)+Igl(2)-lam)*e(2) - Igl(2)*e(3) = 0.0
78 !! -Igu(nz)*e(nz-1) + (Igu(nz)+Igl(nz)-lam)*e(nz) = 0.0
79 !!
80 !! where, upon noting N2 = reduced gravity/layer thickness, we get
81 !! Igl(k) = 1.0/(gprime(k)*H(k)) ; Igu(k) = 1.0/(gprime(k)*H(k-1))
82 !!
83 !! The eigen value for this system is approximated using "wave_speed." This subroutine uses
84 !! these eigen values (mode speeds) to estimate the corresponding eigen vectors (velocity
85 !! structure) using the "inverse iteration with shift" method. The algorithm is
86 !!
87 !! Pick a starting vector reasonably close to mode structure and with unit magnitude, b_guess
88 !! For n=1,2,3,...
89 !! Solve (A-lam*I)e = e_guess for e
90 !! Set e_guess=e/|e| and repeat, with each iteration refining the estimate of e
91 subroutine wave_structure(h, tv, G, GV, US, cn, ModeNum, freq, CS, En, full_halos)
92  type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure.
93  type(verticalgrid_type), intent(in) :: gv !< The ocean's vertical grid structure.
94  type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
95  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2]
96  type(thermo_var_ptrs), intent(in) :: tv !< A structure pointing to various
97  !! thermodynamic variables.
98  real, dimension(SZI_(G),SZJ_(G)), intent(in) :: cn !< The (non-rotational) mode internal
99  !! gravity wave speed [m s-1].
100  integer, intent(in) :: modenum !< Mode number
101  real, intent(in) :: freq !< Intrinsic wave frequency [s-1].
102  type(wave_structure_cs), pointer :: cs !< The control structure returned by a
103  !! previous call to wave_structure_init.
104  real, dimension(SZI_(G),SZJ_(G)), &
105  optional, intent(in) :: en !< Internal wave energy density [J m-2].
106  logical,optional, intent(in) :: full_halos !< If true, do the calculation
107  !! over the entire computational domain.
108  ! Local variables
109  real, dimension(SZK_(G)+1) :: &
110  drho_dt, drho_ds, &
111  pres, t_int, s_int, &
112  gprime ! The reduced gravity across each interface [m2 Z-1 s-2 ~> m s-2].
113  real, dimension(SZK_(G)) :: &
114  igl, igu ! The inverse of the reduced gravity across an interface times
115  ! the thickness of the layer below (Igl) or above (Igu) it [s2 m-2].
116  real, dimension(SZK_(G),SZI_(G)) :: &
117  hf, tf, sf, rf
118  real, dimension(SZK_(G)) :: &
119  hc, tc, sc, rc, &
120  det, ddet
121  real, dimension(SZI_(G),SZJ_(G)) :: &
122  htot
123  real :: lam
124  real :: min_h_frac
125  real :: h_to_pres
126  real, dimension(SZI_(G)) :: &
127  hmin, & ! Thicknesses [Z ~> m].
128  h_here, hxt_here, hxs_here, hxr_here
129  real :: speed2_tot
130  real :: i_hnew, drxh_sum
131  real, parameter :: tol1 = 0.0001, tol2 = 0.001
132  real, pointer, dimension(:,:,:) :: t => null(), s => null()
133  real :: g_rho0 ! G_Earth/Rho0 in m5 Z-1 s-2 kg-1.
134  real :: rescale, i_rescale
135  integer :: kf(szi_(g))
136  integer, parameter :: max_itt = 1 ! number of times to iterate in solving for eigenvector
137  real, parameter :: cg_subro = 1e-100 ! a very small number
138  real, parameter :: a_int = 0.5 ! value of normalized integral: \int(w_strct^2)dz = a_int
139  real :: i_a_int ! inverse of a_int
140  real :: f2 ! squared Coriolis frequency
141  real :: kmag2 ! magnitude of horizontal wave number squared
142  logical :: use_eos ! If true, density is calculated from T & S using an
143  ! equation of state.
144  real, dimension(SZK_(G)+1) :: w_strct, u_strct, w_profile, uavg_profile, z_int, n2
145  ! local representations of variables in CS; note,
146  ! not all rows will be filled if layers get merged!
147  real, dimension(SZK_(G)+1) :: w_strct2, u_strct2
148  ! squared values
149  real, dimension(SZK_(G)) :: dz ! thicknesses of merged layers (same as Hc I hope)
150  real, dimension(SZK_(G)+1) :: dwdz_profile ! profile of dW/dz
151  real :: w2avg ! average of squared vertical velocity structure funtion
152  real :: int_dwdz2, int_w2, int_n2w2, ke_term, pe_term, w0
153  ! terms in vertically averaged energy equation
154  real :: gp_unscaled ! A version of gprime rescaled to [m s-2].
155  real, dimension(SZK_(G)-1) :: lam_z ! product of eigen value and gprime(k); one value for each
156  ! interface (excluding surface and bottom)
157  real, dimension(SZK_(G)-1) :: a_diag, b_diag, c_diag
158  ! diagonals of tridiagonal matrix; one value for each
159  ! interface (excluding surface and bottom)
160  real, dimension(SZK_(G)-1) :: e_guess ! guess at eigen vector with unit amplitde (for TDMA)
161  real, dimension(SZK_(G)-1) :: e_itt ! improved guess at eigen vector (from TDMA)
162  real :: pi
163  integer :: kc
164  integer :: i, j, k, k2, itt, is, ie, js, je, nz, nzm, row, ig, jg, ig_stop, jg_stop
165 
166  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec ; nz = g%ke
167  i_a_int = 1/a_int
168 
169  !if (present(CS)) then
170  if (.not. associated(cs)) call mom_error(fatal, "MOM_wave_structure: "// &
171  "Module must be initialized before it is used.")
172  !endif
173 
174  if (present(full_halos)) then ; if (full_halos) then
175  is = g%isd ; ie = g%ied ; js = g%jsd ; je = g%jed
176  endif ; endif
177 
178  pi = (4.0*atan(1.0))
179 
180  s => tv%S ; t => tv%T
181  g_rho0 = us%L_T_to_m_s**2 * gv%g_Earth /gv%Rho0
182  use_eos = associated(tv%eqn_of_state)
183 
184  h_to_pres = gv%Z_to_H*gv%H_to_Pa
185  rescale = 1024.0**4 ; i_rescale = 1.0/rescale
186 
187  min_h_frac = tol1 / real(nz)
188 
189  do j=js,je
190  ! First merge very thin layers with the one above (or below if they are
191  ! at the top). This also transposes the row order so that columns can
192  ! be worked upon one at a time.
193  do i=is,ie ; htot(i,j) = 0.0 ; enddo
194  do k=1,nz ; do i=is,ie ; htot(i,j) = htot(i,j) + h(i,j,k)*gv%H_to_Z ; enddo ; enddo
195 
196  do i=is,ie
197  hmin(i) = htot(i,j)*min_h_frac ; kf(i) = 1 ; h_here(i) = 0.0
198  hxt_here(i) = 0.0 ; hxs_here(i) = 0.0 ; hxr_here(i) = 0.0
199  enddo
200  if (use_eos) then
201  do k=1,nz ; do i=is,ie
202  if ((h_here(i) > hmin(i)) .and. (h(i,j,k)*gv%H_to_Z > hmin(i))) then
203  hf(kf(i),i) = h_here(i)
204  tf(kf(i),i) = hxt_here(i) / h_here(i)
205  sf(kf(i),i) = hxs_here(i) / h_here(i)
206  kf(i) = kf(i) + 1
207 
208  ! Start a new layer
209  h_here(i) = h(i,j,k)*gv%H_to_Z
210  hxt_here(i) = (h(i,j,k)*gv%H_to_Z)*t(i,j,k)
211  hxs_here(i) = (h(i,j,k)*gv%H_to_Z)*s(i,j,k)
212  else
213  h_here(i) = h_here(i) + h(i,j,k)*gv%H_to_Z
214  hxt_here(i) = hxt_here(i) + (h(i,j,k)*gv%H_to_Z)*t(i,j,k)
215  hxs_here(i) = hxs_here(i) + (h(i,j,k)*gv%H_to_Z)*s(i,j,k)
216  endif
217  enddo ; enddo
218  do i=is,ie ; if (h_here(i) > 0.0) then
219  hf(kf(i),i) = h_here(i)
220  tf(kf(i),i) = hxt_here(i) / h_here(i)
221  sf(kf(i),i) = hxs_here(i) / h_here(i)
222  endif ; enddo
223  else
224  do k=1,nz ; do i=is,ie
225  if ((h_here(i) > hmin(i)) .and. (h(i,j,k)*gv%H_to_Z > hmin(i))) then
226  hf(kf(i),i) = h_here(i) ; rf(kf(i),i) = hxr_here(i) / h_here(i)
227  kf(i) = kf(i) + 1
228 
229  ! Start a new layer
230  h_here(i) = h(i,j,k)*gv%H_to_Z
231  hxr_here(i) = (h(i,j,k)*gv%H_to_Z)*gv%Rlay(k)
232  else
233  h_here(i) = h_here(i) + h(i,j,k)*gv%H_to_Z
234  hxr_here(i) = hxr_here(i) + (h(i,j,k)*gv%H_to_Z)*gv%Rlay(k)
235  endif
236  enddo ; enddo
237  do i=is,ie ; if (h_here(i) > 0.0) then
238  hf(kf(i),i) = h_here(i) ; rf(kf(i),i) = hxr_here(i) / h_here(i)
239  endif ; enddo
240  endif ! use_EOS?
241 
242  ! From this point, we can work on individual columns without causing memory
243  ! to have page faults.
244  do i=is,ie ; if (cn(i,j)>0.0)then
245  !----for debugging, remove later----
246  ig = i + g%idg_offset ; jg = j + g%jdg_offset
247  !if (ig == CS%int_tide_source_x .and. jg == CS%int_tide_source_y) then
248  !-----------------------------------
249  if (g%mask2dT(i,j) > 0.5) then
250 
251  lam = 1/(cn(i,j)**2)
252 
253  ! Calculate drxh_sum
254  if (use_eos) then
255  pres(1) = 0.0
256  do k=2,kf(i)
257  pres(k) = pres(k-1) + h_to_pres*hf(k-1,i)
258  t_int(k) = 0.5*(tf(k,i)+tf(k-1,i))
259  s_int(k) = 0.5*(sf(k,i)+sf(k-1,i))
260  enddo
261  call calculate_density_derivs(t_int, s_int, pres, drho_dt, drho_ds, 2, &
262  kf(i)-1, tv%eqn_of_state)
263 
264  ! Sum the reduced gravities to find out how small a density difference
265  ! is negligibly small.
266  drxh_sum = 0.0
267  do k=2,kf(i)
268  drxh_sum = drxh_sum + 0.5*(hf(k-1,i)+hf(k,i)) * &
269  max(0.0,drho_dt(k)*(tf(k,i)-tf(k-1,i)) + &
270  drho_ds(k)*(sf(k,i)-sf(k-1,i)))
271  enddo
272  else
273  drxh_sum = 0.0
274  do k=2,kf(i)
275  drxh_sum = drxh_sum + 0.5*(hf(k-1,i)+hf(k,i)) * &
276  max(0.0,rf(k,i)-rf(k-1,i))
277  enddo
278  endif ! use_EOS?
279 
280  ! Find gprime across each internal interface, taking care of convective
281  ! instabilities by merging layers.
282  if (drxh_sum >= 0.0) then
283  ! Merge layers to eliminate convective instabilities or exceedingly
284  ! small reduced gravities.
285  if (use_eos) then
286  kc = 1
287  hc(1) = hf(1,i) ; tc(1) = tf(1,i) ; sc(1) = sf(1,i)
288  do k=2,kf(i)
289  if ((drho_dt(k)*(tf(k,i)-tc(kc)) + drho_ds(k)*(sf(k,i)-sc(kc))) * &
290  (hc(kc) + hf(k,i)) < 2.0 * tol2*drxh_sum) then
291  ! Merge this layer with the one above and backtrack.
292  i_hnew = 1.0 / (hc(kc) + hf(k,i))
293  tc(kc) = (hc(kc)*tc(kc) + hf(k,i)*tf(k,i)) * i_hnew
294  sc(kc) = (hc(kc)*sc(kc) + hf(k,i)*sf(k,i)) * i_hnew
295  hc(kc) = (hc(kc) + hf(k,i))
296  ! Backtrack to remove any convective instabilities above... Note
297  ! that the tolerance is a factor of two larger, to avoid limit how
298  ! far back we go.
299  do k2=kc,2,-1
300  if ((drho_dt(k2)*(tc(k2)-tc(k2-1)) + drho_ds(k2)*(sc(k2)-sc(k2-1))) * &
301  (hc(k2) + hc(k2-1)) < tol2*drxh_sum) then
302  ! Merge the two bottommost layers. At this point kc = k2.
303  i_hnew = 1.0 / (hc(kc) + hc(kc-1))
304  tc(kc-1) = (hc(kc)*tc(kc) + hc(kc-1)*tc(kc-1)) * i_hnew
305  sc(kc-1) = (hc(kc)*sc(kc) + hc(kc-1)*sc(kc-1)) * i_hnew
306  hc(kc-1) = (hc(kc) + hc(kc-1))
307  kc = kc - 1
308  else ; exit ; endif
309  enddo
310  else
311  ! Add a new layer to the column.
312  kc = kc + 1
313  drho_ds(kc) = drho_ds(k) ; drho_dt(kc) = drho_dt(k)
314  tc(kc) = tf(k,i) ; sc(kc) = sf(k,i) ; hc(kc) = hf(k,i)
315  endif
316  enddo
317  ! At this point there are kc layers and the gprimes should be positive.
318  do k=2,kc ! Revisit this if non-Boussinesq.
319  gprime(k) = g_rho0 * (drho_dt(k)*(tc(k)-tc(k-1)) + &
320  drho_ds(k)*(sc(k)-sc(k-1)))
321  enddo
322  else ! .not.use_EOS
323  ! Do the same with density directly...
324  kc = 1
325  hc(1) = hf(1,i) ; rc(1) = rf(1,i)
326  do k=2,kf(i)
327  if ((rf(k,i) - rc(kc)) * (hc(kc) + hf(k,i)) < 2.0*tol2*drxh_sum) then
328  ! Merge this layer with the one above and backtrack.
329  rc(kc) = (hc(kc)*rc(kc) + hf(k,i)*rf(k,i)) / (hc(kc) + hf(k,i))
330  hc(kc) = (hc(kc) + hf(k,i))
331  ! Backtrack to remove any convective instabilities above... Note
332  ! that the tolerance is a factor of two larger, to avoid limit how
333  ! far back we go.
334  do k2=kc,2,-1
335  if ((rc(k2)-rc(k2-1)) * (hc(k2)+hc(k2-1)) < tol2*drxh_sum) then
336  ! Merge the two bottommost layers. At this point kc = k2.
337  rc(kc-1) = (hc(kc)*rc(kc) + hc(kc-1)*rc(kc-1)) / (hc(kc) + hc(kc-1))
338  hc(kc-1) = (hc(kc) + hc(kc-1))
339  kc = kc - 1
340  else ; exit ; endif
341  enddo
342  else
343  ! Add a new layer to the column.
344  kc = kc + 1
345  rc(kc) = rf(k,i) ; hc(kc) = hf(k,i)
346  endif
347  enddo
348  ! At this point there are kc layers and the gprimes should be positive.
349  do k=2,kc ! Revisit this if non-Boussinesq.
350  gprime(k) = g_rho0 * (rc(k)-rc(k-1))
351  enddo
352  endif ! use_EOS?
353 
354  !-----------------NOW FIND WAVE STRUCTURE-------------------------------------
355  ! Construct and solve tridiagonal system for the interior interfaces
356  ! Note that kc = number of layers,
357  ! kc+1 = nzm = number of interfaces,
358  ! kc-1 = number of interior interfaces (excluding surface and bottom)
359  ! Also, note that "K" refers to an interface, while "k" refers to the layer below.
360  ! Need at least 3 layers (2 internal interfaces) to generate a matrix, also
361  ! need number of layers to be greater than the mode number
362  if (kc >= modenum + 1) then
363  ! Set depth at surface
364  z_int(1) = 0.0
365  ! Calculate Igu, Igl, depth, and N2 at each interior interface
366  ! [excludes surface (K=1) and bottom (K=kc+1)]
367  do k=2,kc
368  igl(k) = 1.0/(gprime(k)*hc(k)) ; igu(k) = 1.0/(gprime(k)*hc(k-1))
369  z_int(k) = z_int(k-1) + hc(k-1)
370  n2(k) = us%m_to_Z**2*gprime(k)/(0.5*(hc(k)+hc(k-1)))
371  enddo
372  ! Set stratification for surface and bottom (setting equal to nearest interface for now)
373  n2(1) = n2(2) ; n2(kc+1) = n2(kc)
374  ! Calcualte depth at bottom
375  z_int(kc+1) = z_int(kc)+hc(kc)
376  ! check that thicknesses sum to total depth
377  if (abs(z_int(kc+1)-htot(i,j)) > 1.e-14*htot(i,j)) then
378  call mom_error(fatal, "wave_structure: mismatch in total depths")
379  endif
380 
381  ! Note that many of the calcluation from here on revert to using vertical
382  ! distances in m, not Z.
383 
384  ! Populate interior rows of tridiagonal matrix; must multiply through by
385  ! gprime to get tridiagonal matrix to the symmetrical form:
386  ! [-1/H(k-1)]e(k-1) + [1/H(k-1)+1/H(k)-lam_z]e(k) + [-1/H(k)]e(k+1) = 0,
387  ! where lam_z = lam*gprime is now a function of depth.
388  ! Frist, populate interior rows
389  do k=3,kc-1
390  row = k-1 ! indexing for TD matrix rows
391  gp_unscaled = us%m_to_Z*gprime(k)
392  lam_z(row) = lam*gp_unscaled
393  a_diag(row) = gp_unscaled*(-igu(k))
394  b_diag(row) = gp_unscaled*(igu(k)+igl(k)) - lam_z(row)
395  c_diag(row) = gp_unscaled*(-igl(k))
396  if (isnan(lam_z(row)))then ; print *, "Wave_structure: lam_z(row) is NAN" ; endif
397  if (isnan(a_diag(row)))then ; print *, "Wave_structure: a(k) is NAN" ; endif
398  if (isnan(b_diag(row)))then ; print *, "Wave_structure: b(k) is NAN" ; endif
399  if (isnan(c_diag(row)))then ; print *, "Wave_structure: c(k) is NAN" ; endif
400  enddo
401  ! Populate top row of tridiagonal matrix
402  k=2 ; row = k-1 ;
403  gp_unscaled = us%m_to_Z*gprime(k)
404  lam_z(row) = lam*gp_unscaled
405  a_diag(row) = 0.0
406  b_diag(row) = gp_unscaled*(igu(k)+igl(k)) - lam_z(row)
407  c_diag(row) = gp_unscaled*(-igl(k))
408  ! Populate bottom row of tridiagonal matrix
409  k=kc ; row = k-1
410  gp_unscaled = us%m_to_Z*gprime(k)
411  lam_z(row) = lam*gp_unscaled
412  a_diag(row) = gp_unscaled*(-igu(k))
413  b_diag(row) = gp_unscaled*(igu(k)+igl(k)) - lam_z(row)
414  c_diag(row) = 0.0
415 
416  ! Guess a vector shape to start with (excludes surface and bottom)
417  e_guess(1:kc-1) = sin((z_int(2:kc)/htot(i,j)) *pi)
418  e_guess(1:kc-1) = e_guess(1:kc-1)/sqrt(sum(e_guess(1:kc-1)**2))
419 
420  ! Perform inverse iteration with tri-diag solver
421  do itt=1,max_itt
422  call tridiag_solver(a_diag(1:kc-1),b_diag(1:kc-1),c_diag(1:kc-1), &
423  -lam_z(1:kc-1),e_guess(1:kc-1),"TDMA_H",e_itt)
424  e_guess(1:kc-1) = e_itt(1:kc-1)/sqrt(sum(e_itt(1:kc-1)**2))
425  enddo ! itt-loop
426  w_strct(2:kc) = e_guess(1:kc-1)
427  w_strct(1) = 0.0 ! rigid lid at surface
428  w_strct(kc+1) = 0.0 ! zero-flux at bottom
429 
430  ! Check to see if solver worked
431  ig_stop = 0 ; jg_stop = 0
432  if (isnan(sum(w_strct(1:kc+1))))then
433  print *, "Wave_structure: w_strct has a NAN at ig=", ig, ", jg=", jg
434  if (i<g%isc .or. i>g%iec .or. j<g%jsc .or. j>g%jec)then
435  print *, "This is occuring at a halo point."
436  endif
437  ig_stop = ig ; jg_stop = jg
438  endif
439 
440  ! Normalize vertical structure function of w such that
441  ! \int(w_strct)^2dz = a_int (a_int could be any value, e.g., 0.5)
442  nzm = kc+1 ! number of layer interfaces after merging
443  !(including surface and bottom)
444  w2avg = 0.0
445  do k=1,nzm-1
446  dz(k) = us%Z_to_m*hc(k)
447  w2avg = w2avg + 0.5*(w_strct(k)**2+w_strct(k+1)**2)*dz(k)
448  enddo
449  !### Some mathematical cancellations could occur in the next two lines.
450  w2avg = w2avg / htot(i,j)
451  w_strct = w_strct / sqrt(htot(i,j)*w2avg*i_a_int)
452 
453  ! Calculate vertical structure function of u (i.e. dw/dz)
454  do k=2,nzm-1
455  u_strct(k) = 0.5*((w_strct(k-1) - w_strct(k) )/dz(k-1) + &
456  (w_strct(k) - w_strct(k+1))/dz(k))
457  enddo
458  u_strct(1) = (w_strct(1) - w_strct(2) )/dz(1)
459  u_strct(nzm) = (w_strct(nzm-1)- w_strct(nzm))/dz(nzm-1)
460 
461  ! Calculate wavenumber magnitude
462  f2 = us%s_to_T**2 * g%CoriolisBu(i,j)**2
463  !f2 = 0.25*US%s_to_T**2 *((G%CoriolisBu(I,J)**2 + G%CoriolisBu(I-1,J-1)**2) + &
464  ! (G%CoriolisBu(I,J-1)**2 + G%CoriolisBu(I-1,J)**2))
465  kmag2 = (freq**2 - f2) / (cn(i,j)**2 + cg_subro**2)
466 
467  ! Calculate terms in vertically integrated energy equation
468  int_dwdz2 = 0.0 ; int_w2 = 0.0 ; int_n2w2 = 0.0
469  u_strct2 = u_strct(1:nzm)**2
470  w_strct2 = w_strct(1:nzm)**2
471  ! vertical integration with Trapezoidal rule
472  do k=1,nzm-1
473  int_dwdz2 = int_dwdz2 + 0.5*(u_strct2(k)+u_strct2(k+1))*dz(k)
474  int_w2 = int_w2 + 0.5*(w_strct2(k)+w_strct2(k+1))*dz(k)
475  int_n2w2 = int_n2w2 + 0.5*(w_strct2(k)*n2(k)+w_strct2(k+1)*n2(k+1))*dz(k)
476  enddo
477 
478  ! Back-calculate amplitude from energy equation
479  if (kmag2 > 0.0) then
480  ke_term = 0.25*gv%Rho0*( (1+f2/freq**2)/kmag2*int_dwdz2 + int_w2 )
481  pe_term = 0.25*gv%Rho0*( int_n2w2/freq**2 )
482  if (en(i,j) >= 0.0) then
483  w0 = sqrt( en(i,j)/(ke_term + pe_term) )
484  else
485  call mom_error(warning, "wave_structure: En < 0.0; setting to W0 to 0.0")
486  print *, "En(i,j)=", en(i,j), " at ig=", ig, ", jg=", jg
487  w0 = 0.0
488  endif
489  ! Calculate actual vertical velocity profile and derivative
490  w_profile = w0*w_strct
491  dwdz_profile = w0*u_strct
492  ! Calculate average magnitude of actual horizontal velocity over a period
493  uavg_profile = abs(dwdz_profile) * sqrt((1+f2/freq**2)/(2.0*kmag2))
494  else
495  w_profile = 0.0
496  dwdz_profile = 0.0
497  uavg_profile = 0.0
498  endif
499 
500  ! Store values in control structure
501  cs%w_strct(i,j,1:nzm) = w_strct(:)
502  cs%u_strct(i,j,1:nzm) = u_strct(:)
503  cs%W_profile(i,j,1:nzm) = w_profile(:)
504  cs%Uavg_profile(i,j,1:nzm)= uavg_profile(:)
505  cs%z_depths(i,j,1:nzm) = us%Z_to_m*z_int(:)
506  cs%N2(i,j,1:nzm) = n2(:)
507  cs%num_intfaces(i,j) = nzm
508  else
509  ! If not enough layers, default to zero
510  nzm = kc+1
511  cs%w_strct(i,j,1:nzm) = 0.0
512  cs%u_strct(i,j,1:nzm) = 0.0
513  cs%W_profile(i,j,1:nzm) = 0.0
514  cs%Uavg_profile(i,j,1:nzm)= 0.0
515  cs%z_depths(i,j,1:nzm) = 0.0 ! could use actual values
516  cs%N2(i,j,1:nzm) = 0.0 ! could use with actual values
517  cs%num_intfaces(i,j) = nzm
518  endif ! kc >= 3 and kc > ModeNum + 1?
519  endif ! drxh_sum >= 0?
520  !else ! if at test point - delete later
521  ! return ! if at test point - delete later
522  !endif ! if at test point - delete later
523  endif ! mask2dT > 0.5?
524  else
525  ! if cn=0.0, default to zero
526  nzm = nz+1! could use actual values
527  cs%w_strct(i,j,1:nzm) = 0.0
528  cs%u_strct(i,j,1:nzm) = 0.0
529  cs%W_profile(i,j,1:nzm) = 0.0
530  cs%Uavg_profile(i,j,1:nzm)= 0.0
531  cs%z_depths(i,j,1:nzm) = 0.0 ! could use actual values
532  cs%N2(i,j,1:nzm) = 0.0 ! could use with actual values
533  cs%num_intfaces(i,j) = nzm
534  endif ; enddo ! if cn>0.0? ; i-loop
535  enddo ! j-loop
536 
537 end subroutine wave_structure
538 
539 !> Solves a tri-diagonal system Ax=y using either the standard
540 !! Thomas algorithm (TDMA_T) or its more stable variant that invokes the
541 !! "Hallberg substitution" (TDMA_H).
542 subroutine tridiag_solver(a, b, c, h, y, method, x)
543  real, dimension(:), intent(in) :: a !< lower diagonal with first entry equal to zero.
544  real, dimension(:), intent(in) :: b !< middle diagonal.
545  real, dimension(:), intent(in) :: c !< upper diagonal with last entry equal to zero.
546  real, dimension(:), intent(in) :: h !< vector of values that have already been added to b; used
547  !! for systems of the form (e.g. average layer thickness in vertical diffusion case):
548  !! [ -alpha(k-1/2) ] * e(k-1) +
549  !! [ alpha(k-1/2) + alpha(k+1/2) + h(k) ] * e(k) +
550  !! [ -alpha(k+1/2) ] * e(k+1) = y(k)
551  !! where a(k)=[-alpha(k-1/2)], b(k)=[alpha(k-1/2)+alpha(k+1/2) + h(k)],
552  !! and c(k)=[-alpha(k+1/2)]. Only used with TDMA_H method.
553  real, dimension(:), intent(in) :: y !< vector of known values on right hand side.
554  character(len=*), intent(in) :: method !< A string describing the algorithm to use
555  real, dimension(:), intent(out) :: x !< vector of unknown values to solve for.
556  ! Local variables
557  integer :: nrow ! number of rows in A matrix
558 ! real, allocatable, dimension(:,:) :: A_check ! for solution checking
559 ! real, allocatable, dimension(:) :: y_check ! for solution checking
560  real, allocatable, dimension(:) :: c_prime, y_prime, q, alpha
561  ! intermediate values for solvers
562  real :: Q_prime, beta ! intermediate values for solver
563  integer :: k ! row (e.g. interface) index
564  integer :: i,j
565 
566  nrow = size(y)
567  allocate(c_prime(nrow))
568  allocate(y_prime(nrow))
569  allocate(q(nrow))
570  allocate(alpha(nrow))
571 ! allocate(A_check(nrow,nrow))
572 ! allocate(y_check(nrow))
573 
574  if (method == 'TDMA_T') then
575  ! Standard Thomas algoritim (4th variant).
576  ! Note: Requires A to be non-singular for accuracy/stability
577  c_prime(:) = 0.0 ; y_prime(:) = 0.0
578  c_prime(1) = c(1)/b(1) ; y_prime(1) = y(1)/b(1)
579 
580  ! Forward sweep
581  do k=2,nrow-1
582  c_prime(k) = c(k)/(b(k)-a(k)*c_prime(k-1))
583  enddo
584  !print *, 'c_prime=', c_prime(1:nrow)
585  do k=2,nrow
586  y_prime(k) = (y(k)-a(k)*y_prime(k-1))/(b(k)-a(k)*c_prime(k-1))
587  enddo
588  !print *, 'y_prime=', y_prime(1:nrow)
589  x(nrow) = y_prime(nrow)
590 
591  ! Backward sweep
592  do k=nrow-1,1,-1
593  x(k) = y_prime(k)-c_prime(k)*x(k+1)
594  enddo
595  !print *, 'x=',x(1:nrow)
596 
597  ! Check results - delete later
598  !do j=1,nrow ; do i=1,nrow
599  ! if (i==j)then ; A_check(i,j) = b(i)
600  ! elseif (i==j+1)then ; A_check(i,j) = a(i)
601  ! elseif (i==j-1)then ; A_check(i,j) = c(i)
602  ! endif
603  !enddo ; enddo
604  !print *, 'A(2,1),A(2,2),A(1,2)=', A_check(2,1), A_check(2,2), A_check(1,2)
605  !y_check = matmul(A_check,x)
606  !if (all(y_check /= y))then
607  ! print *, "tridiag_solver: Uh oh, something's not right!"
608  ! print *, "y=", y
609  ! print *, "y_check=", y_check
610  !endif
611 
612  elseif (method == 'TDMA_H') then
613  ! Thomas algoritim (4th variant) w/ Hallberg substitution.
614  ! For a layered system where k is at interfaces, alpha{k+1/2} refers to
615  ! some property (e.g. inverse thickness for mode-structure problem) of the
616  ! layer below and alpha{k-1/2} refers to the layer above.
617  ! Here, alpha(k)=alpha{k+1/2} and alpha(k-1)=alpha{k-1/2}.
618  ! Strictly speaking, this formulation requires A to be a non-singular,
619  ! symmetric, diagonally dominant matrix, with h>0.
620  ! Need to add a check for these conditions.
621  do k=1,nrow-1
622  if (abs(a(k+1)-c(k)) > 1.e-10*(abs(a(k+1))+abs(c(k)))) then
623  call mom_error(warning, "tridiag_solver: matrix not symmetric; need symmetry when invoking TDMA_H")
624  endif
625  enddo
626  alpha = -c
627  ! Alpha of the bottom-most layer is not necessarily zero. Therefore,
628  ! back out the value from the provided b(nrow and h(nrow) values
629  alpha(nrow) = b(nrow)-h(nrow)-alpha(nrow-1)
630  ! Prime other variables
631  beta = 1/b(1)
632  y_prime(:) = 0.0 ; q(:) = 0.0
633  y_prime(1) = beta*y(1) ; q(1) = beta*alpha(1)
634  q_prime = 1-q(1)
635 
636  ! Forward sweep
637  do k=2,nrow-1
638  beta = 1/(h(k)+alpha(k-1)*q_prime+alpha(k))
639  if (isnan(beta))then ; print *, "Tridiag_solver: beta is NAN" ; endif
640  q(k) = beta*alpha(k)
641  y_prime(k) = beta*(y(k)+alpha(k-1)*y_prime(k-1))
642  q_prime = beta*(h(k)+alpha(k-1)*q_prime)
643  enddo
644  if ((h(nrow)+alpha(nrow-1)*q_prime+alpha(nrow)) == 0.0)then
645  call mom_error(fatal, "Tridiag_solver: this system is not stable.") ! ; overriding beta(nrow)
646  ! This has hard-coded dimensions: beta = 1/(1e-15) ! place holder for unstable systems - delete later
647  else
648  beta = 1/(h(nrow)+alpha(nrow-1)*q_prime+alpha(nrow))
649  endif
650  y_prime(nrow) = beta*(y(nrow)+alpha(nrow-1)*y_prime(nrow-1))
651  x(nrow) = y_prime(nrow)
652  ! Backward sweep
653  do k=nrow-1,1,-1
654  x(k) = y_prime(k)+q(k)*x(k+1)
655  enddo
656  !print *, 'yprime=',y_prime(1:nrow)
657  !print *, 'x=',x(1:nrow)
658  endif
659 
660  deallocate(c_prime,y_prime,q,alpha)
661 ! deallocate(A_check,y_check)
662 
663 end subroutine tridiag_solver
664 
665 !> Allocate memory associated with the wave structure module and read parameters.
666 subroutine wave_structure_init(Time, G, param_file, diag, CS)
667  type(time_type), intent(in) :: time !< The current model time.
668  type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure.
669  type(param_file_type), intent(in) :: param_file !< A structure to parse for run-time
670  !! parameters.
671  type(diag_ctrl), target, intent(in) :: diag !< A structure that is used to regulate
672  !! diagnostic output.
673  type(wave_structure_cs), pointer :: cs !< A pointer that is set to point to the
674  !! control structure for this module.
675 ! This include declares and sets the variable "version".
676 #include "version_variable.h"
677  character(len=40) :: mdl = "MOM_wave_structure" ! This module's name.
678  integer :: isd, ied, jsd, jed, nz
679 
680  isd = g%isd ; ied = g%ied ; jsd = g%jsd ; jed = g%jed ; nz = g%ke
681 
682  if (associated(cs)) then
683  call mom_error(warning, "wave_structure_init called with an "// &
684  "associated control structure.")
685  return
686  else ; allocate(cs) ; endif
687 
688  call get_param(param_file, mdl, "INTERNAL_TIDE_SOURCE_X", cs%int_tide_source_x, &
689  "X Location of generation site for internal tide", default=1.)
690  call get_param(param_file, mdl, "INTERNAL_TIDE_SOURCE_Y", cs%int_tide_source_y, &
691  "Y Location of generation site for internal tide", default=1.)
692 
693  cs%diag => diag
694 
695  ! Allocate memory for variable in control structure; note,
696  ! not all rows will be filled if layers get merged!
697  allocate(cs%w_strct(isd:ied,jsd:jed,nz+1))
698  allocate(cs%u_strct(isd:ied,jsd:jed,nz+1))
699  allocate(cs%W_profile(isd:ied,jsd:jed,nz+1))
700  allocate(cs%Uavg_profile(isd:ied,jsd:jed,nz+1))
701  allocate(cs%z_depths(isd:ied,jsd:jed,nz+1))
702  allocate(cs%N2(isd:ied,jsd:jed,nz+1))
703  allocate(cs%num_intfaces(isd:ied,jsd:jed))
704 
705  ! Write all relevant parameters to the model log.
706  call log_version(param_file, mdl, version, "")
707 
708 end subroutine wave_structure_init
709 
710 end module mom_wave_structure
mom_verticalgrid
Provides a transparent vertical ocean grid type and supporting routines.
Definition: MOM_verticalGrid.F90:2
mom_file_parser::log_version
An overloaded interface to log version information about modules.
Definition: MOM_file_parser.F90:109
mom_diag_mediator
The subroutines here provide convenient wrappers to the fms diag_manager interfaces with additional d...
Definition: MOM_diag_mediator.F90:3
mom_variables::thermo_var_ptrs
Pointers to an assortment of thermodynamic fields that may be available, including potential temperat...
Definition: MOM_variables.F90:82
mom_eos
Provides subroutines for quantities specific to the equation of state.
Definition: MOM_EOS.F90:2
mom_file_parser::param_file_type
A structure that can be parsed to read and document run-time parameters.
Definition: MOM_file_parser.F90:54
mom_file_parser::get_param
An overloaded interface to read and log the values of various types of parameters.
Definition: MOM_file_parser.F90:102
mom_wave_structure
Vertical structure functions for first baroclinic mode wave speed.
Definition: MOM_wave_structure.F90:2
mom_unit_scaling::unit_scale_type
Describes various unit conversion factors.
Definition: MOM_unit_scaling.F90:14
mom_diag_mediator::post_data
Make a diagnostic available for averaging or output.
Definition: MOM_diag_mediator.F90:70
mom_verticalgrid::verticalgrid_type
Describes the vertical ocean grid, including unit conversion factors.
Definition: MOM_verticalGrid.F90:24
mom_variables
Provides transparent structures with groups of MOM6 variables and supporting routines.
Definition: MOM_variables.F90:2
mom_wave_structure::wave_structure_cs
The control structure for the MOM_wave_structure module.
Definition: MOM_wave_structure.F90:36
mom_file_parser
The MOM6 facility to parse input files for runtime parameters.
Definition: MOM_file_parser.F90:2
mom_eos::calculate_density_derivs
Calculate the derivatives of density with temperature and salinity from T, S, and P.
Definition: MOM_EOS.F90:70
mom_grid
Provides the ocean grid type.
Definition: MOM_grid.F90:2
mom_unit_scaling
Provides a transparent unit rescaling type to facilitate dimensional consistency testing.
Definition: MOM_unit_scaling.F90:2
mom_debugging
Provides checksumming functions for debugging.
Definition: MOM_debugging.F90:7
mom_error_handler
Routines for error handling and I/O management.
Definition: MOM_error_handler.F90:2
mom_grid::ocean_grid_type
Ocean grid type. See mom_grid for details.
Definition: MOM_grid.F90:25
mom_diag_mediator::diag_ctrl
The following data type a list of diagnostic fields an their variants, as well as variables that cont...
Definition: MOM_diag_mediator.F90:239