MOM6
midas_vertmap.F90
1 !> Routines for initialization callable from MOM6 or Python (MIDAS)
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 ! If calling from MOM6, use MOM6 interfaces for EOS functions
7 #ifndef PY_SOLO
9 
10 implicit none ; private
11 
12 public tracer_z_init, determine_temperature, fill_boundaries
13 public find_interfaces, meshgrid
14 #endif
15 
16 ! A note on unit descriptions in comments: MOM6 uses units that can be rescaled for dimensional
17 ! consistency testing. These are noted in comments with units like Z, H, L, and T, along with
18 ! their mks counterparts with notation like "a velocity [Z T-1 ~> m s-1]". If the units
19 ! vary with the Boussinesq approximation, the Boussinesq variant is given first.
20 
21 !> Fill grid edges
22 interface fill_boundaries
23  module procedure fill_boundaries_real
24  module procedure fill_boundaries_int
25 end interface
26 
27 ! real, parameter :: epsln=1.e-10 !< A hard-wired constant!
28  !! \todo Get rid of this constant
29 
30 contains
31 
32 #ifdef PY_SOLO
33 !> Calculate seawater equation of state, given T[degC], S[PSU], and p[Pa]
34 !! Returns density [kg m-3]
35 !!
36 !! These EOS routines are needed only for the stand-alone version of the code
37 !! The subroutines in this file implement the equation of state for
38 !! sea water using the formulae given by Wright, 1997, J. Atmos.
39 !! Ocean. Tech., 14, 735-740.
40 function wright_eos_2d(T,S,p) result(rho)
41  real(kind=8), dimension(:,:), intent(in) :: t,s !< temperature [degC] and Salinity [psu]
42  real, intent(in) :: p !< pressure [Pa]
43  real(kind=8), dimension(size(t,1),size(t,2)) :: rho !< potential density [kg m-3]
44  ! Local variables
45  real(kind=8) :: a0,a1,a2,b0,b1,b2,b3,b4,b5,c0,c1,c2,c3,c4,c5
46  real(kind=8) :: al0,lam,p0,i_denom
47  integer :: i,k
48 
49  a0 = 7.057924e-4; a1 = 3.480336e-7; a2 = -1.112733e-7
50  b0 = 5.790749e8; b1 = 3.516535e6; b2 = -4.002714e4
51  b3 = 2.084372e2; b4 = 5.944068e5; b5 = -9.643486e3
52  c0 = 1.704853e5; c1 = 7.904722e2; c2 = -7.984422
53  c3 = 5.140652e-2; c4 = -2.302158e2; c5 = -3.079464
54 
55  do k=1,size(t,2)
56  do i=1,size(t,1)
57  al0 = a0 + a1*t(i,k) +a2*s(i,k)
58  p0 = b0 + b4*s(i,k) + t(i,k) * (b1 + t(i,k)*(b2 + &
59  b3*t(i,k)) + b5*s(i,k))
60  lam = c0 +c4*s(i,k) + t(i,k) * (c1 + t(i,k)*(c2 + &
61  c3*t(i,k)) + c5*s(i,k))
62  i_denom = 1.0 / (lam + al0*(p+p0))
63  rho(i,k) = (p + p0) * i_denom
64  enddo
65  enddo
66 
67  return
68 end function wright_eos_2d
69 
70 !> Calculate seawater thermal expansion coefficient given T[degC],S[PSU],p[Pa]
71 !! Returns density [kg m-3 degC-1]
72 !!
73 !! The subroutines in this file implement the equation of state for
74 !! sea water using the formulae given by Wright, 1997, J. Atmos.
75 !! Ocean. Tech., 14, 735-740.
76 function alpha_wright_eos_2d(T,S,p) result(drho_dT)
77  real(kind=8), dimension(:,:), intent(in) :: t,s !< temperature [degC] and Salinity [psu]
78  real, intent(in) :: p !< pressure [Pa]
79  real(kind=8), dimension(size(t,1),size(t,2)) :: drho_dt !< partial derivative of density with
80  !! respect to temperature [kg m-3 degC-1]
81  ! Local variables
82  real(kind=8) :: a0,a1,a2,b0,b1,b2,b3,b4,b5,c0,c1,c2,c3,c4,c5
83  real(kind=8) :: al0,lam,p0,i_denom,i_denom2
84  integer :: i,k
85 
86  a0 = 7.057924e-4; a1 = 3.480336e-7; a2 = -1.112733e-7
87  b0 = 5.790749e8; b1 = 3.516535e6; b2 = -4.002714e4
88  b3 = 2.084372e2; b4 = 5.944068e5; b5 = -9.643486e3
89  c0 = 1.704853e5; c1 = 7.904722e2; c2 = -7.984422
90  c3 = 5.140652e-2; c4 = -2.302158e2; c5 = -3.079464
91 
92  do k=1,size(t,2)
93  do i=1,size(t,1)
94  al0 = a0 + a1*t(i,k) +a2*s(i,k)
95  p0 = b0 + b4*s(i,k) + t(i,k) * (b1 + t(i,k)*(b2 + &
96  b3*t(i,k)) + b5*s(i,k))
97  lam = c0 +c4*s(i,k) + t(i,k) * (c1 + t(i,k)*(c2 + &
98  c3*t(i,k)) + c5*s(i,k))
99  i_denom = 1.0 / (lam + al0*(p+p0))
100  i_denom2 = i_denom*i_denom
101  drho_dt(i,k) = i_denom2*(lam*(b1+t(i,k)*(2*b2 + &
102  3*b3*t(i,k)) + b5*s(i,k)) - (p+p0)*((p+p0)*a1 + &
103  (c1+t(i,k)*(2*c2 + 3*c3*t(i,k)) + c5*s(i,k))))
104  enddo
105  enddo
106 
107  return
108 end function alpha_wright_eos_2d
109 
110 !> Calculate seawater haline expansion coefficient given T[degC],S[PSU],p[Pa]
111 !! Returns density [kg m-3 PSU-1]
112 !!
113 !! The subroutines in this file implement the equation of state for
114 !! sea water using the formulae given by Wright, 1997, J. Atmos.
115 !! Ocean. Tech., 14, 735-740.
116 function beta_wright_eos_2d(T,S,p) result(drho_dS)
117  real(kind=8), dimension(:,:), intent(in) :: t,s !< temperature [degC] and salinity [psu]
118  real, intent(in) :: p !< pressure [Pa]
119  real(kind=8), dimension(size(t,1),size(t,2)) :: drho_ds !< partial derivative of density with
120  !! respect to salinity [kg m-3 PSU-1]
121  ! Local variables
122  real(kind=8) :: a0,a1,a2,b0,b1,b2,b3,b4,b5,c0,c1,c2,c3,c4,c5
123  real(kind=8) :: al0,lam,p0,i_denom,i_denom2
124  integer :: i,k
125 
126  a0 = 7.057924e-4; a1 = 3.480336e-7; a2 = -1.112733e-7
127  b0 = 5.790749e8; b1 = 3.516535e6; b2 = -4.002714e4
128  b3 = 2.084372e2; b4 = 5.944068e5; b5 = -9.643486e3
129  c0 = 1.704853e5; c1 = 7.904722e2; c2 = -7.984422
130  c3 = 5.140652e-2; c4 = -2.302158e2; c5 = -3.079464
131 
132  do k=1,size(t,2)
133  do i=1,size(t,1)
134  al0 = a0 + a1*t(i,k) +a2*s(i,k)
135  p0 = b0 + b4*s(i,k) + t(i,k) * (b1 + t(i,k)*(b2 + &
136  b3*t(i,k)) + b5*s(i,k))
137  lam = c0 +c4*s(i,k) + t(i,k) * (c1 + t(i,k)*(c2 + &
138  c3*t(i,k)) + c5*s(i,k))
139  i_denom = 1.0 / (lam + al0*(p+p0))
140  i_denom2 = i_denom*i_denom
141  drho_ds(i,k) = i_denom2*(lam*(b4+b5*t(i,k)) - &
142  (p+p0)*((p+p0)*a2 + (c4+c5*t(i,k))))
143  enddo
144  enddo
145 
146  return
147 end function beta_wright_eos_2d
148 #endif
149 
150 !> Layer model routine for remapping tracers
151 function tracer_z_init(tr_in, z_edges, e, nkml, nkbl, land_fill, wet, nlay, nlevs, &
152  debug, i_debug, j_debug, eps_z) result(tr)
153  real, dimension(:,:,:), intent(in) :: tr_in !< The z-space array of tracer concentrations that is read in.
154  real, dimension(size(tr_in,3)+1), intent(in) :: z_edges !< The depths of the cell edges in the input z* data
155  !! [Z ~> m or m]
156  integer, intent(in) :: nlay !< The number of vertical layers in the target grid
157  real, dimension(size(tr_in,1),size(tr_in,2),nlay+1), &
158  intent(in) :: e !< The depths of the target layer interfaces [Z ~> m or m]
159  integer, intent(in) :: nkml !< The number of mixed layers
160  integer, intent(in) :: nkbl !< The number of buffer layers
161  real, intent(in) :: land_fill !< fill in data over land (1)
162  real, dimension(size(tr_in,1),size(tr_in,2)), &
163  intent(in) :: wet !< The wet mask for the source data (valid points)
164  real, dimension(size(tr_in,1),size(tr_in,2)), &
165  optional, intent(in) :: nlevs !< The number of input levels with valid data
166  logical, optional, intent(in) :: debug !< optional debug flag
167  integer, optional, intent(in) :: i_debug !< i-index of point for debugging
168  integer, optional, intent(in) :: j_debug !< j-index of point for debugging
169  real, optional, intent(in) :: eps_z !< A negligibly small layer thickness [Z ~> m or m].
170  real, dimension(size(tr_in,1),size(tr_in,2),nlay) :: tr !< tracers in layer space
171 
172  ! Local variables
173  real, dimension(size(tr_in,3)) :: tr_1d !< a copy of the input tracer concentrations in a column.
174  real, dimension(nlay+1) :: e_1d ! A 1-d column of intreface heights, in the same units as e.
175  real, dimension(nlay) :: tr_ ! A 1-d column of tracer concentrations
176  integer, dimension(size(tr_in,1),size(tr_in,2)) :: nlevs_data !< number of valid levels in the input dataset
177  integer :: n,i,j,k,l,nx,ny,nz,nt,kz
178  integer :: k_top,k_bot,k_bot_prev,kk,kstart
179  real :: sl_tr ! The tracer concentration slope times the layer thickness, in tracer units.
180  real :: epsln_z ! A negligibly thin layer thickness [Z ~> m].
181  real, dimension(size(tr_in,3)) :: wt !< The fractional weight for each layer in the range between z1 and z2
182  real, dimension(size(tr_in,3)) :: z1, z2 ! z1 and z2 are the fractional depths of the top and bottom
183  ! limits of the part of a z-cell that contributes to a layer, relative
184  ! to the cell center and normalized by the cell thickness [nondim].
185  ! Note that -1/2 <= z1 <= z2 <= 1/2.
186 
187  logical :: debug_msg, debug_, debug_pt
188 
189  nx = size(tr_in,1); ny=size(tr_in,2); nz = size(tr_in,3)
190 
191  nlevs_data = size(tr_in,3)
192  if (PRESENT(nlevs)) nlevs_data = anint(nlevs)
193  epsln_z = 1.0e-10 ; if (PRESENT(eps_z)) epsln_z = eps_z
194 
195  debug_=.false. ; if (PRESENT(debug)) debug_ = debug
196  debug_msg = debug_
197  debug_pt = debug_ ; if (PRESENT(i_debug) .and. PRESENT(j_debug)) debug_pt = debug_
198 
199  do j=1,ny
200  i_loop: do i=1,nx
201  if (nlevs_data(i,j) == 0 .or. wet(i,j) == 0.) then
202  tr(i,j,:) = land_fill
203  cycle i_loop
204  endif
205 
206  do k=1,nz
207  tr_1d(k) = tr_in(i,j,k)
208  enddo
209 
210  do k=1,nlay+1
211  e_1d(k) = e(i,j,k)
212  enddo
213  k_bot = 1 ; k_bot_prev = -1
214  do k=1,nlay
215  if (e_1d(k+1) > z_edges(1)) then
216  tr(i,j,k) = tr_1d(1)
217  elseif (e_1d(k) < z_edges(nlevs_data(i,j)+1)) then
218  if (debug_msg) then
219  print *,'*** WARNING : Found interface below valid range of z data '
220  print *,'(i,j,z_bottom,interface)= ',&
221  i,j,z_edges(nlevs_data(i,j)+1),e_1d(k)
222  print *,'z_edges= ',z_edges
223  print *,'e=',e_1d
224  print *,'*** I will extrapolate below using the bottom-most valid values'
225  debug_msg = .false.
226  endif
227  tr(i,j,k) = tr_1d(nlevs_data(i,j))
228 
229  else
230  kstart=k_bot
231  call find_overlap(z_edges, e_1d(k), e_1d(k+1), nlevs_data(i,j), &
232  kstart, k_top, k_bot, wt, z1, z2)
233 
234  if (debug_pt) then ; if ((i == i_debug) .and. (j == j_debug)) then
235  print *,'0001 k,k_top,k_bot,sum(wt),sum(z2-z1) = ',k,k_top,k_bot,sum(wt),sum(z2-z1)
236  endif ; endif
237  kz = k_top
238  sl_tr=0.0; ! cur_tr=0.0
239  if (kz /= k_bot_prev) then
240  ! Calculate the intra-cell profile.
241  if ((kz < nlevs_data(i,j)) .and. (kz > 1)) then
242  sl_tr = find_limited_slope(tr_1d, z_edges, kz)
243  endif
244  endif
245  if (kz > nlevs_data(i,j)) kz = nlevs_data(i,j)
246  ! This is the piecewise linear form.
247  tr(i,j,k) = wt(kz) * (tr_1d(kz) + 0.5*sl_tr*(z2(kz) + z1(kz)))
248  ! For the piecewise parabolic form add the following...
249  ! + C1_3*wt(kz) * cur_tr*(z2(kz)**2 + z2(kz)*z1(kz) + z1(kz)**2))
250  if (debug_pt) then ; if ((i == i_debug) .and. (j == j_debug)) then
251  print *,'0002 k,k_top,k_bot,k_bot_prev,sl_tr = ',k,k_top,k_bot,k_bot_prev,sl_tr
252  endif ; endif
253 
254  do kz=k_top+1,k_bot-1
255  tr(i,j,k) = tr(i,j,k) + wt(kz)*tr_1d(kz)
256  enddo
257 
258  if (debug_pt) then ; if ((i == i_debug) .and. (j == j_debug)) then
259  print *,'0003 k,tr = ',k,tr(i,j,k)
260  endif ; endif
261 
262  if (k_bot > k_top) then
263  kz = k_bot
264  ! Calculate the intra-cell profile.
265  sl_tr = 0.0 ! ; cur_tr = 0.0
266  if ((kz < nlevs_data(i,j)) .and. (kz > 1)) then
267  sl_tr = find_limited_slope(tr_1d, z_edges, kz)
268  endif
269  ! This is the piecewise linear form.
270  tr(i,j,k) = tr(i,j,k) + wt(kz) * &
271  (tr_1d(kz) + 0.5*sl_tr*(z2(kz) + z1(kz)))
272  ! For the piecewise parabolic form add the following...
273  ! + C1_3*cur_tr*(z2(kz)**2 + z2(kz)*z1(kz) + z1(kz)**2))
274 
275  if (debug_pt) then ; if ((i == i_debug) .and. (j == j_debug)) then
276  print *,'0004 k,kz,nlevs,sl_tr,tr = ',k,kz,nlevs_data(i,j),sl_tr,tr(i,j,k)
277  print *,'0005 k,kz,tr(kz),tr(kz-1),tr(kz+1) = ',k,kz,tr_1d(kz),tr_1d(kz-1),tr_1d(kz+1),z_edges(kz+2)
278  endif ; endif
279 
280  endif
281  k_bot_prev = k_bot
282 
283  endif
284  enddo ! k-loop
285 
286  do k=2,nlay ! simply fill vanished layers with adjacent value
287  if (e_1d(k)-e_1d(k+1) <= epsln_z) tr(i,j,k)=tr(i,j,k-1)
288  enddo
289 
290  enddo i_loop
291  enddo
292 
293 end function tracer_z_init
294 
295 !> Return the index where to insert item x in list a, assuming a is sorted.
296 !! The return values [i] is such that all e in a[:i-1] have e <= x, and all e in
297 !! a[i:] have e > x. So if x already appears in the list, will
298 !! insert just after the rightmost x already there.
299 !! Optional args lo (default 1) and hi (default len(a)) bound the
300 !! slice of a to be searched.
301 function bisect_fast(a, x, lo, hi) result(bi_r)
302  real, dimension(:,:), intent(in) :: a !< Sorted list
303  real, dimension(:), intent(in) :: x !< Item to be inserted
304  integer, dimension(size(a,1)), optional, intent(in) :: lo !< Lower bracket of optional range to search
305  integer, dimension(size(a,1)), optional, intent(in) :: hi !< Upper bracket of optional range to search
306  integer, dimension(size(a,1),size(x,1)) :: bi_r
307 
308  integer :: mid,num_x,num_a,i
309  integer, dimension(size(a,1)) :: lo_,hi_,lo0,hi0
310  integer :: nprofs,j
311 
312  lo_=1;hi_=size(a,2);num_x=size(x,1);bi_r=-1;nprofs=size(a,1)
313 
314  if (PRESENT(lo)) then
315  where (lo>0) lo_=lo
316  endif
317  if (PRESENT(hi)) then
318  where (hi>0) hi_=hi
319  endif
320 
321  lo0=lo_;hi0=hi_
322 
323  do j=1,nprofs
324  do i=1,num_x
325  lo_=lo0;hi_=hi0
326  do while (lo_(j) < hi_(j))
327  mid = (lo_(j)+hi_(j))/2
328  if (x(i) < a(j,mid)) then
329  hi_(j) = mid
330  else
331  lo_(j) = mid+1
332  endif
333  enddo
334  bi_r(j,i)=lo_(j)
335  enddo
336  enddo
337 
338 
339  return
340 
341 end function bisect_fast
342 
343 #ifdef PY_SOLO
344 ! Only for stand-alone python
345 
346 !> This subroutine determines the potential temperature and salinity that
347 !! is consistent with the target density using provided initial guess
348 subroutine determine_temperature(temp, salt, R, p_ref, niter, land_fill, h, k_start)
349  real(kind=8), dimension(:,:,:), intent(inout) :: temp !< potential temperature [degC]
350  real(kind=8), dimension(:,:,:), intent(inout) :: salt !< salinity [PSU]
351  real(kind=8), dimension(size(temp,3)), intent(in) :: r !< desired potential density [kg m-3].
352  real, intent(in) :: p_ref !< reference pressure [Pa].
353  integer, intent(in) :: niter !< maximum number of iterations
354  integer, intent(in) :: k_start !< starting index (i.e. below the buffer layer)
355  real, intent(in) :: land_fill !< land fill value
356  real(kind=8), dimension(:,:,:), intent(in) :: h !< layer thickness . Do not iterate for massless layers
357 
358  ! Local variables
359  real, parameter :: t_max = 35.0, t_min = -2.0
360 #else
361 !> This subroutine determines the potential temperature and salinity that
362 !! is consistent with the target density using provided initial guess
363 subroutine determine_temperature(temp, salt, R, p_ref, niter, land_fill, h, k_start, eos)
364  real, dimension(:,:,:), intent(inout) :: temp !< potential temperature [degC]
365  real, dimension(:,:,:), intent(inout) :: salt !< salinity [PSU]
366  real, dimension(size(temp,3)), intent(in) :: r !< desired potential density [kg m-3].
367  real, intent(in) :: p_ref !< reference pressure [Pa].
368  integer, intent(in) :: niter !< maximum number of iterations
369  integer, intent(in) :: k_start !< starting index (i.e. below the buffer layer)
370  real, intent(in) :: land_fill !< land fill value
371  real, dimension(:,:,:), intent(in) :: h !< layer thickness, used only to avoid working on massless layers
372  type(eos_type), pointer :: eos !< seawater equation of state control structure
373 
374  real, parameter :: t_max = 31.0, t_min = -2.0
375 #endif
376  ! Local variables (All of which need documentation!)
377  real(kind=8), dimension(size(temp,1),size(temp,3)) :: t, s, dt, ds, rho, hin
378  real(kind=8), dimension(size(temp,1),size(temp,3)) :: drho_dt, drho_ds
379  real(kind=8), dimension(size(temp,1)) :: press
380  integer :: nx, ny, nz, nt, i, j, k, n, itt
381  real :: dt_ds
382  logical :: adjust_salt, old_fit
383  real, parameter :: s_min = 0.5, s_max=65.0
384  real, parameter :: tol=1.e-4, max_t_adj=1.0, max_s_adj = 0.5
385 
386  old_fit = .true. ! reproduces siena behavior
387  ! will switch to the newer method which simultaneously adjusts
388  ! temp and salt based on the ratio of the thermal and haline coefficients.
389 
390  nx=size(temp,1) ; ny=size(temp,2) ; nz=size(temp,3)
391 
392  press(:) = p_ref
393 
394  do j=1,ny
395  ds(:,:) = 0. ! Needs to be zero everywhere since there is a maxval(abs(dS)) later...
396  t=temp(:,j,:)
397  s=salt(:,j,:)
398  hin=h(:,j,:)
399  dt=0.0
400  adjust_salt = .true.
401  iter_loop: do itt = 1,niter
402 #ifdef PY_SOLO
403  rho=wright_eos_2d(t,s,p_ref)
404  drho_dt=alpha_wright_eos_2d(t,s,p_ref)
405 #else
406  do k=1, nz
407  call calculate_density(t(:,k), s(:,k), press, rho(:,k), 1, nx, eos)
408  call calculate_density_derivs(t(:,k), s(:,k), press, drho_dt(:,k), drho_ds(:,k), 1, nx, eos)
409  enddo
410 #endif
411  do k=k_start,nz ; do i=1,nx
412 
413 ! if (abs(rho(i,k)-R(k))>tol .and. hin(i,k)>epsln .and. abs(T(i,k)-land_fill) < epsln) then
414  if (abs(rho(i,k)-r(k))>tol) then
415  if (old_fit) then
416  dt(i,k) = max(min((r(k)-rho(i,k)) / drho_dt(i,k), max_t_adj), -max_t_adj)
417  t(i,k) = max(min(t(i,k)+dt(i,k), t_max), t_min)
418  else
419  dt_ds = 10.0 - min(-drho_dt(i,k)/drho_ds(i,k),10.)
420  !### RWH: Based on the dimensions alone, the expression above should be:
421  ! dT_dS = 10.0 - min(-drho_dS(i,k)/drho_dT(i,k),10.)
422  ds(i,k) = (r(k)-rho(i,k)) / (drho_ds(i,k) - drho_dt(i,k)*dt_ds )
423  dt(i,k) = -dt_ds*ds(i,k)
424  ! dT(i,k) = max(min(dT(i,k), max_t_adj), -max_t_adj)
425  t(i,k) = max(min(t(i,k)+dt(i,k), t_max), t_min)
426  s(i,k) = max(min(s(i,k)+ds(i,k), s_max), s_min)
427  endif
428  endif
429  enddo ; enddo
430  if (maxval(abs(dt)) < tol) then
431  adjust_salt = .false.
432  exit iter_loop
433  endif
434  enddo iter_loop
435 
436  if (adjust_salt .and. old_fit) then ; do itt = 1,niter
437 #ifdef PY_SOLO
438  rho = wright_eos_2d(t,s,p_ref)
439  drho_ds = beta_wright_eos_2d(t,s,p_ref)
440 #else
441  do k=1, nz
442  call calculate_density(t(:,k),s(:,k),press,rho(:,k),1,nx,eos)
443  call calculate_density_derivs(t(:,k),s(:,k),press,drho_dt(:,k),drho_ds(:,k),1,nx,eos)
444  enddo
445 #endif
446  do k=k_start,nz ; do i=1,nx
447 ! if (abs(rho(i,k)-R(k))>tol .and. hin(i,k)>epsln .and. abs(T(i,k)-land_fill) < epsln ) then
448  if (abs(rho(i,k)-r(k)) > tol) then
449  ds(i,k) = max(min((r(k)-rho(i,k)) / drho_ds(i,k), max_s_adj), -max_s_adj)
450  s(i,k) = max(min(s(i,k)+ds(i,k), s_max), s_min)
451  endif
452  enddo ; enddo
453  if (maxval(abs(ds)) < tol) exit
454  enddo ; endif
455 
456  temp(:,j,:)=t(:,:)
457  salt(:,j,:)=s(:,:)
458  enddo
459 
460 end subroutine determine_temperature
461 
462 !> This subroutine determines the layers bounded by interfaces e that overlap
463 !! with the depth range between Z_top and Z_bot, and also the fractional weights
464 !! of each layer. It also calculates the normalized relative depths of the range
465 !! of each layer that overlaps that depth range.
466 !! Note that by convention, e decreases with increasing k and Z_top > Z_bot.
467 subroutine find_overlap(e, Z_top, Z_bot, k_max, k_start, k_top, k_bot, wt, z1, z2)
468  real, dimension(:), intent(in) :: e !< The interface positions, [Z ~> m] or other units.
469  real, intent(in) :: Z_top !< The top of the range being mapped to, [Z ~> m] or other units.
470  real, intent(in) :: Z_bot !< The bottom of the range being mapped to, [Z ~> m] or other units.
471  integer, intent(in) :: k_max !< The number of valid layers.
472  integer, intent(in) :: k_start !< The layer at which to start searching.
473  integer, intent(out) :: k_top !< The index of the top layer that overlap with the depth range.
474  integer, intent(out) :: k_bot !< The index of the bottom layer that overlap with the depth range.
475  real, dimension(:), intent(out) :: wt !< The relative weights of each layer from k_top to k_bot [nondim].
476  real, dimension(:), intent(out) :: z1 !< Depth of the top limit of layer that contributes to a level [nondim].
477  real, dimension(:), intent(out) :: z2 !< Depth of the bottom limit of layer that contributes to a level [nondim].
478 
479  ! Local variables
480  real :: Ih, e_c, tot_wt, I_totwt
481  integer :: k
482 
483  wt(:)=0.0 ; z1(:)=0.0 ; z2(:)=0.0
484  k_top = k_start ; k_bot = k_start ; wt(1) = 1.0 ; z1(1) = -0.5 ; z2(1) = 0.5
485 
486  do k=k_start,k_max ; if (e(k+1) < z_top) exit ; enddo
487  k_top = k
488 
489  if (k>k_max) return
490 
491  ! Determine the fractional weights of each layer.
492  ! Note that by convention, e and Z_int decrease with increasing k.
493  if (e(k+1) <= z_bot) then
494  wt(k) = 1.0 ; k_bot = k
495  ih = 0.0 ; if (e(k) /= e(k+1)) ih = 1.0 / (e(k)-e(k+1))
496  e_c = 0.5*(e(k)+e(k+1))
497  z1(k) = (e_c - min(e(k), z_top)) * ih
498  z2(k) = (e_c - z_bot) * ih
499  else
500  wt(k) = min(e(k),z_top) - e(k+1) ; tot_wt = wt(k) ! These are always > 0.
501  ! Ih = 0.0 ; if (e(K) /= e(K+1)) Ih = 1.0 / (e(K)-e(K+1))
502  if (e(k) /= e(k+1)) then
503  z1(k) = (0.5*(e(k)+e(k+1)) - min(e(k), z_top)) / (e(k)-e(k+1))
504  else ; z1(k) = -0.5 ; endif
505  z2(k) = 0.5
506  k_bot = k_max
507  do k=k_top+1,k_max
508  if (e(k+1) <= z_bot) then
509  k_bot = k
510  wt(k) = e(k) - z_bot ; z1(k) = -0.5
511  if (e(k) /= e(k+1)) then
512  z2(k) = (0.5*(e(k)+e(k+1)) - z_bot) / (e(k)-e(k+1))
513  else ; z2(k) = 0.5 ; endif
514  else
515  wt(k) = e(k) - e(k+1) ; z1(k) = -0.5 ; z2(k) = 0.5
516  endif
517  tot_wt = tot_wt + wt(k) ! wt(k) is always > 0.
518  if (k>=k_bot) exit
519  enddo
520 
521  i_totwt = 0.0 ; if (tot_wt > 0.0) i_totwt = 1.0 / tot_wt
522  do k=k_top,k_bot ; wt(k) = i_totwt*wt(k) ; enddo
523  endif
524 
525 end subroutine find_overlap
526 
527 !> This subroutine determines a limited slope for val to be advected with
528 !! a piecewise limited scheme.
529 function find_limited_slope(val, e, k) result(slope)
530  real, dimension(:), intent(in) :: val !< An column the values that are being interpolated.
531  real, dimension(:), intent(in) :: e !< A column's interface heights [Z ~> m] or other units.
532  integer, intent(in) :: k !< The layer whose slope is being determined.
533  real :: slope !< The normalized slope in the intracell distribution of val.
534  ! Local variables
535  real :: amn, cmn
536  real :: d1, d2
537 
538  if ((val(k)-val(k-1)) * (val(k)-val(k+1)) >= 0.0) then
539  slope = 0.0 ! ; curvature = 0.0
540  else
541  d1 = 0.5*(e(k-1)-e(k+1)) ; d2 = 0.5*(e(k)-e(k+2))
542  if (d1*d2 > 0.0) then
543  slope = ((d1**2)*(val(k+1) - val(k)) + (d2**2)*(val(k) - val(k-1))) * &
544  (e(k) - e(k+1)) / (d1*d2*(d1+d2))
545  ! slope = 0.5*(val(k+1) - val(k-1))
546  ! This is S.J. Lin's form of the PLM limiter.
547  amn = min(abs(slope), 2.0*(max(val(k-1), val(k), val(k+1)) - val(k)))
548  cmn = 2.0*(val(k) - min(val(k-1), val(k), val(k+1)))
549  slope = sign(1.0, slope) * min(amn, cmn)
550 
551  ! min(abs(slope), 2.0*(max(val(k-1),val(k),val(k+1)) - val(k)), &
552  ! 2.0*(val(k) - min(val(k-1),val(k),val(k+1))))
553  ! curvature = 0.0
554  else
555  slope = 0.0 ! ; curvature = 0.0
556  endif
557  endif
558 
559 end function find_limited_slope
560 
561 !> Find interface positions corresponding to density profile
562 function find_interfaces(rho, zin, Rb, depth, nlevs, nkml, nkbl, hml, debug, eps_z) result(zi)
563  real, dimension(:,:,:), &
564  intent(in) :: rho !< potential density in z-space [kg m-3]
565  real, dimension(size(rho,3)), &
566  intent(in) :: zin !< Input data levels [Z ~> m or m].
567  real, dimension(:), intent(in) :: rb !< target interface densities [kg m-3]
568  real, dimension(size(rho,1),size(rho,2)), &
569  intent(in) :: depth !< ocean depth [Z ~> m].
570  real, dimension(size(rho,1),size(rho,2)), &
571  optional, intent(in) :: nlevs !< number of valid points in each column
572  logical, optional, intent(in) :: debug !< optional debug flag
573  integer, optional, intent(in) :: nkml !< number of mixed layer pieces
574  integer, optional, intent(in) :: nkbl !< number of buffer layer pieces
575  real, optional, intent(in) :: hml !< mixed layer depth [Z ~> m].
576  real, optional, intent(in) :: eps_z !< A negligibly small layer thickness [Z ~> m or m].
577  real, dimension(size(rho,1),size(rho,2),size(Rb,1)) :: zi !< The returned interface, in the same units az zin.
578 
579  ! Local variables
580  real, dimension(size(rho,1),size(rho,3)) :: rho_
581  real, dimension(size(rho,1)) :: depth_
582  logical :: unstable
583  integer :: dir
584  integer, dimension(size(rho,1),size(Rb,1)) :: ki_
585  real, dimension(size(rho,1),size(Rb,1)) :: zi_
586  integer, dimension(size(rho,1),size(rho,2)) :: nlevs_data
587  integer, dimension(size(rho,1)) :: lo, hi
588  real :: slope,rsm,drhodz,hml_
589  integer :: n,i,j,k,l,nx,ny,nz,nt
590  integer :: nlay,kk,nkml_,nkbl_
591  logical :: debug_ = .false.
592  real :: epsln_z ! A negligibly thin layer thickness [Z ~> m].
593  real :: epsln_rho ! A negligibly small density change [kg m-3].
594  real, parameter :: zoff=0.999
595 
596  nlay=size(rb)-1
597 
598  zi(:,:,:) = 0.0
599 
600  if (PRESENT(debug)) debug_=debug
601 
602  nx = size(rho,1); ny=size(rho,2); nz = size(rho,3)
603  nlevs_data(:,:) = size(rho,3)
604 
605  nkml_ = 0 ; if (PRESENT(nkml)) nkml_ = max(0, nkml)
606  nkbl_ = 0 ; if (PRESENT(nkbl)) nkbl_ = max(0, nkbl)
607  hml_ = 0.0 ; if (PRESENT(hml)) hml_ = hml
608  epsln_z = 1.0e-10 ; if (PRESENT(eps_z)) epsln_z = eps_z
609  epsln_rho = 1.0e-10
610 
611  if (PRESENT(nlevs)) then
612  nlevs_data(:,:) = nlevs(:,:)
613  endif
614 
615  do j=1,ny
616  rho_(:,:) = rho(:,j,:)
617  i_loop: do i=1,nx
618  if (debug_) then
619  print *,'looking for interfaces, i,j,nlevs= ',i,j,nlevs_data(i,j)
620  print *,'initial density profile= ', rho_(i,:)
621  endif
622  unstable=.true.
623  dir=1
624  do while (unstable)
625  unstable=.false.
626  if (dir == 1) then
627  do k=2,nlevs_data(i,j)-1
628  if (rho_(i,k) - rho_(i,k-1) < 0.0 ) then
629  if (k == 2) then
630  rho_(i,k-1) = rho_(i,k)-epsln_rho
631  else
632  drhodz = (rho_(i,k+1)-rho_(i,k-1)) / (zin(k+1)-zin(k-1))
633  if (drhodz < 0.0) unstable=.true.
634  rho_(i,k) = rho_(i,k-1) + drhodz*zoff*(zin(k)-zin(k-1))
635  endif
636  endif
637  enddo
638  dir = -1*dir
639  else
640  do k=nlevs_data(i,j)-1,2,-1
641  if (rho_(i,k+1) - rho_(i,k) < 0.0) then
642  if (k == nlevs_data(i,j)-1) then
643  rho_(i,k+1) = rho_(i,k-1)+epsln_rho
644  else
645  drhodz = (rho_(i,k+1)-rho_(i,k-1))/(zin(k+1)-zin(k-1))
646  if (drhodz < 0.0) unstable=.true.
647  rho_(i,k) = rho_(i,k+1)-drhodz*(zin(k+1)-zin(k))
648  endif
649  endif
650  enddo
651  dir = -1*dir
652  endif
653  enddo
654  if (debug_) then
655  print *,'final density profile= ', rho_(i,:)
656  endif
657  enddo i_loop
658 
659  ki_(:,:) = 0
660  zi_(:,:) = 0.0
661  depth_(:) = -1.0*depth(:,j)
662  lo(:) = 1
663  hi(:) = nlevs_data(:,j)
664  ki_ = bisect_fast(rho_, rb, lo, hi)
665  ki_(:,:) = max(1, ki_(:,:)-1)
666  do i=1,nx
667  do l=2,nlay
668  slope = (zin(ki_(i,l)+1) - zin(ki_(i,l))) / max(rho_(i,ki_(i,l)+1) - rho_(i,ki_(i,l)),epsln_rho)
669  zi_(i,l) = -1.0*(zin(ki_(i,l)) + slope*(rb(l)-rho_(i,ki_(i,l))))
670  zi_(i,l) = max(zi_(i,l), depth_(i))
671  zi_(i,l) = min(zi_(i,l), -1.0*hml_)
672  enddo
673  zi_(i,nlay+1) = depth_(i)
674  do l=2,nkml_+1
675  zi_(i,l) = max(hml_*((1.0-real(l))/real(nkml_)), depth_(i))
676  enddo
677  do l=nlay,nkml_+2,-1
678  if (zi_(i,l) < zi_(i,l+1) + epsln_z) zi_(i,l) = zi_(i,l+1) + epsln_z
679  if (zi_(i,l) > -1.0*hml_) zi_(i,l) = max(-1.0*hml_, depth_(i))
680  enddo
681  enddo
682  zi(:,j,:) = zi_(:,:)
683  enddo
684 
685 end function find_interfaces
686 
687 !> Create a 2d-mesh of grid coordinates from 1-d arrays
688 subroutine meshgrid(x,y,x_T,y_T)
689  real, dimension(:), intent(in) :: x !< input x coordinates
690  real, dimension(:), intent(in) :: y !< input y coordinates
691  real, dimension(size(x,1),size(y,1)), intent(inout) :: x_t !< output 2-d version
692  real, dimension(size(x,1),size(y,1)), intent(inout) :: y_t !< output 2-d version
693 
694  integer :: ni,nj,i,j
695 
696  ni=size(x,1);nj=size(y,1)
697 
698  do j=1,nj
699  x_t(:,j)=x(:)
700  enddo
701 
702  do i=1,ni
703  y_t(i,:)=y(:)
704  enddo
705 
706  return
707 
708 end subroutine meshgrid
709 
710 !> Solve del2 (zi) = 0 using successive iterations
711 !! with a 5 point stencil. Only points fill==1 are
712 !! modified. Except where bad==1, information propagates
713 !! isotropically in index space. The resulting solution
714 !! in each region is an approximation to del2(zi)=0 subject to
715 !! boundary conditions along the valid points curve bounding this region.
716 subroutine smooth_heights(zi,fill,bad,sor,niter,cyclic_x, tripolar_n)
717  real, dimension(:,:), intent(inout) :: zi !< interface positions [m] or arbitrary
718  integer, dimension(size(zi,1),size(zi,2)), intent(in) :: fill !< points to be smoothed
719  integer, dimension(size(zi,1),size(zi,2)), intent(in) :: bad !< ignore these points
720  real, intent(in) :: sor !< successive over-relaxation coefficient (typically 0.6)
721  integer, intent(in) :: niter !< maximum number of iterations
722  logical, intent(in) :: cyclic_x !< input grid cyclic condition in the zonal direction
723  logical, intent(in) :: tripolar_n !< tripolar Arctic fold flag
724 
725  integer :: i,j,k,n
726  integer :: ni,nj
727 
728  real, dimension(size(zi,1),size(zi,2)) :: res, m
729  integer, dimension(size(zi,1),size(zi,2),4) :: B
730  real, dimension(0:size(zi,1)+1,0:size(zi,2)+1) :: mp
731  integer, dimension(0:size(zi,1)+1,0:size(zi,2)+1) :: nm
732 
733  real :: Isum, bsum
734 
735  ni=size(zi,1); nj=size(zi,2)
736 
737 
738  mp=fill_boundaries(zi,cyclic_x,tripolar_n)
739 
740  b(:,:,:)=0.0
741  nm=fill_boundaries(bad,cyclic_x,tripolar_n)
742 
743  do j=1,nj
744  do i=1,ni
745  if (fill(i,j) == 1) then
746  b(i,j,1)=1-nm(i+1,j);b(i,j,2)=1-nm(i-1,j)
747  b(i,j,3)=1-nm(i,j+1);b(i,j,4)=1-nm(i,j-1)
748  endif
749  enddo
750  enddo
751 
752  do n=1,niter
753  do j=1,nj
754  do i=1,ni
755  if (fill(i,j) == 1) then
756  bsum = real(b(i,j,1)+b(i,j,2)+b(i,j,3)+b(i,j,4))
757  isum = 1.0/bsum
758  res(i,j)=isum*(b(i,j,1)*mp(i+1,j)+b(i,j,2)*mp(i-1,j)+&
759  b(i,j,3)*mp(i,j+1)+b(i,j,4)*mp(i,j-1)) - mp(i,j)
760  endif
761  enddo
762  enddo
763  res(:,:)=res(:,:)*sor
764 
765  do j=1,nj
766  do i=1,ni
767  mp(i,j)=mp(i,j)+res(i,j)
768  enddo
769  enddo
770 
771  zi(:,:)=mp(1:ni,1:nj)
772  mp = fill_boundaries(zi,cyclic_x,tripolar_n)
773  enddo
774 
775  return
776 
777 end subroutine smooth_heights
778 
779 !> Fill grid edges
780 function fill_boundaries_int(m,cyclic_x,tripolar_n) result(mp)
781  integer, dimension(:,:), intent(in) :: m !< input array
782  logical, intent(in) :: cyclic_x !< zonal cyclic condition
783  logical, intent(in) :: tripolar_n !< northern fold condition
784  integer, dimension(0:size(m,1)+1,0:size(m,2)+1) :: mp !< output filled array
785  ! Local variables
786  real, dimension(size(m,1),size(m,2)) :: m_real
787  real, dimension(0:size(m,1)+1,0:size(m,2)+1) :: mp_real
788 
789  m_real = real(m)
790 
791  mp_real = fill_boundaries_real(m_real,cyclic_x,tripolar_n)
792 
793  mp = int(mp_real)
794 
795  return
796 
797 end function fill_boundaries_int
798 
799 !> fill grid edges
800 function fill_boundaries_real(m,cyclic_x,tripolar_n) result(mp)
801  real, dimension(:,:), intent(in) :: m !< input array
802  logical, intent(in) :: cyclic_x !< zonal cyclic condition
803  logical, intent(in) :: tripolar_n !< northern fold condition
804  real, dimension(0:size(m,1)+1,0:size(m,2)+1) :: mp !< output filled array
805 
806  integer :: ni,nj,i,j
807 
808  ni=size(m,1); nj=size(m,2)
809 
810  mp(1:ni,1:nj)=m(:,:)
811 
812  if (cyclic_x) then
813  mp(0,1:nj)=m(ni,1:nj)
814  mp(ni+1,1:nj)=m(1,1:nj)
815  else
816  mp(0,1:nj)=m(1,1:nj)
817  mp(ni+1,1:nj)=m(ni,1:nj)
818  endif
819 
820  mp(1:ni,0)=m(1:ni,1)
821  if (tripolar_n) then
822  do i=1,ni
823  mp(i,nj+1)=m(ni-i+1,nj)
824  enddo
825  else
826  mp(1:ni,nj+1)=m(1:ni,nj)
827  endif
828 
829  return
830 
831 end function fill_boundaries_real
832 
833 end module midas_vertmap
mom_eos
Provides subroutines for quantities specific to the equation of state.
Definition: MOM_EOS.F90:2
mom_eos::eos_type
A control structure for the equation of state.
Definition: MOM_EOS.F90:86
mom_eos::calculate_density_derivs
Calculate the derivatives of density with temperature and salinity from T, S, and P.
Definition: MOM_EOS.F90:70
midas_vertmap::fill_boundaries
Fill grid edges.
Definition: midas_vertmap.F90:22
midas_vertmap
Routines for initialization callable from MOM6 or Python (MIDAS)
Definition: midas_vertmap.F90:2
mom_eos::calculate_density
Calculates density of sea water from T, S and P.
Definition: MOM_EOS.F90:60