MOM6
mom_neutral_diffusion Module Reference

Detailed Description

A column-wise toolbox for implementing neutral diffusion.

Data Types

type  neutral_diffusion_cs
 The control structure for the MOM_neutral_diffusion module. More...
 

Functions/Subroutines

logical function, public neutral_diffusion_init (Time, G, US, param_file, diag, EOS, diabatic_CSp, CS)
 Read parameters and allocate control structure for neutral_diffusion module. More...
 
subroutine, public neutral_diffusion_calc_coeffs (G, GV, US, h, T, S, CS, p_surf)
 Calculate remapping factors for u/v columns used to map adjoining columns to a shared coordinate space. More...
 
subroutine, public neutral_diffusion (G, GV, h, Coef_x, Coef_y, dt, Reg, US, CS)
 Update tracer concentration due to neutral diffusion; layer thickness unchanged by this update. More...
 
subroutine interface_scalar (nk, h, S, Si, i_method, h_neglect)
 Returns interface scalar, Si, for a column of layer values, S. More...
 
real function ppm_edge (hkm1, hk, hkp1, hkp2, Ak, Akp1, Pk, Pkp1, h_neglect)
 Returns the PPM quasi-fourth order edge value at k+1/2 following equation 1.6 in Colella & Woodward, 1984: JCP 54, 174-201. More...
 
real function ppm_ave (xL, xR, aL, aR, aMean)
 Returns the average of a PPM reconstruction between two fractional positions. More...
 
real function signum (a, x)
 A true signum function that returns either -abs(a), when x<0; or abs(a) when x>0; or 0 when x=0. More...
 
subroutine plm_diff (nk, h, S, c_method, b_method, diff)
 Returns PLM slopes for a column where the slopes are the difference in value across each cell. The limiting follows equation 1.8 in Colella & Woodward, 1984: JCP 54, 174-201. More...
 
real function fv_diff (hkm1, hk, hkp1, Skm1, Sk, Skp1)
 Returns the cell-centered second-order finite volume (unlimited PLM) slope using three consecutive cell widths and average values. Slope is returned as a difference across the central cell (i.e. units of scalar S). Discretization follows equation 1.7 in Colella & Woodward, 1984: JCP 54, 174-201. More...
 
real function fvlsq_slope (hkm1, hk, hkp1, Skm1, Sk, Skp1)
 Returns the cell-centered second-order weighted least squares slope using three consecutive cell widths and average values. Slope is returned as a gradient (i.e. units of scalar S over width units). More...
 
subroutine find_neutral_surface_positions_continuous (nk, Pl, Tl, Sl, dRdTl, dRdSl, Pr, Tr, Sr, dRdTr, dRdSr, PoL, PoR, KoL, KoR, hEff, bl_kl, bl_kr, bl_zl, bl_zr)
 Returns positions within left/right columns of combined interfaces using continuous reconstructions of T/S. More...
 
real function interpolate_for_nondim_position (dRhoNeg, Pneg, dRhoPos, Ppos)
 Returns the non-dimensional position between Pneg and Ppos where the interpolated density difference equals zero. The result is always bounded to be between 0 and 1. More...
 
subroutine find_neutral_surface_positions_discontinuous (CS, nk, Pres_l, hcol_l, Tl, Sl, ppoly_T_l, ppoly_S_l, stable_l, Pres_r, hcol_r, Tr, Sr, ppoly_T_r, ppoly_S_r, stable_r, PoL, PoR, KoL, KoR, hEff, zeta_bot_L, zeta_bot_R, k_bot_L, k_bot_R, hard_fail_heff)
 Higher order version of find_neutral_surface_positions. Returns positions within left/right columns of combined interfaces using intracell reconstructions of T/S. Note that the polynomial reconstrcutions of T and S are optional to aid with unit testing, but will always be passed otherwise. More...
 
subroutine mark_unstable_cells (CS, nk, T, S, P, stable_cell)
 Sweep down through the column and mark as stable if the bottom interface of a cell is denser than the top. More...
 
real function search_other_column (CS, ksurf, pos_last, T_from, S_from, P_from, T_top, S_top, P_top, T_bot, S_bot, P_bot, T_poly, S_poly)
 Searches the "other" (searched) column for the position of the neutral surface. More...
 
subroutine increment_interface (nk, kl, ki, reached_bottom, searching_this_column, searching_other_column)
 Increments the interface which was just connected and also set flags if the bottom is reached. More...
 
real function find_neutral_pos_linear (CS, z0, T_ref, S_ref, dRdT_ref, dRdS_ref, dRdT_top, dRdS_top, dRdT_bot, dRdS_bot, ppoly_T, ppoly_S)
 Search a layer to find where delta_rho = 0 based on a linear interpolation of alpha and beta of the top and bottom being searched and polynomial reconstructions of T and S. Compressibility is not needed because either, we are assuming incompressibility in the equation of state for this module or alpha and beta are calculated having been displaced to the average pressures of the two pressures We need Newton's method because the T and S reconstructions make delta_rho a polynomial function of z if using PPM or higher. If Newton's method would search fall out of the interval [0,1], a bisection step would be taken instead. Also this linearization of alpha, beta means that second derivatives of the EOS are not needed. Note that delta in variable names below refers to horizontal differences and 'd' refers to vertical differences. More...
 
real function find_neutral_pos_full (CS, z0, T_ref, S_ref, P_ref, P_top, P_bot, ppoly_T, ppoly_S)
 Use the full equation of state to calculate the difference in locally referenced potential density. The derivatives in this case are not trivial to calculate, so instead we use a regula falsi method. More...
 
subroutine calc_delta_rho_and_derivs (CS, T1, S1, p1_in, T2, S2, p2_in, drho, drdt1_out, drds1_out, drdt2_out, drds2_out)
 Calculate the difference in density between two points in a variety of ways. More...
 
real function delta_rho_from_derivs (T1, S1, P1, dRdT1, dRdS1, T2, S2, P2, dRdT2, dRdS2)
 Calculate delta rho from derivatives and gradients of properties \( \Delta \rho = \frac{1}{2}\left[ (\alpha_1 + \alpha_2)*(T_1-T_2) + (\beta_1 + \beta_2)*(S_1-S_2) + (\gamma^{-1}_1 + \gamma^{-1}_2)*(P_1-P_2) \right] \). More...
 
real function absolute_position (n, ns, Pint, Karr, NParr, k_surface)
 Converts non-dimensional position within a layer to absolute position (for debugging) More...
 
real function, dimension(ns) absolute_positions (n, ns, Pint, Karr, NParr)
 Converts non-dimensional positions within layers to absolute positions (for debugging) More...
 
subroutine neutral_surface_flux (nk, nsurf, deg, hl, hr, Tl, Tr, PiL, PiR, KoL, KoR, hEff, Flx, continuous, h_neglect, remap_CS, h_neglect_edge)
 Returns a single column of neutral diffusion fluxes of a tracer. More...
 
subroutine neutral_surface_t_eval (nk, ns, k_sub, Ks, Ps, T_mean, T_int, deg, iMethod, T_poly, T_top, T_bot, T_sub, T_top_int, T_bot_int, T_layer)
 Evaluate various parts of the reconstructions to calculate gradient-based flux limter. More...
 
subroutine ppm_left_right_edge_values (nk, Tl, Ti, aL, aR)
 Discontinuous PPM reconstructions of the left/right edge values within a cell. More...
 
logical function, public neutral_diffusion_unit_tests (verbose)
 Returns true if unit tests of neutral_diffusion functions fail. Otherwise returns false. More...
 
logical function ndiff_unit_tests_continuous (verbose)
 Returns true if unit tests of neutral_diffusion functions fail. Otherwise returns false. More...
 
logical function ndiff_unit_tests_discontinuous (verbose)
 
logical function test_fv_diff (verbose, hkm1, hk, hkp1, Skm1, Sk, Skp1, Ptrue, title)
 Returns true if a test of fv_diff() fails, and conditionally writes results to stream. More...
 
logical function test_fvlsq_slope (verbose, hkm1, hk, hkp1, Skm1, Sk, Skp1, Ptrue, title)
 Returns true if a test of fvlsq_slope() fails, and conditionally writes results to stream. More...
 
logical function test_ifndp (verbose, rhoNeg, Pneg, rhoPos, Ppos, Ptrue, title)
 Returns true if a test of interpolate_for_nondim_position() fails, and conditionally writes results to stream. More...
 
logical function test_data1d (verbose, nk, Po, Ptrue, title)
 Returns true if comparison of Po and Ptrue fails, and conditionally writes results to stream. More...
 
logical function test_data1di (verbose, nk, Po, Ptrue, title)
 Returns true if comparison of Po and Ptrue fails, and conditionally writes results to stream. More...
 
logical function test_nsp (verbose, ns, KoL, KoR, pL, pR, hEff, KoL0, KoR0, pL0, pR0, hEff0, title)
 Returns true if output of find_neutral_surface_positions() does not match correct values, and conditionally writes results to stream. More...
 
logical function compare_nsp_row (KoL, KoR, pL, pR, KoL0, KoR0, pL0, pR0)
 Compares a single row, k, of output from find_neutral_surface_positions() More...
 
logical function test_rnp (expected_pos, test_pos, title)
 Compares output position from refine_nondim_position with an expected value. More...
 
subroutine, public neutral_diffusion_end (CS)
 Deallocates neutral_diffusion control structure. More...
 

Variables

character(len=40) mdl = "MOM_neutral_diffusion"
 module name
 

Function/Subroutine Documentation

◆ absolute_position()

real function mom_neutral_diffusion::absolute_position ( integer, intent(in)  n,
integer, intent(in)  ns,
real, dimension(n+1), intent(in)  Pint,
integer, dimension(ns), intent(in)  Karr,
real, dimension(ns), intent(in)  NParr,
integer, intent(in)  k_surface 
)
private

Converts non-dimensional position within a layer to absolute position (for debugging)

Parameters
[in]nNumber of levels
[in]nsNumber of neutral surfaces
[in]pintPosition of interfaces [R L2 T-2 ~> Pa] or other units
[in]karrIndex of interface above position
[in]nparrNon-dimensional position within layer Karr(:) [nondim]
[in]k_surfacek-interface to query
Returns
The absolute position of a location [R L2 T-2 ~> Pa] or other units following Pint

Definition at line 1837 of file MOM_neutral_diffusion.F90.

1837  integer, intent(in) :: n !< Number of levels
1838  integer, intent(in) :: ns !< Number of neutral surfaces
1839  real, intent(in) :: Pint(n+1) !< Position of interfaces [R L2 T-2 ~> Pa] or other units
1840  integer, intent(in) :: Karr(ns) !< Index of interface above position
1841  real, intent(in) :: NParr(ns) !< Non-dimensional position within layer Karr(:) [nondim]
1842  integer, intent(in) :: k_surface !< k-interface to query
1843  real :: absolute_position !< The absolute position of a location [R L2 T-2 ~> Pa]
1844  !! or other units following Pint
1845  ! Local variables
1846  integer :: k
1847 
1848  k = karr(k_surface)
1849  if (k>n) stop 'absolute_position: k>nk is out of bounds!'
1850  absolute_position = pint(k) + nparr(k_surface) * ( pint(k+1) - pint(k) )
1851 

◆ absolute_positions()

real function, dimension(ns) mom_neutral_diffusion::absolute_positions ( integer, intent(in)  n,
integer, intent(in)  ns,
real, dimension(n+1), intent(in)  Pint,
integer, dimension(ns), intent(in)  Karr,
real, dimension(ns), intent(in)  NParr 
)
private

Converts non-dimensional positions within layers to absolute positions (for debugging)

Parameters
[in]nNumber of levels
[in]nsNumber of neutral surfaces
[in]pintPosition of interface [R L2 T-2 ~> Pa] or other units
[in]karrIndexes of interfaces about positions
[in]nparrNon-dimensional positions within layers Karr(:)
Returns
Absolute positions [R L2 T-2 ~> Pa] or other units following Pint

Definition at line 1856 of file MOM_neutral_diffusion.F90.

1856  integer, intent(in) :: n !< Number of levels
1857  integer, intent(in) :: ns !< Number of neutral surfaces
1858  real, intent(in) :: Pint(n+1) !< Position of interface [R L2 T-2 ~> Pa] or other units
1859  integer, intent(in) :: Karr(ns) !< Indexes of interfaces about positions
1860  real, intent(in) :: NParr(ns) !< Non-dimensional positions within layers Karr(:)
1861 
1862  real, dimension(ns) :: absolute_positions !< Absolute positions [R L2 T-2 ~> Pa]
1863  !! or other units following Pint
1864 
1865  ! Local variables
1866  integer :: k_surface, k
1867 
1868  do k_surface = 1, ns
1869  absolute_positions(k_surface) = absolute_position(n,ns,pint,karr,nparr,k_surface)
1870  enddo
1871 

◆ calc_delta_rho_and_derivs()

