MOM6
mom_lateral_boundary_diffusion Module Reference

Detailed Description

Calculates and applies diffusive fluxes as a parameterization of lateral mixing (non-neutral) by mesoscale eddies near the top and bottom (to be implemented) boundary layers of the ocean.

The Lateral Boundary Diffusion (LBD) framework

The LBD framework accounts for the effects of diabatic mesoscale fluxes within surface and bottom boundary layers. Unlike the equivalent adiabatic fluxes, which is applied along neutral density surfaces, LBD is purely horizontal.

The bottom boundary layer fluxes remain to be implemented, although most of the steps needed to do so have already been added and tested.

Boundary lateral diffusion can be applied using one of the three methods:

A brief summary of these methods is provided below.

Along layer approach (Method #1)

This is the recommended and more straight forward method where diffusion is applied layer by layer using only information from neighboring cells.

Step #1: compute vertical indices containing boundary layer (boundary_k_range). For the TOP boundary layer, these are:

k_top, k_bot, zeta_top, zeta_bot

Step #2: calculate the diffusive flux at each layer:

\[ F_{k} = -KHTR \times h_{eff}(k) \times (\phi_R(k) - \phi_L(k)), \]

where h_eff is the harmonic mean of the layer thickness in the left and right columns. This method does not require a limiter since KHTR is already limted based on a diffusive CFL condition prior to the call of this module.

Step #3: option to linearly decay the flux from k_bot_min to k_bot_max:

If LBD_LINEAR_TRANSITION = True and k_bot_diff > 1, the diffusive flux will decay linearly between the top interface of the layer containing the minimum boundary layer depth (k_bot_min) and the lower interface of the layer containing the maximum layer depth (k_bot_max).

Bulk layer approach (Method #2)

Apply the lateral boundary diffusive fluxes calculated from a 'bulk model'.This is a lower order representation (Kraus-Turner like approach) which assumes that eddies are acting along well mixed layers (i.e., eddies do not know care about vertical tracer gradients within the boundary layer).

Step #1: compute vertical indices containing boundary layer (boundary_k_range). For the TOP boundary layer, these are:

k_top, k_bot, zeta_top, zeta_bot

Step #2: compute bulk averages (thickness weighted) tracer averages (phi_L and phi_R), then calculate the bulk diffusive flux (F_{bulk}):

\[ F_{bulk} = -KHTR \times h_{eff} \times (\phi_R - \phi_L), \]

where h_eff is the harmonic mean of the boundary layer depth in the left and right columns (

\[ HBL_L \]

and

\[ HBL_R \]

, respectively).

Step #3: decompose F_bulk onto individual layers:

\[ F_{layer}(k) = F_{bulk} \times h_{frac}(k) , \]

where h_{frac} is

\[ h_{frac}(k) = h_u(k) \times \frac{1}{\sum(h_u)}. \]

h_u is the harmonic mean of thicknesses at each layer. Special care (layer reconstruction) must be taken at k_min = min(k_botL, k_bot_R).

Step #4: option to linearly decay the flux from k_bot_min to k_bot_max:

If LBD_LINEAR_TRANSITION = True and k_bot_diff > 1, the diffusive flux will decay linearly between the top interface of the layer containing the minimum boundary layer depth (k_bot_min) and the lower interface of the layer containing the maximum layer depth (k_bot_max).

Step #5: limit the tracer flux so that 1) only down-gradient fluxes are applied, and 2) the flux cannot be larger than F_max, which is defined using the tracer gradient:

\[ F_{max} = -0.2 \times [(V_R(k) \times \phi_R(k)) - (V_L(k) \times \phi_L(k))], \]

where V is the cell volume. Why 0.2? t=0 t=inf 0 .2 0 1 0 .2.2.2 0 .2

Harmonic Mean

The harmonic mean (HM) betwen h1 and h2 is defined as:

\[ HM = \frac{2 \times h1 \times h2}{h1 + h2} \]

Data Types

type  lateral_boundary_diffusion_cs
 Sets parameters for lateral boundary mixing module. More...
 

Functions/Subroutines

logical function, public lateral_boundary_diffusion_init (Time, G, param_file, diag, diabatic_CSp, CS)
 Initialization routine that reads runtime parameters and sets up pointers to other control structures that might be needed for lateral boundary diffusion. More...
 
subroutine, public lateral_boundary_diffusion (G, GV, US, h, Coef_x, Coef_y, dt, Reg, CS)
 Driver routine for calculating lateral diffusive fluxes near the top and bottom boundaries. Two different methods are available: Method 1: lower order representation, calculate fluxes from bulk layer integrated quantities. Method 2: more straight forward, diffusion is applied layer by layer using only information from neighboring cells. More...
 
real function bulk_average (boundary, nk, deg, h, hBLT, phi, ppoly0_E, ppoly0_coefs, method, k_top, zeta_top, k_bot, zeta_bot)
 
real function harmonic_mean (h1, h2)
 Calculate the harmonic mean of two quantities See Harmonic Mean. More...
 
subroutine, public boundary_k_range (boundary, nk, h, hbl, k_top, zeta_top, k_bot, zeta_bot)
 Find the k-index range corresponding to the layers that are within the boundary-layer region. More...
 
subroutine fluxes_layer_method (boundary, nk, deg, h_L, h_R, hbl_L, hbl_R, area_L, area_R, phi_L, phi_R, ppoly0_coefs_L, ppoly0_coefs_R, ppoly0_E_L, ppoly0_E_R, method, khtr_u, F_layer, linear_decay)
 Calculate the lateral boundary diffusive fluxes using the layer by layer method. See Along layer approach (Method #1). More...
 
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, ppoly0_coefs_R, ppoly0_E_L, ppoly0_E_R, method, khtr_u, F_bulk, F_layer, F_limit, linear_decay)
 Apply the lateral boundary diffusive fluxes calculated from a 'bulk model' See Bulk layer approach (Method #2). More...
 
logical function, public near_boundary_unit_tests (verbose)
 Unit tests for near-boundary horizontal mixing. More...
 
logical function test_layer_fluxes (verbose, nk, test_name, F_calc, F_ans)
 Returns true if output of near-boundary unit tests does not match correct computed values and conditionally writes results to stream. More...
 
logical function test_boundary_k_range (k_top, zeta_top, k_bot, zeta_bot, k_top_ans, zeta_top_ans, k_bot_ans, zeta_bot_ans, test_name, verbose)
 Return true if output of unit tests for boundary_k_range does not match answers. More...
 

Variables

integer, parameter, public surface = -1
 Set a value that corresponds to the surface bopundary.
 
integer, parameter, public bottom = 1
 Set a value that corresponds to the bottom boundary.
 
character(len=40) mdl = "MOM_lateral_boundary_diffusion"
 Name of this module.
 

Function/Subroutine Documentation

◆ boundary_k_range()

subroutine, public mom_lateral_boundary_diffusion::boundary_k_range ( integer, intent(in)  boundary,
integer, intent(in)  nk,
real, dimension(nk), intent(in)  h,
real, intent(in)  hbl,
integer, intent(out)  k_top,
real, intent(out)  zeta_top,
integer, intent(out)  k_bot,
real, intent(out)  zeta_bot 
)

Find the k-index range corresponding to the layers that are within the boundary-layer region.

Parameters
[in]boundarySURFACE or BOTTOM [nondim]
[in]nkNumber of layers [nondim]
[in]hLayer thicknesses of the column [H ~> m or kg m-2]
[in]hblThickness of the boundary layer [H ~> m or kg m-2] If surface, with respect to zbl_ref = 0. If bottom, with respect to zbl_ref = SUM(h)
[out]k_topIndex of the first layer within the boundary
[out]zeta_topDistance from the top of a layer to the intersection of the top extent of the boundary layer (0 at top, 1 at bottom) [nondim]
[out]k_botIndex of the last layer within the boundary
[out]zeta_botDistance of the lower layer to the boundary layer depth (0 at top, 1 at bottom) [nondim]

Definition at line 375 of file MOM_lateral_boundary_diffusion.F90.

375  integer, intent(in ) :: boundary !< SURFACE or BOTTOM [nondim]
376  integer, intent(in ) :: nk !< Number of layers [nondim]
377  real, dimension(nk), intent(in ) :: h !< Layer thicknesses of the column [H ~> m or kg m-2]
378  real, intent(in ) :: hbl !< Thickness of the boundary layer [H ~> m or kg m-2]
379  !! If surface, with respect to zbl_ref = 0.
380  !! If bottom, with respect to zbl_ref = SUM(h)
381  integer, intent( out) :: k_top !< Index of the first layer within the boundary
382  real, intent( out) :: zeta_top !< Distance from the top of a layer to the intersection of the
383  !! top extent of the boundary layer (0 at top, 1 at bottom) [nondim]
384  integer, intent( out) :: k_bot !< Index of the last layer within the boundary
385  real, intent( out) :: zeta_bot !< Distance of the lower layer to the boundary layer depth
386  !! (0 at top, 1 at bottom) [nondim]
387  ! Local variables
388  real :: htot ! Summed thickness [H ~> m or kg m-2]
389  integer :: k
390  ! Surface boundary layer
391  if ( boundary == surface ) then
392  k_top = 1
393  zeta_top = 0.
394  htot = 0.
395  k_bot = 1
396  zeta_bot = 0.
397  if (hbl == 0.) return
398  if (hbl >= sum(h(:))) then
399  k_bot = nk
400  zeta_bot = 1.
401  return
402  endif
403  do k=1,nk
404  htot = htot + h(k)
405  if ( htot >= hbl) then
406  k_bot = k
407  zeta_bot = 1 - (htot - hbl)/h(k)
408  return
409  endif
410  enddo
411  ! Bottom boundary layer
412  elseif ( boundary == bottom ) then
413  k_top = nk
414  zeta_top = 1.
415  k_bot = nk
416  zeta_bot = 0.
417  htot = 0.
418  if (hbl == 0.) return
419  if (hbl >= sum(h(:))) then
420  k_top = 1
421  zeta_top = 1.
422  return
423  endif
424  do k=nk,1,-1
425  htot = htot + h(k)
426  if (htot >= hbl) then
427  k_top = k
428  zeta_top = 1 - (htot - hbl)/h(k)
429  return
430  endif
431  enddo
432  else
433  call mom_error(fatal,"Houston, we've had a problem in boundary_k_range")
434  endif
435 

◆ bulk_average()

real function mom_lateral_boundary_diffusion::bulk_average ( integer  boundary,
integer  nk,
integer  deg,
real, dimension(nk)  h,
real  hBLT,
real, dimension(nk)  phi,
real, dimension(nk,2)  ppoly0_E,
real, dimension(nk,deg+1)  ppoly0_coefs,
integer  method,
integer  k_top,
real  zeta_top,
integer  k_bot,
real  zeta_bot 
)
private
Parameters
boundarySURFACE or BOTTOM [nondim]
nkNumber of layers [nondim]
degDegree of polynomial [nondim]
hLayer thicknesses [H ~> m or kg m-2]
hbltDepth of the boundary layer [H ~> m or kg m-2]
phiScalar quantity
ppoly0_eEdge value of polynomial
ppoly0_coefsCoefficients of polynomial
methodRemapping scheme to use
k_topIndex of the first layer within the boundary
zeta_topFraction of the layer encompassed by the bottom boundary layer (0 if none, 1. if all). For the surface, this is always 0. because integration starts at the surface [nondim]
k_botIndex of the last layer within the boundary
zeta_botFraction of the layer encompassed by the surface boundary layer (0 if none, 1. if all). For the bottom boundary layer, this is always 1. because integration starts at the bottom [nondim]

Definition at line 312 of file MOM_lateral_boundary_diffusion.F90.

312  integer :: boundary !< SURFACE or BOTTOM [nondim]
313  integer :: nk !< Number of layers [nondim]
314  integer :: deg !< Degree of polynomial [nondim]
315  real, dimension(nk) :: h !< Layer thicknesses [H ~> m or kg m-2]
316  real :: hblt !< Depth of the boundary layer [H ~> m or kg m-2]
317  real, dimension(nk) :: phi !< Scalar quantity
318  real, dimension(nk,2) :: ppoly0_e !< Edge value of polynomial
319  real, dimension(nk,deg+1) :: ppoly0_coefs!< Coefficients of polynomial
320  integer :: method !< Remapping scheme to use
321 
322  integer :: k_top !< Index of the first layer within the boundary
323  real :: zeta_top !< Fraction of the layer encompassed by the bottom boundary layer
324  !! (0 if none, 1. if all). For the surface, this is always 0. because
325  !! integration starts at the surface [nondim]
326  integer :: k_bot !< Index of the last layer within the boundary
327  real :: zeta_bot !< Fraction of the layer encompassed by the surface boundary layer
328  !! (0 if none, 1. if all). For the bottom boundary layer, this is always 1.
329  !! because integration starts at the bottom [nondim]
330  ! Local variables
331  real :: htot !< Running sum of the thicknesses (top to bottom)
332  integer :: k !< k indice
333 
334 
335  htot = 0.
336  bulk_average = 0.
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
341  do k = k_bot-1,1,-1
342  bulk_average = bulk_average + phi(k)*h(k)
343  htot = htot + h(k)
344  enddo
345  elseif (boundary == bottom) then
346  htot = (h(k_top) * zeta_top)
347  ! (note 1-zeta_top because zeta_top is the fraction of the layer)
348  bulk_average = average_value_ppoly( nk, phi, ppoly0_e, ppoly0_coefs, method, k_top, (1.-zeta_top), 1.) * htot
349  do k = k_top+1,nk
350  bulk_average = bulk_average + phi(k)*h(k)
351  htot = htot + h(k)
352  enddo
353  else
354  call mom_error(fatal, "bulk_average: a valid boundary type must be provided.")
355  endif
356 
357  bulk_average = bulk_average / hblt
358 

◆ fluxes_bulk_method()

subroutine mom_lateral_boundary_diffusion::fluxes_bulk_method ( integer, intent(in)  boundary,
integer, intent(in)  nk,
integer, intent(in)  deg,
real, dimension(nk), intent(in)  h_L,
real, dimension(nk), intent(in)  h_R,
real, intent(in)  hbl_L,
real, intent(in)  hbl_R,
real, intent(in)  area_L,
real, intent(in)  area_R,
real, dimension(nk), intent(in)  phi_L,
real, dimension(nk), intent(in)  phi_R,
real, dimension(nk,deg+1), intent(in)  ppoly0_coefs_L,
real, dimension(nk,deg+1), intent(in)  ppoly0_coefs_R,
real, dimension(nk,2), intent(in)  ppoly0_E_L,
real, dimension(nk,2), intent(in)  ppoly0_E_R,
integer, intent(in)  method,
real, intent(in)  khtr_u,
real, intent(out)  F_bulk,
real, dimension(nk), intent(out)  F_layer,
logical, intent(in), optional  F_limit,
logical, intent(in), optional  linear_decay 
)
private

Apply the lateral boundary diffusive fluxes calculated from a 'bulk model' See Bulk layer approach (Method #2).

Parameters
[in]boundaryWhich boundary layer SURFACE or BOTTOM [nondim]
[in]nkNumber of layers [nondim]
[in]degorder of the polynomial reconstruction [nondim]
[in]h_lLayer thickness (left) [H ~> m or kg m-2]
[in]h_rLayer thickness (right) [H ~> m or kg m-2]
[in]hbl_lThickness of the boundary boundary layer (left) [H ~> m or kg m-2]
[in]hbl_rThickness of the boundary boundary layer (left) [H ~> m or kg m-2]
[in]area_lArea of the horizontal grid (left) [L2 ~> m2]
[in]area_rArea of the horizontal grid (right) [L2 ~> m2]
[in]phi_lTracer values (left) [conc]
[in]phi_rTracer values (right) [conc]
[in]ppoly0_coefs_lTracer reconstruction (left) [conc]
[in]ppoly0_coefs_rTracer reconstruction (right) [conc]
[in]ppoly0_e_lPolynomial edge values (left) [nondim]
[in]ppoly0_e_rPolynomial edge values (right) [nondim]
[in]methodMethod of polynomial integration [nondim]
[in]khtr_uHorizontal diffusivities times delta t at a velocity point [L2 ~> m2]
[out]f_bulkThe bulk mixed layer lateral flux [H L2 conc ~> m3 conc]
[out]f_layerLayerwise diffusive flux at U- or V-point [H L2 conc ~> m3 conc]
[in]f_limitIf True, apply a limiter
[in]linear_decayIf True, apply a linear transition at the base of the boundary layer

Definition at line 598 of file MOM_lateral_boundary_diffusion.F90.

598 
599  integer, intent(in ) :: boundary !< Which boundary layer SURFACE or BOTTOM [nondim]
600  integer, intent(in ) :: nk !< Number of layers [nondim]
601  integer, intent(in ) :: deg !< order of the polynomial reconstruction [nondim]
602  real, dimension(nk), intent(in ) :: h_l !< Layer thickness (left) [H ~> m or kg m-2]
603  real, dimension(nk), intent(in ) :: h_r !< Layer thickness (right) [H ~> m or kg m-2]
604  real, intent(in ) :: hbl_l !< Thickness of the boundary boundary
605  !! layer (left) [H ~> m or kg m-2]
606  real, intent(in ) :: hbl_r !< Thickness of the boundary boundary
607  !! layer (left) [H ~> m or kg m-2]
608  real, intent(in ) :: area_l !< Area of the horizontal grid (left) [L2 ~> m2]
609  real, intent(in ) :: area_r !< Area of the horizontal grid (right) [L2 ~> m2]
610  real, dimension(nk), intent(in ) :: phi_l !< Tracer values (left) [conc]
611  real, dimension(nk), intent(in ) :: phi_r !< Tracer values (right) [conc]
612  real, dimension(nk,deg+1), intent(in ) :: ppoly0_coefs_l !< Tracer reconstruction (left) [conc]
613  real, dimension(nk,deg+1), intent(in ) :: ppoly0_coefs_r !< Tracer reconstruction (right) [conc]
614  real, dimension(nk,2), intent(in ) :: ppoly0_e_l !< Polynomial edge values (left) [nondim]
615  real, dimension(nk,2), intent(in ) :: ppoly0_e_r !< Polynomial edge values (right) [nondim]
616  integer, intent(in ) :: method !< Method of polynomial integration [nondim]
617  real, intent(in ) :: khtr_u !< Horizontal diffusivities times delta t
618  !! at a velocity point [L2 ~> m2]
619  real, intent( out) :: f_bulk !< The bulk mixed layer lateral flux
620  !! [H L2 conc ~> m3 conc]
621  real, dimension(nk), intent( out) :: f_layer !< Layerwise diffusive flux at U- or V-point
622  !! [H L2 conc ~> m3 conc]
623  logical, optional, intent(in ) :: f_limit !< If True, apply a limiter
624  logical, optional, intent(in ) :: linear_decay !< If True, apply a linear transition at the base of
625  !! the boundary layer
626 
627  ! Local variables
628  real, dimension(nk) :: h_means !< Calculate the layer-wise harmonic means [H ~> m or kg m-2]
629  real :: khtr_avg !< Thickness-weighted diffusivity at the u-point [m^2 s^-1]
630  !! This is just to remind developers that khtr_avg should be
631  !! computed once khtr is 3D.
632  real :: heff !< Harmonic mean of layer thicknesses [H ~> m or kg m-2]
633  real :: heff_tot !< Total effective column thickness in the transition layer [m]
634  real :: inv_heff !< Inverse of the harmonic mean of layer thicknesses
635  !! [H-1 ~> m-1 or m2 kg-1]
636  real :: phi_l_avg, phi_r_avg !< Bulk, thickness-weighted tracer averages (left and right column)
637  !! [conc m^-3 ]
638  real :: htot ! Total column thickness [H ~> m or kg m-2]
639  integer :: k, k_min, k_max !< k-indices, min and max for top and bottom, respectively
640  integer :: k_diff !< difference between k_max and k_min
641  integer :: k_top_l, k_bot_l !< k-indices left
642  integer :: k_top_r, k_bot_r !< k-indices right
643  real :: zeta_top_l, zeta_top_r !< distance from the top of a layer to the
644  !! boundary layer [nondim]
645  real :: zeta_bot_l, zeta_bot_r !< distance from the bottom of a layer to the
646  !! boundary layer [nondim]
647  real :: h_work_l, h_work_r !< dummy variables
648  real :: f_max !< The maximum amount of flux that can leave a
649  !! cell [m^3 conc]
650  logical :: limiter !< True if flux limiter should be applied
651  logical :: linear !< True if apply a linear transition
652  real :: hfrac !< Layer fraction wrt sum of all layers [nondim]
653  real :: dphi !< tracer gradient [conc m^-3]
654  real :: wgt !< weight to be used in the linear transition to the
655  !! interior [nondim]
656  real :: a !< coefficient to be used in the linear transition to the
657  !! interior [nondim]
658 
659  f_bulk = 0.
660  f_layer(:) = 0.
661  if (hbl_l == 0. .or. hbl_r == 0.) then
662  return
663  endif
664 
665  limiter = .false.
666  if (PRESENT(f_limit)) then
667  limiter = f_limit
668  endif
669  linear = .false.
670  if (PRESENT(linear_decay)) then
671  linear = linear_decay
672  endif
673 
674  ! Calculate vertical indices containing the boundary layer
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)
677 
678  ! Calculate bulk averages of various quantities
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)
683  ! Calculate the 'bulk' diffusive flux from the bulk averaged quantities
684  ! GMM, khtr_avg should be computed once khtr is 3D
685  heff = harmonic_mean(hbl_l, hbl_r)
686  f_bulk = -(khtr_u * heff) * (phi_r_avg - phi_l_avg)
687  ! Calculate the layerwise sum of the vertical effective thickness. This is different than the heff calculated
688  ! above, but is used as a way to decompose the fluxes onto the individual layers
689  h_means(:) = 0.
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
695  do k=1,k_min
696  h_means(k) = harmonic_mean(h_l(k),h_r(k))
697  enddo
698  ! heff_total
699  heff_tot = 0.0
700  do k = k_min+1,k_max, 1
701  heff_tot = heff_tot + harmonic_mean(h_l(k), h_r(k))
702  enddo
703 
704  a = -1.0/heff_tot
705  heff_tot = 0.0
706  ! fluxes will decay linearly at base of hbl
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
712  enddo
713  else
714  ! left hand side
715  if (k_bot_l == k_min) then
716  h_work_l = h_l(k_min) * zeta_bot_l
717  else
718  h_work_l = h_l(k_min)
719  endif
720 
721  ! right hand side
722  if (k_bot_r == k_min) then
723  h_work_r = h_r(k_min) * zeta_bot_r
724  else
725  h_work_r = h_r(k_min)
726  endif
727 
728  h_means(k_min) = harmonic_mean(h_work_l,h_work_r)
729 
730  do k=1,k_min-1
731  h_means(k) = harmonic_mean(h_l(k),h_r(k))
732  enddo
733  endif
734 
735  elseif (boundary == bottom) then
736  !TODO, GMM linear decay is not implemented here
737  k_max = max(k_top_l, k_top_r)
738  ! left hand side
739  if (k_top_l == k_max) then
740  h_work_l = h_l(k_max) * zeta_top_l
741  else
742  h_work_l = h_l(k_max)
743  endif
744 
745  ! right hand side
746  if (k_top_r == k_max) then
747  h_work_r = h_r(k_max) * zeta_top_r
748  else
749  h_work_r = h_r(k_max)
750  endif
751 
752  h_means(k_max) = harmonic_mean(h_work_l,h_work_r)
753 
754  do k=nk,k_max+1,-1
755  h_means(k) = harmonic_mean(h_l(k),h_r(k))
756  enddo
757  endif
758 
759  if ( sum(h_means) == 0. .or. f_bulk == 0.) then
760  return
761  ! Decompose the bulk flux onto the individual layers
762  else
763  ! Initialize remaining thickness
764  inv_heff = 1./sum(h_means)
765  do k=1,nk
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
769 
770  if (limiter) then
771  ! limit the flux to 0.2 of the tracer *gradient*
772  ! Why 0.2?
773  ! t=0 t=inf
774  ! 0 .2
775  ! 0 1 0 .2.2.2
776  ! 0 .2
777  !
778  f_max = -0.2 * ((area_r*(phi_r(k)*h_r(k)))-(area_l*(phi_l(k)*h_r(k))))
779 
780  ! check if bulk flux (or F_layer) and F_max have same direction
781  if ( sign(1.,f_bulk) == sign(1., f_max)) then
782  ! Apply flux limiter calculated above
783  if (f_max >= 0.) then
784  f_layer(k) = min(f_layer(k),f_max)
785  else
786  f_layer(k) = max(f_layer(k),f_max)
787  endif
788  else
789  ! do not apply a flux on this layer
790  f_layer(k) = 0.
791  endif
792  else
793  dphi = -(phi_r(k) - phi_l(k))
794  if (.not. sign(1.,f_bulk) == sign(1., dphi)) then
795  ! upgradient, do not apply a flux on this layer
796  f_layer(k) = 0.
797  endif
798  endif ! limited
799  endif
800  enddo
801  endif
802 

◆ fluxes_layer_method()

subroutine mom_lateral_boundary_diffusion::fluxes_layer_method ( integer, intent(in)  boundary,
integer, intent(in)  nk,
integer, intent(in)  deg,
real, dimension(nk), intent(in)  h_L,
real, dimension(nk), intent(in)  h_R,
real, intent(in)  hbl_L,
real, intent(in)  hbl_R,
real, intent(in)  area_L,
real, intent(in)  area_R,
real, dimension(nk), intent(in)  phi_L,
real, dimension(nk), intent(in)  phi_R,
real, dimension(nk,deg+1), intent(in)  ppoly0_coefs_L,
real, dimension(nk,deg+1), intent(in)  ppoly0_coefs_R,
real, dimension(nk,2), intent(in)  ppoly0_E_L,
real, dimension(nk,2), intent(in)  ppoly0_E_R,
integer, intent(in)  method,
real, intent(in)  khtr_u,
real, dimension(nk), intent(out)  F_layer,
logical, intent(in), optional  linear_decay 
)
private

Calculate the lateral boundary diffusive fluxes using the layer by layer method. See Along layer approach (Method #1).

Parameters
[in]boundaryWhich boundary layer SURFACE or BOTTOM [nondim]
[in]nkNumber of layers [nondim]
[in]degorder of the polynomial reconstruction [nondim]
[in]h_lLayer thickness (left) [H ~> m or kg m-2]
[in]h_rLayer thickness (right) [H ~> m or kg m-2]
[in]hbl_lThickness of the boundary boundary layer (left) [H ~> m or kg m-2]
[in]hbl_rThickness of the boundary boundary layer (right) [H ~> m or kg m-2]
[in]area_lArea of the horizontal grid (left) [L2 ~> m2]
[in]area_rArea of the horizontal grid (right) [L2 ~> m2]
[in]phi_lTracer values (left) [conc]
[in]phi_rTracer values (right) [conc]
[in]ppoly0_coefs_lTracer reconstruction (left) [conc]
[in]ppoly0_coefs_rTracer reconstruction (right) [conc]
[in]ppoly0_e_lPolynomial edge values (left) [nondim]
[in]ppoly0_e_rPolynomial edge values (right) [nondim]
[in]methodMethod of polynomial integration [nondim]
[in]khtr_uHorizontal diffusivities times delta t at a velocity point [L2 ~> m2]
[out]f_layerLayerwise diffusive flux at U- or V-point [H L2 conc ~> m3 conc]
[in]linear_decayIf True, apply a linear transition at the base of the boundary layer

Definition at line 444 of file MOM_lateral_boundary_diffusion.F90.

444 
445  integer, intent(in ) :: boundary !< Which boundary layer SURFACE or BOTTOM [nondim]
446  integer, intent(in ) :: nk !< Number of layers [nondim]
447  integer, intent(in ) :: deg !< order of the polynomial reconstruction [nondim]
448  real, dimension(nk), intent(in ) :: h_l !< Layer thickness (left) [H ~> m or kg m-2]
449  real, dimension(nk), intent(in ) :: h_r !< Layer thickness (right) [H ~> m or kg m-2]
450  real, intent(in ) :: hbl_l !< Thickness of the boundary boundary
451  !! layer (left) [H ~> m or kg m-2]
452  real, intent(in ) :: hbl_r !< Thickness of the boundary boundary
453  !! layer (right) [H ~> m or kg m-2]
454  real, intent(in ) :: area_l !< Area of the horizontal grid (left) [L2 ~> m2]
455  real, intent(in ) :: area_r !< Area of the horizontal grid (right) [L2 ~> m2]
456  real, dimension(nk), intent(in ) :: phi_l !< Tracer values (left) [conc]
457  real, dimension(nk), intent(in ) :: phi_r !< Tracer values (right) [conc]
458  real, dimension(nk,deg+1), intent(in ) :: ppoly0_coefs_l !< Tracer reconstruction (left) [conc]
459  real, dimension(nk,deg+1), intent(in ) :: ppoly0_coefs_r !< Tracer reconstruction (right) [conc]
460  real, dimension(nk,2), intent(in ) :: ppoly0_e_l !< Polynomial edge values (left) [nondim]
461  real, dimension(nk,2), intent(in ) :: ppoly0_e_r !< Polynomial edge values (right) [nondim]
462  integer, intent(in ) :: method !< Method of polynomial integration [nondim]
463  real, intent(in ) :: khtr_u !< Horizontal diffusivities times delta t
464  !! at a velocity point [L2 ~> m2]
465  real, dimension(nk), intent( out) :: f_layer !< Layerwise diffusive flux at U- or V-point
466  !! [H L2 conc ~> m3 conc]
467  logical, optional, intent(in ) :: linear_decay !< If True, apply a linear transition at the base of
468  !! the boundary layer
469  ! Local variables
470  real, dimension(nk) :: h_means !< Calculate the layer-wise harmonic means [H ~> m or kg m-2]
471  real :: khtr_avg !< Thickness-weighted diffusivity at the u-point [m^2 s^-1]
472  !! This is just to remind developers that khtr_avg should be
473  !! computed once khtr is 3D.
474  real :: heff !< Harmonic mean of layer thicknesses [H ~> m or kg m-2]
475  real :: inv_heff !< Inverse of the harmonic mean of layer thicknesses
476  !! [H-1 ~> m-1 or m2 kg-1]
477  real :: phi_l_avg, phi_r_avg !< Bulk, thickness-weighted tracer averages (left and right column)
478  !! [conc m^-3 ]
479  real :: htot !< Total column thickness [H ~> m or kg m-2]
480  real :: heff_tot !< Total effective column thickness in the transition layer [m]
481  integer :: k, k_bot_min, k_top_max !< k-indices, min and max for bottom and top, respectively
482  integer :: k_bot_max, k_top_min !< k-indices, max and min for bottom and top, respectively
483  integer :: k_bot_diff, k_top_diff !< different between left and right k-indices for bottom and top, respectively
484  integer :: k_top_l, k_bot_l !< k-indices left
485  integer :: k_top_r, k_bot_r !< k-indices right
486  real :: zeta_top_l, zeta_top_r !< distance from the top of a layer to the boundary
487  !! layer depth [nondim]
488  real :: zeta_bot_l, zeta_bot_r !< distance from the bottom of a layer to the boundary
489  !!layer depth [nondim]
490  real :: h_work_l, h_work_r !< dummy variables
491  real :: hbl_min !< minimum BLD (left and right) [m]
492  real :: wgt !< weight to be used in the linear transition to the interior [nondim]
493  real :: a !< coefficient to be used in the linear transition to the interior [nondim]
494  logical :: linear !< True if apply a linear transition
495 
496  f_layer(:) = 0.0
497  if (hbl_l == 0. .or. hbl_r == 0.) then
498  return
499  endif
500 
501  linear = .false.
502  if (PRESENT(linear_decay)) then
503  linear = linear_decay
504  endif
505 
506  ! Calculate vertical indices containing the boundary layer
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)
509 
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)
514 
515  ! make sure left and right k indices span same range
516  if (k_bot_min .ne. k_bot_l) then
517  k_bot_l = k_bot_min
518  zeta_bot_l = 1.0
519  endif
520  if (k_bot_min .ne. k_bot_r) then
521  k_bot_r= k_bot_min
522  zeta_bot_r = 1.0
523  endif
524 
525  h_work_l = (h_l(k_bot_l) * zeta_bot_l)
526  h_work_r = (h_r(k_bot_r) * zeta_bot_r)
527 
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)
531  ! tracer flux where the minimum BLD intersets layer
532  ! GMM, khtr_avg should be computed once khtr is 3D
533  if ((linear) .and. (k_bot_diff .gt. 1)) then
534  ! apply linear decay at the base of hbl
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))
538  enddo
539  ! heff_total
540  heff_tot = 0.0
541  do k = k_bot_min+1,k_bot_max, 1
542  heff_tot = heff_tot + harmonic_mean(h_l(k), h_r(k))
543  enddo
544 
545  a = -1.0/heff_tot
546  heff_tot = 0.0
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
552  enddo
553  else
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))
558  enddo
559  endif
560  endif
561 
562  if (boundary == bottom) then
563  ! TODO: GMM add option to apply linear decay
564  k_top_max = max(k_top_l, k_top_r)
565  ! make sure left and right k indices span same range
566  if (k_top_max .ne. k_top_l) then
567  k_top_l = k_top_max
568  zeta_top_l = 1.0
569  endif
570  if (k_top_max .ne. k_top_r) then
571  k_top_r= k_top_max
572  zeta_top_r = 1.0
573  endif
574 
575  h_work_l = (h_l(k_top_l) * zeta_top_l)
576  h_work_r = (h_r(k_top_r) * zeta_top_r)
577 
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)
581 
582  ! tracer flux where the minimum BLD intersets layer
583  f_layer(k_top_max) = (-heff * khtr_u) * (phi_r_avg - phi_l_avg)
584 
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))
588  enddo
589  endif
590 

