MOM6
MOM_tracer_diabatic.F90
Go to the documentation of this file.
1 !> This module contains routines that implement physical fluxes of tracers (e.g. due
2 !! to surface fluxes or mixing). These are intended to be called from call_tracer_column_fns
3 !! in the MOM_tracer_flow_control module.
5 
6 ! This file is part of MOM6. See LICENSE.md for the license.
7 use mom_grid, only : ocean_grid_type
9 use mom_forcing_type, only : forcing
10 use mom_error_handler, only : mom_error, fatal, warning
11 
12 implicit none ; private
13 
14 #include <MOM_memory.h>
15 public tracer_vertdiff
17 !> This subroutine solves a tridiagonal equation for the final tracer
18 !! concentrations after the dual-entrainments, and possibly sinking or surface
19 !! and bottom sources, are applied. The sinking is implemented with an
20 !! fully implicit upwind advection scheme.
21 
22 contains
23 
24 subroutine tracer_vertdiff(h_old, ea, eb, dt, tr, G, GV, &
25  sfc_flux, btm_flux, btm_reservoir, sink_rate, convert_flux_in)
26  type(ocean_grid_type), intent(in) :: G !< ocean grid structure
27  type(verticalgrid_type), intent(in) :: GV !< ocean vertical grid structure
28  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h_old !< layer thickness before entrainment (m or kg m-2)
29  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: ea !< amount of fluid entrained from the layer above (units of h_work)
30  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: eb !< amount of fluid entrained from the layer below (units of h_work)
31  real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(inout) :: tr !< tracer concentration (in concentration units CU)
32  real, intent(in) :: dt !< amount of time covered by this call (seconds)
33  real, dimension(SZI_(G),SZJ_(G)), optional,intent(in) :: sfc_flux !< surface flux of the tracer (in CU * kg m-2 s-1)
34  real, dimension(SZI_(G),SZJ_(G)), optional,intent(in) :: btm_flux !< The (negative upward) bottom flux of the tracer,
35  !! in units of (CU * kg m-2 s-1)
36  real, dimension(SZI_(G),SZJ_(G)), optional,intent(inout) :: btm_reservoir !< amount of tracer in a bottom reservoir (units of CU kg m-2; formerly CU m)
37  real, optional,intent(in) :: sink_rate !< rate at which the tracer sinks, in m s-1
38  logical, optional,intent(in) :: convert_flux_in !< True if the specified sfc_flux needs to be integrated in time
39 
40  real :: sink_dist ! The distance the tracer sinks in a time step, in m or kg m-2.
41  real, dimension(SZI_(G),SZJ_(G)) :: &
42  sfc_src, & ! The time-integrated surface source of the tracer, in
43  ! units of m or kg m-2 times a concentration.
44  btm_src ! The time-integrated bottom source of the tracer, in
45  ! units of m or kg m-2 times a concentration.
46  real, dimension(SZI_(G)) :: &
47  b1, & ! b1 is used by the tridiagonal solver, in m-1 or m2 kg-1.
48  d1 ! d1=1-c1 is used by the tridiagonal solver, nondimensional.
49  real :: c1(szi_(g),szk_(gv)) ! c1 is used by the tridiagonal solver, ND.
50  real :: h_minus_dsink(szi_(g),szk_(gv)) ! The layer thickness minus the
51  ! difference in sinking rates across the layer, in m or kg m-2.
52  ! By construction, 0 <= h_minus_dsink < h_work.
53  real :: sink(szi_(g),szk_(gv)+1) ! The tracer's sinking distances at the
54  ! interfaces, limited to prevent characteristics from
55  ! crossing within a single timestep, in m or kg m-2.
56  real :: b_denom_1 ! The first term in the denominator of b1, in m or kg m-2.
57  real :: h_tr ! h_tr is h at tracer points with a h_neglect added to
58  ! ensure positive definiteness, in m or kg m-2.
59  real :: h_neglect ! A thickness that is so small it is usually lost
60  ! in roundoff and can be neglected, in m.
61  logical :: convert_flux = .true.
62 
63 
64  integer :: i, j, k, is, ie, js, je, nz
65  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec ; nz = gv%ke
66 
67  if (present(convert_flux_in)) convert_flux = convert_flux_in
68  h_neglect = gv%H_subroundoff
69  sink_dist = 0.0
70  if (present(sink_rate)) sink_dist = (dt*sink_rate) * gv%m_to_H
71 !$OMP parallel default(none) shared(is,ie,js,je,sfc_src,btm_src,sfc_flux,dt,G,GV,btm_flux, &
72 !$OMP sink_rate,btm_reservoir,nz,sink_dist,ea, &
73 !$OMP h_old,convert_flux,h_neglect,eb,tr) &
74 !$OMP private(sink,h_minus_dsink,b_denom_1,b1,d1,h_tr,c1)
75 !$OMP do
76  do j=js,je; do i=is,ie ; sfc_src(i,j) = 0.0 ; btm_src(i,j) = 0.0 ; enddo; enddo
77  if (present(sfc_flux)) then
78  if(convert_flux) then
79 !$OMP do
80  do j = js, je; do i = is,ie
81  sfc_src(i,j) = (sfc_flux(i,j)*dt) * gv%kg_m2_to_H
82  enddo; enddo
83  else
84 !$OMP do
85  do j = js, je; do i = is,ie
86  sfc_src(i,j) = sfc_flux(i,j)
87  enddo; enddo
88  endif
89  endif
90  if (present(btm_flux)) then
91  if(convert_flux) then
92 !$OMP do
93  do j = js, je; do i = is,ie
94  btm_src(i,j) = (btm_flux(i,j)*dt) * gv%kg_m2_to_H
95  enddo; enddo
96  else
97 !$OMP do
98  do j = js, je; do i = is,ie
99  btm_src(i,j) = btm_flux(i,j)
100  enddo; enddo
101  endif
102  endif
103 
104  if (present(sink_rate)) then
105 !$OMP do
106  do j=js,je
107  ! Find the sinking rates at all interfaces, limiting them if necesary
108  ! so that the characteristics do not cross within a timestep.
109  ! If a non-constant sinking rate were used, that would be incorprated
110  ! here.
111  if (present(btm_reservoir)) then
112  do i=is,ie ; sink(i,nz+1) = sink_dist ; enddo
113  do k=2,nz ; do i=is,ie
114  sink(i,k) = sink_dist ; h_minus_dsink(i,k) = h_old(i,j,k)
115  enddo ; enddo
116  else
117  do i=is,ie ; sink(i,nz+1) = 0.0 ; enddo
118  ! Find the limited sinking distance at the interfaces.
119  do k=nz,2,-1 ; do i=is,ie
120  if (sink(i,k+1) >= sink_dist) then
121  sink(i,k) = sink_dist
122  h_minus_dsink(i,k) = h_old(i,j,k) + (sink(i,k+1) - sink(i,k))
123  elseif (sink(i,k+1) + h_old(i,j,k) < sink_dist) then
124  sink(i,k) = sink(i,k+1) + h_old(i,j,k)
125  h_minus_dsink(i,k) = 0.0
126  else
127  sink(i,k) = sink_dist
128  h_minus_dsink(i,k) = (h_old(i,j,k) + sink(i,k+1)) - sink(i,k)
129  endif
130  enddo ; enddo
131  endif
132  do i=is,ie
133  sink(i,1) = 0.0 ; h_minus_dsink(i,1) = (h_old(i,j,1) + sink(i,2))
134  enddo
135 
136  ! Now solve the tridiagonal equation for the tracer concentrations.
137  do i=is,ie ; if (g%mask2dT(i,j) > 0.5) then
138  b_denom_1 = h_minus_dsink(i,1) + ea(i,j,1) + h_neglect
139  b1(i) = 1.0 / (b_denom_1 + eb(i,j,1))
140  d1(i) = b_denom_1 * b1(i)
141  h_tr = h_old(i,j,1) + h_neglect
142  tr(i,j,1) = (b1(i)*h_tr)*tr(i,j,1) + sfc_src(i,j)
143  endif ; enddo
144  do k=2,nz-1 ; do i=is,ie ; if (g%mask2dT(i,j) > 0.5) then
145  c1(i,k) = eb(i,j,k-1) * b1(i)
146  b_denom_1 = h_minus_dsink(i,k) + d1(i) * (ea(i,j,k) + sink(i,k)) + &
147  h_neglect
148  b1(i) = 1.0 / (b_denom_1 + eb(i,j,k))
149  d1(i) = b_denom_1 * b1(i)
150  h_tr = h_old(i,j,k) + h_neglect
151  tr(i,j,k) = b1(i) * (h_tr * tr(i,j,k) + &
152  (ea(i,j,k) + sink(i,k)) * tr(i,j,k-1))
153  endif ; enddo ; enddo
154  do i=is,ie ; if (g%mask2dT(i,j) > 0.5) then
155  c1(i,nz) = eb(i,j,nz-1) * b1(i)
156  b_denom_1 = h_minus_dsink(i,nz) + d1(i) * (ea(i,j,nz) + sink(i,nz)) + &
157  h_neglect
158  b1(i) = 1.0 / (b_denom_1 + eb(i,j,nz))
159  h_tr = h_old(i,j,nz) + h_neglect
160  tr(i,j,nz) = b1(i) * ((h_tr * tr(i,j,nz) + btm_src(i,j)) + &
161  (ea(i,j,nz) + sink(i,nz)) * tr(i,j,nz-1))
162  endif ; enddo
163  if (present(btm_reservoir)) then ; do i=is,ie ; if (g%mask2dT(i,j)>0.5) then
164  btm_reservoir(i,j) = btm_reservoir(i,j) + &
165  (sink(i,nz+1)*tr(i,j,nz)) * gv%H_to_kg_m2
166  endif ; enddo ; endif
167 
168  do k=nz-1,1,-1 ; do i=is,ie ; if (g%mask2dT(i,j) > 0.5) then
169  tr(i,j,k) = tr(i,j,k) + c1(i,k+1)*tr(i,j,k+1)
170  endif ; enddo ; enddo
171  enddo
172  else
173 !$OMP do
174  do j=js,je
175  do i=is,ie ; if (g%mask2dT(i,j) > -0.5) then
176  h_tr = h_old(i,j,1) + h_neglect
177  b_denom_1 = h_tr + ea(i,j,1)
178  b1(i) = 1.0 / (b_denom_1 + eb(i,j,1))
179  d1(i) = h_tr * b1(i)
180  tr(i,j,1) = (b1(i)*h_tr)*tr(i,j,1) + sfc_src(i,j)
181  endif
182  enddo
183  do k=2,nz-1 ; do i=is,ie ; if (g%mask2dT(i,j) > -0.5) then
184  c1(i,k) = eb(i,j,k-1) * b1(i)
185  h_tr = h_old(i,j,k) + h_neglect
186  b_denom_1 = h_tr + d1(i) * ea(i,j,k)
187  b1(i) = 1.0 / (b_denom_1 + eb(i,j,k))
188  d1(i) = b_denom_1 * b1(i)
189  tr(i,j,k) = b1(i) * (h_tr * tr(i,j,k) + ea(i,j,k) * tr(i,j,k-1))
190  endif ; enddo ; enddo
191  do i=is,ie ; if (g%mask2dT(i,j) > -0.5) then
192  c1(i,nz) = eb(i,j,nz-1) * b1(i)
193  h_tr = h_old(i,j,nz) + h_neglect
194  b_denom_1 = h_tr + d1(i)*ea(i,j,nz)
195  b1(i) = 1.0 / ( b_denom_1 + eb(i,j,nz))
196  tr(i,j,nz) = b1(i) * (( h_tr * tr(i,j,nz) + btm_src(i,j)) + &
197  ea(i,j,nz) * tr(i,j,nz-1))
198  endif ; enddo
199  do k=nz-1,1,-1 ; do i=is,ie ; if (g%mask2dT(i,j) > -0.5) then
200  tr(i,j,k) = tr(i,j,k) + c1(i,k+1)*tr(i,j,k+1)
201  endif ; enddo ; enddo
202  enddo
203  endif
204 
205 !$OMP end parallel
206 
207 end subroutine tracer_vertdiff
208 
209 !> This routine is modeled after applyBoundaryFluxesInOut in MOM_diabatic_aux.F90
210 !! NOTE: Please note that in this routine sfc_flux gets set to zero to ensure that the surface
211 !! flux of the tracer does not get applied again during a subsequent call to tracer_vertdif
212 subroutine applytracerboundaryfluxesinout(G, GV, Tr, dt, fluxes, h, evap_CFL_limit, minimum_forcing_depth, &
213  in_flux_optional, out_flux_optional, update_h_opt)
215  type(ocean_grid_type), intent(in ) :: G !< Grid structure
216  type(verticalgrid_type), intent(in ) :: GV !< ocean vertical grid structure
217  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(inout) :: Tr !< Tracer concentration on T-cell
218  real, intent(in ) :: dt !< Time-step over which forcing is applied (s)
219  type(forcing), intent(in ) :: fluxes !< Surface fluxes container
220  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(inout) :: h !< Layer thickness in H units
221  real, intent(in ) :: evap_CFL_limit
222  real, intent(in ) :: minimum_forcing_depth
223  real, dimension(SZI_(G),SZJ_(G)), optional, intent(in ) :: in_flux_optional ! The total time-integrated amount of tracer!
224  ! that enters with freshwater
225  real, dimension(SZI_(G),SZJ_(G)), optional, intent(in) :: out_flux_optional ! The total time-integrated amount of tracer!
226  ! that leaves with freshwater
227  !< Optional flag to determine whether h should be updated
228  logical, optional, intent(in) :: update_h_opt
229 
230  integer, parameter :: maxGroundings = 5
231  integer :: numberOfGroundings, iGround(maxgroundings), jGround(maxgroundings)
232  real :: H_limit_fluxes, IforcingDepthScale, Idt
233  real :: dThickness, dTracer
234  real :: fractionOfForcing, hOld, Ithickness
235  real :: RivermixConst ! A constant used in implementing river mixing, in Pa s.
236  real, dimension(SZI_(G)) :: &
237  netMassInOut, & ! surface water fluxes (H units) over time step
238  netMassIn, & ! mass entering ocean surface (H units) over a time step
239  netMassOut ! mass leaving ocean surface (H units) over a time step
240 
241  real, dimension(SZI_(G), SZK_(G)) :: h2d, Tr2d
242  real, dimension(SZI_(G),SZJ_(G)) :: in_flux ! The total time-integrated amount of tracer!
243  ! that enters with freshwater
244  real, dimension(SZI_(G),SZJ_(G)) :: out_flux ! The total time-integrated amount of tracer!
245  ! that leaves with freshwater
246  real, dimension(SZI_(G)) :: in_flux_1d, out_flux_1d
247  real :: hGrounding(maxgroundings)
248  real :: Tr_in
249  logical :: update_h
250  integer :: i, j, is, ie, js, je, k, nz, n, nsw
251  character(len=45) :: mesg
252 
253  is = g%isc ; ie = g%iec ; js = g%jsc ; je = g%jec ; nz = g%ke
254 
255 ! ! Only apply forcing if fluxes%sw is associated.
256 ! if (.not.ASSOCIATED(fluxes%sw)) return
257 
258  in_flux(:,:) = 0.0 ; out_flux(:,:) = 0.0
259  if(present(in_flux_optional)) then
260  do j=js,je ; do i=is,ie
261  in_flux(i,j) = in_flux_optional(i,j)
262  enddo; enddo
263  endif
264  if(present(out_flux_optional)) then
265  do j=js,je ; do i=is,ie
266  out_flux(i,j) = out_flux_optional(i,j)
267  enddo ; enddo
268  endif
269 
270  if (present(update_h_opt)) then
271  update_h = update_h_opt
272  else
273  update_h = .true.
274  endif
275 
276  idt = 1.0/dt
277  numberofgroundings = 0
278 
279 !$OMP parallel do default(none) shared(is,ie,js,je,nz,h,Tr,G,GV,fluxes,dt, &
280 !$OMP IforcingDepthScale,minimum_forcing_depth, &
281 !$OMP numberOfGroundings,iGround,jGround,update_h, &
282 !$OMP in_flux,out_flux,hGrounding,Idt,evap_CFL_limit) &
283 !$OMP private(h2d,Tr2d,netMassInOut,netMassOut, &
284 !$OMP in_flux_1d,out_flux_1d,fractionOfForcing, &
285 !$OMP dThickness,dTracer,hOld,Ithickness, &
286 !$OMP netMassIn, Tr_in)
287 
288  ! Work in vertical slices for efficiency
289  do j=js,je
290 
291  ! Copy state into 2D-slice arrays
292  do k=1,nz ; do i=is,ie
293  h2d(i,k) = h(i,j,k)
294  tr2d(i,k) = tr(i,j,k)
295  enddo ; enddo
296 
297  do i = is,ie
298  in_flux_1d(i) = in_flux(i,j)
299  out_flux_1d(i) = out_flux(i,j)
300  enddo
301  ! The surface forcing is contained in the fluxes type.
302  ! We aggregate the thermodynamic forcing for a time step into the following:
303  ! These should have been set and stored during a call to applyBoundaryFluxesInOut
304  ! netMassIn = net mass entering at ocean surface over a timestep
305  ! netMassOut = net mass leaving ocean surface (H units) over a time step.
306  ! netMassOut < 0 means mass leaves ocean.
307 
308  ! Note here that the aggregateFW flag has already been taken care of in the call to
309  ! applyBoundaryFluxesInOut
310  do i=is,ie
311  netmassout(i) = fluxes%netMassOut(i,j)
312  netmassin(i) = fluxes%netMassIn(i,j)
313  enddo
314 
315  ! Apply the surface boundary fluxes in three steps:
316  ! A/ update concentration from mass entering the ocean
317  ! B/ update concentration from mass leaving ocean.
318  do i=is,ie
319  if (g%mask2dT(i,j)>0.) then
320 
321  ! A/ Update tracer due to incoming mass flux.
322  do k=1,1
323 
324  ! Change in state due to forcing
325  dthickness = netmassin(i) ! Since we are adding mass, we can use all of it
326  dtracer = 0.
327 
328  ! Update the forcing by the part to be consumed within the present k-layer.
329  ! If fractionOfForcing = 1, then updated netMassIn, netHeat, and netSalt vanish.
330  netmassin(i) = netmassin(i) - dthickness
331  dtracer = dtracer + in_flux_1d(i)
332  in_flux_1d(i) = 0.0
333 
334  ! Update state
335  hold = h2d(i,k) ! Keep original thickness in hand
336  h2d(i,k) = h2d(i,k) + dthickness ! New thickness
337  if (h2d(i,k) > 0.0) then
338  ithickness = 1.0/h2d(i,k) ! Inverse new thickness
339  ! The "if"s below avoid changing T/S by roundoff unnecessarily
340  if (dthickness /= 0. .or. dtracer /= 0.) tr2d(i,k) = (hold*tr2d(i,k)+ dtracer)*ithickness
341  endif
342 
343  enddo ! k=1,1
344 
345  ! B/ Update tracer from mass leaving ocean
346  do k=1,nz
347 
348  ! Place forcing into this layer if this layer has nontrivial thickness.
349  ! For layers thin relative to 1/IforcingDepthScale, then distribute
350  ! forcing into deeper layers.
351  iforcingdepthscale = 1. / max(gv%H_subroundoff, minimum_forcing_depth*gv%m_to_H - netmassout(i) )
352  ! fractionOfForcing = 1.0, unless h2d is less than IforcingDepthScale.
353  fractionofforcing = min(1.0, h2d(i,k)*iforcingdepthscale)
354 
355  ! In the case with (-1)*netMassOut*fractionOfForcing greater than cfl*h, we
356  ! limit the forcing applied to this cell, leaving the remaining forcing to
357  ! be distributed downwards.
358  if (-fractionofforcing*netmassout(i) > evap_cfl_limit*h2d(i,k)) then
359  fractionofforcing = -evap_cfl_limit*h2d(i,k)/netmassout(i)
360  endif
361 
362  ! Change in state due to forcing
363  dthickness = max( fractionofforcing*netmassout(i), -h2d(i,k) )
364  ! Note this is slightly different to how salt is currently treated
365  dtracer = fractionofforcing*out_flux_1d(i)
366 
367  ! Update the forcing by the part to be consumed within the present k-layer.
368  ! If fractionOfForcing = 1, then new netMassOut vanishes.
369  netmassout(i) = netmassout(i) - dthickness
370  out_flux_1d(i) = out_flux_1d(i) - dtracer
371 
372  ! Update state by the appropriate increment.
373  hold = h2d(i,k) ! Keep original thickness in hand
374  h2d(i,k) = h2d(i,k) + dthickness ! New thickness
375  if (h2d(i,k) > 0.) then
376  ithickness = 1.0/h2d(i,k) ! Inverse of new thickness
377  tr2d(i,k) = (hold*tr2d(i,k) + dtracer)*ithickness
378  endif
379 
380  enddo ! k
381 
382  endif
383 
384  ! If anything remains after the k-loop, then we have grounded out, which is a problem.
385  if (abs(in_flux_1d(i))+abs(out_flux_1d(i)) /= 0.0) then
386 !$OMP critical
387  numberofgroundings = numberofgroundings +1
388  if (numberofgroundings<=maxgroundings) then
389  iground(numberofgroundings) = i ! Record i,j location of event for
390  jground(numberofgroundings) = j ! warning message
391  hgrounding(numberofgroundings) = abs(in_flux_1d(i))+abs(out_flux_1d(i))
392  endif
393 !$OMP end critical
394  endif
395 
396  enddo ! i
397 
398  ! Step C/ copy updated tracer concentration from the 2d slice now back into model state.
399  do k=1,nz ; do i=is,ie
400  tr(i,j,k) = tr2d(i,k)
401  enddo ; enddo
402 
403  if (update_h) then
404  do k=1,nz ; do i=is,ie
405  h(i,j,k) = h2d(i,k)
406  enddo ; enddo
407  endif
408 
409  enddo ! j-loop finish
410 
411  if (numberofgroundings>0) then
412  do i = 1, min(numberofgroundings, maxgroundings)
413  write(mesg(1:45),'(3es15.3)') g%geoLonT( iground(i), jground(i) ), &
414  g%geoLatT( iground(i), jground(i)) , hgrounding(i)
415  call mom_error(warning, "MOM_tracer_vertical.F90, applyTracerBoundaryFluxesInOut(): "//&
416  "Tracer created. x,y,dh= "//trim(mesg), all_print=.true.)
417  enddo
418 
419  if (numberofgroundings - maxgroundings > 0) then
420  write(mesg, '(i4)') numberofgroundings - maxgroundings
421  call mom_error(warning, "MOM_tracer_vertical.F90, applyTracerBoundaryFluxesInOut(): "//&
422  trim(mesg) // " groundings remaining")
423  endif
424  endif
425 
426 end subroutine applytracerboundaryfluxesinout
427 end module mom_tracer_diabatic
This module implements boundary forcing for MOM6.
Ocean grid type. See mom_grid for details.
Definition: MOM_grid.F90:19
Provides the ocean grid type.
Definition: MOM_grid.F90:2
subroutine, public applytracerboundaryfluxesinout(G, GV, Tr, dt, fluxes, h, evap_CFL_limit, minimum_forcing_depth, in_flux_optional, out_flux_optional, update_h_opt)
This routine is modeled after applyBoundaryFluxesInOut in MOM_diabatic_aux.F90 NOTE: Please note that...
This module contains routines that implement physical fluxes of tracers (e.g. due to surface fluxes o...
Structure that contains pointers to the boundary forcing used to drive the liquid ocean simulated by ...
subroutine, public tracer_vertdiff(h_old, ea, eb, dt, tr, G, GV, sfc_flux, btm_flux, btm_reservoir, sink_rate, convert_flux_in)
This subroutine solves a tridiagonal equation for the final tracer concentrations after the dual-entr...
subroutine, public mom_error(level, message, all_print)