subroutine mom_neutral_diffusion::calc_delta_rho_and_derivs ( type(neutral_diffusion_cs CS,
real, intent(in)  T1,
real, intent(in)  S1,
real, intent(in)  p1_in,
real, intent(in)  T2,
real, intent(in)  S2,
real, intent(in)  p2_in,
real, intent(out)  drho,
real, intent(out), optional  drdt1_out,
real, intent(out), optional  drds1_out,
real, intent(out), optional  drdt2_out,
real, intent(out), optional  drds2_out 
)
private

Calculate the difference in density between two points in a variety of ways.

Parameters
csNeutral diffusion control structure
[in]t1Temperature at point 1 [degC]
[in]s1Salinity at point 1 [ppt]
[in]p1_inPressure at point 1 [R L2 T-2 ~> Pa]
[in]t2Temperature at point 2 [degC]
[in]s2Salinity at point 2 [ppt]
[in]p2_inPressure at point 2 [R L2 T-2 ~> Pa]
[out]drhoDifference in density between the two points [R ~> kg m-3]
[out]drdt1_outdrho_dt at point 1 [R degC-1 ~> kg m-3 degC-1]
[out]drds1_outdrho_ds at point 1 [R ppt-1 ~> kg m-3 ppt-1]
[out]drdt2_outdrho_dt at point 2 [R degC-1 ~> kg m-3 degC-1]
[out]drds2_outdrho_ds at point 2 [R ppt-1 ~> kg m-3 ppt-1]

Definition at line 1756 of file MOM_neutral_diffusion.F90.

1756  type(neutral_diffusion_CS) :: CS !< Neutral diffusion control structure
1757  real, intent(in ) :: T1 !< Temperature at point 1 [degC]
1758  real, intent(in ) :: S1 !< Salinity at point 1 [ppt]
1759  real, intent(in ) :: p1_in !< Pressure at point 1 [R L2 T-2 ~> Pa]
1760  real, intent(in ) :: T2 !< Temperature at point 2 [degC]
1761  real, intent(in ) :: S2 !< Salinity at point 2 [ppt]
1762  real, intent(in ) :: p2_in !< Pressure at point 2 [R L2 T-2 ~> Pa]
1763  real, intent( out) :: drho !< Difference in density between the two points [R ~> kg m-3]
1764  real, optional, intent( out) :: dRdT1_out !< drho_dt at point 1 [R degC-1 ~> kg m-3 degC-1]
1765  real, optional, intent( out) :: dRdS1_out !< drho_ds at point 1 [R ppt-1 ~> kg m-3 ppt-1]
1766  real, optional, intent( out) :: dRdT2_out !< drho_dt at point 2 [R degC-1 ~> kg m-3 degC-1]
1767  real, optional, intent( out) :: dRdS2_out !< drho_ds at point 2 [R ppt-1 ~> kg m-3 ppt-1]
1768  ! Local variables
1769  real :: rho1, rho2 ! Densities [R ~> kg m-3]
1770  real :: p1, p2, pmid ! Pressures [R L2 T-2 ~> Pa]
1771  real :: drdt1, drdt2 ! Partial derivatives of density with temperature [R degC-1 ~> kg m-3 degC-1]
1772  real :: drds1, drds2 ! Partial derivatives of density with salinity [R ppt-1 ~> kg m-3 ppt-1]
1773  real :: drdp1, drdp2 ! Partial derivatives of density with pressure [T2 L-2 ~> s2 m-2]
1774 
1775  ! Use the same reference pressure or the in-situ pressure
1776  if (cs%ref_pres > 0.) then
1777  p1 = cs%ref_pres
1778  p2 = cs%ref_pres
1779  else
1780  p1 = p1_in
1781  p2 = p2_in
1782  endif
1783 
1784  ! Use the full linear equation of state to calculate the difference in density (expensive!)
1785  if (trim(cs%delta_rho_form) == 'full') then
1786  pmid = 0.5 * (p1 + p2)
1787  call calculate_density( t1, s1, pmid, rho1, cs%EOS)
1788  call calculate_density( t2, s2, pmid, rho2, cs%EOS)
1789  drho = rho1 - rho2
1790  ! Use the density derivatives at the average of pressures and the differentces int temperature
1791  elseif (trim(cs%delta_rho_form) == 'mid_pressure') then
1792  pmid = 0.5 * (p1 + p2)
1793  if (cs%ref_pres>=0) pmid = cs%ref_pres
1794  call calculate_density_derivs(t1, s1, pmid, drdt1, drds1, cs%EOS)
1795  call calculate_density_derivs(t2, s2, pmid, drdt2, drds2, cs%EOS)
1796  drho = delta_rho_from_derivs( t1, s1, p1, drdt1, drds1, t2, s2, p2, drdt2, drds2)
1797  elseif (trim(cs%delta_rho_form) == 'local_pressure') then
1798  call calculate_density_derivs(t1, s1, p1, drdt1, drds1, cs%EOS)
1799  call calculate_density_derivs(t2, s2, p2, drdt2, drds2, cs%EOS)
1800  drho = delta_rho_from_derivs( t1, s1, p1, drdt1, drds1, t2, s2, p2, drdt2, drds2)
1801  else
1802  call mom_error(fatal, "delta_rho_form is not recognized")
1803  endif
1804 
1805  if (PRESENT(drdt1_out)) drdt1_out = drdt1
1806  if (PRESENT(drds1_out)) drds1_out = drds1
1807  if (PRESENT(drdt2_out)) drdt2_out = drdt2
1808  if (PRESENT(drds2_out)) drds2_out = drds2
1809 

◆ compare_nsp_row()

logical function mom_neutral_diffusion::compare_nsp_row ( integer, intent(in)  KoL,
integer, intent(in)  KoR,
real, intent(in)  pL,
real, intent(in)  pR,
integer, intent(in)  KoL0,
integer, intent(in)  KoR0,
real, intent(in)  pL0,
real, intent(in)  pR0 
)
private

Compares a single row, k, of output from find_neutral_surface_positions()

Parameters
[in]kolIndex of first left interface above neutral surface
[in]korIndex of first right interface above neutral surface
[in]plFractional position of neutral surface within layer KoL of left column
[in]prFractional position of neutral surface within layer KoR of right column
[in]kol0Correct value for KoL
[in]kor0Correct value for KoR
[in]pl0Correct value for pL
[in]pr0Correct value for pR

Definition at line 2829 of file MOM_neutral_diffusion.F90.

2829  integer, intent(in) :: KoL !< Index of first left interface above neutral surface
2830  integer, intent(in) :: KoR !< Index of first right interface above neutral surface
2831  real, intent(in) :: pL !< Fractional position of neutral surface within layer KoL of left column
2832  real, intent(in) :: pR !< Fractional position of neutral surface within layer KoR of right column
2833  integer, intent(in) :: KoL0 !< Correct value for KoL
2834  integer, intent(in) :: KoR0 !< Correct value for KoR
2835  real, intent(in) :: pL0 !< Correct value for pL
2836  real, intent(in) :: pR0 !< Correct value for pR
2837 
2838  compare_nsp_row = .false.
2839  if (kol /= kol0) compare_nsp_row = .true.
2840  if (kor /= kor0) compare_nsp_row = .true.
2841  if (pl /= pl0) compare_nsp_row = .true.
2842  if (pr /= pr0) compare_nsp_row = .true.

◆ delta_rho_from_derivs()

real function mom_neutral_diffusion::delta_rho_from_derivs ( real  T1,
real  S1,
real  P1,
real  dRdT1,
real  dRdS1,
real  T2,
real  S2,
real  P2,
real  dRdT2,
real  dRdS2 
)
private

Calculate delta rho from derivatives and gradients of properties \( \Delta \rho = \frac{1}{2}\left[ (\alpha_1 + \alpha_2)*(T_1-T_2) + (\beta_1 + \beta_2)*(S_1-S_2) + (\gamma^{-1}_1 + \gamma^{-1}_2)*(P_1-P_2) \right] \).

Parameters
t1Temperature at point 1 [degC]
s1Salinity at point 1 [ppt]
p1Pressure at point 1 [R L2 T-2 ~> Pa]
drdt1The partial derivative of density with temperature at point 1 [R degC-1 ~> kg m-3 degC-1]
drds1The partial derivative of density with salinity at point 1 [R ppt-1 ~> kg m-3 ppt-1]
t2Temperature at point 2 [degC]
s2Salinity at point 2 [ppt]
p2Pressure at point 2 [R L2 T-2 ~> Pa]
drdt2The partial derivative of density with temperature at point 2 [R degC-1 ~> kg m-3 degC-1]
drds2The partial derivative of density with salinity at point 2 [R ppt-1 ~> kg m-3 ppt-1]

Definition at line 1818 of file MOM_neutral_diffusion.F90.

1818  real :: T1 !< Temperature at point 1 [degC]
1819  real :: S1 !< Salinity at point 1 [ppt]
1820  real :: P1 !< Pressure at point 1 [R L2 T-2 ~> Pa]
1821  real :: dRdT1 !< The partial derivative of density with temperature at point 1 [R degC-1 ~> kg m-3 degC-1]
1822  real :: dRdS1 !< The partial derivative of density with salinity at point 1 [R ppt-1 ~> kg m-3 ppt-1]
1823  real :: T2 !< Temperature at point 2 [degC]
1824  real :: S2 !< Salinity at point 2 [ppt]
1825  real :: P2 !< Pressure at point 2 [R L2 T-2 ~> Pa]
1826  real :: dRdT2 !< The partial derivative of density with temperature at point 2 [R degC-1 ~> kg m-3 degC-1]
1827  real :: dRdS2 !< The partial derivative of density with salinity at point 2 [R ppt-1 ~> kg m-3 ppt-1]
1828  ! Local variables
1829  real :: drho ! The density difference [R ~> kg m-3]
1830 
1831  drho = 0.5 * ( (drdt1+drdt2)*(t1-t2) + (drds1+drds2)*(s1-s2))
1832 

◆ find_neutral_pos_full()

real function mom_neutral_diffusion::find_neutral_pos_full ( type(neutral_diffusion_cs), intent(in)  CS,
real, intent(in)  z0,
real, intent(in)  T_ref,
real, intent(in)  S_ref,
real, intent(in)  P_ref,
real, intent(in)  P_top,
real, intent(in)  P_bot,
real, dimension(:), intent(in)  ppoly_T,
real, dimension(:), intent(in)  ppoly_S 
)
private

Use the full equation of state to calculate the difference in locally referenced potential density. The derivatives in this case are not trivial to calculate, so instead we use a regula falsi method.

Parameters
[in]csControl structure with parameters for this module
[in]z0Lower bound of position, also serves as the initial guess [nondim]
[in]t_refTemperature at the searched from interface [degC]
[in]s_refSalinity at the searched from interface [ppt]
[in]p_refPressure at the searched from interface [R L2 T-2 ~> Pa]
[in]p_topPressure at top of layer being searched [R L2 T-2 ~> Pa]
[in]p_botPressure at bottom of layer being searched [R L2 T-2 ~> Pa]
[in]ppoly_tCoefficients of the polynomial reconstruction of T within the layer to be searched [degC]
[in]ppoly_sCoefficients of the polynomial reconstruction of T within the layer to be searched [ppt]
Returns
Position where drho = 0 [nondim]

Definition at line 1662 of file MOM_neutral_diffusion.F90.

1662  type(neutral_diffusion_CS),intent(in) :: CS !< Control structure with parameters for this module
1663  real, intent(in) :: z0 !< Lower bound of position, also serves as the
1664  !! initial guess [nondim]
1665  real, intent(in) :: T_ref !< Temperature at the searched from interface [degC]
1666  real, intent(in) :: S_ref !< Salinity at the searched from interface [ppt]
1667  real, intent(in) :: P_ref !< Pressure at the searched from interface [R L2 T-2 ~> Pa]
1668  real, intent(in) :: P_top !< Pressure at top of layer being searched [R L2 T-2 ~> Pa]
1669  real, intent(in) :: P_bot !< Pressure at bottom of layer being searched [R L2 T-2 ~> Pa]
1670  real, dimension(:), intent(in) :: ppoly_T !< Coefficients of the polynomial reconstruction of T within
1671  !! the layer to be searched [degC]
1672  real, dimension(:), intent(in) :: ppoly_S !< Coefficients of the polynomial reconstruction of T within
1673  !! the layer to be searched [ppt]
1674  real :: z !< Position where drho = 0 [nondim]
1675  ! Local variables
1676  integer :: iter
1677  integer :: nterm
1678 
1679  real :: drho_a, drho_b, drho_c ! Density differences [R ~> kg m-3]
1680  real :: a, b, c ! Fractional positions [nondim]
1681  real :: Ta, Tb, Tc ! Temperatures [degC]
1682  real :: Sa, Sb, Sc ! Salinities [ppt]
1683  real :: Pa, Pb, Pc ! Pressures [R L2 T-2 ~> Pa]
1684  integer :: side
1685 
1686  side = 0
1687  ! Set the first two evaluation to the endpoints of the interval
1688  b = z0 ; c = 1
1689  nterm = SIZE(ppoly_t)
1690 
1691  ! Calculate drho at the minimum bound
1692  tb = evaluation_polynomial( ppoly_t, nterm, b )
1693  sb = evaluation_polynomial( ppoly_s, nterm, b )
1694  pb = p_top*(1.-b) + p_bot*b
1695  call calc_delta_rho_and_derivs(cs, tb, sb, pb, t_ref, s_ref, p_ref, drho_b)
1696 
1697  ! Calculate drho at the maximum bound
1698  tc = evaluation_polynomial( ppoly_t, nterm, 1. )
1699  sc = evaluation_polynomial( ppoly_s, nterm, 1. )
1700  pc = p_bot
1701  call calc_delta_rho_and_derivs(cs, tc, sc, pc, t_ref, s_ref, p_ref, drho_c)
1702 
1703  if (drho_b >= 0.) then
1704  z = z0
1705  return
1706  elseif (drho_c == 0.) then
1707  z = 1.
1708  return
1709  endif
1710  if ( sign(1.,drho_b) == sign(1.,drho_c) ) then
1711  z = z0
1712  return
1713  endif
1714 
1715  do iter = 1, cs%max_iter
1716  ! Calculate new position and evaluate if we have converged
1717  a = (drho_b*c - drho_c*b)/(drho_b-drho_c)
1718  ta = evaluation_polynomial( ppoly_t, nterm, a )
1719  sa = evaluation_polynomial( ppoly_s, nterm, a )
1720  pa = p_top*(1.-a) + p_bot*a
1721  call calc_delta_rho_and_derivs(cs, ta, sa, pa, t_ref, s_ref, p_ref, drho_a)
1722  if (abs(drho_a) < cs%drho_tol) then
1723  z = a
1724  return
1725  endif
1726 
1727  if (drho_a*drho_c > 0.) then
1728  if ( abs(a-c)<cs%x_tol) then
1729  z = a
1730  return
1731  endif
1732  c = a ; drho_c = drho_a;
1733  if (side == -1) drho_b = 0.5*drho_b
1734  side = -1
1735  elseif ( drho_b*drho_a > 0 ) then
1736  if ( abs(a-b)<cs%x_tol) then
1737  z = a
1738  return
1739  endif
1740  b = a ; drho_b = drho_a
1741  if (side == 1) drho_c = 0.5*drho_c
1742  side = 1
1743  else
1744  z = a
1745  return
1746  endif
1747  enddo
1748 
1749  z = a
1750 

◆ find_neutral_pos_linear()

real function mom_neutral_diffusion::find_neutral_pos_linear ( type(neutral_diffusion_cs), intent(in)  CS,
real, intent(in)  z0,
real, intent(in)  T_ref,
real, intent(in)  S_ref,
real, intent(in)  dRdT_ref,
real, intent(in)  dRdS_ref,
real, intent(in)  dRdT_top,
real, intent(in)  dRdS_top,
real, intent(in)  dRdT_bot,
real, intent(in)  dRdS_bot,
real, dimension(:), intent(in)  ppoly_T,
real, dimension(:), intent(in)  ppoly_S 
)
private

Search a layer to find where delta_rho = 0 based on a linear interpolation of alpha and beta of the top and bottom being searched and polynomial reconstructions of T and S. Compressibility is not needed because either, we are assuming incompressibility in the equation of state for this module or alpha and beta are calculated having been displaced to the average pressures of the two pressures We need Newton's method because the T and S reconstructions make delta_rho a polynomial function of z if using PPM or higher. If Newton's method would search fall out of the interval [0,1], a bisection step would be taken instead. Also this linearization of alpha, beta means that second derivatives of the EOS are not needed. Note that delta in variable names below refers to horizontal differences and 'd' refers to vertical differences.

Parameters
[in]csControl structure with parameters for this module
[in]z0Lower bound of position, also serves as the initial guess [nondim]
[in]t_refTemperature at the searched from interface [degC]
[in]s_refSalinity at the searched from interface [ppt]
[in]drdt_refdRho/dT at the searched from interface [R degC-1 ~> kg m-3 degC-1]
[in]drds_refdRho/dS at the searched from interface [R ppt-1 ~> kg m-3 ppt-1]
[in]drdt_topdRho/dT at top of layer being searched [R degC-1 ~> kg m-3 degC-1]
[in]drds_topdRho/dS at top of layer being searched [R ppt-1 ~> kg m-3 ppt-1]
[in]drdt_botdRho/dT at bottom of layer being searched [R degC-1 ~> kg m-3 degC-1]
[in]drds_botdRho/dS at bottom of layer being searched [R ppt-1 ~> kg m-3 ppt-1]
[in]ppoly_tCoefficients of the polynomial reconstruction of T within the layer to be searched [degC].
[in]ppoly_sCoefficients of the polynomial reconstruction of S within the layer to be searched [ppt].
Returns
Position where drho = 0 [nondim]

Definition at line 1542 of file MOM_neutral_diffusion.F90.

1542  type(neutral_diffusion_CS),intent(in) :: CS !< Control structure with parameters for this module
1543  real, intent(in) :: z0 !< Lower bound of position, also serves as the
1544  !! initial guess [nondim]
1545  real, intent(in) :: T_ref !< Temperature at the searched from interface [degC]
1546  real, intent(in) :: S_ref !< Salinity at the searched from interface [ppt]
1547  real, intent(in) :: dRdT_ref !< dRho/dT at the searched from interface
1548  !! [R degC-1 ~> kg m-3 degC-1]
1549  real, intent(in) :: dRdS_ref !< dRho/dS at the searched from interface
1550  !! [R ppt-1 ~> kg m-3 ppt-1]
1551  real, intent(in) :: dRdT_top !< dRho/dT at top of layer being searched
1552  !! [R degC-1 ~> kg m-3 degC-1]
1553  real, intent(in) :: dRdS_top !< dRho/dS at top of layer being searched
1554  !! [R ppt-1 ~> kg m-3 ppt-1]
1555  real, intent(in) :: dRdT_bot !< dRho/dT at bottom of layer being searched
1556  !! [R degC-1 ~> kg m-3 degC-1]
1557  real, intent(in) :: dRdS_bot !< dRho/dS at bottom of layer being searched
1558  !! [R ppt-1 ~> kg m-3 ppt-1]
1559  real, dimension(:), intent(in) :: ppoly_T !< Coefficients of the polynomial reconstruction of T within
1560  !! the layer to be searched [degC].
1561  real, dimension(:), intent(in) :: ppoly_S !< Coefficients of the polynomial reconstruction of S within
1562  !! the layer to be searched [ppt].
1563  real :: z !< Position where drho = 0 [nondim]
1564  ! Local variables
1565  real :: dRdT_diff ! Difference in the partial derivative of density with temperature across the
1566  ! layer [R degC-1 ~> kg m-3 degC-1]
1567  real :: dRdS_diff ! Difference in the partial derivative of density with salinity across the
1568  ! layer [R ppt-1 ~> kg m-3 ppt-1]
1569  real :: drho, drho_dz ! Density anomaly and its derivative with fracitonal position [R ~> kg m-3]
1570  real :: dRdT_z ! Partial derivative of density with temperature at a point [R degC-1 ~> kg m-3 degC-1]
1571  real :: dRdS_z ! Partial derivative of density with salinity at a point [R ppt-1 ~> kg m-3 ppt-1]
1572  real :: T_z, dT_dz ! Temperature at a point and its derivative with fractional position [degC]
1573  real :: S_z, dS_dz ! Salinity at a point and its derivative with fractional position [ppt]
1574  real :: drho_min, drho_max ! Bounds on density differences [R ~> kg m-3]
1575  real :: ztest, zmin, zmax ! Fractional positions in the cell [nondim]
1576  real :: dz ! Change in position in the cell [nondim]
1577  real :: a1, a2 ! Fractional weights of the top and bottom values [nondim]
1578  integer :: iter
1579  integer :: nterm
1580 
1581  nterm = SIZE(ppoly_t)
1582 
1583  ! Position independent quantities
1584  drdt_diff = drdt_bot - drdt_top
1585  drds_diff = drds_bot - drds_top
1586  ! Initial starting drho (used for bisection)
1587  zmin = z0 ! Lower bounding interval
1588  zmax = 1. ! Maximum bounding interval (bottom of layer)
1589  a1 = 1. - zmin
1590  a2 = zmin
1591  t_z = evaluation_polynomial( ppoly_t, nterm, zmin )
1592  s_z = evaluation_polynomial( ppoly_s, nterm, zmin )
1593  drdt_z = a1*drdt_top + a2*drdt_bot
1594  drds_z = a1*drds_top + a2*drds_bot
1595  drho_min = 0.5*((drdt_z+drdt_ref)*(t_z-t_ref) + (drds_z+drds_ref)*(s_z-s_ref))
1596 
1597  t_z = evaluation_polynomial( ppoly_t, nterm, 1. )
1598  s_z = evaluation_polynomial( ppoly_s, nterm, 1. )
1599  drho_max = 0.5*((drdt_bot+drdt_ref)*(t_z-t_ref) + (drds_bot+drds_ref)*(s_z-s_ref))
1600 
1601  if (drho_min >= 0.) then
1602  z = z0
1603  return
1604  elseif (drho_max == 0.) then
1605  z = 1.
1606  return
1607  endif
1608  if ( sign(1.,drho_min) == sign(1.,drho_max) ) then
1609  call mom_error(fatal, "drho_min is the same sign as dhro_max")
1610  endif
1611 
1612  z = z0
1613  ztest = z0
1614  do iter = 1, cs%max_iter
1615  ! Calculate quantities at the current nondimensional position
1616  a1 = 1.-z
1617  a2 = z
1618  drdt_z = a1*drdt_top + a2*drdt_bot
1619  drds_z = a1*drds_top + a2*drds_bot
1620  t_z = evaluation_polynomial( ppoly_t, nterm, z )
1621  s_z = evaluation_polynomial( ppoly_s, nterm, z )
1622  drho = 0.5*((drdt_z+drdt_ref)*(t_z-t_ref) + (drds_z+drds_ref)*(s_z-s_ref))
1623 
1624  ! Check for convergence
1625  if (abs(drho) <= cs%drho_tol) exit
1626  ! Update bisection bracketing intervals
1627  if (drho < 0. .and. drho > drho_min) then
1628  drho_min = drho
1629  zmin = z
1630  elseif (drho > 0. .and. drho < drho_max) then
1631  drho_max = drho
1632  zmax = z
1633  endif
1634 
1635  ! Calculate a Newton step
1636  dt_dz = first_derivative_polynomial( ppoly_t, nterm, z )
1637  ds_dz = first_derivative_polynomial( ppoly_s, nterm, z )
1638  drho_dz = 0.5*( (drdt_diff*(t_z - t_ref) + (drdt_ref+drdt_z)*dt_dz) + &
1639  (drds_diff*(s_z - s_ref) + (drds_ref+drds_z)*ds_dz) )
1640 
1641  ztest = z - drho/drho_dz
1642  ! Take a bisection if z falls out of [zmin,zmax]
1643  if (ztest < zmin .or. ztest > zmax) then
1644  if ( drho < 0. ) then
1645  ztest = 0.5*(z + zmax)
1646  else
1647  ztest = 0.5*(zmin + z)
1648  endif
1649  endif
1650 
1651  ! Test to ensure we haven't stalled out
1652  if ( abs(z-ztest) <= cs%x_tol ) exit
1653  ! Reset for next iteration
1654  z = ztest
1655  enddo
1656 

◆ find_neutral_surface_positions_continuous()

subroutine mom_neutral_diffusion::find_neutral_surface_positions_continuous ( integer, intent(in)  nk,
real, dimension(nk+1), intent(in)  Pl,
real, dimension(nk+1), intent(in)  Tl,
real, dimension(nk+1), intent(in)  Sl,
real, dimension(nk+1), intent(in)  dRdTl,
real, dimension(nk+1), intent(in)  dRdSl,
real, dimension(nk+1), intent(in)  Pr,
real, dimension(nk+1), intent(in)  Tr,
real, dimension(nk+1), intent(in)  Sr,
real, dimension(nk+1), intent(in)  dRdTr,
real, dimension(nk+1), intent(in)  dRdSr,
real, dimension(2*nk+2), intent(inout)  PoL,
real, dimension(2*nk+2), intent(inout)  PoR,
integer, dimension(2*nk+2), intent(inout)  KoL,
integer, dimension(2*nk+2), intent(inout)  KoR,
real, dimension(2*nk+1), intent(inout)  hEff,
integer, intent(in), optional  bl_kl,
integer, intent(in), optional  bl_kr,
real, intent(in), optional  bl_zl,
real, intent(in), optional  bl_zr 
)
private

Returns positions within left/right columns of combined interfaces using continuous reconstructions of T/S.

Parameters
[in]nkNumber of levels
[in]plLeft-column interface pressure [R L2 T-2 ~> Pa] or other units
[in]tlLeft-column interface potential temperature [degC]
[in]slLeft-column interface salinity [ppt]
[in]drdtlLeft-column dRho/dT [R degC-1 ~> kg m-3 degC-1]
[in]drdslLeft-column dRho/dS [R ppt-1 ~> kg m-3 ppt-1]
[in]prRight-column interface pressure [R L2 T-2 ~> Pa] or other units
[in]trRight-column interface potential temperature [degC]
[in]srRight-column interface salinity [ppt]
[in]drdtrLeft-column dRho/dT [R degC-1 ~> kg m-3 degC-1]
[in]drdsrLeft-column dRho/dS [R ppt-1 ~> kg m-3 ppt-1]
[in,out]polFractional position of neutral surface within layer KoL of left column
[in,out]porFractional position of neutral surface within layer KoR of right column
[in,out]kolIndex of first left interface above neutral surface
[in,out]korIndex of first right interface above neutral surface
[in,out]heffEffective thickness between two neutral surfaces [R L2 T-2 ~> Pa] or other units following Pl and Pr.
[in]bl_klLayer index of the boundary layer (left)
[in]bl_krLayer index of the boundary layer (right)
[in]bl_zlNondimensional position of the boundary layer (left)
[in]bl_zrNondimensional position of the boundary layer (right)

Definition at line 933 of file MOM_neutral_diffusion.F90.

933  integer, intent(in) :: nk !< Number of levels
934  real, dimension(nk+1), intent(in) :: Pl !< Left-column interface pressure [R L2 T-2 ~> Pa] or other units
935  real, dimension(nk+1), intent(in) :: Tl !< Left-column interface potential temperature [degC]
936  real, dimension(nk+1), intent(in) :: Sl !< Left-column interface salinity [ppt]
937  real, dimension(nk+1), intent(in) :: dRdTl !< Left-column dRho/dT [R degC-1 ~> kg m-3 degC-1]
938  real, dimension(nk+1), intent(in) :: dRdSl !< Left-column dRho/dS [R ppt-1 ~> kg m-3 ppt-1]
939  real, dimension(nk+1), intent(in) :: Pr !< Right-column interface pressure [R L2 T-2 ~> Pa] or other units
940  real, dimension(nk+1), intent(in) :: Tr !< Right-column interface potential temperature [degC]
941  real, dimension(nk+1), intent(in) :: Sr !< Right-column interface salinity [ppt]
942  real, dimension(nk+1), intent(in) :: dRdTr !< Left-column dRho/dT [R degC-1 ~> kg m-3 degC-1]
943  real, dimension(nk+1), intent(in) :: dRdSr !< Left-column dRho/dS [R ppt-1 ~> kg m-3 ppt-1]
944  real, dimension(2*nk+2), intent(inout) :: PoL !< Fractional position of neutral surface within
945  !! layer KoL of left column
946  real, dimension(2*nk+2), intent(inout) :: PoR !< Fractional position of neutral surface within
947  !! layer KoR of right column
948  integer, dimension(2*nk+2), intent(inout) :: KoL !< Index of first left interface above neutral surface
949  integer, dimension(2*nk+2), intent(inout) :: KoR !< Index of first right interface above neutral surface
950  real, dimension(2*nk+1), intent(inout) :: hEff !< Effective thickness between two neutral surfaces
951  !! [R L2 T-2 ~> Pa] or other units following Pl and Pr.
952  integer, optional, intent(in) :: bl_kl !< Layer index of the boundary layer (left)
953  integer, optional, intent(in) :: bl_kr !< Layer index of the boundary layer (right)
954  real, optional, intent(in) :: bl_zl !< Nondimensional position of the boundary layer (left)
955  real, optional, intent(in) :: bl_zr !< Nondimensional position of the boundary layer (right)
956 
957  ! Local variables
958  integer :: ns ! Number of neutral surfaces
959  integer :: k_surface ! Index of neutral surface
960  integer :: kl ! Index of left interface
961  integer :: kr ! Index of right interface
962  real :: dRdT, dRdS ! dRho/dT [kg m-3 degC-1] and dRho/dS [kg m-3 ppt-1] for the neutral surface
963  logical :: searching_left_column ! True if searching for the position of a right interface in the left column
964  logical :: searching_right_column ! True if searching for the position of a left interface in the right column
965  logical :: reached_bottom ! True if one of the bottom-most interfaces has been used as the target
966  integer :: krm1, klm1
967  real :: dRho, dRhoTop, dRhoBot ! Potential density differences at various points [R ~> kg m-3]
968  real :: hL, hR ! Pressure thicknesses [R L2 T-2 ~> Pa]
969  integer :: lastK_left, lastK_right ! Layers used during the last iteration
970  real :: lastP_left, lastP_right ! Fractional positions during the last iteration [nondim]
971  logical :: interior_limit
972 
973  ns = 2*nk+2
974 
975  ! Initialize variables for the search
976  kr = 1 ;
977  kl = 1 ;
978  lastp_right = 0.
979  lastp_left = 0.
980  lastk_right = 1
981  lastk_left = 1
982  reached_bottom = .false.
983 
984  ! Check to see if we should limit the diffusion to the interior
985  interior_limit = PRESENT(bl_kl) .and. PRESENT(bl_kr) .and. PRESENT(bl_zr) .and. PRESENT(bl_zl)
986 
987  ! Loop over each neutral surface, working from top to bottom
988  neutral_surfaces: do k_surface = 1, ns
989  klm1 = max(kl-1, 1)
990  if (klm1>nk) stop 'find_neutral_surface_positions(): klm1 went out of bounds!'
991  krm1 = max(kr-1, 1)
992  if (krm1>nk) stop 'find_neutral_surface_positions(): krm1 went out of bounds!'
993 
994  ! Potential density difference, rho(kr) - rho(kl)
995  drho = 0.5 * ( ( drdtr(kr) + drdtl(kl) ) * ( tr(kr) - tl(kl) ) &
996  + ( drdsr(kr) + drdsl(kl) ) * ( sr(kr) - sl(kl) ) )
997  ! Which column has the lighter surface for the current indexes, kr and kl
998  if (.not. reached_bottom) then
999  if (drho < 0.) then
1000  searching_left_column = .true.
1001  searching_right_column = .false.
1002  elseif (drho > 0.) then
1003  searching_right_column = .true.
1004  searching_left_column = .false.
1005  else ! dRho == 0.
1006  if (kl + kr == 2) then ! Still at surface
1007  searching_left_column = .true.
1008  searching_right_column = .false.
1009  else ! Not the surface so we simply change direction
1010  searching_left_column = .not. searching_left_column
1011  searching_right_column = .not. searching_right_column
1012  endif
1013  endif
1014  endif
1015 
1016  if (searching_left_column) then
1017  ! Interpolate for the neutral surface position within the left column, layer klm1
1018  ! Potential density difference, rho(kl-1) - rho(kr) (should be negative)
1019  drhotop = 0.5 * ( ( drdtl(klm1) + drdtr(kr) ) * ( tl(klm1) - tr(kr) ) &
1020  + ( drdsl(klm1) + drdsr(kr) ) * ( sl(klm1) - sr(kr) ) )
1021  ! Potential density difference, rho(kl) - rho(kr) (will be positive)
1022  drhobot = 0.5 * ( ( drdtl(klm1+1) + drdtr(kr) ) * ( tl(klm1+1) - tr(kr) ) &
1023  + ( drdsl(klm1+1) + drdsr(kr) ) * ( sl(klm1+1) - sr(kr) ) )
1024 
1025  ! Because we are looking left, the right surface, kr, is lighter than klm1+1 and should be denser than klm1
1026  ! unless we are still at the top of the left column (kl=1)
1027  if (drhotop > 0. .or. kr+kl==2) then
1028  pol(k_surface) = 0. ! The right surface is lighter than anything in layer klm1
1029  elseif (drhotop >= drhobot) then ! Left layer is unstratified
1030  pol(k_surface) = 1.
1031  else
1032  ! Linearly interpolate for the position between Pl(kl-1) and Pl(kl) where the density difference
1033  ! between right and left is zero. The Pl here are only used to handle massless layers.
1034  pol(k_surface) = interpolate_for_nondim_position( drhotop, pl(klm1), drhobot, pl(klm1+1) )
1035  endif
1036  if (pol(k_surface)>=1. .and. klm1<nk) then ! >= is really ==, when PoL==1 we point to the bottom of the cell
1037  klm1 = klm1 + 1
1038  pol(k_surface) = pol(k_surface) - 1.
1039  endif
1040  if (real(klm1-lastk_left)+(pol(k_surface)-lastp_left)<0.) then
1041  pol(k_surface) = lastp_left
1042  klm1 = lastk_left
1043  endif
1044  kol(k_surface) = klm1
1045  if (kr <= nk) then
1046  por(k_surface) = 0.
1047  kor(k_surface) = kr
1048  else
1049  por(k_surface) = 1.
1050  kor(k_surface) = nk
1051  endif
1052  if (kr <= nk) then
1053  kr = kr + 1
1054  else
1055  reached_bottom = .true.
1056  searching_right_column = .true.
1057  searching_left_column = .false.
1058  endif
1059  elseif (searching_right_column) then
1060  ! Interpolate for the neutral surface position within the right column, layer krm1
1061  ! Potential density difference, rho(kr-1) - rho(kl) (should be negative)
1062  drhotop = 0.5 * ( ( drdtr(krm1) + drdtl(kl) ) * ( tr(krm1) - tl(kl) ) + &
1063  ( drdsr(krm1) + drdsl(kl) ) * ( sr(krm1) - sl(kl) ) )
1064  ! Potential density difference, rho(kr) - rho(kl) (will be positive)
1065  drhobot = 0.5 * ( ( drdtr(krm1+1) + drdtl(kl) ) * ( tr(krm1+1) - tl(kl) ) + &
1066  ( drdsr(krm1+1) + drdsl(kl) ) * ( sr(krm1+1) - sl(kl) ) )
1067 
1068  ! Because we are looking right, the left surface, kl, is lighter than krm1+1 and should be denser than krm1
1069  ! unless we are still at the top of the right column (kr=1)
1070  if (drhotop >= 0. .or. kr+kl==2) then
1071  por(k_surface) = 0. ! The left surface is lighter than anything in layer krm1
1072  elseif (drhotop >= drhobot) then ! Right layer is unstratified
1073  por(k_surface) = 1.
1074  else
1075  ! Linearly interpolate for the position between Pr(kr-1) and Pr(kr) where the density difference
1076  ! between right and left is zero. The Pr here are only used to handle massless layers.
1077  por(k_surface) = interpolate_for_nondim_position( drhotop, pr(krm1), drhobot, pr(krm1+1) )
1078  endif
1079  if (por(k_surface)>=1. .and. krm1<nk) then ! >= is really ==, when PoR==1 we point to the bottom of the cell
1080  krm1 = krm1 + 1
1081  por(k_surface) = por(k_surface) - 1.
1082  endif
1083  if (real(krm1-lastk_right)+(por(k_surface)-lastp_right)<0.) then
1084  por(k_surface) = lastp_right
1085  krm1 = lastk_right
1086  endif
1087  kor(k_surface) = krm1
1088  if (kl <= nk) then
1089  pol(k_surface) = 0.
1090  kol(k_surface) = kl
1091  else
1092  pol(k_surface) = 1.
1093  kol(k_surface) = nk
1094  endif
1095  if (kl <= nk) then
1096  kl = kl + 1
1097  else
1098  reached_bottom = .true.
1099  searching_right_column = .false.
1100  searching_left_column = .true.
1101  endif
1102  else
1103  stop 'Else what?'
1104  endif
1105  if (interior_limit) then
1106  if (kol(k_surface)<=bl_kl) then
1107  kol(k_surface) = bl_kl
1108  if (pol(k_surface)<bl_zl) then
1109  pol(k_surface) = bl_zl
1110  endif
1111  endif
1112  if (kor(k_surface)<=bl_kr) then
1113  kor(k_surface) = bl_kr
1114  if (por(k_surface)<bl_zr) then
1115  por(k_surface) = bl_zr
1116  endif
1117  endif
1118  endif
1119 
1120  lastk_left = kol(k_surface) ; lastp_left = pol(k_surface)
1121  lastk_right = kor(k_surface) ; lastp_right = por(k_surface)
1122  ! Effective thickness
1123  ! NOTE: This would be better expressed in terms of the layers thicknesses rather
1124  ! than as differences of position - AJA
1125  if (k_surface>1) then
1126  hl = absolute_position(nk,ns,pl,kol,pol,k_surface) - absolute_position(nk,ns,pl,kol,pol,k_surface-1)
1127  hr = absolute_position(nk,ns,pr,kor,por,k_surface) - absolute_position(nk,ns,pr,kor,por,k_surface-1)
1128  if ( hl + hr > 0.) then
1129  heff(k_surface-1) = 2. * hl * hr / ( hl + hr ) ! Harmonic mean of layer thicknesses
1130  else
1131  heff(k_surface-1) = 0.
1132  endif
1133  endif
1134 
1135  enddo neutral_surfaces
1136 

◆ find_neutral_surface_positions_discontinuous()

subroutine mom_neutral_diffusion::find_neutral_surface_positions_discontinuous ( type(neutral_diffusion_cs), intent(inout)  CS,
integer, intent(in)  nk,
real, dimension(nk,2), intent(in)  Pres_l,
real, dimension(nk), intent(in)  hcol_l,
real, dimension(nk,2), intent(in)  Tl,
real, dimension(nk,2), intent(in)  Sl,
real, dimension(:,:), intent(in)  ppoly_T_l,
real, dimension(:,:), intent(in)  ppoly_S_l,
logical, dimension(nk), intent(in)  stable_l,
real, dimension(nk,2), intent(in)  Pres_r,
real, dimension(nk), intent(in)  hcol_r,
real, dimension(nk,2), intent(in)  Tr,
real, dimension(nk,2), intent(in)  Sr,
real, dimension(:,:), intent(in)  ppoly_T_r,
real, dimension(:,:), intent(in)  ppoly_S_r,
logical, dimension(nk), intent(in)  stable_r,
real, dimension(4*nk), intent(inout)  PoL,
real, dimension(4*nk), intent(inout)  PoR,
integer, dimension(4*nk), intent(inout)  KoL,
integer, dimension(4*nk), intent(inout)  KoR,
real, dimension(4*nk-1), intent(inout)  hEff,
real, intent(in), optional  zeta_bot_L,
real, intent(in), optional  zeta_bot_R,
integer, intent(in), optional  k_bot_L,
integer, intent(in), optional  k_bot_R,
logical, intent(in), optional  hard_fail_heff 
)
private

Higher order version of find_neutral_surface_positions. Returns positions within left/right columns of combined interfaces using intracell reconstructions of T/S. Note that the polynomial reconstrcutions of T and S are optional to aid with unit testing, but will always be passed otherwise.

Parameters
[in,out]csNeutral diffusion control structure
[in]nkNumber of levels
[in]pres_lLeft-column interface pressure [R L2 T-2 ~> Pa]
[in]hcol_lLeft-column layer thicknesses [H ~> m or kg m-2] or other units
[in]tlLeft-column top interface potential temperature [degC]
[in]slLeft-column top interface salinity [ppt]
[in]ppoly_t_lLeft-column coefficients of T reconstruction [degC]
[in]ppoly_s_lLeft-column coefficients of S reconstruction [ppt]
[in]stable_lTrue where the left-column is stable
[in]pres_rRight-column interface pressure [R L2 T-2 ~> Pa]
[in]hcol_rLeft-column layer thicknesses [H ~> m or kg m-2] or other units
[in]trRight-column top interface potential temperature [degC]
[in]srRight-column top interface salinity [ppt]
[in]ppoly_t_rRight-column coefficients of T reconstruction [degC]
[in]ppoly_s_rRight-column coefficients of S reconstruction [ppt]
[in]stable_rTrue where the right-column is stable
[in,out]polFractional position of neutral surface within layer KoL of left column [nondim]
[in,out]porFractional position of neutral surface within layer KoR of right column [nondim]
[in,out]kolIndex of first left interface above neutral surface
[in,out]korIndex of first right interface above neutral surface
[in,out]heffEffective thickness between two neutral surfaces [H ~> m or kg m-2] or other units taken from hcol_l
[in]zeta_bot_lNon-dimensional distance to where the boundary layer intersetcs the cell (left) [nondim]
[in]zeta_bot_rNon-dimensional distance to where the boundary layer intersetcs the cell (right) [nondim]
[in]k_bot_lk-index for the boundary layer (left) [nondim]
[in]k_bot_rk-index for the boundary layer (right) [nondim]
[in]hard_fail_heffIf true (default) bring down the model if the neutral surfaces ever cross [logical]

Definition at line 1187 of file MOM_neutral_diffusion.F90.

1187 
1188  type(neutral_diffusion_CS), intent(inout) :: CS !< Neutral diffusion control structure
1189  integer, intent(in) :: nk !< Number of levels
1190  real, dimension(nk,2), intent(in) :: Pres_l !< Left-column interface pressure [R L2 T-2 ~> Pa]
1191  real, dimension(nk), intent(in) :: hcol_l !< Left-column layer thicknesses [H ~> m or kg m-2]
1192  !! or other units
1193  real, dimension(nk,2), intent(in) :: Tl !< Left-column top interface potential temperature [degC]
1194  real, dimension(nk,2), intent(in) :: Sl !< Left-column top interface salinity [ppt]
1195  real, dimension(:,:), intent(in) :: ppoly_T_l !< Left-column coefficients of T reconstruction [degC]
1196  real, dimension(:,:), intent(in) :: ppoly_S_l !< Left-column coefficients of S reconstruction [ppt]
1197  logical, dimension(nk), intent(in) :: stable_l !< True where the left-column is stable
1198  real, dimension(nk,2), intent(in) :: Pres_r !< Right-column interface pressure [R L2 T-2 ~> Pa]
1199  real, dimension(nk), intent(in) :: hcol_r !< Left-column layer thicknesses [H ~> m or kg m-2]
1200  !! or other units
1201  real, dimension(nk,2), intent(in) :: Tr !< Right-column top interface potential temperature [degC]
1202  real, dimension(nk,2), intent(in) :: Sr !< Right-column top interface salinity [ppt]
1203  real, dimension(:,:), intent(in) :: ppoly_T_r !< Right-column coefficients of T reconstruction [degC]
1204  real, dimension(:,:), intent(in) :: ppoly_S_r !< Right-column coefficients of S reconstruction [ppt]
1205  logical, dimension(nk), intent(in) :: stable_r !< True where the right-column is stable
1206  real, dimension(4*nk), intent(inout) :: PoL !< Fractional position of neutral surface within
1207  !! layer KoL of left column [nondim]
1208  real, dimension(4*nk), intent(inout) :: PoR !< Fractional position of neutral surface within
1209  !! layer KoR of right column [nondim]
1210  integer, dimension(4*nk), intent(inout) :: KoL !< Index of first left interface above neutral surface
1211  integer, dimension(4*nk), intent(inout) :: KoR !< Index of first right interface above neutral surface
1212  real, dimension(4*nk-1), intent(inout) :: hEff !< Effective thickness between two neutral surfaces
1213  !! [H ~> m or kg m-2] or other units taken from hcol_l
1214  real, optional, intent(in) :: zeta_bot_L!< Non-dimensional distance to where the boundary layer
1215  !! intersetcs the cell (left) [nondim]
1216  real, optional, intent(in) :: zeta_bot_R!< Non-dimensional distance to where the boundary layer
1217  !! intersetcs the cell (right) [nondim]
1218 
1219  integer, optional, intent(in) :: k_bot_L !< k-index for the boundary layer (left) [nondim]
1220  integer, optional, intent(in) :: k_bot_R !< k-index for the boundary layer (right) [nondim]
1221  logical, optional, intent(in) :: hard_fail_heff !< If true (default) bring down the model if the
1222  !! neutral surfaces ever cross [logical]
1223  ! Local variables
1224  integer :: ns ! Number of neutral surfaces
1225  integer :: k_surface ! Index of neutral surface
1226  integer :: kl_left, kl_right ! Index of layers on the left/right
1227  integer :: ki_left, ki_right ! Index of interfaces on the left/right
1228  logical :: searching_left_column ! True if searching for the position of a right interface in the left column
1229  logical :: searching_right_column ! True if searching for the position of a left interface in the right column
1230  logical :: reached_bottom ! True if one of the bottom-most interfaces has been used as the target
1231  logical :: search_layer
1232  logical :: fail_heff ! Fail if negative thickness are encountered. By default this
1233  ! is true, but it can take its value from hard_fail_heff.
1234  real :: dRho ! A density difference between columns [R ~> kg m-3]
1235  real :: hL, hR ! Left and right layer thicknesses [H ~> m or kg m-2] or units from hcol_l
1236  real :: lastP_left, lastP_right ! Previous positions for left and right [nondim]
1237  integer :: k_init_L, k_init_R ! Starting indices layers for left and right
1238  real :: p_init_L, p_init_R ! Starting positions for left and right [nondim]
1239  ! Initialize variables for the search
1240  ns = 4*nk
1241  ki_right = 1
1242  ki_left = 1
1243  kl_left = 1
1244  kl_right = 1
1245  lastp_left = 0.
1246  lastp_right = 0.
1247  reached_bottom = .false.
1248  searching_left_column = .false.
1249  searching_right_column = .false.
1250 
1251  fail_heff = .true.
1252  if (PRESENT(hard_fail_heff)) fail_heff = hard_fail_heff
1253 
1254  if (PRESENT(k_bot_l) .and. PRESENT(k_bot_r) .and. PRESENT(zeta_bot_l) .and. PRESENT(zeta_bot_r)) then
1255  k_init_l = k_bot_l; k_init_r = k_bot_r
1256  p_init_l = zeta_bot_l; p_init_r = zeta_bot_r
1257  lastp_left = zeta_bot_l; lastp_right = zeta_bot_r
1258  kl_left = k_bot_l; kl_right = k_bot_r
1259  else
1260  k_init_l = 1 ; k_init_r = 1
1261  p_init_l = 0. ; p_init_r = 0.
1262  endif
1263  ! Loop over each neutral surface, working from top to bottom
1264  neutral_surfaces: do k_surface = 1, ns
1265 
1266  if (k_surface == ns) then
1267  pol(k_surface) = 1.
1268  por(k_surface) = 1.
1269  kol(k_surface) = nk
1270  kor(k_surface) = nk
1271  ! If the layers are unstable, then simply point the surface to the previous location
1272  elseif (.not. stable_l(kl_left)) then
1273  if (k_surface > 1) then
1274  pol(k_surface) = ki_left - 1 ! Top interface is at position = 0., Bottom is at position = 1
1275  kol(k_surface) = kl_left
1276  por(k_surface) = por(k_surface-1)
1277  kor(k_surface) = kor(k_surface-1)
1278  else
1279  por(k_surface) = p_init_r
1280  kor(k_surface) = k_init_r
1281  pol(k_surface) = p_init_l
1282  kol(k_surface) = k_init_l
1283  endif
1284  call increment_interface(nk, kl_left, ki_left, reached_bottom, searching_left_column, searching_right_column)
1285  searching_left_column = .true.
1286  searching_right_column = .false.
1287  elseif (.not. stable_r(kl_right)) then ! Check the right layer for stability
1288  if (k_surface > 1) then
1289  por(k_surface) = ki_right - 1 ! Top interface is at position = 0., Bottom is at position = 1
1290  kor(k_surface) = kl_right
1291  pol(k_surface) = pol(k_surface-1)
1292  kol(k_surface) = kol(k_surface-1)
1293  else
1294  por(k_surface) = 0.
1295  kor(k_surface) = 1
1296  pol(k_surface) = 0.
1297  kol(k_surface) = 1
1298  endif
1299  call increment_interface(nk, kl_right, ki_right, reached_bottom, searching_right_column, searching_left_column)
1300  searching_left_column = .false.
1301  searching_right_column = .true.
1302  else ! Layers are stable so need to figure out whether we need to search right or left
1303  ! For convenience, the left column uses the searched "from" interface variables, and the right column
1304  ! uses the searched 'to'. These will get reset in subsequent calc_delta_rho calls
1305 
1306  call calc_delta_rho_and_derivs(cs, &
1307  tr(kl_right, ki_right), sr(kl_right, ki_right), pres_r(kl_right,ki_right), &
1308  tl(kl_left, ki_left), sl(kl_left, ki_left) , pres_l(kl_left,ki_left), &
1309  drho)
1310  if (cs%debug) write(stdout,'(A,I2,A,E12.4,A,I2,A,I2,A,I2,A,I2)') &
1311  "k_surface=",k_surface, " dRho=",cs%R_to_kg_m3*drho, &
1312  "kl_left=",kl_left, " ki_left=",ki_left, " kl_right=",kl_right, " ki_right=",ki_right
1313  ! Which column has the lighter surface for the current indexes, kr and kl
1314  if (.not. reached_bottom) then
1315  if (drho < 0.) then
1316  searching_left_column = .true.
1317  searching_right_column = .false.
1318  elseif (drho > 0.) then
1319  searching_left_column = .false.
1320  searching_right_column = .true.
1321  else ! dRho == 0.
1322  if ( ( kl_left + kl_right == 2 ) .and. (ki_left + ki_right == 2) ) then ! Still at surface
1323  searching_left_column = .true.
1324  searching_right_column = .false.
1325  else ! Not the surface so we simply change direction
1326  searching_left_column = .not. searching_left_column
1327  searching_right_column = .not. searching_right_column
1328  endif
1329  endif
1330  endif
1331  if (searching_left_column) then
1332  ! Position of the right interface is known and all quantities are fixed
1333  por(k_surface) = ki_right - 1.
1334  kor(k_surface) = kl_right
1335  pol(k_surface) = search_other_column(cs, k_surface, lastp_left, &
1336  tr(kl_right, ki_right), sr(kl_right, ki_right), pres_r(kl_right, ki_right), &
1337  tl(kl_left,1), sl(kl_left,1), pres_l(kl_left,1), &
1338  tl(kl_left,2), sl(kl_left,2), pres_l(kl_left,2), &
1339  ppoly_t_l(kl_left,:), ppoly_s_l(kl_left,:))
1340  kol(k_surface) = kl_left
1341 
1342  if (cs%debug) then
1343  write(stdout,'(A,I2)') "Searching left layer ", kl_left
1344  write(stdout,'(A,I2,X,I2)') "Searching from right: ", kl_right, ki_right
1345  write(stdout,*) "Temp/Salt Reference: ", tr(kl_right,ki_right), sr(kl_right,ki_right)
1346  write(stdout,*) "Temp/Salt Top L: ", tl(kl_left,1), sl(kl_left,1)
1347  write(stdout,*) "Temp/Salt Bot L: ", tl(kl_left,2), sl(kl_left,2)
1348  endif
1349  call increment_interface(nk, kl_right, ki_right, reached_bottom, searching_right_column, searching_left_column)
1350  lastp_left = pol(k_surface)
1351  ! If the right layer increments, then we need to reset the last position on the right
1352  if ( kl_right == (kor(k_surface) + 1) ) lastp_right = 0.
1353 
1354  elseif (searching_right_column) then
1355  ! Position of the right interface is known and all quantities are fixed
1356  pol(k_surface) = ki_left - 1.
1357  kol(k_surface) = kl_left
1358  por(k_surface) = search_other_column(cs, k_surface, lastp_right, &
1359  tl(kl_left, ki_left), sl(kl_left, ki_left), pres_l(kl_left, ki_left), &
1360  tr(kl_right,1), sr(kl_right,1), pres_r(kl_right,1), &
1361  tr(kl_right,2), sr(kl_right,2), pres_r(kl_right,2), &
1362  ppoly_t_r(kl_right,:), ppoly_s_r(kl_right,:))
1363  kor(k_surface) = kl_right
1364 
1365  if (cs%debug) then
1366  write(stdout,'(A,I2)') "Searching right layer ", kl_right
1367  write(stdout,'(A,I2,X,I2)') "Searching from left: ", kl_left, ki_left
1368  write(stdout,*) "Temp/Salt Reference: ", tl(kl_left,ki_left), sl(kl_left,ki_left)
1369  write(stdout,*) "Temp/Salt Top L: ", tr(kl_right,1), sr(kl_right,1)
1370  write(stdout,*) "Temp/Salt Bot L: ", tr(kl_right,2), sr(kl_right,2)
1371  endif
1372  call increment_interface(nk, kl_left, ki_left, reached_bottom, searching_left_column, searching_right_column)
1373  lastp_right = por(k_surface)
1374  ! If the right layer increments, then we need to reset the last position on the right
1375  if ( kl_left == (kol(k_surface) + 1) ) lastp_left = 0.
1376  else
1377  stop 'Else what?'
1378  endif
1379  if (cs%debug) write(stdout,'(A,I3,A,ES16.6,A,I2,A,ES16.6)') "KoL:", kol(k_surface), " PoL:", pol(k_surface), &
1380  " KoR:", kor(k_surface), " PoR:", por(k_surface)
1381  endif
1382  ! Effective thickness
1383  if (k_surface>1) then
1384  if ( kol(k_surface) == kol(k_surface-1) .and. kor(k_surface) == kor(k_surface-1) ) then
1385  hl = (pol(k_surface) - pol(k_surface-1))*hcol_l(kol(k_surface))
1386  hr = (por(k_surface) - por(k_surface-1))*hcol_r(kor(k_surface))
1387  if (hl < 0. .or. hr < 0.) then
1388  if (fail_heff) then
1389  call mom_error(fatal,"Negative thicknesses in neutral diffusion")
1390  else
1391  if (searching_left_column) then
1392  pol(k_surface) = pol(k_surface-1)
1393  kol(k_surface) = kol(k_surface-1)
1394  elseif (searching_right_column) then
1395  por(k_surface) = por(k_surface-1)
1396  kor(k_surface) = kor(k_surface-1)
1397  endif
1398  endif
1399  elseif ( hl + hr == 0. ) then
1400  heff(k_surface-1) = 0.
1401  else
1402  heff(k_surface-1) = 2. * ( (hl * hr) / ( hl + hr ) )! Harmonic mean
1403  if ( kol(k_surface) /= kol(k_surface-1) ) then
1404  call mom_error(fatal,"Neutral sublayer spans multiple layers")
1405  endif
1406  if ( kor(k_surface) /= kor(k_surface-1) ) then
1407  call mom_error(fatal,"Neutral sublayer spans multiple layers")
1408  endif
1409  endif
1410  else
1411  heff(k_surface-1) = 0.
1412  endif
1413  endif
1414  enddo neutral_surfaces

◆ fv_diff()

real function mom_neutral_diffusion::fv_diff ( real, intent(in)  hkm1,
real, intent(in)  hk,
real, intent(in)  hkp1,
real, intent(in)  Skm1,
real, intent(in)  Sk,
real, intent(in)  Skp1 
)
private

Returns the cell-centered second-order finite volume (unlimited PLM) slope using three consecutive cell widths and average values. Slope is returned as a difference across the central cell (i.e. units of scalar S). Discretization follows equation 1.7 in Colella & Woodward, 1984: JCP 54, 174-201.

Parameters
[in]hkm1Left cell width
[in]hkCenter cell width
[in]hkp1Right cell width
[in]skm1Left cell average value
[in]skCenter cell average value
[in]skp1Right cell average value

Definition at line 876 of file MOM_neutral_diffusion.F90.

876  real, intent(in) :: hkm1 !< Left cell width
877  real, intent(in) :: hk !< Center cell width
878  real, intent(in) :: hkp1 !< Right cell width
879  real, intent(in) :: Skm1 !< Left cell average value
880  real, intent(in) :: Sk !< Center cell average value
881  real, intent(in) :: Skp1 !< Right cell average value
882 
883  ! Local variables
884  real :: h_sum, hp, hm
885 
886  h_sum = ( hkm1 + hkp1 ) + hk
887  if (h_sum /= 0.) h_sum = 1./ h_sum
888  hm = hkm1 + hk
889  if (hm /= 0.) hm = 1./ hm
890  hp = hkp1 + hk
891  if (hp /= 0.) hp = 1./ hp
892  fv_diff = ( hk * h_sum ) * &
893  ( ( 2. * hkm1 + hk ) * hp * ( skp1 - sk ) &
894  + ( 2. * hkp1 + hk ) * hm * ( sk - skm1 ) )

◆ fvlsq_slope()

real function mom_neutral_diffusion::fvlsq_slope ( real, intent(in)  hkm1,
real, intent(in)  hk,
real, intent(in)  hkp1,
real, intent(in)  Skm1,
real, intent(in)  Sk,
real, intent(in)  Skp1 
)
private

Returns the cell-centered second-order weighted least squares slope using three consecutive cell widths and average values. Slope is returned as a gradient (i.e. units of scalar S over width units).

Parameters
[in]hkm1Left cell width
[in]hkCenter cell width
[in]hkp1Right cell width
[in]skm1Left cell average value
[in]skCenter cell average value
[in]skp1Right cell average value

Definition at line 902 of file MOM_neutral_diffusion.F90.

902  real, intent(in) :: hkm1 !< Left cell width
903  real, intent(in) :: hk !< Center cell width
904  real, intent(in) :: hkp1 !< Right cell width
905  real, intent(in) :: Skm1 !< Left cell average value
906  real, intent(in) :: Sk !< Center cell average value
907  real, intent(in) :: Skp1 !< Right cell average value
908 
909  ! Local variables
910  real :: xkm1, xkp1
911  real :: h_sum, hx_sum, hxsq_sum, hxy_sum, hy_sum, det
912 
913  xkm1 = -0.5 * ( hk + hkm1 )
914  xkp1 = 0.5 * ( hk + hkp1 )
915  h_sum = ( hkm1 + hkp1 ) + hk
916  hx_sum = hkm1*xkm1 + hkp1*xkp1
917  hxsq_sum = hkm1*(xkm1**2) + hkp1*(xkp1**2)
918  hxy_sum = hkm1*xkm1*skm1 + hkp1*xkp1*skp1
919  hy_sum = ( hkm1*skm1 + hkp1*skp1 ) + hk*sk
920  det = h_sum * hxsq_sum - hx_sum**2
921  if (det /= 0.) then
922  !a = ( hxsq_sum * hy_sum - hx_sum*hxy_sum ) / det ! a would be mean of straight line fit
923  fvlsq_slope = ( h_sum * hxy_sum - hx_sum*hy_sum ) / det ! Gradient of straight line fit
924  else
925  fvlsq_slope = 0. ! Adcroft's reciprocal rule
926  endif

◆ increment_interface()

subroutine mom_neutral_diffusion::increment_interface ( integer, intent(in)  nk,
integer, intent(inout)  kl,
integer, intent(inout)  ki,
logical, intent(inout)  reached_bottom,
logical, intent(inout)  searching_this_column,
logical, intent(inout)  searching_other_column 
)
private

Increments the interface which was just connected and also set flags if the bottom is reached.

Parameters
[in]nkNumber of vertical levels
[in,out]klCurrent layer (potentially updated)
[in,out]kiCurrent interface
[in,out]reached_bottomUpdated when kl == nk and ki == 2
[in,out]searching_this_columnUpdated when kl == nk and ki == 2
[in,out]searching_other_columnUpdated when kl == nk and ki == 2

Definition at line 1507 of file MOM_neutral_diffusion.F90.

1507  integer, intent(in ) :: nk !< Number of vertical levels
1508  integer, intent(inout) :: kl !< Current layer (potentially updated)
1509  integer, intent(inout) :: ki !< Current interface
1510  logical, intent(inout) :: reached_bottom !< Updated when kl == nk and ki == 2
1511  logical, intent(inout) :: searching_this_column !< Updated when kl == nk and ki == 2
1512  logical, intent(inout) :: searching_other_column !< Updated when kl == nk and ki == 2
1513  integer :: k
1514 
1515  reached_bottom = .false.
1516  if (ki == 2) then ! At the bottom interface
1517  if ((ki == 2) .and. (kl < nk) ) then ! Not at the bottom so just go to the next layer
1518  kl = kl+1
1519  ki = 1
1520  elseif ((kl == nk) .and. (ki==2)) then
1521  reached_bottom = .true.
1522  searching_this_column = .false.
1523  searching_other_column = .true.
1524  endif
1525  elseif (ki==1) then ! At the top interface
1526  ki = 2 ! Next interface is same layer, but bottom interface
1527  else
1528  call mom_error(fatal,"Unanticipated eventuality in increment_interface")
1529  endif

◆ interface_scalar()

subroutine mom_neutral_diffusion::interface_scalar ( integer, intent(in)  nk,
real, dimension(nk), intent(in)  h,
real, dimension(nk), intent(in)  S,
real, dimension(nk+1), intent(inout)  Si,
integer, intent(in)  i_method,
real, intent(in)  h_neglect 
)
private

Returns interface scalar, Si, for a column of layer values, S.

Parameters
[in]nkNumber of levels
[in]hLayer thickness [H ~> m or kg m-2]
[in]sLayer scalar (conc, e.g. ppt)
[in,out]siInterface scalar (conc, e.g. ppt)
[in]i_method=1 use average of PLM edges =2 use continuous PPM edge interpolation
[in]h_neglectA negligibly small thickness [H ~> m or kg m-2]

Definition at line 693 of file MOM_neutral_diffusion.F90.

693  integer, intent(in) :: nk !< Number of levels
694  real, dimension(nk), intent(in) :: h !< Layer thickness [H ~> m or kg m-2]
695  real, dimension(nk), intent(in) :: S !< Layer scalar (conc, e.g. ppt)
696  real, dimension(nk+1), intent(inout) :: Si !< Interface scalar (conc, e.g. ppt)
697  integer, intent(in) :: i_method !< =1 use average of PLM edges
698  !! =2 use continuous PPM edge interpolation
699  real, intent(in) :: h_neglect !< A negligibly small thickness [H ~> m or kg m-2]
700  ! Local variables
701  integer :: k, km2, kp1
702  real, dimension(nk) :: diff
703  real :: Sb, Sa
704 
705  call plm_diff(nk, h, s, 2, 1, diff)
706  si(1) = s(1) - 0.5 * diff(1)
707  if (i_method==1) then
708  do k = 2, nk
709  ! Average of the two edge values (will be bounded and,
710  ! when slopes are unlimited, notionally second-order accurate)
711  sa = s(k-1) + 0.5 * diff(k-1) ! Lower edge value of a PLM reconstruction for layer above
712  sb = s(k) - 0.5 * diff(k) ! Upper edge value of a PLM reconstruction for layer below
713  si(k) = 0.5 * ( sa + sb )
714  enddo
715  elseif (i_method==2) then
716  do k = 2, nk
717  ! PPM quasi-fourth order interpolation for edge values following
718  ! equation 1.6 in Colella & Woodward, 1984: JCP 54, 174-201.
719  km2 = max(1, k-2)
720  kp1 = min(nk, k+1)
721  si(k) = ppm_edge(h(km2), h(k-1), h(k), h(kp1), s(k-1), s(k), diff(k-1), diff(k), h_neglect)
722  enddo
723  endif
724  si(nk+1) = s(nk) + 0.5 * diff(nk)
725 

◆ interpolate_for_nondim_position()

real function mom_neutral_diffusion::interpolate_for_nondim_position ( real, intent(in)  dRhoNeg,
real, intent(in)  Pneg,
real, intent(in)  dRhoPos,
real, intent(in)  Ppos 
)
private

Returns the non-dimensional position between Pneg and Ppos where the interpolated density difference equals zero. The result is always bounded to be between 0 and 1.

Parameters
[in]drhonegNegative density difference [R ~> kg m-3]
[in]pnegPosition of negative density difference [R L2 T-2 ~> Pa] or [nondim]
[in]drhoposPositive density difference [R ~> kg m-3]
[in]pposPosition of positive density difference [R L2 T-2 ~> Pa] or [nondim]

Definition at line 1143 of file MOM_neutral_diffusion.F90.

1143  real, intent(in) :: dRhoNeg !< Negative density difference [R ~> kg m-3]
1144  real, intent(in) :: Pneg !< Position of negative density difference [R L2 T-2 ~> Pa] or [nondim]
1145  real, intent(in) :: dRhoPos !< Positive density difference [R ~> kg m-3]
1146  real, intent(in) :: Ppos !< Position of positive density difference [R L2 T-2 ~> Pa] or [nondim]
1147 
1148  character(len=120) :: mesg
1149 
1150  if (ppos < pneg) then
1151  call mom_error(fatal, 'interpolate_for_nondim_position: Houston, we have a problem! Ppos<Pneg')
1152  elseif (drhoneg>drhopos) then
1153  write(stderr,*) 'dRhoNeg, Pneg, dRhoPos, Ppos=',drhoneg, pneg, drhopos, ppos
1154  write(mesg,*) 'dRhoNeg, Pneg, dRhoPos, Ppos=', drhoneg, pneg, drhopos, ppos
1155  call mom_error(warning, 'interpolate_for_nondim_position: '//trim(mesg))
1156  elseif (drhoneg>drhopos) then !### Does this duplicated test belong here?
1157  call mom_error(fatal, 'interpolate_for_nondim_position: Houston, we have a problem! dRhoNeg>dRhoPos')
1158  endif
1159  if (ppos<=pneg) then ! Handle vanished or inverted layers
1160  interpolate_for_nondim_position = 0.5
1161  elseif ( drhopos - drhoneg > 0. ) then
1162  interpolate_for_nondim_position = min( 1., max( 0., -drhoneg / ( drhopos - drhoneg ) ) )
1163  elseif ( drhopos - drhoneg == 0) then
1164  if (drhoneg>0.) then
1165  interpolate_for_nondim_position = 0.
1166  elseif (drhoneg<0.) then
1167  interpolate_for_nondim_position = 1.
1168  else ! dRhoPos = dRhoNeg = 0
1169  interpolate_for_nondim_position = 0.5
1170  endif
1171  else ! dRhoPos - dRhoNeg < 0
1172  interpolate_for_nondim_position = 0.5
1173  endif
1174  if ( interpolate_for_nondim_position < 0. ) &
1175  call mom_error(fatal, 'interpolate_for_nondim_position: Houston, we have a problem! Pint < Pneg')
1176  if ( interpolate_for_nondim_position > 1. ) &
1177  call mom_error(fatal, 'interpolate_for_nondim_position: Houston, we have a problem! Pint > Ppos')

◆ mark_unstable_cells()

subroutine mom_neutral_diffusion::mark_unstable_cells ( type(neutral_diffusion_cs), intent(inout)  CS,
integer, intent(in)  nk,
real, dimension(nk,2), intent(in)  T,
real, dimension(nk,2), intent(in)  S,
real, dimension(nk,2), intent(in)  P,
logical, dimension(nk), intent(out)  stable_cell 
)
private

Sweep down through the column and mark as stable if the bottom interface of a cell is denser than the top.

Parameters
[in,out]csNeutral diffusion control structure
[in]nkNumber of levels in a column
[in]tTemperature at interfaces [degC]
[in]sSalinity at interfaces [ppt]
[in]pPressure at interfaces [R L2 T-2 ~> Pa]
[out]stable_cellTrue if this cell is unstably stratified

Definition at line 1419 of file MOM_neutral_diffusion.F90.

1419  type(neutral_diffusion_CS), intent(inout) :: CS !< Neutral diffusion control structure
1420  integer, intent(in) :: nk !< Number of levels in a column
1421  real, dimension(nk,2), intent(in) :: T !< Temperature at interfaces [degC]
1422  real, dimension(nk,2), intent(in) :: S !< Salinity at interfaces [ppt]
1423  real, dimension(nk,2), intent(in) :: P !< Pressure at interfaces [R L2 T-2 ~> Pa]
1424  logical, dimension(nk), intent( out) :: stable_cell !< True if this cell is unstably stratified
1425 
1426  integer :: k, first_stable, prev_stable
1427  real :: delta_rho ! A density difference [R ~> kg m-3]
1428 
1429  do k = 1,nk
1430  call calc_delta_rho_and_derivs( cs, t(k,2), s(k,2), max(p(k,2), cs%ref_pres), &
1431  t(k,1), s(k,1), max(p(k,1), cs%ref_pres), delta_rho )
1432  stable_cell(k) = (delta_rho > 0.)
1433  enddo

◆ ndiff_unit_tests_continuous()

logical function mom_neutral_diffusion::ndiff_unit_tests_continuous ( logical, intent(in)  verbose)
private

Returns true if unit tests of neutral_diffusion functions fail. Otherwise returns false.

Parameters
[in]verboseIf true, write results to stdout

Definition at line 2089 of file MOM_neutral_diffusion.F90.

2089  logical, intent(in) :: verbose !< If true, write results to stdout
2090  ! Local variables
2091  integer, parameter :: nk = 4
2092  real, dimension(nk+1) :: TiL, TiR1, TiR2, TiR4, Tio ! Test interface temperatures
2093  real, dimension(nk) :: TL ! Test layer temperatures
2094  real, dimension(nk+1) :: SiL ! Test interface salinities
2095  real, dimension(nk+1) :: PiL, PiR4 ! Test interface positions
2096  real, dimension(2*nk+2) :: PiLRo, PiRLo ! Test positions
2097  integer, dimension(2*nk+2) :: KoL, KoR ! Test indexes
2098  real, dimension(2*nk+1) :: hEff ! Test positions
2099  real, dimension(2*nk+1) :: Flx ! Test flux
2100  integer :: k
2101  logical :: v
2102  real :: h_neglect
2103 
2104  h_neglect = 1.0e-30
2105 
2106  v = verbose
2107 
2108  ndiff_unit_tests_continuous = .false. ! Normally return false
2109  write(stdout,*) '==== MOM_neutral_diffusion: ndiff_unit_tests_continuous ='
2110 
2111  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2112  test_fv_diff(v,1.,1.,1., 0.,1.,2., 1., 'FV: Straight line on uniform grid')
2113  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2114  test_fv_diff(v,1.,1.,0., 0.,4.,8., 7., 'FV: Vanished right cell')
2115  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2116  test_fv_diff(v,0.,1.,1., 0.,4.,8., 7., 'FV: Vanished left cell')
2117  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2118  test_fv_diff(v,1.,2.,4., 0.,3.,9., 4., 'FV: Stretched grid')
2119  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2120  test_fv_diff(v,2.,0.,2., 0.,1.,2., 0., 'FV: Vanished middle cell')
2121  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2122  test_fv_diff(v,0.,1.,0., 0.,1.,2., 2., 'FV: Vanished on both sides')
2123  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2124  test_fv_diff(v,1.,0.,0., 0.,1.,2., 0., 'FV: Two vanished cell sides')
2125  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2126  test_fv_diff(v,0.,0.,0., 0.,1.,2., 0., 'FV: All vanished cells')
2127 
2128  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2129  test_fvlsq_slope(v,1.,1.,1., 0.,1.,2., 1., 'LSQ: Straight line on uniform grid')
2130  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2131  test_fvlsq_slope(v,1.,1.,0., 0.,1.,2., 1., 'LSQ: Vanished right cell')
2132  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2133  test_fvlsq_slope(v,0.,1.,1., 0.,1.,2., 1., 'LSQ: Vanished left cell')
2134  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2135  test_fvlsq_slope(v,1.,2.,4., 0.,3.,9., 2., 'LSQ: Stretched grid')
2136  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2137  test_fvlsq_slope(v,1.,0.,1., 0.,1.,2., 2., 'LSQ: Vanished middle cell')
2138  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2139  test_fvlsq_slope(v,0.,1.,0., 0.,1.,2., 0., 'LSQ: Vanished on both sides')
2140  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2141  test_fvlsq_slope(v,1.,0.,0., 0.,1.,2., 0., 'LSQ: Two vanished cell sides')
2142  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2143  test_fvlsq_slope(v,0.,0.,0., 0.,1.,2., 0., 'LSQ: All vanished cells')
2144 
2145  call interface_scalar(4, (/10.,10.,10.,10./), (/24.,18.,12.,6./), tio, 1, h_neglect)
2146  !ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2147  ! test_data1d(5, Tio, (/27.,21.,15.,9.,3./), 'Linear profile, interface temperatures')
2148  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2149  test_data1d(v,5, tio, (/24.,22.5,15.,7.5,6./), 'Linear profile, linear interface temperatures')
2150  call interface_scalar(4, (/10.,10.,10.,10./), (/24.,18.,12.,6./), tio, 2, h_neglect)
2151  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2152  test_data1d(v,5, tio, (/24.,22.,15.,8.,6./), 'Linear profile, PPM interface temperatures')
2153 
2154  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2155  test_ifndp(v,-1.0, 0., 1.0, 1.0, 0.5, 'Check mid-point')
2156  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2157  test_ifndp(v, 0.0, 0., 1.0, 1.0, 0.0, 'Check bottom')
2158  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2159  test_ifndp(v, 0.1, 0., 1.1, 1.0, 0.0, 'Check below')
2160  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2161  test_ifndp(v,-1.0, 0., 0.0, 1.0, 1.0, 'Check top')
2162  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2163  test_ifndp(v,-1.0, 0., -0.1, 1.0, 1.0, 'Check above')
2164  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2165  test_ifndp(v,-1.0, 0., 3.0, 1.0, 0.25, 'Check 1/4')
2166  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2167  test_ifndp(v,-3.0, 0., 1.0, 1.0, 0.75, 'Check 3/4')
2168  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2169  test_ifndp(v, 1.0, 0., 1.0, 1.0, 0.0, 'Check dRho=0 below')
2170  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2171  test_ifndp(v,-1.0, 0., -1.0, 1.0, 1.0, 'Check dRho=0 above')
2172  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2173  test_ifndp(v, 0.0, 0., 0.0, 1.0, 0.5, 'Check dRho=0 mid')
2174  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. &
2175  test_ifndp(v,-2.0, .5, 5.0, 0.5, 0.5, 'Check dP=0')
2176 
2177  ! Identical columns
2178  call find_neutral_surface_positions_continuous(3, &
2179  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Left positions, T and S
2180  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2181  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Right positions, T and S
2182  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2183  pilro, pirlo, kol, kor, heff)
2184  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2185  (/1,1,2,2,3,3,3,3/), & ! KoL
2186  (/1,1,2,2,3,3,3,3/), & ! KoR
2187  (/0.,0.,0.,0.,0.,0.,1.,1./), & ! pL
2188  (/0.,0.,0.,0.,0.,0.,1.,1./), & ! pR
2189  (/0.,10.,0.,10.,0.,10.,0./), & ! hEff
2190  'Identical columns')
2191  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_data1d(v, 8, &
2192  absolute_positions(3, 8, (/0.,10.,20.,30./), kol, pilro), &
2193  (/0.,0.,10.,10.,20.,20.,30.,30./), '... left positions')
2194  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_data1d(v, 8, &
2195  absolute_positions(3, 8, (/0.,10.,20.,30./), kor, pirlo), &
2196  (/0.,0.,10.,10.,20.,20.,30.,30./), '... right positions')
2197  call neutral_surface_flux(3, 2*3+2, 2, (/10.,10.,10./), (/10.,10.,10./), & ! nk, hL, hR
2198  (/20.,16.,12./), (/20.,16.,12./), & ! Tl, Tr
2199  pilro, pirlo, kol, kor, heff, flx, .true., h_neglect)
2200  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_data1d(v, 7, flx, &
2201  (/0.,0.,0.,0.,0.,0.,0./), 'Identical columns, rho flux (=0)')
2202  call neutral_surface_flux(3, 2*3+2, 2, (/10.,10.,10./), (/10.,10.,10./), & ! nk, hL, hR
2203  (/-1.,-1.,-1./), (/1.,1.,1./), & ! Sl, Sr
2204  pilro, pirlo, kol, kor, heff, flx, .true., h_neglect)
2205  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_data1d(v, 7, flx, &
2206  (/0.,20.,0.,20.,0.,20.,0./), 'Identical columns, S flux')
2207 
2208  ! Right column slightly cooler than left
2209  call find_neutral_surface_positions_continuous(3, &
2210  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Left positions, T and S
2211  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2212  (/0.,10.,20.,30./), (/20.,16.,12.,8./), (/0.,0.,0.,0./), & ! Right positions, T and S
2213  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2214  pilro, pirlo, kol, kor, heff)
2215  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2216  (/1,1,2,2,3,3,3,3/), & ! kL
2217  (/1,1,1,2,2,3,3,3/), & ! kR
2218  (/0.,0.5,0.,0.5,0.,0.5,1.,1./), & ! pL
2219  (/0.,0.,0.5,0.,0.5,0.,0.5,1./), & ! pR
2220  (/0.,5.,5.,5.,5.,5.,0./), & ! hEff
2221  'Right column slightly cooler')
2222  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_data1d(v, 8, &
2223  absolute_positions(3, 8, (/0.,10.,20.,30./), kol, pilro), &
2224  (/0.,5.,10.,15.,20.,25.,30.,30./), '... left positions')
2225  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_data1d(v, 8, &
2226  absolute_positions(3, 8, (/0.,10.,20.,30./), kor, pirlo), &
2227  (/0.,0.,5.,10.,15.,20.,25.,30./), '... right positions')
2228 
2229  ! Right column slightly warmer than left
2230  call find_neutral_surface_positions_continuous(3, &
2231  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Left positions, T and S
2232  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2233  (/0.,10.,20.,30./), (/24.,20.,16.,12./), (/0.,0.,0.,0./), & ! Right positions, T and S
2234  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2235  pilro, pirlo, kol, kor, heff)
2236  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2237  (/1,1,1,2,2,3,3,3/), & ! kL
2238  (/1,1,2,2,3,3,3,3/), & ! kR
2239  (/0.,0.,0.5,0.,0.5,0.,0.5,1./), & ! pL
2240  (/0.,0.5,0.,0.5,0.,0.5,1.,1./), & ! pR
2241  (/0.,5.,5.,5.,5.,5.,0./), & ! hEff
2242  'Right column slightly warmer')
2243 
2244  ! Right column somewhat cooler than left
2245  call find_neutral_surface_positions_continuous(3, &
2246  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Left positions, T and S
2247  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2248  (/0.,10.,20.,30./), (/16.,12.,8.,4./), (/0.,0.,0.,0./), & ! Right positions, T and S
2249  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2250  pilro, pirlo, kol, kor, heff)
2251  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2252  (/1,2,2,3,3,3,3,3/), & ! kL
2253  (/1,1,1,1,2,2,3,3/), & ! kR
2254  (/0.,0.,0.5,0.,0.5,1.,1.,1./), & ! pL
2255  (/0.,0.,0.,0.5,0.,0.5,0.,1./), & ! pR
2256  (/0.,0.,5.,5.,5.,0.,0./), & ! hEff
2257  'Right column somewhat cooler')
2258 
2259  ! Right column much colder than left with no overlap
2260  call find_neutral_surface_positions_continuous(3, &
2261  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Left positions, T and S
2262  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2263  (/0.,10.,20.,30./), (/9.,7.,5.,3./), (/0.,0.,0.,0./), & ! Right positions, T and S
2264  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2265  pilro, pirlo, kol, kor, heff)
2266  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2267  (/1,2,3,3,3,3,3,3/), & ! kL
2268  (/1,1,1,1,1,2,3,3/), & ! kR
2269  (/0.,0.,0.,1.,1.,1.,1.,1./), & ! pL
2270  (/0.,0.,0.,0.,0.,0.,0.,1./), & ! pR
2271  (/0.,0.,0.,0.,0.,0.,0./), & ! hEff
2272  'Right column much cooler')
2273 
2274  ! Right column with mixed layer
2275  call find_neutral_surface_positions_continuous(3, &
2276  (/0.,10.,20.,30./), (/22.,18.,14.,10./), (/0.,0.,0.,0./), & ! Left positions, T and S
2277  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2278  (/0.,10.,20.,30./), (/14.,14.,10.,2./), (/0.,0.,0.,0./), & ! Right positions, T and S
2279  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2280  pilro, pirlo, kol, kor, heff)
2281  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2282  (/1,2,3,3,3,3,3,3/), & ! kL
2283  (/1,1,1,1,2,3,3,3/), & ! kR
2284  (/0.,0.,0.,0.,0.,1.,1.,1./), & ! pL
2285  (/0.,0.,0.,0.,0.,0.,0.,1./), & ! pR
2286  (/0.,0.,0.,0.,10.,0.,0./), & ! hEff
2287  'Right column with mixed layer')
2288 
2289  ! Identical columns with mixed layer
2290  call find_neutral_surface_positions_continuous(3, &
2291  (/0.,10.,20.,30./), (/14.,14.,10.,2./), (/0.,0.,0.,0./), & ! Left positions, T and S
2292  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2293  (/0.,10.,20.,30./), (/14.,14.,10.,2./), (/0.,0.,0.,0./), & ! Right positions, T and S
2294  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2295  pilro, pirlo, kol, kor, heff)
2296  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2297  (/1,1,2,2,3,3,3,3/), & ! kL
2298  (/1,1,2,2,3,3,3,3/), & ! kR
2299  (/0.,0.,0.,0.,0.,0.,1.,1./), & ! pL
2300  (/0.,0.,0.,0.,0.,0.,1.,1./), & ! pR
2301  (/0.,10.,0.,10.,0.,10.,0./), & ! hEff
2302  'Identical columns with mixed layer')
2303 
2304  ! Right column with unstable mixed layer
2305  call find_neutral_surface_positions_continuous(3, &
2306  (/0.,10.,20.,30./), (/14.,14.,10.,2./), (/0.,0.,0.,0./), & ! Left positions, T and S
2307  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2308  (/0.,10.,20.,30./), (/10.,14.,12.,4./), (/0.,0.,0.,0./), & ! Right positions, T and S
2309  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2310  pilro, pirlo, kol, kor, heff)
2311  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2312  (/1,2,3,3,3,3,3,3/), & ! kL
2313  (/1,1,1,2,3,3,3,3/), & ! kR
2314  (/0.,0.,0.,0.,0.,0.,.75,1./), & ! pL
2315  (/0.,0.,0.,0.,0.,0.25,1.,1./), & ! pR
2316  (/0.,0.,0.,0.,0.,7.5,0./), & ! hEff
2317  'Right column with unstable mixed layer')
2318 
2319  ! Left column with unstable mixed layer
2320  call find_neutral_surface_positions_continuous(3, &
2321  (/0.,10.,20.,30./), (/10.,14.,12.,4./), (/0.,0.,0.,0./), & ! Left positions, T and S
2322  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2323  (/0.,10.,20.,30./), (/14.,14.,10.,2./), (/0.,0.,0.,0./), & ! Right positions, T and S
2324  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2325  pilro, pirlo, kol, kor, heff)
2326  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2327  (/1,1,1,2,3,3,3,3/), & ! kL
2328  (/1,2,3,3,3,3,3,3/), & ! kR
2329  (/0.,0.,0.,0.,0.,0.25,1.,1./), & ! pL
2330  (/0.,0.,0.,0.,0.,0.,.75,1./), & ! pR
2331  (/0.,0.,0.,0.,0.,7.5,0./), & ! hEff
2332  'Left column with unstable mixed layer')
2333 
2334  ! Two unstable mixed layers
2335  call find_neutral_surface_positions_continuous(3, &
2336  (/0.,10.,20.,30./), (/8.,12.,10.,2./), (/0.,0.,0.,0./), & ! Left positions, T and S
2337  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Left dRdT and dRdS
2338  (/0.,10.,20.,30./), (/10.,14.,12.,4./), (/0.,0.,0.,0./), & ! Right positions, T and S
2339  (/-1.,-1.,-1.,-1./), (/1.,1.,1.,1./), &! Right dRdT and dRdS
2340  pilro, pirlo, kol, kor, heff)
2341  ndiff_unit_tests_continuous = ndiff_unit_tests_continuous .or. test_nsp(v, 8, kol, kor, pilro, pirlo, heff, &
2342  (/1,1,1,1,2,3,3,3/), & ! kL
2343  (/1,2,3,3,3,3,3,3/), & ! kR
2344  (/0.,0.,0.,0.,0.,0.,0.75,1./), & ! pL
2345  (/0.,0.,0.,0.5,0.5,0.5,1.,1./), & ! pR
2346  (/0.,0.,0.,0.,0.,6.,0./), & ! hEff
2347  'Two unstable mixed layers')
2348 
2349  if (.not. ndiff_unit_tests_continuous) write(stdout,*) 'Pass'
2350 