◆ harmonic_mean()

real function mom_lateral_boundary_diffusion::harmonic_mean ( real  h1,
real  h2 
)
private

Calculate the harmonic mean of two quantities See Harmonic Mean.

Parameters
h1Scalar quantity
h2Scalar quantity

Definition at line 364 of file MOM_lateral_boundary_diffusion.F90.

364  real :: h1 !< Scalar quantity
365  real :: h2 !< Scalar quantity
366  if (h1 + h2 == 0.) then
367  harmonic_mean = 0.
368  else
369  harmonic_mean = 2.*(h1*h2)/(h1+h2)
370  endif

◆ lateral_boundary_diffusion()

subroutine, public mom_lateral_boundary_diffusion::lateral_boundary_diffusion ( type(ocean_grid_type), intent(inout)  G,
type(verticalgrid_type), intent(in)  GV,
type(unit_scale_type), intent(in)  US,
real, dimension( g %isd: g %ied, g %jsd: g %jed, g %ke), intent(in)  h,
real, dimension( g %isdb: g %iedb, g %jsd: g %jed), intent(in)  Coef_x,
real, dimension( g %isd: g %ied, g %jsdb: g %jedb), intent(in)  Coef_y,
real, intent(in)  dt,
type(tracer_registry_type), pointer  Reg,
type(lateral_boundary_diffusion_cs), intent(in)  CS 
)

