← 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
39387132 do j=js,je ; do i=is,ie
394 CFL(i,j) = 2.0*((khdt_x(I-1,j) + khdt_x(I,j)) + &
39586400 (khdt_y(i,J-1) + khdt_y(i,J))) * G%IareaT(i,j)
39687120 if (max_CFL < CFL(i,j)) max_CFL = CFL(i,j)
397 enddo ; enddo
39812 call cpu_clock_begin(id_clock_sync)
39912 call max_across_PEs(max_CFL)
40012 call cpu_clock_end(id_clock_sync)
40112 num_itts = max(1, ceiling(max_CFL - 4.0*EPSILON(max_CFL)))
40212 I_numitts = 1.0 / (real(num_itts))
40312 if (CS%id_CFL > 0) call post_data(CS%id_CFL, CFL, CS%diag)
4040 elseif (CS%max_diff_CFL > 0.0) then
4050 num_itts = max(1, ceiling(CS%max_diff_CFL - 4.0*EPSILON(CS%max_diff_CFL)))
4060 I_numitts = 1.0 / (real(num_itts))
407 else
4080 num_itts = 1 ; I_numitts = 1.0
409 endif
410
41148 do m=1,ntr
41236 if (associated(Reg%Tr(m)%df_x)) then
4130 do k=1,nz ; do j=js,je ; do I=is-1,ie
4140 Reg%Tr(m)%df_x(I,j,k) = 0.0
415 enddo ; enddo ; enddo
416 !$omp target update to(Reg%Tr(m)%df_x)
417 endif
41836 if (associated(Reg%Tr(m)%df_y)) then
4190 do k=1,nz ; do J=js-1,je ; do i=is,ie
4200 Reg%Tr(m)%df_y(i,J,k) = 0.0
421 enddo ; enddo ; enddo
422 !$omp target update to(Reg%Tr(m)%df_y)
423 endif
42436 if (associated(Reg%Tr(m)%df2d_x)) then
4250 do j=js,je ; do I=is-1,ie ; Reg%Tr(m)%df2d_x(I,j) = 0.0 ; enddo ; enddo
426 !$omp target update to(Reg%Tr(m)%df2d_x)
427 endif
42848 if (associated(Reg%Tr(m)%df2d_y)) then
4290 do J=js-1,je ; do i=is,ie ; Reg%Tr(m)%df2d_y(i,J) = 0.0 ; enddo ; enddo
430 !$omp target update to(Reg%Tr(m)%df2d_y)
431 endif
432 enddo
433
43412 if (CS%use_hor_bnd_diffusion) then
435 !$omp target update from(h)
436
4370 if (CS%show_call_tree) call callTree_waypoint("Calling horizontal boundary diffusion (tracer_hordiff)")
438 !$omp target update from(khdt_x, khdt_y)
4390 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
440
4410 do k=1,nz+1
4420 do J=js-1,je
4430 do i=is,ie
4440 Coef_y(i,J,K) = I_numitts * khdt_y(i,J)
445 enddo
446 enddo
447 enddo
4480 do k=1,nz+1
4490 do j=js,je
4500 do I=is-1,ie
4510 Coef_x(I,j,K) = I_numitts * khdt_x(I,j)
452 enddo
453 enddo
454 enddo
4550 if (CS%KhTr_use_vert_struct) then
4560 if (CS%full_depth_khtr_min) then
4570 do K=2,nz+1
4580 do J=js-1,je
4590 do i=is,ie
4600 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) )
4610 Coef_min = I_numitts * dt * (CS%KhTr_min*(G%dx_Cv(i,J)*G%IdyCv(i,J)))
4620 Coef_y(i,J,K) = max(Coef_y(i,J,K), Coef_min)
463 enddo
464 enddo
465 enddo
4660 do k=2,nz+1
4670 do j=js,je
4680 do I=is-1,ie
4690 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) )
4700 Coef_min = I_numitts * dt * (CS%KhTr_min*(G%dy_Cu(I,j)*G%IdxCu(I,j)))
4710 Coef_x(I,j,K) = max(Coef_x(I,j,K), Coef_min)
472 enddo
473 enddo
474 enddo
475 else
4760 do K=2,nz+1
4770 do J=js-1,je
4780 do i=is,ie
4790 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) )
480 enddo
481 enddo
482 enddo
4830 do k=2,nz+1
4840 do j=js,je
4850 do I=is-1,ie
4860 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) )
487 enddo
488 enddo
489 enddo
490 endif
491 endif
492
4930 do itt=1,num_itts
4940 if (CS%show_call_tree) call callTree_waypoint("Calling horizontal boundary diffusion (tracer_hordiff)",itt)
4950 if (itt>1) then ! Update halos for subsequent iterations
4960 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
497 endif
498 call hor_bnd_diffusion(G, GV, US, h, Coef_x, Coef_y, I_numitts*dt, Reg, visc, &
4990 CS%hor_bnd_diffusion_CSp)
500 enddo ! itt
501 endif
502
50312 if (CS%use_neutral_diffusion) then
504 !$omp target update from(h)
505
5060 if (CS%show_call_tree) call callTree_waypoint("Calling neutral diffusion coeffs (tracer_hordiff)")
507
5080 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
509 ! We are assuming that neutral surfaces do not evolve (much) as a result of multiple
510 !horizontal diffusion iterations. Otherwise the call to neutral_diffusion_calc_coeffs()
511 ! would be inside the itt-loop. -AJA
512
5130 if (associated(tv%p_surf)) then
5140 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp, p_surf=tv%p_surf)
515 else
5160 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp)
517 endif
518
5190 do k=1,nz+1
5200 do J=js-1,je
5210 do i=is,ie
5220 Coef_y(i,J,K) = I_numitts * khdt_y(i,J)
523 enddo
524 enddo
525 enddo
5260 do k=1,nz+1
5270 do j=js,je
5280 do I=is-1,ie
5290 Coef_x(I,j,K) = I_numitts * khdt_x(I,j)
530 enddo
531 enddo
532 enddo
5330 if (CS%KhTr_use_vert_struct) then
5340 do K=2,nz+1
5350 do J=js-1,je
5360 do i=is,ie
5370 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) )
538 enddo
539 enddo
540 enddo
5410 do k=2,nz+1
5420 do j=js,je
5430 do I=is-1,ie
5440 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) )
545 enddo
546 enddo
547 enddo
548 endif
549
5500 do itt=1,num_itts
5510 if (CS%show_call_tree) call callTree_waypoint("Calling neutral diffusion (tracer_hordiff)",itt)
5520 if (itt>1) then ! Update halos for subsequent iterations
5530 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass)
5540 if (CS%recalc_neutral_surf) then
5550 if (associated(tv%p_surf)) then
556 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp, &
5570 p_surf=tv%p_surf)
558 else
5590 call neutral_diffusion_calc_coeffs(G, GV, US, h, tv%T, tv%S, visc, CS%neutral_diffusion_CSp)
560 endif
561 endif
562 endif
5630 call neutral_diffusion(G, GV, h, Coef_x, Coef_y, I_numitts*dt, Reg, US, CS%neutral_diffusion_CSp)
564 enddo ! itt
565
566 else ! following if not using neutral diffusion, but instead along-surface diffusion
567
56812 if (CS%show_call_tree) call callTree_waypoint("Calculating horizontal diffusion (tracer_hordiff)")
569 !$omp target enter data map(alloc: dTr, Ihdxdy)
570 !$omp target enter data map(to: Coef_x, Coef_y) if(CS%use_hor_bnd_diffusion) ! copy from non-ported loops above
571 !$omp target enter data map(alloc: Coef_x, Coef_y) if(.not.CS%use_hor_bnd_diffusion)
57224 do itt=1,num_itts
57312 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass, omp_offload=.true.)
574 ! loop should probably be reordered
575912 do k=1,nz
576900 scale = I_numitts
577900 if (CS%Diffuse_ML_interior) then
5780 if (k<=GV%nkml) then
5790 if (CS%ML_KhTr_scale <= 0.0) cycle
5800 scale = I_numitts * CS%ML_KhTr_scale
581 endif
5820 if ((k>GV%nkml) .and. (k<=GV%nk_rho_varies)) cycle
583 endif
584
585900 do concurrent (J=js-1:je, i=is:ie)
586 Coef_y(i,J,1) = ((scale * khdt_y(i,J))*2.0*(h(i,j,k)*h(i,j+1,k))) / &
5876696900 (h(i,j,k)+h(i,j+1,k)+h_neglect)
588 enddo
589
590900 do concurrent (j=js:je)
59154000 do concurrent (I=is-1:ie)
592 Coef_x(I,j,1) = ((scale * khdt_x(I,j))*2.0*(h(i,j,k)*h(i+1,j,k))) / &
5936588000 (h(i,j,k)+h(i+1,j,k)+h_neglect)
594 enddo
595
59654900 do concurrent (i=is:ie)
5976534000 Ihdxdy(i,j) = G%IareaT(i,j) / (h(i,j,k)+h_neglect)
598 enddo
599 enddo
600
6013612 do m=1,ntr
6022700 do concurrent (j=js:je, i=is:ie)
603 dTr(i,j) = Ihdxdy(i,j) * &
604 ( ((Coef_x(I-1,j,1) * (Reg%Tr(m)%t(i-1,j,k) - Reg%Tr(m)%t(i,j,k))) - &
605 (Coef_x(I,j,1) * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i+1,j,k)))) + &
606 ((Coef_y(i,J-1,1) * (Reg%Tr(m)%t(i,j-1,k) - Reg%Tr(m)%t(i,j,k))) - &
60719766700 (Coef_y(i,J,1) * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i,j+1,k)))) )
608 enddo
6092700 if (associated(Reg%Tr(m)%df_x)) then ; do j=js,je ; do I=G%IscB,G%IecB
610 Reg%Tr(m)%df_x(I,j,k) = Reg%Tr(m)%df_x(I,j,k) + Coef_x(I,j,1) &
6110 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i+1,j,k)) * Idt
612 enddo ; enddo ; endif
6132700 if (associated(Reg%Tr(m)%df_y)) then ; do J=G%JscB,G%JecB ; do i=is,ie
614 Reg%Tr(m)%df_y(i,J,k) = Reg%Tr(m)%df_y(i,J,k) + Coef_y(i,J,1) &
6150 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i,j+1,k)) * Idt
616 enddo ; enddo ; endif
6172700 if (associated(Reg%Tr(m)%df2d_x)) then ; do j=js,je ; do I=G%IscB,G%IecB
618 Reg%Tr(m)%df2d_x(I,j) = Reg%Tr(m)%df2d_x(I,j) + Coef_x(I,j,1) &
6190 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i+1,j,k)) * Idt
620 enddo ; enddo ; endif
6212700 if (associated(Reg%Tr(m)%df2d_y)) then ; do J=G%JscB,G%JecB ; do i=is,ie
622 Reg%Tr(m)%df2d_y(i,J) = Reg%Tr(m)%df2d_y(i,J) + Coef_y(i,J,1) &
6230 * (Reg%Tr(m)%t(i,j,k) - Reg%Tr(m)%t(i,j+1,k)) * Idt
624 enddo ; enddo ; endif
625900 do concurrent (j=js:je, i=is:ie)
62619766700 Reg%Tr(m)%t(i,j,k) = Reg%Tr(m)%t(i,j,k) + dTr(i,j)
627 enddo
628 enddo
629
630 enddo ! End of k loop.
631
632 ! Do user controlled underflow of the tracer concentrations.
63360 do m=1,ntr ; if (Reg%Tr(m)%conc_underflow > 0.0) then
6340 do concurrent (k=1:nz, j=js:je, i=is:ie, abs(Reg%Tr(m)%t(i,j,k)) < Reg%Tr(m)%conc_underflow)
6350 Reg%Tr(m)%t(i,j,k) = 0.0
636 enddo
637 endif ; enddo
638
639 enddo ! End of "while" loop.
640 !$omp target exit data map(release: dTr, Ihdxdy, Coef_x, Coef_y)
641 endif ! endif for CS%use_neutral_diffusion
64212 call cpu_clock_end(id_clock_diffuse)
643
64412 if (CS%Diffuse_ML_interior) then
6450 if (CS%show_call_tree) call callTree_waypoint("Calling epipycnal_ML_diff (tracer_hordiff)")
6460 if (CS%debug) call MOM_tracer_chksum("Before epipycnal diff ", Reg, G)
647
6480 call cpu_clock_begin(id_clock_epimix)
649 call tracer_epipycnal_ML_diff(h, dt, Reg%Tr, ntr, khdt_x, khdt_y, G, GV, US, &
6500 CS, tv, num_itts)
6510 call cpu_clock_end(id_clock_epimix)
652 endif
653 !$ do m = 1, Reg%ntr
654 !$omp target exit data map(from: Reg%Tr(m)%t)
655 !$omp target exit data map(from: Reg%Tr(m)%df_x) if(associated(Reg%Tr(m)%df_x))
656 !$omp target exit data map(from: Reg%Tr(m)%df_y) if(associated(Reg%Tr(m)%df_y))
657 !$omp target exit data map(from: Reg%Tr(m)%df2d_x) if(associated(Reg%Tr(m)%df2d_x))
658 !$omp target exit data map(from: Reg%Tr(m)%df2d_y) if(associated(Reg%Tr(m)%df2d_y))
659 !$ enddo
660 !$omp target exit data map(release: Reg%Tr, Reg)
661
66212 if (CS%debug) call MOM_tracer_chksum("After tracer diffusion ", Reg, G)
663
664 ! post diagnostics for 2d tracer diffusivity
66512 if (CS%id_KhTr_u > 0) then
666 !$omp target exit data map(from: Kh_u)
6670 do j=js,je ; do I=is-1,ie
6680 Kh_u(I,j,:) = G%mask2dCu(I,j)*Kh_u(I,j,1)
669 enddo ; enddo
6700 if (CS%KhTr_use_vert_struct) then
6710 do K=2,nz+1
6720 do j=js,je
6730 do I=is-1,ie
6740 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) )
675 enddo
676 enddo
677 enddo
678 endif
6790 call post_data(CS%id_KhTr_u, Kh_u, CS%diag)
680 endif
68112 if (CS%id_KhTr_v > 0) then
682 !$omp target exit data map(from: Kh_v)
6830 do J=js-1,je ; do i=is,ie
6840 Kh_v(i,J,:) = G%mask2dCv(i,J)*Kh_v(i,J,1)
685 enddo ; enddo
6860 if (CS%KhTr_use_vert_struct) then
6870 do K=2,nz+1
6880 do J=js-1,je
6890 do i=is,ie
6900 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) )
691 enddo
692 enddo
693 enddo
694 endif
6950 call post_data(CS%id_KhTr_v, Kh_v, CS%diag)
696 endif
69712 if (CS%id_KhTr_h > 0) then
698 !$omp target exit data map(from: Kh_u, Kh_v)
6990 Kh_h(:,:,:) = 0.0
7000 do j=js,je ; do I=is-1,ie
7010 Kh_u(I,j,1) = G%mask2dCu(I,j)*Kh_u(I,j,1)
702 enddo ; enddo
7030 do J=js-1,je ; do i=is,ie
7040 Kh_v(i,J,1) = G%mask2dCv(i,J)*Kh_v(i,J,1)
705 enddo ; enddo
706
7070 do j=js,je ; do i=is,ie
708 normalize = 1.0 / ((G%mask2dCu(I-1,j)+G%mask2dCu(I,j)) + &
7090 (G%mask2dCv(i,J-1)+G%mask2dCv(i,J)) + 1.0e-37)
710 Kh_h(i,j,:) = normalize*G%mask2dT(i,j)*((Kh_u(I-1,j,1)+Kh_u(I,j,1)) + &
7110 (Kh_v(i,J-1,1)+Kh_v(i,J,1)))
7120 if (CS%KhTr_use_vert_struct) then
7130 do K=2,nz+1
714 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)) + &
7150 (Kh_v(i,J-1,1)+Kh_v(i,J,1)))
716 enddo
717 endif
718 enddo ; enddo
7190 call post_data(CS%id_KhTr_h, Kh_h, CS%diag)
720 endif
721
722 !$omp target update from(khdt_x, khdt_y) if(CS%debug .or. CS%id_khdt_x>0 .or. CS%id_khdt_y>0)
723 !$omp target exit data map(release: khdt_x, khdt_y, Kh_u, Kh_v) map(release: CS)
724
72512 if (CS%debug) then
726 call uvchksum("After tracer diffusion khdt_[xy]", khdt_x, khdt_y, &
727 G%HI, haloshift=0, symmetric=.true., unscale=US%L_to_m**2, &
7280 scalar_pair=.true.)
729 endif
730
73112 if (CS%id_khdt_x > 0) call post_data(CS%id_khdt_x, khdt_x, CS%diag)
73212 if (CS%id_khdt_y > 0) call post_data(CS%id_khdt_y, khdt_y, CS%diag)
733
73412 if (CS%show_call_tree) call callTree_leave("tracer_hordiff()")
735
73612end subroutine tracer_hordiff
737
738!> This subroutine does epipycnal diffusion of all tracers between the mixed
739!! and buffer layers and the interior, using the diffusivity in CS%KhTr.
740!! Multiple iterations are used (if necessary) so that there is no limit on the
741!! acceptable time increment.
7420subroutine tracer_epipycnal_ML_diff(h, dt, Tr, ntr, khdt_epi_x, khdt_epi_y, G, &
743 GV, US, CS, tv, num_itts)
744 type(ocean_grid_type), intent(inout) :: G !< ocean grid structure
745 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
746 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)), intent(in) :: h !< layer thickness [H ~> m or kg m-2]
747 real, intent(in) :: dt !< time step [T ~> s]
748 type(tracer_type), intent(inout) :: Tr(:) !< tracer array
749 integer, intent(in) :: ntr !< number of tracers
750 real, dimension(SZIB_(G),SZJ_(G)), intent(in) :: khdt_epi_x !< Zonal epipycnal diffusivity times
751 !! a time step and the ratio of the open face width over
752 !! the distance between adjacent tracer points [L2 ~> m2]
753 real, dimension(SZI_(G),SZJB_(G)), intent(in) :: khdt_epi_y !< Meridional epipycnal diffusivity times
754 !! a time step and the ratio of the open face width over
755 !! the distance between adjacent tracer points [L2 ~> m2]
756 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
757 type(tracer_hor_diff_CS), intent(inout) :: CS !< module control structure
758 type(thermo_var_ptrs), intent(in) :: tv !< thermodynamic structure
759 integer, intent(in) :: num_itts !< number of iterations (usually=1)
760
761
762 real, dimension(SZI_(G), SZJ_(G)) :: &
7630 Rml_max ! The maximum coordinate density within the mixed layer [R ~> kg m-3].
764 real, dimension(SZI_(G), SZJ_(G), max(1,GV%nk_rho_varies)) :: &
7650 rho_coord ! The coordinate density that is used to mix along [R ~> kg m-3].
766
767 ! The naming mnemonic is a=above,b=below,L=Left,R=Right,u=u-point,v=v-point.
768 ! These are 1-D arrays of pointers to 2-d arrays to minimize memory usage.
769 real, dimension(:,:,:), allocatable :: &
7700 deep_wt_Lu, deep_wt_Ru, & ! The relative weighting of the deeper of a pair [nondim].
7710 hP_Lu, hP_Ru, & ! The total thickness on each side for each pair [H ~> m or kg m-2].
7720 deep_wt_Lv, deep_wt_Rv, & ! The relative weighting of the deeper of a pair [nondim].
7730 hP_Lv, hP_Rv ! The total thickness on each side for each pair [H ~> m or kg m-2].
774
775 integer, dimension(:,:,:), allocatable :: &
7760 k0b_Lu, k0a_Lu, & ! The original k-indices of the layers that participate
7770 k0b_Ru, k0a_Ru, & ! in each pair of mixing at u-faces.
7780 k0b_Lv, k0a_Lv, & ! The original k-indices of the layers that participate
7790 k0b_Rv, k0a_Rv ! in each pair of mixing at v-faces.
780
781 real, dimension(SZI_(G),SZJ_(G),SZK_(GV)) :: &
7820 tr_flux_N, & ! The tracer flux through the northern face [conc H L2 ~> conc m3 or conc kg]
7830 tr_flux_S, & ! The tracer flux through the southern face [conc H L2 ~> conc m3 or conc kg]
7840 tr_flux_E, & ! The tracer flux through the eastern face [conc H L2 ~> conc m3 or conc kg]
7850 tr_flux_W, & ! The tracer flux through the western face [conc H L2 ~> conc m3 or conc kg]
7860 tr_flux_conv ! The flux convergence of tracers [conc H L2 ~> conc m3 or conc kg]
787
788 ! The following 3-d arrays were created in 2014 in MOM6 PR#12 to facilitate openMP threading
789 ! on an i-loop, which might have been ill advised.
790 real, dimension(SZI_(G),SZJB_(G),SZK_(GV)*2) :: &
7910 Tr_flux_3d, & ! The tracer flux through pairings at meridional faces [conc H L2 ~> conc m3 or conc kg]
7920 Tr_adj_vert_L, & ! Vertical adjustments to which layer the fluxes go into in the southern
793 ! columns at meridional face [conc H L2 ~> conc m3 or conc kg]
7940 Tr_adj_vert_R ! Vertical adjustments to which layer the fluxes go into in the northern
795 ! columns at meridional face [conc H L2 ~> conc m3 or conc kg]
796
797 real, dimension(SZI_(G),SZK_(GV), SZJ_(G)) :: &
7980 rho_srt, & ! The density of each layer of the sorted columns [R ~> kg m-3].
7990 h_srt ! The thickness of each layer of the sorted columns [H ~> m or kg m-2].
800 integer, dimension(SZI_(G),SZK_(GV), SZJ_(G)) :: &
8010 k0_srt ! The original k-index that each layer of the sorted column corresponds to.
802
803 real, dimension(SZK_(GV)) :: &
8040 h_demand_L, & ! The thickness in the left column that is demanded to match the thickness
805 ! in the counterpart [H ~> m or kg m-2].
8060 h_demand_R, & ! The thickness in the right column that is demanded to match the thickness
807 ! in the counterpart [H ~> m or kg m-2].
8080 h_used_L, & ! The summed thickness from the left column that has actually been used [H ~> m or kg m-2]
8090 h_used_R, & ! The summed thickness from the right columns that has actually been used [H ~> m or kg m-2]
8100 h_supply_frac_L, & ! The fraction of the demanded thickness that can actually be supplied
811 ! from a layer on the left [nondim].
8120 h_supply_frac_R ! The fraction of the demanded thickness that can actually be supplied
813 ! from a layer on the right [nondim].
814 integer, dimension(SZI_(G), SZJ_(G)) :: &
8150 num_srt, & ! The number of layers that are sorted in each column.
8160 k_end_srt, & ! The maximum index in each column that might need to be
817 ! sorted, based on neighboring values of max_kRho
8180 max_kRho ! The index of the layer whose target density is just denser
819 ! than the densest part of the mixed layer.
820 integer, dimension(SZJ_(G)) :: &
8210 max_srt ! The maximum value of num_srt in a k-row.
822 integer, dimension(SZIB_(G), SZJ_(G)) :: &
8230 nPu ! The number of epipycnal pairings at each u-point.
824 integer, dimension(SZI_(G), SZJB_(G)) :: &
8250 nPv ! The number of epipycnal pairings at each v-point.
826 real :: h_exclude ! A thickness that layers must attain to be considered
827 ! for inclusion in mixing [H ~> m or kg m-2].
828 real :: Idt ! The inverse of the time step [T-1 ~> s-1].
829 real :: I_maxitt ! The inverse of the maximum number of iterations [nondim]
830 real :: rho_pair, rho_a, rho_b ! Temporary densities [R ~> kg m-3].
831 real :: Tr_min_face ! The minimum tracer concentration associated with a pairing [Conc]
832 real :: Tr_max_face ! The maximum tracer concentration associated with a pairing [Conc]
833 real :: Tr_La, Tr_Lb ! The 2 left-side tracer concentrations that might be associated with a pairing [Conc]
834 real :: Tr_Ra, Tr_Rb ! The 2 right-side tracer concentrations that might be associated with a pairing [Conc]
835 real :: Tr_av_L ! The average tracer concentrations on the left side of a pairing [Conc].
836 real :: Tr_av_R ! The average tracer concentrations on the right side of a pairing [Conc].
837 real :: Tr_flux ! The tracer flux from left to right in a pair [conc H L2 ~> conc m3 or conc kg].
838 real :: Tr_adj_vert ! A downward vertical adjustment to Tr_flux between the two cells that
839 ! make up one side of the pairing [conc H L2 ~> conc m3 or conc kg].
840 real :: h_L, h_R ! Thicknesses to the left and right [H ~> m or kg m-2].
841 real :: wt_a, wt_b ! Fractional weights of layers above and below [nondim].
842 real :: vol ! A cell volume or mass [H L2 ~> m3 or kg].
843
844 ! The total number of pairings is usually much less than twice the number of layers, but
845 ! the memory in these 1-d columns of pairings can be allocated generously for safety.
846 integer, dimension(SZK_(GV)*2) :: &
8470 kbs_Lp, & ! The sorted indices of the Left and Right columns for
8480 kbs_Rp ! each pairing.
849 logical, dimension(SZK_(GV)*2) :: &
8500 left_set, & ! If true, the left or right point determines the density of
8510 right_set ! of the trio. If densities are exactly equal, both are true.
852
853 real :: tmp ! A temporary variable used in swaps [various]
8540 real :: p_ref_cv(SZI_(G),SZJ_(G)) ! The reference pressure for the coordinate density [R L2 T-2 ~> Pa]
855
856 integer, dimension(2,2) :: EOSdom ! The i-computational domain for the equation of state
857 integer :: k_max, k_min, k_test, itmp
858 integer :: i, j, k, k2, m, is, ie, js, je, nz, nkmb
859 integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB, k_size
860 integer :: kL, kR, kLa, kLb, kRa, kRb, nP, itt, ns, max_itt
861 integer :: PEmax_kRho
862
8630 is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec ; nz = GV%ke
8640 isd = G%isd ; ied = G%ied ; jsd = G%jsd ; jed = G%jed
8650 IsdB = G%IsdB ; IedB = G%IedB ; JsdB = G%JsdB ; JedB = G%JedB
8660 Idt = 1.0 / dt
8670 nkmb = GV%nk_rho_varies
868
8690 if (num_itts <= 1) then
8700 max_itt = 1 ; I_maxitt = 1.0
871 else
8720 max_itt = num_itts ; I_maxitt = 1.0 / (real(max_itt))
873 endif
874
875 !$omp target enter data map(alloc: rho_coord, Rml_max, max_kRho, rho_srt, k0_srt, num_srt, &
876 !$omp h_srt, p_ref_cv, k_end_srt, max_srt)
877
8780 do concurrent (j=jsd:jed, i=isd:ied)
8790 p_ref_cv(i,j) = tv%P_Ref
880 enddo
881
8820 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass, omp_offload=.true.)
883 ! Determine which layers the mixed- and buffer-layers map into...
884 !$OMP parallel do default(shared)
8850 do k=1,nkmb
886 call calculate_density(tv%T(:,:,k), tv%S(:,:,k), p_ref_cv, rho_coord(:,:,k), &
8870 tv%eqn_of_state)
888 enddo
889 !$omp target exit data map(release: p_ref_cv)
890
8910 do concurrent (j=js-2:je+2, i=is-2:ie+2)
8920 Rml_max(i,j) = rho_coord(i,j,1)
8930 num_srt(i,j) = 0 ; max_kRho(i,j) = 0
894 enddo
8950 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))
8960 Rml_max(i,j) = rho_coord(i,j,k)
897 enddo ; enddo
898
899 ! Use bracketing and bisection to find the k-level that the densest of the
900 ! mixed and buffer layer corresponds to, such that:
901 ! GV%Rlay(max_kRho-1) < Rml_max <= GV%Rlay(max_kRho)
9020 do concurrent (j=js-2:je+2, i=is-2:ie+2, G%mask2dT(i,j) > 0.0)
9030 if ((Rml_max(i,j) > GV%Rlay(nz)) .or. (nkmb+1 > nz)) then ; max_kRho(i,j) = nz+1
9040 elseif ((Rml_max(i,j) <= GV%Rlay(nkmb+1)) .or. (nkmb+2 > nz)) then ; max_kRho(i,j) = nkmb+1
905 else
9060 k_min = nkmb+2 ; k_max = nz
9070 do
9080 k_test = (k_min + k_max) / 2
9090 if (Rml_max(i,j) <= GV%Rlay(k_test-1)) then ; k_max = k_test-1
9100 elseif (GV%Rlay(k_test) < Rml_max(i,j)) then ; k_min = k_test+1
9110 else ; max_kRho(i,j) = k_test ; exit ; endif
912
9130 if (k_min == k_max) then ; max_kRho(i,j) = k_max ; exit ; endif
914 enddo
915 endif
916 enddo
917
9180 PEmax_kRho = 0
9190 do concurrent (j=js-1:je+1, i=is-1:ie+1) DO_LOCALITY(reduce(max:PEmax_kRho))
920 k_end_srt(i,j) = max(max_kRho(i,j), max_kRho(i-1,j), max_kRho(i+1,j), &
9210 max_kRho(i,j-1), max_kRho(i,j+1))
9220 if (PEmax_kRho < k_end_srt(i,j)) PEmax_kRho = k_end_srt(i,j)
923 enddo
9240 if (PEmax_kRho > nz) PEmax_kRho = nz ! PEmax_kRho could have been nz+1.
925
9260 h_exclude = 10.0*(GV%Angstrom_H + GV%H_subroundoff)
927
9280 do concurrent (j=js-1:je+1) DO_LOCALITY(local(k, ns))
9290 do k=1,nkmb ; do concurrent (i=is-1:ie+1, G%mask2dT(i,j) > 0.0) DO_LOCALITY(local(ns))
9300 if (h(i,j,k) > h_exclude) then
9310 num_srt(i,j) = num_srt(i,j) + 1 ; ns = num_srt(i,j)
9320 k0_srt(i,ns,j) = k
9330 rho_srt(i,ns,j) = rho_coord(i,j,k)
9340 h_srt(i,ns,j) = h(i,j,k)
935 endif
936 enddo ; enddo
9370 do k=nkmb+1,PEmax_kRho ; do concurrent (i=is-1:ie+1, G%mask2dT(i,j) > 0.0) DO_LOCALITY(local(ns))
9380 if ((k<=k_end_srt(i,j)) .and. (h(i,j,k) > h_exclude)) then
9390 num_srt(i,j) = num_srt(i,j) + 1 ; ns = num_srt(i,j)
9400 k0_srt(i,ns,j) = k
9410 rho_srt(i,ns,j) = GV%Rlay(k)
9420 h_srt(i,ns,j) = h(i,j,k)
943 endif
944 enddo ; enddo
945 enddo
946 !$omp target exit data map(release: rho_coord, Rml_max, k_end_srt)
947 ! Sort each column by increasing density. This should already be close,
948 ! and the size of the arrays are small, so straight insertion is used.
9490 do concurrent (j=js-1:je+1, i=is-1:ie+1)
9500 do k=2,num_srt(i,j) ; if (rho_srt(i,k,j) < rho_srt(i,k-1,j)) then
951 ! The last segment needs to be shuffled earlier in the list.
9520 do k2 = k,2,-1 ; if (rho_srt(i,k2,j) < rho_srt(i,k2-1,j)) then
9530 itmp = k0_srt(i,k2-1,j) ; k0_srt(i,k2-1,j) = k0_srt(i,k2,j) ; k0_srt(i,k2,j) = itmp
9540 tmp = rho_srt(i,k2-1,j) ; rho_srt(i,k2-1,j) = rho_srt(i,k2,j) ; rho_srt(i,k2,j) = tmp
9550 tmp = h_srt(i,k2-1,j) ; h_srt(i,k2-1,j) = h_srt(i,k2,j) ; h_srt(i,k2,j) = tmp
956 endif ; enddo
957 endif ; enddo
958 enddo
9590 do concurrent (j=js-1:je+1) DO_LOCALITY(local(itmp))
960 ! max_srt(j) = 0
9610 itmp = 0
962 ! nvfortran do concurrent cannot reduce array elements
9630 do concurrent (i=is-1:ie+1) DO_LOCALITY(reduce(max:itmp))
9640 itmp = max(itmp, num_srt(i,j))
965 enddo
9660 max_srt(j) = itmp
967 enddo
9680 k_size = 1
9690 do concurrent (j=js-1:je+1) DO_LOCALITY(reduce(max:k_size))
9700 k_size = max(k_size, 2*max_srt(j))
971 enddo
9720 allocate(k0a_Lu(IsdB:iedB,k_size,jsd:jed))
9730 allocate(k0a_Ru(IsdB:iedB,k_size,jsd:jed))
9740 allocate(k0b_Lu(IsdB:iedB,k_size,jsd:jed))
9750 allocate(k0b_Ru(IsdB:iedB,k_size,jsd:jed))
9760 allocate(deep_wt_Lu(IsdB:iedB,k_size,jsd:jed))
9770 allocate(deep_wt_Ru(IsdB:iedB,k_size,jsd:jed))
9780 allocate(hP_Lu(IsdB:iedB,k_size,jsd:jed))
9790 allocate(hP_Ru(IsdB:iedB,k_size,jsd:jed))
980 !$omp target enter data map(alloc: deep_wt_Lu, deep_wt_Ru, hP_Lu, hP_Ru, k0a_Lu, k0a_Ru, k0b_Lu, &
981 !$omp k0b_Ru)
982 !$omp target enter data map(alloc: nPu)
983!$OMP target teams loop collapse(2) &
984!$OMP private(h_demand_L,h_used_L,h_demand_R,h_used_R, &
985!$OMP kR,kL,nP,rho_pair,kbs_Lp,kbs_Rp,rho_a,rho_b, &
986!$OMP wt_b,left_set,right_set,h_supply_frac_R, &
987!$OMP h_supply_frac_L, k)
9880 do j=js,je ; do I=is-1,ie ; if (G%mask2dCu(I,j) > 0.0) then
989 ! Set up the pairings for fluxes through the zonal faces.
990
9910 do k=1,num_srt(i,j) ; h_demand_L(k) = 0.0 ; h_used_L(k) = 0.0 ; enddo
9920 do k=1,num_srt(i+1,j) ; h_demand_R(k) = 0.0 ; h_used_R(k) = 0.0 ; enddo
993
994 ! First merge the left and right lists into a single, sorted list.
995
996 ! Discard any layers that are lighter than the lightest in the other
997 ! column. They can only participate in mixing as the lighter part of a
998 ! pair of points.
9990 if (rho_srt(i,1,j) < rho_srt(i+1,1,j)) then
10000 kR = 1
10010 do kL=2,num_srt(i,j) ; if (rho_srt(i,kL,j) >= rho_srt(i+1,1,j)) exit ; enddo
10020 elseif (rho_srt(i+1,1,j) < rho_srt(i,1,j)) then
10030 kL = 1
10040 do kR=2,num_srt(i+1,j) ; if (rho_srt(i+1,kR,j) >= rho_srt(i,1,j)) exit ; enddo
1005 else
10060 kL = 1 ; kR = 1
1007 endif
10080 nP = 0
10090 do ! Loop to accumulate pairs of columns.
10100 if ((kL > num_srt(i,j)) .or. (kR > num_srt(i+1,j))) exit
1011
10120 if (rho_srt(i,kL,j) > rho_srt(i+1,kR,j)) then
1013 ! The right point is lighter and defines the density for this trio.
10140 nP = nP+1 ; k = nP
10150 rho_pair = rho_srt(i+1,kR,j)
1016
10170 k0b_Lu(I,k,j) = k0_srt(i,kL,j) ; k0b_Ru(I,k,j) = k0_srt(i+1,kR,j)
10180 k0a_Lu(I,k,j) = k0_srt(i,kL-1,j) ; k0a_Ru(I,k,j) = k0b_Ru(I,k,j)
10190 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1020
10210 rho_a = rho_srt(i,kL-1,j) ; rho_b = rho_srt(i,kL,j)
10220 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
10230 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
10240 deep_wt_Lu(I,k,j) = wt_b ; deep_wt_Ru(I,k,j) = 1.0
1025
10260 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i+1,kR,j) * wt_b
10270 h_demand_L(kL-1) = h_demand_L(kL-1) + 0.5*h_srt(i+1,kR,j) * (1.0-wt_b)
1028
10290 kR = kR+1 ; left_set(k) = .false. ; right_set(k) = .true.
10300 elseif (rho_srt(i,kL,j) < rho_srt(i+1,kR,j)) then
1031 ! The left point is lighter and defines the density for this trio.
10320 nP = nP+1 ; k = nP
10330 rho_pair = rho_srt(i,kL,j)
10340 k0b_Lu(I,k,j) = k0_srt(i,kL,j) ; k0b_Ru(I,k,j) = k0_srt(i+1,kR,j)
10350 k0a_Lu(I,k,j) = k0b_Lu(I,k,j) ; k0a_Ru(I,k,j) = k0_srt(i+1,kR-1,j)
1036
10370 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1038
10390 rho_a = rho_srt(i+1,kR-1,j) ; rho_b = rho_srt(i+1,kR,j)
10400 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
10410 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
10420 deep_wt_Lu(I,k,j) = 1.0 ; deep_wt_Ru(I,k,j) = wt_b
1043
10440 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j) * wt_b
10450 h_demand_R(kR-1) = h_demand_R(kR-1) + 0.5*h_srt(i,kL,j) * (1.0-wt_b)
1046
10470 kL = kL+1 ; left_set(k) = .true. ; right_set(k) = .false.
10480 elseif ((k0_srt(i,kL,j) <= nkmb) .or. (k0_srt(i+1,kR,j) <= nkmb)) then
1049 ! The densities are exactly equal and one layer is above the interior.
10500 nP = nP+1 ; k = nP
10510 k0b_Lu(I,k,j) = k0_srt(i,kL,j) ; k0b_Ru(I,k,j) = k0_srt(i+1,kR,j)
10520 k0a_Lu(I,k,j) = k0b_Lu(I,k,j) ; k0a_Ru(I,k,j) = k0b_Ru(I,k,j)
10530 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
10540 deep_wt_Lu(I,k,j) = 1.0 ; deep_wt_Ru(I,k,j) = 1.0
1055
10560 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i+1,kR,j)
10570 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1058
10590 kL = kL+1 ; kR = kR+1 ; left_set(k) = .true. ; right_set(k) = .true.
1060 else ! The densities are exactly equal and in the interior.
1061 ! Mixing in this case has already occurred, so accumulate the thickness
1062 ! demanded for that mixing and skip onward.
10630 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i+1,kR,j)
10640 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1065
10660 kL = kL+1 ; kR = kR+1
1067 endif
1068 enddo ! Loop to accumulate pairs of columns.
10690 nPu(I,j) = nP ! This is the number of active pairings.
1070
1071 ! Determine what fraction of the thickness "demand" can be supplied.
10720 do k=1,num_srt(i+1,j)
10730 h_supply_frac_R(k) = 1.0
10740 if (h_demand_R(k) > 0.5*h_srt(i+1,k,j)) &
10750 h_supply_frac_R(k) = 0.5*h_srt(i+1,k,j) / h_demand_R(k)
1076 enddo
10770 do k=1,num_srt(i,j)
10780 h_supply_frac_L(k) = 1.0
10790 if (h_demand_L(k) > 0.5*h_srt(i,k,j)) &
10800 h_supply_frac_L(k) = 0.5*h_srt(i,k,j) / h_demand_L(k)
1081 enddo
1082
1083 ! Distribute the "exported" thicknesses proportionately.
10840 do k=1,nPu(I,j)
10850 kL = kbs_Lp(k) ; kR = kbs_Rp(k)
10860 hP_Lu(I,k,j) = 0.0 ; hP_Ru(I,k,j) = 0.0
10870 if (left_set(k)) then ! Add the contributing thicknesses on the right.
10880 if (deep_wt_Ru(I,k,j) < 1.0) then
10890 hP_Ru(I,k,j) = 0.5*h_srt(i,kL,j) * min(h_supply_frac_R(kR), h_supply_frac_R(kR-1))
10900 wt_b = deep_wt_Ru(I,k,j)
10910 h_used_R(kR-1) = h_used_R(kR-1) + (1.0 - wt_b)*hP_Ru(I,k,j)
10920 h_used_R(kR) = h_used_R(kR) + wt_b*hP_Ru(I,k,j)
1093 else
10940 hP_Ru(I,k,j) = 0.5*h_srt(i,kL,j) * h_supply_frac_R(kR)
10950 h_used_R(kR) = h_used_R(kR) + hP_Ru(I,k,j)
1096 endif
1097 endif
10980 if (right_set(k)) then ! Add the contributing thicknesses on the left.
10990 if (deep_wt_Lu(I,k,j) < 1.0) then
11000 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))
11010 wt_b = deep_wt_Lu(I,k,j)
11020 h_used_L(kL-1) = h_used_L(kL-1) + (1.0 - wt_b)*hP_Lu(I,k,j)
11030 h_used_L(kL) = h_used_L(kL) + wt_b*hP_Lu(I,k,j)
1104 else
11050 hP_Lu(I,k,j) = 0.5*h_srt(i+1,kR,j) * h_supply_frac_L(kL)
11060 h_used_L(kL) = h_used_L(kL) + hP_Lu(I,k,j)
1107 endif
1108 endif
1109 enddo
1110
1111 ! The left-over thickness (at least half the layer thickness) is now
1112 ! added to the thicknesses of the importing columns.
11130 do k=1,nPu(I,j)
11140 if (left_set(k)) hP_Lu(I,k,j) = hP_Lu(I,k,j) + &
11150 (h_srt(i,kbs_Lp(k),j) - h_used_L(kbs_Lp(k)))
11160 if (right_set(k)) hP_Ru(I,k,j) = hP_Ru(I,k,j) + &
11170 (h_srt(i+1,kbs_Rp(k),j) - h_used_R(kbs_Rp(k)))
1118 enddo
1119
1120 endif ; enddo ; enddo ! i- & j- loops over zonal faces.
1121
11220 allocate(deep_wt_Lv(isd:ied,k_size,JsdB:JedB))
11230 allocate(deep_wt_Rv(isd:ied,k_size,JsdB:JedB))
11240 allocate(hP_Lv(isd:ied,k_size,JsdB:JedB))
11250 allocate(hP_Rv(isd:ied,k_size,JsdB:JedB))
11260 allocate(k0a_Lv(isd:ied,k_size,JsdB:JedB))
11270 allocate(k0a_Rv(isd:ied,k_size,JsdB:JedB))
11280 allocate(k0b_Lv(isd:ied,k_size,JsdB:JedB))
11290 allocate(k0b_Rv(isd:ied,k_size,JsdB:JedB))
1130 !$omp target enter data map(alloc: deep_wt_Lv, deep_wt_Rv, hP_Lv, hP_Rv, k0a_Lv, k0a_Rv, k0b_Lv, &
1131 !$omp k0b_Rv)
1132
1133 !$omp target enter data map(alloc: nPv)
1134
1135!$OMP target teams loop collapse(2) &
1136!$OMP private(h_demand_L,h_used_L,h_demand_R,h_used_R, &
1137!$OMP kR,kL,nP,rho_pair,kbs_Lp,kbs_Rp,rho_a,rho_b, &
1138!$OMP wt_b,left_set,right_set,h_supply_frac_R, &
1139!$OMP h_supply_frac_L,k) map(to: num_srt)
11400 do J=js-1,je ; do i=is,ie ; if (G%mask2dCv(i,J) > 0.0) then
1141 ! Set up the pairings for fluxes through the meridional faces.
1142
11430 do k=1,num_srt(i,j) ; h_demand_L(k) = 0.0 ; h_used_L(k) = 0.0 ; enddo
11440 do k=1,num_srt(i,j+1) ; h_demand_R(k) = 0.0 ; h_used_R(k) = 0.0 ; enddo
1145
1146 ! First merge the left and right lists into a single, sorted list.
1147
1148 ! Discard any layers that are lighter than the lightest in the other
1149 ! column. They can only participate in mixing as the lighter part of a
1150 ! pair of points.
11510 if (rho_srt(i,1,j) < rho_srt(i,1,j+1)) then
11520 kR = 1
11530 do kL=2,num_srt(i,j) ; if (rho_srt(i,kL,j) >= rho_srt(i,1,j+1)) exit ; enddo
11540 elseif (rho_srt(i,1,j+1) < rho_srt(i,1,j)) then
11550 kL = 1
11560 do kR=2,num_srt(i,j+1) ; if (rho_srt(i,kR,j+1) >= rho_srt(i,1,j)) exit ; enddo
1157 else
11580 kL = 1 ; kR = 1
1159 endif
11600 nP = 0
11610 do ! Loop to accumulate pairs of columns.
11620 if ((kL > num_srt(i,j)) .or. (kR > num_srt(i,j+1))) exit
1163
11640 if (rho_srt(i,kL,j) > rho_srt(i,kR,j+1)) then
1165 ! The right point is lighter and defines the density for this trio.
11660 nP = nP+1 ; k = nP
11670 rho_pair = rho_srt(i,kR,j+1)
1168
11690 k0b_Lv(i,k,J) = k0_srt(i,kL,j) ; k0b_Rv(i,k,J) = k0_srt(i,kR,j+1)
11700 k0a_Lv(i,k,J) = k0_srt(i,kL-1,j) ; k0a_Rv(i,k,J) = k0b_Rv(i,k,J)
11710 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1172
11730 rho_a = rho_srt(i,kL-1,j) ; rho_b = rho_srt(i,kL,j)
11740 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
11750 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
11760 deep_wt_Lv(I,k,J) = wt_b ; deep_wt_Rv(I,k,J) = 1.0
1177
11780 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i,kR,j+1) * wt_b
11790 h_demand_L(kL-1) = h_demand_L(kL-1) + 0.5*h_srt(i,kR,j+1) * (1.0-wt_b)
1180
11810 kR = kR+1 ; left_set(k) = .false. ; right_set(k) = .true.
11820 elseif (rho_srt(i,kL,j) < rho_srt(i,kR,j+1)) then
1183 ! The left point is lighter and defines the density for this trio.
11840 nP = nP+1 ; k = nP
11850 rho_pair = rho_srt(i,kL,j)
11860 k0b_Lv(i,k,J) = k0_srt(i,kL,j) ; k0b_Rv(i,k,J) = k0_srt(i,kR,j+1)
11870 k0a_Lv(i,k,J) = k0b_Lv(i,k,J) ; k0a_Rv(i,k,J) = k0_srt(i,kR-1,j+1)
1188
11890 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
1190
11910 rho_a = rho_srt(i,kR-1,j+1) ; rho_b = rho_srt(i,kR,j+1)
11920 wt_b = 1.0 ; if (abs(rho_a - rho_b) > abs(rho_pair - rho_a)) &
11930 wt_b = (rho_pair - rho_a) / (rho_b - rho_a)
11940 deep_wt_Lv(I,k,J) = 1.0 ; deep_wt_Rv(I,k,J) = wt_b
1195
11960 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j) * wt_b
11970 h_demand_R(kR-1) = h_demand_R(kR-1) + 0.5*h_srt(i,kL,j) * (1.0-wt_b)
1198
11990 kL = kL+1 ; left_set(k) = .true. ; right_set(k) = .false.
12000 elseif ((k0_srt(i,kL,j) <= nkmb) .or. (k0_srt(i,kR,j+1) <= nkmb)) then
1201 ! The densities are exactly equal and one layer is above the interior.
12020 nP = nP+1 ; k = nP
12030 k0b_Lv(i,k,J) = k0_srt(i,kL,j) ; k0b_Rv(i,k,J) = k0_srt(i,kR,j+1)
12040 k0a_Lv(i,k,J) = k0b_Lv(i,k,J) ; k0a_Rv(i,k,J) = k0b_Rv(i,k,J)
12050 kbs_Lp(k) = kL ; kbs_Rp(k) = kR
12060 deep_wt_Lv(I,k,J) = 1.0 ; deep_wt_Rv(I,k,J) = 1.0
1207
12080 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i,kR,j+1)
12090 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1210
12110 kL = kL+1 ; kR = kR+1 ; left_set(k) = .true. ; right_set(k) = .true.
1212 else ! The densities are exactly equal and in the interior.
1213 ! Mixing in this case has already occurred, so accumulate the thickness
1214 ! demanded for that mixing and skip onward.
12150 h_demand_L(kL) = h_demand_L(kL) + 0.5*h_srt(i,kR,j+1)
12160 h_demand_R(kR) = h_demand_R(kR) + 0.5*h_srt(i,kL,j)
1217
12180 kL = kL+1 ; kR = kR+1
1219 endif
1220 enddo ! Loop to accumulate pairs of columns.
12210 nPv(i,J) = nP ! This is the number of active pairings.
1222
1223 ! Determine what fraction of the thickness "demand" can be supplied.
12240 do k=1,num_srt(i,j+1)
12250 h_supply_frac_R(k) = 1.0
12260 if (h_demand_R(k) > 0.5*h_srt(i,k,j+1)) &
12270 h_supply_frac_R(k) = 0.5*h_srt(i,k,j+1) / h_demand_R(k)
1228 enddo
12290 do k=1,num_srt(i,j)
12300 h_supply_frac_L(k) = 1.0
12310 if (h_demand_L(k) > 0.5*h_srt(i,k,j)) &
12320 h_supply_frac_L(k) = 0.5*h_srt(i,k,j) / h_demand_L(k)
1233 enddo
1234
1235 ! Distribute the "exported" thicknesses proportionately.
12360 do k=1,nPv(i,J)
12370 kL = kbs_Lp(k) ; kR = kbs_Rp(k)
12380 hP_Lv(I,k,J) = 0.0 ; hP_Rv(I,k,J) = 0.0
12390 if (left_set(k)) then ! Add the contributing thicknesses on the right.
12400 if (deep_wt_Rv(I,k,J) < 1.0) then
12410 hP_Rv(I,k,J) = 0.5*h_srt(i,kL,j) * min(h_supply_frac_R(kR), h_supply_frac_R(kR-1))
12420 wt_b = deep_wt_Rv(I,k,J)
12430 h_used_R(kR-1) = h_used_R(kR-1) + (1.0 - wt_b) * hP_Rv(I,k,J)
12440 h_used_R(kR) = h_used_R(kR) + wt_b * hP_Rv(I,k,J)
1245 else
12460 hP_Rv(I,k,J) = 0.5*h_srt(i,kL,j) * h_supply_frac_R(kR)
12470 h_used_R(kR) = h_used_R(kR) + hP_Rv(I,k,J)
1248 endif
1249 endif
12500 if (right_set(k)) then ! Add the contributing thicknesses on the left.
12510 if (deep_wt_Lv(I,k,J) < 1.0) then
12520 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))
12530 wt_b = deep_wt_Lv(I,k,J)
12540 h_used_L(kL-1) = h_used_L(kL-1) + (1.0 - wt_b) * hP_Lv(I,k,J)
12550 h_used_L(kL) = h_used_L(kL) + wt_b * hP_Lv(I,k,J)
1256 else
12570 hP_Lv(I,k,J) = 0.5*h_srt(i,kR,j+1) * h_supply_frac_L(kL)
12580 h_used_L(kL) = h_used_L(kL) + hP_Lv(I,k,J)
1259 endif
1260 endif
1261 enddo
1262
1263 ! The left-over thickness (at least half the layer thickness) is now
1264 ! added to the thicknesses of the importing columns.
12650 do k=1,nPv(i,J)
12660 if (left_set(k)) hP_Lv(I,k,J) = hP_Lv(I,k,J) + &
12670 (h_srt(i,kbs_Lp(k),j) - h_used_L(kbs_Lp(k)))
12680 if (right_set(k)) hP_Rv(I,k,J) = hP_Rv(I,k,J) + &
12690 (h_srt(i,kbs_Rp(k),j+1) - h_used_R(kbs_Rp(k)))
1270 enddo
1271
1272
1273 endif ; enddo ; enddo ! i- & j- loops over meridional faces.
1274 !$omp target exit data map(release: h_srt, k0_srt)
1275 ! The tracer-specific calculations start here.
1276
1277 !$omp target enter data map(alloc: Tr_flux_3d, Tr_adj_vert_L, Tr_adj_vert_R, tr_flux_N, &
1278 !$omp tr_flux_S, tr_flux_E, tr_flux_W, tr_flux_conv)
1279
12800 do itt=1,max_itt
1281
12820 if (itt > 1) then ! The halos have already been filled if itt==1.
12830 call do_group_pass(CS%pass_t, G%Domain, clock=id_clock_pass, omp_offload=.true.)
1284 endif
12850 do m=1,ntr
1286 ! Zero out tracer tendencies.
12870 if (CS%answer_date <= 20240330) then
12880 do concurrent (k=1:nz, j=jsd:jed, i=isd:ied)
12890 tr_flux_conv(i,j,k) = 0.0
1290 enddo
1291 else
12920 do concurrent (k=1:nz, j=jsd:jed, i=isd:ied)
12930 tr_flux_N(i,j,k) = 0.0 ; tr_flux_S(i,j,k) = 0.0
12940 tr_flux_E(i,j,k) = 0.0 ; tr_flux_W(i,j,k) = 0.0
1295 enddo
1296 endif
12970 do concurrent (k=1:2*nz, J=JsdB:JedB, i=isd:ied)
12980 tr_flux_3d(i,j,k) = 0.0
12990 tr_adj_vert_R(i,j,k) = 0.0 ; tr_adj_vert_L(i,j,k) = 0.0
1300 enddo
1301
1302 ! collapse(2) is reproducible for CS%answer_date > 20240330 but not <= 20240330.
1303 ! Do concurrent around j-loop doesn't seem to do the right thing.
1304 !$omp target teams loop collapse(2) private(Tr_min_face,Tr_max_face,kLa,kLb,kRa,kRb, &
1305 !$omp Tr_La,Tr_Lb,Tr_Ra,Tr_Rb,Tr_av_L,wt_b,Tr_av_R, &
1306 !$omp h_L,h_R,Tr_flux,Tr_adj_vert,wt_a,vol)
13070 do j=js,je ; do I=is-1,ie ; if (G%mask2dCu(I,j) > 0.0) then
1308 ! Determine the fluxes through the zonal faces.
1309
1310 ! Find the acceptable range of tracer concentration around this face.
13110 if (nPu(I,j) >= 1) then
13120 Tr_min_face = min(Tr(m)%t(i,j,1), Tr(m)%t(i+1,j,1))
13130 Tr_max_face = max(Tr(m)%t(i,j,1), Tr(m)%t(i+1,j,1))
13140 do k=2,nkmb
13150 Tr_min_face = min(Tr_min_face, Tr(m)%t(i,j,k), Tr(m)%t(i+1,j,k))
13160 Tr_max_face = max(Tr_max_face, Tr(m)%t(i,j,k), Tr(m)%t(i+1,j,k))
1317 enddo
1318
1319 ! Include the next two layers denser than the densest buffer layer.
13200 kLa = nkmb+1 ; if (max_kRho(i,j) < nz+1) kLa = max_kRho(i,j)
13210 kLb = kLa ; if (max_kRho(i,j) < nz) kLb = max_kRho(i,j)+1
13220 kRa = nkmb+1 ; if (max_kRho(i+1,j) < nz+1) kRa = max_kRho(i+1,j)
13230 kRb = kRa ; if (max_kRho(i+1,j) < nz) kRb = max_kRho(i+1,j)+1
13240 Tr_La = Tr_min_face ; Tr_Lb = Tr_La ; Tr_Ra = Tr_La ; Tr_Rb = Tr_La
13250 if (h(i,j,kLa) > h_exclude) Tr_La = Tr(m)%t(i,j,kLa)
13260 if ((CS%answer_date <= 20240330) .and. CS%limit_bug) then
13270 if (h(i,j,kLb) > h_exclude) Tr_La = Tr(m)%t(i,j,kLb)
1328 else
13290 if (h(i,j,kLb) > h_exclude) Tr_Lb = Tr(m)%t(i,j,kLb)
1330 endif
13310 if (h(i+1,j,kRa) > h_exclude) Tr_Ra = Tr(m)%t(i+1,j,kRa)
13320 if (h(i+1,j,kRb) > h_exclude) Tr_Rb = Tr(m)%t(i+1,j,kRb)
13330 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
13340 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1335
1336 ! Include all points in diffusive pairings at this face.
13370 do k=1,nPu(I,j)
13380 Tr_Lb = Tr(m)%t(i,j,k0b_Lu(I,k,j))
13390 Tr_Rb = Tr(m)%t(i+1,j,k0b_Ru(I,k,j))
13400 Tr_La = Tr_Lb ; Tr_Ra = Tr_Rb
13410 if (deep_wt_Lu(I,k,j) < 1.0) Tr_La = Tr(m)%t(i,j,k0a_Lu(I,k,j))
13420 if (deep_wt_Ru(I,k,j) < 1.0) Tr_Ra = Tr(m)%t(i+1,j,k0a_Ru(I,k,j))
13430 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
13440 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1345 enddo
1346 endif
1347
13480 do k=1,nPu(I,j)
13490 kLb = k0b_Lu(I,k,j) ; Tr_Lb = Tr(m)%t(i,j,kLb) ; Tr_av_L = Tr_Lb
13500 if (deep_wt_Lu(I,k,j) < 1.0) then
13510 kLa = k0a_Lu(I,k,j) ; Tr_La = Tr(m)%t(i,j,kLa)
13520 wt_b = deep_wt_Lu(I,k,j)
13530 Tr_av_L = wt_b*Tr_Lb + (1.0-wt_b)*Tr_La
1354 endif
1355
13560 kRb = k0b_Ru(I,k,j) ; Tr_Rb = Tr(m)%t(i+1,j,kRb) ; Tr_av_R = Tr_Rb
13570 if (deep_wt_Ru(I,k,j) < 1.0) then
13580 kRa = k0a_Ru(I,k,j) ; Tr_Ra = Tr(m)%t(i+1,j,kRa)
13590 wt_b = deep_wt_Ru(I,k,j)
13600 Tr_av_R = wt_b*Tr_Rb + (1.0-wt_b)*Tr_Ra
1361 endif
1362
13630 h_L = hP_Lu(I,k,j) ; h_R = hP_Ru(I,k,j)
13640 if (CS%answer_date <= 20240330) then
1365 Tr_flux = I_maxitt * khdt_epi_x(I,j) * (Tr_av_L - Tr_av_R) * &
13660 ((2.0 * h_L * h_R) / (h_L + h_R))
1367 else
1368 Tr_flux = I_maxitt * ((2.0 * h_L * h_R) / (h_L + h_R)) * &
13690 khdt_epi_x(I,j) * (Tr_av_L - Tr_av_R)
1370 endif
1371
13720 if (deep_wt_Lu(I,k,j) >= 1.0) then
13730 if (CS%answer_date <= 20240330) then
13740 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - Tr_flux
1375 else
13760 tr_flux_E(i,j,kLb) = tr_flux_E(i,j,kLb) + Tr_flux
1377 endif
1378 else
13790 Tr_adj_vert = 0.0
13800 wt_b = deep_wt_Lu(I,k,j) ; wt_a = 1.0 - wt_b
13810 vol = hP_Lu(I,k,j) * G%areaT(i,j)
1382
1383 ! Ensure that the tracer flux does not drive the tracer values
1384 ! outside of the range Tr_min_face <= Tr <= Tr_max_face, or if it
1385 ! does that the concentration in both contributing pieces exceed
1386 ! this range equally. With down-gradient fluxes and the initial tracer
1387 ! concentrations determining the valid range, the latter condition
1388 ! only enters for large values of the effective diffusive CFL number.
13890 if (Tr_flux > 0.0) then
13900 if (Tr_La < Tr_Lb) then ; if (vol*(Tr_La-Tr_min_face) < Tr_flux) &
1391 Tr_adj_vert = -wt_a * min(Tr_flux - vol * (Tr_La-Tr_min_face), &
13920 (vol*wt_b) * (Tr_Lb - Tr_La))
13930 else ; if (vol*(Tr_Lb-Tr_min_face) < Tr_flux) &
1394 Tr_adj_vert = wt_b * min(Tr_flux - vol * (Tr_Lb-Tr_min_face), &
13950 (vol*wt_a) * (Tr_La - Tr_Lb))
1396 endif
13970 elseif (Tr_flux < 0.0) then
13980 if (Tr_La > Tr_Lb) then ; if (vol * (Tr_max_face-Tr_La) < -Tr_flux) &
1399 Tr_adj_vert = wt_a * min(-Tr_flux - vol * (Tr_max_face-Tr_La), &
14000 (vol*wt_b) * (Tr_La - Tr_Lb))
14010 else ; if (vol*(Tr_max_face-Tr_Lb) < -Tr_flux) &
1402 Tr_adj_vert = -wt_b * min(-Tr_flux - vol * (Tr_max_face-Tr_Lb), &
14030 (vol*wt_a)*(Tr_Lb - Tr_La))
1404 endif
1405 endif
1406
14070 if (CS%answer_date <= 20240330) then
14080 tr_flux_conv(i,j,kLa) = tr_flux_conv(i,j,kLa) - (wt_a*Tr_flux + Tr_adj_vert)
14090 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - (wt_b*Tr_flux - Tr_adj_vert)
1410 else
14110 tr_flux_E(i,j,kLa) = tr_flux_E(i,j,kLa) + (wt_a*Tr_flux + Tr_adj_vert)
14120 tr_flux_E(i,j,kLb) = tr_flux_E(i,j,kLb) + (wt_b*Tr_flux - Tr_adj_vert)
1413 endif
1414 endif
1415
14160 if (deep_wt_Ru(I,k,j) >= 1.0) then
14170 if (CS%answer_date <= 20240330) then
14180 tr_flux_conv(i+1,j,kRb) = tr_flux_conv(i+1,j,kRb) + Tr_flux
1419 else
14200 tr_flux_W(i+1,j,kRb) = tr_flux_W(i+1,j,kRb) + Tr_flux
1421 endif
1422 else
14230 Tr_adj_vert = 0.0
14240 wt_b = deep_wt_Ru(I,k,j) ; wt_a = 1.0 - wt_b
14250 vol = hP_Ru(I,k,j) * G%areaT(i+1,j)
1426
1427 ! Ensure that the tracer flux does not drive the tracer values
1428 ! outside of the range Tr_min_face <= Tr <= Tr_max_face, or if it
1429 ! does that the concentration in both contributing pieces exceed
1430 ! this range equally. With down-gradient fluxes and the initial tracer
1431 ! concentrations determining the valid range, the latter condition
1432 ! only enters for large values of the effective diffusive CFL number.
14330 if (Tr_flux < 0.0) then
14340 if (Tr_Ra < Tr_Rb) then ; if (vol * (Tr_Ra-Tr_min_face) < -Tr_flux) &
1435 Tr_adj_vert = -wt_a * min(-Tr_flux - vol * (Tr_Ra-Tr_min_face), &
14360 (vol*wt_b) * (Tr_Rb - Tr_Ra))
14370 else ; if (vol*(Tr_Rb-Tr_min_face) < (-Tr_flux)) &
1438 Tr_adj_vert = wt_b * min(-Tr_flux - vol * (Tr_Rb-Tr_min_face), &
14390 (vol*wt_a) * (Tr_Ra - Tr_Rb))
1440 endif
14410 elseif (Tr_flux > 0.0) then
14420 if (Tr_Ra > Tr_Rb) then ; if (vol * (Tr_max_face-Tr_Ra) < Tr_flux) &
1443 Tr_adj_vert = wt_a * min(Tr_flux - vol * (Tr_max_face-Tr_Ra), &
14440 (vol*wt_b) * (Tr_Ra - Tr_Rb))
14450 else ; if (vol*(Tr_max_face-Tr_Rb) < Tr_flux) &
1446 Tr_adj_vert = -wt_b * min(Tr_flux - vol * (Tr_max_face-Tr_Rb), &
14470 (vol*wt_a)*(Tr_Rb - Tr_Ra))
1448 endif
1449 endif
1450
14510 if (CS%answer_date <= 20240330) then
14520 tr_flux_conv(i+1,j,kRa) = tr_flux_conv(i+1,j,kRa) + (wt_a*Tr_flux - Tr_adj_vert)
14530 tr_flux_conv(i+1,j,kRb) = tr_flux_conv(i+1,j,kRb) + (wt_b*Tr_flux + Tr_adj_vert)
1454 else
14550 tr_flux_W(i+1,j,kRa) = tr_flux_W(i+1,j,kRa) + (wt_a*Tr_flux - Tr_adj_vert)
14560 tr_flux_W(i+1,j,kRb) = tr_flux_W(i+1,j,kRb) + (wt_b*Tr_flux + Tr_adj_vert)
1457 endif
1458 endif
14590 if (associated(Tr(m)%df2d_x)) &
14600 Tr(m)%df2d_x(I,j) = Tr(m)%df2d_x(I,j) + Tr_flux * Idt
1461 enddo ! Loop over pairings at faces.
1462 endif ; enddo ; enddo ! i- & j- loops over zonal faces.
1463
1464 ! this gives wrong result when using do concurrent on NVHPC 25.9
1465 !$omp target teams loop collapse(2) private(Tr_min_face,Tr_max_face,kLa,kLb,kRa,kRb, &
1466 !$omp Tr_La,Tr_Lb,Tr_Ra,Tr_Rb,Tr_av_L,wt_b,Tr_av_R, &
1467 !$omp h_L,h_R,Tr_flux,Tr_adj_vert,wt_a,vol)
14680 do J=js-1,je ; do i=is,ie ; if (G%mask2dCv(i,J) > 0.0) then
1469 ! Determine the fluxes through the meridional faces.
1470
1471 ! Find the acceptable range of tracer concentration around this face.
14720 if (nPv(i,J) >= 1) then
14730 Tr_min_face = min(Tr(m)%t(i,j,1), Tr(m)%t(i,j+1,1))
14740 Tr_max_face = max(Tr(m)%t(i,j,1), Tr(m)%t(i,j+1,1))
14750 do k=2,nkmb
14760 Tr_min_face = min(Tr_min_face, Tr(m)%t(i,j,k), Tr(m)%t(i,j+1,k))
14770 Tr_max_face = max(Tr_max_face, Tr(m)%t(i,j,k), Tr(m)%t(i,j+1,k))
1478 enddo
1479
1480 ! Include the next two layers denser than the densest buffer layer.
14810 kLa = nkmb+1 ; if (max_kRho(i,j) < nz+1) kLa = max_kRho(i,j)
14820 kLb = kLa ; if (max_kRho(i,j) < nz) kLb = max_kRho(i,j)+1
14830 kRa = nkmb+1 ; if (max_kRho(i,j+1) < nz+1) kRa = max_kRho(i,j+1)
14840 kRb = kRa ; if (max_kRho(i,j+1) < nz) kRb = max_kRho(i,j+1)+1
14850 Tr_La = Tr_min_face ; Tr_Lb = Tr_La ; Tr_Ra = Tr_La ; Tr_Rb = Tr_La
14860 if (h(i,j,kLa) > h_exclude) Tr_La = Tr(m)%t(i,j,kLa)
14870 if ((CS%answer_date <= 20240330) .and. CS%limit_bug) then
14880 if (h(i,j,kLb) > h_exclude) Tr_La = Tr(m)%t(i,j,kLb)
1489 else
14900 if (h(i,j,kLb) > h_exclude) Tr_Lb = Tr(m)%t(i,j,kLb)
1491 endif
14920 if (h(i,j+1,kRa) > h_exclude) Tr_Ra = Tr(m)%t(i,j+1,kRa)
14930 if (h(i,j+1,kRb) > h_exclude) Tr_Rb = Tr(m)%t(i,j+1,kRb)
14940 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
14950 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1496
1497 ! Include all points in diffusive pairings at this face.
14980 do k=1,nPv(i,J)
14990 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))
15000 Tr_La = Tr_Lb ; Tr_Ra = Tr_Rb
15010 if (deep_wt_Lv(I,k,J) < 1.0) Tr_La = Tr(m)%t(i,j,k0a_Lv(i,k,J))
15020 if (deep_wt_Rv(I,k,J) < 1.0) Tr_Ra = Tr(m)%t(i,j+1,k0a_Rv(i,k,J))
15030 Tr_min_face = min(Tr_min_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
15040 Tr_max_face = max(Tr_max_face, Tr_La, Tr_Lb, Tr_Ra, Tr_Rb)
1505 enddo
1506 endif
1507
15080 do k=1,nPv(i,J)
15090 kLb = k0b_Lv(i,k,J) ; Tr_Lb = Tr(m)%t(i,j,kLb) ; Tr_av_L = Tr_Lb
15100 if (deep_wt_Lv(I,k,J) < 1.0) then
15110 kLa = k0a_Lv(i,k,J) ; Tr_La = Tr(m)%t(i,j,kLa)
15120 wt_b = deep_wt_Lv(I,k,J)
15130 Tr_av_L = wt_b * Tr_Lb + (1.0-wt_b) * Tr_La
1514 endif
1515
15160 kRb = k0b_Rv(i,k,J) ; Tr_Rb = Tr(m)%t(i,j+1,kRb) ; Tr_av_R = Tr_Rb
15170 if (deep_wt_Rv(I,k,J) < 1.0) then
15180 kRa = k0a_Rv(i,k,J) ; Tr_Ra = Tr(m)%t(i,j+1,kRa)
15190 wt_b = deep_wt_Rv(I,k,J)
15200 Tr_av_R = wt_b * Tr_Rb + (1.0-wt_b) * Tr_Ra
1521 endif
1522
15230 h_L = hP_Lv(I,k,J) ; h_R = hP_Rv(I,k,J)
1524 Tr_flux = I_maxitt * ((2.0 * h_L * h_R) / (h_L + h_R)) * &
15250 khdt_epi_y(i,J) * (Tr_av_L - Tr_av_R)
15260 Tr_flux_3d(i,J,k) = Tr_flux
1527
15280 if (deep_wt_Lv(I,k,J) < 1.0) then
15290 Tr_adj_vert = 0.0
15300 wt_b = deep_wt_Lv(I,k,J) ; wt_a = 1.0 - wt_b
15310 vol = hP_Lv(I,k,J) * G%areaT(i,j)
1532
1533 ! Ensure that the tracer flux does not drive the tracer values
1534 ! outside of the range Tr_min_face <= Tr <= Tr_max_face.
15350 if (Tr_flux > 0.0) then
15360 if (Tr_La < Tr_Lb) then ; if (vol * (Tr_La-Tr_min_face) < Tr_flux) &
1537 Tr_adj_vert = -wt_a * min(Tr_flux - vol * (Tr_La-Tr_min_face), &
15380 (vol*wt_b) * (Tr_Lb - Tr_La))
15390 else ; if (vol*(Tr_Lb-Tr_min_face) < Tr_flux) &
1540 Tr_adj_vert = wt_b * min(Tr_flux - vol * (Tr_Lb-Tr_min_face), &
15410 (vol*wt_a) * (Tr_La - Tr_Lb))
1542 endif
15430 elseif (Tr_flux < 0.0) then
15440 if (Tr_La > Tr_Lb) then ; if (vol * (Tr_max_face-Tr_La) < -Tr_flux) &
1545 Tr_adj_vert = wt_a * min(-Tr_flux - vol * (Tr_max_face-Tr_La), &
15460 (vol*wt_b) * (Tr_La - Tr_Lb))
15470 else ; if (vol*(Tr_max_face-Tr_Lb) < -Tr_flux) &
1548 Tr_adj_vert = -wt_b * min(-Tr_flux - vol * (Tr_max_face-Tr_Lb), &
15490 (vol*wt_a)*(Tr_Lb - Tr_La))
1550 endif
1551 endif
15520 Tr_adj_vert_L(i,J,k) = Tr_adj_vert
1553 endif
1554
15550 if (deep_wt_Rv(I,k,J) < 1.0) then
15560 Tr_adj_vert = 0.0
15570 wt_b = deep_wt_Rv(I,k,J) ; wt_a = 1.0 - wt_b
15580 vol = hP_Rv(I,k,J) * G%areaT(i,j+1)
1559
1560 ! Ensure that the tracer flux does not drive the tracer values
1561 ! outside of the range Tr_min_face <= Tr <= Tr_max_face.
15620 if (Tr_flux < 0.0) then
15630 if (Tr_Ra < Tr_Rb) then ; if (vol * (Tr_Ra-Tr_min_face) < -Tr_flux) &
1564 Tr_adj_vert = -wt_a * min(-Tr_flux - vol * (Tr_Ra-Tr_min_face), &
15650 (vol*wt_b) * (Tr_Rb - Tr_Ra))
15660 else ; if (vol*(Tr_Rb-Tr_min_face) < (-Tr_flux)) &
1567 Tr_adj_vert = wt_b * min(-Tr_flux - vol * (Tr_Rb-Tr_min_face), &
15680 (vol*wt_a) * (Tr_Ra - Tr_Rb))
1569 endif
15700 elseif (Tr_flux > 0.0) then
15710 if (Tr_Ra > Tr_Rb) then ; if (vol * (Tr_max_face-Tr_Ra) < Tr_flux) &
1572 Tr_adj_vert = wt_a * min(Tr_flux - vol * (Tr_max_face-Tr_Ra), &
15730 (vol*wt_b) * (Tr_Ra - Tr_Rb))
15740 else ; if (vol*(Tr_max_face-Tr_Rb) < Tr_flux) &
1575 Tr_adj_vert = -wt_b * min(Tr_flux - vol * (Tr_max_face-Tr_Rb), &
15760 (vol*wt_a)*(Tr_Rb - Tr_Ra))
1577 endif
1578 endif
15790 Tr_adj_vert_R(i,J,k) = Tr_adj_vert
1580 endif
15810 if (associated(Tr(m)%df2d_y)) &
15820 Tr(m)%df2d_y(i,J) = Tr(m)%df2d_y(i,J) + Tr_flux * Idt
1583 enddo ! Loop over pairings at faces.
1584 endif ; enddo ; enddo ! i- & j- loops over meridional faces.
1585
1586 ! The non-stride-1 loop order here is to facilitate openMP threading. However, it might be
1587 ! suboptimal when openMP threading is not used, at which point it might be better to fuse
1588 ! this loop with those that precede it and thereby eliminate the need for three 3-d arrays.
15890 if (CS%answer_date <= 20240330) then
1590 ! KRa/b aren't guaranteed to be unique and update of tr_flux_conv(:, j/j+1, :) means this
1591 ! loop must be serial in both j and k. i can be paralellised.
15920 do concurrent (i=is:ie) DO_LOCALITY(local(j, k, kLb, kRb, kLa, wt_b, wt_a, kRa))
15930 do J=js-1,je ; if (G%mask2dCv(i,J) > 0.0) then
15940 do k=1,nPv(i,J)
15950 kLb = k0b_Lv(i,k,J) ; kRb = k0b_Rv(i,k,J)
15960 if (deep_wt_Lv(I,k,J) >= 1.0) then
15970 tr_flux_conv(i,j,kLb) = tr_flux_conv(i,j,kLb) - Tr_flux_3d(i,J,k)
1598 else
15990 kLa = k0a_Lv(i,k,J)
16000 wt_b = deep_wt_Lv(I,k,J) ; wt_a = 1.0 - wt_b
16010 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))
16020 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))
1603 endif
16040 if (deep_wt_Rv(I,k,J) >= 1.0) then
16050 tr_flux_conv(i,j+1,kRb) = tr_flux_conv(i,j+1,kRb) + Tr_flux_3d(i,J,k)
1606 else
16070 kRa = k0a_Rv(i,k,J)
16080 wt_b = deep_wt_Rv(I,k,J) ; wt_a = 1.0 - wt_b
1609 tr_flux_conv(i,j+1,kRa) = tr_flux_conv(i,j+1,kRa) + &
16100 (wt_a*Tr_flux_3d(i,J,k) - Tr_adj_vert_R(i,J,k))
1611 tr_flux_conv(i,j+1,kRb) = tr_flux_conv(i,j+1,kRb) + &
16120 (wt_b*Tr_flux_3d(i,J,k) + Tr_adj_vert_R(i,J,k))
1613 endif
1614 enddo
1615 endif ; enddo
1616 enddo
1617 else
1618 ! Update of tr_flux_N/S can be done independently so both i and j can be parallelised.
16190 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))
16200 do k=1,nPv(i,J)
16210 kLb = k0b_Lv(i,k,J) ; kRb = k0b_Rv(i,k,J)
16220 if (deep_wt_Lv(I,k,J) >= 1.0) then
16230 tr_flux_N(i,j,kLb) = tr_flux_N(i,j,kLb) + Tr_flux_3d(i,J,k)
1624 else
16250 kLa = k0a_Lv(i,k,J)
16260 wt_b = deep_wt_Lv(I,k,J) ; wt_a = 1.0 - wt_b
16270 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))
16280 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))
1629 endif
16300 if (deep_wt_Rv(I,k,J) >= 1.0) then
16310 tr_flux_S(i,j+1,kRb) = tr_flux_S(i,j+1,kRb) + Tr_flux_3d(i,J,k)
1632 else
16330 kRa = k0a_Rv(i,k,J)
16340 wt_b = deep_wt_Rv(I,k,J) ; wt_a = 1.0 - wt_b
16350 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))
16360 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))
1637 endif
1638 enddo
1639 enddo
1640 endif
1641
16420 if (CS%answer_date >= 20240331) then
16430 do concurrent (k=1:PEmax_kRho, j=js:je, i=is:ie)
1644 tr_flux_conv(i,j,k) = ((tr_flux_W(i,j,k) - tr_flux_E(i,j,k)) + &
16450 (tr_flux_S(i,j,k) - tr_flux_N(i,j,k)))
1646 enddo
1647 endif
1648
16490 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))
16500 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))
1651 enddo
1652
1653 ! Do user controlled underflow of the tracer concentrations.
16540 if (Tr(m)%conc_underflow > 0.0) then
1655 !$omp target update from(Tr(m)%t)
1656 !$OMP parallel do default(shared)
16570 do k=1,nz ; do j=js,je ; do i=is,ie
16580 if (abs(Tr(m)%t(i,j,k)) < Tr(m)%conc_underflow) Tr(m)%t(i,j,k) = 0.0
1659 enddo ; enddo ; enddo
1660 !$omp target update to(Tr(m)%t)
1661 endif
1662
1663 enddo ! Loop over tracers
1664 enddo ! Loop over iterations
1665
1666 !$omp target exit data map(release: Tr_flux_3d, Tr_adj_vert_L, Tr_adj_vert_R, tr_flux_N, &
1667 !$omp tr_flux_S, tr_flux_E, tr_flux_W, tr_flux_conv, nPv, nPu, max_kRho, rho_srt, num_srt, &
1668 !$omp max_srt)
1669
1670 !$omp target exit data map(release: deep_wt_Lu, deep_wt_Ru, hP_Lu, hP_Ru, k0a_Lu, k0a_Ru, &
1671 !$omp k0b_Lu, k0b_Ru)
1672
1673 !$omp target exit data map(release: deep_wt_Lv, deep_wt_Rv, hP_Lv, hP_Rv, k0a_Lv, k0a_Rv, &
1674 !$Omp k0b_Lv, k0b_Rv)
1675
16760end subroutine tracer_epipycnal_ML_diff
1677
1678
1679!> Initialize lateral tracer diffusion module
16801subroutine tracer_hor_diff_init(Time, G, GV, US, param_file, diag, EOS, diabatic_CSp, CS)
1681 type(time_type), target, intent(in) :: Time !< current model time
1682 type(ocean_grid_type), intent(in) :: G !< ocean grid structure
1683 type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
1684 type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
1685 type(diag_ctrl), target, intent(inout) :: diag !< diagnostic control
1686 type(EOS_type), target, intent(in) :: EOS !< Equation of state CS
1687 type(diabatic_CS), pointer, intent(in) :: diabatic_CSp !< Equation of state CS
1688 type(param_file_type), intent(in) :: param_file !< parameter file
1689 type(tracer_hor_diff_CS), pointer :: CS !< horz diffusion control structure
1690
1691 ! This include declares and sets the variable "version".
1692# include "version_variable.h"
1693 character(len=40) :: mdl = "MOM_tracer_hor_diff" ! This module's name.
1694 integer :: default_answer_date
1695
16961 if (associated(CS)) then
16970 call MOM_error(WARNING, "tracer_hor_diff_init called with associated control structure.")
16980 return
1699 endif
17001 allocate(CS)
1701
17021 CS%diag => diag
17031 CS%show_call_tree = callTree_showQuery()
1704
1705 ! Read all relevant parameters and write them to the model log.
17061 call log_version(param_file, mdl, version, "")
1707 call get_param(param_file, mdl, "KHTR", CS%KhTr, &
1708 "The background along-isopycnal tracer diffusivity.", &
17091 units="m2 s-1", default=0.0, scale=US%m_to_L**2*US%T_to_s)
1710! call get_param(param_file, mdl, "KHTR_USE_EBT_STRUCT", CS%KhTh_use_ebt_struct, &
1711! "If true, uses the equivalent barotropic structure "//&
1712! "as the vertical structure of the tracer diffusivity.",&
1713! default=.false.)
1714 call get_param(param_file, mdl, "KHTR_SLOPE_CFF", CS%KhTr_Slope_Cff, &
1715 "The scaling coefficient for along-isopycnal tracer "//&
1716 "diffusivity using a shear-based (Visbeck-like) "//&
1717 "parameterization. A non-zero value enables this param.", &
17181 units="nondim", default=0.0)
1719 call get_param(param_file, mdl, "KHTR_MIN", CS%KhTr_Min, &
1720 "The minimum along-isopycnal tracer diffusivity.", &
17211 units="m2 s-1", default=0.0, scale=US%m_to_L**2*US%T_to_s)
17221 if (CS%KhTr_Min > 0.0) then
1723 call get_param(param_file, mdl, "FULL_DEPTH_KHTR_MIN", CS%full_depth_khtr_min, &
1724 "If true, KHTR_MIN is enforced throughout the whole water column. "//&
1725 "Otherwise, KHTR_MIN is only enforced at the surface. This parameter "//&
1726 "is only available when KHTR_USE_EBT_STRUCT=True and KHTR_MIN>0.", &
17270 default=.false.)
1728 endif
1729 call get_param(param_file, mdl, "KHTR_MAX", CS%KhTr_Max, &
1730 "The maximum along-isopycnal tracer diffusivity.", &
17311 units="m2 s-1", default=0.0, scale=US%m_to_L**2*US%T_to_s)
1732 call get_param(param_file, mdl, "KHTR_PASSIVITY_COEFF", CS%KhTr_passivity_coeff, &
1733 "The coefficient that scales deformation radius over "//&
1734 "grid-spacing in passivity, where passivity is the ratio "//&
1735 "between along isopycnal mixing of tracers to thickness mixing. "//&
1736 "A non-zero value enables this parameterization.", &
17371 units="nondim", default=0.0)
1738 call get_param(param_file, mdl, "KHTR_PASSIVITY_MIN", CS%KhTr_passivity_min, &
1739 "The minimum passivity which is the ratio between "//&
1740 "along isopycnal mixing of tracers to thickness mixing.", &
17411 units="nondim", default=0.5)
1742 call get_param(param_file, mdl, "DIFFUSE_ML_TO_INTERIOR", CS%Diffuse_ML_interior, &
1743 "If true, enable epipycnal mixing between the surface "//&
17441 "boundary layer and the interior.", default=.false.)
1745 call get_param(param_file, mdl, "CHECK_DIFFUSIVE_CFL", CS%check_diffusive_CFL, &
1746 "If true, use enough iterations the diffusion to ensure "//&
1747 "that the diffusive equivalent of the CFL limit is not "//&
1748 "violated. If false, always use the greater of 1 or "//&
17491 "MAX_TR_DIFFUSION_CFL iteration.", default=.false.)
1750 call get_param(param_file, mdl, "MAX_TR_DIFFUSION_CFL", CS%max_diff_CFL, &
1751 "If positive, locally limit the along-isopycnal tracer "//&
1752 "diffusivity to keep the diffusive CFL locally at or "//&
1753 "below this value. The number of diffusive iterations "//&
1754 "is often this value or the next greater integer.", &
17551 units="nondim", default=-1.0)
1756 call get_param(param_File, mdl, "RECALC_NEUTRAL_SURF", CS%recalc_neutral_surf, &
1757 "If true, then recalculate the neutral surfaces if the \n"//&
1758 "diffusive CFL is exceeded. If false, assume that the \n"//&
17591 "positions of the surfaces do not change \n", default=.false.)
1760 call get_param(param_file, mdl, "DEFAULT_ANSWER_DATE", default_answer_date, &
1761 "This sets the default value for the various _ANSWER_DATE parameters.", &
17621 default=99991231, do_not_log=.true.)
1763 call get_param(param_file, mdl, "HOR_DIFF_ANSWER_DATE", CS%answer_date, &
1764 "The vintage of the order of arithmetic to use for the tracer diffusion. "//&
1765 "Values of 20240330 or below recover the answers from the original form of the "//&
1766 "along-isopycnal mixed layer to interior mixing code, while higher values use "//&
1767 "mathematically equivalent expressions that recover rotational symmetry "//&
1768 "when DIFFUSE_ML_TO_INTERIOR is true.", &
17691 default=default_answer_date, do_not_log=.not.CS%Diffuse_ML_interior)
1770 call get_param(param_file, mdl, "HOR_DIFF_LIMIT_BUG", CS%limit_bug, &
1771 "If true and the answer date is 20240330 or below, use a rotational symmetry "//&
1772 "breaking bug when limiting the tracer properties in tracer_epipycnal_ML_diff.", &
17731 default=.false., do_not_log=((.not.CS%Diffuse_ML_interior).or.(CS%answer_date>=20240331)))
17741 CS%ML_KhTR_scale = 1.0
17751 if (CS%Diffuse_ML_interior) then
1776 call get_param(param_file, mdl, "ML_KHTR_SCALE", CS%ML_KhTR_scale, &
1777 "With Diffuse_ML_interior, the ratio of the truly "//&
1778 "horizontal diffusivity in the mixed layer to the "//&
1779 "epipycnal diffusivity. The valid range is 0 to 1.", &
17800 units="nondim", default=1.0)
1781 endif
1782
1783 CS%use_neutral_diffusion = neutral_diffusion_init(Time, G, GV, US, param_file, diag, EOS, &
17841 diabatic_CSp, CS%neutral_diffusion_CSp )
17851 if (CS%use_neutral_diffusion .and. CS%Diffuse_ML_interior) call MOM_error(FATAL, "MOM_tracer_hor_diff: "// &
17860 "USE_NEUTRAL_DIFFUSION and DIFFUSE_ML_TO_INTERIOR are mutually exclusive!")
1787 CS%use_hor_bnd_diffusion = hor_bnd_diffusion_init(Time, G, GV, US, param_file, diag, diabatic_CSp, &
17881 CS%hor_bnd_diffusion_CSp)
17891 if (CS%use_hor_bnd_diffusion .and. CS%Diffuse_ML_interior) call MOM_error(FATAL, "MOM_tracer_hor_diff: "// &
17900 "USE_HORIZONTAL_BOUNDARY_DIFFUSION and DIFFUSE_ML_TO_INTERIOR are mutually exclusive!")
1791
17921 call get_param(param_file, mdl, "DEBUG", CS%debug, default=.false.)
1793
17941 id_clock_diffuse = cpu_clock_id('(Ocean diffuse tracer)', grain=CLOCK_MODULE)
17951 id_clock_epimix = cpu_clock_id('(Ocean epipycnal diffuse tracer)',grain=CLOCK_MODULE)
17961 id_clock_pass = cpu_clock_id('(Ocean tracer halo updates)', grain=CLOCK_ROUTINE)
17971 id_clock_sync = cpu_clock_id('(Ocean tracer global synch)', grain=CLOCK_ROUTINE)
1798
17991 CS%id_KhTr_u = -1
18001 CS%id_KhTr_v = -1
18011 CS%id_KhTr_h = -1
18021 CS%id_CFL = -1
1803
1804 CS%id_KhTr_u = register_diag_field('ocean_model', 'KHTR_u', diag%axesCui, Time, &
18051 'Epipycnal tracer diffusivity at zonal faces of tracer cell', 'm2 s-1', conversion=US%L_to_m**2*US%s_to_T)
1806 CS%id_KhTr_v = register_diag_field('ocean_model', 'KHTR_v', diag%axesCvi, Time, &
18071 'Epipycnal tracer diffusivity at meridional faces of tracer cell', 'm2 s-1', conversion=US%L_to_m**2*US%s_to_T)
1808 CS%id_KhTr_h = register_diag_field('ocean_model', 'KHTR_h', diag%axesTi, Time, &
1809 'Epipycnal tracer diffusivity at tracer cell center', 'm2 s-1', conversion=US%L_to_m**2*US%s_to_T, &
1810 cmor_field_name='diftrelo', &
1811 cmor_standard_name= 'ocean_tracer_epineutral_laplacian_diffusivity', &
18121 cmor_long_name = 'Ocean Tracer Epineutral Laplacian Diffusivity')
1813
1814 CS%id_khdt_x = register_diag_field('ocean_model', 'KHDT_x', diag%axesCu1, Time, &
18151 'Epipycnal tracer diffusivity operator at zonal faces of tracer cell', 'm2', conversion=US%L_to_m**2)
1816 CS%id_khdt_y = register_diag_field('ocean_model', 'KHDT_y', diag%axesCv1, Time, &
18171 'Epipycnal tracer diffusivity operator at meridional faces of tracer cell', 'm2', conversion=US%L_to_m**2)
18181 if (CS%check_diffusive_CFL) then
1819 CS%id_CFL = register_diag_field('ocean_model', 'CFL_lateral_diff', diag%axesT1, Time,&
18201 'Grid CFL number for lateral/neutral tracer diffusion', 'nondim')
1821 endif
1822
1823
1824end subroutine tracer_hor_diff_init
1825
18261subroutine tracer_hor_diff_end(CS)
1827 type(tracer_hor_diff_CS), pointer :: CS !< module control structure
1828
18291 call neutral_diffusion_end(CS%neutral_diffusion_CSp)
18301 call hor_bnd_diffusion_end(CS%hor_bnd_diffusion_CSp)
18311 if (associated(CS)) deallocate(CS)
1832
18331end subroutine tracer_hor_diff_end
1834
1835
1836!> \namespace mom_tracer_hor_diff
1837!!
1838!! \section section_intro Introduction to the module
1839!!
1840!! This module contains subroutines that handle horizontal
1841!! diffusion (i.e., isoneutral or along layer) of tracers.
1842!!
1843!! Each of the tracers are subject to Fickian along-coordinate
1844!! diffusion if Khtr is defined and positive. The tracer diffusion
1845!! can use a suitable number of iterations to guarantee stability
1846!! with an arbitrarily large time step.
1847
18480end module MOM_tracer_hor_diff