◆ ndiff_unit_tests_discontinuous()

logical function mom_neutral_diffusion::ndiff_unit_tests_discontinuous ( logical, intent(in)  verbose)
private
Parameters
[in]verboseIt true, write results to stdout

Definition at line 2354 of file MOM_neutral_diffusion.F90.

2354  logical, intent(in) :: verbose !< It true, write results to stdout
2355  ! Local variables
2356  integer, parameter :: nk = 3
2357  integer, parameter :: ns = nk*4
2358  real, dimension(nk) :: Sl, Sr, Tl, Tr ! Salinities [ppt] and temperatures [degC]
2359  real, dimension(nk) :: hl, hr ! Thicknesses in pressure units [R L2 T-2 ~> Pa]
2360  real, dimension(nk,2) :: TiL, SiL, TiR, SiR ! Cell edge salinities [ppt] and temperatures [degC]
2361  real, dimension(nk,2) :: Pres_l, Pres_r ! Interface pressures [R L2 T-2 ~> Pa]
2362  integer, dimension(ns) :: KoL, KoR
2363  real, dimension(ns) :: PoL, PoR
2364  real, dimension(ns-1) :: hEff, Flx
2365  type(neutral_diffusion_CS) :: CS !< Neutral diffusion control structure
2366  type(EOS_type), pointer :: EOS !< Structure for linear equation of state
2367  type(remapping_CS), pointer :: remap_CS !< Remapping control structure (PLM)
2368  real, dimension(nk,2) :: ppoly_T_l, ppoly_T_r ! Linear reconstruction for T
2369  real, dimension(nk,2) :: ppoly_S_l, ppoly_S_r ! Linear reconstruction for S
2370  real, dimension(nk,2) :: dRdT !< Partial derivative of density with temperature at
2371  !! cell edges [R degC-1 ~> kg m-3 degC-1]
2372  real, dimension(nk,2) :: dRdS !< Partial derivative of density with salinity at
2373  !! cell edges [R ppt-1 ~> kg m-3 ppt-1]
2374  logical, dimension(nk) :: stable_l, stable_r
2375  integer :: iMethod
2376  integer :: ns_l, ns_r
2377  integer :: k
2378  logical :: v
2379 
2380  v = verbose
2381  ndiff_unit_tests_discontinuous = .false. ! Normally return false
2382  write(stdout,*) '==== MOM_neutral_diffusion: ndiff_unit_tests_discontinuous ='
2383 
2384  ! Unit tests for find_neutral_surface_positions_discontinuous
2385  ! Salinity is 0 for all these tests
2386  allocate(cs%EOS)
2387  call eos_manual_init(cs%EOS, form_of_eos=eos_linear, drho_dt=-1., drho_ds=0.)
2388  sl(:) = 0. ; sr(:) = 0. ; ; sil(:,:) = 0. ; sir(:,:) = 0.
2389  ppoly_t_l(:,:) = 0.; ppoly_t_r(:,:) = 0.
2390  ppoly_s_l(:,:) = 0.; ppoly_s_r(:,:) = 0.
2391  ! Intialize any control structures needed for unit tests
2392  cs%ref_pres = -1.
2393 
2394  hl = (/10.,10.,10./) ; hr = (/10.,10.,10./)
2395  pres_l(1,1) = 0. ; pres_l(1,2) = hl(1) ; pres_r(1,1) = 0. ; pres_r(1,2) = hr(1)
2396  do k = 2,nk
2397  pres_l(k,1) = pres_l(k-1,2)
2398  pres_l(k,2) = pres_l(k,1) + hl(k)
2399  pres_r(k,1) = pres_r(k-1,2)
2400  pres_r(k,2) = pres_r(k,1) + hr(k)
2401  enddo
2402  cs%delta_rho_form = 'mid_pressure'
2403  cs%neutral_pos_method = 1
2404 
2405  til(1,:) = (/ 22.00, 18.00 /); til(2,:) = (/ 18.00, 14.00 /); til(3,:) = (/ 14.00, 10.00 /);
2406  tir(1,:) = (/ 22.00, 18.00 /); tir(2,:) = (/ 18.00, 14.00 /); tir(3,:) = (/ 14.00, 10.00 /);
2407  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2408  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2409  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2410  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2411  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2412  (/ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 /), & ! KoL
2413  (/ 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3 /), & ! KoR
2414  (/ 0.00, 0.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00 /), & ! PoL
2415  (/ 0.00, 0.00, 1.00, 0.00, 0.00, 0.00, 1.00, 0.00, 0.00, 0.00, 1.00, 1.00 /), & ! PoR
2416  (/ 0.00, 10.00, 0.00, 0.00, 0.00, 10.00, 0.00, 0.00, 0.00, 10.00, 0.00 /), & ! hEff
2417  'Identical Columns')
2418 
2419  til(1,:) = (/ 22.00, 18.00 /); til(2,:) = (/ 18.00, 14.00 /); til(3,:) = (/ 14.00, 10.00 /);
2420  tir(1,:) = (/ 20.00, 16.00 /); tir(2,:) = (/ 16.00, 12.00 /); tir(3,:) = (/ 12.00, 8.00 /);
2421  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2422  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2423  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2424  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2425  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2426  (/ 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3 /), & ! KoL
2427  (/ 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3 /), & ! KoR
2428  (/ 0.00, 0.50, 1.00, 0.00, 0.50, 0.50, 1.00, 0.00, 0.50, 0.50, 1.00, 1.00 /), & ! PoL
2429  (/ 0.00, 0.00, 0.50, 0.50, 1.00, 0.00, 0.50, 0.50, 1.00, 0.00, 0.50, 1.00 /), & ! PoR
2430  (/ 0.00, 5.00, 0.00, 5.00, 0.00, 5.00, 0.00, 5.00, 0.00, 5.00, 0.00 /), & ! hEff
2431  'Right slightly cooler')
2432 
2433  til(1,:) = (/ 20.00, 16.00 /); til(2,:) = (/ 16.00, 12.00 /); til(3,:) = (/ 12.00, 8.00 /);
2434  tir(1,:) = (/ 22.00, 18.00 /); tir(2,:) = (/ 18.00, 14.00 /); tir(3,:) = (/ 14.00, 10.00 /);
2435  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2436  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2437  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2438  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2439  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2440  (/ 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3 /), & ! KoL
2441  (/ 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3 /), & ! KoR
2442  (/ 0.00, 0.00, 0.50, 0.50, 1.00, 0.00, 0.50, 0.50, 1.00, 0.00, 0.50, 1.00 /), & ! PoL
2443  (/ 0.00, 0.50, 1.00, 0.00, 0.50, 0.50, 1.00, 0.00, 0.50, 0.50, 1.00, 1.00 /), & ! PoR
2444  (/ 0.00, 5.00, 0.00, 5.00, 0.00, 5.00, 0.00, 5.00, 0.00, 5.00, 0.00 /), & ! hEff
2445  'Left slightly cooler')
2446 
2447  til(1,:) = (/ 22.00, 20.00 /); til(2,:) = (/ 18.00, 16.00 /); til(3,:) = (/ 14.00, 12.00 /);
2448  tir(1,:) = (/ 32.00, 24.00 /); tir(2,:) = (/ 22.00, 14.00 /); tir(3,:) = (/ 12.00, 4.00 /);
2449  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2450  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2451  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2452  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2453  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2454  (/ 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3 /), & ! KoL
2455  (/ 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3 /), & ! KoR
2456  (/ 0.00, 0.00, 0.00, 0.00, 1.00, 0.00, 1.00, 0.00, 0.00, 1.00, 1.00, 1.00 /), & ! PoL
2457  (/ 0.00, 1.00, 0.00, 0.00, 0.25, 0.50, 0.75, 1.00, 0.00, 0.00, 0.00, 1.00 /), & ! PoR
2458  (/ 0.00, 0.00, 0.00, 4.00, 0.00, 4.00, 0.00, 0.00, 0.00, 0.00, 0.00 /), & ! hEff
2459  'Right more strongly stratified')
2460 
2461  til(1,:) = (/ 22.00, 18.00 /); til(2,:) = (/ 18.00, 14.00 /); til(3,:) = (/ 14.00, 10.00 /);
2462  tir(1,:) = (/ 14.00, 14.00 /); tir(2,:) = (/ 14.00, 14.00 /); tir(3,:) = (/ 12.00, 8.00 /);
2463  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2464  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2465  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2466  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2467  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2468  (/ 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3 /), & ! KoL
2469  (/ 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3 /), & ! KoR
2470  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 1.00, 0.00, 1.00, 0.00, 0.50, 1.00, 1.00 /), & ! PoL
2471  (/ 0.00, 1.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.50, 1.00 /), & ! PoR
2472  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 5.00, 0.00 /), & ! hEff
2473  'Deep Mixed layer on the right')
2474 
2475  til(1,:) = (/ 14.00, 14.00 /); til(2,:) = (/ 14.00, 12.00 /); til(3,:) = (/ 10.00, 8.00 /);
2476  tir(1,:) = (/ 14.00, 14.00 /); tir(2,:) = (/ 14.00, 14.00 /); tir(3,:) = (/ 14.00, 14.00 /);
2477  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2478  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2479  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2480  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2481  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2482  (/ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3 /), & ! KoL
2483  (/ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3 /), & ! KoR
2484  (/ 0.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00, 1.00 /), & ! PoL
2485  (/ 0.00, 0.00, 0.00, 1.00, 0.00, 1.00, 0.00, 1.00, 1.00, 1.00, 1.00, 1.00 /), & ! PoR
2486  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00 /), & ! hEff
2487  'Right unstratified column')
2488 
2489  til(1,:) = (/ 14.00, 14.00 /); til(2,:) = (/ 14.00, 12.00 /); til(3,:) = (/ 10.00, 8.00 /);
2490  tir(1,:) = (/ 14.00, 14.00 /); tir(2,:) = (/ 14.00, 14.00 /); tir(3,:) = (/ 12.00, 4.00 /);
2491  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2492  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2493  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2494  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2495  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2496  (/ 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 3 /), & ! KoL
2497  (/ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3 /), & ! KoR
2498  (/ 0.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.00, 1.00, 1.00, 0.00, 1.00, 1.00 /), & ! PoL
2499  (/ 0.00, 0.00, 0.00, 1.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.25, 0.50, 1.00 /), & ! PoR
2500  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 4.00, 0.00 /), & ! hEff
2501  'Right unstratified column')
2502 
2503  til(1,:) = (/ 14.00, 14.00 /); til(2,:) = (/ 14.00, 10.00 /); til(3,:) = (/ 10.00, 2.00 /);
2504  tir(1,:) = (/ 14.00, 14.00 /); tir(2,:) = (/ 14.00, 10.00 /); tir(3,:) = (/ 10.00, 2.00 /);
2505  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2506  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2507  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2508  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2509  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2510  (/ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 /), & ! KoL
2511  (/ 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3 /), & ! KoR
2512  (/ 0.00, 1.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00 /), & ! PoL
2513  (/ 0.00, 0.00, 0.00, 1.00, 0.00, 0.00, 1.00, 0.00, 0.00, 0.00, 1.00, 1.00 /), & ! PoR
2514  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 10.00, 0.00, 0.00, 0.00, 10.00, 0.00 /), & ! hEff
2515  'Identical columns with mixed layer')
2516 
2517  til(1,:) = (/ 14.00, 12.00 /); til(2,:) = (/ 10.00, 10.00 /); til(3,:) = (/ 8.00, 2.00 /);
2518  tir(1,:) = (/ 14.00, 12.00 /); tir(2,:) = (/ 12.00, 8.00 /); tir(3,:) = (/ 8.00, 2.00 /);
2519  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2520  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2521  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2522  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2523  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2524  (/ 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3 /), & ! KoL
2525  (/ 1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3 /), & ! KoR
2526  (/ 0.00, 0.00, 1.00, 1.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, 1.00, 1.00 /), & ! PoL
2527  (/ 0.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, 1.00, 1.00, 0.00, 1.00, 1.00 /), & ! PoR
2528  (/ 0.00, 10.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 10.00, 0.00 /), & ! hEff
2529  'Left interior unstratified')
2530 
2531  til(1,:) = (/ 12.00, 12.00 /); til(2,:) = (/ 12.00, 10.00 /); til(3,:) = (/ 10.00, 6.00 /);
2532  tir(1,:) = (/ 12.00, 10.00 /); tir(2,:) = (/ 10.00, 12.00 /); tir(3,:) = (/ 8.00, 4.00 /);
2533  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2534  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2535  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2536  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2537  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2538  (/ 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3 /), & ! KoL
2539  (/ 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3 /), & ! KoR
2540  (/ 0.00, 1.00, 0.00, 0.00, 1.00, 0.00, 0.00, 0.00, 0.00, 0.50, 1.00, 1.00 /), & ! PoL
2541  (/ 0.00, 0.00, 0.00, 0.00, 1.00, 1.00, 0.00, 1.00, 0.00, 0.00, 0.50, 1.00 /), & ! PoR
2542  (/ 0.00, 0.00, 0.00, 10.00, 0.00, 0.00, 0.00, 0.00, 0.00, 5.00, 0.00 /), & ! hEff
2543  'Left mixed layer, Right unstable interior')
2544 
2545  til(1,:) = (/ 14.00, 14.00 /); til(2,:) = (/ 10.00, 10.00 /); til(3,:) = (/ 8.00, 6.00 /);
2546  tir(1,:) = (/ 10.00, 14.00 /); tir(2,:) = (/ 16.00, 16.00 /); tir(3,:) = (/ 12.00, 4.00 /);
2547  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2548  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2549  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2550  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2551  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2552  (/ 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3 /), & ! KoL
2553  (/ 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3 /), & ! KoR
2554  (/ 0.00, 1.00, 0.00, 1.00, 1.00, 1.00, 1.00, 1.00, 0.00, 0.00, 1.00, 1.00 /), & ! PoL
2555  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 1.00, 0.00, 1.00, 0.00, 0.50, 0.75, 1.00 /), & ! PoR
2556  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 4.00, 0.00 /), & ! hEff
2557  'Left thick mixed layer, Right unstable mixed')
2558 
2559  til(1,:) = (/ 8.00, 12.00 /); til(2,:) = (/ 12.00, 10.00 /); til(3,:) = (/ 8.00, 4.00 /);
2560  tir(1,:) = (/ 10.00, 14.00 /); tir(2,:) = (/ 14.00, 12.00 /); tir(3,:) = (/ 10.00, 6.00 /);
2561  call mark_unstable_cells( cs, nk, til, sil, pres_l, stable_l )
2562  call mark_unstable_cells( cs, nk, tir, sir, pres_r, stable_r )
2563  call find_neutral_surface_positions_discontinuous(cs, nk, pres_l, hl, til, sil, ppoly_t_l, ppoly_s_l, stable_l, &
2564  pres_r, hr, tir, sir, ppoly_t_r, ppoly_s_r, stable_r, pol, por, kol, kor, heff)
2565  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. test_nsp(v, 12, kol, kor, pol, por, heff, &
2566  (/ 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3 /), & ! KoL
2567  (/ 1, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3 /), & ! KoR
2568  (/ 0.00, 1.00, 1.00, 1.00, 0.00, 0.00, 0.00, 1.00, 0.00, 0.00, 0.50, 1.00 /), & ! PoL
2569  (/ 0.00, 0.00, 0.00, 1.00, 0.00, 1.00, 1.00, 0.00, 0.00, 0.50, 1.00, 1.00 /), & ! PoR
2570  (/ 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 5.00, 0.00 /), & ! hEff
2571  'Unstable mixed layers, left cooler')
2572 
2573  call eos_manual_init(cs%EOS, form_of_eos = eos_linear, drho_dt = -1., drho_ds = 2.)
2574  ! Tests for linearized version of searching the layer for neutral surface position
2575  ! EOS linear in T, uniform alpha
2576  cs%max_iter = 10
2577  ! Unit tests require explicit initialization of tolerance
2578  cs%Drho_tol = 0.
2579  cs%x_tol = 0.
2580  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. (test_rnp(0.5, &
2581  find_neutral_pos_linear(cs, 0., 10., 35., -0.2, 0., &
2582  -0.2, 0., -0.2, 0., &
2583  (/12.,-4./), (/34.,0./)), "Temp Uniform Linearized Alpha/Beta"))
2584  ! EOS linear in S, uniform beta
2585  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. (test_rnp(0.5, &
2586  find_neutral_pos_linear(cs, 0., 10., 35., 0., 0.8, &
2587  0., 0.8, 0., 0.8, &
2588  (/12.,0./), (/34.,2./)), "Salt Uniform Linearized Alpha/Beta"))
2589  ! EOS linear in T/S, uniform alpha/beta
2590  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. (test_rnp(0.5, &
2591  find_neutral_pos_linear(cs, 0., 10., 35., -0.5, 0.5, &
2592  -0.5, 0.5, -0.5, 0.5, &
2593  (/12.,-4./), (/34.,2./)), "Temp/salt Uniform Linearized Alpha/Beta"))
2594  ! EOS linear in T, insensitive to So
2595  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. (test_rnp(0.5, &
2596  find_neutral_pos_linear(cs, 0., 10., 35., -0.2, 0., &
2597  -0.4, 0., -0.6, 0., &
2598  (/12.,-4./), (/34.,0./)), "Temp stratified Linearized Alpha/Beta"))
2599  ! EOS linear in S, insensitive to T
2600  ndiff_unit_tests_discontinuous = ndiff_unit_tests_discontinuous .or. (test_rnp(0.5, &
2601  find_neutral_pos_linear(cs, 0., 10., 35., 0., 0.8, &
2602  0., 1.0, 0., 0.5, &
2603  (/12.,0./), (/34.,2./)), "Salt stratified Linearized Alpha/Beta"))
2604  if (.not. ndiff_unit_tests_discontinuous) write(stdout,*) 'Pass'
2605 