Driver routine for calculating lateral diffusive fluxes near the top and bottom boundaries. Two different methods are available: Method 1: lower order representation, calculate fluxes from bulk layer integrated quantities. Method 2: more straight forward, diffusion is applied layer by layer using only information from neighboring cells.

Parameters
[in,out]gGrid type
[in]gvocean vertical grid structure
[in]usA dimensional unit scaling type
[in]hLayer thickness [H ~> m or kg m-2]
[in]coef_xdt * Kh * dy / dx at u-points [L2 ~> m2]
[in]coef_ydt * Kh * dx / dy at v-points [L2 ~> m2]
[in]dtTracer time step * I_numitts (I_numitts in tracer_hordiff)
regTracer registry
[in]csControl structure for this module

Definition at line 138 of file MOM_lateral_boundary_diffusion.F90.

138  type(ocean_grid_type), intent(inout) :: g !< Grid type
139  type(verticalgrid_type), intent(in) :: gv !< ocean vertical grid structure
140  type(unit_scale_type), intent(in) :: us !< A dimensional unit scaling type
141  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), &
142  intent(in) :: h !< Layer thickness [H ~> m or kg m-2]
143  real, dimension(SZIB_(G),SZJ_(G)), intent(in) :: coef_x !< dt * Kh * dy / dx at u-points [L2 ~> m2]
144  real, dimension(SZI_(G),SZJB_(G)), intent(in) :: coef_y !< dt * Kh * dx / dy at v-points [L2 ~> m2]
145  real, intent(in) :: dt !< Tracer time step * I_numitts
146  !! (I_numitts in tracer_hordiff)
147  type(tracer_registry_type), pointer :: reg !< Tracer registry
148  type(lateral_boundary_diffusion_cs), intent(in) :: cs !< Control structure for this module
149 
150  ! Local variables
151  real, dimension(SZI_(G),SZJ_(G)) :: hbl !< bnd. layer depth [H ~> m or kg m-2]
152  real, dimension(SZI_(G),SZJ_(G),SZK_(G),CS%deg+1) :: ppoly0_coefs !< Coefficients of polynomial
153  real, dimension(SZI_(G),SZJ_(G),SZK_(G),2) :: ppoly0_e !< Edge values from reconstructions
154  real, dimension(SZK_(G),CS%deg+1) :: ppoly_s !< Slopes from reconstruction (placeholder)
155  real, dimension(SZIB_(G),SZJ_(G),SZK_(G)) :: uflx !< Zonal flux of tracer [conc m^3]
156  real, dimension(SZIB_(G),SZJ_(G)) :: uflx_bulk !< Total calculated bulk-layer u-flux for the tracer
157  real, dimension(SZI_(G),SZJB_(G),SZK_(G)) :: vflx !< Meridional flux of tracer [conc m^3]
158  real, dimension(SZI_(G),SZJB_(G)) :: vflx_bulk !< Total calculated bulk-layer v-flux for the tracer
159  real, dimension(SZIB_(G),SZJ_(G)) :: uwork_2d !< Layer summed u-flux transport
160  real, dimension(SZI_(G),SZJB_(G)) :: vwork_2d !< Layer summed v-flux transport
161  real, dimension(SZI_(G),SZJ_(G),G%ke) :: tendency !< tendency array for diagn
162  real, dimension(SZI_(G),SZJ_(G)) :: tendency_2d !< depth integrated content tendency for diagn
163  type(tracer_type), pointer :: tracer => null() !< Pointer to the current tracer
164  integer :: remap_method !< Reconstruction method
165  integer :: i,j,k,m !< indices to loop over
166  real :: idt !< inverse of the time step [s-1]
167 
168  idt = 1./dt
169  hbl(:,:) = 0.
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)
173 
174  call pass_var(hbl,g%Domain)
175  do m = 1,reg%ntr
176  tracer => reg%tr(m)
177 
178  ! for diagnostics
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
181  endif
182 
183  ! Interpolate state to interface
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)
187  enddo ; enddo
188 
189  ! Diffusive fluxes in the i-direction
190  uflx(:,:,:) = 0.
191  vflx(:,:,:) = 0.
192  uflx_bulk(:,:) = 0.
193  vflx_bulk(:,:) = 0.
194 
195  ! Method #1 (layer by layer)
196  if (cs%method == 1) then
197  do j=g%jsc,g%jec
198  do i=g%isc-1,g%iec
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)
204  endif
205  enddo
206  enddo
207  do j=g%jsc-1,g%jec
208  do i=g%isc,g%iec
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)
214  endif
215  enddo
216  enddo
217 
218  ! Method #2 (bulk approach)
219  elseif (cs%method == 2) then
220  do j=g%jsc,g%jec
221  do i=g%isc-1,g%iec
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, &
227  cs%linear)
228  endif
229  enddo
230  enddo
231  do j=g%jsc-1,g%jec
232  do i=g%isc,g%iec
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, &
238  cs%linear)
239  endif
240  enddo
241  enddo
242  ! Post tracer bulk diags
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)
245  endif
246 
247  ! Update the tracer fluxes
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))
252 
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
255  endif
256 
257  endif
258  enddo ; enddo ; enddo
259 
260  ! Post the tracer diagnostics
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
264  uwork_2d(:,:) = 0.
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)
269  endif
270 
271  if (tracer%id_lbd_dfy_2d>0) then
272  vwork_2d(:,:) = 0.
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)
277  endif
278 
279  ! post tendency of tracer content
280  if (tracer%id_lbdxy_cont > 0) then
281  call post_data(tracer%id_lbdxy_cont, tendency, cs%diag)
282  endif
283 
284  ! post depth summed tendency for tracer content
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
288  do k=1,gv%ke
289  tendency_2d(i,j) = tendency_2d(i,j) + tendency(i,j,k)
290  enddo
291  enddo ; enddo
292  call post_data(tracer%id_lbdxy_cont_2d, tendency_2d, cs%diag)
293  endif
294 
295  ! post tendency of tracer concentration; this step must be
296  ! done after posting tracer content tendency, since we alter
297  ! the tendency array and its units.
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)
303  endif
304 
305  enddo
306 

