← back to index

src/tracer/MOM_tracer_hor_diff.F90

portedportable, not yet portedexecuted, not portableexecutable, not hit by this run

1! This file is part of MOM6, the Modular Ocean Model version 6.
2! See the LICENSE file for licensing information.
3! SPDX-License-Identifier: Apache-2.0
4#include "do_concurrent_compat.h"
5
6!> Main routine for lateral (along surface or neutral) diffusion of tracers
7module MOM_tracer_hor_diff
8
9use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end
10use MOM_cpu_clock, only : CLOCK_MODULE, CLOCK_ROUTINE
11use MOM_diag_mediator, only : post_data, diag_ctrl
12use MOM_diag_mediator, only : register_diag_field, safe_alloc_ptr, time_type
13use MOM_domains, only : sum_across_PEs, max_across_PEs
14use MOM_domains, only : create_group_pass, do_group_pass, group_pass_type
15use MOM_domains, only : pass_vector
16use MOM_debugging, only : hchksum, uvchksum
17use MOM_diabatic_driver, only : diabatic_CS
18use MOM_EOS, only : calculate_density, EOS_type, EOS_domain
19use MOM_error_handler, only : MOM_error, FATAL, WARNING, MOM_mesg, is_root_pe
20use MOM_error_handler, only : MOM_set_verbosity, callTree_showQuery
21use MOM_error_handler, only : callTree_enter, callTree_leave, callTree_waypoint
22use MOM_file_parser, only : get_param, log_version, param_file_type
23use MOM_grid, only : ocean_grid_type
24use MOM_lateral_mixing_coeffs, only : VarMix_CS
25use MOM_MEKE_types, only : MEKE_type
26use MOM_neutral_diffusion, only : neutral_diffusion_init, neutral_diffusion_end
27use MOM_neutral_diffusion, only : neutral_diffusion_CS
28use MOM_neutral_diffusion, only : neutral_diffusion_calc_coeffs, neutral_diffusion
29use MOM_hor_bnd_diffusion, only : hbd_CS, hor_bnd_diffusion_init
30use MOM_hor_bnd_diffusion, only : hor_bnd_diffusion, hor_bnd_diffusion_end
31use MOM_tracer_registry, only : tracer_registry_type, tracer_type, MOM_tracer_chksum
32use MOM_unit_scaling, only : unit_scale_type
33use MOM_variables, only : thermo_var_ptrs, vertvisc_type
34use MOM_verticalGrid, only : verticalGrid_type
35
36implicit none ; private
37
38#include <MOM_memory.h>
39
40public tracer_hordiff, tracer_hor_diff_init, tracer_hor_diff_end
41
42!> The control structure for along-layer and epineutral tracer diffusion
43type, public :: tracer_hor_diff_CS ; private
44 real :: KhTr !< The along-isopycnal tracer diffusivity [L2 T-1 ~> m2 s-1].
45 real :: KhTr_Slope_Cff !< The non-dimensional coefficient in KhTr formula [nondim]
46 real :: KhTr_min !< Minimum along-isopycnal tracer diffusivity [L2 T-1 ~> m2 s-1].
47 real :: KhTr_max !< Maximum along-isopycnal tracer diffusivity [L2 T-1 ~> m2 s-1].
48 real :: KhTr_passivity_coeff !< Passivity coefficient that scales Rd/dx (default = 0)
49 !! where passivity is the ratio between along-isopycnal
50 !! tracer mixing and thickness mixing [nondim]
51 real :: KhTr_passivity_min !< Passivity minimum (default = 1/2) [nondim]
52 real :: ML_KhTR_scale !< With Diffuse_ML_interior, the ratio of the
53 !! truly horizontal diffusivity in the mixed
54 !! layer to the epipycnal diffusivity [nondim].
55 real :: max_diff_CFL !< If positive, locally limit the along-isopycnal
56 !! tracer diffusivity to keep the diffusive CFL
57 !! locally at or below this value [nondim].
58 logical :: KhTr_use_vert_struct !< If true, uses the equivalent barotropic structure
59 !! as the vertical structure of tracer diffusivity.
60 logical :: full_depth_khtr_min !< If true, KHTR_MIN is enforced throughout the whole water column.
61 !! Otherwise, KHTR_MIN is only enforced at the surface. This parameter
62 !! is only available when KHTR_USE_EBT_STRUCT=True and KHTR_MIN>0.
63 logical :: Diffuse_ML_interior !< If true, diffuse along isopycnals between
64 !! the mixed layer and the interior.
65 logical :: check_diffusive_CFL !< If true, automatically iterate the diffusion
66 !! to ensure that the diffusive equivalent of
67 !! the CFL limit is not violated.
68 logical :: use_neutral_diffusion !< If true, use the neutral_diffusion module from within
69 !! tracer_hor_diff.
70 logical :: use_hor_bnd_diffusion !< If true, use the hor_bnd_diffusion module from within
71 !! tracer_hor_diff.
72 logical :: recalc_neutral_surf !< If true, recalculate the neutral surfaces if CFL has been
73 !! exceeded
74 logical :: limit_bug !< If true and the answer date is 20240330 or below, use a
75 !! rotational symmetry breaking bug when limiting the tracer
76 !! properties in tracer_epipycnal_ML_diff.
77 integer :: answer_date !< The vintage of the order of arithmetic to use for the tracer
78 !! diffusion. Values of 20240330 or below recover the answers
79 !! from the original form of this code, while higher values use
80 !! mathematically equivalent expressions that recover rotational symmetry
81 !! when DIFFUSE_ML_TO_INTERIOR is true.
82 type(neutral_diffusion_CS), pointer :: neutral_diffusion_CSp => NULL() !< Control structure for neutral diffusion.
83 type(hbd_CS), pointer :: hor_bnd_diffusion_CSp => NULL() !< Control structure for
84 !! horizontal boundary diffusion.
85 type(diag_ctrl), pointer :: diag => NULL() !< A structure that is used to
86 !! regulate the timing of diagnostic output.
87 logical :: debug !< If true, write verbose checksums for debugging purposes.
88 logical :: show_call_tree !< Display the call tree while running. Set by VERBOSITY level.
89 logical :: first_call = .true. !< This is true until after the first call
90 !>@{ Diagnostic IDs
91 integer :: id_KhTr_u = -1
92 integer :: id_KhTr_v = -1
93 integer :: id_KhTr_h = -1
94 integer :: id_CFL = -1
95 integer :: id_khdt_x = -1
96 integer :: id_khdt_y = -1
97 !>@}
98
99 type(group_pass_type) :: pass_t !< For group halo pass, used in both
100 !! tracer_hordiff and tracer_epipycnal_ML_diff
101end type tracer_hor_diff_CS
102
103!> A type that can be used to create arrays of pointers to 2D arrays
104type p2d
105 real, dimension(:,:), pointer :: p => NULL() !< A pointer to a 2D array of reals [various]
106end type p2d
107!> A type that can be used to create arrays of pointers to 2D integer arrays
108type p2di
109 integer, dimension(:,:), pointer :: p => NULL() !< A pointer to a 2D array of integers
110end type p2di
111
112!>@{ CPU time clocks
113integer :: id_clock_diffuse, id_clock_epimix, id_clock_pass, id_clock_sync
114!>@}
115
116contains
117
118!> Compute along-coordinate diffusion of all tracers
119!! using the diffusivity in CS%KhTr, or using space-dependent diffusivity.
120!! Multiple iterations are used (if necessary) so that there is no limit
121!! on the acceptable time increment.
1220subroutine tracer_hordiff(h, dt, MEKE, VarMix, visc, G, GV, US, CS, Reg, tv, do_online_flag, read_khdt_x, read_khdt_y)
123 type(ocean_grid_type), intent(inout) :: G !< Grid type
124 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
125 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), &
126 intent(in) :: h !< Layer thickness [H ~> m or kg m-2]
127 real, intent(in) :: dt !< time step [T ~> s]
128 type(MEKE_type), intent(in) :: MEKE !< MEKE fields
129 type(VarMix_CS), intent(in) :: VarMix !< Variable mixing type
130 type(vertvisc_type), intent(in) :: visc !< Structure with vertical viscosities,
131 !! boundary layer properties and related fields
132 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
133 type(tracer_hor_diff_CS), pointer :: CS !< module control structure
134 type(tracer_registry_type), pointer :: Reg !< registered tracers
135 type(thermo_var_ptrs), intent(in) :: tv !< A structure containing pointers to any available
136 !! thermodynamic fields, including potential temp and
137 !! salinity or mixed layer density. Absent fields have
138 !! NULL ptrs, and these may (probably will) point to
139 !! some of the same arrays as Tr does. tv is required
140 !! for epipycnal mixing between mixed layer and the interior.
141 ! Optional inputs for offline tracer transport
142 logical, optional, intent(in) :: do_online_flag !< If present and true, do online
143 !! tracer transport with stored velocities.
144 ! The next two arguments do not appear to be used anywhere.
145 real, dimension(SZIB_(G),SZJ_(G)), &
146 optional, intent(in) :: read_khdt_x !< If present, these are the zonal diffusivities
147 !! times a timestep from a previous run [L2 ~> m2]
148 real, dimension(SZI_(G),SZJB_(G)), &
149 optional, intent(in) :: read_khdt_y !< If present, these are the meridional diffusivities
150 !! times a timestep from a previous run [L2 ~> m2]
151
152
153 real, dimension(SZI_(G),SZJ_(G)) :: &
15424 Ihdxdy, & ! The inverse of the volume or mass of fluid in a layer in a
155 ! grid cell [H-1 L-2 ~> m-3 or kg-1].
15612 CFL, & ! A diffusive CFL number for each cell [nondim].
15724 dTr ! The change in a tracer's concentration, in units of concentration [Conc].
158
15924 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1) :: Kh_h
160 ! The tracer diffusivity averaged to tracer points [L2 T-1 ~> m2 s-1].
161 real, dimension(SZIB_(G),SZJ_(G)) :: &
16224 khdt_x ! The value of Khtr*dt times the open face width divided by
163 ! the distance between adjacent tracer points [L2 ~> m2].
164 real, dimension(SZI_(G),SZJB_(G)) :: &
16524 khdt_y ! The value of Khtr*dt times the open face width divided by
166 ! the distance between adjacent tracer points [L2 ~> m2].
167 real, dimension(SZIB_(G),SZJ_(G),SZK_(GV)+1) :: &
16824 Coef_x, & ! The coefficients relating zonal tracer differences to time-integrated
169 ! fluxes, in [L2 ~> m2] for some schemes and [H L2 ~> m3 or kg] for others.
17024 Kh_u ! Tracer mixing coefficient at u-points [L2 T-1 ~> m2 s-1].
171 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)+1) :: &
17224 Coef_y, & ! The coefficients relating meridional tracer differences to time-integrated
173 ! fluxes, in [L2 ~> m2] for some schemes and [H L2 ~> m3 or kg] for others.
17412 Kh_v ! Tracer mixing coefficient at u-points [L2 T-1 ~> m2 s-1].
175
176 real :: khdt_max ! The local limiting value of khdt_x or khdt_y [L2 ~> m2].
177 real :: Coef_min ! The local limiting value of Coef_x or Coef_y, in [L2 ~> m2] for some
178 ! schemes and [H L2 ~> m3 or kg] for others.
179 real :: max_CFL ! The global maximum of the diffusive CFL number [nondim]
180 logical :: use_VarMix, Resoln_scaled, do_online, use_Eady
181 integer :: i, j, k, m, is, ie, js, je, nz, ntr, itt, num_itts
182 real :: I_numitts ! The inverse of the number of iterations, num_itts [nondim]
183 real :: scale ! The fraction of khdt_x or khdt_y that is applied in this
184 ! layer for this iteration [nondim].
185 real :: Idt ! The inverse of the time step [T-1 ~> s-1].
186 real :: h_neglect ! A thickness that is so small it is usually lost
187 ! in roundoff and can be neglected [H ~> m or kg m-2].
188 real :: Kh_loc ! The local value of Kh [L2 T-1 ~> m2 s-1].
189 real :: Res_Fn ! The local value of the resolution function [nondim].
190 real :: Rd_dx ! The local value of deformation radius over grid-spacing [nondim].
191 real :: normalize ! normalization used for diagnostic Kh_h [nondim]; diffusivity averaged to h-points.
192 real :: MEKE_KhTr_fac
193
19412 is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke
195
19612 do_online = .true.
19712 if (present(do_online_flag)) do_online = do_online_flag
198
19912 if (.not. associated(CS)) call MOM_error(FATAL, "MOM_tracer_hor_diff: "// &
2000 "register_tracer must be called before tracer_hordiff.")
20112 if (.not. associated(Reg)) call MOM_error(FATAL, "MOM_tracer_hor_diff: "// &
2020 "register_tracer must be called before tracer_hordiff.")
20312 if (Reg%ntr == 0 .or. (CS%KhTr <= 0.0 .and. .not. VarMix%use_variable_mixing)) return
204
20512 if (CS%show_call_tree) call callTree_enter("tracer_hordiff(), MOM_tracer_hor_diff.F90")
206
20712 call cpu_clock_begin(id_clock_diffuse)
208
209 !$omp target enter data map(to: Reg, Reg%Tr, CS) map(alloc: khdt_x, khdt_y, kh_u, kh_v)
210 !$ do m = 1, Reg%ntr
211 !$omp target enter data map(to: Reg%Tr(m)%t)
212 !$omp target enter data map(to: Reg%Tr(m)%df_x) if(associated(Reg%Tr(m)%df_x))
213 !$omp target enter data map(to: Reg%Tr(m)%df_y) if(associated(Reg%Tr(m)%df_y))
214 !$omp target enter data map(to: Reg%Tr(m)%df2d_x) if(associated(Reg%Tr(m)%df2d_x))
215 !$omp target enter data map(to: Reg%Tr(m)%df2d_y) if(associated(Reg%Tr(m)%df2d_y))
216 !$ enddo
217
21812 ntr = Reg%ntr
21912 Idt = 1.0 / dt
22012 h_neglect = GV%H_subroundoff
221
22212 if (CS%Diffuse_ML_interior .and. CS%first_call) then ; if (is_root_pe()) then
2230 do m=1,ntr ; if (associated(Reg%Tr(m)%df_x) .or. associated(Reg%Tr(m)%df_y)) &
224 call MOM_error(WARNING, "tracer_hordiff: Tracer "//trim(Reg%Tr(m)%name)// &
225 " has associated 3-d diffusive flux diagnostics. These are not "//&
226 "valid when DIFFUSE_ML_TO_INTERIOR is defined. Use 2-d tracer "//&
2270 "diffusion diagnostics instead to get accurate total fluxes.")
228 enddo
229 endif ; endif
23012 CS%first_call = .false.
231
23212 if (CS%debug) call MOM_tracer_chksum("Before tracer diffusion ", Reg, G)
233
23412 use_VarMix = .false. ; Resoln_scaled = .false. ; use_Eady = .false.
23512 if (VarMix%use_variable_mixing) then
23612 use_VarMix = VarMix%use_variable_mixing
23712 Resoln_scaled = VarMix%Resoln_scaled_KhTr
23812 use_Eady = CS%KhTr_Slope_Cff > 0.
23912 CS%KhTr_use_vert_struct = allocated(VarMix%khtr_struct)
240 endif
241
24212 call cpu_clock_begin(id_clock_pass)
24348 do m=1,ntr
24448 call create_group_pass(CS%pass_t, Reg%Tr(m)%t(:,:,:), G%Domain)
245 enddo
24612 call cpu_clock_end(id_clock_pass)
247
24812 if (CS%show_call_tree) call callTree_waypoint("Calculating diffusivity (tracer_hordiff)")
249
25012 if (do_online) then
25112 if (use_VarMix) then
25212 MEKE_KhTr_fac = MEKE%KhTr_fac
253 !$omp target enter data map(to: VarMix, VarMix%SN_u, VarMix%L2u, VarMix%Res_fn_h, &
254 !$omp VarMix%Rd_dx_h, MEKE, MEKE%Kh)
25512 do concurrent (j=js:je, I=is-1:ie)
25687120 Kh_loc = CS%KhTr
25787120 if (use_Eady) Kh_loc = Kh_loc + CS%KhTr_Slope_Cff*VarMix%L2u(I,j)*VarMix%SN_u(I,j)
25887120 if (allocated(MEKE%Kh)) &
25987120 Kh_loc = Kh_loc + MEKE_KhTr_fac*sqrt(MEKE%Kh(i,j)*MEKE%Kh(i+1,j))
26087120 if (CS%KhTr_max > 0.) Kh_loc = min(Kh_loc, CS%KhTr_max)
26187120 if (Resoln_scaled) &
2620 Kh_loc = Kh_loc * 0.5*(VarMix%Res_fn_h(i,j) + VarMix%Res_fn_h(i+1,j))
26387120 Kh_u(I,j,1) = max(Kh_loc, CS%KhTr_min)
26488584 if (CS%KhTr_passivity_coeff>0.) then ! Apply passivity
2650 Rd_dx=0.5*( VarMix%Rd_dx_h(i,j)+VarMix%Rd_dx_h(i+1,j) ) ! Rd/dx at u-points
2660 Kh_loc = Kh_u(I,j,1)*max( CS%KhTr_passivity_min, CS%KhTr_passivity_coeff*Rd_dx )
2670 if (CS%KhTr_max > 0.) Kh_loc = min(Kh_loc, CS%KhTr_max) ! Re-apply max
2680 Kh_u(I,j,1) = max(Kh_loc, CS%KhTr_min) ! Re-apply min
269 endif
270 enddo
27112 do concurrent (J=js-1:je, i=is:ie)
27287840 Kh_loc = CS%KhTr
27387840 if (use_Eady) Kh_loc = Kh_loc + CS%KhTr_Slope_Cff*VarMix%L2v(i,J)*VarMix%SN_v(i,J)
27487840 if (allocated(MEKE%Kh)) &
27587840 Kh_loc = Kh_loc + MEKE_KhTr_fac*sqrt(MEKE%Kh(i,j)*MEKE%Kh(i,j+1))
27687840 if (CS%KhTr_max > 0.) Kh_loc = min(Kh_loc, CS%KhTr_max)
27787840 if (Resoln_scaled) &
2780 Kh_loc = Kh_loc * 0.5*(VarMix%Res_fn_h(i,j) + VarMix%Res_fn_h(i,j+1))
27987840 Kh_v(i,J,1) = max(Kh_loc, CS%KhTr_min)
28089292 if (CS%KhTr_passivity_coeff>0.) then ! Apply passivity
2810 Rd_dx = 0.5*( VarMix%Rd_dx_h(i,j)+VarMix%Rd_dx_h(i,j+1) ) ! Rd/dx at v-points
2820 Kh_loc = Kh_v(i,J,1)*max( CS%KhTr_passivity_min, CS%KhTr_passivity_coeff*Rd_dx )
2830 if (CS%KhTr_max > 0.) Kh_loc = min(Kh_loc, CS%KhTr_max) ! Re-apply max
2840 Kh_v(i,J,1) = max(Kh_loc, CS%KhTr_min) ! Re-apply min
285 endif
286 enddo
287 !$omp target exit data map(release: VarMix, VarMix%SN_u, VarMix%L2u, VarMix%SN_v, &
288 !$omp VarMix%L2v, VarMix%Res_fn_h, VarMix%Rd_dx_h, MEKE, MEKE%Kh)
289
29012 do concurrent (j=js:je, I=is-1:ie)
29188584 khdt_x(I,j) = dt*(Kh_u(I,j,1)*(G%dy_Cu(I,j)*G%IdxCu(I,j)))
292 enddo
29312 do concurrent (J=js-1:je, i=is:ie)
29489292 khdt_y(i,J) = dt*(Kh_v(i,J,1)*(G%dx_Cv(i,J)*G%IdyCv(i,J)))
295 enddo
2960 elseif (Resoln_scaled) then
297 !$OMP parallel do default(shared) private(Res_fn)
2980 do j=js,je ; do I=is-1,ie
2990 Res_fn = 0.5 * (VarMix%Res_fn_h(i,j) + VarMix%Res_fn_h(i+1,j))
3000 Kh_u(I,j,1) = max(CS%KhTr * Res_fn, CS%KhTr_min)
3010 khdt_x(I,j) = dt*(CS%KhTr*(G%dy_Cu(I,j)*G%IdxCu(I,j))) * Res_fn
302 enddo ; enddo
303 !$OMP parallel do default(shared) private(Res_fn)
3040 do J=js-1,je ; do i=is,ie
3050 Res_fn = 0.5*(VarMix%Res_fn_h(i,j) + VarMix%Res_fn_h(i,j+1))
3060 Kh_v(i,J,1) = max(CS%KhTr * Res_fn, CS%KhTr_min)
3070 khdt_y(i,J) = dt*(CS%KhTr*(G%dx_Cv(i,J)*G%IdyCv(i,J))) * Res_fn
308 enddo ; enddo
309 !$omp target update to(khdt_x, khdt_y, Kh_u, Kh_v)
310 else ! Use a simple constant diffusivity.
3110 if (CS%id_KhTr_u > 0) then
312 !$OMP parallel do default(shared)
3130 do j=js,je ; do I=is-1,ie
3140 Kh_u(I,j,1) = CS%KhTr
3150 khdt_x(I,j) = dt*(CS%KhTr*(G%dy_Cu(I,j)*G%IdxCu(I,j)))
316 enddo ; enddo
317 else
318 !$OMP parallel do default(shared)
3190 do j=js,je ; do I=is-1,ie
3200 khdt_x(I,j) = dt*(CS%KhTr*(G%dy_Cu(I,j)*G%IdxCu(I,j)))
321 enddo ; enddo
322 endif
3230 if (CS%id_KhTr_v > 0) then
324 !$OMP parallel do default(shared)
3250 do J=js-1,je ; do i=is,ie
3260 Kh_v(i,J,1) = CS%KhTr
3270 khdt_y(i,J) = dt*(CS%KhTr*(G%dx_Cv(i,J)*G%IdyCv(i,J)))
328 enddo ; enddo
329 else
330 !$OMP parallel do default(shared)
3310 do J=js-1,je ; do i=is,ie
3320 khdt_y(i,J) = dt*(CS%KhTr*(G%dx_Cv(i,J)*G%IdyCv(i,J)))
333 enddo ; enddo
334 endif
335 !$omp target update to(khdt_x, khdt_y, Kh_u, Kh_v)
336 endif ! VarMix
337
33812 if (CS%max_diff_CFL > 0.0) then
339 !$omp target update from(khdt_x, khdt_y, Kh_u, Kh_v)
3400 if ((CS%id_KhTr_u > 0) .or. (CS%id_KhTr_h > 0)) then
341 !$OMP parallel do default(shared) private(khdt_max)
3420 do j=js,je ; do I=is-1,ie
3430 khdt_max = 0.125*CS%max_diff_CFL * min(G%areaT(i,j), G%areaT(i+1,j))
3440 if (khdt_x(I,j) > khdt_max) then
3450 khdt_x(I,j) = khdt_max
3460 if (dt*(G%dy_Cu(I,j)*G%IdxCu(I,j)) > 0.0) &
3470 Kh_u(I,j,1) = khdt_x(I,j) / (dt*(G%dy_Cu(I,j)*G%IdxCu(I,j)))
348 endif
349 enddo ; enddo
350 else
351 !$OMP parallel do default(shared) private(khdt_max)
3520 do j=js,je ; do I=is-1,ie
3530 khdt_max = 0.125*CS%max_diff_CFL * min(G%areaT(i,j), G%areaT(i+1,j))
3540 khdt_x(I,j) = min(khdt_x(I,j), khdt_max)
355 enddo ; enddo
356 endif
3570 if ((CS%id_KhTr_v > 0) .or. (CS%id_KhTr_h > 0)) then
358 !$OMP parallel do default(shared) private(khdt_max)
3590 do J=js-1,je ; do i=is,ie
3600 khdt_max = 0.125*CS%max_diff_CFL * min(G%areaT(i,j), G%areaT(i,j+1))
3610 if (khdt_y(i,J) > khdt_max) then
3620 khdt_y(i,J) = khdt_max
3630 if (dt*(G%dx_Cv(i,J)*G%IdyCv(i,J)) > 0.0) &
3640 Kh_v(i,J,1) = khdt_y(i,J) / (dt*(G%dx_Cv(i,J)*G%IdyCv(i,J)))
365 endif
366 enddo ; enddo
367 else
368 !$OMP parallel do default(shared) private(khdt_max)
3690 do J=js-1,je ; do i=is,ie
3700 khdt_max = 0.125*CS%max_diff_CFL * min(G%areaT(i,j), G%areaT(i,j+1))
3710 khdt_y(i,J) = min(khdt_y(i,J), khdt_max)
372 enddo ; enddo
373 endif
374 !$omp target update to(khdt_x, khdt_y, Kh_u, Kh_v)
375 endif
376
377 else ! .not. do_online
378 !$OMP parallel do default(shared)
3790 do j=js,je ; do I=is-1,ie
3800 khdt_x(I,j) = read_khdt_x(I,j)
381 enddo ; enddo
382 !$OMP parallel do default(shared)
3830 do J=js-1,je ; do i=is,ie
3840 khdt_y(i,J) = read_khdt_y(i,J)
385 enddo ; enddo
3860 call pass_vector(khdt_x, khdt_y, G%Domain)
387 !$omp target update to(khdt_x, khdt_y, Kh_u, Kh_v)
388 endif ! do_online
389
39012 if (CS%check_diffusive_CFL) then
39112 if (CS%show_call_tree) call callTree_waypoint("Checking diffusive CFL (tracer_hordiff)")
39212 max_CFL = 0.0
393 !$omp target enter data map(alloc: CFL)
39412 do concurrent (j=js:je, i=is:ie) DO_LOCALITY(reduce(max:max_CFL))
395 CFL(i,j) = 2.0*((khdt_x(I-1,j) + khdt_x(I,j)) + &
39686400 (khdt_y(i,J-1) + khdt_y(i,J))) * G%IareaT(i,j)
39787852 max_CFL = max(max_CFL, CFL(i,j))
398 enddo
39912 call cpu_clock_begin(id_clock_sync)
40012 call max_across_PEs(max_CFL)
40112 call cpu_clock_end(id_clock_sync)
40212 num_itts = max(1, ceiling(max_CFL - 4.0*EPSILON(max_CFL)))
40312 I_numitts = 1.0 / (real(num_itts))
40412 if (CS%id_CFL > 0) then
405 !$omp target update from(CFL)
4060 call post_data(CS%id_CFL, CFL, CS%diag)
407 endif
408 !$omp target exit data map(release: CFL)
4090 elseif (CS%max_diff_CFL > 0.0) then
4100 num_itts = max(1, ceiling(CS%max_diff_CFL - 4.0*EPSILON(CS%max_diff_CFL)))
4110 I_numitts = 1.0 / (real(num_itts))
412 else
4130 num_itts = 1 ; I_numitts = 1.0
414 endif
415
41648 do m=1,ntr
41736 if (associated(Reg%Tr(m)%df_x)) then
4180 do k=1,nz ; do j=js,je ; do I=is-1,ie
4190 Reg%Tr(m)%df_x(I,j,k) = 0.0
420 enddo ; enddo ; enddo
421 !$omp target update to(Reg%Tr(m)%df_x)
422 endif
42336 if (associated(Reg%Tr(m)%df_y)) then
4240 do k=1,nz ; do J=js-1,je ; do i=is,ie
4250 Reg%Tr(m)%df_y(i,J,k) = 0.0
426 enddo ; enddo ; enddo
427 !$omp target update to(Reg%Tr(m)%df_y)
428 endif
42936 if (associated(Reg%Tr(m)%df2d_x)) then
4300 do j=js,je ; do I=is-1,ie ; Reg%Tr(m)%df2d_x(I,j) = 0.0 ; enddo ; enddo
431 !$omp target update to(Reg%Tr(m)%df2d_x)
432 endif
43348 if (associated(Reg%Tr(m)%df2d_y)) then
4340 do J=js-1,je ; do i=is,ie ; Reg%Tr(m)%df2d_y(i,J) = 0.0 ; enddo ; enddo
435 !$omp target update to(Reg%Tr(m)%df2d_y)
436 endif
437 enddo
438
43912 if (CS%use_hor_bnd_diffusion) then
440 !$omp target update from(h)
441
4420 if (CS%show_call_tree) call callTree_waypoint("Calling horizontal boundary diffusion (tracer_hordiff)")
443 !$omp target update from(khdt_x, khdt_y)
4440 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
445
4460 do k=1,nz+1
4470 do J=js-1,je
4480 do i=is,ie
4490 Coef_y(i,J,K) = I_numitts * khdt_y(i,J)
450 enddo
451 enddo
452 enddo
4530 do k=1,nz+1
4540 do j=js,je
4550 do I=is-1,ie
4560 Coef_x(I,j,K) = I_numitts * khdt_x(I,j)
457 enddo
458 enddo
459 enddo
4600 if (CS%KhTr_use_vert_struct) then
4610 if (CS%full_depth_khtr_min) then
4620 do K=2,nz+1
4630 do J=js-1,je
4640 do i=is,ie
4650 Coef_y(i,J,K) = Coef_y(i,J,1) * 0.5 * ( VarMix%khtr_struct(i,j,k-1) + VarMix%khtr_struct(i,j+1,k-1) )
4660 Coef_min = I_numitts * dt * (CS%KhTr_min*(G%dx_Cv(i,J)*G%IdyCv(i,J)))
4670 Coef_y(i,J,K) = max(Coef_y(i,J,K), Coef_min)
468 enddo
469 enddo
470 enddo
4710 do k=2,nz+1
4720 do j=js,je
4730 do I=is-1,ie
4740 Coef_x(I,j,K) = Coef_x(I,j,1) * 0.5 * ( VarMix%khtr_struct(i,j,k-1) + VarMix%khtr_struct(i+1,j,k-1) )
4750 Coef_min = I_numitts * dt * (CS%KhTr_min*(G%dy_Cu(I,j)*G%IdxCu(I,j)))
4760 Coef_x(I,j,K) = max(Coef_x(I,j,K), Coef_min)
477 enddo
478 enddo
479 enddo
480 else
4810 do K=2,nz+1
4820 do J=js-1,je
4830 do i=is,ie
4840 Coef_y(i,J,K) = Coef_y(i,J,1) * 0.5 * ( VarMix%ebt_struct(i,j,k-1) + VarMix%ebt_struct(i,j+1,k-1) )
485 enddo
486 enddo
487 enddo
4880 do k=2,nz+1
4890 do j=js,je
4900 do I=is-1,ie
4910 Coef_x(I,j,K) = Coef_x(I,j,1) * 0.5 * ( VarMix%ebt_struct(i,j,k-1) + VarMix%ebt_struct(i+1,j,k-1) )
492 enddo
493 enddo
494 enddo
495 endif
496 endif
497
4980 do itt=1,num_itts
4990 if (CS%show_call_tree) call callTree_waypoint("Calling horizontal boundary diffusion (tracer_hordiff)",itt)
5000 if (itt>1) then ! Update halos for subsequent iterations
5010 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
502 endif
503 call hor_bnd_diffusion(G, GV, US, h, Coef_x, Coef_y, I_numitts*dt, Reg, visc, &
5040 CS%hor_bnd_diffusion_CSp)
505 enddo ! itt
506 endif
507
50812 if (CS%use_neutral_diffusion) then
509 !$omp target update from(h)
510
5110 if (CS%show_call_tree) call callTree_waypoint("Calling neutral diffusion coeffs (tracer_hordiff)")
512
5130 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
514 ! We are assuming that neutral surfaces do not evolve (much) as a result of multiple
515 !horizontal diffusion iterations. Otherwise the call to neutral_diffusion_calc_coeffs()
516 ! would be inside the itt-loop. -AJA
517
5180 if (associated(tv%p_surf)) then
5190 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp, p_surf=tv%p_surf)
520 else
5210 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp)
522 endif
523
5240 do k=1,nz+1
5250 do J=js-1,je
5260 do i=is,ie
5270 Coef_y(i,J,K) = I_numitts * khdt_y(i,J)
528 enddo
529 enddo
530 enddo
5310 do k=1,nz+1
5320 do j=js,je
5330 do I=is-1,ie
5340 Coef_x(I,j,K) = I_numitts * khdt_x(I,j)
535 enddo
536 enddo
537 enddo
5380 if (CS%KhTr_use_vert_struct) then
5390 do K=2,nz+1
5400 do J=js-1,je
5410 do i=is,ie
5420 Coef_y(i,J,K) = Coef_y(i,J,1) * 0.5 * ( VarMix%khtr_struct(i,j,k-1) + VarMix%khtr_struct(i,j+1,k-1) )
543 enddo
544 enddo
545 enddo
5460 do k=2,nz+1
5470 do j=js,je
5480 do I=is-1,ie
5490 Coef_x(I,j,K) = Coef_x(I,j,1) * 0.5 * ( VarMix%khtr_struct(i,j,k-1) + VarMix%khtr_struct(i+1,j,k-1) )
550 enddo
551 enddo
552 enddo
553 endif
554
5550 do itt=1,num_itts
5560 if (CS%show_call_tree) call callTree_waypoint("Calling neutral diffusion (tracer_hordiff)",itt)
5570 if (itt>1) then ! Update halos for subsequent iterations
5580 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
5590 if (CS%recalc_neutral_surf) then
5600 if (associated(tv%p_surf)) then
561 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp, &
5620 p_surf=tv%p_surf)
563 else
5640 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp)
565 endif
566 endif
567 endif
5680 call neutral_diffusion(G, GV, h, Coef_x, Coef_y, I_numitts*dt, Reg, US, CS%neutral_diffusion_CSp)
569 enddo ! itt
570
571 else ! following if not using neutral diffusion, but instead along-surface diffusion
572
57312 if (CS%show_call_tree) call callTree_waypoint("Calculating horizontal diffusion (tracer_hordiff)")
574 !$omp target enter data map(alloc: dTr, Ihdxdy)
575 !$omp target enter data map(to: Coef_x, Coef_y) if(CS%use_hor_bnd_diffusion) ! copy from non-ported loops above
576 !$omp target enter data map(alloc: Coef_x, Coef_y) if(.not.CS%use_hor_bnd_diffusion)
57724 do itt=1,num_itts
57812 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass, omp_offload=.true.)
579 ! loop should probably be reordered
580912 do k=1,nz
581900 scale = I_numitts
582900 if (CS%Diffuse_ML_interior) then
5830 if (k<=GV%nkml) then
5840 if (CS%ML_KhTr_scale <= 0.0) cycle
5850 scale = I_numitts * CS%ML_KhTr_scale
586 endif
5870 if ((k>GV%nkml) .and. (k<=GV%nk_rho_varies)) cycle
588 endif
589
590900 do concurrent (J=js-1:je, i=is:ie)
591 Coef_y(i,J,1) = ((scale * khdt_y(i,J))*2.0*(h(i,j,k)*h(i,j+1,k))) / &
5926696900 (h(i,j,k)+h(i,j+1,k)+h_neglect)
593 enddo
594
595900 do concurrent (j=js:je)
59654000 do concurrent (I=is-1:ie)
597 Coef_x(I,j,1) = ((scale * khdt_x(I,j))*2.0*(h(i,j,k)*h(i+1,j,k))) / &
5986588000 (h(i,j,k)+h(i+1,j,k)+h_neglect)
599 enddo
600
60154900 do concurrent (i=is:ie)
6026534000 Ihdxdy(i,j) = G%IareaT(i,j) / (h(i,j,k)+h_neglect)
603 enddo
604 enddo
605
6063612 do m=1,ntr
6072700 do concurrent (j=js:je, i=is:ie)
608 dTr(i,j) = Ihdxdy(i,j) * &
609 ( ((Coef_x(I-1,j,1) * (Reg%Tr(m)%t(i-1,j,k) - Reg%Tr(m)%t(i,j,k))) - &
610 (Coef_x(I,j,1) * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i+1,j,k)))) + &
611 ((Coef_y(i,J-1,1) * (Reg%Tr(m)%t(i,j-1,k) - Reg%Tr(m)%t(i,j,k))) - &
61219766700 (Coef_y(i,J,1) * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i,j+1,k)))) )
613 enddo
6142700 if (associated(Reg%Tr(m)%df_x)) then ; do j=js,je ; do I=G%IscB,G%IecB
615 Reg%Tr(m)%df_x(I,j,k) = Reg%Tr(m)%df_x(I,j,k) + Coef_x(I,j,1) &
6160 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i+1,j,k)) * Idt
617 enddo ; enddo ; endif
6182700 if (associated(Reg%Tr(m)%df_y)) then ; do J=G%JscB,G%JecB ; do i=is,ie
619 Reg%Tr(m)%df_y(i,J,k) = Reg%Tr(m)%df_y(i,J,k) + Coef_y(i,J,1) &
6200 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i,j+1,k)) * Idt
621 enddo ; enddo ; endif
6222700 if (associated(Reg%Tr(m)%df2d_x)) then ; do j=js,je ; do I=G%IscB,G%IecB
623 Reg%Tr(m)%df2d_x(I,j) = Reg%Tr(m)%df2d_x(I,j) + Coef_x(I,j,1) &
6240 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i+1,j,k)) * Idt
625 enddo ; enddo ; endif
6262700 if (associated(Reg%Tr(m)%df2d_y)) then ; do J=G%JscB,G%JecB ; do i=is,ie
627 Reg%Tr(m)%df2d_y(i,J) = Reg%Tr(m)%df2d_y(i,J) + Coef_y(i,J,1) &
6280 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i,j+1,k)) * Idt
629 enddo ; enddo ; endif
630900 do concurrent (j=js:je, i=is:ie)
63119766700 Reg%Tr(m)%t(i,j,k) = Reg%Tr(m)%t(i,j,k) + dTr(i,j)
632 enddo
633 enddo
634
635 enddo ! End of k loop.
636
637 ! Do user controlled underflow of the tracer concentrations.
63860 do m=1,ntr ; if (Reg%Tr(m)%conc_underflow > 0.0) then
6390 do concurrent (k=1:nz, j=js:je, i=is:ie, abs(Reg%Tr(m)%t(i,j,k)) < Reg%Tr(m)%conc_underflow)
6400 Reg%Tr(m)%t(i,j,k) = 0.0
641 enddo
642 endif ; enddo
643
644 enddo ! End of "while" loop.
645 !$omp target exit data map(release: dTr, Ihdxdy, Coef_x, Coef_y)
646 endif ! endif for CS%use_neutral_diffusion
64712 call cpu_clock_end(id_clock_diffuse)
648
64912 if (CS%Diffuse_ML_interior) then
6500 if (CS%show_call_tree) call callTree_waypoint("Calling epipycnal_ML_diff (tracer_hordiff)")
6510 if (CS%debug) call MOM_tracer_chksum("Before epipycnal diff ", Reg, G)
652
6530 call cpu_clock_begin(id_clock_epimix)
654 call tracer_epipycnal_ML_diff(h, dt, Reg%Tr, ntr, khdt_x, khdt_y, G, GV, US, &
6550 CS, tv, num_itts)
6560 call cpu_clock_end(id_clock_epimix)
657 endif
658 !$ do m = 1, Reg%ntr
659 !$omp target exit data map(from: Reg%Tr(m)%t)
660 !$omp target exit data map(from: Reg%Tr(m)%df_x) if(associated(Reg%Tr(m)%df_x))
661 !$omp target exit data map(from: Reg%Tr(m)%df_y) if(associated(Reg%Tr(m)%df_y))
662 !$omp target exit data map(from: Reg%Tr(m)%df2d_x) if(associated(Reg%Tr(m)%df2d_x))
663 !$omp target exit data map(from: Reg%Tr(m)%df2d_y) if(associated(Reg%Tr(m)%df2d_y))
664 !$ enddo
665 !$omp target exit data map(release: Reg%Tr, Reg)
666
66712 if (CS%debug) call MOM_tracer_chksum("After tracer diffusion ", Reg, G)
668
669 ! post diagnostics for 2d tracer diffusivity
67012 if (CS%id_KhTr_u > 0) then
671 !$omp target exit data map(from: Kh_u)
6720 do j=js,je ; do I=is-1,ie
6730 Kh_u(I,j,:) = G%mask2dCu(I,j)*Kh_u(I,j,1)
674 enddo ; enddo
6750 if (CS%KhTr_use_vert_struct) then
6760 do K=2,nz+1
6770 do j=js,je
6780 do I=is-1,ie
6790 Kh_u(I,j,K) = Kh_u(I,j,1) * 0.5 * ( VarMix%khtr_struct(i,j,k-1) + VarMix%khtr_struct(i+1,j,k-1) )
680 enddo
681 enddo
682 enddo
683 endif
6840 call post_data(CS%id_KhTr_u, Kh_u, CS%diag)
685 endif
68612 if (CS%id_KhTr_v > 0) then
687 !$omp target exit data map(from: Kh_v)
6880 do J=js-1,je ; do i=is,ie
6890 Kh_v(i,J,:) = G%mask2dCv(i,J)*Kh_v(i,J,1)
690 enddo ; enddo
6910 if (CS%KhTr_use_vert_struct) then
6920 do K=2,nz+1
6930 do J=js-1,je
6940 do i=is,ie
6950 Kh_v(i,J,K) = Kh_v(i,J,1) * 0.5 * ( VarMix%khtr_struct(i,j,k-1) + VarMix%khtr_struct(i,j+1,k-1) )
696 enddo
697 enddo
698 enddo
699 endif
7000 call post_data(CS%id_KhTr_v, Kh_v, CS%diag)
701 endif
70212 if (CS%id_KhTr_h > 0) then
703 !$omp target exit data map(from: Kh_u, Kh_v)
7040 Kh_h(:,:,:) = 0.0
7050 do j=js,je ; do I=is-1,ie
7060 Kh_u(I,j,1) = G%mask2dCu(I,j)*Kh_u(I,j,1)
707 enddo ; enddo
7080 do J=js-1,je ; do i=is,ie
7090 Kh_v(i,J,1) = G%mask2dCv(i,J)*Kh_v(i,J,1)
710 enddo ; enddo
711
7120 do j=js,je ; do i=is,ie
713 normalize = 1.0 / ((G%mask2dCu(I-1,j)+G%mask2dCu(I,j)) + &
7140 (G%mask2dCv(i,J-1)+G%mask2dCv(i,J)) + 1.0e-37)
715 Kh_h(i,j,:) = normalize*G%mask2dT(i,j)*((Kh_u(I-1,j,1)+Kh_u(I,j,1)) + &
7160 (Kh_v(i,J-1,1)+Kh_v(i,J,1)))
7170 if (CS%KhTr_use_vert_struct) then
7180 do K=2,nz+1
719 Kh_h(i,j,K) = normalize*G%mask2dT(i,j)*VarMix%khtr_struct(i,j,k-1)*((Kh_u(I-1,j,1)+Kh_u(I,j,1)) + &
7200 (Kh_v(i,J-1,1)+Kh_v(i,J,1)))
721 enddo
722 endif
723 enddo ; enddo
7240 call post_data(CS%id_KhTr_h, Kh_h, CS%diag)
725 endif
726
727 !$omp target update from(khdt_x, khdt_y) if(CS%debug .or. CS%id_khdt_x>0 .or. CS%id_khdt_y>0)
728 !$omp target exit data map(release: khdt_x, khdt_y, Kh_u, Kh_v) map(release: CS)
729
73012 if (CS%debug) then
731 call uvchksum("After tracer diffusion khdt_[xy]", khdt_x, khdt_y, &
732 G%HI, haloshift=0, symmetric=.true., unscale=US%L_to_m**2, &
7330 scalar_pair=.true.)
734 endif
735
73612 if (CS%id_khdt_x > 0) call post_data(CS%id_khdt_x, khdt_x, CS%diag)
73712 if (CS%id_khdt_y > 0) call post_data(CS%id_khdt_y, khdt_y, CS%diag)
738
73912 if (CS%show_call_tree) call callTree_leave("tracer_hordiff()")
740
74112end subroutine tracer_hordiff
742
743!> This subroutine does epipycnal diffusion of all tracers between the mixed
744!! and buffer layers and the interior, using the diffusivity in CS%KhTr.
745!! Multiple iterations are used (if necessary) so that there is no limit on the
746!! acceptable time increment.
7470subroutine tracer_epipycnal_ML_diff(h, dt, Tr, ntr, khdt_epi_x, khdt_epi_y, G, &
748 GV, US, CS, tv, num_itts)
749 type(ocean_grid_type), intent(inout) :: G !< ocean grid structure
750 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
751 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< layer thickness [H ~> m or kg m-2]
752 real, intent(in) :: dt !< time step [T ~> s]
753 type(tracer_type), intent(inout) :: Tr(:) !< tracer array
754 integer, intent(in) :: ntr !< number of tracers
755 real, dimension(SZIB_(G),SZJ_(G)), intent(in) :: khdt_epi_x !< Zonal epipycnal diffusivity times
756 !! a time step and the ratio of the open face width over
757 !! the distance between adjacent tracer points [L2 ~> m2]
758 real, dimension(SZI_(G),SZJB_(G)), intent(in) :: khdt_epi_y !< Meridional epipycnal diffusivity times
759 !! a time step and the ratio of the open face width over
760 !! the distance between adjacent tracer points [L2 ~> m2]
761 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
762 type(tracer_hor_diff_CS), intent(inout) :: CS !< module control structure
763 type(thermo_var_ptrs), intent(in) :: tv !< thermodynamic structure
764 integer, intent(in) :: num_itts !< number of iterations (usually=1)
765
766
767 real, dimension(SZI_(G), SZJ_(G)) :: &
7680 Rml_max ! The maximum coordinate density within the mixed layer [R ~> kg m-3].
769 real, dimension(SZI_(G), SZJ_(G), max(1,GV%nk_rho_varies)) :: &
7700 rho_coord ! The coordinate density that is used to mix along [R ~> kg m-3].
771
772 ! The naming mnemonic is a=above,b=below,L=Left,R=Right,u=u-point,v=v-point.
773 ! These are 1-D arrays of pointers to 2-d arrays to minimize memory usage.
774 real, dimension(:,:,:), allocatable :: &
7750 deep_wt_Lu, deep_wt_Ru, & ! The relative weighting of the deeper of a pair [nondim].
7760 hP_Lu, hP_Ru, & ! The total thickness on each side for each pair [H ~> m or kg m-2].
7770 deep_wt_Lv, deep_wt_Rv, & ! The relative weighting of the deeper of a pair [nondim].
7780 hP_Lv, hP_Rv ! The total thickness on each side for each pair [H ~> m or kg m-2].
779
780 integer, dimension(:,:,:), allocatable :: &
7810 k0b_Lu, k0a_Lu, & ! The original k-indices of the layers that participate
7820 k0b_Ru, k0a_Ru, & ! in each pair of mixing at u-faces.
7830 k0b_Lv, k0a_Lv, & ! The original k-indices of the layers that participate
7840 k0b_Rv, k0a_Rv ! in each pair of mixing at v-faces.
785
786 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)) :: &
7870 tr_flux_N, & ! The tracer flux through the northern face [conc H L2 ~> conc m3 or conc kg]
7880 tr_flux_S, & ! The tracer flux through the southern face [conc H L2 ~> conc m3 or conc kg]
7890 tr_flux_E, & ! The tracer flux through the eastern face [conc H L2 ~> conc m3 or conc kg]
7900 tr_flux_W, & ! The tracer flux through the western face [conc H L2 ~> conc m3 or conc kg]
7910 tr_flux_conv ! The flux convergence of tracers [conc H L2 ~> conc m3 or conc kg]
792
793 ! The following 3-d arrays were created in 2014 in MOM6 PR#12 to facilitate openMP threading
794 ! on an i-loop, which might have been ill advised.
795 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)*2) :: &
7960 Tr_flux_3d, & ! The tracer flux through pairings at meridional faces [conc H L2 ~> conc m3 or conc kg]
7970 Tr_adj_vert_L, & ! Vertical adjustments to which layer the fluxes go into in the southern
798 ! columns at meridional face [conc H L2 ~> conc m3 or conc kg]
7990 Tr_adj_vert_R ! Vertical adjustments to which layer the fluxes go into in the northern
800 ! columns at meridional face [conc H L2 ~> conc m3 or conc kg]
801
802 real, dimension(SZI_(G),SZK_(GV), SZJ_(G)) :: &
8030 rho_srt, & ! The density of each layer of the sorted columns [R ~> kg m-3].
8040 h_srt ! The thickness of each layer of the sorted columns [H ~> m or kg m-2].
805 integer, dimension(SZI_(G),SZK_(GV), SZJ_(G)) :: &
8060 k0_srt ! The original k-index that each layer of the sorted column corresponds to.
807
808 real, dimension(SZK_(GV)) :: &
8090 h_demand_L, & ! The thickness in the left column that is demanded to match the thickness
810 ! in the counterpart [H ~> m or kg m-2].
8110 h_demand_R, & ! The thickness in the right column that is demanded to match the thickness
812 ! in the counterpart [H ~> m or kg m-2].
8130 h_used_L, & ! The summed thickness from the left column that has actually been used [H ~> m or kg m-2]
8140 h_used_R, & ! The summed thickness from the right columns that has actually been used [H ~> m or kg m-2]
8150 h_supply_frac_L, & ! The fraction of the demanded thickness that can actually be supplied
816 ! from a layer on the left [nondim].
8170 h_supply_frac_R ! The fraction of the demanded thickness that can actually be supplied
818 ! from a layer on the right [nondim].
819 integer, dimension(SZI_(G), SZJ_(G)) :: &
8200 num_srt, & ! The number of layers that are sorted in each column.
8210 k_end_srt, & ! The maximum index in each column that might need to be
822 ! sorted, based on neighboring values of max_kRho
8230 max_kRho ! The index of the layer whose target density is just denser
824 ! than the densest part of the mixed layer.
825 integer, dimension(SZJ_(G)) :: &
8260 max_srt ! The maximum value of num_srt in a k-row.
827 integer, dimension(SZIB_(G), SZJ_(G)) :: &
8280 nPu ! The number of epipycnal pairings at each u-point.
829 integer, dimension(SZI_(G), SZJB_(G)) :: &
8300 nPv ! The number of epipycnal pairings at each v-point.
831 real :: h_exclude ! A thickness that layers must attain to be considered
832 ! for inclusion in mixing [H ~> m or kg m-2].
833 real :: Idt ! The inverse of the time step [T-1 ~> s-1].
834 real :: I_maxitt ! The inverse of the maximum number of iterations [nondim]
835 real :: rho_pair, rho_a, rho_b ! Temporary densities [R ~> kg m-3].
836 real :: Tr_min_face ! The minimum tracer concentration associated with a pairing [Conc]
837 real :: Tr_max_face ! The maximum tracer concentration associated with a pairing [Conc]
838 real :: Tr_La, Tr_Lb ! The 2 left-side tracer concentrations that might be associated with a pairing [Conc]
839 real :: Tr_Ra, Tr_Rb ! The 2 right-side tracer concentrations that might be associated with a pairing [Conc]
840 real :: Tr_av_L ! The average tracer concentrations on the left side of a pairing [Conc].
841 real :: Tr_av_R ! The average tracer concentrations on the right side of a pairing [Conc].
842 real :: Tr_flux ! The tracer flux from left to right in a pair [conc H L2 ~> conc m3 or conc kg].
843 real :: Tr_adj_vert ! A downward vertical adjustment to Tr_flux between the two cells that
844 ! make up one side of the pairing [conc H L2 ~> conc m3 or conc kg].
845 real :: h_L, h_R ! Thicknesses to the left and right [H ~> m or kg m-2].
846 real :: wt_a, wt_b ! Fractional weights of layers above and below [nondim].
847 real :: vol ! A cell volume or mass [H L2 ~> m3 or kg].
848
849 ! The total number of pairings is usually much less than twice the number of layers, but
850 ! the memory in these 1-d columns of pairings can be allocated generously for safety.
851 integer, dimension(SZK_(GV)*2) :: &
8520 kbs_Lp, & ! The sorted indices of the Left and Right columns for
8530 kbs_Rp ! each pairing.
854 logical, dimension(SZK_(GV)*2) :: &
8550 left_set, & ! If true, the left or right point determines the density of
8560 right_set ! of the trio. If densities are exactly equal, both are true.
857
858 real :: tmp ! A temporary variable used in swaps [various]
8590 real :: p_ref_cv(SZI_(G),SZJ_(G)) ! The reference pressure for the coordinate density [R L2 T-2 ~> Pa]
860
861 integer, dimension(2,2) :: EOSdom ! The i-computational domain for the equation of state
862 integer :: k_max, k_min, k_test, itmp
863 integer :: i, j, k, k2, m, is, ie, js, je, nz, nkmb
864 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB, k_size
865 integer :: kL, kR, kLa, kLb, kRa, kRb, nP, itt, ns, max_itt
866 integer :: PEmax_kRho
867
8680 is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke
8690 isd = G%isd ; ied = G%ied ; jsd = G%jsd ; jed = G%jed
8700 IsdB = G%IsdB ; IedB = G%IedB ; JsdB = G%JsdB ; JedB = G%JedB
8710 Idt = 1.0 / dt
8720 nkmb = GV%nk_rho_varies
873
8740 if (num_itts <= 1) then
8750 max_itt = 1 ; I_maxitt = 1.0
876 else
8770 max_itt = num_itts ; I_maxitt = 1.0 / (real(max_itt))
878 endif
879
880 !$omp target enter data map(alloc: rho_coord, Rml_max, max_kRho, rho_srt, k0_srt, num_srt, &
881 !$omp h_srt, p_ref_cv, k_end_srt, max_srt)
882
8830 do concurrent (j=jsd:jed, i=isd:ied)
8840 p_ref_cv(i,j) = tv%P_Ref
885 enddo
886
8870 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass, omp_offload=.true.)
888 ! Determine which layers the mixed- and buffer-layers map into...
889 !$OMP parallel do default(shared)
8900 do k=1,nkmb
891 call calculate_density(tv%T(:,:,k), tv%S(:,:,k), p_ref_cv, rho_coord(:,:,k), &
8920 tv%eqn_of_state)
893 enddo
894 !$omp target exit data map(release: p_ref_cv)
895
8960 do concurrent (j=js-2:je+2, i=is-2:ie+2)
8970 Rml_max(i,j) = rho_coord(i,j,1)
8980 num_srt(i,j) = 0 ; max_kRho(i,j) = 0
899 enddo
9000 do k=2,nkmb ; do concurrent (j=js-2:je+2, i=is-2:ie+2, Rml_max(i,j) < rho_coord(i,j,k))
9010 Rml_max(i,j) = rho_coord(i,j,k)
902 enddo ; enddo
903
904 ! Use bracketing and bisection to find the k-level that the densest of the
905 ! mixed and buffer layer corresponds to, such that:
906 ! GV%Rlay(max_kRho-1) < Rml_max <= GV%Rlay(max_kRho)
9070 do concurrent (j=js-2:je+2, i=is-2:ie+2, G%mask2dT(i,j) > 0.0)
9080 if ((Rml_max(i,j) > GV%Rlay(nz)) .or. (nkmb+1 > nz)) then ; max_kRho(i,j) = nz+1
9090 elseif ((Rml_max(i,j) <= GV%Rlay(nkmb+1)) .or. (nkmb+2 > nz)) then ; max_kRho(i,j) = nkmb+1
910 else
9110 k_min = nkmb+2 ; k_max = nz
9120 do
9130 k_test = (k_min + k_max) / 2
9140 if (Rml_max(i,j) <= GV%Rlay(k_test-1)) then ; k_max = k_test-1
9150 elseif (GV%Rlay(k_test) < Rml_max(i,j)) then ; k_min = k_test+1
9160 else ; max_kRho(i,j) = k_test ; exit ; endif
917
9180 if (k_min == k_max) then ; max_kRho(i,j) = k_max ; exit ; endif
919 enddo
920 endif
921 enddo
922
9230 PEmax_kRho = 0
9240 do concurrent (j=js-1:je+1, i=is-1:ie+1) DO_LOCALITY(reduce(max:PEmax_kRho))
925 k_end_srt(i,j) = max(max_kRho(i,j), max_kRho(i-1,j), max_kRho(i+1,j), &
9260 max_kRho(i,j-1), max_kRho(i,j+1))
9270 if (PEmax_kRho < k_end_srt(i,j)) PEmax_kRho = k_end_srt(i,j)
928 enddo
9290 if (PEmax_kRho > nz) PEmax_kRho = nz ! PEmax_kRho could have been nz+1.
930
9310 h_exclude = 10.0*(GV%Angstrom_H + GV%H_subroundoff)
932
9330 do concurrent (j=js-1:je+1) DO_LOCALITY(local(k, ns))
9340 do k=1,nkmb ; do concurrent (i=is-1:ie+1, G%mask2dT(i,j) > 0.0) DO_LOCALITY(local(ns))
9350 if (h(i,j,k) > h_exclude) then
9360 num_srt(i,j) = num_srt(i,j) + 1 ; ns = num_srt(i,j)
9370 k0_srt(i,ns,j) = k
9380 rho_srt(i,ns,j) = rho_coord(i,j,k)
9390 h_srt(i,ns,j) = h(i,j,k)
940 endif
941 enddo ; enddo
9420 do k=nkmb+1,PEmax_kRho ; do concurrent (i=is-1:ie+1, G%mask2dT(i,j) > 0.0) DO_LOCALITY(local(ns))
9430 if ((k<=k_end_srt(i,j)) .and. (h(i,j,k) > h_exclude)) then
9440 num_srt(i,j) = num_srt(i,j) + 1 ; ns = num_srt(i,j)
9450 k0_srt(i,ns,j) = k
9460 rho_srt(i,ns,j) = GV%Rlay(k)
9470 h_srt(i,ns,j) = h(i,j,k)
948 endif
949 enddo ; enddo
950 enddo
951 !$omp target exit data map(release: rho_coord, Rml_max, k_end_srt)
952 ! Sort each column by increasing density. This should already be close,
953 ! and the size of the arrays are small, so straight insertion is used.
9540 do concurrent (j=js-1:je+1, i=is-1:ie+1)
9550 do k=2,num_srt(i,j) ; if (rho_srt(i,k,j) < rho_srt(i,k-1,j)) then
956 ! The last segment needs to be shuffled earlier in the list.
9570 do k2 = k,2,-1 ; if (rho_srt(i,k2,j) < rho_srt(i,k2-1,j)) then
9580 itmp = k0_srt(i,k2-1,j) ; k0_srt(i,k2-1,j) = k0_srt(i,k2,j) ; k0_srt(i,k2,j) = itmp
9590 tmp = rho_srt(i,k2-1,j) ; rho_srt(i,k2-1,j) = rho_srt(i,k2,j) ; rho_srt(i,k2,j) = tmp
9600 tmp = h_srt(i,k2-1,j) ; h_srt(i,k2-1,j) = h_srt(i,k2,j) ; h_srt(i,k2,j) = tmp
961 endif ; enddo
962 endif ; enddo
963 enddo
9640 do concurrent (j=js-1:je+1) DO_LOCALITY(local(itmp))
965 ! max_srt(j) = 0
9660 itmp = 0
967 ! nvfortran do concurrent cannot reduce array elements
9680 do concurrent (i=is-1:ie+1) DO_LOCALITY(reduce(max:itmp))
9690 itmp = max(itmp, num_srt(i,j))
970 enddo
9710 max_srt(j) = itmp
972 enddo
9730 k_size = 1
9740 do concurrent (j=js-1:je+1) DO_LOCALITY(reduce(max:k_size))
9750 k_size = max(k_size, 2*max_srt(j))
976 enddo
9770 allocate(k0a_Lu(IsdB:iedB,k_size,jsd:jed))
9780 allocate(k0a_Ru(IsdB:iedB,k_size,jsd:jed))
9790 allocate(k0b_Lu(IsdB:iedB,k_size,jsd:jed))
9800 allocate(k0b_Ru(IsdB:iedB,k_size,jsd:jed))
9810 allocate(deep_wt_Lu(IsdB:iedB,k_size,jsd:jed))
9820 allocate(deep_wt_Ru(IsdB:iedB,k_size,jsd:jed))
9830 allocate(hP_Lu(IsdB:iedB,k_size,jsd:jed))
9840 allocate(hP_Ru(IsdB:iedB,k_size,jsd:jed))
985 !$omp target enter data map(alloc: deep_wt_Lu, deep_wt_Ru, hP_Lu, hP_Ru, k0a_Lu, k0a_Ru, k0b_Lu, &
986 !$omp k0b_Ru)
987 !$omp target enter data map(alloc: nPu)
988!$OMP target teams loop collapse(2) &
989!$OMP private(h_demand_L,h_used_L,h_demand_R,h_used_R, &
990!$OMP kR,kL,nP,rho_pair,kbs_Lp,kbs_Rp,rho_a,rho_b, &
991!$OMP wt_b,left_set,right_set,h_supply_frac_R, &
992!$OMP h_supply_frac_L, k)
9930 do j=js,je ; do I=is-1,ie ; if (G%mask2dCu(I,j) > 0.0) then
994 ! Set up the pairings for fluxes through the zonal faces.
995
9960 do k=1,num_srt(i,j) ; h_demand_L(k) = 0.0 ; h_used_L(k) = 0.0 ; enddo
9970 do k=1,num_srt(i+1,j) ; h_demand_R(k) = 0.0 ; h_used_R(k) = 0.0 ; enddo
998
999 ! First merge the left and right lists into a single, sorted list.
1000
1001 ! Discard any layers that are lighter than the lightest in the other
1002 ! column. They can only participate in mixing as the lighter part of a
1003 ! pair of points.
10040 if (rho_srt(i,1,j) < rho_srt(i+1,1,j)) then
10050 kR = 1
10060 do kL=2,num_srt(i,j) ; if (rho_srt(i,kL,j) >= rho_srt(i+1,1,j)) exit ; enddo
10070 elseif (rho_srt(i+1,1,j) < rho_srt(i,1,j)) then
10080 kL = 1
10090 do kR=2,num_srt(i+1,j) ; if (rho_srt(i+1,kR,j) >= rho_srt(i,1,j)) exit ; enddo
1010 else
10110 kL = 1 ; kR = 1
1012 endif
10130 nP = 0
10140 do ! Loop to accumulate pairs of columns.
10150 if ((kL > num_srt(i,j)) .or. (kR > num_srt(i+1,j))) exit
1016
10170 if (rho_srt(i,kL,j) > rho_srt(i+1,kR,j)) then
1018 ! The right point is lighter and defines the density for this trio.
10190 nP = nP+1 ; k = nP
10200 rho_pair = rho_srt(i+1,kR,j)
1021
10220 k0b_Lu(I,k,j) = k0_srt(i,kL,j) ; k0b_Ru(I,k,j) = k0_srt(i+1,kR,j)
10230 k0a_Lu(I,k,j) = k0_srt(i,kL-1,j) ; k0a_Ru(I,k,j) = k0b_Ru(I,k,j)
10240 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1025
10260 rho_a = rho_srt(i,kL-1,j) ; rho_b = rho_srt(i,kL,j)
10270 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
10280 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
10290 deep_wt_Lu(I,k,j) = wt_b ; deep_wt_Ru(I,k,j) = 1.0
1030
10310 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i+1,kR,j) * wt_b
10320 h_demand_L(kL-1) = h_demand_L(kL-1) + 0.5*h_srt(i+1,kR,j) * (1.0-wt_b)
1033
10340 kR = kR+1 ; left_set(k) = .false. ; right_set(k) = .true.
10350 elseif (rho_srt(i,kL,j) < rho_srt(i+1,kR,j)) then
1036 ! The left point is lighter and defines the density for this trio.
10370 nP = nP+1 ; k = nP
10380 rho_pair = rho_srt(i,kL,j)
10390 k0b_Lu(I,k,j) = k0_srt(i,kL,j) ; k0b_Ru(I,k,j) = k0_srt(i+1,kR,j)
10400 k0a_Lu(I,k,j) = k0b_Lu(I,k,j) ; k0a_Ru(I,k,j) = k0_srt(i+1,kR-1,j)
1041
10420 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1043
10440 rho_a = rho_srt(i+1,kR-1,j) ; rho_b = rho_srt(i+1,kR,j)
10450 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
10460 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
10470 deep_wt_Lu(I,k,j) = 1.0 ; deep_wt_Ru(I,k,j) = wt_b
1048
10490 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j) * wt_b
10500 h_demand_R(kR-1) = h_demand_R(kR-1) + 0.5*h_srt(i,kL,j) * (1.0-wt_b)
1051
10520 kL = kL+1 ; left_set(k) = .true. ; right_set(k) = .false.
10530 elseif ((k0_srt(i,kL,j) <= nkmb) .or. (k0_srt(i+1,kR,j) <= nkmb)) then
1054 ! The densities are exactly equal and one layer is above the interior.
10550 nP = nP+1 ; k = nP
10560 k0b_Lu(I,k,j) = k0_srt(i,kL,j) ; k0b_Ru(I,k,j) = k0_srt(i+1,kR,j)
10570 k0a_Lu(I,k,j) = k0b_Lu(I,k,j) ; k0a_Ru(I,k,j) = k0b_Ru(I,k,j)
10580 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
10590 deep_wt_Lu(I,k,j) = 1.0 ; deep_wt_Ru(I,k,j) = 1.0
1060
10610 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i+1,kR,j)
10620 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1063
10640 kL = kL+1 ; kR = kR+1 ; left_set(k) = .true. ; right_set(k) = .true.
1065 else ! The densities are exactly equal and in the interior.
1066 ! Mixing in this case has already occurred, so accumulate the thickness
1067 ! demanded for that mixing and skip onward.
10680 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i+1,kR,j)
10690 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1070
10710 kL = kL+1 ; kR = kR+1
1072 endif
1073 enddo ! Loop to accumulate pairs of columns.
10740 nPu(I,j) = nP ! This is the number of active pairings.
1075
1076 ! Determine what fraction of the thickness "demand" can be supplied.
10770 do k=1,num_srt(i+1,j)
10780 h_supply_frac_R(k) = 1.0
10790 if (h_demand_R(k) > 0.5*h_srt(i+1,k,j)) &
10800 h_supply_frac_R(k) = 0.5*h_srt(i+1,k,j) / h_demand_R(k)
1081 enddo
10820 do k=1,num_srt(i,j)
10830 h_supply_frac_L(k) = 1.0
10840 if (h_demand_L(k) > 0.5*h_srt(i,k,j)) &
10850 h_supply_frac_L(k) = 0.5*h_srt(i,k,j) / h_demand_L(k)
1086 enddo
1087
1088 ! Distribute the "exported" thicknesses proportionately.
10890 do k=1,nPu(I,j)
10900 kL = kbs_Lp(k) ; kR = kbs_Rp(k)
10910 hP_Lu(I,k,j) = 0.0 ; hP_Ru(I,k,j) = 0.0
10920 if (left_set(k)) then ! Add the contributing thicknesses on the right.
10930 if (deep_wt_Ru(I,k,j) < 1.0) then
10940 hP_Ru(I,k,j) = 0.5*h_srt(i,kL,j) * min(h_supply_frac_R(kR), h_supply_frac_R(kR-1))
10950 wt_b = deep_wt_Ru(I,k,j)
10960 h_used_R(kR-1) = h_used_R(kR-1) + (1.0 - wt_b)*hP_Ru(I,k,j)
10970 h_used_R(kR) = h_used_R(kR) + wt_b*hP_Ru(I,k,j)
1098 else
10990 hP_Ru(I,k,j) = 0.5*h_srt(i,kL,j) * h_supply_frac_R(kR)
11000 h_used_R(kR) = h_used_R(kR) + hP_Ru(I,k,j)
1101 endif
1102 endif
11030 if (right_set(k)) then ! Add the contributing thicknesses on the left.
11040 if (deep_wt_Lu(I,k,j) < 1.0) then
11050 hP_Lu(I,k,j) = 0.5*h_srt(i+1,kR,j) * min(h_supply_frac_L(kL), h_supply_frac_L(kL-1))
11060 wt_b = deep_wt_Lu(I,k,j)
11070 h_used_L(kL-1) = h_used_L(kL-1) + (1.0 - wt_b)*hP_Lu(I,k,j)
11080 h_used_L(kL) = h_used_L(kL) + wt_b*hP_Lu(I,k,j)
1109 else
11100 hP_Lu(I,k,j) = 0.5*h_srt(i+1,kR,j) * h_supply_frac_L(kL)
11110 h_used_L(kL) = h_used_L(kL) + hP_Lu(I,k,j)
1112 endif
1113 endif
1114 enddo
1115
1116 ! The left-over thickness (at least half the layer thickness) is now
1117 ! added to the thicknesses of the importing columns.
11180 do k=1,nPu(I,j)
11190 if (left_set(k)) hP_Lu(I,k,j) = hP_Lu(I,k,j) + &
11200 (h_srt(i,kbs_Lp(k),j) - h_used_L(kbs_Lp(k)))
11210 if (right_set(k)) hP_Ru(I,k,j) = hP_Ru(I,k,j) + &
11220 (h_srt(i+1,kbs_Rp(k),j) - h_used_R(kbs_Rp(k)))
1123 enddo
1124
1125 endif ; enddo ; enddo ! i- & j- loops over zonal faces.
1126
11270 allocate(deep_wt_Lv(isd:ied,k_size,JsdB:JedB))
11280 allocate(deep_wt_Rv(isd:ied,k_size,JsdB:JedB))
11290 allocate(hP_Lv(isd:ied,k_size,JsdB:JedB))
11300 allocate(hP_Rv(isd:ied,k_size,JsdB:JedB))
11310 allocate(k0a_Lv(isd:ied,k_size,JsdB:JedB))
11320 allocate(k0a_Rv(isd:ied,k_size,JsdB:JedB))
11330 allocate(k0b_Lv(isd:ied,k_size,JsdB:JedB))
11340 allocate(k0b_Rv(isd:ied,k_size,JsdB:JedB))
1135 !$omp target enter data map(alloc: deep_wt_Lv, deep_wt_Rv, hP_Lv, hP_Rv, k0a_Lv, k0a_Rv, k0b_Lv, &
1136 !$omp k0b_Rv)
1137
1138 !$omp target enter data map(alloc: nPv)
1139
1140!$OMP target teams loop collapse(2) &
1141!$OMP private(h_demand_L,h_used_L,h_demand_R,h_used_R, &
1142!$OMP kR,kL,nP,rho_pair,kbs_Lp,kbs_Rp,rho_a,rho_b, &
1143!$OMP wt_b,left_set,right_set,h_supply_frac_R, &
1144!$OMP h_supply_frac_L,k) map(to: num_srt)
11450 do J=js-1,je ; do i=is,ie ; if (G%mask2dCv(i,J) > 0.0) then
1146 ! Set up the pairings for fluxes through the meridional faces.
1147
11480 do k=1,num_srt(i,j) ; h_demand_L(k) = 0.0 ; h_used_L(k) = 0.0 ; enddo
11490 do k=1,num_srt(i,j+1) ; h_demand_R(k) = 0.0 ; h_used_R(k) = 0.0 ; enddo
1150
1151 ! First merge the left and right lists into a single, sorted list.
1152
1153 ! Discard any layers that are lighter than the lightest in the other
1154 ! column. They can only participate in mixing as the lighter part of a
1155 ! pair of points.
11560 if (rho_srt(i,1,j) < rho_srt(i,1,j+1)) then
11570 kR = 1
11580 do kL=2,num_srt(i,j) ; if (rho_srt(i,kL,j) >= rho_srt(i,1,j+1)) exit ; enddo
11590 elseif (rho_srt(i,1,j+1) < rho_srt(i,1,j)) then
11600 kL = 1
11610 do kR=2,num_srt(i,j+1) ; if (rho_srt(i,kR,j+1) >= rho_srt(i,1,j)) exit ; enddo
1162 else
11630 kL = 1 ; kR = 1
1164 endif
11650 nP = 0
11660 do ! Loop to accumulate pairs of columns.
11670 if ((kL > num_srt(i,j)) .or. (kR > num_srt(i,j+1))) exit
1168
11690 if (rho_srt(i,kL,j) > rho_srt(i,kR,j+1)) then
1170 ! The right point is lighter and defines the density for this trio.
11710 nP = nP+1 ; k = nP
11720 rho_pair = rho_srt(i,kR,j+1)
1173
11740 k0b_Lv(i,k,J) = k0_srt(i,kL,j) ; k0b_Rv(i,k,J) = k0_srt(i,kR,j+1)
11750 k0a_Lv(i,k,J) = k0_srt(i,kL-1,j) ; k0a_Rv(i,k,J) = k0b_Rv(i,k,J)
11760 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1177
11780 rho_a = rho_srt(i,kL-1,j) ; rho_b = rho_srt(i,kL,j)
11790 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
11800 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
11810 deep_wt_Lv(I,k,J) = wt_b ; deep_wt_Rv(I,k,J) = 1.0
1182
11830 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i,kR,j+1) * wt_b
11840 h_demand_L(kL-1) = h_demand_L(kL-1) + 0.5*h_srt(i,kR,j+1) * (1.0-wt_b)
1185
11860 kR = kR+1 ; left_set(k) = .false. ; right_set(k) = .true.
11870 elseif (rho_srt(i,kL,j) < rho_srt(i,kR,j+1)) then
1188 ! The left point is lighter and defines the density for this trio.
11890 nP = nP+1 ; k = nP
11900 rho_pair = rho_srt(i,kL,j)
11910 k0b_Lv(i,k,J) = k0_srt(i,kL,j) ; k0b_Rv(i,k,J) = k0_srt(i,kR,j+1)
11920 k0a_Lv(i,k,J) = k0b_Lv(i,k,J) ; k0a_Rv(i,k,J) = k0_srt(i,kR-1,j+1)
1193
11940 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1195
11960 rho_a = rho_srt(i,kR-1,j+1) ; rho_b = rho_srt(i,kR,j+1)
11970 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
11980 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
11990 deep_wt_Lv(I,k,J) = 1.0 ; deep_wt_Rv(I,k,J) = wt_b
1200
12010 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j) * wt_b
12020 h_demand_R(kR-1) = h_demand_R(kR-1) + 0.5*h_srt(i,kL,j) * (1.0-wt_b)
1203
12040 kL = kL+1 ; left_set(k) = .true. ; right_set(k) = .false.
12050 elseif ((k0_srt(i,kL,j) <= nkmb) .or. (k0_srt(i,kR,j+1) <= nkmb)) then
1206 ! The densities are exactly equal and one layer is above the interior.
12070 nP = nP+1 ; k = nP
12080 k0b_Lv(i,k,J) = k0_srt(i,kL,j) ; k0b_Rv(i,k,J) = k0_srt(i,kR,j+1)
12090 k0a_Lv(i,k,J) = k0b_Lv(i,k,J) ; k0a_Rv(i,k,J) = k0b_Rv(i,k,J)
12100 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
12110 deep_wt_Lv(I,k,J) = 1.0 ; deep_wt_Rv(I,k,J) = 1.0
1212
12130 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i,kR,j+1)
12140 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1215
12160 kL = kL+1 ; kR = kR+1 ; left_set(k) = .true. ; right_set(k) = .true.
1217 else ! The densities are exactly equal and in the interior.
1218 ! Mixing in this case has already occurred, so accumulate the thickness
1219 ! demanded for that mixing and skip onward.
12200 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i,kR,j+1)
12210 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1222
12230 kL = kL+1 ; kR = kR+1
1224 endif
1225 enddo ! Loop to accumulate pairs of columns.
12260 nPv(i,J) = nP ! This is the number of active pairings.
1227
1228 ! Determine what fraction of the thickness "demand" can be supplied.
12290 do k=1,num_srt(i,j+1)
12300 h_supply_frac_R(k) = 1.0
12310 if (h_demand_R(k) > 0.5*h_srt(i,k,j+1)) &
12320 h_supply_frac_R(k) = 0.5*h_srt(i,k,j+1) / h_demand_R(k)
1233 enddo
12340 do k=1,num_srt(i,j)
12350 h_supply_frac_L(k) = 1.0
12360 if (h_demand_L(k) > 0.5*h_srt(i,k,j)) &
12370 h_supply_frac_L(k) = 0.5*h_srt(i,k,j) / h_demand_L(k)
1238 enddo
1239
1240 ! Distribute the "exported" thicknesses proportionately.
12410 do k=1,nPv(i,J)
12420 kL = kbs_Lp(k) ; kR = kbs_Rp(k)
12430 hP_Lv(I,k,J) = 0.0 ; hP_Rv(I,k,J) = 0.0
12440 if (left_set(k)) then ! Add the contributing thicknesses on the right.
12450 if (deep_wt_Rv(I,k,J) < 1.0) then
12460 hP_Rv(I,k,J) = 0.5*h_srt(i,kL,j) * min(h_supply_frac_R(kR), h_supply_frac_R(kR-1))
12470 wt_b = deep_wt_Rv(I,k,J)
12480 h_used_R(kR-1) = h_used_R(kR-1) + (1.0 - wt_b) * hP_Rv(I,k,J)
12490 h_used_R(kR) = h_used_R(kR) + wt_b * hP_Rv(I,k,J)
1250 else
12510 hP_Rv(I,k,J) = 0.5*h_srt(i,kL,j) * h_supply_frac_R(kR)
12520 h_used_R(kR) = h_used_R(kR) + hP_Rv(I,k,J)
1253 endif
1254 endif
12550 if (right_set(k)) then ! Add the contributing thicknesses on the left.
12560 if (deep_wt_Lv(I,k,J) < 1.0) then
12570 hP_Lv(I,k,J) = 0.5*h_srt(i,kR,j+1) * min(h_supply_frac_L(kL), h_supply_frac_L(kL-1))
12580 wt_b = deep_wt_Lv(I,k,J)
12590 h_used_L(kL-1) = h_used_L(kL-1) + (1.0 - wt_b) * hP_Lv(I,k,J)
12600 h_used_L(kL) = h_used_L(kL) + wt_b * hP_Lv(I,k,J)
1261 else
12620 hP_Lv(I,k,J) = 0.5*h_srt(i,kR,j+1) * h_supply_frac_L(kL)
12630 h_used_L(kL) = h_used_L(kL) + hP_Lv(I,k,J)
1264 endif
1265 endif
1266 enddo
1267
1268 ! The left-over thickness (at least half the layer thickness) is now
1269 ! added to the thicknesses of the importing columns.
12700 do k=1,nPv(i,J)
12710 if (left_set(k)) hP_Lv(I,k,J) = hP_Lv(I,k,J) + &
12720 (h_srt(i,kbs_Lp(k),j) - h_used_L(kbs_Lp(k)))
12730 if (right_set(k)) hP_Rv(I,k,J) = hP_Rv(I,k,J) + &
12740 (h_srt(i,kbs_Rp(k),j+1) - h_used_R(kbs_Rp(k)))
1275 enddo
1276
1277
1278 endif ; enddo ; enddo ! i- & j- loops over meridional faces.
1279 !$omp target exit data map(release: h_srt, k0_srt)
1280 ! The tracer-specific calculations start here.
1281
1282 !$omp target enter data map(alloc: Tr_flux_3d, Tr_adj_vert_L, Tr_adj_vert_R, tr_flux_N, &
1283 !$omp tr_flux_S, tr_flux_E, tr_flux_W, tr_flux_conv)
1284
12850 do itt=1,max_itt
1286
12870 if (itt > 1) then ! The halos have already been filled if itt==1.
12880 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass, omp_offload=.true.)
1289 endif
12900 do m=1,ntr
1291 ! Zero out tracer tendencies.
12920 if (CS%answer_date <= 20240330) then
12930 do concurrent (k=1:nz, j=jsd:jed, i=isd:ied)
12940 tr_flux_conv(i,j,k) = 0.0
1295 enddo
1296 else
12970 do concurrent (k=1:nz, j=jsd:jed, i=isd:ied)
12980 tr_flux_N(i,j,k) = 0.0 ; tr_flux_S(i,j,k) = 0.0
12990 tr_flux_E(i,j,k) = 0.0 ; tr_flux_W(i,j,k) = 0.0
1300 enddo
1301 endif
13020 do concurrent (k=1:2*nz, J=JsdB:JedB, i=isd:ied)
13030 tr_flux_3d(i,j,k) = 0.0
13040 tr_adj_vert_R(i,j,k) = 0.0 ; tr_adj_vert_L(i,j,k) = 0.0
1305 enddo
1306
1307 ! collapse(2) is reproducible for CS%answer_date > 20240330 but not <= 20240330.
1308 ! Do concurrent around j-loop doesn't seem to do the right thing.
1309 !$omp target teams loop collapse(2) private(Tr_min_face,Tr_max_face,kLa,kLb,kRa,kRb, &
1310 !$omp Tr_La,Tr_Lb,Tr_Ra,Tr_Rb,Tr_av_L,wt_b,Tr_av_R, &
1311 !$omp h_L,h_R,Tr_flux,Tr_adj_vert,wt_a,vol)
13120 do j=js,je ; do I=is-1,ie ; if (G%mask2dCu(I,j) > 0.0) then
1313 ! Determine the fluxes through the zonal faces.
1314
1315 ! Find the acceptable range of tracer concentration around this face.
13160 if (nPu(I,j) >= 1) then
13170 Tr_min_face = min(Tr(m)%t(i,j,1), Tr(m)%t(i+1,j,1))
13180 Tr_max_face = max(Tr(m)%t(i,j,1), Tr(m)%t(i+1,j,1))
13190 do k=2,nkmb
13200 Tr_min_face = min(Tr_min_face, Tr(m)%t(i,j,k), Tr(m)%t(i+1,j,k))
13210 Tr_max_face = max(Tr_max_face, Tr(m)%t(i,j,k), Tr(m)%t(i+1,j,k))
1322 enddo
1323
1324 ! Include the next two layers denser than the densest buffer layer.
13250 kLa = nkmb+1 ; if (max_kRho(i,j) < nz+1) kLa = max_kRho(i,j)
13260 kLb = kLa ; if (max_kRho(i,j) < nz) kLb = max_kRho(i,j)+1
13270 kRa = nkmb+1 ; if (max_kRho(i+1,j) < nz+1) kRa = max_kRho(i+1,j)
13280 kRb = kRa ; if (max_kRho(i+1,j) < nz) kRb = max_kRho(i+1,j)+1
13290 Tr_La = Tr_min_face ; Tr_Lb = Tr_La ; Tr_Ra = Tr_La ; Tr_Rb = Tr_La
13300 if (h(i,j,kLa) > h_exclude) Tr_La = Tr(m)%t(i,j,kLa)
13310 if ((CS%answer_date <= 20240330) .and. CS%limit_bug) then
13320 if (h(i,j,kLb) > h_exclude) Tr_La = Tr(m)%t(i,j,kLb)
1333 else
13340 if (h(i,j,kLb) > h_exclude) Tr_Lb = Tr(m)%t(i,j,kLb)
1335 endif
13360 if (h(i+1,j,kRa) > h_exclude) Tr_Ra = Tr(m)%t(i+1,j,kRa)
13370 if (h(i+1,j,kRb) > h_exclude) Tr_Rb = Tr(m)%t(i+1,j,kRb)
13380 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
13390 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1340
1341 ! Include all points in diffusive pairings at this face.
13420 do k=1,nPu(I,j)
13430 Tr_Lb = Tr(m)%t(i,j,k0b_Lu(I,k,j))
13440 Tr_Rb = Tr(m)%t(i+1,j,k0b_Ru(I,k,j))
13450 Tr_La = Tr_Lb ; Tr_Ra = Tr_Rb
13460 if (deep_wt_Lu(I,k,j) < 1.0) Tr_La = Tr(m)%t(i,j,k0a_Lu(I,k,j))
13470 if (deep_wt_Ru(I,k,j) < 1.0) Tr_Ra = Tr(m)%t(i+1,j,k0a_Ru(I,k,j))
13480 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
13490 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1350 enddo
1351 endif
1352
13530 do k=1,nPu(I,j)
13540 kLb = k0b_Lu(I,k,j) ; Tr_Lb = Tr(m)%t(i,j,kLb) ; Tr_av_L = Tr_Lb
13550 if (deep_wt_Lu(I,k,j) < 1.0) then
13560 kLa = k0a_Lu(I,k,j) ; Tr_La = Tr(m)%t(i,j,kLa)
13570 wt_b = deep_wt_Lu(I,k,j)
13580 Tr_av_L = wt_b*Tr_Lb + (1.0-wt_b)*Tr_La
1359 endif
1360
13610 kRb = k0b_Ru(I,k,j) ; Tr_Rb = Tr(m)%t(i+1,j,kRb) ; Tr_av_R = Tr_Rb
13620 if (deep_wt_Ru(I,k,j) < 1.0) then
13630 kRa = k0a_Ru(I,k,j) ; Tr_Ra = Tr(m)%t(i+1,j,kRa)
13640 wt_b = deep_wt_Ru(I,k,j)
13650 Tr_av_R = wt_b*Tr_Rb + (1.0-wt_b)*Tr_Ra
1366 endif
1367
13680 h_L = hP_Lu(I,k,j) ; h_R = hP_Ru(I,k,j)
13690 if (CS%answer_date <= 20240330) then
1370 Tr_flux = I_maxitt * khdt_epi_x(I,j) * (Tr_av_L - Tr_av_R) * &
13710 ((2.0 * h_L * h_R) / (h_L + h_R))
1372 else
1373 Tr_flux = I_maxitt * ((2.0 * h_L * h_R) / (h_L + h_R)) * &
13740 khdt_epi_x(I,j) * (Tr_av_L - Tr_av_R)
1375 endif
1376
13770 if (deep_wt_Lu(I,k,j) >= 1.0) then
13780 if (CS%answer_date <= 20240330) then
13790 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - Tr_flux
1380 else
13810 tr_flux_E(i,j,kLb) = tr_flux_E(i,j,kLb) + Tr_flux
1382 endif
1383 else
13840 Tr_adj_vert = 0.0
13850 wt_b = deep_wt_Lu(I,k,j) ; wt_a = 1.0 - wt_b
13860 vol = hP_Lu(I,k,j) * G%areaT(i,j)
1387
1388 ! Ensure that the tracer flux does not drive the tracer values
1389 ! outside of the range Tr_min_face <= Tr <= Tr_max_face, or if it
1390 ! does that the concentration in both contributing pieces exceed
1391 ! this range equally. With down-gradient fluxes and the initial tracer
1392 ! concentrations determining the valid range, the latter condition
1393 ! only enters for large values of the effective diffusive CFL number.
13940 if (Tr_flux > 0.0) then
13950 if (Tr_La < Tr_Lb) then ; if (vol*(Tr_La-Tr_min_face) < Tr_flux) &
1396 Tr_adj_vert = -wt_a * min(Tr_flux - vol * (Tr_La-Tr_min_face), &
13970 (vol*wt_b) * (Tr_Lb - Tr_La))
13980 else ; if (vol*(Tr_Lb-Tr_min_face) < Tr_flux) &
1399 Tr_adj_vert = wt_b * min(Tr_flux - vol * (Tr_Lb-Tr_min_face), &
14000 (vol*wt_a) * (Tr_La - Tr_Lb))
1401 endif
14020 elseif (Tr_flux < 0.0) then
14030 if (Tr_La > Tr_Lb) then ; if (vol * (Tr_max_face-Tr_La) < -Tr_flux) &
1404 Tr_adj_vert = wt_a * min(-Tr_flux - vol * (Tr_max_face-Tr_La), &
14050 (vol*wt_b) * (Tr_La - Tr_Lb))
14060 else ; if (vol*(Tr_max_face-Tr_Lb) < -Tr_flux) &
1407 Tr_adj_vert = -wt_b * min(-Tr_flux - vol * (Tr_max_face-Tr_Lb), &
14080 (vol*wt_a)*(Tr_Lb - Tr_La))
1409 endif
1410 endif
1411
14120 if (CS%answer_date <= 20240330) then
14130 tr_flux_conv(i,j,kLa) = tr_flux_conv(i,j,kLa) - (wt_a*Tr_flux + Tr_adj_vert)
14140 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - (wt_b*Tr_flux - Tr_adj_vert)
1415 else
14160 tr_flux_E(i,j,kLa) = tr_flux_E(i,j,kLa) + (wt_a*Tr_flux + Tr_adj_vert)
14170 tr_flux_E(i,j,kLb) = tr_flux_E(i,j,kLb) + (wt_b*Tr_flux - Tr_adj_vert)
1418 endif
1419 endif
1420
14210 if (deep_wt_Ru(I,k,j) >= 1.0) then
14220 if (CS%answer_date <= 20240330) then
14230 tr_flux_conv(i+1,j,kRb) = tr_flux_conv(i+1,j,kRb) + Tr_flux
1424 else
14250 tr_flux_W(i+1,j,kRb) = tr_flux_W(i+1,j,kRb) + Tr_flux
1426 endif
1427 else
14280 Tr_adj_vert = 0.0
14290 wt_b = deep_wt_Ru(I,k,j) ; wt_a = 1.0 - wt_b
14300 vol = hP_Ru(I,k,j) * G%areaT(i+1,j)
1431
1432 ! Ensure that the tracer flux does not drive the tracer values
1433 ! outside of the range Tr_min_face <= Tr <= Tr_max_face, or if it
1434 ! does that the concentration in both contributing pieces exceed
1435 ! this range equally. With down-gradient fluxes and the initial tracer
1436 ! concentrations determining the valid range, the latter condition
1437 ! only enters for large values of the effective diffusive CFL number.
14380 if (Tr_flux < 0.0) then
14390 if (Tr_Ra < Tr_Rb) then ; if (vol * (Tr_Ra-Tr_min_face) < -Tr_flux) &
1440 Tr_adj_vert = -wt_a * min(-Tr_flux - vol * (Tr_Ra-Tr_min_face), &
14410 (vol*wt_b) * (Tr_Rb - Tr_Ra))
14420 else ; if (vol*(Tr_Rb-Tr_min_face) < (-Tr_flux)) &
1443 Tr_adj_vert = wt_b * min(-Tr_flux - vol * (Tr_Rb-Tr_min_face), &
14440 (vol*wt_a) * (Tr_Ra - Tr_Rb))
1445 endif
14460 elseif (Tr_flux > 0.0) then
14470 if (Tr_Ra > Tr_Rb) then ; if (vol * (Tr_max_face-Tr_Ra) < Tr_flux) &
1448 Tr_adj_vert = wt_a * min(Tr_flux - vol * (Tr_max_face-Tr_Ra), &
14490 (vol*wt_b) * (Tr_Ra - Tr_Rb))
14500 else ; if (vol*(Tr_max_face-Tr_Rb) < Tr_flux) &
1451 Tr_adj_vert = -wt_b * min(Tr_flux - vol * (Tr_max_face-Tr_Rb), &
14520 (vol*wt_a)*(Tr_Rb - Tr_Ra))
1453 endif
1454 endif
1455
14560 if (CS%answer_date <= 20240330) then
14570 tr_flux_conv(i+1,j,kRa) = tr_flux_conv(i+1,j,kRa) + (wt_a*Tr_flux - Tr_adj_vert)
14580 tr_flux_conv(i+1,j,kRb) = tr_flux_conv(i+1,j,kRb) + (wt_b*Tr_flux + Tr_adj_vert)
1459 else
14600 tr_flux_W(i+1,j,kRa) = tr_flux_W(i+1,j,kRa) + (wt_a*Tr_flux - Tr_adj_vert)
14610 tr_flux_W(i+1,j,kRb) = tr_flux_W(i+1,j,kRb) + (wt_b*Tr_flux + Tr_adj_vert)
1462 endif
1463 endif
14640 if (associated(Tr(m)%df2d_x)) &
14650 Tr(m)%df2d_x(I,j) = Tr(m)%df2d_x(I,j) + Tr_flux * Idt
1466 enddo ! Loop over pairings at faces.
1467 endif ; enddo ; enddo ! i- & j- loops over zonal faces.
1468
1469 ! this gives wrong result when using do concurrent on NVHPC 25.9
1470 !$omp target teams loop collapse(2) private(Tr_min_face,Tr_max_face,kLa,kLb,kRa,kRb, &
1471 !$omp Tr_La,Tr_Lb,Tr_Ra,Tr_Rb,Tr_av_L,wt_b,Tr_av_R, &
1472 !$omp h_L,h_R,Tr_flux,Tr_adj_vert,wt_a,vol)
14730 do J=js-1,je ; do i=is,ie ; if (G%mask2dCv(i,J) > 0.0) then
1474 ! Determine the fluxes through the meridional faces.
1475
1476 ! Find the acceptable range of tracer concentration around this face.
14770 if (nPv(i,J) >= 1) then
14780 Tr_min_face = min(Tr(m)%t(i,j,1), Tr(m)%t(i,j+1,1))
14790 Tr_max_face = max(Tr(m)%t(i,j,1), Tr(m)%t(i,j+1,1))
14800 do k=2,nkmb
14810 Tr_min_face = min(Tr_min_face, Tr(m)%t(i,j,k), Tr(m)%t(i,j+1,k))
14820 Tr_max_face = max(Tr_max_face, Tr(m)%t(i,j,k), Tr(m)%t(i,j+1,k))
1483 enddo
1484
1485 ! Include the next two layers denser than the densest buffer layer.
14860 kLa = nkmb+1 ; if (max_kRho(i,j) < nz+1) kLa = max_kRho(i,j)
14870 kLb = kLa ; if (max_kRho(i,j) < nz) kLb = max_kRho(i,j)+1
14880 kRa = nkmb+1 ; if (max_kRho(i,j+1) < nz+1) kRa = max_kRho(i,j+1)
14890 kRb = kRa ; if (max_kRho(i,j+1) < nz) kRb = max_kRho(i,j+1)+1
14900 Tr_La = Tr_min_face ; Tr_Lb = Tr_La ; Tr_Ra = Tr_La ; Tr_Rb = Tr_La
14910 if (h(i,j,kLa) > h_exclude) Tr_La = Tr(m)%t(i,j,kLa)
14920 if ((CS%answer_date <= 20240330) .and. CS%limit_bug) then
14930 if (h(i,j,kLb) > h_exclude) Tr_La = Tr(m)%t(i,j,kLb)
1494 else
14950 if (h(i,j,kLb) > h_exclude) Tr_Lb = Tr(m)%t(i,j,kLb)
1496 endif
14970 if (h(i,j+1,kRa) > h_exclude) Tr_Ra = Tr(m)%t(i,j+1,kRa)
14980 if (h(i,j+1,kRb) > h_exclude) Tr_Rb = Tr(m)%t(i,j+1,kRb)
14990 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
15000 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1501
1502 ! Include all points in diffusive pairings at this face.
15030 do k=1,nPv(i,J)
15040 Tr_Lb = Tr(m)%t(i,j,k0b_Lv(i,k,J)) ; Tr_Rb = Tr(m)%t(i,j+1,k0b_Rv(i,k,J))
15050 Tr_La = Tr_Lb ; Tr_Ra = Tr_Rb
15060 if (deep_wt_Lv(I,k,J) < 1.0) Tr_La = Tr(m)%t(i,j,k0a_Lv(i,k,J))
15070 if (deep_wt_Rv(I,k,J) < 1.0) Tr_Ra = Tr(m)%t(i,j+1,k0a_Rv(i,k,J))
15080 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
15090 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1510 enddo
1511 endif
1512
15130 do k=1,nPv(i,J)
15140 kLb = k0b_Lv(i,k,J) ; Tr_Lb = Tr(m)%t(i,j,kLb) ; Tr_av_L = Tr_Lb
15150 if (deep_wt_Lv(I,k,J) < 1.0) then
15160 kLa = k0a_Lv(i,k,J) ; Tr_La = Tr(m)%t(i,j,kLa)
15170 wt_b = deep_wt_Lv(I,k,J)
15180 Tr_av_L = wt_b * Tr_Lb + (1.0-wt_b) * Tr_La
1519 endif
1520
15210 kRb = k0b_Rv(i,k,J) ; Tr_Rb = Tr(m)%t(i,j+1,kRb) ; Tr_av_R = Tr_Rb
15220 if (deep_wt_Rv(I,k,J) < 1.0) then
15230 kRa = k0a_Rv(i,k,J) ; Tr_Ra = Tr(m)%t(i,j+1,kRa)
15240 wt_b = deep_wt_Rv(I,k,J)
15250 Tr_av_R = wt_b * Tr_Rb + (1.0-wt_b) * Tr_Ra
1526 endif
1527
15280 h_L = hP_Lv(I,k,J) ; h_R = hP_Rv(I,k,J)
1529 Tr_flux = I_maxitt * ((2.0 * h_L * h_R) / (h_L + h_R)) * &
15300 khdt_epi_y(i,J) * (Tr_av_L - Tr_av_R)
15310 Tr_flux_3d(i,J,k) = Tr_flux
1532
15330 if (deep_wt_Lv(I,k,J) < 1.0) then
15340 Tr_adj_vert = 0.0
15350 wt_b = deep_wt_Lv(I,k,J) ; wt_a = 1.0 - wt_b
15360 vol = hP_Lv(I,k,J) * G%areaT(i,j)
1537
1538 ! Ensure that the tracer flux does not drive the tracer values
1539 ! outside of the range Tr_min_face <= Tr <= Tr_max_face.
15400 if (Tr_flux > 0.0) then
15410 if (Tr_La < Tr_Lb) then ; if (vol * (Tr_La-Tr_min_face) < Tr_flux) &
1542 Tr_adj_vert = -wt_a * min(Tr_flux - vol * (Tr_La-Tr_min_face), &
15430 (vol*wt_b) * (Tr_Lb - Tr_La))
15440 else ; if (vol*(Tr_Lb-Tr_min_face) < Tr_flux) &
1545 Tr_adj_vert = wt_b * min(Tr_flux - vol * (Tr_Lb-Tr_min_face), &
15460 (vol*wt_a) * (Tr_La - Tr_Lb))
1547 endif
15480 elseif (Tr_flux < 0.0) then
15490 if (Tr_La > Tr_Lb) then ; if (vol * (Tr_max_face-Tr_La) < -Tr_flux) &
1550 Tr_adj_vert = wt_a * min(-Tr_flux - vol * (Tr_max_face-Tr_La), &
15510 (vol*wt_b) * (Tr_La - Tr_Lb))
15520 else ; if (vol*(Tr_max_face-Tr_Lb) < -Tr_flux) &
1553 Tr_adj_vert = -wt_b * min(-Tr_flux - vol * (Tr_max_face-Tr_Lb), &
15540 (vol*wt_a)*(Tr_Lb - Tr_La))
1555 endif
1556 endif
15570 Tr_adj_vert_L(i,J,k) = Tr_adj_vert
1558 endif
1559
15600 if (deep_wt_Rv(I,k,J) < 1.0) then
15610 Tr_adj_vert = 0.0
15620 wt_b = deep_wt_Rv(I,k,J) ; wt_a = 1.0 - wt_b
15630 vol = hP_Rv(I,k,J) * G%areaT(i,j+1)
1564
1565 ! Ensure that the tracer flux does not drive the tracer values
1566 ! outside of the range Tr_min_face <= Tr <= Tr_max_face.
15670 if (Tr_flux < 0.0) then
15680 if (Tr_Ra < Tr_Rb) then ; if (vol * (Tr_Ra-Tr_min_face) < -Tr_flux) &
1569 Tr_adj_vert = -wt_a * min(-Tr_flux - vol * (Tr_Ra-Tr_min_face), &
15700 (vol*wt_b) * (Tr_Rb - Tr_Ra))
15710 else ; if (vol*(Tr_Rb-Tr_min_face) < (-Tr_flux)) &
1572 Tr_adj_vert = wt_b * min(-Tr_flux - vol * (Tr_Rb-Tr_min_face), &
15730 (vol*wt_a) * (Tr_Ra - Tr_Rb))
1574 endif
15750 elseif (Tr_flux > 0.0) then
15760 if (Tr_Ra > Tr_Rb) then ; if (vol * (Tr_max_face-Tr_Ra) < Tr_flux) &
1577 Tr_adj_vert = wt_a * min(Tr_flux - vol * (Tr_max_face-Tr_Ra), &
15780 (vol*wt_b) * (Tr_Ra - Tr_Rb))
15790 else ; if (vol*(Tr_max_face-Tr_Rb) < Tr_flux) &
1580 Tr_adj_vert = -wt_b * min(Tr_flux - vol * (Tr_max_face-Tr_Rb), &
15810 (vol*wt_a)*(Tr_Rb - Tr_Ra))
1582 endif
1583 endif
15840 Tr_adj_vert_R(i,J,k) = Tr_adj_vert
1585 endif
15860 if (associated(Tr(m)%df2d_y)) &
15870 Tr(m)%df2d_y(i,J) = Tr(m)%df2d_y(i,J) + Tr_flux * Idt
1588 enddo ! Loop over pairings at faces.
1589 endif ; enddo ; enddo ! i- & j- loops over meridional faces.
1590
1591 ! The non-stride-1 loop order here is to facilitate openMP threading. However, it might be
1592 ! suboptimal when openMP threading is not used, at which point it might be better to fuse
1593 ! this loop with those that precede it and thereby eliminate the need for three 3-d arrays.
15940 if (CS%answer_date <= 20240330) then
1595 ! KRa/b aren't guaranteed to be unique and update of tr_flux_conv(:, j/j+1, :) means this
1596 ! loop must be serial in both j and k. i can be paralellised.
15970 do concurrent (i=is:ie) DO_LOCALITY(local(j, k, kLb, kRb, kLa, wt_b, wt_a, kRa))
15980 do J=js-1,je ; if (G%mask2dCv(i,J) > 0.0) then
15990 do k=1,nPv(i,J)
16000 kLb = k0b_Lv(i,k,J) ; kRb = k0b_Rv(i,k,J)
16010 if (deep_wt_Lv(I,k,J) >= 1.0) then
16020 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - Tr_flux_3d(i,J,k)
1603 else
16040 kLa = k0a_Lv(i,k,J)
16050 wt_b = deep_wt_Lv(I,k,J) ; wt_a = 1.0 - wt_b
16060 tr_flux_conv(i,j,kLa) = tr_flux_conv(i,j,kLa) - (wt_a*Tr_flux_3d(i,J,k) + Tr_adj_vert_L(i,J,k))
16070 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - (wt_b*Tr_flux_3d(i,J,k) - Tr_adj_vert_L(i,J,k))
1608 endif
16090 if (deep_wt_Rv(I,k,J) >= 1.0) then
16100 tr_flux_conv(i,j+1,kRb) = tr_flux_conv(i,j+1,kRb) + Tr_flux_3d(i,J,k)
1611 else
16120 kRa = k0a_Rv(i,k,J)
16130 wt_b = deep_wt_Rv(I,k,J) ; wt_a = 1.0 - wt_b
1614 tr_flux_conv(i,j+1,kRa) = tr_flux_conv(i,j+1,kRa) + &
16150 (wt_a*Tr_flux_3d(i,J,k) - Tr_adj_vert_R(i,J,k))
1616 tr_flux_conv(i,j+1,kRb) = tr_flux_conv(i,j+1,kRb) + &
16170 (wt_b*Tr_flux_3d(i,J,k) + Tr_adj_vert_R(i,J,k))
1618 endif
1619 enddo
1620 endif ; enddo
1621 enddo
1622 else
1623 ! Update of tr_flux_N/S can be done independently so both i and j can be parallelised.
16240 do concurrent (J=js-1:je, i=is:ie, G%mask2dCv(i,J) > 0.0) DO_LOCALITY(local(k, kLb, kRb, kLa, wt_b, wt_a, kRa))
16250 do k=1,nPv(i,J)
16260 kLb = k0b_Lv(i,k,J) ; kRb = k0b_Rv(i,k,J)
16270 if (deep_wt_Lv(I,k,J) >= 1.0) then
16280 tr_flux_N(i,j,kLb) = tr_flux_N(i,j,kLb) + Tr_flux_3d(i,J,k)
1629 else
16300 kLa = k0a_Lv(i,k,J)
16310 wt_b = deep_wt_Lv(I,k,J) ; wt_a = 1.0 - wt_b
16320 tr_flux_N(i,j,kLa) = tr_flux_N(i,j,kLa) + (wt_a*Tr_flux_3d(i,J,k) + Tr_adj_vert_L(i,J,k))
16330 tr_flux_N(i,j,kLb) = tr_flux_N(i,j,kLb) + (wt_b*Tr_flux_3d(i,J,k) - Tr_adj_vert_L(i,J,k))
1634 endif
16350 if (deep_wt_Rv(I,k,J) >= 1.0) then
16360 tr_flux_S(i,j+1,kRb) = tr_flux_S(i,j+1,kRb) + Tr_flux_3d(i,J,k)
1637 else
16380 kRa = k0a_Rv(i,k,J)
16390 wt_b = deep_wt_Rv(I,k,J) ; wt_a = 1.0 - wt_b
16400 tr_flux_S(i,j+1,kRa) = tr_flux_S(i,j+1,kRa) + (wt_a*Tr_flux_3d(i,J,k) - Tr_adj_vert_R(i,J,k))
16410 tr_flux_S(i,j+1,kRb) = tr_flux_S(i,j+1,kRb) + (wt_b*Tr_flux_3d(i,J,k) + Tr_adj_vert_R(i,J,k))
1642 endif
1643 enddo
1644 enddo
1645 endif
1646
16470 if (CS%answer_date >= 20240331) then
16480 do concurrent (k=1:PEmax_kRho, j=js:je, i=is:ie)
1649 tr_flux_conv(i,j,k) = ((tr_flux_W(i,j,k) - tr_flux_E(i,j,k)) + &
16500 (tr_flux_S(i,j,k) - tr_flux_N(i,j,k)))
1651 enddo
1652 endif
1653
16540 do concurrent (k=1:PEmax_kRho, j=js:je, i=is:ie, (G%mask2dT(i,j) > 0.0) .and. (h(i,j,k) > 0.0))
16550 Tr(m)%t(i,j,k) = Tr(m)%t(i,j,k) + tr_flux_conv(i,j,k) / (h(i,j,k)*G%areaT(i,j))
1656 enddo
1657
1658 ! Do user controlled underflow of the tracer concentrations.
16590 if (Tr(m)%conc_underflow > 0.0) then
1660 !$omp target update from(Tr(m)%t)
1661 !$OMP parallel do default(shared)
16620 do k=1,nz ; do j=js,je ; do i=is,ie
16630 if (abs(Tr(m)%t(i,j,k)) < Tr(m)%conc_underflow) Tr(m)%t(i,j,k) = 0.0
1664 enddo ; enddo ; enddo
1665 !$omp target update to(Tr(m)%t)
1666 endif
1667
1668 enddo ! Loop over tracers
1669 enddo ! Loop over iterations
1670
1671 !$omp target exit data map(release: Tr_flux_3d, Tr_adj_vert_L, Tr_adj_vert_R, tr_flux_N, &
1672 !$omp tr_flux_S, tr_flux_E, tr_flux_W, tr_flux_conv, nPv, nPu, max_kRho, rho_srt, num_srt, &
1673 !$omp max_srt)
1674
1675 !$omp target exit data map(release: deep_wt_Lu, deep_wt_Ru, hP_Lu, hP_Ru, k0a_Lu, k0a_Ru, &
1676 !$omp k0b_Lu, k0b_Ru)
1677
1678 !$omp target exit data map(release: deep_wt_Lv, deep_wt_Rv, hP_Lv, hP_Rv, k0a_Lv, k0a_Rv, &
1679 !$Omp k0b_Lv, k0b_Rv)
1680
16810end subroutine tracer_epipycnal_ML_diff
1682
1683
1684!> Initialize lateral tracer diffusion module
16851subroutine tracer_hor_diff_init(Time, G, GV, US, param_file, diag, EOS, diabatic_CSp, CS)
1686 type(time_type), target, intent(in) :: Time !< current model time
1687 type(ocean_grid_type), intent(in) :: G !< ocean grid structure
1688 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
1689 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1690 type(diag_ctrl), target, intent(inout) :: diag !< diagnostic control
1691 type(EOS_type), target, intent(in) :: EOS !< Equation of state CS
1692 type(diabatic_CS), pointer, intent(in) :: diabatic_CSp !< Equation of state CS
1693 type(param_file_type), intent(in) :: param_file !< parameter file
1694 type(tracer_hor_diff_CS), pointer :: CS !< horz diffusion control structure
1695
1696 ! This include declares and sets the variable "version".
1697# include "version_variable.h"
1698 character(len=40) :: mdl = "MOM_tracer_hor_diff" ! This module's name.
1699 integer :: default_answer_date
1700
17011 if (associated(CS)) then
17020 call MOM_error(WARNING, "tracer_hor_diff_init called with associated control structure.")
17030 return
1704 endif
17051 allocate(CS)
1706
17071 CS%diag => diag
17081 CS%show_call_tree = callTree_showQuery()
1709
1710 ! Read all relevant parameters and write them to the model log.
17111 call log_version(param_file, mdl, version, "")
1712 call get_param(param_file, mdl, "KHTR", CS%KhTr, &
1713 "The background along-isopycnal tracer diffusivity.", &
17141 units="m2 s-1", default=0.0, scale=US%m_to_L**2*US%T_to_s)
1715! call get_param(param_file, mdl, "KHTR_USE_EBT_STRUCT", CS%KhTh_use_ebt_struct, &
1716! "If true, uses the equivalent barotropic structure "//&
1717! "as the vertical structure of the tracer diffusivity.",&
1718! default=.false.)
1719 call get_param(param_file, mdl, "KHTR_SLOPE_CFF", CS%KhTr_Slope_Cff, &
1720 "The scaling coefficient for along-isopycnal tracer "//&
1721 "diffusivity using a shear-based (Visbeck-like) "//&
1722 "parameterization. A non-zero value enables this param.", &
17231 units="nondim", default=0.0)
1724 call get_param(param_file, mdl, "KHTR_MIN", CS%KhTr_Min, &
1725 "The minimum along-isopycnal tracer diffusivity.", &
17261 units="m2 s-1", default=0.0, scale=US%m_to_L**2*US%T_to_s)
17271 if (CS%KhTr_Min > 0.0) then
1728 call get_param(param_file, mdl, "FULL_DEPTH_KHTR_MIN", CS%full_depth_khtr_min, &
1729 "If true, KHTR_MIN is enforced throughout the whole water column. "//&
1730 "Otherwise, KHTR_MIN is only enforced at the surface. This parameter "//&
1731 "is only available when KHTR_USE_EBT_STRUCT=True and KHTR_MIN>0.", &
17320 default=.false.)
1733 endif
1734 call get_param(param_file, mdl, "KHTR_MAX", CS%KhTr_Max, &
1735 "The maximum along-isopycnal tracer diffusivity.", &
17361 units="m2 s-1", default=0.0, scale=US%m_to_L**2*US%T_to_s)
1737 call get_param(param_file, mdl, "KHTR_PASSIVITY_COEFF", CS%KhTr_passivity_coeff, &
1738 "The coefficient that scales deformation radius over "//&
1739 "grid-spacing in passivity, where passivity is the ratio "//&
1740 "between along isopycnal mixing of tracers to thickness mixing. "//&
1741 "A non-zero value enables this parameterization.", &
17421 units="nondim", default=0.0)
1743 call get_param(param_file, mdl, "KHTR_PASSIVITY_MIN", CS%KhTr_passivity_min, &
1744 "The minimum passivity which is the ratio between "//&
1745 "along isopycnal mixing of tracers to thickness mixing.", &
17461 units="nondim", default=0.5)
1747 call get_param(param_file, mdl, "DIFFUSE_ML_TO_INTERIOR", CS%Diffuse_ML_interior, &
1748 "If true, enable epipycnal mixing between the surface "//&
17491 "boundary layer and the interior.", default=.false.)
1750 call get_param(param_file, mdl, "CHECK_DIFFUSIVE_CFL", CS%check_diffusive_CFL, &
1751 "If true, use enough iterations the diffusion to ensure "//&
1752 "that the diffusive equivalent of the CFL limit is not "//&
1753 "violated. If false, always use the greater of 1 or "//&
17541 "MAX_TR_DIFFUSION_CFL iteration.", default=.false.)
1755 call get_param(param_file, mdl, "MAX_TR_DIFFUSION_CFL", CS%max_diff_CFL, &
1756 "If positive, locally limit the along-isopycnal tracer "//&
1757 "diffusivity to keep the diffusive CFL locally at or "//&
1758 "below this value. The number of diffusive iterations "//&
1759 "is often this value or the next greater integer.", &
17601 units="nondim", default=-1.0)
1761 call get_param(param_File, mdl, "RECALC_NEUTRAL_SURF", CS%recalc_neutral_surf, &
1762 "If true, then recalculate the neutral surfaces if the \n"//&
1763 "diffusive CFL is exceeded. If false, assume that the \n"//&
17641 "positions of the surfaces do not change \n", default=.false.)
1765 call get_param(param_file, mdl, "DEFAULT_ANSWER_DATE", default_answer_date, &
1766 "This sets the default value for the various _ANSWER_DATE parameters.", &
17671 default=99991231, do_not_log=.true.)
1768 call get_param(param_file, mdl, "HOR_DIFF_ANSWER_DATE", CS%answer_date, &
1769 "The vintage of the order of arithmetic to use for the tracer diffusion. "//&
1770 "Values of 20240330 or below recover the answers from the original form of the "//&
1771 "along-isopycnal mixed layer to interior mixing code, while higher values use "//&
1772 "mathematically equivalent expressions that recover rotational symmetry "//&
1773 "when DIFFUSE_ML_TO_INTERIOR is true.", &
17741 default=default_answer_date, do_not_log=.not.CS%Diffuse_ML_interior)
1775 call get_param(param_file, mdl, "HOR_DIFF_LIMIT_BUG", CS%limit_bug, &
1776 "If true and the answer date is 20240330 or below, use a rotational symmetry "//&
1777 "breaking bug when limiting the tracer properties in tracer_epipycnal_ML_diff.", &
17781 default=.false., do_not_log=((.not.CS%Diffuse_ML_interior).or.(CS%answer_date>=20240331)))
17791 CS%ML_KhTR_scale = 1.0
17801 if (CS%Diffuse_ML_interior) then
1781 call get_param(param_file, mdl, "ML_KHTR_SCALE", CS%ML_KhTR_scale, &
1782 "With Diffuse_ML_interior, the ratio of the truly "//&
1783 "horizontal diffusivity in the mixed layer to the "//&
1784 "epipycnal diffusivity. The valid range is 0 to 1.", &
17850 units="nondim", default=1.0)
1786 endif
1787
1788 CS%use_neutral_diffusion = neutral_diffusion_init(Time, G, GV, US, param_file, diag, EOS, &
17891 diabatic_CSp, CS%neutral_diffusion_CSp )
17901 if (CS%use_neutral_diffusion .and. CS%Diffuse_ML_interior) call MOM_error(FATAL, "MOM_tracer_hor_diff: "// &
17910 "USE_NEUTRAL_DIFFUSION and DIFFUSE_ML_TO_INTERIOR are mutually exclusive!")
1792 CS%use_hor_bnd_diffusion = hor_bnd_diffusion_init(Time, G, GV, US, param_file, diag, diabatic_CSp, &
17931 CS%hor_bnd_diffusion_CSp)
17941 if (CS%use_hor_bnd_diffusion .and. CS%Diffuse_ML_interior) call MOM_error(FATAL, "MOM_tracer_hor_diff: "// &
17950 "USE_HORIZONTAL_BOUNDARY_DIFFUSION and DIFFUSE_ML_TO_INTERIOR are mutually exclusive!")
1796
17971 call get_param(param_file, mdl, "DEBUG", CS%debug, default=.false.)
1798
17991 id_clock_diffuse = cpu_clock_id('(Ocean diffuse tracer)', grain=CLOCK_MODULE)
18001 id_clock_epimix = cpu_clock_id('(Ocean epipycnal diffuse tracer)',grain=CLOCK_MODULE)
18011 id_clock_pass = cpu_clock_id('(Ocean tracer halo updates)', grain=CLOCK_ROUTINE)
18021 id_clock_sync = cpu_clock_id('(Ocean tracer global synch)', grain=CLOCK_ROUTINE)
1803
18041 CS%id_KhTr_u = -1
18051 CS%id_KhTr_v = -1
18061 CS%id_KhTr_h = -1
18071 CS%id_CFL = -1
1808
1809 CS%id_KhTr_u = register_diag_field('ocean_model', 'KHTR_u', diag%axesCui, Time, &
18101 'Epipycnal tracer diffusivity at zonal faces of tracer cell', 'm2 s-1', conversion=US%L_to_m**2*US%s_to_T)
1811 CS%id_KhTr_v = register_diag_field('ocean_model', 'KHTR_v', diag%axesCvi, Time, &
18121 'Epipycnal tracer diffusivity at meridional faces of tracer cell', 'm2 s-1', conversion=US%L_to_m**2*US%s_to_T)
1813 CS%id_KhTr_h = register_diag_field('ocean_model', 'KHTR_h', diag%axesTi, Time, &
1814 'Epipycnal tracer diffusivity at tracer cell center', 'm2 s-1', conversion=US%L_to_m**2*US%s_to_T, &
1815 cmor_field_name='diftrelo', &
1816 cmor_standard_name= 'ocean_tracer_epineutral_laplacian_diffusivity', &
18171 cmor_long_name = 'Ocean Tracer Epineutral Laplacian Diffusivity')
1818
1819 CS%id_khdt_x = register_diag_field('ocean_model', 'KHDT_x', diag%axesCu1, Time, &
18201 'Epipycnal tracer diffusivity operator at zonal faces of tracer cell', 'm2', conversion=US%L_to_m**2)
1821 CS%id_khdt_y = register_diag_field('ocean_model', 'KHDT_y', diag%axesCv1, Time, &
18221 'Epipycnal tracer diffusivity operator at meridional faces of tracer cell', 'm2', conversion=US%L_to_m**2)
18231 if (CS%check_diffusive_CFL) then
1824 CS%id_CFL = register_diag_field('ocean_model', 'CFL_lateral_diff', diag%axesT1, Time,&
18251 'Grid CFL number for lateral/neutral tracer diffusion', 'nondim')
1826 endif
1827
1828
1829end subroutine tracer_hor_diff_init
1830
18311subroutine tracer_hor_diff_end(CS)
1832 type(tracer_hor_diff_CS), pointer :: CS !< module control structure
1833
18341 call neutral_diffusion_end(CS%neutral_diffusion_CSp)
18351 call hor_bnd_diffusion_end(CS%hor_bnd_diffusion_CSp)
18361 if (associated(CS)) deallocate(CS)
1837
18381end subroutine tracer_hor_diff_end
1839
1840
1841!> \namespace mom_tracer_hor_diff
1842!!
1843!! \section section_intro Introduction to the module
1844!!
1845!! This module contains subroutines that handle horizontal
1846!! diffusion (i.e., isoneutral or along layer) of tracers.
1847!!
1848!! Each of the tracers are subject to Fickian along-coordinate
1849!! diffusion if Khtr is defined and positive. The tracer diffusion
1850!! can use a suitable number of iterations to guarantee stability
1851!! with an arbitrarily large time step.
1852
18530end module MOM_tracer_hor_diff