◆ neutral_diffusion()

subroutine, public mom_neutral_diffusion::neutral_diffusion ( type(ocean_grid_type), intent(in)  G,
type(verticalgrid_type), intent(in)  GV,
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(unit_scale_type), intent(in)  US,
type(neutral_diffusion_cs), pointer  CS 
)

Update tracer concentration due to neutral diffusion; layer thickness unchanged by this update.

Parameters
[in]gOcean grid structure
[in]gvocean vertical grid structure
[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 [T ~> s] (I_numitts in tracer_hordiff)
regTracer registry
[in]usA dimensional unit scaling type
csNeutral diffusion control structure

Definition at line 530 of file MOM_neutral_diffusion.F90.

530  type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
531  type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
532  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(in) :: h !< Layer thickness [H ~> m or kg m-2]
533  real, dimension(SZIB_(G),SZJ_(G)), intent(in) :: Coef_x !< dt * Kh * dy / dx at u-points [L2 ~> m2]
534  real, dimension(SZI_(G),SZJB_(G)), intent(in) :: Coef_y !< dt * Kh * dx / dy at v-points [L2 ~> m2]
535  real, intent(in) :: dt !< Tracer time step * I_numitts [T ~> s]
536  !! (I_numitts in tracer_hordiff)
537  type(tracer_registry_type), pointer :: Reg !< Tracer registry
538  type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
539  type(neutral_diffusion_CS), pointer :: CS !< Neutral diffusion control structure
540 
541  ! Local variables
542  real, dimension(SZIB_(G),SZJ_(G),CS%nsurf-1) :: uFlx ! Zonal flux of tracer [H conc ~> m conc or conc kg m-2]
543  real, dimension(SZI_(G),SZJB_(G),CS%nsurf-1) :: vFlx ! Meridional flux of tracer
544  ! [H conc ~> m conc or conc kg m-2]
545  real, dimension(SZI_(G),SZJ_(G),G%ke) :: tendency ! tendency array for diagn
546  real, dimension(SZI_(G),SZJ_(G)) :: tendency_2d ! depth integrated content tendency for diagn
547  real, dimension(SZIB_(G),SZJ_(G)) :: trans_x_2d ! depth integrated diffusive tracer x-transport diagn
548  real, dimension(SZI_(G),SZJB_(G)) :: trans_y_2d ! depth integrated diffusive tracer y-transport diagn
549  real, dimension(G%ke) :: dTracer ! change in tracer concentration due to ndiffusion
550 
551  type(tracer_type), pointer :: Tracer => null() ! Pointer to the current tracer
552 
553  integer :: i, j, k, m, ks, nk
554  real :: Idt ! The inverse of the time step [T-1 ~> s-1]
555  real :: h_neglect, h_neglect_edge
556 
557  if (.not.cs%remap_answers_2018) then
558  h_neglect = gv%H_subroundoff ; h_neglect_edge = gv%H_subroundoff
559  else
560  h_neglect = gv%m_to_H*1.0e-30 ; h_neglect_edge = gv%m_to_H*1.0e-10
561  endif
562 
563  nk = gv%ke
564 
565  do m = 1,reg%ntr ! Loop over tracer registry
566 
567  tracer => reg%Tr(m)
568 
569  ! for diagnostics
570  if (tracer%id_dfxy_conc > 0 .or. tracer%id_dfxy_cont > 0 .or. tracer%id_dfxy_cont_2d > 0 .or. &
571  tracer%id_dfx_2d > 0 .or. tracer%id_dfy_2d > 0) then
572  idt = 1.0 / dt
573  tendency(:,:,:) = 0.0
574  endif
575 
576  uflx(:,:,:) = 0.
577  vflx(:,:,:) = 0.
578 
579  ! x-flux
580  do j = g%jsc,g%jec ; do i = g%isc-1,g%iec
581  if (g%mask2dCu(i,j)>0.) then
582  call neutral_surface_flux(nk, cs%nsurf, cs%deg, h(i,j,:), h(i+1,j,:), &
583  tracer%t(i,j,:), tracer%t(i+1,j,:), &
584  cs%uPoL(i,j,:), cs%uPoR(i,j,:), &
585  cs%uKoL(i,j,:), cs%uKoR(i,j,:), &
586  cs%uhEff(i,j,:), uflx(i,j,:), &
587  cs%continuous_reconstruction, h_neglect, cs%remap_CS, h_neglect_edge)
588  endif
589  enddo ; enddo
590 
591  ! y-flux
592  do j = g%jsc-1,g%jec ; do i = g%isc,g%iec
593  if (g%mask2dCv(i,j)>0.) then
594  call neutral_surface_flux(nk, cs%nsurf, cs%deg, h(i,j,:), h(i,j+1,:), &
595  tracer%t(i,j,:), tracer%t(i,j+1,:), &
596  cs%vPoL(i,j,:), cs%vPoR(i,j,:), &
597  cs%vKoL(i,j,:), cs%vKoR(i,j,:), &
598  cs%vhEff(i,j,:), vflx(i,j,:), &
599  cs%continuous_reconstruction, h_neglect, cs%remap_CS, h_neglect_edge)
600  endif
601  enddo ; enddo
602 
603  ! Update the tracer concentration from divergence of neutral diffusive flux components
604  do j = g%jsc,g%jec ; do i = g%isc,g%iec
605  if (g%mask2dT(i,j)>0.) then
606 
607  dtracer(:) = 0.
608  do ks = 1,cs%nsurf-1
609  k = cs%uKoL(i,j,ks)
610  dtracer(k) = dtracer(k) + coef_x(i,j) * uflx(i,j,ks)
611  k = cs%uKoR(i-1,j,ks)
612  dtracer(k) = dtracer(k) - coef_x(i-1,j) * uflx(i-1,j,ks)
613  k = cs%vKoL(i,j,ks)
614  dtracer(k) = dtracer(k) + coef_y(i,j) * vflx(i,j,ks)
615  k = cs%vKoR(i,j-1,ks)
616  dtracer(k) = dtracer(k) - coef_y(i,j-1) * vflx(i,j-1,ks)
617  enddo
618  do k = 1, gv%ke
619  tracer%t(i,j,k) = tracer%t(i,j,k) + dtracer(k) * &
620  ( g%IareaT(i,j) / ( h(i,j,k) + gv%H_subroundoff ) )
621  enddo
622 
623  if (tracer%id_dfxy_conc > 0 .or. tracer%id_dfxy_cont > 0 .or. tracer%id_dfxy_cont_2d > 0 ) then
624  do k = 1, gv%ke
625  tendency(i,j,k) = dtracer(k) * g%IareaT(i,j) * idt
626  enddo
627  endif
628 
629  endif
630  enddo ; enddo
631 
632  ! Diagnose vertically summed zonal flux, giving zonal tracer transport from ndiff.
633  ! Note sign corresponds to downgradient flux convention.
634  if (tracer%id_dfx_2d > 0) then
635  do j = g%jsc,g%jec ; do i = g%isc-1,g%iec
636  trans_x_2d(i,j) = 0.
637  if (g%mask2dCu(i,j)>0.) then
638  do ks = 1,cs%nsurf-1
639  trans_x_2d(i,j) = trans_x_2d(i,j) - coef_x(i,j) * uflx(i,j,ks)
640  enddo
641  trans_x_2d(i,j) = trans_x_2d(i,j) * idt
642  endif
643  enddo ; enddo
644  call post_data(tracer%id_dfx_2d, trans_x_2d(:,:), cs%diag)
645  endif
646 
647  ! Diagnose vertically summed merid flux, giving meridional tracer transport from ndiff.
648  ! Note sign corresponds to downgradient flux convention.
649  if (tracer%id_dfy_2d > 0) then
650  do j = g%jsc-1,g%jec ; do i = g%isc,g%iec
651  trans_y_2d(i,j) = 0.
652  if (g%mask2dCv(i,j)>0.) then
653  do ks = 1,cs%nsurf-1
654  trans_y_2d(i,j) = trans_y_2d(i,j) - coef_y(i,j) * vflx(i,j,ks)
655  enddo
656  trans_y_2d(i,j) = trans_y_2d(i,j) * idt
657  endif
658  enddo ; enddo
659  call post_data(tracer%id_dfy_2d, trans_y_2d(:,:), cs%diag)
660  endif
661 
662  ! post tendency of tracer content
663  if (tracer%id_dfxy_cont > 0) then
664  call post_data(tracer%id_dfxy_cont, tendency(:,:,:), cs%diag)
665  endif
666 
667  ! post depth summed tendency for tracer content
668  if (tracer%id_dfxy_cont_2d > 0) then
669  tendency_2d(:,:) = 0.
670  do j = g%jsc,g%jec ; do i = g%isc,g%iec
671  do k = 1, gv%ke
672  tendency_2d(i,j) = tendency_2d(i,j) + tendency(i,j,k)
673  enddo
674  enddo ; enddo
675  call post_data(tracer%id_dfxy_cont_2d, tendency_2d(:,:), cs%diag)
676  endif
677 
678  ! post tendency of tracer concentration; this step must be
679  ! done after posting tracer content tendency, since we alter
680  ! the tendency array.
681  if (tracer%id_dfxy_conc > 0) then
682  do k = 1, gv%ke ; do j = g%jsc,g%jec ; do i = g%isc,g%iec
683  tendency(i,j,k) = tendency(i,j,k) / ( h(i,j,k) + gv%H_subroundoff )
684  enddo ; enddo ; enddo
685  call post_data(tracer%id_dfxy_conc, tendency, cs%diag)
686  endif
687  enddo ! Loop over tracer registry
688 

◆ neutral_diffusion_calc_coeffs()

subroutine, public mom_neutral_diffusion::neutral_diffusion_calc_coeffs ( type(ocean_grid_type), intent(in)  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 %isd: g %ied, g %jsd: g %jed, g %ke), intent(in)  T,
real, dimension( g %isd: g %ied, g %jsd: g %jed, g %ke), intent(in)  S,
type(neutral_diffusion_cs), pointer  CS,
real, dimension( g %isd: g %ied, g %jsd: g %jed), intent(in), optional  p_surf 
)