◆ lateral_boundary_diffusion_init()

logical function, public mom_lateral_boundary_diffusion::lateral_boundary_diffusion_init ( type(time_type), intent(in), target  Time,
type(ocean_grid_type), intent(in)  G,
type(param_file_type), intent(in)  param_file,
type(diag_ctrl), intent(inout), target  diag,
type(diabatic_cs), pointer  diabatic_CSp,
type(lateral_boundary_diffusion_cs), pointer  CS 
)

Initialization routine that reads runtime parameters and sets up pointers to other control structures that might be needed for lateral boundary diffusion.

Parameters
[in]timeTime structure
[in]gGrid structure
[in]param_fileParameter file structure
[in,out]diagDiagnostics control structure
diabatic_cspKPP control structure needed to get BLD
csLateral boundary mixing control structure

Definition at line 68 of file MOM_lateral_boundary_diffusion.F90.

68  type(time_type), target, intent(in) :: time !< Time structure
69  type(ocean_grid_type), intent(in) :: g !< Grid structure
70  type(param_file_type), intent(in) :: param_file !< Parameter file structure
71  type(diag_ctrl), target, intent(inout) :: diag !< Diagnostics control structure
72  type(diabatic_cs), pointer :: diabatic_csp !< KPP control structure needed to get BLD
73  type(lateral_boundary_diffusion_cs), pointer :: cs !< Lateral boundary mixing control structure
74 
75  ! local variables
76  character(len=80) :: string ! Temporary strings
77  logical :: boundary_extrap
78 
79  if (ASSOCIATED(cs)) then
80  call mom_error(fatal, "lateral_boundary_diffusion_init called with associated control structure.")
81  return
82  endif
83 
84  ! Log this module and master switch for turning it on/off
85  call get_param(param_file, mdl, "USE_LATERAL_BOUNDARY_DIFFUSION", lateral_boundary_diffusion_init, &
86  default=.false., do_not_log=.true.)
87  call log_version(param_file, mdl, version, &
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.", &
92  default=.false.)
93 
94  if (.not. lateral_boundary_diffusion_init) return
95 
96  allocate(cs)
97  cs%diag => diag
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)
100 
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")
104  endif
105 
106  ! Read all relevant parameters and write them to the model log.
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.)
115  endif
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", &
121  default=.false.)
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)
129 

