MOM6
user_change_diffusivity.F90
Go to the documentation of this file.
2 !***********************************************************************
3 !* GNU General Public License *
4 !* This file is a part of MOM. *
5 !* *
6 !* MOM is free software; you can redistribute it and/or modify it and *
7 !* are expected to follow the terms of the GNU General Public License *
8 !* as published by the Free Software Foundation; either version 2 of *
9 !* the License, or (at your option) any later version. *
10 !* *
11 !* MOM is distributed in the hope that it will be useful, but WITHOUT *
12 !* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
13 !* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public *
14 !* License for more details. *
15 !* *
16 !* For the full text of the GNU General Public License, *
17 !* write to: Free Software Foundation, Inc., *
18 !* 675 Mass Ave, Cambridge, MA 02139, USA. *
19 !* or see: http://www.gnu.org/licenses/gpl.html *
20 !***********************************************************************
21 
22 use mom_diag_mediator, only : diag_ctrl, time_type
23 use mom_error_handler, only : mom_error, is_root_pe, fatal, warning, note
25 use mom_grid, only : ocean_grid_type
27 use mom_eos, only : calculate_density
28 
29 implicit none ; private
30 
31 #include <MOM_memory.h>
32 
35 
36 type, public :: user_change_diff_cs ; private
37  real :: kd_add ! The scale of a diffusivity that is added everywhere
38  ! without any filtering or scaling, in m2 s-1.
39  real :: lat_range(4) ! 4 values that define the latitude range over which
40  ! a diffusivity scaled by Kd_add is added, in deg.
41  real :: rho_range(4) ! 4 values that define the coordinate potential
42  ! density range over which a diffusivity scaled by
43  ! Kd_add is added, in kg m-3.
44  logical :: use_abs_lat ! If true, use the absolute value of latitude when
45  ! setting lat_range.
46  type(diag_ctrl), pointer :: diag ! A structure that is used to regulate the
47  ! timing of diagnostic output.
48 end type user_change_diff_cs
49 
50 contains
51 
52 !> This subroutine provides an interface for a user to use to modify the
53 !! main code to alter the diffusivities as needed. The specific example
54 !! implemented here augments the diffusivity for a specified range of latitude
55 !! and coordinate potential density.
56 subroutine user_change_diff(h, tv, G, CS, Kd, Kd_int, T_f, S_f, Kd_int_add)
57  type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure.
58  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(in) :: h !< Layer thickness, in m or kg m-2.
59  type(thermo_var_ptrs), intent(in) :: tv !< A structure containing pointers
60  !! to any available thermodynamic
61  !! fields. Absent fields have NULL ptrs.
62  type(user_change_diff_cs), pointer :: CS !< This module's control structure.
63  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), optional, intent(inout) :: Kd !< The diapycnal diffusivity of
64  !! each layer in m2 s-1.
65  real, dimension(SZI_(G),SZJ_(G),SZK_(G)+1), optional, intent(inout) :: Kd_int !< The diapycnal diffusivity
66  !! at each interface in m2 s-1.
67  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), optional, intent(in) :: T_f !< Temperature with massless
68  !! layers filled in vertically.
69  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), optional, intent(in) :: S_f !< Salinity with massless
70  !! layers filled in vertically.
71  real, dimension(:,:,:), optional, pointer :: Kd_int_add !< The diapycnal
72  !! diffusivity that is being added at
73  !! each interface in m2 s-1.
74 
75  real :: Rcv(szi_(g),szk_(g)) ! The coordinate density in layers in kg m-3.
76  real :: p_ref(szi_(g)) ! An array of tv%P_Ref pressures.
77  real :: rho_fn ! The density dependence of the input function, 0-1, ND.
78  real :: lat_fn ! The latitude dependence of the input function, 0-1, ND.
79  logical :: use_EOS ! If true, density is calculated from T & S using an
80  ! equation of state.
81  logical :: store_Kd_add ! Save the added diffusivity as a diagnostic if true.
82  integer :: i, j, k, is, ie, js, je, nz
83  integer :: isd, ied, jsd, jed
84 
85  real :: kappa_fill ! diffusivity used to fill massless layers
86  real :: dt_fill ! timestep used to fill massless layers
87  character(len=200) :: mesg
88 
89  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec ; nz = g%ke
90  isd = g%isd ; ied = g%ied ; jsd = g%jsd ; jed = g%jed
91 
92  if (.not.associated(cs)) call mom_error(fatal,"user_set_diffusivity: "//&
93  "Module must be initialized before it is used.")
94 
95  use_eos = associated(tv%eqn_of_state)
96  if (.not.use_eos) return
97  store_kd_add = .false.
98  if (present(kd_int_add)) store_kd_add = associated(kd_int_add)
99 
100  if (.not.range_ok(cs%lat_range)) then
101  write(mesg, '(4(1pe15.6))') cs%lat_range(1:4)
102  call mom_error(fatal, "user_set_diffusivity: bad latitude range: \n "//&
103  trim(mesg))
104  endif
105  if (.not.range_ok(cs%rho_range)) then
106  write(mesg, '(4(1pe15.6))') cs%rho_range(1:4)
107  call mom_error(fatal, "user_set_diffusivity: bad density range: \n "//&
108  trim(mesg))
109  endif
110 
111  if (store_kd_add) kd_int_add(:,:,:) = 0.0
112 
113  do i=is,ie ; p_ref(i) = tv%P_Ref ; enddo
114  do j=js,je
115  if (present(t_f) .and. present(s_f)) then
116  do k=1,nz
117  call calculate_density(t_f(:,j,k),s_f(:,j,k),p_ref,rcv(:,k),&
118  is,ie-is+1,tv%eqn_of_state)
119  enddo
120  else
121  do k=1,nz
122  call calculate_density(tv%T(:,j,k),tv%S(:,j,k),p_ref,rcv(:,k),&
123  is,ie-is+1,tv%eqn_of_state)
124  enddo
125  endif
126 
127  if (present(kd)) then
128  do k=1,nz ; do i=is,ie
129  if (cs%use_abs_lat) then
130  lat_fn = val_weights(abs(g%geoLatT(i,j)), cs%lat_range)
131  else
132  lat_fn = val_weights(g%geoLatT(i,j), cs%lat_range)
133  endif
134  rho_fn = val_weights(rcv(i,k), cs%rho_range)
135  if (rho_fn * lat_fn > 0.0) &
136  kd(i,j,k) = kd(i,j,k) + cs%Kd_add * rho_fn * lat_fn
137  enddo ; enddo
138  endif
139  if (present(kd_int)) then
140  do k=2,nz ; do i=is,ie
141  if (cs%use_abs_lat) then
142  lat_fn = val_weights(abs(g%geoLatT(i,j)), cs%lat_range)
143  else
144  lat_fn = val_weights(g%geoLatT(i,j), cs%lat_range)
145  endif
146  ! rho_int = 0.5*(Rcv(i,k-1) + Rcv(i,k))
147  rho_fn = val_weights( 0.5*(rcv(i,k-1) + rcv(i,k)), cs%rho_range)
148  if (rho_fn * lat_fn > 0.0) then
149  kd_int(i,j,k) = kd_int(i,j,k) + cs%Kd_add * rho_fn * lat_fn
150  if (store_kd_add) kd_int_add(i,j,k) = cs%Kd_add * rho_fn * lat_fn
151  endif
152  enddo ; enddo
153  endif
154  enddo
155 
156 end subroutine user_change_diff
157 
158 !> This subroutine checks whether the 4 values of range are in ascending order.
159 function range_ok(range) result(OK)
160  real, dimension(4), intent(in) :: range !< Four values to check.
161  logical :: OK !< Return value.
162 
163 
164  ok = ((range(1) <= range(2)) .and. (range(2) <= range(3)) .and. &
165  (range(3) <= range(4)))
166 
167 end function range_ok
168 
169 !> This subroutine returns a value that goes smoothly from 0 to 1, stays
170 !! at 1, and then goes smoothly back to 0 at the four values of range. The
171 !! transitions are cubic, and have zero first derivatives where the curves
172 !! hit 0 and 1. The values in range must be in ascending order, as can be
173 !! checked by calling range_OK.
174 function val_weights(val, range) result(ans)
175  real, intent(in) :: val !< Value for which we need an answer.
176  real, dimension(4), intent(in) :: range !< Range over which the answer is non-zero.
177  real :: ans !< Return value.
178 
179  real :: x ! A nondimensional number between 0 and 1.
180 
181  ans = 0.0
182  if ((val > range(1)) .and. (val < range(4))) then
183  if (val < range(2)) then
184  ! x goes from 0 to 1; ans goes from 0 to 1, with 0 derivatives at the ends.
185  x = (val - range(1)) / (range(2) - range(1))
186  ans = x**2 * (3.0 - 2.0 * x)
187  elseif (val > range(3)) then
188  ! x goes from 0 to 1; ans goes from 0 to 1, with 0 derivatives at the ends.
189  x = (range(4) - val) / (range(4) - range(3))
190  ans = x**2 * (3.0 - 2.0 * x)
191  else
192  ans = 1.0
193  endif
194  endif
195 
196 end function val_weights
197 
198 !> Set up the module control structure.
199 subroutine user_change_diff_init(Time, G, param_file, diag, CS)
200  type(time_type), intent(in) :: Time !< The current model time.
201  type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure.
202  type(param_file_type), intent(in) :: param_file !< A structure indicating the
203  !! open file to parse for
204  !! model parameter values.
205  type(diag_ctrl), target, intent(inout) :: diag !< A structure that is used to
206  !! regulate diagnostic output.
207  type(user_change_diff_cs), pointer :: CS !< A pointer that is set to
208  !! point to the control
209  !! structure for this module.
210 
211 ! This include declares and sets the variable "version".
212 #include "version_variable.h"
213  character(len=40) :: mdl = "user_set_diffusivity" ! This module's name.
214  character(len=200) :: mesg
215  integer :: i, j, is, ie, js, je
216 
217  if (associated(cs)) then
218  call mom_error(warning, "diabatic_entrain_init called with an associated "// &
219  "control structure.")
220  return
221  endif
222  allocate(cs)
223 
224  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec
225 
226  cs%diag => diag
227 
228  ! Read all relevant parameters and write them to the model log.
229  call log_version(param_file, mdl, version, "")
230  call get_param(param_file, mdl, "USER_KD_ADD", cs%Kd_add, &
231  "A user-specified additional diffusivity over a range of \n"//&
232  "latitude and density.", units="m2 s-1", default=0.0)
233  if (cs%Kd_add /= 0.0) then
234  call get_param(param_file, mdl, "USER_KD_ADD_LAT_RANGE", cs%lat_range(:), &
235  "Four successive values that define a range of latitudes \n"//&
236  "over which the user-specified extra diffusivity is \n"//&
237  "applied. The four values specify the latitudes at \n"//&
238  "which the extra diffusivity starts to increase from 0, \n"//&
239  "hits its full value, starts to decrease again, and is \n"//&
240  "back to 0.", units="degree", default=-1.0e9)
241  call get_param(param_file, mdl, "USER_KD_ADD_RHO_RANGE", cs%rho_range(:), &
242  "Four successive values that define a range of potential \n"//&
243  "densities over which the user-given extra diffusivity \n"//&
244  "is applied. The four values specify the density at \n"//&
245  "which the extra diffusivity starts to increase from 0, \n"//&
246  "hits its full value, starts to decrease again, and is \n"//&
247  "back to 0.", units="kg m-3", default=-1.0e9)
248  call get_param(param_file, mdl, "USER_KD_ADD_USE_ABS_LAT", cs%use_abs_lat, &
249  "If true, use the absolute value of latitude when \n"//&
250  "checking whether a point fits into range of latitudes.", &
251  default=.false.)
252  endif
253 
254  if (.not.range_ok(cs%lat_range)) then
255  write(mesg, '(4(1pe15.6))') cs%lat_range(1:4)
256  call mom_error(fatal, "user_set_diffusivity: bad latitude range: \n "//&
257  trim(mesg))
258  endif
259  if (.not.range_ok(cs%rho_range)) then
260  write(mesg, '(4(1pe15.6))') cs%rho_range(1:4)
261  call mom_error(fatal, "user_set_diffusivity: bad density range: \n "//&
262  trim(mesg))
263  endif
264 
265 end subroutine user_change_diff_init
266 
267 !> Clean up the module control structure.
268 subroutine user_change_diff_end(CS)
269  type(user_change_diff_cs), pointer :: CS !< A pointer that is set to
270  !! point to the control
271  !! structure for this module.
272 
273  if (associated(cs)) deallocate(cs)
274 
275 end subroutine user_change_diff_end
276 
277 !> \namespace user_change_diffusivity
278 !!
279 !! By Robert Hallberg, May 2012
280 !!
281 !! This file contains a subroutine that increments the diapycnal
282 !! diffusivity in a specified band of latitudes and densities.
283 !!
284 !! A small fragment of the grid is shown below:
285 !!
286 !! j+1 x ^ x ^ x At x: q
287 !! j+1 > o > o > At ^: v
288 !! j x ^ x ^ x At >: u
289 !! j > o > o > At o: h, T, S, Kd, etc.
290 !! j-1 x ^ x ^ x
291 !! i-1 i i+1 At x & ^:
292 !! i i+1 At > & o:
293 !!
294 !! The boundaries always run through q grid points (x).
295 
296 end module user_change_diffusivity
logical function range_ok(range)
This subroutine checks whether the 4 values of range are in ascending order.
subroutine, public user_change_diff_end(CS)
Clean up the module control structure.
real function val_weights(val, range)
This subroutine returns a value that goes smoothly from 0 to 1, stays at 1, and then goes smoothly ba...
Ocean grid type. See mom_grid for details.
Definition: MOM_grid.F90:19
The following data type a list of diagnostic fields an their variants, as well as variables that cont...
Calculates density of sea water from T, S and P.
Definition: MOM_EOS.F90:45
Provides the ocean grid type.
Definition: MOM_grid.F90:2
The vertvisc_type structure contains vertical viscosities, drag coefficients, and related fields...
subroutine, public user_change_diff_init(Time, G, param_file, diag, CS)
Set up the module control structure.
logical function, public is_root_pe()
Provides subroutines for quantities specific to the equation of state.
Definition: MOM_EOS.F90:2
subroutine, public user_change_diff(h, tv, G, CS, Kd, Kd_int, T_f, S_f, Kd_int_add)
This subroutine provides an interface for a user to use to modify the main code to alter the diffusiv...
The thermo_var_ptrs structure contains pointers to an assortment of thermodynamic fields that may be ...
By Robert Hallberg, May 2012.
subroutine, public mom_error(level, message, all_print)