Calculate remapping factors for u/v columns used to map adjoining columns to a shared coordinate space.

Parameters
[in]gOcean grid structure
[in]gvocean vertical grid structure
[in]usA dimensional unit scaling type
[in]hLayer thickness [H ~> m or kg m-2]
[in]tPotential temperature [degC]
[in]sSalinity [ppt]
csNeutral diffusion control structure
[in]p_surfSurface pressure to include in pressures used for equation of state calculations [R L2 T-2 ~> Pa]

Definition at line 284 of file MOM_neutral_diffusion.F90.

284  type(ocean_grid_type), intent(in) :: G !< Ocean grid structure
285  type(verticalGrid_type), intent(in) :: GV !< ocean vertical grid structure
286  type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
287  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(in) :: h !< Layer thickness [H ~> m or kg m-2]
288  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(in) :: T !< Potential temperature [degC]
289  real, dimension(SZI_(G),SZJ_(G),SZK_(G)), intent(in) :: S !< Salinity [ppt]
290  type(neutral_diffusion_CS), pointer :: CS !< Neutral diffusion control structure
291  real, dimension(SZI_(G),SZJ_(G)), optional, intent(in) :: p_surf !< Surface pressure to include in pressures used
292  !! for equation of state calculations [R L2 T-2 ~> Pa]
293 
294  ! Local variables
295  integer, dimension(2) :: EOSdom ! The i-computational domain for the equation of state
296  integer :: i, j, k
297  ! Variables used for reconstructions
298  real, dimension(SZK_(G),2) :: ppoly_r_S ! Reconstruction slopes
299  real, dimension(SZI_(G), SZJ_(G)) :: hEff_sum ! Summed effective face thicknesses [H ~> m or kg m-2]
300  real, dimension(SZI_(G),SZJ_(G)) :: hbl ! Boundary layer depth [H ~> m or kg m-2]
301  integer :: iMethod
302  real, dimension(SZI_(G)) :: ref_pres ! Reference pressure used to calculate alpha/beta [R L2 T-2 ~> Pa]
303  real, dimension(SZI_(G)) :: rho_tmp ! Routine to calculate drho_dp, returns density which is not used
304  real :: h_neglect, h_neglect_edge ! Negligible thicknesses [H ~> m or kg m-2]
305  integer, dimension(SZI_(G), SZJ_(G)) :: k_top ! Index of the first layer within the boundary
306  real, dimension(SZI_(G), SZJ_(G)) :: zeta_top ! Distance from the top of a layer to the intersection of the
307  ! top extent of the boundary layer (0 at top, 1 at bottom) [nondim]
308  integer, dimension(SZI_(G), SZJ_(G)) :: k_bot ! Index of the last layer within the boundary
309  real, dimension(SZI_(G), SZJ_(G)) :: zeta_bot ! Distance of the lower layer to the boundary layer depth
310  real :: pa_to_H ! A conversion factor from pressure to H units [H T2 R-1 Z-2 ~> m Pa-1 or s2 m-2]
311 
312  pa_to_h = 1. / (gv%H_to_RZ * gv%g_Earth)
313 
314  k_top(:,:) = 1 ; k_bot(:,:) = 1
315  zeta_top(:,:) = 0. ; zeta_bot(:,:) = 0.
316 
317  ! Check if hbl needs to be extracted
318  if (cs%interior_only) then
319  hbl(:,:) = 0.
320  if (ASSOCIATED(cs%KPP_CSp)) call kpp_get_bld(cs%KPP_CSp, hbl, g, us, m_to_bld_units=gv%m_to_H)
321  if (ASSOCIATED(cs%energetic_PBL_CSp)) &
322  call energetic_pbl_get_mld(cs%energetic_PBL_CSp, hbl, g, us, m_to_mld_units=gv%m_to_H)
323  call pass_var(hbl, g%Domain)
324  ! get k-indices and zeta
325  do j=g%jsc-1, g%jec+1 ; do i=g%isc-1,g%iec+1
326  call boundary_k_range(surface, g%ke, h(i,j,:), hbl(i,j), k_top(i,j), zeta_top(i,j), k_bot(i,j), zeta_bot(i,j))
327  enddo; enddo
328  ! TODO: add similar code for BOTTOM boundary layer
329  endif
330 
331  if (.not.cs%remap_answers_2018) then
332  h_neglect = gv%H_subroundoff ; h_neglect_edge = gv%H_subroundoff
333  elseif (gv%Boussinesq) then
334  h_neglect = gv%m_to_H*1.0e-30 ; h_neglect_edge = gv%m_to_H*1.0e-10
335  else
336  h_neglect = gv%kg_m2_to_H*1.0e-30 ; h_neglect_edge = gv%kg_m2_to_H*1.0e-10
337  endif
338 
339  ! If doing along isopycnal diffusion (as opposed to neutral diffusion, set the reference pressure)
340  if (cs%ref_pres>=0.) then
341  ref_pres(:) = cs%ref_pres
342  endif
343 
344  if (cs%continuous_reconstruction) then
345  cs%dRdT(:,:,:) = 0.
346  cs%dRdS(:,:,:) = 0.
347  else
348  cs%T_i(:,:,:,:) = 0.
349  cs%S_i(:,:,:,:) = 0.
350  cs%dRdT_i(:,:,:,:) = 0.
351  cs%dRdS_i(:,:,:,:) = 0.
352  cs%ns(:,:) = 0.
353  cs%stable_cell(:,:,:) = .true.
354  endif
355 
356  ! Calculate pressure at interfaces and layer averaged alpha/beta
357  if (present(p_surf)) then
358  do j=g%jsc-1,g%jec+1 ; do i=g%isc-1,g%iec+1
359  cs%Pint(i,j,1) = p_surf(i,j)
360  enddo ; enddo
361  else
362  cs%Pint(:,:,1) = 0.
363  endif
364  do k=1,g%ke ; do j=g%jsc-1,g%jec+1 ; do i=g%isc-1,g%iec+1
365  cs%Pint(i,j,k+1) = cs%Pint(i,j,k) + h(i,j,k)*(gv%g_Earth*gv%H_to_RZ)
366  enddo ; enddo ; enddo
367 
368  ! Pressures at the interfaces, this is redundant as P_i(k,1) = P_i(k-1,2) however retain this
369  ! for now to ensure consitency of indexing for diiscontinuous reconstructions
370  if (.not. cs%continuous_reconstruction) then
371  if (present(p_surf)) then
372  do j=g%jsc-1,g%jec+1 ; do i=g%isc-1,g%iec+1
373  cs%P_i(i,j,1,1) = p_surf(i,j)
374  cs%P_i(i,j,1,2) = p_surf(i,j) + h(i,j,1)*(gv%H_to_RZ*gv%g_Earth)
375  enddo ; enddo
376  else
377  do j=g%jsc-1,g%jec+1 ; do i=g%isc-1,g%iec+1
378  cs%P_i(i,j,1,1) = 0.
379  cs%P_i(i,j,1,2) = h(i,j,1)*(gv%H_to_RZ*gv%g_Earth)
380  enddo ; enddo
381  endif
382  do k=2,g%ke ; do j=g%jsc-1,g%jec+1 ; do i=g%isc-1,g%iec+1
383  cs%P_i(i,j,k,1) = cs%P_i(i,j,k-1,2)
384  cs%P_i(i,j,k,2) = cs%P_i(i,j,k-1,2) + h(i,j,k)*(gv%H_to_RZ*gv%g_Earth)
385  enddo ; enddo ; enddo
386  endif
387 
388  eosdom(:) = eos_domain(g%HI, halo=1)
389  do j = g%jsc-1, g%jec+1
390  ! Interpolate state to interface
391  do i = g%isc-1, g%iec+1
392  if (cs%continuous_reconstruction) then
393  call interface_scalar(g%ke, h(i,j,:), t(i,j,:), cs%Tint(i,j,:), 2, h_neglect)
394  call interface_scalar(g%ke, h(i,j,:), s(i,j,:), cs%Sint(i,j,:), 2, h_neglect)
395  else
396  call build_reconstructions_1d( cs%remap_CS, g%ke, h(i,j,:), t(i,j,:), cs%ppoly_coeffs_T(i,j,:,:), &
397  cs%T_i(i,j,:,:), ppoly_r_s, imethod, h_neglect, h_neglect_edge )
398  call build_reconstructions_1d( cs%remap_CS, g%ke, h(i,j,:), s(i,j,:), cs%ppoly_coeffs_S(i,j,:,:), &
399  cs%S_i(i,j,:,:), ppoly_r_s, imethod, h_neglect, h_neglect_edge )
400  ! In the current ALE formulation, interface values are not exactly at the 0. or 1. of the
401  ! polynomial reconstructions
402  do k=1,g%ke
403  cs%T_i(i,j,k,1) = evaluation_polynomial( cs%ppoly_coeffs_T(i,j,k,:), cs%deg+1, 0. )
404  cs%T_i(i,j,k,2) = evaluation_polynomial( cs%ppoly_coeffs_T(i,j,k,:), cs%deg+1, 1. )
405  cs%S_i(i,j,k,1) = evaluation_polynomial( cs%ppoly_coeffs_S(i,j,k,:), cs%deg+1, 0. )
406  cs%S_i(i,j,k,2) = evaluation_polynomial( cs%ppoly_coeffs_S(i,j,k,:), cs%deg+1, 1. )
407  enddo
408  endif
409  enddo
410 
411  ! Continuous reconstruction
412  if (cs%continuous_reconstruction) then
413  do k = 1, g%ke+1
414  if (cs%ref_pres<0) ref_pres(:) = cs%Pint(:,j,k)
415  call calculate_density_derivs(cs%Tint(:,j,k), cs%Sint(:,j,k), ref_pres, cs%dRdT(:,j,k), &
416  cs%dRdS(:,j,k), cs%EOS, eosdom)
417  enddo
418  else ! Discontinuous reconstruction
419  do k = 1, g%ke
420  if (cs%ref_pres<0) ref_pres(:) = cs%Pint(:,j,k)
421  ! Calculate derivatives for the top interface
422  call calculate_density_derivs(cs%T_i(:,j,k,1), cs%S_i(:,j,k,1), ref_pres, cs%dRdT_i(:,j,k,1), &
423  cs%dRdS_i(:,j,k,1), cs%EOS, eosdom)
424  if (cs%ref_pres<0) ref_pres(:) = cs%Pint(:,j,k+1)
425  ! Calculate derivatives at the bottom interface
426  call calculate_density_derivs(cs%T_i(:,j,k,2), cs%S_i(:,j,k,2), ref_pres, cs%dRdT_i(:,j,k,2), &
427  cs%dRdS_i(:,j,k,2), cs%EOS, eosdom)
428  enddo
429  endif
430  enddo
431 
432  if (.not. cs%continuous_reconstruction) then
433  do j = g%jsc-1, g%jec+1 ; do i = g%isc-1, g%iec+1
434  call mark_unstable_cells( cs, g%ke, cs%T_i(i,j,:,:), cs%S_i(i,j,:,:), cs%P_i(i,j,:,:), cs%stable_cell(i,j,:) )
435  if (cs%interior_only) then
436  if (.not. cs%stable_cell(i,j,k_bot(i,j))) zeta_bot(i,j) = -1.
437  ! set values in the surface and bottom boundary layer to false.
438  do k = 1, k_bot(i,j)-1
439  cs%stable_cell(i,j,k) = .false.
440  enddo
441  endif
442  enddo ; enddo
443  endif
444 
445  cs%uhEff(:,:,:) = 0.
446  cs%vhEff(:,:,:) = 0.
447  cs%uPoL(:,:,:) = 0.
448  cs%vPoL(:,:,:) = 0.
449  cs%uPoR(:,:,:) = 0.
450  cs%vPoR(:,:,:) = 0.
451  cs%uKoL(:,:,:) = 1
452  cs%vKoL(:,:,:) = 1
453  cs%uKoR(:,:,:) = 1
454  cs%vKoR(:,:,:) = 1
455 
456  ! Neutral surface factors at U points
457  do j = g%jsc, g%jec ; do i = g%isc-1, g%iec
458  if (g%mask2dCu(i,j) > 0.) then
459  if (cs%continuous_reconstruction) then
460  call find_neutral_surface_positions_continuous(g%ke, &
461  cs%Pint(i,j,:), cs%Tint(i,j,:), cs%Sint(i,j,:), cs%dRdT(i,j,:), cs%dRdS(i,j,:), &
462  cs%Pint(i+1,j,:), cs%Tint(i+1,j,:), cs%Sint(i+1,j,:), cs%dRdT(i+1,j,:), cs%dRdS(i+1,j,:), &
463  cs%uPoL(i,j,:), cs%uPoR(i,j,:), cs%uKoL(i,j,:), cs%uKoR(i,j,:), cs%uhEff(i,j,:), &
464  k_bot(i,j), k_bot(i+1,j), zeta_bot(i,j), zeta_bot(i+1,j))
465  else
466  call find_neutral_surface_positions_discontinuous(cs, g%ke, &
467  cs%P_i(i,j,:,:), h(i,j,:), cs%T_i(i,j,:,:), cs%S_i(i,j,:,:), cs%ppoly_coeffs_T(i,j,:,:), &
468  cs%ppoly_coeffs_S(i,j,:,:),cs%stable_cell(i,j,:), &
469  cs%P_i(i+1,j,:,:), h(i+1,j,:), cs%T_i(i+1,j,:,:), cs%S_i(i+1,j,:,:), cs%ppoly_coeffs_T(i+1,j,:,:), &
470  cs%ppoly_coeffs_S(i+1,j,:,:), cs%stable_cell(i+1,j,:), &
471  cs%uPoL(i,j,:), cs%uPoR(i,j,:), cs%uKoL(i,j,:), cs%uKoR(i,j,:), cs%uhEff(i,j,:), &
472  hard_fail_heff = cs%hard_fail_heff)
473  endif
474  endif
475  enddo ; enddo
476 
477  ! Neutral surface factors at V points
478  do j = g%jsc-1, g%jec ; do i = g%isc, g%iec
479  if (g%mask2dCv(i,j) > 0.) then
480  if (cs%continuous_reconstruction) then
481  call find_neutral_surface_positions_continuous(g%ke, &
482  cs%Pint(i,j,:), cs%Tint(i,j,:), cs%Sint(i,j,:), cs%dRdT(i,j,:), cs%dRdS(i,j,:), &
483  cs%Pint(i,j+1,:), cs%Tint(i,j+1,:), cs%Sint(i,j+1,:), cs%dRdT(i,j+1,:), cs%dRdS(i,j+1,:), &
484  cs%vPoL(i,j,:), cs%vPoR(i,j,:), cs%vKoL(i,j,:), cs%vKoR(i,j,:), cs%vhEff(i,j,:), &
485  k_bot(i,j), k_bot(i,j+1), zeta_bot(i,j), zeta_bot(i,j+1))
486  else
487  call find_neutral_surface_positions_discontinuous(cs, g%ke, &
488  cs%P_i(i,j,:,:), h(i,j,:), cs%T_i(i,j,:,:), cs%S_i(i,j,:,:), cs%ppoly_coeffs_T(i,j,:,:), &
489  cs%ppoly_coeffs_S(i,j,:,:),cs%stable_cell(i,j,:), &
490  cs%P_i(i,j+1,:,:), h(i,j+1,:), cs%T_i(i,j+1,:,:), cs%S_i(i,j+1,:,:), cs%ppoly_coeffs_T(i,j+1,:,:), &
491  cs%ppoly_coeffs_S(i,j+1,:,:), cs%stable_cell(i,j+1,:), &
492  cs%vPoL(i,j,:), cs%vPoR(i,j,:), cs%vKoL(i,j,:), cs%vKoR(i,j,:), cs%vhEff(i,j,:), &
493  hard_fail_heff = cs%hard_fail_heff)
494  endif
495  endif
496  enddo ; enddo
497 
498  ! Continuous reconstructions calculate hEff as the difference between the pressures of the
499  ! neutral surfaces which need to be reconverted to thickness units. The discontinuous version
500  ! calculates hEff from the nondimensional fraction of the layer spanned by adjacent neutral
501  ! surfaces, so hEff is already in thickness units.
502  if (cs%continuous_reconstruction) then
503  do k = 1, cs%nsurf-1 ; do j = g%jsc, g%jec ; do i = g%isc-1, g%iec
504  if (g%mask2dCu(i,j) > 0.) cs%uhEff(i,j,k) = cs%uhEff(i,j,k) * pa_to_h
505  enddo ; enddo ; enddo
506  do k = 1, cs%nsurf-1 ; do j = g%jsc-1, g%jec ; do i = g%isc, g%iec
507  if (g%mask2dCv(i,j) > 0.) cs%vhEff(i,j,k) = cs%vhEff(i,j,k) * pa_to_h
508  enddo ; enddo ; enddo
509  endif
510 
511  if (cs%id_uhEff_2d>0) then
512  heff_sum(:,:) = 0.
513  do k = 1,cs%nsurf-1 ; do j=g%jsc,g%jec ; do i=g%isc-1,g%iec
514  heff_sum(i,j) = heff_sum(i,j) + cs%uhEff(i,j,k)
515  enddo ; enddo ; enddo
516  call post_data(cs%id_uhEff_2d, heff_sum, cs%diag)
517  endif
518  if (cs%id_vhEff_2d>0) then
519  heff_sum(:,:) = 0.
520  do k = 1,cs%nsurf-1 ; do j=g%jsc-1,g%jec ; do i=g%isc,g%iec
521  heff_sum(i,j) = heff_sum(i,j) + cs%vhEff(i,j,k)
522  enddo ; enddo ; enddo
523  call post_data(cs%id_vhEff_2d, heff_sum, cs%diag)
524  endif
525 