◆ near_boundary_unit_tests()

logical function, public mom_lateral_boundary_diffusion::near_boundary_unit_tests ( logical, intent(in)  verbose)

Unit tests for near-boundary horizontal mixing.

Parameters
[in]verboseIf true, output additional information for debugging unit tests

Definition at line 807 of file MOM_lateral_boundary_diffusion.F90.

807  logical, intent(in) :: verbose !< If true, output additional information for debugging unit tests
808 
809  ! Local variables
810  integer, parameter :: nk = 2 ! Number of layers
811  integer, parameter :: deg = 1 ! Degree of reconstruction (linear here)
812  integer, parameter :: method = 1 ! Method used for integrating polynomials
813  real, dimension(nk) :: phi_l, phi_r ! Tracer values (left and right column) [ nondim m^-3 ]
814  real, dimension(nk) :: phi_l_avg, phi_r_avg ! Bulk, thickness-weighted tracer averages (left and right column)
815  real, dimension(nk,deg+1) :: phi_pp_l, phi_pp_r ! Coefficients for the linear pseudo-reconstructions
816  ! [ nondim m^-3 ]
817 
818  real, dimension(nk,2) :: ppoly0_e_l, ppoly0_e_r! Polynomial edge values (left and right) [concentration]
819  real, dimension(nk) :: h_l, h_r ! Layer thickness (left and right) [m]
820  real :: khtr_u ! Horizontal diffusivities at U-point [m^2 s^-1]
821  real :: hbl_l, hbl_r ! Depth of the boundary layer (left and right) [m]
822  real :: f_bulk ! Total diffusive flux across the U point [nondim s^-1]
823  real, dimension(nk) :: f_layer ! Diffusive flux within each layer at U-point [nondim s^-1]
824  real :: h_u, hblt_u ! Thickness at the u-point [m]
825  real :: khtr_avg ! Thickness-weighted diffusivity at the u-point [m^2 s^-1]
826  real :: heff ! Harmonic mean of layer thicknesses [m]
827  real :: inv_heff ! Inverse of the harmonic mean of layer thicknesses [m^[-1]
828  character(len=120) :: test_name ! Title of the unit test
829  integer :: k_top ! Index of cell containing top of boundary
830  real :: zeta_top ! Nondimension position
831  integer :: k_bot ! Index of cell containing bottom of boundary
832  real :: zeta_bot ! Nondimension position
833  real :: area_l,area_r ! Area of grid cell [m^2]
834  area_l = 1.; area_r = 1. ! Set to unity for all unit tests
835 
836  near_boundary_unit_tests = .false.
837 
838  ! Unit tests for boundary_k_range
839  test_name = 'Surface boundary spans the entire top cell'
840  h_l = (/5.,5./)
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)
844 
845  test_name = 'Surface boundary spans the entire column'
846  h_l = (/5.,5./)
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)
850 
851  test_name = 'Bottom boundary spans the entire bottom cell'
852  h_l = (/5.,5./)
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)
856 
857  test_name = 'Bottom boundary spans the entire column'
858  h_l = (/5.,5./)
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)
862 
863  test_name = 'Surface boundary intersects second layer'
864  h_l = (/10.,10./)
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)
868 
869  test_name = 'Surface boundary intersects first layer'
870  h_l = (/10.,10./)
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)
874 
875  test_name = 'Surface boundary is deeper than column thickness'
876  h_l = (/10.,10./)
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)
880 
881  test_name = 'Bottom boundary intersects first layer'
882  h_l = (/10.,10./)
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)
886 
887  test_name = 'Bottom boundary intersects second layer'
888  h_l = (/10.,10./)
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)
892 
893  ! All cases in this section have hbl which are equal to the column thicknesses
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.
906  khtr_u = 1.
907  ! Without limiter
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/) )
912 
913  ! same as above, but with limiter
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/) )
918 
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.
931  khtr_u = 1.
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/) )
936 
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.
949  khtr_u = 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/) )
954 
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.
967  khtr_u = 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/) )
972 
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.
985  khtr_u = 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/) )
990 
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.
1003  khtr_u = 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/) )
1008 
1009  ! Cases where hbl < column thickness (polynomial coefficients specified for pseudo-linear reconstruction)
1010 
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.
1023  khtr_u = 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./) )
1028 
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.
1037  khtr_u = 1.
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./) )
1046 
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.
1055  khtr_u = 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./) )
1064 
1065  ! unit tests for layer by layer method
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.
1078  khtr_u = 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/) )
1083 
1084  test_name = 'Different hbl and different column thicknesses (linear profile right)'
1085 
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.
1097  khtr_u = 1.
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/) )

