8 use mom_cpu_clock,
only : cpu_clock_id, cpu_clock_begin, cpu_clock_end
18 use mom_remapping,
only : extract_member_remapping_cs, build_reconstructions_1d
19 use mom_remapping,
only : average_value_ppoly, remappingschemesdoc, remappingdefaultscheme
26 use iso_fortran_env,
only : stdout=>output_unit, stderr=>error_unit
28 implicit none ;
private
30 public near_boundary_unit_tests, lateral_boundary_diffusion, lateral_boundary_diffusion_init
31 public boundary_k_range
34 integer,
public,
parameter :: surface = -1
35 integer,
public,
parameter :: bottom = 1
36 #include <MOM_memory.h>
45 integer :: surface_boundary_scheme
53 type(
kpp_cs),
pointer :: kpp_csp => null()
60 #include "version_variable.h"
61 character(len=40) :: mdl =
"MOM_lateral_boundary_diffusion"
67 logical function lateral_boundary_diffusion_init(Time, G, param_file, diag, diabatic_CSp, CS)
68 type(time_type),
target,
intent(in) :: time
71 type(
diag_ctrl),
target,
intent(inout) :: diag
76 character(len=80) :: string
77 logical :: boundary_extrap
79 if (
ASSOCIATED(cs))
then
80 call mom_error(fatal,
"lateral_boundary_diffusion_init called with associated control structure.")
85 call get_param(param_file, mdl,
"USE_LATERAL_BOUNDARY_DIFFUSION", lateral_boundary_diffusion_init, &
86 default=.false., do_not_log=.true.)
88 "This module implements lateral diffusion of tracers near boundaries", &
89 all_default=.not.lateral_boundary_diffusion_init)
90 call get_param(param_file, mdl,
"USE_LATERAL_BOUNDARY_DIFFUSION", lateral_boundary_diffusion_init, &
91 "If true, enables the lateral boundary tracer's diffusion module.", &
94 if (.not. lateral_boundary_diffusion_init)
return
98 call extract_diabatic_member(diabatic_csp, kpp_csp=cs%KPP_CSp)
99 call extract_diabatic_member(diabatic_csp, energetic_pbl_csp=cs%energetic_PBL_CSp)
101 cs%surface_boundary_scheme = -1
102 if ( .not.
ASSOCIATED(cs%energetic_PBL_CSp) .and. .not.
ASSOCIATED(cs%KPP_CSp) )
then
103 call mom_error(fatal,
"Lateral boundary diffusion is true, but no valid boundary layer scheme was found")
107 call get_param(param_file, mdl,
"LATERAL_BOUNDARY_METHOD", cs%method, &
108 "Determine how to apply boundary lateral diffusion of tracers: \n"//&
109 "1. Along layer approach \n"//&
110 "2. Bulk layer approach (this option is not recommended)", default=1)
111 if (cs%method == 2)
then
112 call get_param(param_file, mdl,
"APPLY_LIMITER", cs%limiter, &
113 "If True, apply a flux limiter in the LBD. This is only available \n"//&
114 "when LATERAL_BOUNDARY_METHOD=2.", default=.false.)
116 call get_param(param_file, mdl,
"LBD_LINEAR_TRANSITION", cs%linear, &
117 "If True, apply a linear transition at the base/top of the boundary. \n"//&
118 "The flux will be fully applied at k=k_min and zero at k=k_max.", default=.false.)
119 call get_param(param_file, mdl,
"LBD_BOUNDARY_EXTRAP", boundary_extrap, &
120 "Use boundary extrapolation in LBD code", &
122 call get_param(param_file, mdl,
"LBD_REMAPPING_SCHEME", string, &
123 "This sets the reconstruction scheme used "//&
124 "for vertical remapping for all variables. "//&
125 "It can be one of the following schemes: "//&
126 trim(remappingschemesdoc), default=remappingdefaultscheme)
127 call initialize_remapping( cs%remap_CS, string, boundary_extrapolation = boundary_extrap )
128 call extract_member_remapping_cs(cs%remap_CS, degree=cs%deg)
130 end function lateral_boundary_diffusion_init
137 subroutine lateral_boundary_diffusion(G, GV, US, h, Coef_x, Coef_y, dt, Reg, CS)
141 real,
dimension(SZI_(G),SZJ_(G),SZK_(G)), &
143 real,
dimension(SZIB_(G),SZJ_(G)),
intent(in) :: coef_x
144 real,
dimension(SZI_(G),SZJB_(G)),
intent(in) :: coef_y
145 real,
intent(in) :: dt
151 real,
dimension(SZI_(G),SZJ_(G)) :: hbl
152 real,
dimension(SZI_(G),SZJ_(G),SZK_(G),CS%deg+1) :: ppoly0_coefs
153 real,
dimension(SZI_(G),SZJ_(G),SZK_(G),2) :: ppoly0_e
154 real,
dimension(SZK_(G),CS%deg+1) :: ppoly_s
155 real,
dimension(SZIB_(G),SZJ_(G),SZK_(G)) :: uflx
156 real,
dimension(SZIB_(G),SZJ_(G)) :: uflx_bulk
157 real,
dimension(SZI_(G),SZJB_(G),SZK_(G)) :: vflx
158 real,
dimension(SZI_(G),SZJB_(G)) :: vflx_bulk
159 real,
dimension(SZIB_(G),SZJ_(G)) :: uwork_2d
160 real,
dimension(SZI_(G),SZJB_(G)) :: vwork_2d
161 real,
dimension(SZI_(G),SZJ_(G),G%ke) :: tendency
162 real,
dimension(SZI_(G),SZJ_(G)) :: tendency_2d
164 integer :: remap_method
170 if (
ASSOCIATED(cs%KPP_CSp))
call kpp_get_bld(cs%KPP_CSp, hbl, g, us, m_to_bld_units=gv%m_to_H)
171 if (
ASSOCIATED(cs%energetic_PBL_CSp)) &
172 call energetic_pbl_get_mld(cs%energetic_PBL_CSp, hbl, g, us, m_to_mld_units=gv%m_to_H)
179 if (tracer%id_lbdxy_conc > 0 .or. tracer%id_lbdxy_cont > 0 .or. tracer%id_lbdxy_cont_2d > 0)
then
180 tendency(:,:,:) = 0.0
184 do j=g%jsc-1,g%jec+1 ;
do i=g%isc-1,g%iec+1
185 call build_reconstructions_1d( cs%remap_CS, g%ke, h(i,j,:), tracer%t(i,j,:), ppoly0_coefs(i,j,:,:), &
186 ppoly0_e(i,j,:,:), ppoly_s, remap_method, gv%H_subroundoff, gv%H_subroundoff)
196 if (cs%method == 1)
then
199 if (g%mask2dCu(i,j)>0.)
then
200 call fluxes_layer_method(surface, gv%ke, cs%deg, h(i,j,:), h(i+1,j,:), hbl(i,j), hbl(i+1,j), &
201 g%areaT(i,j), g%areaT(i+1,j), tracer%t(i,j,:), tracer%t(i+1,j,:), ppoly0_coefs(i,j,:,:), &
202 ppoly0_coefs(i+1,j,:,:), ppoly0_e(i,j,:,:), ppoly0_e(i+1,j,:,:), remap_method, coef_x(i,j), &
203 uflx(i,j,:), cs%linear)
209 if (g%mask2dCv(i,j)>0.)
then
210 call fluxes_layer_method(surface, gv%ke, cs%deg, h(i,j,:), h(i,j+1,:), hbl(i,j), hbl(i,j+1), &
211 g%areaT(i,j), g%areaT(i,j+1), tracer%t(i,j,:), tracer%t(i,j+1,:), ppoly0_coefs(i,j,:,:), &
212 ppoly0_coefs(i,j+1,:,:), ppoly0_e(i,j,:,:), ppoly0_e(i,j+1,:,:), remap_method, coef_y(i,j), &
213 vflx(i,j,:), cs%linear)
219 elseif (cs%method == 2)
then
222 if (g%mask2dCu(i,j)>0.)
then
223 call fluxes_bulk_method(surface, gv%ke, cs%deg, h(i,j,:), h(i+1,j,:), hbl(i,j), hbl(i+1,j), &
224 g%areaT(i,j), g%areaT(i+1,j), tracer%t(i,j,:), tracer%t(i+1,j,:), &
225 ppoly0_coefs(i,j,:,:), ppoly0_coefs(i+1,j,:,:), ppoly0_e(i,j,:,:), &
226 ppoly0_e(i+1,j,:,:), remap_method, coef_x(i,j), uflx_bulk(i,j), uflx(i,j,:), cs%limiter, &
233 if (g%mask2dCv(i,j)>0.)
then
234 call fluxes_bulk_method(surface, gv%ke, cs%deg, h(i,j,:), h(i,j+1,:), hbl(i,j), hbl(i,j+1), &
235 g%areaT(i,j), g%areaT(i,j+1), tracer%t(i,j,:), tracer%t(i,j+1,:), &
236 ppoly0_coefs(i,j,:,:), ppoly0_coefs(i,j+1,:,:), ppoly0_e(i,j,:,:), &
237 ppoly0_e(i,j+1,:,:), remap_method, coef_y(i,j), vflx_bulk(i,j), vflx(i,j,:), cs%limiter, &
243 if (tracer%id_lbd_bulk_dfx>0)
call post_data(tracer%id_lbd_bulk_dfx, uflx_bulk*idt, cs%diag)
244 if (tracer%id_lbd_bulk_dfy>0)
call post_data(tracer%id_lbd_bulk_dfy, vflx_bulk*idt, cs%diag)
248 do k=1,gv%ke ;
do j=g%jsc,g%jec ;
do i=g%isc,g%iec
249 if (g%mask2dT(i,j)>0.)
then
250 tracer%t(i,j,k) = tracer%t(i,j,k) + (( (uflx(i-1,j,k)-uflx(i,j,k)) ) + ( (vflx(i,j-1,k)-vflx(i,j,k) ) ))* &
251 (g%IareaT(i,j)/( h(i,j,k) + gv%H_subroundoff))
253 if (tracer%id_lbdxy_conc > 0 .or. tracer%id_lbdxy_cont > 0 .or. tracer%id_lbdxy_cont_2d > 0 )
then
254 tendency(i,j,k) = (( (uflx(i-1,j,k)-uflx(i,j,k)) ) + ( (vflx(i,j-1,k)-vflx(i,j,k) ) )) * g%IareaT(i,j) * idt
258 enddo ;
enddo ;
enddo
261 if (tracer%id_lbd_dfx>0)
call post_data(tracer%id_lbd_dfx, uflx*idt, cs%diag)
262 if (tracer%id_lbd_dfy>0)
call post_data(tracer%id_lbd_dfy, vflx*idt, cs%diag)
263 if (tracer%id_lbd_dfx_2d>0)
then
265 do k=1,gv%ke ;
do j=g%jsc,g%jec ;
do i=g%isc-1,g%iec
266 uwork_2d(i,j) = uwork_2d(i,j) + (uflx(i,j,k) * idt)
267 enddo ;
enddo ;
enddo
268 call post_data(tracer%id_lbd_dfx_2d, uwork_2d, cs%diag)
271 if (tracer%id_lbd_dfy_2d>0)
then
273 do k=1,gv%ke ;
do j=g%jsc-1,g%jec ;
do i=g%isc,g%iec
274 vwork_2d(i,j) = vwork_2d(i,j) + (vflx(i,j,k) * idt)
275 enddo ;
enddo ;
enddo
276 call post_data(tracer%id_lbd_dfy_2d, vwork_2d, cs%diag)
280 if (tracer%id_lbdxy_cont > 0)
then
281 call post_data(tracer%id_lbdxy_cont, tendency, cs%diag)
285 if (tracer%id_lbdxy_cont_2d > 0)
then
286 tendency_2d(:,:) = 0.
287 do j=g%jsc,g%jec ;
do i=g%isc,g%iec
289 tendency_2d(i,j) = tendency_2d(i,j) + tendency(i,j,k)
292 call post_data(tracer%id_lbdxy_cont_2d, tendency_2d, cs%diag)
298 if (tracer%id_lbdxy_conc > 0)
then
299 do k=1,gv%ke ;
do j=g%jsc,g%jec ;
do i=g%isc,g%iec
300 tendency(i,j,k) = tendency(i,j,k) / ( h(i,j,k) + gv%H_subroundoff )
301 enddo ;
enddo ;
enddo
302 call post_data(tracer%id_lbdxy_conc, tendency, cs%diag)
307 end subroutine lateral_boundary_diffusion
310 real function bulk_average(boundary, nk, deg, h, hBLT, phi, ppoly0_E, ppoly0_coefs, method, k_top, zeta_top, k_bot, &
315 real,
dimension(nk) :: h
317 real,
dimension(nk) :: phi
318 real,
dimension(nk,2) :: ppoly0_e
319 real,
dimension(nk,deg+1) :: ppoly0_coefs
337 if (hblt == 0.)
return
338 if (boundary == surface)
then
339 htot = (h(k_bot) * zeta_bot)
340 bulk_average = average_value_ppoly( nk, phi, ppoly0_e, ppoly0_coefs, method, k_bot, 0., zeta_bot) * htot
342 bulk_average = bulk_average + phi(k)*h(k)
345 elseif (boundary == bottom)
then
346 htot = (h(k_top) * zeta_top)
348 bulk_average = average_value_ppoly( nk, phi, ppoly0_e, ppoly0_coefs, method, k_top, (1.-zeta_top), 1.) * htot
350 bulk_average = bulk_average + phi(k)*h(k)
354 call mom_error(fatal,
"bulk_average: a valid boundary type must be provided.")
357 bulk_average = bulk_average / hblt
359 end function bulk_average
363 real function harmonic_mean(h1,h2)
366 if (h1 + h2 == 0.)
then
369 harmonic_mean = 2.*(h1*h2)/(h1+h2)
371 end function harmonic_mean
374 subroutine boundary_k_range(boundary, nk, h, hbl, k_top, zeta_top, k_bot, zeta_bot)
375 integer,
intent(in ) :: boundary
376 integer,
intent(in ) :: nk
377 real,
dimension(nk),
intent(in ) :: h
378 real,
intent(in ) :: hbl
381 integer,
intent( out) :: k_top
382 real,
intent( out) :: zeta_top
384 integer,
intent( out) :: k_bot
385 real,
intent( out) :: zeta_bot
391 if ( boundary == surface )
then
397 if (hbl == 0.)
return
398 if (hbl >= sum(h(:)))
then
405 if ( htot >= hbl)
then
407 zeta_bot = 1 - (htot - hbl)/h(k)
412 elseif ( boundary == bottom )
then
418 if (hbl == 0.)
return
419 if (hbl >= sum(h(:)))
then
426 if (htot >= hbl)
then
428 zeta_top = 1 - (htot - hbl)/h(k)
433 call mom_error(fatal,
"Houston, we've had a problem in boundary_k_range")
436 end subroutine boundary_k_range
441 subroutine fluxes_layer_method(boundary, nk, deg, h_L, h_R, hbl_L, hbl_R, area_L, area_R, phi_L, phi_R, &
442 ppoly0_coefs_L, ppoly0_coefs_R, ppoly0_E_L, ppoly0_E_R, method, khtr_u, &
443 F_layer, linear_decay)
445 integer,
intent(in ) :: boundary
446 integer,
intent(in ) :: nk
447 integer,
intent(in ) :: deg
448 real,
dimension(nk),
intent(in ) :: h_L
449 real,
dimension(nk),
intent(in ) :: h_R
450 real,
intent(in ) :: hbl_L
452 real,
intent(in ) :: hbl_R
454 real,
intent(in ) :: area_L
455 real,
intent(in ) :: area_R
456 real,
dimension(nk),
intent(in ) :: phi_L
457 real,
dimension(nk),
intent(in ) :: phi_R
458 real,
dimension(nk,deg+1),
intent(in ) :: ppoly0_coefs_L
459 real,
dimension(nk,deg+1),
intent(in ) :: ppoly0_coefs_R
460 real,
dimension(nk,2),
intent(in ) :: ppoly0_E_L
461 real,
dimension(nk,2),
intent(in ) :: ppoly0_E_R
462 integer,
intent(in ) :: method
463 real,
intent(in ) :: khtr_u
465 real,
dimension(nk),
intent( out) :: F_layer
467 logical,
optional,
intent(in ) :: linear_decay
470 real,
dimension(nk) :: h_means
477 real :: phi_L_avg, phi_R_avg
481 integer :: k, k_bot_min, k_top_max
482 integer :: k_bot_max, k_top_min
483 integer :: k_bot_diff, k_top_diff
484 integer :: k_top_L, k_bot_L
485 integer :: k_top_R, k_bot_R
486 real :: zeta_top_L, zeta_top_R
488 real :: zeta_bot_L, zeta_bot_R
490 real :: h_work_L, h_work_R
497 if (hbl_l == 0. .or. hbl_r == 0.)
then
502 if (
PRESENT(linear_decay))
then
503 linear = linear_decay
507 call boundary_k_range(boundary, nk, h_l, hbl_l, k_top_l, zeta_top_l, k_bot_l, zeta_bot_l)
508 call boundary_k_range(boundary, nk, h_r, hbl_r, k_top_r, zeta_top_r, k_bot_r, zeta_bot_r)
510 if (boundary == surface)
then
511 k_bot_min = min(k_bot_l, k_bot_r)
512 k_bot_max = max(k_bot_l, k_bot_r)
513 k_bot_diff = (k_bot_max - k_bot_min)
516 if (k_bot_min .ne. k_bot_l)
then
520 if (k_bot_min .ne. k_bot_r)
then
525 h_work_l = (h_l(k_bot_l) * zeta_bot_l)
526 h_work_r = (h_r(k_bot_r) * zeta_bot_r)
528 phi_l_avg = average_value_ppoly( nk, phi_l, ppoly0_e_l, ppoly0_coefs_l, method, k_bot_l, 0., zeta_bot_l)
529 phi_r_avg = average_value_ppoly( nk, phi_r, ppoly0_e_r, ppoly0_coefs_r, method, k_bot_r, 0., zeta_bot_r)
530 heff = harmonic_mean(h_work_l, h_work_r)
533 if ((linear) .and. (k_bot_diff .gt. 1))
then
535 do k = k_bot_min,1,-1
536 heff = harmonic_mean(h_l(k), h_r(k))
537 f_layer(k) = -(heff * khtr_u) * (phi_r(k) - phi_l(k))
541 do k = k_bot_min+1,k_bot_max, 1
542 heff_tot = heff_tot + harmonic_mean(h_l(k), h_r(k))
547 do k = k_bot_min+1,k_bot_max, 1
548 heff = harmonic_mean(h_l(k), h_r(k))
549 wgt = (a*(heff_tot + (heff * 0.5))) + 1.0
550 f_layer(k) = -(heff * khtr_u) * (phi_r(k) - phi_l(k)) * wgt
551 heff_tot = heff_tot + heff
554 f_layer(k_bot_min) = -(heff * khtr_u) * (phi_r_avg - phi_l_avg)
555 do k = k_bot_min-1,1,-1
556 heff = harmonic_mean(h_l(k), h_r(k))
557 f_layer(k) = -(heff * khtr_u) * (phi_r(k) - phi_l(k))
562 if (boundary == bottom)
then
564 k_top_max = max(k_top_l, k_top_r)
566 if (k_top_max .ne. k_top_l)
then
570 if (k_top_max .ne. k_top_r)
then
575 h_work_l = (h_l(k_top_l) * zeta_top_l)
576 h_work_r = (h_r(k_top_r) * zeta_top_r)
578 phi_l_avg = average_value_ppoly( nk, phi_l, ppoly0_e_l, ppoly0_coefs_l, method, k_top_l, 1.0-zeta_top_l, 1.0)
579 phi_r_avg = average_value_ppoly( nk, phi_r, ppoly0_e_r, ppoly0_coefs_r, method, k_top_r, 1.0-zeta_top_r, 1.0)
580 heff = harmonic_mean(h_work_l, h_work_r)
583 f_layer(k_top_max) = (-heff * khtr_u) * (phi_r_avg - phi_l_avg)
585 do k = k_top_max+1,nk
586 heff = harmonic_mean(h_l(k), h_r(k))
587 f_layer(k) = -(heff * khtr_u) * (phi_r(k) - phi_l(k))
591 end subroutine fluxes_layer_method
595 subroutine fluxes_bulk_method(boundary, nk, deg, h_L, h_R, hbl_L, hbl_R, area_L, area_R, phi_L, phi_R, ppoly0_coefs_L, &
596 ppoly0_coefs_R, ppoly0_E_L, ppoly0_E_R, method, khtr_u, F_bulk, F_layer, F_limit, &
599 integer,
intent(in ) :: boundary
600 integer,
intent(in ) :: nk
601 integer,
intent(in ) :: deg
602 real,
dimension(nk),
intent(in ) :: h_L
603 real,
dimension(nk),
intent(in ) :: h_R
604 real,
intent(in ) :: hbl_L
606 real,
intent(in ) :: hbl_R
608 real,
intent(in ) :: area_L
609 real,
intent(in ) :: area_R
610 real,
dimension(nk),
intent(in ) :: phi_L
611 real,
dimension(nk),
intent(in ) :: phi_R
612 real,
dimension(nk,deg+1),
intent(in ) :: ppoly0_coefs_L
613 real,
dimension(nk,deg+1),
intent(in ) :: ppoly0_coefs_R
614 real,
dimension(nk,2),
intent(in ) :: ppoly0_E_L
615 real,
dimension(nk,2),
intent(in ) :: ppoly0_E_R
616 integer,
intent(in ) :: method
617 real,
intent(in ) :: khtr_u
619 real,
intent( out) :: F_bulk
621 real,
dimension(nk),
intent( out) :: F_layer
623 logical,
optional,
intent(in ) :: F_limit
624 logical,
optional,
intent(in ) :: linear_decay
628 real,
dimension(nk) :: h_means
636 real :: phi_L_avg, phi_R_avg
639 integer :: k, k_min, k_max
641 integer :: k_top_L, k_bot_L
642 integer :: k_top_R, k_bot_R
643 real :: zeta_top_L, zeta_top_R
645 real :: zeta_bot_L, zeta_bot_R
647 real :: h_work_L, h_work_R
661 if (hbl_l == 0. .or. hbl_r == 0.)
then
666 if (
PRESENT(f_limit))
then
670 if (
PRESENT(linear_decay))
then
671 linear = linear_decay
675 call boundary_k_range(boundary, nk, h_l, hbl_l, k_top_l, zeta_top_l, k_bot_l, zeta_bot_l)
676 call boundary_k_range(boundary, nk, h_r, hbl_r, k_top_r, zeta_top_r, k_bot_r, zeta_bot_r)
679 phi_l_avg = bulk_average(boundary, nk, deg, h_l, hbl_l, phi_l, ppoly0_e_l, ppoly0_coefs_l, method, k_top_l, &
680 zeta_top_l, k_bot_l, zeta_bot_l)
681 phi_r_avg = bulk_average(boundary, nk, deg, h_r, hbl_r, phi_r, ppoly0_e_r, ppoly0_coefs_r, method, k_top_r, &
682 zeta_top_r, k_bot_r, zeta_bot_r)
685 heff = harmonic_mean(hbl_l, hbl_r)
686 f_bulk = -(khtr_u * heff) * (phi_r_avg - phi_l_avg)
690 if (boundary == surface)
then
691 k_min = min(k_bot_l, k_bot_r)
692 k_max = max(k_bot_l, k_bot_r)
693 k_diff = (k_max - k_min)
694 if ((linear) .and. (k_diff .gt. 1))
then
696 h_means(k) = harmonic_mean(h_l(k),h_r(k))
700 do k = k_min+1,k_max, 1
701 heff_tot = heff_tot + harmonic_mean(h_l(k), h_r(k))
707 do k = k_min+1,k_max, 1
708 heff = harmonic_mean(h_l(k), h_r(k))
709 wgt = (a*(heff_tot + (heff * 0.5))) + 1.0
710 h_means(k) = harmonic_mean(h_l(k), h_r(k)) * wgt
711 heff_tot = heff_tot + heff
715 if (k_bot_l == k_min)
then
716 h_work_l = h_l(k_min) * zeta_bot_l
718 h_work_l = h_l(k_min)
722 if (k_bot_r == k_min)
then
723 h_work_r = h_r(k_min) * zeta_bot_r
725 h_work_r = h_r(k_min)
728 h_means(k_min) = harmonic_mean(h_work_l,h_work_r)
731 h_means(k) = harmonic_mean(h_l(k),h_r(k))
735 elseif (boundary == bottom)
then
737 k_max = max(k_top_l, k_top_r)
739 if (k_top_l == k_max)
then
740 h_work_l = h_l(k_max) * zeta_top_l
742 h_work_l = h_l(k_max)
746 if (k_top_r == k_max)
then
747 h_work_r = h_r(k_max) * zeta_top_r
749 h_work_r = h_r(k_max)
752 h_means(k_max) = harmonic_mean(h_work_l,h_work_r)
755 h_means(k) = harmonic_mean(h_l(k),h_r(k))
759 if ( sum(h_means) == 0. .or. f_bulk == 0.)
then
764 inv_heff = 1./sum(h_means)
766 if ((h_means(k) > 0.) .and. (phi_l(k) /= phi_r(k)))
then
767 hfrac = h_means(k)*inv_heff
768 f_layer(k) = f_bulk * hfrac
778 f_max = -0.2 * ((area_r*(phi_r(k)*h_r(k)))-(area_l*(phi_l(k)*h_r(k))))
781 if ( sign(1.,f_bulk) == sign(1., f_max))
then
783 if (f_max >= 0.)
then
784 f_layer(k) = min(f_layer(k),f_max)
786 f_layer(k) = max(f_layer(k),f_max)
793 dphi = -(phi_r(k) - phi_l(k))
794 if (.not. sign(1.,f_bulk) == sign(1., dphi))
then
803 end subroutine fluxes_bulk_method
806 logical function near_boundary_unit_tests( verbose )
807 logical,
intent(in) :: verbose
810 integer,
parameter :: nk = 2
811 integer,
parameter :: deg = 1
812 integer,
parameter :: method = 1
813 real,
dimension(nk) :: phi_l, phi_r
814 real,
dimension(nk) :: phi_l_avg, phi_r_avg
815 real,
dimension(nk,deg+1) :: phi_pp_l, phi_pp_r
818 real,
dimension(nk,2) :: ppoly0_e_l, ppoly0_e_r
819 real,
dimension(nk) :: h_l, h_r
823 real,
dimension(nk) :: f_layer
828 character(len=120) :: test_name
833 real :: area_l,area_r
834 area_l = 1.; area_r = 1.
836 near_boundary_unit_tests = .false.
839 test_name =
'Surface boundary spans the entire top cell'
841 call boundary_k_range(surface, nk, h_l, 5., k_top, zeta_top, k_bot, zeta_bot)
842 near_boundary_unit_tests = near_boundary_unit_tests .or. &
843 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 0., 1, 1., test_name, verbose)
845 test_name =
'Surface boundary spans the entire column'
847 call boundary_k_range(surface, nk, h_l, 10., k_top, zeta_top, k_bot, zeta_bot)
848 near_boundary_unit_tests = near_boundary_unit_tests .or. &
849 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 0., 2, 1., test_name, verbose)
851 test_name =
'Bottom boundary spans the entire bottom cell'
853 call boundary_k_range(bottom, nk, h_l, 5., k_top, zeta_top, k_bot, zeta_bot)
854 near_boundary_unit_tests = near_boundary_unit_tests .or. &
855 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 2, 1., 2, 0., test_name, verbose)
857 test_name =
'Bottom boundary spans the entire column'
859 call boundary_k_range(bottom, nk, h_l, 10., k_top, zeta_top, k_bot, zeta_bot)
860 near_boundary_unit_tests = near_boundary_unit_tests .or. &
861 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 1., 2, 0., test_name, verbose)
863 test_name =
'Surface boundary intersects second layer'
865 call boundary_k_range(surface, nk, h_l, 17.5, k_top, zeta_top, k_bot, zeta_bot)
866 near_boundary_unit_tests = near_boundary_unit_tests .or. &
867 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 0., 2, 0.75, test_name, verbose)
869 test_name =
'Surface boundary intersects first layer'
871 call boundary_k_range(surface, nk, h_l, 2.5, k_top, zeta_top, k_bot, zeta_bot)
872 near_boundary_unit_tests = near_boundary_unit_tests .or. &
873 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 0., 1, 0.25, test_name, verbose)
875 test_name =
'Surface boundary is deeper than column thickness'
877 call boundary_k_range(surface, nk, h_l, 21.0, k_top, zeta_top, k_bot, zeta_bot)
878 near_boundary_unit_tests = near_boundary_unit_tests .or. &
879 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 0., 2, 1., test_name, verbose)
881 test_name =
'Bottom boundary intersects first layer'
883 call boundary_k_range(bottom, nk, h_l, 17.5, k_top, zeta_top, k_bot, zeta_bot)
884 near_boundary_unit_tests = near_boundary_unit_tests .or. &
885 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 1, 0.75, 2, 0., test_name, verbose)
887 test_name =
'Bottom boundary intersects second layer'
889 call boundary_k_range(bottom, nk, h_l, 2.5, k_top, zeta_top, k_bot, zeta_bot)
890 near_boundary_unit_tests = near_boundary_unit_tests .or. &
891 test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, 2, 0.25, 2, 0., test_name, verbose)
894 test_name =
'Equal hbl and same layer thicknesses (gradient from right to left)'
895 hbl_l = 10; hbl_r = 10
896 h_l = (/5.,5./) ; h_r = (/5.,5./)
897 phi_l = (/0.,0./) ; phi_r = (/1.,1./)
898 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
899 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
900 phi_pp_r(1,1) = 1.; phi_pp_r(1,2) = 0.
901 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
902 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
903 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
904 ppoly0_e_r(1,1) = 1.; ppoly0_e_r(1,2) = 1.
905 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
908 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r, &
909 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
910 near_boundary_unit_tests = near_boundary_unit_tests .or. &
911 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-5.0,-5.0/) )
914 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r, &
915 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer, .true.)
916 near_boundary_unit_tests = near_boundary_unit_tests .or. &
917 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-1.0,-1.0/) )
919 test_name =
'Equal hbl and same layer thicknesses (gradient from left to right)'
920 hbl_l = 10.; hbl_r = 10.
921 h_l = (/5.,5./) ; h_r = (/5.,5./)
922 phi_l = (/1.,1./) ; phi_r = (/0.,0./)
923 phi_pp_l(1,1) = 1.; phi_pp_l(1,2) = 0.
924 phi_pp_l(2,1) = 1.; phi_pp_l(2,2) = 0.
925 phi_pp_r(1,1) = 0.; phi_pp_r(1,2) = 0.
926 phi_pp_r(2,1) = 0.; phi_pp_r(2,2) = 0.
927 ppoly0_e_l(1,1) = 1.; ppoly0_e_l(1,2) = 1.
928 ppoly0_e_l(2,1) = 1.; ppoly0_e_l(2,2) = 1.
929 ppoly0_e_r(1,1) = 0.; ppoly0_e_r(1,2) = 0.
930 ppoly0_e_r(2,1) = 0.; ppoly0_e_r(2,2) = 0.
932 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
933 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
934 near_boundary_unit_tests = near_boundary_unit_tests .or. &
935 test_layer_fluxes( verbose, nk, test_name, f_layer, (/5.0,5.0/) )
937 test_name =
'Equal hbl and same layer thicknesses (no gradient)'
938 hbl_l = 10; hbl_r = 10
939 h_l = (/5.,5./) ; h_r = (/5.,5./)
940 phi_l = (/1.,1./) ; phi_r = (/1.,1./)
941 phi_pp_l(1,1) = 1.; phi_pp_l(1,2) = 0.
942 phi_pp_l(2,1) = 1.; phi_pp_l(2,2) = 0.
943 phi_pp_r(1,1) = 1.; phi_pp_r(1,2) = 0.
944 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
945 ppoly0_e_l(1,1) = 1.; ppoly0_e_l(1,2) = 1.
946 ppoly0_e_l(2,1) = 1.; ppoly0_e_l(2,2) = 1.
947 ppoly0_e_r(1,1) = 1.; ppoly0_e_r(1,2) = 1.
948 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
950 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
951 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
952 near_boundary_unit_tests = near_boundary_unit_tests .or. &
953 test_layer_fluxes( verbose, nk, test_name, f_layer, (/0.0,0.0/) )
955 test_name =
'Equal hbl and different layer thicknesses (gradient right to left)'
956 hbl_l = 16.; hbl_r = 16.
957 h_l = (/10.,6./) ; h_r = (/6.,10./)
958 phi_l = (/0.,0./) ; phi_r = (/1.,1./)
959 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
960 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
961 phi_pp_r(1,1) = 1.; phi_pp_r(1,2) = 0.
962 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
963 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
964 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
965 ppoly0_e_r(1,1) = 1.; ppoly0_e_r(1,2) = 1.
966 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
968 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
969 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
970 near_boundary_unit_tests = near_boundary_unit_tests .or. &
971 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-8.0,-8.0/) )
973 test_name =
'Equal hbl and same layer thicknesses (diagonal tracer values)'
974 hbl_l = 10.; hbl_r = 10.
975 h_l = (/5.,5./) ; h_r = (/5.,5./)
976 phi_l = (/1.,0./) ; phi_r = (/0.,1./)
977 phi_pp_l(1,1) = 1.; phi_pp_l(1,2) = 0.
978 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
979 phi_pp_r(1,1) = 0.; phi_pp_r(1,2) = 0.
980 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
981 ppoly0_e_l(1,1) = 1.; ppoly0_e_l(1,2) = 1.
982 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
983 ppoly0_e_r(1,1) = 0.; ppoly0_e_r(1,2) = 0.
984 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
986 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
987 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
988 near_boundary_unit_tests = near_boundary_unit_tests .or. &
989 test_layer_fluxes( verbose, nk, test_name, f_layer, (/0.0,0.0/) )
991 test_name =
'Different hbl and different layer thicknesses (gradient from right to left)'
992 hbl_l = 12; hbl_r = 20
993 h_l = (/6.,6./) ; h_r = (/10.,10./)
994 phi_l = (/0.,0./) ; phi_r = (/1.,1./)
995 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
996 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
997 phi_pp_r(1,1) = 1.; phi_pp_r(1,2) = 0.
998 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
999 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
1000 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
1001 ppoly0_e_r(1,1) = 1.; ppoly0_e_r(1,2) = 1.
1002 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
1004 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
1005 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
1006 near_boundary_unit_tests = near_boundary_unit_tests .or. &
1007 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-7.5,-7.5/) )
1011 test_name =
'hbl < column thickness, hbl same, constant concentration each column'
1012 hbl_l = 2; hbl_r = 2
1013 h_l = (/1.,2./) ; h_r = (/1.,2./)
1014 phi_l = (/0.,0./) ; phi_r = (/1.,1./)
1015 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
1016 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
1017 phi_pp_r(1,1) = 1.; phi_pp_r(1,2) = 0.
1018 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
1019 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
1020 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
1021 ppoly0_e_r(1,1) = 1.; ppoly0_e_r(1,2) = 1.
1022 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
1024 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
1025 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
1026 near_boundary_unit_tests = near_boundary_unit_tests .or. &
1027 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-1.,-1./) )
1029 test_name =
'hbl < column thickness, hbl same, linear profile right'
1030 hbl_l = 2; hbl_r = 2
1031 h_l = (/1.,2./) ; h_r = (/1.,2./)
1032 phi_l = (/0.,0./) ; phi_r = (/0.5,2./)
1033 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
1034 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
1035 phi_pp_r(1,1) = 0.; phi_pp_r(1,2) = 1.
1036 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 2.
1038 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
1039 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
1040 ppoly0_e_r(1,1) = 0.; ppoly0_e_r(1,2) = 1.
1041 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 3.
1042 call fluxes_bulk_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, phi_pp_r,&
1043 ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_bulk, f_layer)
1044 near_boundary_unit_tests = near_boundary_unit_tests .or. &
1045 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-1.,-1./) )
1047 test_name =
'hbl < column thickness, hbl same, linear profile right, khtr=2'
1048 hbl_l = 2; hbl_r = 2
1049 h_l = (/1.,2./) ; h_r = (/1.,2./)
1050 phi_l = (/0.,0./) ; phi_r = (/0.5,2./)
1051 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
1052 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
1053 phi_pp_r(1,1) = 0.; phi_pp_r(1,2) = 1.
1054 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 2.
1056 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
1057 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
1058 ppoly0_e_r(1,1) = 0.; ppoly0_e_r(1,2) = 1.
1059 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 3.
1060 call fluxes_layer_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, &
1061 phi_pp_r, ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_layer)
1062 near_boundary_unit_tests = near_boundary_unit_tests .or. &
1063 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-1.,-3./) )
1066 test_name =
'Different hbl and different column thicknesses (gradient from right to left)'
1067 hbl_l = 12; hbl_r = 20
1068 h_l = (/6.,6./) ; h_r = (/10.,10./)
1069 phi_l = (/0.,0./) ; phi_r = (/1.,1./)
1070 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
1071 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
1072 phi_pp_r(1,1) = 1.; phi_pp_r(1,2) = 0.
1073 phi_pp_r(2,1) = 1.; phi_pp_r(2,2) = 0.
1074 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
1075 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
1076 ppoly0_e_r(1,1) = 1.; ppoly0_e_r(1,2) = 1.
1077 ppoly0_e_r(2,1) = 1.; ppoly0_e_r(2,2) = 1.
1079 call fluxes_layer_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, &
1080 phi_pp_r, ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_layer)
1081 near_boundary_unit_tests = near_boundary_unit_tests .or. &
1082 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-7.5,-7.5/) )
1084 test_name =
'Different hbl and different column thicknesses (linear profile right)'
1086 hbl_l = 15; hbl_r = 6
1087 h_l = (/10.,10./) ; h_r = (/12.,10./)
1088 phi_l = (/0.,0./) ; phi_r = (/1.,3./)
1089 phi_pp_l(1,1) = 0.; phi_pp_l(1,2) = 0.
1090 phi_pp_l(2,1) = 0.; phi_pp_l(2,2) = 0.
1091 phi_pp_r(1,1) = 0.; phi_pp_r(1,2) = 2.
1092 phi_pp_r(2,1) = 2.; phi_pp_r(2,2) = 2.
1093 ppoly0_e_l(1,1) = 0.; ppoly0_e_l(1,2) = 0.
1094 ppoly0_e_l(2,1) = 0.; ppoly0_e_l(2,2) = 0.
1095 ppoly0_e_r(1,1) = 0.; ppoly0_e_r(1,2) = 2.
1096 ppoly0_e_r(2,1) = 2.; ppoly0_e_r(2,2) = 4.
1098 call fluxes_layer_method(surface, nk, deg, h_l, h_r, hbl_l, hbl_r, area_l, area_r, phi_l, phi_r, phi_pp_l, &
1099 phi_pp_r, ppoly0_e_l, ppoly0_e_r, method, khtr_u, f_layer)
1100 near_boundary_unit_tests = near_boundary_unit_tests .or. &
1101 test_layer_fluxes( verbose, nk, test_name, f_layer, (/-3.75,0.0/) )
1102 end function near_boundary_unit_tests
1106 logical function test_layer_fluxes(verbose, nk, test_name, F_calc, F_ans)
1107 logical,
intent(in) :: verbose
1108 character(len=80),
intent(in) :: test_name
1109 integer,
intent(in) :: nk
1110 real,
dimension(nk),
intent(in) :: f_calc
1111 real,
dimension(nk),
intent(in) :: f_ans
1114 integer,
parameter :: stdunit = stdout
1116 test_layer_fluxes = .false.
1118 if ( f_calc(k) /= f_ans(k) )
then
1119 test_layer_fluxes = .true.
1120 write(stdunit,*)
"MOM_lateral_boundary_diffusion, UNIT TEST FAILED: ", test_name
1121 write(stdunit,10) k, f_calc(k), f_ans(k)
1122 elseif (verbose)
then
1123 write(stdunit,10) k, f_calc(k), f_ans(k)
1127 10
format(
"Layer=",i3,
" F_calc=",f20.16,
" F_ans",f20.16)
1128 end function test_layer_fluxes
1131 logical function test_boundary_k_range(k_top, zeta_top, k_bot, zeta_bot, k_top_ans, zeta_top_ans,&
1132 k_bot_ans, zeta_bot_ans, test_name, verbose)
1137 integer :: k_top_ans
1138 real :: zeta_top_ans
1139 integer :: k_bot_ans
1140 real :: zeta_bot_ans
1141 character(len=80) :: test_name
1144 integer,
parameter :: stdunit = stdout
1146 test_boundary_k_range = k_top .ne. k_top_ans
1147 test_boundary_k_range = test_boundary_k_range .or. (zeta_top .ne. zeta_top_ans)
1148 test_boundary_k_range = test_boundary_k_range .or. (k_bot .ne. k_bot_ans)
1149 test_boundary_k_range = test_boundary_k_range .or. (zeta_bot .ne. zeta_bot_ans)
1151 if (test_boundary_k_range)
write(stdunit,*)
"UNIT TEST FAILED: ", test_name
1152 if (test_boundary_k_range .or. verbose)
then
1153 write(stdunit,20)
"k_top", k_top,
"k_top_ans", k_top_ans
1154 write(stdunit,20)
"k_bot", k_bot,
"k_bot_ans", k_bot_ans
1155 write(stdunit,30)
"zeta_top", zeta_top,
"zeta_top_ans", zeta_top_ans
1156 write(stdunit,30)
"zeta_bot", zeta_bot,
"zeta_bot_ans", zeta_bot_ans
1159 20
format(a,
"=",i3,x,a,
"=",i3)
1160 30
format(a,
"=",f20.16,x,a,
"=",f20.16)
1163 end function test_boundary_k_range