◆ neutral_diffusion_end()

subroutine, public mom_neutral_diffusion::neutral_diffusion_end ( type(neutral_diffusion_cs), pointer  CS)

Deallocates neutral_diffusion control structure.

Parameters
csNeutral diffusion control structure

Definition at line 2863 of file MOM_neutral_diffusion.F90.

2863  type(neutral_diffusion_CS), pointer :: CS !< Neutral diffusion control structure
2864 
2865  if (associated(cs)) deallocate(cs)
2866 

◆ neutral_diffusion_init()

logical function, public mom_neutral_diffusion::neutral_diffusion_init ( type(time_type), intent(in), target  Time,
type(ocean_grid_type), intent(in)  G,
type(unit_scale_type), intent(in)  US,
type(param_file_type), intent(in)  param_file,
type(diag_ctrl), intent(inout), target  diag,
type(eos_type), intent(in), target  EOS,
type(diabatic_cs), pointer  diabatic_CSp,
type(neutral_diffusion_cs), pointer  CS 
)

Read parameters and allocate control structure for neutral_diffusion module.

Parameters
[in]timeTime structure
[in]gGrid structure
[in]usA dimensional unit scaling type
[in,out]diagDiagnostics control structure
[in]param_fileParameter file structure
[in]eosEquation of state
diabatic_cspKPP control structure needed to get BLD
csNeutral diffusion control structure

Definition at line 117 of file MOM_neutral_diffusion.F90.