◆ test_boundary_k_range()

logical function mom_lateral_boundary_diffusion::test_boundary_k_range ( integer  k_top,
real  zeta_top,
integer  k_bot,
real  zeta_bot,
integer  k_top_ans,
real  zeta_top_ans,
integer  k_bot_ans,
real  zeta_bot_ans,
character(len=80)  test_name,
logical  verbose 
)
private

Return true if output of unit tests for boundary_k_range does not match answers.

Parameters
k_topIndex of cell containing top of boundary
zeta_topNondimension position
k_botIndex of cell containing bottom of boundary
zeta_botNondimension position
k_top_ansIndex of cell containing top of boundary
zeta_top_ansNondimension position
k_bot_ansIndex of cell containing bottom of boundary
zeta_bot_ansNondimension position
test_nameName of the unit test
verboseIf true always print output

Definition at line 1133 of file MOM_lateral_boundary_diffusion.F90.

1133  integer :: k_top !< Index of cell containing top of boundary
1134  real :: zeta_top !< Nondimension position
1135  integer :: k_bot !< Index of cell containing bottom of boundary
1136  real :: zeta_bot !< Nondimension position
1137  integer :: k_top_ans !< Index of cell containing top of boundary
1138  real :: zeta_top_ans !< Nondimension position
1139  integer :: k_bot_ans !< Index of cell containing bottom of boundary
1140  real :: zeta_bot_ans !< Nondimension position
1141  character(len=80) :: test_name !< Name of the unit test
1142  logical :: verbose !< If true always print output
1143 
1144  integer, parameter :: stdunit = stdout
1145 
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)
1150 
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
1157  endif
1158 
1159  20 format(a,"=",i3,x,a,"=",i3)
1160  30 format(a,"=",f20.16,x,a,"=",f20.16)
1161 
1162 

