diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 42e6514ab9..cccfa1c2e4 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -39,8 +39,10 @@ module MOM_PressureForce_FV !> Finite volume pressure gradient control structure type, public :: PressureForce_FV_CS ; private logical :: initialized = .false. !< True if this control structure has been initialized. - logical :: calculate_SAL !< If true, calculate self-attraction and loading. - logical :: tides !< If true, apply tidal momentum forcing. + logical :: calculate_SAL = .false. !< If true, calculate self-attraction and loading. + logical :: sal_use_bpa = .false. !< If true, use bottom pressure anomaly instead of SSH + !! to calculate SAL. + logical :: tides = .false. !< If true, apply tidal momentum forcing. real :: Rho0 !< The density used in the Boussinesq !! approximation [R ~> kg m-3]. real :: GFS_scale !< A scaling of the surface pressure gradients to @@ -80,10 +82,12 @@ module MOM_PressureForce_FV !! equation of state is 0 to account for the displacement of the sea !! surface including adjustments for atmospheric or sea-ice pressure. logical :: use_stanley_pgf !< If true, turn on Stanley parameterization in the PGF - integer :: tides_answer_date !< Recover old answers with tides in Boussinesq mode + logical :: bq_sal_tides = .false. !< If true, use an alternative method for SAL and tides + !! in Boussinesq mode + integer :: tides_answer_date = 99991231 !< Recover old answers with tides integer :: id_e_tide = -1 !< Diagnostic identifier - integer :: id_e_tide_eq = -1 !< Diagnostic identifier - integer :: id_e_tide_sal = -1 !< Diagnostic identifier + integer :: id_e_tidal_eq = -1 !< Diagnostic identifier + integer :: id_e_tidal_sal = -1 !< Diagnostic identifier integer :: id_e_sal = -1 !< Diagnostic identifier integer :: id_rho_pgf = -1 !< Diagnostic identifier integer :: id_rho_stanley_pgf = -1 !< Diagnostic identifier @@ -141,12 +145,16 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ ! the pressure anomaly at the top of the layer [R L4 T-4 ~> Pa m2 s-2]. real, dimension(SZI_(G),SZJ_(G)) :: & dp, & ! The (positive) change in pressure across a layer [R L2 T-2 ~> Pa]. - SSH, & ! The sea surface height anomaly, in depth units [Z ~> m]. + SSH, & ! Sea surfae height anomaly for self-attraction and loading. Used if + ! CALCULATE_SAL is True and SAL_USE_BPA is False [Z ~> m]. + pbot, & ! Total bottom pressure for self-attraction and loading. Used if + ! CALCULATE_SAL is True and SAL_USE_BPA is True [R L2 T-2 ~> Pa]. e_sal, & ! The bottom geopotential anomaly due to self-attraction and loading [Z ~> m]. - e_tide_eq, & ! The bottom geopotential anomaly due to tidal forces from astronomical sources [Z ~> m]. - e_tide_sal, & ! The bottom geopotential anomaly due to harmonic self-attraction and loading + e_tidal_eq, & ! The bottom geopotential anomaly due to tidal forces from astronomical sources [Z ~> m]. + e_tidal_sal, & ! The bottom geopotential anomaly due to harmonic self-attraction and loading ! specific to tides [Z ~> m]. - e_sal_tide, & ! The summation of self-attraction and loading and tidal forcing [Z ~> m]. + e_sal_and_tide, & ! The summation of self-attraction and loading and tidal forcing, used for recovering + ! old answers only [Z ~> m]. dM ! The barotropic adjustment to the Montgomery potential to ! account for a reduced gravity model [L2 T-2 ~> m2 s-2]. real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1) :: & @@ -399,15 +407,24 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ enddo ; enddo enddo - ! Calculate and add the self-attraction and loading geopotential anomaly. + ! Calculate and add self-attraction and loading (SAL) geopotential height anomaly to interface height. if (CS%calculate_SAL) then - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - SSH(i,j) = (za(i,j,1) - alpha_ref*p(i,j,1)) * I_gEarth - G%Z_ref & - - max(-G%bathyT(i,j)-G%Z_ref, 0.0) - enddo ; enddo - call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + if (CS%sal_use_bpa) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + pbot(i,j) = p(i,j,nz+1) + enddo ; enddo + call calc_SAL(pbot, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + else + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + SSH(i,j) = (za(i,j,1) - alpha_ref*p(i,j,1)) * I_gEarth - G%Z_ref & + - max(-G%bathyT(i,j)-G%Z_ref, 0.0) + enddo ; enddo + call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + endif + ! This gives new answers after the change of separating SAL from tidal forcing module. if ((CS%tides_answer_date>20230630) .or. (.not.GV%semi_Boussinesq) .or. (.not.CS%tides)) then !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 @@ -416,21 +433,21 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ endif endif - ! Calculate and add the tidal geopotential anomaly. + ! Calculate and add tidal geopotential height anomaly to interface height. if (CS%tides) then if ((CS%tides_answer_date>20230630) .or. (.not.GV%semi_Boussinesq)) then - call calc_tidal_forcing(CS%Time, e_tide_eq, e_tide_sal, G, US, CS%tides_CSp) + call calc_tidal_forcing(CS%Time, e_tidal_eq, e_tidal_sal, G, US, CS%tides_CSp) !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - za(i,j,1) = za(i,j,1) - GV%g_Earth * (e_tide_eq(i,j) + e_tide_sal(i,j)) + za(i,j,1) = za(i,j,1) - GV%g_Earth * (e_tidal_eq(i,j) + e_tidal_sal(i,j)) enddo ; enddo else ! This block recreates older answers with tides. if (.not.CS%calculate_SAL) e_sal(:,:) = 0.0 - call calc_tidal_forcing_legacy(CS%Time, e_sal, e_sal_tide, e_tide_eq, e_tide_sal, & + call calc_tidal_forcing_legacy(CS%Time, e_sal, e_sal_and_tide, e_tidal_eq, e_tidal_sal, & G, US, CS%tides_CSp) !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - za(i,j,1) = za(i,j,1) - GV%g_Earth * e_sal_tide(i,j) + za(i,j,1) = za(i,j,1) - GV%g_Earth * e_sal_and_tide(i,j) enddo ; enddo endif endif @@ -808,10 +825,15 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ ! To be consistent with old runs, tidal forcing diagnostic also includes total SAL. ! New diagnostics are given for each individual field. - if (CS%id_e_tide>0) call post_data(CS%id_e_tide, e_sal+e_tide_eq+e_tide_sal, CS%diag) + if (CS%id_e_tide>0) then + if (CS%tides_answer_date>20230630) then ; do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + e_sal_and_tide(i,j) = e_sal(i,j) + e_tidal_eq(i,j) + e_tidal_sal(i,j) + enddo ; enddo ; endif + call post_data(CS%id_e_tide, e_sal_and_tide, CS%diag) + endif if (CS%id_e_sal>0) call post_data(CS%id_e_sal, e_sal, CS%diag) - if (CS%id_e_tide_eq>0) call post_data(CS%id_e_tide_eq, e_tide_eq, CS%diag) - if (CS%id_e_tide_sal>0) call post_data(CS%id_e_tide_sal, e_tide_sal, CS%diag) + if (CS%id_e_tidal_eq>0) call post_data(CS%id_e_tidal_eq, e_tidal_eq, CS%diag) + if (CS%id_e_tidal_sal>0) call post_data(CS%id_e_tidal_sal, e_tidal_sal, CS%diag) if (CS%id_MassWt_u>0) call post_data(CS%id_MassWt_u, MassWt_u, CS%diag) if (CS%id_MassWt_v>0) call post_data(CS%id_MassWt_v, MassWt_v, CS%diag) @@ -846,14 +868,17 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm ! Local variables real, dimension(SZI_(G),SZJ_(G),SZK_(GV)+1) :: e ! Interface height in depth units [Z ~> m]. real, dimension(SZI_(G),SZJ_(G)) :: & - e_sal_tide, & ! The summation of self-attraction and loading and tidal forcing [Z ~> m]. + e_sal_and_tide, & ! The summation of self-attraction and loading and tidal forcing [Z ~> m]. e_sal, & ! The bottom geopotential anomaly due to self-attraction and loading [Z ~> m]. - e_tide_eq, & ! The bottom geopotential anomaly due to tidal forces from astronomical sources + e_tidal_eq, & ! The bottom geopotential anomaly due to tidal forces from astronomical sources ! [Z ~> m]. - e_tide_sal, & ! The bottom geopotential anomaly due to harmonic self-attraction and loading + e_tidal_sal, & ! The bottom geopotential anomaly due to harmonic self-attraction and loading ! specific to tides [Z ~> m]. Z_0p, & ! The height at which the pressure used in the equation of state is 0 [Z ~> m] - SSH, & ! The sea surface height anomaly, in depth units [Z ~> m]. + SSH, & ! Sea surfae height anomaly for self-attraction and loading. Used if + ! CALCULATE_SAL is True and SAL_USE_BPA is False [Z ~> m]. + pbot, & ! Total bottom pressure for self-attraction and loading. Used if + ! CALCULATE_SAL is True and SAL_USE_BPA is True [R L2 T-2 ~> Pa]. dM ! The barotropic adjustment to the Montgomery potential to ! account for a reduced gravity model [L2 T-2 ~> m2 s-2]. real, dimension(SZI_(G)) :: & @@ -950,7 +975,7 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm real :: h_neglect ! A thickness that is so small it is usually lost ! in roundoff and can be neglected [H ~> m]. real :: I_Rho0 ! The inverse of the Boussinesq reference density [R-1 ~> m3 kg-1]. - real :: G_Rho0 ! G_Earth / Rho0 in [L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1]. + real :: G_Rho0 ! G_Earth / Rho_0 in [L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1]. real :: I_g_rho ! The inverse of the density times the gravitational acceleration [Z T2 L-2 R-1 ~> m Pa-1] real :: rho_ref ! The reference density [R ~> kg m-3]. real :: dz_neglect ! A minimal thickness [Z ~> m], like e. @@ -1003,77 +1028,51 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm MassWt_u(:,:,:) = 0.0 ; MassWt_v(:,:,:) = 0.0 endif - if (CS%tides_answer_date>20230630) then - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - e(i,j,nz+1) = -G%bathyT(i,j) - enddo ; enddo + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + e(i,j,nz+1) = -G%bathyT(i,j) + enddo ; enddo - ! Calculate and add the self-attraction and loading geopotential anomaly. - if (CS%calculate_SAL) then - ! Determine the surface height anomaly for calculating self attraction - ! and loading. This should really be based on bottom pressure anomalies, - ! but that is not yet implemented, and the current form is correct for - ! barotropic tides. - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 - do i=Isq,Ieq+1 - SSH(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) - enddo - do k=1,nz ; do i=Isq,Ieq+1 - SSH(i,j) = SSH(i,j) + h(i,j,k)*GV%H_to_Z - enddo ; enddo + ! The following two if-blocks are used to recover old answers for self-attraction and loading + ! (SAL) and tides only. The old algorithm moves interface heights before density calculations, + ! and therefore is incorrect without SSH_IN_EOS_PRESSURE_FOR_PGF=True (added in August 2024). + ! See the code right after Pa calculation loop for the new algorithm. + + ! Calculate and add SAL geopotential anomaly to interface height (old answers) + if (CS%calculate_SAL .and. CS%tides_answer_date<=20250131) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 + do i=Isq,Ieq+1 + SSH(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) enddo - call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - e(i,j,nz+1) = e(i,j,nz+1) - e_sal(i,j) + do k=1,nz ; do i=Isq,Ieq+1 + SSH(i,j) = SSH(i,j) + h(i,j,k)*GV%H_to_Z enddo ; enddo - endif + enddo + call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) - ! Calculate and add the tidal geopotential anomaly. - if (CS%tides) then - call calc_tidal_forcing(CS%Time, e_tide_eq, e_tide_sal, G, US, CS%tides_CSp) + if (CS%tides_answer_date>20230630) then ! answers_date between [20230701, 20250131] !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - e(i,j,nz+1) = e(i,j,nz+1) - (e_tide_eq(i,j) + e_tide_sal(i,j)) - enddo ; enddo - endif - else ! Old answers - ! Calculate and add the self-attraction and loading geopotential anomaly. - if (CS%calculate_SAL) then - ! Determine the surface height anomaly for calculating self attraction - ! and loading. This should really be based on bottom pressure anomalies, - ! but that is not yet implemented, and the current form is correct for - ! barotropic tides. - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 - do i=Isq,Ieq+1 - SSH(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) - enddo - do k=1,nz ; do i=Isq,Ieq+1 - SSH(i,j) = SSH(i,j) + h(i,j,k)*GV%H_to_Z - enddo ; enddo - enddo - call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) - else - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - e_sal(i,j) = 0.0 + e(i,j,nz+1) = e(i,j,nz+1) - e_sal(i,j) enddo ; enddo endif + endif - ! Calculate and add the tidal geopotential anomaly. - if (CS%tides) then - call calc_tidal_forcing_legacy(CS%Time, e_sal, e_sal_tide, e_tide_eq, e_tide_sal, & - G, US, CS%tides_CSp) - !$OMP parallel do default(shared) + ! Calculate and add tidal geopotential anomaly to interface height (old answers) + if (CS%tides .and. CS%tides_answer_date<=20250131) then + if (CS%tides_answer_date>20230630) then ! answers_date between [20230701, 20250131] + call calc_tidal_forcing(CS%Time, e_tidal_eq, e_tidal_sal, G, US, CS%tides_CSp) + !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - e(i,j,nz+1) = -(G%bathyT(i,j) + e_sal_tide(i,j)) + e(i,j,nz+1) = e(i,j,nz+1) - (e_tidal_eq(i,j) + e_tidal_sal(i,j)) enddo ; enddo - else + else ! answers_date before 20230701 + if (.not.CS%calculate_SAL) e_sal(:,:) = 0.0 + call calc_tidal_forcing_legacy(CS%Time, e_sal, e_sal_and_tide, e_tidal_eq, e_tidal_sal, & + G, US, CS%tides_CSp) !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - e(i,j,nz+1) = -(G%bathyT(i,j) + e_sal(i,j)) + e(i,j,nz+1) = e(i,j,nz+1) - e_sal_and_tide(i,j) enddo ; enddo endif endif @@ -1223,6 +1222,42 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; enddo enddo + ! Calculate and add SAL geopotential anomaly to interface height (new answers) + if (CS%calculate_SAL .and. CS%tides_answer_date>20250131) then + if (CS%sal_use_bpa) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + pbot(i,j) = pa(i,j,nz+1) - GxRho * e(i,j,nz+1) + enddo ; enddo + call calc_SAL(pbot, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + else + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + SSH(i,j) = e(i,j,1) - max(-G%bathyT(i,j) - G%Z_ref, 0.0) ! Remove topography above sea level + enddo ; enddo + call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + endif + if (.not.CS%bq_sal_tides) then ; do K=1,nz+1 + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + e(i,j,K) = e(i,j,K) - e_sal(i,j) + pa(i,j,K) = pa(i,j,K) - GxRho * e_sal(i,j) + enddo ; enddo + enddo ; endif + endif + + ! Calculate and add tidal geopotential anomaly to interface height (new answers) + if (CS%tides .and. CS%tides_answer_date>20250131) then + call calc_tidal_forcing(CS%Time, e_tidal_eq, e_tidal_sal, G, US, CS%tides_CSp) + if (.not.CS%bq_sal_tides) then ; do K=1,nz+1 + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + e(i,j,K) = e(i,j,K) - (e_tidal_eq(i,j) + e_tidal_sal(i,j)) + pa(i,j,K) = pa(i,j,K) - GxRho * (e_tidal_eq(i,j) + e_tidal_sal(i,j)) + enddo ; enddo + enddo ; endif + endif + if (CS%correction_intxpa .or. CS%reset_intxpa_integral) then ! Determine surface temperature and salinity for use in the pressure gradient corrections if (use_ALE .and. (CS%Recon_Scheme > 0)) then @@ -1599,6 +1634,34 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm ((h(i,j,k) + h(i,j+1,k)) + h_neglect)) enddo ; enddo ; enddo + ! Calculate SAL geopotential anomaly and add its gradient to pressure gradient force + if (CS%calculate_SAL .and. CS%tides_answer_date>20230630 .and. CS%bq_sal_tides) then + !$OMP parallel do default(shared) + do k=1,nz + do j=js,je ; do I=Isq,Ieq + PFu(I,j,k) = PFu(I,j,k) + (e_sal(i+1,j) - e_sal(i,j)) * GV%g_Earth * G%IdxCu(I,j) + enddo ; enddo + do J=Jsq,Jeq ; do i=is,ie + PFv(i,J,k) = PFv(i,J,k) + (e_sal(i,j+1) - e_sal(i,j)) * GV%g_Earth * G%IdyCv(i,J) + enddo ; enddo + enddo + endif + + ! Calculate tidal geopotential anomaly and add its gradient to pressure gradient force + if (CS%tides .and. CS%tides_answer_date>20230630 .and. CS%bq_sal_tides) then + !$OMP parallel do default(shared) + do k=1,nz + do j=js,je ; do I=Isq,Ieq + PFu(I,j,k) = PFu(I,j,k) + ((e_tidal_eq(i+1,j) + e_tidal_sal(i+1,j)) & + - (e_tidal_eq(i,j) + e_tidal_sal(i,j))) * GV%g_Earth * G%IdxCu(I,j) + enddo ; enddo + do J=Jsq,Jeq ; do i=is,ie + PFv(i,J,k) = PFv(i,J,k) + ((e_tidal_eq(i,j+1) + e_tidal_sal(i,j+1)) & + - (e_tidal_eq(i,j) + e_tidal_sal(i,j))) * GV%g_Earth * G%IdyCv(i,J) + enddo ; enddo + enddo + endif + if (CS%GFS_scale < 1.0) then ! Adjust the Montgomery potential to make this a reduced gravity model. if (use_EOS) then @@ -1640,36 +1703,29 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm if (present(eta)) then ! eta is the sea surface height relative to a time-invariant geoid, for comparison with ! what is used for eta in btstep. See how e was calculated about 200 lines above. - if (CS%tides_answer_date>20230630) then - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta(i,j) = e(i,j,1)*GV%Z_to_H - enddo ; enddo - if (CS%tides) then - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta(i,j) = eta(i,j) + (e_tide_eq(i,j)+e_tide_sal(i,j))*GV%Z_to_H - enddo ; enddo - endif - if (CS%calculate_SAL) then - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta(i,j) = eta(i,j) + e_sal(i,j)*GV%Z_to_H - enddo ; enddo - endif - else ! Old answers - if (CS%tides) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + eta(i,j) = e(i,j,1)*GV%Z_to_H + enddo ; enddo + if (CS%tides .and. (.not.CS%bq_sal_tides)) then + if (CS%tides_answer_date>20230630) then !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta(i,j) = e(i,j,1)*GV%Z_to_H + (e_sal_tide(i,j))*GV%Z_to_H + eta(i,j) = eta(i,j) + (e_tidal_eq(i,j)+e_tidal_sal(i,j))*GV%Z_to_H enddo ; enddo else !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta(i,j) = (e(i,j,1) + e_sal(i,j))*GV%Z_to_H + eta(i,j) = eta(i,j) + e_sal_and_tide(i,j)*GV%Z_to_H enddo ; enddo endif endif + if (CS%calculate_SAL .and. (CS%tides_answer_date>20230630) .and. (.not.CS%bq_sal_tides)) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + eta(i,j) = eta(i,j) + e_sal(i,j)*GV%Z_to_H + enddo ; enddo + endif endif if (CS%use_stanley_pgf) then @@ -1711,10 +1767,15 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm ! To be consistent with old runs, tidal forcing diagnostic also includes total SAL. ! New diagnostics are given for each individual field. - if (CS%id_e_tide>0) call post_data(CS%id_e_tide, e_sal_tide, CS%diag) + if (CS%id_e_tide>0) then + if (CS%tides_answer_date>20230630) then ; do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + e_sal_and_tide(i,j) = e_sal(i,j) + e_tidal_eq(i,j) + e_tidal_sal(i,j) + enddo ; enddo ; endif + call post_data(CS%id_e_tide, e_sal_and_tide, CS%diag) + endif if (CS%id_e_sal>0) call post_data(CS%id_e_sal, e_sal, CS%diag) - if (CS%id_e_tide_eq>0) call post_data(CS%id_e_tide_eq, e_tide_eq, CS%diag) - if (CS%id_e_tide_sal>0) call post_data(CS%id_e_tide_sal, e_tide_sal, CS%diag) + if (CS%id_e_tidal_eq>0) call post_data(CS%id_e_tidal_eq, e_tidal_eq, CS%diag) + if (CS%id_e_tidal_sal>0) call post_data(CS%id_e_tidal_sal, e_tidal_sal, CS%diag) if (CS%id_MassWt_u>0) call post_data(CS%id_MassWt_u, MassWt_u, CS%diag) if (CS%id_MassWt_v>0) call post_data(CS%id_MassWt_v, MassWt_v, CS%diag) @@ -1769,19 +1830,29 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, SAL_CSp, units="kg m-3", default=GV%Rho0*US%R_to_kg_m3, scale=US%kg_m3_to_R) call get_param(param_file, mdl, "TIDES", CS%tides, & "If true, apply tidal momentum forcing.", default=.false.) - if (CS%tides) then - call get_param(param_file, mdl, "DEFAULT_ANSWER_DATE", default_answer_date, & - "This sets the default value for the various _ANSWER_DATE parameters.", & - default=99991231) - call get_param(param_file, mdl, "TIDES_ANSWER_DATE", CS%tides_answer_date, & - "The vintage of self-attraction and loading (SAL) and tidal forcing calculations in "//& - "Boussinesq mode. Values below 20230701 recover the old answers in which the SAL is "//& - "part of the tidal forcing calculation. The change is due to a reordered summation "//& - "and the difference is only at bit level.", default=20230630) - endif + call get_param(param_file, '', "DEFAULT_ANSWER_DATE", default_answer_date, default=99991231) + if (CS%tides) & + call get_param(param_file, mdl, "TIDES_ANSWER_DATE", CS%tides_answer_date, "The vintage of "//& + "self-attraction and loading (SAL) and tidal forcing calculations. Setting "//& + "dates before 20230701 recovers old answers (Boussinesq and non-Boussinesq "//& + "modes) when SAL is part of the tidal forcing calculation. The answer "//& + "difference is only at bit level and due to a reordered summation. Setting "//& + "dates before 20250201 recovers answers (Boussinesq mode) that interface "//& + "heights are modified before pressure force integrals are calculated.", & + default=20230630, do_not_log=(.not.CS%tides)) call get_param(param_file, mdl, "CALCULATE_SAL", CS%calculate_SAL, & "If true, calculate self-attraction and loading.", default=CS%tides) - + if (CS%calculate_SAL) & + call get_param(param_file, '', "SAL_USE_BPA", CS%sal_use_bpa, default=.false., & + do_not_log=.true.) + if ((CS%tides .or. CS%calculate_SAL) .and. GV%Boussinesq) & + call get_param(param_file, mdl, "BOUSSINESQ_SAL_TIDES", CS%bq_sal_tides, "If true, "//& + "in Boussinesq mode, use an alternative method to include self-attraction "//& + "and loading (SAL) and tidal forcings in pressure gradient, in which their "//& + "gradients are calculated separately, instead of adding geopotential "//& + "anomalies as corrections to the interface height. This alternative method "//& + "elimates a baroclinic component of the SAL and tidal forcings.", & + default=.false.) call get_param(param_file, "MOM", "ENABLE_THERMODYNAMICS", use_temperature, & "If true, Temperature and salinity are used as state variables.", & default=.true., do_not_log=.true.) @@ -1796,6 +1867,9 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, SAL_CSp, "pressure used in the equation of state calculations for the Boussinesq pressure "//& "gradient forces, including adjustments for atmospheric or sea-ice pressure.", & default=.false., do_not_log=.not.GV%Boussinesq) + if (CS%tides .and. CS%tides_answer_date<=20250131 .and. CS%use_SSH_in_Z0p) & + call MOM_error(FATAL, trim(mdl) // ", PressureForce_FV_init: SSH_IN_EOS_PRESSURE_FOR_PGF "//& + "needs to be FALSE to recover tide answers before 20250131.") call get_param(param_file, "MOM", "USE_REGRIDDING", use_ALE, & "If True, use the ALE algorithm (regridding/remapping). "//& @@ -1880,9 +1954,9 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, SAL_CSp, if (CS%tides) then CS%id_e_tide = register_diag_field('ocean_model', 'e_tidal', diag%axesT1, Time, & 'Tidal Forcing Astronomical and SAL Height Anomaly', 'meter', conversion=US%Z_to_m) - CS%id_e_tide_eq = register_diag_field('ocean_model', 'e_tide_eq', diag%axesT1, Time, & + CS%id_e_tidal_eq = register_diag_field('ocean_model', 'e_tide_eq', diag%axesT1, Time, & 'Equilibrium tides height anomaly', 'meter', conversion=US%Z_to_m) - CS%id_e_tide_sal = register_diag_field('ocean_model', 'e_tide_sal', diag%axesT1, Time, & + CS%id_e_tidal_sal = register_diag_field('ocean_model', 'e_tide_sal', diag%axesT1, Time, & 'Read-in tidal self-attraction and loading height anomaly', 'meter', conversion=US%Z_to_m) endif diff --git a/src/core/MOM_dynamics_split_RK2.F90 b/src/core/MOM_dynamics_split_RK2.F90 index f602b65240..2978bd46b9 100644 --- a/src/core/MOM_dynamics_split_RK2.F90 +++ b/src/core/MOM_dynamics_split_RK2.F90 @@ -1506,7 +1506,7 @@ subroutine initialize_dyn_split_RK2(u, v, h, tv, uh, vh, eta, Time, G, GV, US, p call continuity_init(Time, G, GV, US, param_file, diag, CS%continuity_CSp) cont_stencil = continuity_stencil(CS%continuity_CSp) call CoriolisAdv_init(Time, G, GV, US, param_file, diag, CS%ADp, CS%CoriolisAdv) - if (CS%calculate_SAL) call SAL_init(G, US, param_file, CS%SAL_CSp) + if (CS%calculate_SAL) call SAL_init(G, GV, US, param_file, CS%SAL_CSp) if (CS%use_tides) then call tidal_forcing_init(Time, G, US, param_file, CS%tides_CSp, CS%HA_CSp) HA_CSp => CS%HA_CSp diff --git a/src/core/MOM_dynamics_split_RK2b.F90 b/src/core/MOM_dynamics_split_RK2b.F90 index 87e46795b5..31ee923aae 100644 --- a/src/core/MOM_dynamics_split_RK2b.F90 +++ b/src/core/MOM_dynamics_split_RK2b.F90 @@ -1419,7 +1419,7 @@ subroutine initialize_dyn_split_RK2b(u, v, h, tv, uh, vh, eta, Time, G, GV, US, call continuity_init(Time, G, GV, US, param_file, diag, CS%continuity_CSp) cont_stencil = continuity_stencil(CS%continuity_CSp) call CoriolisAdv_init(Time, G, GV, US, param_file, diag, CS%ADp, CS%CoriolisAdv) - if (CS%calculate_SAL) call SAL_init(G, US, param_file, CS%SAL_CSp) + if (CS%calculate_SAL) call SAL_init(G, GV, US, param_file, CS%SAL_CSp) if (CS%use_tides) then call tidal_forcing_init(Time, G, US, param_file, CS%tides_CSp, CS%HA_CSp) HA_CSp => CS%HA_CSp diff --git a/src/core/MOM_dynamics_unsplit.F90 b/src/core/MOM_dynamics_unsplit.F90 index 72c7dbe6cd..c4b7c89edc 100644 --- a/src/core/MOM_dynamics_unsplit.F90 +++ b/src/core/MOM_dynamics_unsplit.F90 @@ -707,7 +707,7 @@ subroutine initialize_dyn_unsplit(u, v, h, Time, G, GV, US, param_file, diag, CS call continuity_init(Time, G, GV, US, param_file, diag, CS%continuity_CSp) cont_stencil = continuity_stencil(CS%continuity_CSp) call CoriolisAdv_init(Time, G, GV, US, param_file, diag, CS%ADp, CS%CoriolisAdv) - if (CS%calculate_SAL) call SAL_init(G, US, param_file, CS%SAL_CSp) + if (CS%calculate_SAL) call SAL_init(G, GV, US, param_file, CS%SAL_CSp) if (CS%use_tides) call tidal_forcing_init(Time, G, US, param_file, CS%tides_CSp) call PressureForce_init(Time, G, GV, US, param_file, diag, CS%PressureForce_CSp, & CS%SAL_CSp, CS%tides_CSp) diff --git a/src/core/MOM_dynamics_unsplit_RK2.F90 b/src/core/MOM_dynamics_unsplit_RK2.F90 index cc37f1c2bc..7ca2c700f7 100644 --- a/src/core/MOM_dynamics_unsplit_RK2.F90 +++ b/src/core/MOM_dynamics_unsplit_RK2.F90 @@ -671,7 +671,7 @@ subroutine initialize_dyn_unsplit_RK2(u, v, h, Time, G, GV, US, param_file, diag call continuity_init(Time, G, GV, US, param_file, diag, CS%continuity_CSp) cont_stencil = continuity_stencil(CS%continuity_CSp) call CoriolisAdv_init(Time, G, GV, US, param_file, diag, CS%ADp, CS%CoriolisAdv) - if (CS%calculate_SAL) call SAL_init(G, US, param_file, CS%SAL_CSp) + if (CS%calculate_SAL) call SAL_init(G, GV, US, param_file, CS%SAL_CSp) if (CS%use_tides) call tidal_forcing_init(Time, G, US, param_file, CS%tides_CSp) call PressureForce_init(Time, G, GV, US, param_file, diag, CS%PressureForce_CSp, & CS%SAL_CSp, CS%tides_CSp) diff --git a/src/core/MOM_open_boundary.F90 b/src/core/MOM_open_boundary.F90 index c15b6bd54b..fb833189ec 100644 --- a/src/core/MOM_open_boundary.F90 +++ b/src/core/MOM_open_boundary.F90 @@ -5470,7 +5470,7 @@ subroutine update_segment_tracer_reservoirs(G, GV, uhr, vhr, h, OBC, dt, Reg) endif I_scale = 1.0 ; if (segment%tr_Reg%Tr(m)%scale /= 0.0) I_scale = 1.0 / segment%tr_Reg%Tr(m)%scale if (allocated(segment%tr_Reg%Tr(m)%tres)) then ; do k=1,nz - ! Calculate weights. Both a and u_L are nodim. Adding them together has no meaning. + ! Calculate weights. Both a and u_L are nondim. Adding them together has no meaning. ! However, since they cannot be both non-zero, adding them works like a switch. ! When InvLscale_out is 0 and outflow, only interior data is applied to reservoirs ! When InvLscale_in is 0 and inflow, only nudged data is applied to reservoirs diff --git a/src/parameterizations/lateral/MOM_self_attr_load.F90 b/src/parameterizations/lateral/MOM_self_attr_load.F90 index f72b6f513e..9aa325cfb8 100644 --- a/src/parameterizations/lateral/MOM_self_attr_load.F90 +++ b/src/parameterizations/lateral/MOM_self_attr_load.F90 @@ -1,16 +1,18 @@ module MOM_self_attr_load -use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end, CLOCK_MODULE -use MOM_domains, only : pass_var -use MOM_error_handler, only : MOM_error, FATAL, WARNING -use MOM_file_parser, only : read_param, get_param, log_version, param_file_type +use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end, CLOCK_MODULE +use MOM_domains, only : pass_var +use MOM_error_handler, only : MOM_error, FATAL, WARNING +use MOM_file_parser, only : get_param, log_param, log_version, param_file_type +use MOM_grid, only : ocean_grid_type +use MOM_io, only : slasher, MOM_read_data +use MOM_load_love_numbers, only : Love_Data use MOM_obsolete_params, only : obsolete_logical, obsolete_int -use MOM_grid, only : ocean_grid_type -use MOM_unit_scaling, only : unit_scale_type use MOM_spherical_harmonics, only : spherical_harmonics_init, spherical_harmonics_end use MOM_spherical_harmonics, only : spherical_harmonics_forward, spherical_harmonics_inverse use MOM_spherical_harmonics, only : sht_CS, order2index, calc_lmax -use MOM_load_love_numbers, only : Love_Data +use MOM_unit_scaling, only : unit_scale_type +use MOM_verticalGrid, only : verticalGrid_type implicit none ; private @@ -27,12 +29,21 @@ module MOM_self_attr_load logical :: use_tidal_sal_prev = .false. !< If true, read the tidal SAL from the previous iteration of the tides to !! facilitate convergence. - real :: sal_scalar_value !< The constant of proportionality between sea surface height - !! (really it should be bottom pressure) anomalies and bottom - !! geopotential anomalies [nondim]. - type(sht_CS), allocatable :: sht !< Spherical harmonic transforms (SHT) control structure - integer :: sal_sht_Nd !< Maximum degree for SHT [nodim] - real, allocatable :: Love_Scaling(:) !< Love number for each SHT mode [nodim] + logical :: use_bpa = .false. + !< If true, use bottom pressure anomaly instead of SSH to calculate SAL. + real :: eta_prop + !< The partial derivative of eta_sal with the local value of eta [nondim]. + real :: linear_scaling + !< Dimensional coefficients for scalar SAL [nondim or Z T2 L-2 R-1 ~> m Pa-1] + type(sht_CS), allocatable :: sht + !< Spherical harmonic transforms (SHT) control structure + integer :: sal_sht_Nd + !< Maximum degree for spherical harmonic transforms [nondim] + real, allocatable :: ebot_ref(:,:) + !< Reference bottom pressure scaled by Rho_0 and G_Earth[Z ~> m] + real, allocatable :: Love_scaling(:) + !< Dimensional coefficients for harmonic SAL, which are functions of Love numbers + !! [nondim] or [Z T2 L-2 R-1 ~> m Pa-1], depending on the value of use_ppa. real, allocatable :: Snm_Re(:), & !< Real SHT coefficient for SHT SAL [Z ~> m] Snm_Im(:) !< Imaginary SHT coefficient for SHT SAL [Z ~> m] end type SAL_CS @@ -41,47 +52,54 @@ module MOM_self_attr_load contains -!> This subroutine calculates seawater self-attraction and loading based on sea surface height. This should -!! be changed into bottom pressure anomaly in the future. Note that the SAL calculation applies to all motions -!! across the spectrum. Tidal-specific methods that assume periodicity, i.e. iterative and read-in SAL, are -!! stored in MOM_tidal_forcing module. +!> This subroutine calculates seawater self-attraction and loading based on either sea surface height (SSH) or bottom +!! pressure anomaly. Note that the SAL calculation applies to all motions across the spectrum. Tidal-specific methods +!! that assume periodicity, i.e. iterative and read-in SAL, are stored in MOM_tidal_forcing module. +!! The input field can be either SSH [Z ~> m] or total bottom pressure [R L2 T-2 ~> Pa]. If total bottom pressure is +!! used, bottom pressure anomaly is first calculated by subtracting a reference bottom pressure from an input file. +!! The output field is expressed as geopotential height anomaly, and therefore has the unit of [Z ~> m]. subroutine calc_SAL(eta, eta_sal, G, CS, tmp_scale) - type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure. + type(ocean_grid_type), intent(in) :: G !< The ocean's grid structure. real, dimension(SZI_(G),SZJ_(G)), intent(in) :: eta !< The sea surface height anomaly from - !! a time-mean geoid [Z ~> m]. - real, dimension(SZI_(G),SZJ_(G)), intent(out) :: eta_sal !< The sea surface height anomaly from - !! self-attraction and loading [Z ~> m]. + !! a time-mean geoid or total bottom pressure [Z ~> m] or [R L2 T-2 ~> Pa]. + real, dimension(SZI_(G),SZJ_(G)), intent(out) :: eta_sal !< The geopotential height anomaly from + !! self-attraction and loading [Z ~> m]. type(SAL_CS), intent(inout) :: CS !< The control structure returned by a previous call to SAL_init. - real, optional, intent(in) :: tmp_scale !< A rescaling factor to temporarily convert eta - !! to MKS units in reproducing sumes [m Z-1 ~> 1] + real, optional, intent(in) :: tmp_scale !< A rescaling factor to temporarily convert eta + !! to MKS units in reproducing sumes [m Z-1 ~> 1] ! Local variables + real, dimension(SZI_(G),SZJ_(G)) :: bpa ! SSH or bottom pressure anomaly [Z ~> m] or [R L2 T-2 ~> Pa] integer :: n, m, l integer :: Isq, Ieq, Jsq, Jeq integer :: i, j - real :: eta_prop ! The scalar constant of proportionality between eta and eta_sal [nondim] call cpu_clock_begin(id_clock_SAL) Isq = G%IscB ; Ieq = G%IecB ; Jsq = G%JscB ; Jeq = G%JecB + if (CS%use_bpa) then ; do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + bpa(i,j) = eta(i,j) - CS%ebot_ref(i,j) + enddo ; enddo ; else ; do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + bpa(i,j) = eta(i,j) + enddo ; enddo ; endif + ! use the scalar approximation and/or iterative tidal SAL if (CS%use_sal_scalar .or. CS%use_tidal_sal_prev) then - call scalar_SAL_sensitivity(CS, eta_prop) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta_sal(i,j) = eta_prop*eta(i,j) + eta_sal(i,j) = CS%linear_scaling * bpa(i,j) enddo ; enddo ! use the spherical harmonics method elseif (CS%use_sal_sht) then - call spherical_harmonics_forward(G, CS%sht, eta, CS%Snm_Re, CS%Snm_Im, CS%sal_sht_Nd, tmp_scale=tmp_scale) + call spherical_harmonics_forward(G, CS%sht, bpa, CS%Snm_Re, CS%Snm_Im, CS%sal_sht_Nd, tmp_scale=tmp_scale) ! Multiply scaling factors to each mode do m = 0,CS%sal_sht_Nd l = order2index(m, CS%sal_sht_Nd) do n = m,CS%sal_sht_Nd - CS%Snm_Re(l+n-m) = CS%Snm_Re(l+n-m) * CS%Love_Scaling(l+n-m) - CS%Snm_Im(l+n-m) = CS%Snm_Im(l+n-m) * CS%Love_Scaling(l+n-m) + CS%Snm_Re(l+n-m) = CS%Snm_Re(l+n-m) * CS%Love_scaling(l+n-m) + CS%Snm_Im(l+n-m) = CS%Snm_Im(l+n-m) * CS%Love_scaling(l+n-m) enddo enddo @@ -98,38 +116,36 @@ subroutine calc_SAL(eta, eta_sal, G, CS, tmp_scale) call cpu_clock_end(id_clock_SAL) end subroutine calc_SAL -!> This subroutine calculates the partial derivative of the local geopotential height with the input -!! sea surface height due to the scalar approximation of self-attraction and loading. +!> This subroutine returns eta_prop member of SAL_CS type, which is the non-dimensional partial +!! derivative of the local geopotential height with the input sea surface height due to the scalar +!! approximation of self-attraction and loading. subroutine scalar_SAL_sensitivity(CS, deta_sal_deta) type(SAL_CS), intent(in) :: CS !< The control structure returned by a previous call to SAL_init. real, intent(out) :: deta_sal_deta !< The partial derivative of eta_sal with !! the local value of eta [nondim]. - - if (CS%use_sal_scalar .and. CS%use_tidal_sal_prev) then - deta_sal_deta = 2.0*CS%sal_scalar_value - elseif (CS%use_sal_scalar .or. CS%use_tidal_sal_prev) then - deta_sal_deta = CS%sal_scalar_value - else - deta_sal_deta = 0.0 - endif + deta_sal_deta = CS%eta_prop end subroutine scalar_SAL_sensitivity !> This subroutine calculates coefficients of the spherical harmonic modes for self-attraction and loading. !! The algorithm is based on the SAL implementation in MPAS-ocean, which was modified by Kristin Barton from !! routine written by K. Quinn (March 2010) and modified by M. Schindelegger (May 2017). -subroutine calc_love_scaling(nlm, rhoW, rhoE, Love_Scaling) - integer, intent(in) :: nlm !< Maximum spherical harmonics degree [nondim] - real, intent(in) :: rhoW !< The average density of sea water [R ~> kg m-3] - real, intent(in) :: rhoE !< The average density of Earth [R ~> kg m-3] - real, dimension(:), intent(out) :: Love_Scaling !< Scaling factors for inverse SHT [nondim] +subroutine calc_love_scaling(rhoW, rhoE, grav, CS) + real, intent(in) :: rhoW !< The average density of sea water [R ~> kg m-3] + real, intent(in) :: rhoE !< The average density of Earth [R ~> kg m-3] + real, intent(in) :: grav !< The gravitational acceleration [L2 Z-1 T-2 ~> m s-2] + type(SAL_CS), intent(inout) :: CS !< The control structure returned by a previous call to SAL_init. ! Local variables + real :: coef_rhoE ! A scaling coefficient of solid Earth density. coef_rhoE = rhoW / rhoE with USE_BPA=False + ! and coef_rhoE = 1.0 / (rhoE * grav) with USE_BPA=True. [nondim] or [Z T2 L-2 R-1 ~> m Pa-1] real, dimension(:), allocatable :: HDat, LDat, KDat ! Love numbers converted in CF reference frames [nondim] real :: H1, L1, K1 ! Temporary variables to store degree 1 Love numbers [nondim] - integer :: n_tot ! Size of the stored Love numbers + integer :: n_tot ! Size of the stored Love numbers [nondim] + integer :: nlm ! Maximum spherical harmonics degree [nondim] integer :: n, m, l n_tot = size(Love_Data, dim=2) + nlm = CS%sal_sht_Nd if (nlm+1 > n_tot) call MOM_error(FATAL, "MOM_tidal_forcing " // & "calc_love_scaling: maximum spherical harmonics degree is larger than " // & @@ -146,36 +162,47 @@ subroutine calc_love_scaling(nlm, rhoW, rhoE, Love_Scaling) KDat(2) = (-1.0 / 3.0) * H1 - (2.0 / 3.0) * L1 - 1.0 endif + if (CS%use_bpa) then + coef_rhoE = 1.0 / (rhoE * grav) ! [Z T2 L-2 R-1 ~> m Pa-1] + else + coef_rhoE = rhoW / rhoE ! [nondim] + endif + do m=0,nlm ; do n=m,nlm - l = order2index(m,nlm) - Love_Scaling(l+n-m) = (3.0 / real(2*n+1)) * (rhoW / rhoE) * (1.0 + KDat(n+1) - HDat(n+1)) + l = order2index(m, nlm) + ! Love_scaling has the same as coef_rhoE. + CS%Love_scaling(l+n-m) = (3.0 / real(2*n+1)) * coef_rhoE * (1.0 + KDat(n+1) - HDat(n+1)) enddo ; enddo end subroutine calc_love_scaling !> This subroutine initializes the self-attraction and loading control structure. -subroutine SAL_init(G, US, param_file, CS) - type(ocean_grid_type), intent(inout) :: G !< The ocean's grid structure. - type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type - type(param_file_type), intent(in) :: param_file !< A structure to parse for run-time parameters. +subroutine SAL_init(G, GV, US, param_file, CS) + type(ocean_grid_type), intent(inout) :: G !< The ocean's grid structure. + type(verticalGrid_type), intent(in) :: GV !< Vertical grid structure + type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type + type(param_file_type), intent(in) :: param_file !< A structure to parse for run-time parameters. type(SAL_CS), intent(inout) :: CS !< Self-attraction and loading control structure ! Local variables # include "version_variable.h" character(len=40) :: mdl = "MOM_self_attr_load" ! This module's name. integer :: lmax ! Total modes of the real spherical harmonics [nondim] - real :: rhoW ! The average density of sea water [R ~> kg m-3]. real :: rhoE ! The average density of Earth [R ~> kg m-3]. + character(len=200) :: filename, ebot_ref_file, inputdir ! Strings for file/path + character(len=200) :: ebot_ref_varname ! Variable name in file + logical :: calculate_sal=.false. + logical :: tides=.false., use_tidal_sal_file=.false., bq_sal_tides_bug=.false. + integer :: tides_answer_date=99991231 ! Recover old answers with tides + real :: sal_scalar_value, tide_sal_scalar_value ! Scaling SAL factors [nondim] + integer :: isd, ied, jsd, jed - logical :: calculate_sal - logical :: tides, use_tidal_sal_file - real :: tide_sal_scalar_value ! Scaling SAL factor [nondim] + isd = G%isd ; ied = G%ied ; jsd = G%jsd ; jed = G%jed ! Read all relevant parameters and write them to the model log. call log_version(param_file, mdl, version, "") call get_param(param_file, '', "TIDES", tides, default=.false., do_not_log=.True.) - call get_param(param_file, mdl, "CALCULATE_SAL", calculate_sal, "If true, calculate "//& - " self-attraction and loading.", default=tides, do_not_log=.True.) + call get_param(param_file, '', "CALCULATE_SAL", calculate_sal, default=tides, do_not_log=.True.) if (.not. calculate_sal) return if (tides) then @@ -183,17 +210,47 @@ subroutine SAL_init(G, US, param_file, CS) default=.false., do_not_log=.True.) call get_param(param_file, '', "TIDAL_SAL_FROM_FILE", use_tidal_sal_file, & default=.false., do_not_log=.True.) + call get_param(param_file, '', "TIDES_ANSWER_DATE", tides_answer_date, & + default=20230630, do_not_log=.True.) endif + call get_param(param_file, mdl, "SAL_USE_BPA", CS%use_bpa, & + "If true, use bottom pressure anomaly to calculate self-attraction and "// & + "loading (SAL). Otherwise sea surface height anomaly is used, which is "// & + "only correct for homogenous flow.", default=.False.) + if (CS%use_bpa) then + call get_param(param_file, '', "INPUTDIR", inputdir, default=".", do_not_log=.True.) + inputdir = slasher(inputdir) + call get_param(param_file, mdl, "REF_BOT_PRES_FILE", ebot_ref_file, & + "Reference bottom pressure file used by self-attraction and loading (SAL).", & + default="pbot.nc") + call get_param(param_file, mdl, "REF_BOT_PRES_VARNAME", ebot_ref_varname, & + "The name of the variable in REF_BOT_PRES_FILE with reference bottom "//& + "pressure. The variable should have the unit of Pa.", & + default="pbot") + filename = trim(inputdir)//trim(ebot_ref_file) + call log_param(param_file, mdl, "INPUTDIR/REF_BOT_PRES_FILE", filename) + + allocate(CS%ebot_ref(isd:ied, jsd:jed), source=0.0) + call MOM_read_data(filename, trim(ebot_ref_varname), CS%ebot_ref, G%Domain,& + scale=US%Pa_to_RL2_T2) + call pass_var(CS%ebot_ref, G%Domain) + endif + if (tides_answer_date<=20250131 .and. CS%use_bpa) & + call MOM_error(FATAL, trim(mdl) // ", SAL_init: SAL_USE_BPA needs to be false to recover "//& + "tide answers before 20250131.") call get_param(param_file, mdl, "SAL_SCALAR_APPROX", CS%use_sal_scalar, & "If true, use the scalar approximation to calculate self-attraction and "//& "loading.", default=tides .and. (.not. use_tidal_sal_file)) + if (CS%use_sal_scalar .and. CS%use_bpa) & + call MOM_error(WARNING, trim(mdl) // ", SAL_init: Using bottom pressure anomaly for scalar "//& + "approximation SAL is unsubstantiated.") call get_param(param_file, '', "TIDE_SAL_SCALAR_VALUE", tide_sal_scalar_value, & units="m m-1", default=0.0, do_not_log=.True.) if (tide_sal_scalar_value/=0.0) & call MOM_error(WARNING, "TIDE_SAL_SCALAR_VALUE is a deprecated parameter. "//& "Use SAL_SCALAR_VALUE instead." ) - call get_param(param_file, mdl, "SAL_SCALAR_VALUE", CS%sal_scalar_value, & + call get_param(param_file, mdl, "SAL_SCALAR_VALUE", sal_scalar_value, & "The constant of proportionality between sea surface "//& "height (really it should be bottom pressure) anomalies "//& "and bottom geopotential anomalies. This is only used if "//& @@ -207,20 +264,35 @@ subroutine SAL_init(G, US, param_file, CS) "The maximum degree of the spherical harmonics transformation used for "// & "calculating the self-attraction and loading term.", & default=0, do_not_log=(.not. CS%use_sal_sht)) - call get_param(param_file, '', "RHO_0", rhoW, default=1035.0, scale=US%kg_m3_to_R, & - units="kg m-3", do_not_log=.True.) call get_param(param_file, mdl, "RHO_SOLID_EARTH", rhoE, & "The mean solid earth density. This is used for calculating the "// & "self-attraction and loading term.", units="kg m-3", & default=5517.0, scale=US%kg_m3_to_R, do_not_log=(.not. CS%use_sal_sht)) + ! Set scaling coefficients for scalar approximation + if (CS%use_sal_scalar .or. CS%use_tidal_sal_prev) then + if (CS%use_sal_scalar .and. CS%use_tidal_sal_prev) then + CS%eta_prop = 2.0 * sal_scalar_value + else + CS%eta_prop = sal_scalar_value + endif + if (CS%use_bpa) then + CS%linear_scaling = CS%eta_prop / (GV%Rho0 * GV%g_Earth) + else + CS%linear_scaling = CS%eta_prop + endif + else + CS%eta_prop = 0.0 ; CS%linear_scaling = 0.0 + endif + + ! Set scaling coefficients for spherical harmonics if (CS%use_sal_sht) then lmax = calc_lmax(CS%sal_sht_Nd) - allocate(CS%Snm_Re(lmax)); CS%Snm_Re(:) = 0.0 - allocate(CS%Snm_Im(lmax)); CS%Snm_Im(:) = 0.0 + allocate(CS%Snm_Re(lmax), source=0.0) + allocate(CS%Snm_Im(lmax), source=0.0) - allocate(CS%Love_Scaling(lmax)); CS%Love_Scaling(:) = 0.0 - call calc_love_scaling(CS%sal_sht_Nd, rhoW, rhoE, CS%Love_Scaling) + allocate(CS%Love_scaling(lmax), source=0.0) + call calc_love_scaling(GV%Rho0, rhoE, GV%g_Earth, CS) allocate(CS%sht) call spherical_harmonics_init(G, param_file, CS%sht) @@ -234,8 +306,11 @@ end subroutine SAL_init subroutine SAL_end(CS) type(SAL_CS), intent(inout) :: CS !< The control structure returned by a previous call !! to SAL_init; it is deallocated here. + + if (allocated(CS%ebot_ref)) deallocate(CS%ebot_ref) + if (CS%use_sal_sht) then - if (allocated(CS%Love_Scaling)) deallocate(CS%Love_Scaling) + if (allocated(CS%Love_scaling)) deallocate(CS%Love_scaling) if (allocated(CS%Snm_Re)) deallocate(CS%Snm_Re) if (allocated(CS%Snm_Im)) deallocate(CS%Snm_Im) call spherical_harmonics_end(CS%sht) @@ -247,20 +322,20 @@ end subroutine SAL_end !! !! \section section_SAL Self attraction and loading !! -!! This module contains methods to calculate self-attraction and loading (SAL) as a function of sea surface height (SSH) -!! (rather, it should be bottom pressure anomaly). SAL is primarily used for fast evolving processes like tides or -!! storm surges, but the effect applies to all motions. +!! This module contains methods to calculate self-attraction and loading (SAL) as a function of sea surface height or +!! bottom pressure anomaly. SAL is primarily used for fast evolving processes like tides or storm surges, but the +!! effect applies to all motions. !! !! If SAL_SCALAR_APPROX is true, a scalar approximation is applied (\cite Accad1978) and the SAL is simply -!! a fraction (set by SAL_SCALAR_VALUE, usually around 10% for global tides) of local SSH. -!! For tides, the scalar approximation can also be used to iterate the SAL to convergence [see -!! USE_PREVIOUS_TIDES in MOM_tidal_forcing, \cite Arbic2004]. +!! a fraction (set by SAL_SCALAR_VALUE, usually around 10% for global tides) of local SSH. For tides, the +!! scalar approximation can also be used to iterate the SAL to convergence [see USE_PREVIOUS_TIDES in +!! MOM_tidal_forcing, \cite Arbic2004]. !! !! If SAL_HARMONICS is true, a more accurate online spherical harmonic transforms are used to calculate -!! SAL. Subroutines in module MOM_spherical_harmonics are called and the degree of spherical harmonic transforms is -!! set by SAL_HARMONICS_DEGREE. The algorithm is based on SAL calculation in Model for Prediction Across -!! Scales (MPAS)-Ocean -!! developed by Los Alamos National Laboratory and University of Michigan [\cite Barton2022 and \cite Brus2023]. +!! SAL. Subroutines in module MOM_spherical_harmonics are called and the degree of spherical harmonic transforms is set +!! by SAL_HARMONICS_DEGREE. The algorithm is based on SAL calculation in Model for Prediction Across +!! Scales (MPAS)-Ocean developed by Los Alamos National Laboratory and University of Michigan +!! [\cite Barton2022 and \cite Brus2023]. !! !! References: !! diff --git a/src/parameterizations/lateral/MOM_spherical_harmonics.F90 b/src/parameterizations/lateral/MOM_spherical_harmonics.F90 index 4f9cee03aa..067e2e3e94 100644 --- a/src/parameterizations/lateral/MOM_spherical_harmonics.F90 +++ b/src/parameterizations/lateral/MOM_spherical_harmonics.F90 @@ -1,12 +1,12 @@ !> Laplace's spherical harmonic transforms (SHT) module MOM_spherical_harmonics +use MOM_coms_infra, only : sum_across_PEs +use MOM_coms, only : reproducing_sum use MOM_cpu_clock, only : cpu_clock_id, cpu_clock_begin, cpu_clock_end, & CLOCK_MODULE, CLOCK_ROUTINE, CLOCK_LOOP use MOM_error_handler, only : MOM_error, FATAL use MOM_file_parser, only : get_param, log_version, param_file_type use MOM_grid, only : ocean_grid_type -use MOM_coms_infra, only : sum_across_PEs -use MOM_coms, only : reproducing_sum implicit none ; private diff --git a/src/parameterizations/vertical/MOM_diapyc_energy_req.F90 b/src/parameterizations/vertical/MOM_diapyc_energy_req.F90 index 7ca432fea4..8ed75a9f44 100644 --- a/src/parameterizations/vertical/MOM_diapyc_energy_req.F90 +++ b/src/parameterizations/vertical/MOM_diapyc_energy_req.F90 @@ -73,7 +73,7 @@ subroutine diapyc_energy_req_test(h_3d, dt, tv, G, GV, US, CS, Kd_int) Kd, & ! A column of diapycnal diffusivities at interfaces [H Z T-1 ~> m2 s-1 or kg m-1 s-1]. h_top, h_bot ! Distances from the top or bottom [H ~> m or kg m-2]. real :: dz_h_int ! The ratio of the vertical distances across the layers surrounding an interface - ! over the layer thicknesses [H Z-1 ~> nonodim or kg m-3] + ! over the layer thicknesses [H Z-1 ~> nondim or kg m-3] real :: ustar ! The local friction velocity [Z T-1 ~> m s-1] real :: absf ! The absolute value of the Coriolis parameter [T-1 ~> s-1] real :: htot ! The sum of the thicknesses [H ~> m or kg m-2].