117  type(time_type), target, intent(in) :: Time !< Time structure
118  type(ocean_grid_type), intent(in) :: G !< Grid structure
119  type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type
120  type(diag_ctrl), target, intent(inout) :: diag !< Diagnostics control structure
121  type(param_file_type), intent(in) :: param_file !< Parameter file structure
122  type(EOS_type), target, intent(in) :: EOS !< Equation of state
123  type(diabatic_CS), pointer :: diabatic_CSp!< KPP control structure needed to get BLD
124  type(neutral_diffusion_CS), pointer :: CS !< Neutral diffusion control structure
125 
126  ! Local variables
127  character(len=256) :: mesg ! Message for error messages.
128  character(len=80) :: string ! Temporary strings
129  logical :: default_2018_answers
130  logical :: boundary_extrap
131 
132  if (associated(cs)) then
133  call mom_error(fatal, "neutral_diffusion_init called with associated control structure.")
134  return
135  endif
136 
137  ! Log this module and master switch for turning it on/off
138  call get_param(param_file, mdl, "USE_NEUTRAL_DIFFUSION", neutral_diffusion_init, &
139  default=.false., do_not_log=.true.)
140  call log_version(param_file, mdl, version, &
141  "This module implements neutral diffusion of tracers", &
142  all_default=.not.neutral_diffusion_init)
143  call get_param(param_file, mdl, "USE_NEUTRAL_DIFFUSION", neutral_diffusion_init, &
144  "If true, enables the neutral diffusion module.", &
145  default=.false.)
146 
147  if (.not.neutral_diffusion_init) return
148 
149  allocate(cs)
150  cs%diag => diag
151  cs%EOS => eos
152  ! call openParameterBlock(param_file,'NEUTRAL_DIFF')
153 
154  ! Read all relevant parameters and write them to the model log.
155  call get_param(param_file, mdl, "NDIFF_CONTINUOUS", cs%continuous_reconstruction, &
156  "If true, uses a continuous reconstruction of T and S when "//&
157  "finding neutral surfaces along which diffusion will happen. "//&
158  "If false, a PPM discontinuous reconstruction of T and S "//&
159  "is done which results in a higher order routine but exacts "//&
160  "a higher computational cost.", default=.true.)
161  call get_param(param_file, mdl, "NDIFF_REF_PRES", cs%ref_pres, &
162  "The reference pressure (Pa) used for the derivatives of "//&
163  "the equation of state. If negative (default), local pressure is used.", &
164  units="Pa", default = -1., scale=us%kg_m3_to_R*us%m_s_to_L_T**2)
165  call get_param(param_file, mdl, "NDIFF_INTERIOR_ONLY", cs%interior_only, &
166  "If true, only applies neutral diffusion in the ocean interior."//&
167  "That is, the algorithm will exclude the surface and bottom"//&
168  "boundary layers.", default = .false.)
169 
170  ! Initialize and configure remapping
171  if ( .not.cs%continuous_reconstruction ) then
172  call get_param(param_file, mdl, "NDIFF_BOUNDARY_EXTRAP", boundary_extrap, &
173  "Extrapolate at the top and bottommost cells, otherwise \n"// &
174  "assume boundaries are piecewise constant", &
175  default=.false.)
176  call get_param(param_file, mdl, "NDIFF_REMAPPING_SCHEME", string, &
177  "This sets the reconstruction scheme used "//&
178  "for vertical remapping for all variables. "//&
179  "It can be one of the following schemes: "//&
180  trim(remappingschemesdoc), default=remappingdefaultscheme)
181  call get_param(param_file, mdl, "DEFAULT_2018_ANSWERS", default_2018_answers, &
182  "This sets the default value for the various _2018_ANSWERS parameters.", &
183  default=.false.)
184  call get_param(param_file, mdl, "REMAPPING_2018_ANSWERS", cs%remap_answers_2018, &
185  "If true, use the order of arithmetic and expressions that recover the "//&
186  "answers from the end of 2018. Otherwise, use updated and more robust "//&
187  "forms of the same expressions.", default=default_2018_answers)
188  call initialize_remapping( cs%remap_CS, string, boundary_extrapolation=boundary_extrap, &
189  answers_2018=cs%remap_answers_2018 )
190  call extract_member_remapping_cs(cs%remap_CS, degree=cs%deg)
191  call get_param(param_file, mdl, "NEUTRAL_POS_METHOD", cs%neutral_pos_method, &
192  "Method used to find the neutral position \n"// &
193  "1. Delta_rho varies linearly, find 0 crossing \n"// &
194  "2. Alpha and beta vary linearly from top to bottom, \n"// &
195  " Newton's method for neutral position \n"// &
196  "3. Full nonlinear equation of state, use regula falsi \n"// &
197  " for neutral position", default=3)
198  if (cs%neutral_pos_method > 4 .or. cs%neutral_pos_method < 0) then
199  call mom_error(fatal,"Invalid option for NEUTRAL_POS_METHOD")
200  endif
201 
202  call get_param(param_file, mdl, "DELTA_RHO_FORM", cs%delta_rho_form, &
203  "Determine how the difference in density is calculated \n"// &
204  " full : Difference of in-situ densities \n"// &
205  " no_pressure: Calculated from dRdT, dRdS, but no \n"// &
206  " pressure dependence", &
207  default="mid_pressure")
208  if (cs%neutral_pos_method > 1) then
209  call get_param(param_file, mdl, "NDIFF_DRHO_TOL", cs%drho_tol, &
210  "Sets the convergence criterion for finding the neutral\n"// &
211  "position within a layer in kg m-3.", &
212  default=1.e-10, scale=us%kg_m3_to_R)
213  call get_param(param_file, mdl, "NDIFF_X_TOL", cs%x_tol, &
214  "Sets the convergence criterion for a change in nondim\n"// &
215  "position within a layer.", &
216  default=0.)
217  call get_param(param_file, mdl, "NDIFF_MAX_ITER", cs%max_iter, &
218  "The maximum number of iterations to be done before \n"// &
219  "exiting the iterative loop to find the neutral surface", &
220  default=10)
221  endif
222  call get_param(param_file, mdl, "NDIFF_DEBUG", cs%debug, &
223  "Turns on verbose output for discontinuous neutral "//&
224  "diffusion routines.", &
225  default = .false.)
226  call get_param(param_file, mdl, "HARD_FAIL_HEFF", cs%hard_fail_heff, &
227  "Bring down the model if a problem with heff is detected",&
228  default = .true.)
229  endif
230 
231  ! Store a rescaling factor for use in diagnostic messages.
232  cs%R_to_kg_m3 = us%R_to_kg_m3
233 
234  if (cs%interior_only) then
235  call extract_diabatic_member(diabatic_csp, kpp_csp=cs%KPP_CSp)
236  call extract_diabatic_member(diabatic_csp, energetic_pbl_csp=cs%energetic_PBL_CSp)
237  if ( .not. ASSOCIATED(cs%energetic_PBL_CSp) .and. .not. ASSOCIATED(cs%KPP_CSp) ) then
238  call mom_error(fatal,"NDIFF_INTERIOR_ONLY is true, but no valid boundary layer scheme was found")
239  endif
240  endif
241 
242 ! call get_param(param_file, mdl, "KHTR", CS%KhTr, &
243 ! "The background along-isopycnal tracer diffusivity.", &
244 ! units="m2 s-1", default=0.0)
245 ! call closeParameterBlock(param_file)
246  if (cs%continuous_reconstruction) then
247  cs%nsurf = 2*g%ke+2 ! Continuous reconstruction means that every interface has two connections
248  allocate(cs%dRdT(szi_(g),szj_(g),szk_(g)+1)) ; cs%dRdT(:,:,:) = 0.
249  allocate(cs%dRdS(szi_(g),szj_(g),szk_(g)+1)) ; cs%dRdS(:,:,:) = 0.
250  else
251  cs%nsurf = 4*g%ke ! Discontinuous means that every interface has four connections
252  allocate(cs%T_i(szi_(g),szj_(g),szk_(g),2)) ; cs%T_i(:,:,:,:) = 0.
253  allocate(cs%S_i(szi_(g),szj_(g),szk_(g),2)) ; cs%S_i(:,:,:,:) = 0.
254  allocate(cs%P_i(szi_(g),szj_(g),szk_(g),2)) ; cs%P_i(:,:,:,:) = 0.
255  allocate(cs%dRdT_i(szi_(g),szj_(g),szk_(g),2)) ; cs%dRdT_i(:,:,:,:) = 0.
256  allocate(cs%dRdS_i(szi_(g),szj_(g),szk_(g),2)) ; cs%dRdS_i(:,:,:,:) = 0.
257  allocate(cs%ppoly_coeffs_T(szi_(g),szj_(g),szk_(g),cs%deg+1)) ; cs%ppoly_coeffs_T(:,:,:,:) = 0.
258  allocate(cs%ppoly_coeffs_S(szi_(g),szj_(g),szk_(g),cs%deg+1)) ; cs%ppoly_coeffs_S(:,:,:,:) = 0.
259  allocate(cs%ns(szi_(g),szj_(g))) ; cs%ns(:,:) = 0.
260  endif
261  ! T-points
262  allocate(cs%Tint(szi_(g),szj_(g),szk_(g)+1)) ; cs%Tint(:,:,:) = 0.
263  allocate(cs%Sint(szi_(g),szj_(g),szk_(g)+1)) ; cs%Sint(:,:,:) = 0.
264  allocate(cs%Pint(szi_(g),szj_(g),szk_(g)+1)) ; cs%Pint(:,:,:) = 0.
265  allocate(cs%stable_cell(szi_(g),szj_(g),szk_(g))) ; cs%stable_cell(:,:,:) = .true.
266  ! U-points
267  allocate(cs%uPoL(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%uPoL(g%isc-1:g%iec,g%jsc:g%jec,:) = 0.
268  allocate(cs%uPoR(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%uPoR(g%isc-1:g%iec,g%jsc:g%jec,:) = 0.
269  allocate(cs%uKoL(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%uKoL(g%isc-1:g%iec,g%jsc:g%jec,:) = 0
270  allocate(cs%uKoR(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%uKoR(g%isc-1:g%iec,g%jsc:g%jec,:) = 0
271  allocate(cs%uHeff(g%isd:g%ied,g%jsd:g%jed,cs%nsurf-1)); cs%uHeff(g%isc-1:g%iec,g%jsc:g%jec,:) = 0
272  ! V-points
273  allocate(cs%vPoL(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%vPoL(g%isc:g%iec,g%jsc-1:g%jec,:) = 0.
274  allocate(cs%vPoR(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%vPoR(g%isc:g%iec,g%jsc-1:g%jec,:) = 0.
275  allocate(cs%vKoL(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%vKoL(g%isc:g%iec,g%jsc-1:g%jec,:) = 0
276  allocate(cs%vKoR(g%isd:g%ied,g%jsd:g%jed, cs%nsurf)); cs%vKoR(g%isc:g%iec,g%jsc-1:g%jec,:) = 0
277  allocate(cs%vHeff(g%isd:g%ied,g%jsd:g%jed,cs%nsurf-1)); cs%vHeff(g%isc:g%iec,g%jsc-1:g%jec,:) = 0
278 

◆ neutral_diffusion_unit_tests()

logical function, public mom_neutral_diffusion::neutral_diffusion_unit_tests ( logical, intent(in)  verbose)

Returns true if unit tests of neutral_diffusion functions fail. Otherwise returns false.

Parameters
[in]verboseIf true, write results to stdout

Definition at line 2080 of file MOM_neutral_diffusion.F90.

2080  logical, intent(in) :: verbose !< If true, write results to stdout
2081 
2082  neutral_diffusion_unit_tests = .false. .or. &
2083  ndiff_unit_tests_continuous(verbose) .or. ndiff_unit_tests_discontinuous(verbose)
2084 

◆ neutral_surface_flux()

subroutine mom_neutral_diffusion::neutral_surface_flux ( integer, intent(in)  nk,
integer, intent(in)  nsurf,
integer, intent(in)  deg,
real, dimension(nk), intent(in)  hl,
real, dimension(nk), intent(in)  hr,
real, dimension(nk), intent(in)  Tl,
real, dimension(nk), intent(in)  Tr,
real, dimension(nsurf), intent(in)  PiL,
real, dimension(nsurf), intent(in)  PiR,
integer, dimension(nsurf), intent(in)  KoL,
integer, dimension(nsurf), intent(in)  KoR,
real, dimension(nsurf-1), intent(in)  hEff,
real, dimension(nsurf-1), intent(inout)  Flx,
logical, intent(in)  continuous,
real, intent(in)  h_neglect,
type(remapping_cs), intent(in), optional  remap_CS,
real, intent(in), optional  h_neglect_edge 
)
private

Returns a single column of neutral diffusion fluxes of a tracer.

Parameters
[in]nkNumber of levels
[in]nsurfNumber of neutral surfaces
[in]degDegree of polynomial reconstructions
[in]hlLeft-column layer thickness [H ~> m or kg m-2]
[in]hrRight-column layer thickness [H ~> m or kg m-2]
[in]tlLeft-column layer tracer (conc, e.g. degC)
[in]trRight-column layer tracer (conc, e.g. degC)
[in]pilFractional position of neutral surface within layer KoL of left column
[in]pirFractional position of neutral surface within layer KoR of right column
[in]kolIndex of first left interface above neutral surface
[in]korIndex of first right interface above neutral surface
[in]heffEffective thickness between two neutral surfaces [H ~> m or kg m-2]
[in,out]flxFlux of tracer between pairs of neutral layers (conc H)
[in]continuousTrue if using continuous reconstruction
[in]h_neglectA negligibly small width for the purpose of cell reconstructions [H ~> m or kg m-2]
[in]remap_csRemapping control structure used to create sublayers
[in]h_neglect_edgeA negligibly small width used for edge value calculations if continuous is false [H ~> m or kg m-2]

Definition at line 1877 of file MOM_neutral_diffusion.F90.

1877  integer, intent(in) :: nk !< Number of levels
1878  integer, intent(in) :: nsurf !< Number of neutral surfaces
1879  integer, intent(in) :: deg !< Degree of polynomial reconstructions
1880  real, dimension(nk), intent(in) :: hl !< Left-column layer thickness [H ~> m or kg m-2]
1881  real, dimension(nk), intent(in) :: hr !< Right-column layer thickness [H ~> m or kg m-2]
1882  real, dimension(nk), intent(in) :: Tl !< Left-column layer tracer (conc, e.g. degC)
1883  real, dimension(nk), intent(in) :: Tr !< Right-column layer tracer (conc, e.g. degC)
1884  real, dimension(nsurf), intent(in) :: PiL !< Fractional position of neutral surface
1885  !! within layer KoL of left column
1886  real, dimension(nsurf), intent(in) :: PiR !< Fractional position of neutral surface
1887  !! within layer KoR of right column
1888  integer, dimension(nsurf), intent(in) :: KoL !< Index of first left interface above neutral surface
1889  integer, dimension(nsurf), intent(in) :: KoR !< Index of first right interface above neutral surface
1890  real, dimension(nsurf-1), intent(in) :: hEff !< Effective thickness between two neutral
1891  !! surfaces [H ~> m or kg m-2]
1892  real, dimension(nsurf-1), intent(inout) :: Flx !< Flux of tracer between pairs of neutral layers (conc H)
1893  logical, intent(in) :: continuous !< True if using continuous reconstruction
1894  real, intent(in) :: h_neglect !< A negligibly small width for the
1895  !! purpose of cell reconstructions [H ~> m or kg m-2]
1896  type(remapping_CS), optional, intent(in) :: remap_CS !< Remapping control structure used
1897  !! to create sublayers
1898  real, optional, intent(in) :: h_neglect_edge !< A negligibly small width used for
1899  !! edge value calculations if continuous is false [H ~> m or kg m-2]
1900  ! Local variables
1901  integer :: k_sublayer, klb, klt, krb, krt, k
1902  real :: T_right_top, T_right_bottom, T_right_layer, T_right_sub, T_right_top_int, T_right_bot_int
1903  real :: T_left_top, T_left_bottom, T_left_layer, T_left_sub, T_left_top_int, T_left_bot_int
1904  real :: dT_top, dT_bottom, dT_layer, dT_ave, dT_sublayer, dT_top_int, dT_bot_int
1905  real, dimension(nk+1) :: Til !< Left-column interface tracer (conc, e.g. degC)
1906  real, dimension(nk+1) :: Tir !< Right-column interface tracer (conc, e.g. degC)
1907  real, dimension(nk) :: aL_l !< Left-column left edge value of tracer (conc, e.g. degC)
1908  real, dimension(nk) :: aR_l !< Left-column right edge value of tracer (conc, e.g. degC)
1909  real, dimension(nk) :: aL_r !< Right-column left edge value of tracer (conc, e.g. degC)
1910  real, dimension(nk) :: aR_r !< Right-column right edge value of tracer (conc, e.g. degC)
1911  ! Discontinuous reconstruction
1912  integer :: iMethod
1913  real, dimension(nk,2) :: Tid_l !< Left-column interface tracer (conc, e.g. degC)
1914  real, dimension(nk,2) :: Tid_r !< Right-column interface tracer (conc, e.g. degC)
1915  real, dimension(nk,deg+1) :: ppoly_r_coeffs_l
1916  real, dimension(nk,deg+1) :: ppoly_r_coeffs_r
1917  real, dimension(nk,deg+1) :: ppoly_r_S_l
1918  real, dimension(nk,deg+1) :: ppoly_r_S_r
1919  logical :: down_flux
1920  ! Setup reconstruction edge values
1921  if (continuous) then
1922  call interface_scalar(nk, hl, tl, til, 2, h_neglect)
1923  call interface_scalar(nk, hr, tr, tir, 2, h_neglect)
1924  call ppm_left_right_edge_values(nk, tl, til, al_l, ar_l)
1925  call ppm_left_right_edge_values(nk, tr, tir, al_r, ar_r)
1926  else
1927  ppoly_r_coeffs_l(:,:) = 0.
1928  ppoly_r_coeffs_r(:,:) = 0.
1929  tid_l(:,:) = 0.
1930  tid_r(:,:) = 0.
1931 
1932  call build_reconstructions_1d( remap_cs, nk, hl, tl, ppoly_r_coeffs_l, tid_l, &
1933  ppoly_r_s_l, imethod, h_neglect, h_neglect_edge )
1934  call build_reconstructions_1d( remap_cs, nk, hr, tr, ppoly_r_coeffs_r, tid_r, &
1935  ppoly_r_s_r, imethod, h_neglect, h_neglect_edge )
1936  endif
1937 
1938  do k_sublayer = 1, nsurf-1
1939  if (heff(k_sublayer) == 0.) then
1940  flx(k_sublayer) = 0.
1941  else
1942  if (continuous) then
1943  klb = kol(k_sublayer+1)
1944  t_left_bottom = ( 1. - pil(k_sublayer+1) ) * til(klb) + pil(k_sublayer+1) * til(klb+1)
1945  klt = kol(k_sublayer)
1946  t_left_top = ( 1. - pil(k_sublayer) ) * til(klt) + pil(k_sublayer) * til(klt+1)
1947  t_left_layer = ppm_ave(pil(k_sublayer), pil(k_sublayer+1) + real(klb-klt), &
1948  al_l(klt), ar_l(klt), tl(klt))
1949 
1950  krb = kor(k_sublayer+1)
1951  t_right_bottom = ( 1. - pir(k_sublayer+1) ) * tir(krb) + pir(k_sublayer+1) * tir(krb+1)
1952  krt = kor(k_sublayer)
1953  t_right_top = ( 1. - pir(k_sublayer) ) * tir(krt) + pir(k_sublayer) * tir(krt+1)
1954  t_right_layer = ppm_ave(pir(k_sublayer), pir(k_sublayer+1) + real(krb-krt), &
1955  al_r(krt), ar_r(krt), tr(krt))
1956  dt_top = t_right_top - t_left_top
1957  dt_bottom = t_right_bottom - t_left_bottom
1958  dt_ave = 0.5 * ( dt_top + dt_bottom )
1959  dt_layer = t_right_layer - t_left_layer
1960  if (signum(1.,dt_top) * signum(1.,dt_bottom) <= 0. .or. signum(1.,dt_ave) * signum(1.,dt_layer) <= 0.) then
1961  dt_ave = 0.
1962  else
1963  dt_ave = dt_layer
1964  endif
1965  flx(k_sublayer) = dt_ave * heff(k_sublayer)
1966  else ! Discontinuous reconstruction
1967  ! Calculate tracer values on left and right side of the neutral surface
1968  call neutral_surface_t_eval(nk, nsurf, k_sublayer, kol, pil, tl, tid_l, deg, imethod, &
1969  ppoly_r_coeffs_l, t_left_top, t_left_bottom, t_left_sub, &
1970  t_left_top_int, t_left_bot_int, t_left_layer)
1971  call neutral_surface_t_eval(nk, nsurf, k_sublayer, kor, pir, tr, tid_r, deg, imethod, &
1972  ppoly_r_coeffs_r, t_right_top, t_right_bottom, t_right_sub, &
1973  t_right_top_int, t_right_bot_int, t_right_layer)
1974 
1975  dt_top = t_right_top - t_left_top
1976  dt_bottom = t_right_bottom - t_left_bottom
1977  dt_sublayer = t_right_sub - t_left_sub
1978  dt_top_int = t_right_top_int - t_left_top_int
1979  dt_bot_int = t_right_bot_int - t_left_bot_int
1980  ! Enforcing the below criterion incorrectly zero out fluxes
1981  !dT_layer = T_right_layer - T_left_layer
1982 
1983  down_flux = dt_top <= 0. .and. dt_bottom <= 0. .and. &
1984  dt_sublayer <= 0. .and. dt_top_int <= 0. .and. &
1985  dt_bot_int <= 0.
1986  down_flux = down_flux .or. &
1987  (dt_top >= 0. .and. dt_bottom >= 0. .and. &
1988  dt_sublayer >= 0. .and. dt_top_int >= 0. .and. &
1989  dt_bot_int >= 0.)
1990  if (down_flux) then
1991  flx(k_sublayer) = dt_sublayer * heff(k_sublayer)
1992  else
1993  flx(k_sublayer) = 0.
1994  endif
1995  endif
1996  endif
1997  enddo
1998 

◆ neutral_surface_t_eval()

subroutine mom_neutral_diffusion::neutral_surface_t_eval ( integer, intent(in)  nk,
integer, intent(in)  ns,
integer, intent(in)  k_sub,
integer, dimension(ns), intent(in)  Ks,
real, dimension(ns), intent(in)  Ps,
real, dimension(nk), intent(in)  T_mean,
real, dimension(nk,2), intent(in)  T_int,
integer, intent(in)  deg,
integer, intent(in)  iMethod,
real, dimension(nk,deg+1), intent(in)  T_poly,
real, intent(out)  T_top,
real, intent(out)  T_bot,
real, intent(out)  T_sub,
real, intent(out)  T_top_int,
real, intent(out)  T_bot_int,
real, intent(out)  T_layer 
)
private

Evaluate various parts of the reconstructions to calculate gradient-based flux limter.

Parameters
[in]nkNumber of cell everages
[in]nsNumber of neutral surfaces
[in]k_subIndex of current neutral layer
[in]ksList of the layers associated with each neutral surface
[in]psList of the positions within a layer of each surface
[in]t_meanCell average of tracer
[in]t_intCell interface values of tracer from reconstruction
[in]degDegree of reconstruction polynomial (e.g. 1 is linear)
[in]imethodMethod of integration to use
[in]t_polyCoefficients of polynomial reconstructions
[out]t_topTracer value at top (across discontinuity if necessary)
[out]t_botTracer value at bottom (across discontinuity if necessary)
[out]t_subAverage of the tracer value over the sublayer
[out]t_top_intTracer value at top interface of neutral layer
[out]t_bot_intTracer value at bottom interface of neutral layer
[out]t_layerCell-average that the the reconstruction belongs to

Definition at line 2004 of file MOM_neutral_diffusion.F90.

2004  integer, intent(in ) :: nk !< Number of cell everages
2005  integer, intent(in ) :: ns !< Number of neutral surfaces
2006  integer, intent(in ) :: k_sub !< Index of current neutral layer
2007  integer, dimension(ns), intent(in ) :: Ks !< List of the layers associated with each neutral surface
2008  real, dimension(ns), intent(in ) :: Ps !< List of the positions within a layer of each surface
2009  real, dimension(nk), intent(in ) :: T_mean !< Cell average of tracer
2010  real, dimension(nk,2), intent(in ) :: T_int !< Cell interface values of tracer from reconstruction
2011  integer, intent(in ) :: deg !< Degree of reconstruction polynomial (e.g. 1 is linear)
2012  integer, intent(in ) :: iMethod !< Method of integration to use
2013  real, dimension(nk,deg+1), intent(in ) :: T_poly !< Coefficients of polynomial reconstructions
2014  real, intent( out) :: T_top !< Tracer value at top (across discontinuity if necessary)
2015  real, intent( out) :: T_bot !< Tracer value at bottom (across discontinuity if necessary)
2016  real, intent( out) :: T_sub !< Average of the tracer value over the sublayer
2017  real, intent( out) :: T_top_int !< Tracer value at top interface of neutral layer
2018  real, intent( out) :: T_bot_int !< Tracer value at bottom interface of neutral layer
2019  real, intent( out) :: T_layer !< Cell-average that the the reconstruction belongs to
2020 
2021  integer :: kl, ks_top, ks_bot
2022 
2023  ks_top = k_sub
2024  ks_bot = k_sub + 1
2025  if ( ks(ks_top) /= ks(ks_bot) ) then
2026  call mom_error(fatal, "Neutral surfaces span more than one layer")
2027  endif
2028  kl = ks(k_sub)
2029  ! First if the neutral surfaces spans the entirety of a cell, then do not search across the discontinuity
2030  if ( (ps(ks_top) == 0.) .and. (ps(ks_bot) == 1.)) then
2031  t_top = t_int(kl,1)
2032  t_bot = t_int(kl,2)
2033  else
2034  ! Search across potential discontinuity at top
2035  if ( (kl > 1) .and. (ps(ks_top) == 0.) ) then
2036  t_top = t_int(kl-1,2)
2037  else
2038  t_top = evaluation_polynomial( t_poly(kl,:), deg+1, ps(ks_top) )
2039  endif
2040  ! Search across potential discontinuity at bottom
2041  if ( (kl < nk) .and. (ps(ks_bot) == 1.) ) then
2042  t_bot = t_int(kl+1,1)
2043  else
2044  t_bot = evaluation_polynomial( t_poly(kl,:), deg+1, ps(ks_bot) )
2045  endif
2046  endif
2047  t_sub = average_value_ppoly(nk, t_mean, t_int, t_poly, imethod, kl, ps(ks_top), ps(ks_bot))
2048  t_top_int = evaluation_polynomial( t_poly(kl,:), deg+1, ps(ks_top))
2049  t_bot_int = evaluation_polynomial( t_poly(kl,:), deg+1, ps(ks_bot))
2050  t_layer = t_mean(kl)
2051 

◆ plm_diff()

subroutine mom_neutral_diffusion::plm_diff ( integer, intent(in)  nk,
real, dimension(nk), intent(in)  h,
real, dimension(nk), intent(in)  S,
integer, intent(in)  c_method,
integer, intent(in)  b_method,
real, dimension(nk), intent(inout)  diff 
)
private

Returns PLM slopes for a column where the slopes are the difference in value across each cell. The limiting follows equation 1.8 in Colella & Woodward, 1984: JCP 54, 174-201.

Parameters
[in]nkNumber of levels
[in]hLayer thickness [H ~> m or kg m-2]
[in]sLayer salinity (conc, e.g. ppt)
[in]c_methodMethod to use for the centered difference
[in]b_method=1, use PCM in first/last cell, =2 uses linear extrapolation
[in,out]diffScalar difference across layer (conc, e.g. ppt) determined by the following values for c_method:
  1. Second order finite difference (not recommended)
  2. Second order finite volume (used in original PPM)
  3. Finite-volume weighted least squares linear fit
Todo:
The use of c_method to choose a scheme is inefficient and should eventually be moved up the call tree.

Definition at line 809 of file MOM_neutral_diffusion.F90.

809  integer, intent(in) :: nk !< Number of levels
810  real, dimension(nk), intent(in) :: h !< Layer thickness [H ~> m or kg m-2]
811  real, dimension(nk), intent(in) :: S !< Layer salinity (conc, e.g. ppt)
812  integer, intent(in) :: c_method !< Method to use for the centered difference
813  integer, intent(in) :: b_method !< =1, use PCM in first/last cell, =2 uses linear extrapolation
814  real, dimension(nk), intent(inout) :: diff !< Scalar difference across layer (conc, e.g. ppt)
815  !! determined by the following values for c_method:
816  !! 1. Second order finite difference (not recommended)
817  !! 2. Second order finite volume (used in original PPM)
818  !! 3. Finite-volume weighted least squares linear fit
819  !! \todo The use of c_method to choose a scheme is inefficient
820  !! and should eventually be moved up the call tree.
821 
822  ! Local variables
823  integer :: k
824  real :: hkm1, hk, hkp1, Skm1, Sk, Skp1, diff_l, diff_r, diff_c
825 
826  do k = 2, nk-1
827  hkm1 = h(k-1)
828  hk = h(k)
829  hkp1 = h(k+1)
830 
831  if ( ( hkp1 + hk ) * ( hkm1 + hk ) > 0.) then
832  skm1 = s(k-1)
833  sk = s(k)
834  skp1 = s(k+1)
835  if (c_method==1) then
836  ! Simple centered diff (from White)
837  if ( hk + 0.5 * (hkm1 + hkp1) /= 0. ) then
838  diff_c = ( skp1 - skm1 ) * ( hk / ( hk + 0.5 * (hkm1 + hkp1) ) )
839  else
840  diff_c = 0.
841  endif
842  elseif (c_method==2) then
843  ! Second order accurate centered FV slope (from Colella and Woodward, JCP 1984)
844  diff_c = fv_diff(hkm1, hk, hkp1, skm1, sk, skp1)
845  elseif (c_method==3) then
846  ! Second order accurate finite-volume least squares slope
847  diff_c = hk * fvlsq_slope(hkm1, hk, hkp1, skm1, sk, skp1)
848  endif
849  ! Limit centered slope by twice the side differenced slopes
850  diff_l = 2. * ( sk - skm1 )
851  diff_r = 2. * ( skp1 - sk )
852  if ( signum(1., diff_l) * signum(1., diff_r) <= 0. ) then
853  diff(k) = 0. ! PCM for local extrema
854  else
855  diff(k) = sign( min( abs(diff_l), abs(diff_c), abs(diff_r) ), diff_c )
856  endif
857  else
858  diff(k) = 0. ! PCM next to vanished layers
859  endif
860  enddo
861  if (b_method==1) then ! PCM for top and bottom layer
862  diff(1) = 0.
863  diff(nk) = 0.
864  elseif (b_method==2) then ! Linear extrapolation for top and bottom interfaces
865  diff(1) = ( s(2) - s(1) ) * 2. * ( h(1) / ( h(1) + h(2) ) )
866  diff(nk) = s(nk) - s(nk-1) * 2. * ( h(nk) / ( h(nk-1) + h(nk) ) )
867  endif
868 

◆ ppm_ave()

real function mom_neutral_diffusion::ppm_ave ( real, intent(in)  xL,
real, intent(in)  xR,
real, intent(in)  aL,
real, intent(in)  aR,
real, intent(in)  aMean 
)
private

Returns the average of a PPM reconstruction between two fractional positions.

Parameters
[in]xlFraction position of left bound (0,1)
[in]xrFraction position of right bound (0,1)
[in]alLeft edge scalar value, at x=0
[in]arRight edge scalar value, at x=1
[in]ameanAverage scalar value of cell

Definition at line 771 of file MOM_neutral_diffusion.F90.

771  real, intent(in) :: xL !< Fraction position of left bound (0,1)
772  real, intent(in) :: xR !< Fraction position of right bound (0,1)
773  real, intent(in) :: aL !< Left edge scalar value, at x=0
774  real, intent(in) :: aR !< Right edge scalar value, at x=1
775  real, intent(in) :: aMean !< Average scalar value of cell
776 
777  ! Local variables
778  real :: dx, xave, a6, a6o3
779 
780  dx = xr - xl
781  xave = 0.5 * ( xr + xl )
782  a6o3 = 2. * amean - ( al + ar ) ! a6 / 3.
783  a6 = 3. * a6o3
784 
785  if (dx<0.) then
786  stop 'ppm_ave: dx<0 should not happend!'
787  elseif (dx>1.) then
788  stop 'ppm_ave: dx>1 should not happend!'
789  elseif (dx==0.) then
790  ppm_ave = al + ( ar - al ) * xr + a6 * xr * ( 1. - xr )
791  else
792  ppm_ave = ( al + xave * ( ( ar - al ) + a6 ) ) - a6o3 * ( xr**2 + xr * xl + xl**2 )
793  endif

◆ ppm_edge()

real function mom_neutral_diffusion::ppm_edge ( real, intent(in)  hkm1,
real, intent(in)  hk,
real, intent(in)  hkp1,
real, intent(in)  hkp2,
real, intent(in)  Ak,
real, intent(in)  Akp1,
real, intent(in)  Pk,
real, intent(in)  Pkp1,
real, intent(in)  h_neglect 
)
private

Returns the PPM quasi-fourth order edge value at k+1/2 following equation 1.6 in Colella & Woodward, 1984: JCP 54, 174-201.

Parameters
[in]hkm1Width of cell k-1
[in]hkWidth of cell k
[in]hkp1Width of cell k+1
[in]hkp2Width of cell k+2
[in]akAverage scalar value of cell k
[in]akp1Average scalar value of cell k+1
[in]pkPLM slope for cell k
[in]pkp1PLM slope for cell k+1
[in]h_neglectA negligibly small thickness [H ~> m or kg m-2]

Definition at line 731 of file MOM_neutral_diffusion.F90.

731  real, intent(in) :: hkm1 !< Width of cell k-1
732  real, intent(in) :: hk !< Width of cell k
733  real, intent(in) :: hkp1 !< Width of cell k+1
734  real, intent(in) :: hkp2 !< Width of cell k+2
735  real, intent(in) :: Ak !< Average scalar value of cell k
736  real, intent(in) :: Akp1 !< Average scalar value of cell k+1
737  real, intent(in) :: Pk !< PLM slope for cell k
738  real, intent(in) :: Pkp1 !< PLM slope for cell k+1
739  real, intent(in) :: h_neglect !< A negligibly small thickness [H ~> m or kg m-2]
740 
741  ! Local variables
742  real :: R_hk_hkp1, R_2hk_hkp1, R_hk_2hkp1, f1, f2, f3, f4
743 
744  r_hk_hkp1 = hk + hkp1
745  if (r_hk_hkp1 <= 0.) then
746  ppm_edge = 0.5 * ( ak + akp1 )
747  return
748  endif
749  r_hk_hkp1 = 1. / r_hk_hkp1
750  if (hk<hkp1) then
751  ppm_edge = ak + ( hk * r_hk_hkp1 ) * ( akp1 - ak )
752  else
753  ppm_edge = akp1 + ( hkp1 * r_hk_hkp1 ) * ( ak - akp1 )
754  endif
755 
756  r_2hk_hkp1 = 1. / ( ( 2. * hk + hkp1 ) + h_neglect )
757  r_hk_2hkp1 = 1. / ( ( hk + 2. * hkp1 ) + h_neglect )
758  f1 = 1./ ( ( hk + hkp1) + ( hkm1 + hkp2 ) )
759  f2 = 2. * ( hkp1 * hk ) * r_hk_hkp1 * &
760  ( ( hkm1 + hk ) * r_2hk_hkp1 - ( hkp2 + hkp1 ) * r_hk_2hkp1 )
761  f3 = hk * ( hkm1 + hk ) * r_2hk_hkp1
762  f4 = hkp1 * ( hkp1 + hkp2 ) * r_hk_2hkp1
763 
764  ppm_edge = ppm_edge + f1 * ( f2 * ( akp1 - ak ) - ( f3 * pkp1 - f4 * pk ) )
765 

◆ ppm_left_right_edge_values()

subroutine mom_neutral_diffusion::ppm_left_right_edge_values ( integer, intent(in)  nk,
real, dimension(nk), intent(in)  Tl,
real, dimension(nk+1), intent(in)  Ti,
real, dimension(nk), intent(inout)  aL,
real, dimension(nk), intent(inout)  aR 
)
private

Discontinuous PPM reconstructions of the left/right edge values within a cell.

Parameters
[in]nkNumber of levels
[in]tlLayer tracer (conc, e.g. degC)
[in]tiInterface tracer (conc, e.g. degC)
[in,out]alLeft edge value of tracer (conc, e.g. degC)
[in,out]arRight edge value of tracer (conc, e.g. degC)

Definition at line 2056 of file MOM_neutral_diffusion.F90.

2056  integer, intent(in) :: nk !< Number of levels
2057  real, dimension(nk), intent(in) :: Tl !< Layer tracer (conc, e.g. degC)
2058  real, dimension(nk+1), intent(in) :: Ti !< Interface tracer (conc, e.g. degC)
2059  real, dimension(nk), intent(inout) :: aL !< Left edge value of tracer (conc, e.g. degC)
2060  real, dimension(nk), intent(inout) :: aR !< Right edge value of tracer (conc, e.g. degC)
2061 
2062  integer :: k
2063  ! Setup reconstruction edge values
2064  do k = 1, nk
2065  al(k) = ti(k)
2066  ar(k) = ti(k+1)
2067  if ( signum(1., ar(k) - tl(k))*signum(1., tl(k) - al(k)) <= 0.0 ) then
2068  al(k) = tl(k)
2069  ar(k) = tl(k)
2070  elseif ( sign(3., ar(k) - al(k)) * ( (tl(k) - al(k)) + (tl(k) - ar(k))) > abs(ar(k) - al(k)) ) then
2071  al(k) = tl(k) + 2.0 * ( tl(k) - ar(k) )
2072  elseif ( sign(3., ar(k) - al(k)) * ( (tl(k) - al(k)) + (tl(k) - ar(k))) < -abs(ar(k) - al(k)) ) then
2073  ar(k) = tl(k) + 2.0 * ( tl(k) - al(k) )
2074  endif
2075  enddo

◆ search_other_column()

real function mom_neutral_diffusion::search_other_column ( type(neutral_diffusion_cs), intent(in)  CS,
integer, intent(in)  ksurf,
real, intent(in)  pos_last,
real, intent(in)  T_from,
real, intent(in)  S_from,
real, intent(in)  P_from,
real, intent(in)  T_top,
real, intent(in)  S_top,
real, intent(in)  P_top,
real, intent(in)  T_bot,
real, intent(in)  S_bot,
real, intent(in)  P_bot,
real, dimension(:), intent(in)  T_poly,
real, dimension(:), intent(in)  S_poly 
)
private

Searches the "other" (searched) column for the position of the neutral surface.

Parameters
[in]csNeutral diffusion control structure
[in]ksurfCurrent index of neutral surface
[in]pos_lastLast position within the current layer, used as the lower bound in the root finding algorithm [nondim]
[in]t_fromTemperature at the searched from interface [degC]
[in]s_fromSalinity at the searched from interface [ppt]
[in]p_fromPressure at the searched from interface [R L2 T-2 ~> Pa]
[in]t_topTemperature at the searched to top interface [degC]
[in]s_topSalinity at the searched to top interface [ppt]
[in]p_topPressure at the searched to top interface [R L2 T-2 ~> Pa] interface [R L2 T-2 ~> Pa]
[in]t_botTemperature at the searched to bottom interface [degC]
[in]s_botSalinity at the searched to bottom interface [ppt]
[in]p_botPressure at the searched to bottom interface [R L2 T-2 ~> Pa]
[in]t_polyTemperature polynomial reconstruction coefficients [degC]
[in]s_polySalinity polynomial reconstruction coefficients [ppt]

Definition at line 1439 of file MOM_neutral_diffusion.F90.

1439  type(neutral_diffusion_CS), intent(in ) :: CS !< Neutral diffusion control structure
1440  integer, intent(in ) :: ksurf !< Current index of neutral surface
1441  real, intent(in ) :: pos_last !< Last position within the current layer, used as the lower
1442  !! bound in the root finding algorithm [nondim]
1443  real, intent(in ) :: T_from !< Temperature at the searched from interface [degC]
1444  real, intent(in ) :: S_from !< Salinity at the searched from interface [ppt]
1445  real, intent(in ) :: P_from !< Pressure at the searched from interface [R L2 T-2 ~> Pa]
1446  real, intent(in ) :: T_top !< Temperature at the searched to top interface [degC]
1447  real, intent(in ) :: S_top !< Salinity at the searched to top interface [ppt]
1448  real, intent(in ) :: P_top !< Pressure at the searched to top interface [R L2 T-2 ~> Pa]
1449  !! interface [R L2 T-2 ~> Pa]
1450  real, intent(in ) :: T_bot !< Temperature at the searched to bottom interface [degC]
1451  real, intent(in ) :: S_bot !< Salinity at the searched to bottom interface [ppt]
1452  real, intent(in ) :: P_bot !< Pressure at the searched to bottom
1453  !! interface [R L2 T-2 ~> Pa]
1454  real, dimension(:), intent(in ) :: T_poly !< Temperature polynomial reconstruction coefficients [degC]
1455  real, dimension(:), intent(in ) :: S_poly !< Salinity polynomial reconstruction coefficients [ppt]
1456  ! Local variables
1457  real :: dRhotop, dRhobot ! Density differences [R ~> kg m-3]
1458  real :: dRdT_top, dRdT_bot, dRdT_from ! Partial derivatives of density with temperature [R degC-1 ~> kg m-3 degC-1]
1459  real :: dRdS_top, dRdS_bot, dRdS_from ! Partial derivatives of density with salinity [R ppt-1 ~> kg m-3 ppt-1]
1460 
1461  ! Calculate the differencei in density at the tops or the bottom
1462  if (cs%neutral_pos_method == 1 .or. cs%neutral_pos_method == 3) then
1463  call calc_delta_rho_and_derivs(cs, t_top, s_top, p_top, t_from, s_from, p_from, drhotop)
1464  call calc_delta_rho_and_derivs(cs, t_bot, s_bot, p_bot, t_from, s_from, p_from, drhobot)
1465  elseif (cs%neutral_pos_method == 2) then
1466  call calc_delta_rho_and_derivs(cs, t_top, s_top, p_top, t_from, s_from, p_from, drhotop, &
1467  drdt_top, drds_top, drdt_from, drds_from)
1468  call calc_delta_rho_and_derivs(cs, t_bot, s_bot, p_bot, t_from, s_from, p_from, drhobot, &
1469  drdt_bot, drds_bot, drdt_from, drds_from)
1470  endif
1471 
1472  ! Handle all the special cases EXCEPT if it connects within the layer
1473  if ( (drhotop > 0.) .or. (ksurf == 1) ) then ! First interface or lighter than anything in layer
1474  pos = pos_last
1475  elseif ( drhotop > drhobot ) then ! Unstably stratified
1476  pos = 1.
1477  elseif ( drhotop < 0. .and. drhobot < 0.) then ! Denser than anything in layer
1478  pos = 1.
1479  elseif ( drhotop == 0. .and. drhobot == 0. ) then ! Perfectly unstratified
1480  pos = 1.
1481  elseif ( drhobot == 0. ) then ! Matches perfectly at the Top
1482  pos = 1.
1483  elseif ( drhotop == 0. ) then ! Matches perfectly at the Bottom
1484  pos = pos_last
1485  else ! Neutral surface within layer
1486  pos = -1
1487  endif
1488 
1489  ! Can safely return if position is >= 0 otherwise will need to find the position within the layer
1490  if (pos>=0) return
1491 
1492  if (cs%neutral_pos_method==1) then
1493  pos = interpolate_for_nondim_position( drhotop, p_top, drhobot, p_bot )
1494  ! For the 'Linear' case of finding the neutral position, the fromerence pressure to use is the average
1495  ! of the midpoint of the layer being searched and the interface being searched from
1496  elseif (cs%neutral_pos_method == 2) then
1497  pos = find_neutral_pos_linear( cs, pos_last, t_from, s_from, drdt_from, drds_from, &
1498  drdt_top, drds_top, drdt_bot, drds_bot, t_poly, s_poly )
1499  elseif (cs%neutral_pos_method == 3) then
1500  pos = find_neutral_pos_full( cs, pos_last, t_from, s_from, p_from, p_top, p_bot, t_poly, s_poly)
1501  endif
1502 

◆ signum()

real function mom_neutral_diffusion::signum ( real, intent(in)  a,
real, intent(in)  x 
)
private

A true signum function that returns either -abs(a), when x<0; or abs(a) when x>0; or 0 when x=0.

Parameters
[in]aThe magnitude argument
[in]xThe sign (or zero) argument

Definition at line 798 of file MOM_neutral_diffusion.F90.

798  real, intent(in) :: a !< The magnitude argument
799  real, intent(in) :: x !< The sign (or zero) argument
800 
801  signum = sign(a,x)
802  if (x==0.) signum = 0.
803 

◆ test_data1d()

logical function mom_neutral_diffusion::test_data1d ( logical, intent(in)  verbose,
integer, intent(in)  nk,
real, dimension(nk), intent(in)  Po,
real, dimension(nk), intent(in)  Ptrue,
character(len=*), intent(in)  title 
)
private

Returns true if comparison of Po and Ptrue fails, and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]nkNumber of layers
[in]poCalculated answer
[in]ptrueTrue answer
[in]titleTitle for messages

Definition at line 2706 of file MOM_neutral_diffusion.F90.

2706  logical, intent(in) :: verbose !< If true, write results to stdout
2707  integer, intent(in) :: nk !< Number of layers
2708  real, dimension(nk), intent(in) :: Po !< Calculated answer
2709  real, dimension(nk), intent(in) :: Ptrue !< True answer
2710  character(len=*), intent(in) :: title !< Title for messages
2711 
2712  ! Local variables
2713  integer :: k, stdunit
2714 
2715  test_data1d = .false.
2716  do k = 1,nk
2717  if (po(k) /= ptrue(k)) test_data1d = .true.
2718  enddo
2719 
2720  if (test_data1d .or. verbose) then
2721  stdunit = stdout
2722  if (test_data1d) stdunit = stderr ! In case of wrong results, write to error stream
2723  write(stdunit,'(a)') title
2724  do k = 1,nk
2725  if (po(k) /= ptrue(k)) then
2726  test_data1d = .true.
2727  write(stdunit,'(a,i2,2(x,a,f20.16),x,a,1pe22.15,x,a)') &
2728  'k=',k,'Po=',po(k),'Ptrue=',ptrue(k),'err=',po(k)-ptrue(k),'WRONG!'
2729  else
2730  if (verbose) &
2731  write(stdunit,'(a,i2,2(x,a,f20.16),x,a,1pe22.15)') &
2732  'k=',k,'Po=',po(k),'Ptrue=',ptrue(k),'err=',po(k)-ptrue(k)
2733  endif
2734  enddo
2735  endif
2736 

◆ test_data1di()

logical function mom_neutral_diffusion::test_data1di ( logical, intent(in)  verbose,
integer, intent(in)  nk,
integer, dimension(nk), intent(in)  Po,
integer, dimension(nk), intent(in)  Ptrue,
character(len=*), intent(in)  title 
)
private

Returns true if comparison of Po and Ptrue fails, and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]nkNumber of layers
[in]poCalculated answer
[in]ptrueTrue answer
[in]titleTitle for messages

Definition at line 2741 of file MOM_neutral_diffusion.F90.

2741  logical, intent(in) :: verbose !< If true, write results to stdout
2742  integer, intent(in) :: nk !< Number of layers
2743  integer, dimension(nk), intent(in) :: Po !< Calculated answer
2744  integer, dimension(nk), intent(in) :: Ptrue !< True answer
2745  character(len=*), intent(in) :: title !< Title for messages
2746 
2747  ! Local variables
2748  integer :: k, stdunit
2749 
2750  test_data1di = .false.
2751  do k = 1,nk
2752  if (po(k) /= ptrue(k)) test_data1di = .true.
2753  enddo
2754 
2755  if (test_data1di .or. verbose) then
2756  stdunit = stdout
2757  if (test_data1di) stdunit = stderr ! In case of wrong results, write to error stream
2758  write(stdunit,'(a)') title
2759  do k = 1,nk
2760  if (po(k) /= ptrue(k)) then
2761  test_data1di = .true.
2762  write(stdunit,'(a,i2,2(x,a,i5),x,a)') 'k=',k,'Io=',po(k),'Itrue=',ptrue(k),'WRONG!'
2763  else
2764  if (verbose) &
2765  write(stdunit,'(a,i2,2(x,a,i5))') 'k=',k,'Io=',po(k),'Itrue=',ptrue(k)
2766  endif
2767  enddo
2768  endif
2769 

◆ test_fv_diff()

logical function mom_neutral_diffusion::test_fv_diff ( logical, intent(in)  verbose,
real, intent(in)  hkm1,
real, intent(in)  hk,
real, intent(in)  hkp1,
real, intent(in)  Skm1,
real, intent(in)  Sk,
real, intent(in)  Skp1,
real, intent(in)  Ptrue,
character(len=*), intent(in)  title 
)
private

Returns true if a test of fv_diff() fails, and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]hkm1Left cell width [nondim]
[in]hkCenter cell width [nondim]
[in]hkp1Right cell width [nondim]
[in]skm1Left cell average value
[in]skCenter cell average value
[in]skp1Right cell average value
[in]ptrueTrue answer [nondim]
[in]titleTitle for messages

Definition at line 2610 of file MOM_neutral_diffusion.F90.

2610  logical, intent(in) :: verbose !< If true, write results to stdout
2611  real, intent(in) :: hkm1 !< Left cell width [nondim]
2612  real, intent(in) :: hk !< Center cell width [nondim]
2613  real, intent(in) :: hkp1 !< Right cell width [nondim]
2614  real, intent(in) :: Skm1 !< Left cell average value
2615  real, intent(in) :: Sk !< Center cell average value
2616  real, intent(in) :: Skp1 !< Right cell average value
2617  real, intent(in) :: Ptrue !< True answer [nondim]
2618  character(len=*), intent(in) :: title !< Title for messages
2619 
2620  ! Local variables
2621  integer :: stdunit
2622  real :: Pret
2623 
2624  pret = fv_diff(hkm1, hk, hkp1, skm1, sk, skp1)
2625  test_fv_diff = (pret /= ptrue)
2626 
2627  if (test_fv_diff .or. verbose) then
2628  stdunit = stdout
2629  if (test_fv_diff) stdunit = stderr ! In case of wrong results, write to error stream
2630  write(stdunit,'(a)') title
2631  if (test_fv_diff) then
2632  write(stdunit,'(2(x,a,f20.16),x,a)') 'pRet=',pret,'pTrue=',ptrue,'WRONG!'
2633  else
2634  write(stdunit,'(2(x,a,f20.16))') 'pRet=',pret,'pTrue=',ptrue
2635  endif
2636  endif
2637 

◆ test_fvlsq_slope()

logical function mom_neutral_diffusion::test_fvlsq_slope ( logical, intent(in)  verbose,
real, intent(in)  hkm1,
real, intent(in)  hk,
real, intent(in)  hkp1,
real, intent(in)  Skm1,
real, intent(in)  Sk,
real, intent(in)  Skp1,
real, intent(in)  Ptrue,
character(len=*), intent(in)  title 
)
private

Returns true if a test of fvlsq_slope() fails, and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]hkm1Left cell width
[in]hkCenter cell width
[in]hkp1Right cell width
[in]skm1Left cell average value
[in]skCenter cell average value
[in]skp1Right cell average value
[in]ptrueTrue answer
[in]titleTitle for messages

Definition at line 2642 of file MOM_neutral_diffusion.F90.

2642  logical, intent(in) :: verbose !< If true, write results to stdout
2643  real, intent(in) :: hkm1 !< Left cell width
2644  real, intent(in) :: hk !< Center cell width
2645  real, intent(in) :: hkp1 !< Right cell width
2646  real, intent(in) :: Skm1 !< Left cell average value
2647  real, intent(in) :: Sk !< Center cell average value
2648  real, intent(in) :: Skp1 !< Right cell average value
2649  real, intent(in) :: Ptrue !< True answer
2650  character(len=*), intent(in) :: title !< Title for messages
2651 
2652  ! Local variables
2653  integer :: stdunit
2654  real :: Pret
2655 
2656  pret = fvlsq_slope(hkm1, hk, hkp1, skm1, sk, skp1)
2657  test_fvlsq_slope = (pret /= ptrue)
2658 
2659  if (test_fvlsq_slope .or. verbose) then
2660  stdunit = stdout
2661  if (test_fvlsq_slope) stdunit = stderr ! In case of wrong results, write to error stream
2662  write(stdunit,'(a)') title
2663  if (test_fvlsq_slope) then
2664  write(stdunit,'(2(x,a,f20.16),x,a)') 'pRet=',pret,'pTrue=',ptrue,'WRONG!'
2665  else
2666  write(stdunit,'(2(x,a,f20.16))') 'pRet=',pret,'pTrue=',ptrue
2667  endif
2668  endif
2669 

◆ test_ifndp()

logical function mom_neutral_diffusion::test_ifndp ( logical, intent(in)  verbose,
real, intent(in)  rhoNeg,
real, intent(in)  Pneg,
real, intent(in)  rhoPos,
real, intent(in)  Ppos,
real, intent(in)  Ptrue,
character(len=*), intent(in)  title 
)
private

Returns true if a test of interpolate_for_nondim_position() fails, and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]rhonegLighter density [R ~> kg m-3]
[in]pnegInterface position of lighter density [nondim]
[in]rhoposHeavier density [R ~> kg m-3]
[in]pposInterface position of heavier density [nondim]
[in]ptrueTrue answer [nondim]
[in]titleTitle for messages

Definition at line 2674 of file MOM_neutral_diffusion.F90.

2674  logical, intent(in) :: verbose !< If true, write results to stdout
2675  real, intent(in) :: rhoNeg !< Lighter density [R ~> kg m-3]
2676  real, intent(in) :: Pneg !< Interface position of lighter density [nondim]
2677  real, intent(in) :: rhoPos !< Heavier density [R ~> kg m-3]
2678  real, intent(in) :: Ppos !< Interface position of heavier density [nondim]
2679  real, intent(in) :: Ptrue !< True answer [nondim]
2680  character(len=*), intent(in) :: title !< Title for messages
2681 
2682  ! Local variables
2683  integer :: stdunit
2684  real :: Pret
2685 
2686  pret = interpolate_for_nondim_position(rhoneg, pneg, rhopos, ppos)
2687  test_ifndp = (pret /= ptrue)
2688 
2689  if (test_ifndp .or. verbose) then
2690  stdunit = stdout
2691  if (test_ifndp) stdunit = stderr ! In case of wrong results, write to error stream
2692  write(stdunit,'(a)') title
2693  if (test_ifndp) then
2694  write(stdunit,'(4(x,a,f20.16),2(x,a,1pe22.15),x,a)') &
2695  'r1=',rhoneg,'p1=',pneg,'r2=',rhopos,'p2=',ppos,'pRet=',pret,'pTrue=',ptrue,'WRONG!'
2696  else
2697  write(stdunit,'(4(x,a,f20.16),2(x,a,1pe22.15))') &
2698  'r1=',rhoneg,'p1=',pneg,'r2=',rhopos,'p2=',ppos,'pRet=',pret,'pTrue=',ptrue
2699  endif
2700  endif
2701 

◆ test_nsp()

logical function mom_neutral_diffusion::test_nsp ( logical, intent(in)  verbose,
integer, intent(in)  ns,
integer, dimension(ns), intent(in)  KoL,
integer, dimension(ns), intent(in)  KoR,
real, dimension(ns), intent(in)  pL,
real, dimension(ns), intent(in)  pR,
real, dimension(ns-1), intent(in)  hEff,
integer, dimension(ns), intent(in)  KoL0,
integer, dimension(ns), intent(in)  KoR0,
real, dimension(ns), intent(in)  pL0,
real, dimension(ns), intent(in)  pR0,
real, dimension(ns-1), intent(in)  hEff0,
character(len=*), intent(in)  title 
)
private

Returns true if output of find_neutral_surface_positions() does not match correct values, and conditionally writes results to stream.

Parameters
[in]verboseIf true, write results to stdout
[in]nsNumber of surfaces
[in]kolIndex of first left interface above neutral surface
[in]korIndex of first right interface above neutral surface
[in]plFractional position of neutral surface within layer KoL of left column
[in]prFractional position of neutral surface within layer KoR of right column
[in]heffEffective thickness between two neutral surfaces [R L2 T-2 ~> Pa]
[in]kol0Correct value for KoL
[in]kor0Correct value for KoR
[in]pl0Correct value for pL
[in]pr0Correct value for pR
[in]heff0Correct value for hEff
[in]titleTitle for messages

Definition at line 2775 of file MOM_neutral_diffusion.F90.

2775  logical, intent(in) :: verbose !< If true, write results to stdout
2776  integer, intent(in) :: ns !< Number of surfaces
2777  integer, dimension(ns), intent(in) :: KoL !< Index of first left interface above neutral surface
2778  integer, dimension(ns), intent(in) :: KoR !< Index of first right interface above neutral surface
2779  real, dimension(ns), intent(in) :: pL !< Fractional position of neutral surface within layer KoL of left column
2780  real, dimension(ns), intent(in) :: pR !< Fractional position of neutral surface within layer KoR of right column
2781  real, dimension(ns-1), intent(in) :: hEff !< Effective thickness between two neutral surfaces [R L2 T-2 ~> Pa]
2782  integer, dimension(ns), intent(in) :: KoL0 !< Correct value for KoL
2783  integer, dimension(ns), intent(in) :: KoR0 !< Correct value for KoR
2784  real, dimension(ns), intent(in) :: pL0 !< Correct value for pL
2785  real, dimension(ns), intent(in) :: pR0 !< Correct value for pR
2786  real, dimension(ns-1), intent(in) :: hEff0 !< Correct value for hEff
2787  character(len=*), intent(in) :: title !< Title for messages
2788 
2789  ! Local variables
2790  integer :: k, stdunit
2791  logical :: this_row_failed
2792 
2793  test_nsp = .false.
2794  do k = 1,ns
2795  test_nsp = test_nsp .or. compare_nsp_row(kol(k), kor(k), pl(k), pr(k), kol0(k), kor0(k), pl0(k), pr0(k))
2796  if (k < ns) then
2797  if (heff(k) /= heff0(k)) test_nsp = .true.
2798  endif
2799  enddo
2800 
2801  if (test_nsp .or. verbose) then
2802  stdunit = stdout
2803  if (test_nsp) stdunit = stderr ! In case of wrong results, write to error stream
2804  write(stdunit,'(a)') title
2805  do k = 1,ns
2806  this_row_failed = compare_nsp_row(kol(k), kor(k), pl(k), pr(k), kol0(k), kor0(k), pl0(k), pr0(k))
2807  if (this_row_failed) then
2808  write(stdunit,10) k,kol(k),pl(k),kor(k),pr(k),' <-- WRONG!'
2809  write(stdunit,10) k,kol0(k),pl0(k),kor0(k),pr0(k),' <-- should be this'
2810  else
2811  write(stdunit,10) k,kol(k),pl(k),kor(k),pr(k)
2812  endif
2813  if (k < ns) then
2814  if (heff(k) /= heff0(k)) then
2815  write(stdunit,'(i3,8x,"layer hEff =",2(f20.16,a))') k,heff(k)," .neq. ",heff0(k),' <-- WRONG!'
2816  else
2817  write(stdunit,'(i3,8x,"layer hEff =",f20.16)') k,heff(k)
2818  endif
2819  endif
2820  enddo
2821  endif
2822  if (test_nsp) call mom_error(fatal,"test_nsp failed")
2823 
2824 10 format("ks=",i3," kL=",i3," pL=",f20.16," kR=",i3," pR=",f20.16,a)

◆ test_rnp()

logical function mom_neutral_diffusion::test_rnp ( real, intent(in)  expected_pos,
real, intent(in)  test_pos,
character(len=*), intent(in)  title 
)
private

Compares output position from refine_nondim_position with an expected value.

Parameters
[in]expected_posThe expected position
[in]test_posThe position returned by the code
[in]titleA label for this test

Definition at line 2847 of file MOM_neutral_diffusion.F90.

2847  real, intent(in) :: expected_pos !< The expected position
2848  real, intent(in) :: test_pos !< The position returned by the code
2849  character(len=*), intent(in) :: title !< A label for this test
2850  ! Local variables
2851  integer :: stdunit
2852 
2853  stdunit = stdout ! Output to standard error
2854  test_rnp = abs(expected_pos - test_pos) > 2*epsilon(test_pos)
2855  if (test_rnp) then
2856  write(stdunit,'(A, f20.16, " .neq. ", f20.16, " <-- WRONG")') title, expected_pos, test_pos
2857  else
2858  write(stdunit,'(A, f20.16, " == ", f20.16)') title, expected_pos, test_pos
2859  endif