◆ test_layer_fluxes()

logical function mom_lateral_boundary_diffusion::test_layer_fluxes ( logical, intent(in)  verbose,
integer, intent(in)  nk,
character(len=80), intent(in)  test_name,
real, dimension(nk), intent(in)  F_calc,
real, dimension(nk), intent(in)  F_ans 
)
private

Returns true if output of near-boundary unit tests does not match correct computed values and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]test_nameBrief description of the unit test
[in]nkNumber of layers
[in]f_calcFluxes of the unitless tracer from the algorithm [s^-1]
[in]f_ansFluxes of the unitless tracer calculated by hand [s^-1]

Definition at line 1107 of file MOM_lateral_boundary_diffusion.F90.

1107  logical, intent(in) :: verbose !< If true, write results to stdout
1108  character(len=80), intent(in) :: test_name !< Brief description of the unit test
1109  integer, intent(in) :: nk !< Number of layers
1110  real, dimension(nk), intent(in) :: f_calc !< Fluxes of the unitless tracer from the algorithm [s^-1]
1111  real, dimension(nk), intent(in) :: f_ans !< Fluxes of the unitless tracer calculated by hand [s^-1]
1112  ! Local variables
1113  integer :: k
1114  integer, parameter :: stdunit = stdout
1115 
1116  test_layer_fluxes = .false.
1117  do k=1,nk
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)
1124  endif
1125  enddo
1126 
1127 10 format("Layer=",i3," F_calc=",f20.16," F_ans",f20.16)