MOM6
coord_slight.F90
1 !> Regrid columns for the SLight coordinate
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
6 use mom_error_handler, only : mom_error, fatal
7 use mom_eos, only : eos_type, calculate_compress
9 use regrid_interp, only : interp_cs_type, regridding_set_ppolys
10 use regrid_interp, only : nr_iterations, nr_tolerance, degree_max
11 
12 implicit none ; private
13 
14 !> Control structure containing required parameters for the SLight coordinate
15 type, public :: slight_cs ; private
16 
17  !> Number of layers/levels
18  integer :: nk
19 
20  !> Minimum thickness allowed when building the new grid through regridding [H ~> m or kg m-2]
21  real :: min_thickness
22 
23  !> Reference pressure for potential density calculations [Pa]
24  real :: ref_pressure
25 
26  !> Fraction (between 0 and 1) of compressibility to add to potential density
27  !! profiles when interpolating for target grid positions. [nondim]
28  real :: compressibility_fraction
29 
30  ! The following 4 parameters were introduced for use with the SLight coordinate:
31  !> Depth over which to average to determine the mixed layer potential density [H ~> m or kg m-2]
32  real :: rho_ml_avg_depth
33 
34  !> Number of layers to offset the mixed layer density to find resolved stratification [nondim]
35  real :: nlay_ml_offset
36 
37  !> The number of fixed-thickness layers at the top of the model
38  integer :: nz_fixed_surface = 2
39 
40  !> The fixed resolution in the topmost SLight_nkml_min layers [H ~> m or kg m-2]
41  real :: dz_ml_min
42 
43  !> If true, detect regions with much weaker stratification in the coordinate
44  !! than based on in-situ density, and use a stretched coordinate there.
45  logical :: fix_haloclines = .false.
46 
47  !> A length scale over which to filter T & S when looking for spuriously
48  !! unstable water mass profiles [H ~> m or kg m-2].
49  real :: halocline_filter_length
50 
51  !> A value of the stratification ratio that defines a problematic halocline region [nondim].
52  real :: halocline_strat_tol
53 
54  !> Nominal density of interfaces [kg m-3].
55  real, allocatable, dimension(:) :: target_density
56 
57  !> Maximum depths of interfaces [H ~> m or kg m-2].
58  real, allocatable, dimension(:) :: max_interface_depths
59 
60  !> Maximum thicknesses of layers [H ~> m or kg m-2].
61  real, allocatable, dimension(:) :: max_layer_thickness
62 
63  !> Interpolation control structure
64  type(interp_cs_type) :: interp_cs
65 end type slight_cs
66 
67 public init_coord_slight, set_slight_params, build_slight_column, end_coord_slight
68 
69 contains
70 
71 !> Initialise a slight_CS with pointers to parameters
72 subroutine init_coord_slight(CS, nk, ref_pressure, target_density, interp_CS, m_to_H)
73  type(slight_cs), pointer :: cs !< Unassociated pointer to hold the control structure
74  integer, intent(in) :: nk !< Number of layers in the grid
75  real, intent(in) :: ref_pressure !< Coordinate reference pressure [Pa]
76  real, dimension(:), intent(in) :: target_density !< Nominal density of interfaces [kg m-3]
77  type(interp_cs_type), intent(in) :: interp_cs !< Controls for interpolation
78  real, optional, intent(in) :: m_to_h !< A conversion factor from m to the units of thicknesses
79 
80  real :: m_to_h_rescale ! A unit conversion factor.
81 
82  if (associated(cs)) call mom_error(fatal, "init_coord_slight: CS already associated!")
83  allocate(cs)
84  allocate(cs%target_density(nk+1))
85 
86  m_to_h_rescale = 1.0 ; if (present(m_to_h)) m_to_h_rescale = m_to_h
87 
88  cs%nk = nk
89  cs%ref_pressure = ref_pressure
90  cs%target_density(:) = target_density(:)
91  cs%interp_CS = interp_cs
92 
93  ! Set real parameter default values
94  cs%compressibility_fraction = 0. ! Nondim.
95  cs%Rho_ML_avg_depth = 1.0 * m_to_h_rescale
96  cs%nlay_ml_offset = 2.0 ! Nondim.
97  cs%dz_ml_min = 1.0 * m_to_h_rescale
98  cs%halocline_filter_length = 2.0 * m_to_h_rescale
99  cs%halocline_strat_tol = 0.25 ! Nondim.
100 
101 end subroutine init_coord_slight
102 
103 !> This subroutine deallocates memory in the control structure for the coord_slight module
104 subroutine end_coord_slight(CS)
105  type(slight_cs), pointer :: cs !< Coordinate control structure
106 
107  ! nothing to do
108  if (.not. associated(cs)) return
109  deallocate(cs%target_density)
110  deallocate(cs)
111 end subroutine end_coord_slight
112 
113 !> This subroutine can be used to set the parameters for the coord_slight module
114 subroutine set_slight_params(CS, max_interface_depths, max_layer_thickness, &
115  min_thickness, compressibility_fraction, dz_ml_min, &
116  nz_fixed_surface, Rho_ML_avg_depth, nlay_ML_offset, fix_haloclines, &
117  halocline_filter_length, halocline_strat_tol, interp_CS)
118  type(slight_cs), pointer :: cs !< Coordinate control structure
119  real, dimension(:), &
120  optional, intent(in) :: max_interface_depths !< Maximum depths of interfaces [H ~> m or kg m-2]
121  real, dimension(:), &
122  optional, intent(in) :: max_layer_thickness !< Maximum thicknesses of layers [H ~> m or kg m-2]
123  real, optional, intent(in) :: min_thickness !< Minimum thickness allowed when building the
124  !! new grid through regridding [H ~> m or kg m-2]
125  real, optional, intent(in) :: compressibility_fraction !< Fraction (between 0 and 1) of
126  !! compressibility to add to potential density profiles when
127  !! interpolating for target grid positions. [nondim]
128  real, optional, intent(in) :: dz_ml_min !< The fixed resolution in the topmost
129  !! SLight_nkml_min layers [H ~> m or kg m-2]
130  integer, optional, intent(in) :: nz_fixed_surface !< The number of fixed-thickness layers at the
131  !! top of the model
132  real, optional, intent(in) :: rho_ml_avg_depth !< Depth over which to average to determine
133  !! the mixed layer potential density [H ~> m or kg m-2]
134  real, optional, intent(in) :: nlay_ml_offset !< Number of layers to offset the mixed layer
135  !! density to find resolved stratification [nondim]
136  logical, optional, intent(in) :: fix_haloclines !< If true, detect regions with much weaker than
137  !! based on in-situ density, and use a stretched coordinate there.
138  real, optional, intent(in) :: halocline_filter_length !< A length scale over which to filter T & S
139  !! when looking for spuriously unstable water mass profiles [H ~> m or kg m-2].
140  real, optional, intent(in) :: halocline_strat_tol !< A value of the stratification ratio that
141  !! defines a problematic halocline region [nondim].
142  type(interp_cs_type), &
143  optional, intent(in) :: interp_cs !< Controls for interpolation
144 
145  if (.not. associated(cs)) call mom_error(fatal, "set_slight_params: CS not associated")
146 
147  if (present(max_interface_depths)) then
148  if (size(max_interface_depths) /= cs%nk+1) &
149  call mom_error(fatal, "set_slight_params: max_interface_depths inconsistent size")
150  allocate(cs%max_interface_depths(cs%nk+1))
151  cs%max_interface_depths(:) = max_interface_depths(:)
152  endif
153 
154  if (present(max_layer_thickness)) then
155  if (size(max_layer_thickness) /= cs%nk) &
156  call mom_error(fatal, "set_slight_params: max_layer_thickness inconsistent size")
157  allocate(cs%max_layer_thickness(cs%nk))
158  cs%max_layer_thickness(:) = max_layer_thickness(:)
159  endif
160 
161  if (present(min_thickness)) cs%min_thickness = min_thickness
162  if (present(compressibility_fraction)) cs%compressibility_fraction = compressibility_fraction
163 
164  if (present(dz_ml_min)) cs%dz_ml_min = dz_ml_min
165  if (present(nz_fixed_surface)) cs%nz_fixed_surface = nz_fixed_surface
166  if (present(rho_ml_avg_depth)) cs%Rho_ML_avg_depth = rho_ml_avg_depth
167  if (present(nlay_ml_offset)) cs%nlay_ML_offset = nlay_ml_offset
168  if (present(fix_haloclines)) cs%fix_haloclines = fix_haloclines
169  if (present(halocline_filter_length)) cs%halocline_filter_length = halocline_filter_length
170  if (present(halocline_strat_tol)) then
171  if (halocline_strat_tol > 1.0) call mom_error(fatal, "set_slight_params: "//&
172  "HALOCLINE_STRAT_TOL must not exceed 1.0.")
173  cs%halocline_strat_tol = halocline_strat_tol
174  endif
175 
176  if (present(interp_cs)) cs%interp_CS = interp_cs
177 end subroutine set_slight_params
178 
179 !> Build a SLight coordinate column
180 subroutine build_slight_column(CS, eqn_of_state, H_to_Pa, H_subroundoff, &
181  nz, depth, h_col, T_col, S_col, p_col, z_col, z_col_new, &
182  h_neglect, h_neglect_edge)
183  type(slight_cs), intent(in) :: cs !< Coordinate control structure
184  type(eos_type), pointer :: eqn_of_state !< Equation of state structure
185  real, intent(in) :: h_to_pa !< GV%H_to_Pa
186  real, intent(in) :: h_subroundoff !< GV%H_subroundoff
187  integer, intent(in) :: nz !< Number of levels
188  real, intent(in) :: depth !< Depth of ocean bottom (positive [H ~> m or kg m-2])
189  real, dimension(nz), intent(in) :: t_col !< T for column
190  real, dimension(nz), intent(in) :: s_col !< S for column
191  real, dimension(nz), intent(in) :: h_col !< Layer thicknesses [H ~> m or kg m-2]
192  real, dimension(nz), intent(in) :: p_col !< Layer quantities
193  real, dimension(nz+1), intent(in) :: z_col !< Interface positions relative to the surface [H ~> m or kg m-2]
194  real, dimension(nz+1), intent(inout) :: z_col_new !< Absolute positions of interfaces [H ~> m or kg m-2]
195  real, optional, intent(in) :: h_neglect !< A negligibly small width for the purpose of
196  !! cell reconstructions [H ~> m or kg m-2].
197  real, optional, intent(in) :: h_neglect_edge !< A negligibly small width for the purpose
198  !! of edge value calculations [H ~> m or kg m-2].
199  ! Local variables
200  real, dimension(nz) :: rho_col ! Layer quantities
201  real, dimension(nz) :: t_f, s_f ! Filtered ayer quantities
202  logical, dimension(nz+1) :: reliable ! If true, this interface is in a reliable position.
203  real, dimension(nz+1) :: t_int, s_int ! Temperature and salinity interpolated to interfaces.
204  real, dimension(nz+1) :: rho_tmp, drho_dp, p_is, p_r
205  real, dimension(nz+1) :: drhois_dt, drhois_ds
206  real, dimension(nz+1) :: drhor_dt, drhor_ds
207  real, dimension(nz+1) :: strat_rat
208  real :: h_to_cpa
209  real :: dris, drr, fn_now, i_hstol, fn_zero_val
210  real :: z_int_unst
211  real :: dz ! A uniform layer thickness in very shallow water [H ~> m or kg m-2].
212  real :: dz_ur ! The total thickness of an unstable region [H ~> m or kg m-2].
213  real :: wgt, cowgt ! A weight and its complement, nondim.
214  real :: rho_ml_av ! The average potential density in a near-surface region [kg m-3].
215  real :: h_ml_av ! A thickness to try to use in taking the near-surface average [H ~> m or kg m-2].
216  real :: rho_x_z ! A cumulative integral of a density [kg m-3 H ~> kg m-2 or kg2 m-5].
217  real :: z_wt ! The thickness actually used in taking the near-surface average [H ~> m or kg m-2].
218  real :: k_interior ! The (real) value of k where the interior grid starts.
219  real :: k_int2 ! The (real) value of k where the interior grid starts.
220  real :: z_interior ! The depth where the interior grid starts [H ~> m or kg m-2].
221  real :: z_ml_fix ! The depth at which the fixed-thickness near-surface layers end [H ~> m or kg m-2].
222  real :: dz_dk ! The thickness of layers between the fixed-thickness
223  ! near-surface layars and the interior [H ~> m or kg m-2].
224  real :: lfilt ! A filtering lengthscale [H ~> m or kg m-2].
225  logical :: maximum_depths_set ! If true, the maximum depths of interface have been set.
226  logical :: maximum_h_set ! If true, the maximum layer thicknesses have been set.
227  real :: k2_used, k2here, dz_sum, z_max
228  integer :: k2
229  real :: h_tr, b_denom_1, b1, d1 ! Temporary variables used by the tridiagonal solver.
230  real, dimension(nz) :: c1 ! Temporary variables used by the tridiagonal solver.
231  integer :: kur1, kur2 ! The indicies at the top and bottom of an unreliable region.
232  integer :: kur_ss ! The index to start with in the search for the next unstable region.
233  integer :: i, j, k, nkml
234 
235  maximum_depths_set = allocated(cs%max_interface_depths)
236  maximum_h_set = allocated(cs%max_layer_thickness)
237 
238  if (z_col(nz+1) - z_col(1) < nz*cs%min_thickness) then
239  ! This is a nearly massless total depth, so distribute the water evenly.
240  dz = (z_col(nz+1) - z_col(1)) / real(nz)
241  do k=2,nz ; z_col_new(k) = z_col(1) + dz*real(k-1) ; enddo
242  else
243  call calculate_density(t_col, s_col, p_col, rho_col, 1, nz, &
244  eqn_of_state)
245 
246  ! Find the locations of the target potential densities, flagging
247  ! locations in apparently unstable regions as not reliable.
248  call rho_interfaces_col(rho_col, h_col, z_col, cs%target_density, nz, &
249  z_col_new, cs, reliable, debug=.true., &
250  h_neglect=h_neglect, h_neglect_edge=h_neglect_edge)
251 
252  ! Ensure that the interfaces are at least CS%min_thickness apart.
253  if (cs%min_thickness > 0.0) then
254  ! Move down interfaces below overly thin layers.
255  do k=2,nz ; if (z_col_new(k) < z_col_new(k-1) + cs%min_thickness) then
256  z_col_new(k) = z_col_new(k-1) + cs%min_thickness
257  endif ; enddo
258  ! Now move up any interfaces that are too close to the bottom.
259  do k=nz,2,-1 ; if (z_col_new(k) > z_col_new(k+1) - cs%min_thickness) then
260  z_col_new(k) = z_col_new(k+1) - cs%min_thickness
261  else
262  exit ! No more interfaces can be too close to the bottom.
263  endif ; enddo
264  endif
265 
266  ! Fix up the unreliable regions.
267  kur_ss = 2 ! reliable(1) and reliable(nz+1) must always be true.
268  do
269  ! Search for the uppermost unreliable interface postion.
270  kur1 = nz+2
271  do k=kur_ss,nz ; if (.not.reliable(k)) then
272  kur1 = k ; exit
273  endif ; enddo
274  if (kur1 > nz) exit ! Everything is now reliable.
275 
276  kur2 = kur1-1 ! For error checking.
277  do k=kur1+1,nz+1 ; if (reliable(k)) then
278  kur2 = k-1 ; kur_ss = k ; exit
279  endif ; enddo
280  if (kur2 < kur1) call mom_error(fatal, "Bad unreliable range.")
281 
282  dz_ur = z_col_new(kur2+1) - z_col_new(kur1-1)
283  ! drho = CS%target_density(kur2+1) - CS%target_density(kur1-1)
284  ! Perhaps reset the wgt and cowgt depending on how bad the old interface
285  ! locations were.
286  wgt = 1.0 ; cowgt = 0.0 ! = 1.0-wgt
287  do k=kur1,kur2
288  z_col_new(k) = cowgt*z_col_new(k) + &
289  wgt * (z_col_new(kur1-1) + dz_ur*(k - (kur1-1)) / ((kur2 - kur1) + 2))
290  enddo
291  enddo
292 
293  ! Determine which interfaces are in the s-space region and the depth extent
294  ! of this region.
295  z_wt = 0.0 ; rho_x_z = 0.0
296  h_ml_av = cs%Rho_ml_avg_depth
297  do k=1,nz
298  if (z_wt + h_col(k) >= h_ml_av) then
299  rho_x_z = rho_x_z + rho_col(k) * (h_ml_av - z_wt)
300  z_wt = h_ml_av
301  exit
302  else
303  rho_x_z = rho_x_z + rho_col(k) * h_col(k)
304  z_wt = z_wt + h_col(k)
305  endif
306  enddo
307  if (z_wt > 0.0) rho_ml_av = rho_x_z / z_wt
308 
309  nkml = cs%nz_fixed_surface
310  ! Find the interface that matches rho_ml_av.
311  if (rho_ml_av <= cs%target_density(nkml)) then
312  k_interior = cs%nlay_ml_offset + real(nkml)
313  elseif (rho_ml_av > cs%target_density(nz+1)) then
314  k_interior = real(nz+1)
315  else ; do k=nkml,nz
316  if ((rho_ml_av >= cs%target_density(k)) .and. &
317  (rho_ml_av < cs%target_density(k+1))) then
318  k_interior = (cs%nlay_ml_offset + k) + &
319  (rho_ml_av - cs%target_density(k)) / &
320  (cs%target_density(k+1) - cs%target_density(k))
321  exit
322  endif
323  enddo ; endif
324  if (k_interior > real(nz+1)) k_interior = real(nz+1)
325 
326  ! Linearly interpolate to find z_interior. This could be made more sophisticated.
327  k = int(ceiling(k_interior))
328  z_interior = (k-k_interior)*z_col_new(k-1) + (1.0+(k_interior-k))*z_col_new(k)
329 
330  if (cs%fix_haloclines) then
331  ! ! Identify regions above the reference pressure where the chosen
332  ! ! potential density significantly underestimates the actual
333  ! ! stratification, and use these to find a second estimate of
334  ! ! z_int_unst and k_interior.
335 
336  if (cs%halocline_filter_length > 0.0) then
337  lfilt = cs%halocline_filter_length
338 
339  ! Filter the temperature and salnity with a fixed lengthscale.
340  h_tr = h_col(1) + h_subroundoff
341  b1 = 1.0 / (h_tr + lfilt) ; d1 = h_tr * b1
342  t_f(1) = (b1*h_tr)*t_col(1) ; s_f(1) = (b1*h_tr)*s_col(1)
343  do k=2,nz
344  c1(k) = lfilt * b1
345  h_tr = h_col(k) + h_subroundoff ; b_denom_1 = h_tr + d1*lfilt
346  b1 = 1.0 / (b_denom_1 + lfilt) ; d1 = b_denom_1 * b1
347  t_f(k) = b1 * (h_tr*t_col(k) + lfilt*t_f(k-1))
348  s_f(k) = b1 * (h_tr*s_col(k) + lfilt*s_f(k-1))
349  enddo
350  do k=nz-1,1,-1
351  t_f(k) = t_f(k) + c1(k+1)*t_f(k+1) ; s_f(k) = s_f(k) + c1(k+1)*s_f(k+1)
352  enddo
353  else
354  do k=1,nz ; t_f(k) = t_col(k) ; s_f(k) = s_col(k) ; enddo
355  endif
356 
357  t_int(1) = t_f(1) ; s_int(1) = s_f(1)
358  do k=2,nz
359  t_int(k) = 0.5*(t_f(k-1) + t_f(k)) ; s_int(k) = 0.5*(s_f(k-1) + s_f(k))
360  p_is(k) = z_col(k) * h_to_pa
361  p_r(k) = cs%ref_pressure + cs%compressibility_fraction * ( p_is(k) - cs%ref_pressure )
362  enddo
363  t_int(nz+1) = t_f(nz) ; s_int(nz+1) = s_f(nz)
364  p_is(nz+1) = z_col(nz+1) * h_to_pa
365  call calculate_density_derivs(t_int, s_int, p_is, drhois_dt, drhois_ds, 2, nz-1, &
366  eqn_of_state)
367  call calculate_density_derivs(t_int, s_int, p_r, drhor_dt, drhor_ds, 2, nz-1, &
368  eqn_of_state)
369  if (cs%compressibility_fraction > 0.0) then
370  call calculate_compress(t_int, s_int, p_r, rho_tmp, drho_dp, 2, nz-1, &
371  eqn_of_state)
372  else
373  do k=2,nz ; drho_dp(k) = 0.0 ; enddo
374  endif
375 
376  h_to_cpa = cs%compressibility_fraction*h_to_pa
377  strat_rat(1) = 1.0
378  do k=2,nz
379  dris = drhois_dt(k) * (t_f(k) - t_f(k-1)) + &
380  drhois_ds(k) * (s_f(k) - s_f(k-1))
381  drr = (drhor_dt(k) * (t_f(k) - t_f(k-1)) + &
382  drhor_ds(k) * (s_f(k) - s_f(k-1))) + &
383  drho_dp(k) * (h_to_cpa*0.5*(h_col(k) + h_col(k-1)))
384 
385  if (dris <= 0.0) then
386  strat_rat(k) = 2.0 ! Maybe do this? => ; if (drR < 0.0) strat_rat(K) = -2.0
387  else
388  strat_rat(k) = 2.0*max(drr,0.0) / (dris + abs(drr))
389  endif
390  enddo
391  strat_rat(nz+1) = 1.0
392 
393  z_int_unst = 0.0 ; fn_now = 0.0
394  fn_zero_val = min(2.0*cs%halocline_strat_tol, &
395  0.5*(1.0 + cs%halocline_strat_tol))
396  if (cs%halocline_strat_tol > 0.0) then
397  ! Use Adcroft's reciprocal rule.
398  i_hstol = 0.0 ; if (fn_zero_val - cs%halocline_strat_tol > 0.0) &
399  i_hstol = 1.0 / (fn_zero_val - cs%halocline_strat_tol)
400  do k=nz,1,-1 ; if (cs%ref_pressure > p_is(k+1)) then
401  z_int_unst = z_int_unst + fn_now * h_col(k)
402  if (strat_rat(k) <= fn_zero_val) then
403  if (strat_rat(k) <= cs%halocline_strat_tol) then ; fn_now = 1.0
404  else
405  fn_now = max(fn_now, (fn_zero_val - strat_rat(k)) * i_hstol)
406  endif
407  endif
408  endif ; enddo
409  else
410  do k=nz,1,-1 ; if (cs%ref_pressure > p_is(k+1)) then
411  z_int_unst = z_int_unst + fn_now * h_col(k)
412  if (strat_rat(k) <= cs%halocline_strat_tol) fn_now = 1.0
413  endif ; enddo
414  endif
415 
416  if (z_interior < z_int_unst) then
417  ! Find a second estimate of the extent of the s-coordinate region.
418  kur1 = max(int(ceiling(k_interior)),2)
419  if (z_col_new(kur1-1) < z_interior) then
420  k_int2 = kur1
421  do k = kur1,nz+1 ; if (z_col_new(k) >= z_int_unst) then
422  ! This is linear interpolation again.
423  if (z_col_new(k-1) >= z_int_unst) &
424  call mom_error(fatal,"build_grid_SLight, bad halocline structure.")
425  k_int2 = real(k-1) + (z_int_unst - z_col_new(k-1)) / &
426  (z_col_new(k) - z_col_new(k-1))
427  exit
428  endif ; enddo
429  if (z_col_new(nz+1) < z_int_unst) then
430  ! This should be unnecessary.
431  z_int_unst = z_col_new(nz+1) ; k_int2 = real(nz+1)
432  endif
433 
434  ! Now take the larger values.
435  if (k_int2 > k_interior) then
436  k_interior = k_int2 ; z_interior = z_int_unst
437  endif
438  endif
439  endif
440  endif ! fix_haloclines
441 
442  z_col_new(1) = 0.0
443  do k=2,nkml+1
444  z_col_new(k) = min((k-1)*cs%dz_ml_min, &
445  z_col_new(nz+1) - cs%min_thickness*(nz+1-k))
446  enddo
447  z_ml_fix = z_col_new(nkml+1)
448  if (z_interior > z_ml_fix) then
449  dz_dk = (z_interior - z_ml_fix) / (k_interior - (nkml+1))
450  do k=nkml+2,int(floor(k_interior))
451  z_col_new(k) = z_ml_fix + dz_dk * (k - (nkml+1))
452  enddo
453  else ! The fixed-thickness z-region penetrates into the interior.
454  do k=nkml+2,nz
455  if (z_col_new(k) <= z_col_new(cs%nz_fixed_surface+1)) then
456  z_col_new(k) = z_col_new(cs%nz_fixed_surface+1)
457  else ; exit ; endif
458  enddo
459  endif
460 
461  if (maximum_depths_set .and. maximum_h_set) then ; do k=2,nz
462  ! The loop bounds are 2 & nz so the top and bottom interfaces do not move.
463  ! Recall that z_col_new is positive downward.
464  z_col_new(k) = min(z_col_new(k), cs%max_interface_depths(k), &
465  z_col_new(k-1) + cs%max_layer_thickness(k-1))
466  enddo ; elseif (maximum_depths_set) then ; do k=2,nz
467  z_col_new(k) = min(z_col_new(k), cs%max_interface_depths(k))
468  enddo ; elseif (maximum_h_set) then ; do k=2,nz
469  z_col_new(k) = min(z_col_new(k), z_col_new(k-1) + cs%max_layer_thickness(k-1))
470  enddo ; endif
471 
472  endif ! Total thickness exceeds nz*CS%min_thickness.
473 
474 end subroutine build_slight_column
475 
476 !> Finds the new interface locations in a column of water that match the
477 !! prescribed target densities.
478 subroutine rho_interfaces_col(rho_col, h_col, z_col, rho_tgt, nz, z_col_new, &
479  CS, reliable, debug, h_neglect, h_neglect_edge)
480  integer, intent(in) :: nz !< Number of layers
481  real, dimension(nz), intent(in) :: rho_col !< Initial layer reference densities.
482  real, dimension(nz), intent(in) :: h_col !< Initial layer thicknesses.
483  real, dimension(nz+1), intent(in) :: z_col !< Initial interface heights.
484  real, dimension(nz+1), intent(in) :: rho_tgt !< Interface target densities.
485  real, dimension(nz+1), intent(inout) :: z_col_new !< New interface heights.
486  type(slight_cs), intent(in) :: CS !< Coordinate control structure
487  logical, dimension(nz+1), intent(inout) :: reliable !< If true, the interface positions
488  !! are well defined from a stable region.
489  logical, optional, intent(in) :: debug !< If present and true, do debugging checks.
490  real, optional, intent(in) :: h_neglect !< A negligibly small width for the
491  !! purpose of cell reconstructions
492  !! in the same units as h_col.
493  real, optional, intent(in) :: h_neglect_edge !< A negligibly small width
494  !! for the purpose of edge value calculations
495  !! in the same units as h_col.
496 
497  real, dimension(nz+1) :: ru_max_int ! The maximum and minimum densities in
498  real, dimension(nz+1) :: ru_min_int ! an unstable region around an interface.
499  real, dimension(nz) :: ru_max_lay ! The maximum and minimum densities in
500  real, dimension(nz) :: ru_min_lay ! an unstable region containing a layer.
501  real, dimension(nz,2) :: ppoly_i_E ! Edge value of polynomial
502  real, dimension(nz,2) :: ppoly_i_S ! Edge slope of polynomial
503  real, dimension(nz,DEGREE_MAX+1) :: ppoly_i_coefficients ! Coefficients of polynomial
504  logical, dimension(nz) :: unstable_lay ! If true, this layer is in an unstable region.
505  logical, dimension(nz+1) :: unstable_int ! If true, this interface is in an unstable region.
506  real :: rt ! The current target density [kg m-3].
507  real :: zf ! The fractional z-position within a layer of the target density.
508  real :: rfn
509  real :: a(5) ! Coefficients of a local polynomial minus the target density.
510  real :: zf1, zf2, rfn1, rfn2
511  real :: drfn_dzf, sgn, delta_zf, zf_prev
512  real :: tol
513  logical :: k_found ! If true, the position has been found.
514  integer :: k_layer ! The index of the stable layer containing an interface.
515  integer :: ppoly_degree
516  integer :: k, k1, k1_min, itt, max_itt, m
517 
518  real :: z_sgn ! 1 or -1, depending on whether z increases with increasing K.
519  logical :: debugging
520 
521  debugging = .false. ; if (present(debug)) debugging = debug
522  max_itt = nr_iterations
523  tol = nr_tolerance
524 
525  z_sgn = 1.0 ; if ( z_col(1) > z_col(nz+1) ) z_sgn = -1.0
526  if (debugging) then
527  do k=1,nz
528  if (abs((z_col(k+1) - z_col(k)) - z_sgn*h_col(k)) > &
529  1.0e-14*(abs(z_col(k+1)) + abs(z_col(k)) + abs(h_col(k))) ) &
530  call mom_error(fatal, "rho_interfaces_col: Inconsistent z_col and h_col")
531  enddo
532  endif
533 
534  if ( z_col(1) == z_col(nz+1) ) then
535  ! This is a massless column!
536  do k=1,nz+1 ; z_col_new(k) = z_col(1) ; reliable(k) = .true. ; enddo
537  return
538  endif
539 
540  ! This sets up the piecewise polynomials based on the rho_col profile.
541  call regridding_set_ppolys(cs%interp_CS, rho_col, nz, h_col, ppoly_i_e, ppoly_i_s, &
542  ppoly_i_coefficients, ppoly_degree, h_neglect, h_neglect_edge)
543 
544  ! Determine the density ranges of unstably stratified segments.
545  ! Interfaces that start out in an unstably stratified segment can
546  ! only escape if they are outside of the bounds of that segment, and no
547  ! interfaces are ever mapped into an unstable segment.
548  unstable_int(1) = .false.
549  ru_max_int(1) = ppoly_i_e(1,1)
550 
551  unstable_lay(1) = (ppoly_i_e(1,1) > ppoly_i_e(1,2))
552  ru_max_lay(1) = max(ppoly_i_e(1,1), ppoly_i_e(1,2))
553 
554  do k=2,nz
555  unstable_int(k) = (ppoly_i_e(k-1,2) > ppoly_i_e(k,1))
556  ru_max_int(k) = max(ppoly_i_e(k-1,2), ppoly_i_e(k,1))
557  ru_min_int(k) = min(ppoly_i_e(k-1,2), ppoly_i_e(k,1))
558  if (unstable_int(k) .and. unstable_lay(k-1)) &
559  ru_max_int(k) = max(ru_max_lay(k-1), ru_max_int(k))
560 
561  unstable_lay(k) = (ppoly_i_e(k,1) > ppoly_i_e(k,2))
562  ru_max_lay(k) = max(ppoly_i_e(k,1), ppoly_i_e(k,2))
563  ru_min_lay(k) = min(ppoly_i_e(k,1), ppoly_i_e(k,2))
564  if (unstable_lay(k) .and. unstable_int(k)) &
565  ru_max_lay(k) = max(ru_max_int(k), ru_max_lay(k))
566  enddo
567  unstable_int(nz+1) = .false.
568  ru_min_int(nz+1) = ppoly_i_e(nz,2)
569 
570  do k=nz,1,-1
571  if (unstable_lay(k) .and. unstable_int(k+1)) &
572  ru_min_lay(k) = min(ru_min_int(k+1), ru_min_lay(k))
573 
574  if (unstable_int(k) .and. unstable_lay(k)) &
575  ru_min_int(k) = min(ru_min_lay(k), ru_min_int(k))
576  enddo
577 
578  z_col_new(1) = z_col(1) ; reliable(1) = .true.
579  k1_min = 1
580  do k=2,nz ! Find the locations of the various target densities for the interfaces.
581  rt = rho_tgt(k)
582  k_layer = -1
583  k_found = .false.
584 
585  ! Many light layers are found at the top, so start there.
586  if (rt <= ppoly_i_e(k1_min,1)) then
587  z_col_new(k) = z_col(k1_min)
588  k_found = .true.
589  ! Do not change k1_min for the next layer.
590  elseif (k1_min == nz+1) then
591  z_col_new(k) = z_col(nz+1)
592  else
593  ! Start with the previous location and search outward.
594  if (unstable_int(k) .and. (rt >= ru_min_int(k)) .and. (rt <= ru_max_int(k))) then
595  ! This interface started in an unstable region and should not move due to remapping.
596  z_col_new(k) = z_col(k) ; reliable(k) = .false.
597  k1_min = k ; k_found = .true.
598  elseif ((rt >= ppoly_i_e(k-1,2)) .and. (rt <= ppoly_i_e(k,1))) then
599  ! This interface is already in the right place and does not move.
600  z_col_new(k) = z_col(k) ; reliable(k) = .true.
601  k1_min = k ; k_found = .true.
602  elseif (rt < ppoly_i_e(k-1,2)) then ! Search upward
603  do k1=k-1,k1_min,-1
604  ! Check whether rt is in layer k.
605  if ((rt < ppoly_i_e(k1,2)) .and. (rt > ppoly_i_e(k1,1))) then
606  ! rt is in layer k.
607  k_layer = k1
608  k1_min = k1 ; k_found = .true. ; exit
609  elseif (unstable_lay(k1) .and. (rt >= ru_min_lay(k1)) .and. (rt <= ru_max_lay(k1))) then
610  ! rt would be found at unstable layer that it can not penetrate.
611  ! It is possible that this can never happen?
612  z_col_new(k) = z_col(k1+1) ; reliable(k) = .false.
613  k1_min = k1 ; k_found = .true. ; exit
614  endif
615  ! Check whether rt is at interface K.
616  if (k1 > 1) then ; if ((rt <= ppoly_i_e(k1,1)) .and. (rt >= ppoly_i_e(k1-1,2))) then
617  ! rt is at interface K1
618  z_col_new(k) = z_col(k1) ; reliable(k) = .true.
619  k1_min = k1 ; k_found = .true. ; exit
620  elseif (unstable_int(k1) .and. (rt >= ru_min_int(k1)) .and. (rt <= ru_max_int(k1))) then
621  ! rt would be found at an unstable interface that it can not pass.
622  ! It is possible that this can never happen?
623  z_col_new(k) = z_col(k1) ; reliable(k) = .false.
624  k1_min = k1 ; k_found = .true. ; exit
625  endif ; endif
626  enddo
627 
628  if (.not.k_found) then
629  ! This should not happen unless k1_min = 1.
630  if (k1_min < 2) then
631  z_col_new(k) = z_col(k1_min)
632  else
633  z_col_new(k) = z_col(k1_min)
634  endif
635  endif
636 
637  else ! Search downward
638  do k1=k,nz
639  if ((rt < ppoly_i_e(k1,2)) .and. (rt > ppoly_i_e(k1,1))) then
640  ! rt is in layer k.
641  k_layer = k1
642  k1_min = k1 ; k_found = .true. ; exit
643  elseif (unstable_lay(k1) .and. (rt >= ru_min_lay(k1)) .and. (rt <= ru_max_lay(k1))) then
644  ! rt would be found at unstable layer that it can not penetrate.
645  ! It is possible that this can never happen?
646  z_col_new(k) = z_col(k1)
647  reliable(k) = .false.
648  k1_min = k1 ; k_found = .true. ; exit
649  endif
650  if (k1 < nz) then ; if ((rt <= ppoly_i_e(k1+1,1)) .and. (rt >= ppoly_i_e(k1,2))) then
651  ! rt is at interface K1+1
652 
653  z_col_new(k) = z_col(k1+1) ; reliable(k) = .true.
654  k1_min = k1+1 ; k_found = .true. ; exit
655  elseif (unstable_int(k1+1) .and. (rt >= ru_min_int(k1+1)) .and. (rt <= ru_max_int(k1+1))) then
656  ! rt would be found at an unstable interface that it can not pass.
657  ! It is possible that this can never happen?
658  z_col_new(k) = z_col(k1+1)
659  reliable(k) = .false.
660  k1_min = k1+1 ; k_found = .true. ; exit
661  endif ; endif
662  enddo
663  if (.not.k_found) then
664  z_col_new(k) = z_col(nz+1)
665  if (rt >= ppoly_i_e(nz,2)) then
666  reliable(k) = .true.
667  else
668  reliable(k) = .false.
669  endif
670  endif
671  endif
672 
673  if (k_layer > 0) then ! The new location is inside of layer k_layer.
674  ! Note that this is coded assuming that this layer is stably stratified.
675  if (.not.(ppoly_i_e(k1,2) > ppoly_i_e(k1,1))) call mom_error(fatal, &
676  "build_grid_SLight: Erroneously searching for an interface in an unstratified layer.") !### COMMENT OUT LATER?
677 
678  ! Use the false position method to find the location (degree <= 1) or the first guess.
679  zf = (rt - ppoly_i_e(k1,1)) / (ppoly_i_e(k1,2) - ppoly_i_e(k1,1))
680 
681  if (ppoly_degree > 1) then ! Iterate to find the solution.
682  a(:) = 0.0 ; a(1) = ppoly_i_coefficients(k_layer,1) - rt
683  do m=2,ppoly_degree+1 ; a(m) = ppoly_i_coefficients(k_layer,m) ; enddo
684  ! Bracket the root.
685  zf1 = 0.0 ; rfn1 = a(1)
686  zf2 = 1.0 ; rfn2 = a(1) + (a(2) + (a(3) + (a(4) + a(5))))
687  if (rfn1 * rfn2 > 0.0) call mom_error(fatal, "build_grid_SLight: Bad bracketing.") !### COMMENT OUT LATER?
688 
689  do itt=1,max_itt
690  rfn = a(1) + zf*(a(2) + zf*(a(3) + zf*(a(4) + zf*a(5))))
691  ! Reset one of the ends of the bracket.
692  if (rfn * rfn1 > 0.0) then
693  zf1 = zf ; rfn1 = rfn
694  else
695  zf2 = zf ; rfn2 = rfn
696  endif
697  if (rfn1 == rfn2) exit
698 
699  drfn_dzf = (a(2) + zf*(2.0*a(3) + zf*(3.0*a(4) + zf*4.0*a(5))))
700  sgn = 1.0 ; if (drfn_dzf < 0.0) sgn = -1.0
701 
702  if ((sgn*(zf - rfn) >= zf1 * abs(drfn_dzf)) .and. &
703  (sgn*(zf - rfn) <= zf2 * abs(drfn_dzf))) then
704  delta_zf = -rfn / drfn_dzf
705  zf = zf + delta_zf
706  else ! Newton's method goes out of bounds, so use a false position method estimate
707  zf_prev = zf
708  zf = ( rfn2 * zf1 - rfn1 * zf2 ) / (rfn2 - rfn1)
709  delta_zf = zf - zf_prev
710  endif
711 
712  if (abs(delta_zf) < tol) exit
713  enddo
714  endif
715  z_col_new(k) = z_col(k_layer) + zf * z_sgn * h_col(k_layer)
716  reliable(k) = .true.
717  endif
718 
719  endif
720 
721  enddo
722  z_col_new(nz+1) = z_col(nz+1) ; reliable(nz+1) = .true.
723 
724 end subroutine rho_interfaces_col
725 
726 end module coord_slight
coord_slight::slight_cs
Control structure containing required parameters for the SLight coordinate.
Definition: coord_slight.F90:15
mom_eos
Provides subroutines for quantities specific to the equation of state.
Definition: MOM_EOS.F90:2
regrid_interp
Vertical interpolation for regridding.
Definition: regrid_interp.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
regrid_interp::interp_cs_type
Control structure for regrid_interp module.
Definition: regrid_interp.F90:23
mom_error_handler
Routines for error handling and I/O management.
Definition: MOM_error_handler.F90:2
coord_slight
Regrid columns for the SLight coordinate.
Definition: coord_slight.F90:2
mom_eos::calculate_density
Calculates density of sea water from T, S and P.
Definition: MOM_EOS.F90:60