MOM6
coord_adapt.F90
1 !> Regrid columns for the adaptive coordinate
2 module coord_adapt
3 
4 ! This file is part of MOM6. See LICENSE.md for the license.
5 
7 use mom_error_handler, only : mom_error, fatal
9 use mom_variables, only : ocean_grid_type, thermo_var_ptrs
11 
12 implicit none ; private
13 
14 #include <MOM_memory.h>
15 
16 !> Control structure for adaptive coordinates (coord_adapt).
17 type, public :: adapt_cs ; private
18 
19  !> Number of layers/levels
20  integer :: nk
21 
22  !> Nominal near-surface resolution [H ~> m or kg m-2]
23  real, allocatable, dimension(:) :: coordinateresolution
24 
25  !> Ratio of optimisation and diffusion timescales
26  real :: adapttimeratio
27 
28  !> Nondimensional coefficient determining how much optimisation to apply
29  real :: adaptalpha
30 
31  !> Near-surface zooming depth [H ~> m or kg m-2]
32  real :: adaptzoom
33 
34  !> Near-surface zooming coefficient
35  real :: adaptzoomcoeff
36 
37  !> Stratification-dependent diffusion coefficient
38  real :: adaptbuoycoeff
39 
40  !> Reference density difference for stratification-dependent diffusion [R ~> kg m-3]
41  real :: adaptdrho0
42 
43  !> If true, form a HYCOM1-like mixed layet by preventing interfaces
44  !! from becoming shallower than the depths set by coordinateResolution
45  logical :: adaptdomin = .false.
46 end type adapt_cs
47 
48 public init_coord_adapt, set_adapt_params, build_adapt_column, end_coord_adapt
49 
50 contains
51 
52 !> Initialise an adapt_CS with parameters
53 subroutine init_coord_adapt(CS, nk, coordinateResolution, m_to_H, kg_m3_to_R)
54  type(adapt_cs), pointer :: cs !< Unassociated pointer to hold the control structure
55  integer, intent(in) :: nk !< Number of layers in the grid
56  real, dimension(:), intent(in) :: coordinateresolution !< Nominal near-surface resolution [m] or
57  !! other units specified with m_to_H
58  real, intent(in) :: m_to_h !< A conversion factor from m to the units of thicknesses
59  real, intent(in) :: kg_m3_to_r !< A conversion factor from kg m-3 to the units of density
60 
61  if (associated(cs)) call mom_error(fatal, "init_coord_adapt: CS already associated")
62  allocate(cs)
63  allocate(cs%coordinateResolution(nk))
64 
65  cs%nk = nk
66  cs%coordinateResolution(:) = coordinateresolution(:)
67 
68  ! Set real parameter default values
69  cs%adaptTimeRatio = 1e-1 ! Nondim.
70  cs%adaptAlpha = 1.0 ! Nondim.
71  cs%adaptZoom = 200.0 * m_to_h ! [H ~> m or kg m-2]
72  cs%adaptZoomCoeff = 0.0 ! Nondim.
73  cs%adaptBuoyCoeff = 0.0 ! Nondim.
74  cs%adaptDrho0 = 0.5 * kg_m3_to_r ! [R ~> kg m-3]
75 
76 end subroutine init_coord_adapt
77 
78 !> Clean up the coordinate control structure
79 subroutine end_coord_adapt(CS)
80  type(adapt_cs), pointer :: cs !< The control structure for this module
81 
82  ! nothing to do
83  if (.not. associated(cs)) return
84  deallocate(cs%coordinateResolution)
85  deallocate(cs)
86 end subroutine end_coord_adapt
87 
88 !> This subtroutine can be used to set the parameters for coord_adapt module
89 subroutine set_adapt_params(CS, adaptTimeRatio, adaptAlpha, adaptZoom, adaptZoomCoeff, &
90  adaptBuoyCoeff, adaptDrho0, adaptDoMin)
91  type(adapt_cs), pointer :: cs !< The control structure for this module
92  real, optional, intent(in) :: adapttimeratio !< Ratio of optimisation and diffusion timescales
93  real, optional, intent(in) :: adaptalpha !< Nondimensional coefficient determining
94  !! how much optimisation to apply
95  real, optional, intent(in) :: adaptzoom !< Near-surface zooming depth [H ~> m or kg m-2]
96  real, optional, intent(in) :: adaptzoomcoeff !< Near-surface zooming coefficient
97  real, optional, intent(in) :: adaptbuoycoeff !< Stratification-dependent diffusion coefficient
98  real, optional, intent(in) :: adaptdrho0 !< Reference density difference for
99  !! stratification-dependent diffusion [R ~> kg m-3]
100  logical, optional, intent(in) :: adaptdomin !< If true, form a HYCOM1-like mixed layer by
101  !! preventing interfaces from becoming shallower than
102  !! the depths set by coordinateResolution
103 
104  if (.not. associated(cs)) call mom_error(fatal, "set_adapt_params: CS not associated")
105 
106  if (present(adapttimeratio)) cs%adaptTimeRatio = adapttimeratio
107  if (present(adaptalpha)) cs%adaptAlpha = adaptalpha
108  if (present(adaptzoom)) cs%adaptZoom = adaptzoom
109  if (present(adaptzoomcoeff)) cs%adaptZoomCoeff = adaptzoomcoeff
110  if (present(adaptbuoycoeff)) cs%adaptBuoyCoeff = adaptbuoycoeff
111  if (present(adaptdrho0)) cs%adaptDrho0 = adaptdrho0
112  if (present(adaptdomin)) cs%adaptDoMin = adaptdomin
113 end subroutine set_adapt_params
114 
115 subroutine build_adapt_column(CS, G, GV, US, tv, i, j, zInt, tInt, sInt, h, zNext)
116  type(adapt_cs), intent(in) :: cs !< The control structure for this module
117  type(ocean_grid_type), intent(in) :: g !< The ocean's grid structure
118  type(verticalgrid_type), intent(in) :: gv !< The ocean's vertical grid structure
119  type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
120  type(thermo_var_ptrs), intent(in) :: tv !< A structure pointing to various
121  !! thermodynamic variables
122  integer, intent(in) :: i !< The i-index of the column to work on
123  integer, intent(in) :: j !< The j-index of the column to work on
124  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1), intent(in) :: zint !< Interface heights [H ~> m or kg m-2].
125  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1), intent(in) :: tint !< Interface temperatures [degC]
126  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1), intent(in) :: sint !< Interface salinities [ppt]
127  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< Layer thicknesses [H ~> m or kg m-2]
128  real, dimension(SZK_(GV)+1), intent(inout) :: znext !< updated interface positions
129 
130  ! Local variables
131  integer :: k, nz
132  real :: h_up, b1, b_denom_1, d1, depth, nominal_z, stretching
133  real :: drdz ! The vertical density gradient [R H-1 ~> kg m-4 or m-1]
134  real, dimension(SZK_(GV)+1) :: alpha ! drho/dT [R degC-1 ~> kg m-3 degC-1]
135  real, dimension(SZK_(GV)+1) :: beta ! drho/dS [R ppt-1 ~> kg m-3 ppt-1]
136  real, dimension(SZK_(GV)+1) :: del2sigma ! Laplacian of in situ density times grid spacing [R ~> kg m-3]
137  real, dimension(SZK_(GV)+1) :: dh_d2s ! Thickness change in response to del2sigma [H ~> m or kg m-2]
138  real, dimension(SZK_(GV)) :: kgrid, c1 ! grid diffusivity on layers, and tridiagonal work array
139 
140  nz = cs%nk
141 
142  ! set bottom and surface of zNext
143  znext(1) = 0.
144  znext(nz+1) = zint(i,j,nz+1)
145 
146  ! local depth for scaling diffusivity
147  depth = g%bathyT(i,j) * gv%Z_to_H
148 
149  ! initialize del2sigma and the thickness change response to it zero
150  del2sigma(:) = 0.0 ; dh_d2s(:) = 0.0
151 
152  ! calculate del-squared of neutral density by a
153  ! stencilled finite difference
154  ! TODO: this needs to be adjusted to account for vanished layers near topography
155 
156  ! up (j-1)
157  if (g%mask2dT(i,j-1) > 0.) then
159  0.5 * (tint(i,j,2:nz) + tint(i,j-1,2:nz)), &
160  0.5 * (sint(i,j,2:nz) + sint(i,j-1,2:nz)), &
161  0.5 * (zint(i,j,2:nz) + zint(i,j-1,2:nz)) * (gv%H_to_RZ * gv%g_Earth), &
162  alpha, beta, tv%eqn_of_state, (/2,nz/) )
163 
164  del2sigma(2:nz) = del2sigma(2:nz) + &
165  (alpha(2:nz) * (tint(i,j-1,2:nz) - tint(i,j,2:nz)) + &
166  beta(2:nz) * (sint(i,j-1,2:nz) - sint(i,j,2:nz)))
167  endif
168  ! down (j+1)
169  if (g%mask2dT(i,j+1) > 0.) then
171  0.5 * (tint(i,j,2:nz) + tint(i,j+1,2:nz)), &
172  0.5 * (sint(i,j,2:nz) + sint(i,j+1,2:nz)), &
173  0.5 * (zint(i,j,2:nz) + zint(i,j+1,2:nz)) * (gv%H_to_RZ * gv%g_Earth), &
174  alpha, beta, tv%eqn_of_state, (/2,nz/) )
175 
176  del2sigma(2:nz) = del2sigma(2:nz) + &
177  (alpha(2:nz) * (tint(i,j+1,2:nz) - tint(i,j,2:nz)) + &
178  beta(2:nz) * (sint(i,j+1,2:nz) - sint(i,j,2:nz)))
179  endif
180  ! left (i-1)
181  if (g%mask2dT(i-1,j) > 0.) then
183  0.5 * (tint(i,j,2:nz) + tint(i-1,j,2:nz)), &
184  0.5 * (sint(i,j,2:nz) + sint(i-1,j,2:nz)), &
185  0.5 * (zint(i,j,2:nz) + zint(i-1,j,2:nz)) * (gv%H_to_RZ * gv%g_Earth), &
186  alpha, beta, tv%eqn_of_state, (/2,nz/) )
187 
188  del2sigma(2:nz) = del2sigma(2:nz) + &
189  (alpha(2:nz) * (tint(i-1,j,2:nz) - tint(i,j,2:nz)) + &
190  beta(2:nz) * (sint(i-1,j,2:nz) - sint(i,j,2:nz)))
191  endif
192  ! right (i+1)
193  if (g%mask2dT(i+1,j) > 0.) then
195  0.5 * (tint(i,j,2:nz) + tint(i+1,j,2:nz)), &
196  0.5 * (sint(i,j,2:nz) + sint(i+1,j,2:nz)), &
197  0.5 * (zint(i,j,2:nz) + zint(i+1,j,2:nz)) * (gv%H_to_RZ * gv%g_Earth), &
198  alpha, beta, tv%eqn_of_state, (/2,nz/) )
199 
200  del2sigma(2:nz) = del2sigma(2:nz) + &
201  (alpha(2:nz) * (tint(i+1,j,2:nz) - tint(i,j,2:nz)) + &
202  beta(2:nz) * (sint(i+1,j,2:nz) - sint(i,j,2:nz)))
203  endif
204 
205  ! at this point, del2sigma contains the local neutral density curvature at
206  ! h-points, on interfaces
207  ! we need to divide by drho/dz to give an interfacial displacement
208  !
209  ! a positive curvature means we're too light relative to adjacent columns,
210  ! so del2sigma needs to be positive too (push the interface deeper)
211  call calculate_density_derivs(tint(i,j,:), sint(i,j,:), zint(i,j,:) * (gv%H_to_RZ * gv%g_Earth), &
212  alpha, beta, tv%eqn_of_state, (/1,nz+1/) )
213  do k = 2, nz
214  ! TODO make lower bound here configurable
215  dh_d2s(k) = del2sigma(k) * (0.5 * (h(i,j,k-1) + h(i,j,k))) / &
216  max(alpha(k) * (tv%T(i,j,k) - tv%T(i,j,k-1)) + &
217  beta(k) * (tv%S(i,j,k) - tv%S(i,j,k-1)), 1e-20*us%kg_m3_to_R)
218 
219  ! don't move the interface so far that it would tangle with another
220  ! interface in the direction we're moving (or exceed a Nyquist limit
221  ! that could cause oscillations of the interface)
222  h_up = merge(h(i,j,k), h(i,j,k-1), dh_d2s(k) > 0.)
223  dh_d2s(k) = 0.5 * cs%adaptAlpha * &
224  sign(min(abs(del2sigma(k)), 0.5 * h_up), dh_d2s(k))
225 
226  ! update interface positions so we can diffuse them
227  znext(k) = zint(i,j,k) + dh_d2s(k)
228  enddo
229 
230  ! solve diffusivity equation to smooth grid
231  ! upper diagonal coefficients: -kGrid(2:nz)
232  ! lower diagonal coefficients: -kGrid(1:nz-1)
233  ! diagonal coefficients: 1 + (kGrid(1:nz-1) + kGrid(2:nz))
234  !
235  ! first, calculate the diffusivities within layers
236  do k = 1, nz
237  ! calculate the dr bit of drdz
238  drdz = 0.5 * (alpha(k) + alpha(k+1)) * (tint(i,j,k+1) - tint(i,j,k)) + &
239  0.5 * (beta(k) + beta(k+1)) * (sint(i,j,k+1) - sint(i,j,k))
240  ! divide by dz from the new interface positions
241  drdz = drdz / (znext(k) - znext(k+1) + gv%H_subroundoff)
242  ! don't do weird stuff in unstably-stratified regions
243  drdz = max(drdz, 0.)
244 
245  ! set vertical grid diffusivity
246  kgrid(k) = (cs%adaptTimeRatio * nz**2 * depth) * &
247  (cs%adaptZoomCoeff / (cs%adaptZoom + 0.5*(znext(k) + znext(k+1))) + &
248  (cs%adaptBuoyCoeff * drdz / cs%adaptDrho0) + &
249  max(1.0 - cs%adaptZoomCoeff - cs%adaptBuoyCoeff, 0.0) / depth)
250  enddo
251 
252  ! initial denominator (first diagonal element)
253  b1 = 1.0
254  ! initial Q_1 = 1 - q_1 = 1 - 0/1
255  d1 = 1.0
256  ! work on all interior interfaces
257  do k = 2, nz
258  ! calculate numerator of Q_k
259  b_denom_1 = 1. + d1 * kgrid(k-1)
260  ! update denominator for k
261  b1 = 1.0 / (b_denom_1 + kgrid(k))
262 
263  ! calculate q_k
264  c1(k) = kgrid(k) * b1
265  ! update Q_k = 1 - q_k
266  d1 = b_denom_1 * b1
267 
268  ! update RHS
269  znext(k) = b1 * (znext(k) + kgrid(k-1)*znext(k-1))
270  enddo
271  ! final substitution
272  do k = nz, 2, -1
273  znext(k) = znext(k) + c1(k)*znext(k+1)
274  enddo
275 
276  if (cs%adaptDoMin) then
277  nominal_z = 0.
278  stretching = zint(i,j,nz+1) / depth
279 
280  do k = 2, nz+1
281  nominal_z = nominal_z + cs%coordinateResolution(k-1) * stretching
282  ! take the deeper of the calculated and nominal positions
283  znext(k) = max(znext(k), nominal_z)
284  ! interface can't go below topography
285  znext(k) = min(znext(k), zint(i,j,nz+1))
286  enddo
287  endif
288 end subroutine build_adapt_column
289 
290 end module coord_adapt
Describes various unit conversion factors.
Provides a transparent unit rescaling type to facilitate dimensional consistency testing.
Control structure for adaptive coordinates (coord_adapt).
Definition: coord_adapt.F90:17
Routines for error handling and I/O management.
Calculate the derivatives of density with temperature and salinity from T, S, and P...
Definition: MOM_EOS.F90:81
Provides subroutines for quantities specific to the equation of state.
Definition: MOM_EOS.F90:2
Describes the vertical ocean grid, including unit conversion factors.
Pointers to an assortment of thermodynamic fields that may be available, including potential temperat...
Provides a transparent vertical ocean grid type and supporting routines.
Provides transparent structures with groups of MOM6 variables and supporting routines.
Regrid columns for the adaptive coordinate.
Definition: coord_adapt.F90:2