From 5bee6cc2cafd8bdd6259705fa1f9b30f1b6041a6 Mon Sep 17 00:00:00 2001 From: He Wang Date: Mon, 22 Jul 2024 14:23:15 -0400 Subject: [PATCH 1/6] Refactor SAL in MOM_PressureForce_FV Self-attraction and loading calculation in Boussinesq pressure gradient force is refactored to be consistent with the algorithm in non-Boussinesq version. --- src/core/MOM_PressureForce_FV.F90 | 112 +++++++++++------------------- 1 file changed, 40 insertions(+), 72 deletions(-) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 42e6514ab9..905df97d68 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -1003,77 +1003,50 @@ 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 + ! 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 - call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + 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) + + if (CS%tides_answer_date>20230630) then !$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) enddo ; enddo endif + endif - ! Calculate and add the tidal geopotential anomaly. - if (CS%tides) then + ! Calculate and add the tidal geopotential anomaly. + if (CS%tides) then + if (CS%tides_answer_date>20230630) then call calc_tidal_forcing(CS%Time, e_tide_eq, e_tide_sal, G, US, CS%tides_CSp) - !$OMP parallel do default(shared) + !$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 - enddo ; enddo - endif - - ! Calculate and add the tidal geopotential anomaly. - if (CS%tides) then + else ! Old answers + 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, & 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)) - enddo ; enddo - else - !$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_tide(i,j) enddo ; enddo endif endif @@ -1640,33 +1613,28 @@ 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) = e(i,j,1)*GV%Z_to_H + enddo ; enddo + if (CS%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) = 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 + else !$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 + eta(i,j) = eta(i,j) + e_sal_tide(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 + (e_sal_tide(i,j))*GV%Z_to_H - enddo ; enddo - else + endif + if (CS%calculate_SAL) 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) + e_sal(i,j))*GV%Z_to_H + eta(i,j) = eta(i,j) + e_sal(i,j)*GV%Z_to_H enddo ; enddo endif endif From 22d910a23277ad4206f48ab0e575a2ed030ce889 Mon Sep 17 00:00:00 2001 From: He Wang Date: Sat, 27 Jul 2024 06:31:19 -0400 Subject: [PATCH 2/6] Add option for SAL to use bottom pressure anomaly Using SSH to calculate self-attraction and loading (SAL) is only accurate for barotropic flows. Bottom pressure anomaly should really be used for general purposes. * New runtime parameter A runtime parameter SAL_USE_BPA is added to use bottom pressure anomaly. The option requires an input file for long term mean reference bottom pressure. The reference bottom pressure field is stored with SAL_CS. * Refactor SAL and tides in Boussinesq mode As the total bottom pressure is needed for bottom pressure anomaly, SAL calculation in Boussinesq mode needs to be refactored. In addition, there is a longstanding bug in Boussinesq mode that interface height is modified by SAL and tides, and the modified interface height is erroneously used for density and pressure calculation later on. Therefore the SAL and tides are refactored by moving their calculations after pressure is calculated. Tide answers before 20230630 is retained. Answers after 20230630 are changed for Boussinesq mode. --- src/core/MOM_PressureForce_FV.F90 | 158 +++++++------ src/core/MOM_dynamics_split_RK2.F90 | 2 +- src/core/MOM_dynamics_split_RK2b.F90 | 2 +- src/core/MOM_dynamics_unsplit.F90 | 2 +- src/core/MOM_dynamics_unsplit_RK2.F90 | 2 +- .../lateral/MOM_self_attr_load.F90 | 211 ++++++++++++------ 6 files changed, 238 insertions(+), 139 deletions(-) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 905df97d68..8fec00fb90 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,7 +82,7 @@ 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 + 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 @@ -217,6 +219,7 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ real :: dp_neglect ! A thickness that is so small it is usually lost ! in roundoff and can be neglected [R L2 T-2 ~> Pa]. real :: I_gEarth ! The inverse of GV%g_Earth [T2 Z L-2 ~> s2 m-1] + real :: I_g_rho ! The inverse of the density times the gravitational acceleration [Z T2 L-2 R-1 ~> m Pa-1] real :: alpha_anom ! The in-situ specific volume, averaged over a ! layer, less alpha_ref [R-1 ~> m3 kg-1]. logical :: use_p_atm ! If true, use the atmospheric pressure. @@ -399,15 +402,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 + if (CS%sal_use_bpa) then + I_g_rho = 1.0 / (GV%rho0*GV%g_Earth) + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + SSH(i,j) = p(i,j,nz+1) * I_g_rho + enddo ; enddo + 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 + endif call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + ! 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,7 +428,7 @@ 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) @@ -950,7 +962,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. @@ -998,6 +1010,7 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm G_Rho0 = GV%g_Earth / GV%Rho0 GxRho = GV%g_Earth * GV%Rho0 rho_ref = CS%Rho0 + I_g_rho = 1.0 / (CS%rho0*GV%g_Earth) ! I think it should be I_g_rho = 1.0 / (GV%rho0*GV%g_Earth) if ((CS%id_MassWt_u > 0) .or. (CS%id_MassWt_v > 0)) then MassWt_u(:,:,:) = 0.0 ; MassWt_v(:,:,:) = 0.0 @@ -1007,48 +1020,28 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm 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. + ! Self-attraction and loading (SAL) and tides (old answers) + ! SAL and tidal geopotential height anomalies are calculated and added as corrections to interface heights. + ! The following code is for recovering old answers only. The 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 method. + if (CS%tides_answer_date<=20230630) 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) + SSH(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) ! reference bottom pressure 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) - - if (CS%tides_answer_date>20230630) then - !$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) - enddo ; enddo - endif - endif - - ! Calculate and add the tidal geopotential anomaly. - if (CS%tides) then - if (CS%tides_answer_date>20230630) then - call calc_tidal_forcing(CS%Time, e_tide_eq, e_tide_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) = e(i,j,nz+1) - (e_tide_eq(i,j) + e_tide_sal(i,j)) - enddo ; enddo - else ! Old answers - 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, & - 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) = e(i,j,nz+1) - e_sal_tide(i,j) - enddo ; enddo - endif + 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) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + e(i,j,nz+1) = e(i,j,nz+1) - e_sal_tide(i,j) + enddo ; enddo endif !$OMP parallel do default(shared) @@ -1117,7 +1110,6 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm endif if (CS%use_SSH_in_Z0p .and. use_p_atm) then - I_g_rho = 1.0 / (CS%rho0*GV%g_Earth) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 Z_0p(i,j) = e(i,j,1) + p_atm(i,j) * I_g_rho enddo ; enddo @@ -1196,6 +1188,45 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; enddo enddo + ! Self-attraction and loading (SAL) and tides (new answers) + if (CS%tides_answer_date>20230630) then + ! SAL + if (CS%calculate_SAL) then + if (CS%sal_use_bpa) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + SSH(i,j) = pa(i,j,nz+1) * I_g_rho - e(i,j,nz+1) + enddo ; enddo + 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 topogrpahy above sea level + enddo ; enddo + endif + call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + 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 + + ! Tides + if (CS%tides) then + call calc_tidal_forcing(CS%Time, e_tide_eq, e_tide_sal, G, US, CS%tides_CSp) + !$OMP parallel do default(shared) + 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_tide_eq(i,j) + e_tide_sal(i,j)) + pa(i,j,K) = pa(i,j,K) - GxRho * (e_tide_eq(i,j) + e_tide_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 @@ -1630,13 +1661,11 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; enddo endif endif - if (CS%calculate_SAL) 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) = eta(i,j) + e_sal(i,j)*GV%Z_to_H - enddo ; enddo - endif + if (CS%calculate_SAL .and. (CS%tides_answer_date>20230630)) 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 @@ -1737,19 +1766,19 @@ 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. In both "//& + "Boussinesq and non-Boussinesq modes, 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, 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.) 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.) @@ -1764,6 +1793,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<=20230630 .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 20230630.") call get_param(param_file, "MOM", "USE_REGRIDDING", use_ALE, & "If True, use the ALE algorithm (regridding/remapping). "//& diff --git a/src/core/MOM_dynamics_split_RK2.F90 b/src/core/MOM_dynamics_split_RK2.F90 index addfb0e212..22b6c33b23 100644 --- a/src/core/MOM_dynamics_split_RK2.F90 +++ b/src/core/MOM_dynamics_split_RK2.F90 @@ -1507,7 +1507,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 9227ea57ed..61123998f5 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/parameterizations/lateral/MOM_self_attr_load.F90 b/src/parameterizations/lateral/MOM_self_attr_load.F90 index f72b6f513e..09adb06730 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,61 +29,81 @@ 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] - real, allocatable :: Snm_Re(:), & !< Real SHT coefficient for SHT SAL [Z ~> m] - Snm_Im(:) !< Imaginary SHT coefficient for SHT SAL [Z ~> m] + 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 + !< A dimensional coefficient for scalar approximation SAL, equal to eta_prop * unit_convert + !! [nondim or L2 Z-1 T-2 ~> m s-2 or R-1 ~> m3 kg-1 or L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1] + type(sht_CS), allocatable :: sht + !< Spherical harmonic transforms (SHT) control structure + integer :: sal_sht_Nd + !< Maximum degree for spherical harmonic transforms [nodim] + real, allocatable :: ebot_ref(:,:) + !< Reference bottom pressure normalized 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 L2 Z-1 T-2 ~> m s-2 or R-1 ~> m3 kg-1 or L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1] + real, allocatable :: Snm_Re(:), Snm_Im(:) + !< Real and imaginary coefficients for harmonic SAL [Z ~> m or L2 T-2 ~> m2 s-2] end type SAL_CS integer :: id_clock_SAL !< CPU clock for self-attraction and loading 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 is always assume to have the unit of [Z ~> m], which can be either SSH or total bottom pressure +!! normalized by mean seawater density Rho_0 and earth gravity G_Earth. For spherical harmonic method, the mean +!! seawater density would be cancelled by the same parameter in Love Number scalings. If total bottom pressure is used +!! as input, bottom pressure anomaly is calculated in the subroutine by subtracting a reference pressure from the +!! input bottom pressure. +!! 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. 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 normalized by mean density [Z ~> m]. + 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 integer :: n, m, l + real, dimension(SZI_(G),SZJ_(G)) :: bpa ! [Z ~> m] 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%eta_prop * 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,36 +120,32 @@ 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) +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] + real, dimension(:), intent(out) :: Love_scaling !< Scaling factors for inverse spherical harmonic + !! transforms [nondim] ! Local variables 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, m, l + real :: unit n_tot = size(Love_Data, dim=2) @@ -148,34 +166,40 @@ subroutine calc_love_scaling(nlm, rhoW, rhoE, Love_Scaling) 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)) + Love_scaling(l+n-m) = (3.0 / real(2*n+1)) * (rhoW / 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]. - - logical :: calculate_sal - logical :: tides, use_tidal_sal_file - real :: tide_sal_scalar_value ! Scaling SAL factor [nondim] + character(len=200) :: filename, ebot_ref_file, inputdir ! Strings for file/path + character(len=200) :: ebot_ref_varname ! Variable name in file + real :: I_g_rho ! The inverse of the density times the gravitational acceleration [Z T2 L-2 R-1 ~> m Pa-1] + logical :: calculate_sal=.false. + logical :: tides=.false., use_tidal_sal_file=.false., bq_sal_tides_bug=.false. + integer :: tides_answer_date=99991203 ! Recover old answers with tides + real :: sal_scalar_value, tide_sal_scalar_value ! Scaling SAL factors [nondim] + + integer :: isd, ied, jsd, jed + integer :: i, j + 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 +207,51 @@ 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) + I_g_rho = 1.0 / (GV%g_Earth * GV%Rho0) + do j=jsd,jed ; do i=isd,ied + CS%ebot_ref(i,j) = CS%ebot_ref(i,j) * I_g_rho + enddo ; enddo + call pass_var(CS%ebot_ref, G%Domain) + endif + if (tides_answer_date<=20230630 .and. CS%use_bpa) & + call MOM_error(FATAL, trim(mdl) // ", SAL_init: SAL_USE_BPA needs to be false to recover "//& + "tide answers before 20230630.") 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 +265,28 @@ 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 .and. CS%use_tidal_sal_prev) then + CS%eta_prop = 2.0 * sal_scalar_value + elseif (CS%use_sal_scalar .or. CS%use_tidal_sal_prev) then + CS%eta_prop = sal_scalar_value + else + CS%eta_prop = 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%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)); CS%Love_scaling(:) = 0.0 + call calc_love_scaling(CS%sal_sht_Nd, GV%Rho0, rhoE, CS%Love_scaling) allocate(CS%sht) call spherical_harmonics_init(G, param_file, CS%sht) @@ -234,8 +300,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) @@ -245,22 +314,20 @@ end subroutine SAL_end !> \namespace self_attr_load !! -!! \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: !! From 1d7bc7f6d21aee8fdcd0426447bad39441074af4 Mon Sep 17 00:00:00 2001 From: He Wang Date: Fri, 27 Sep 2024 11:03:27 -0400 Subject: [PATCH 3/6] Rename variables with tides in Pressure Force * Local variable SSH is renamed to pbot_anom. * Tides related local variable names are changed to be less confusing. Notably, `e_sal_tide` is renamed to `e_sal_and_tide` (the summation of sal and tide), not to be confused with `e_tide_sal`, which is renamed to `e_tidal_sal` (sal from tides). * Fix e_tidal diagnostics in Boussinesq mode. The term was not assigned if tide answer date is >20230630. --- src/core/MOM_PressureForce_FV.F90 | 79 ++++++++++--------- .../lateral/MOM_self_attr_load.F90 | 15 ++-- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 8fec00fb90..f86ce5e57f 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -84,8 +84,8 @@ module MOM_PressureForce_FV logical :: use_stanley_pgf !< If true, turn on Stanley parameterization in the PGF 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 @@ -143,12 +143,13 @@ 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]. + pbot_anom, & ! Bottom pressure (anomaly) in depth units, used for self-attraction and loading [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 [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) :: & @@ -408,16 +409,16 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ I_g_rho = 1.0 / (GV%rho0*GV%g_Earth) !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - SSH(i,j) = p(i,j,nz+1) * I_g_rho + pbot_anom(i,j) = p(i,j,nz+1) * I_g_rho enddo ; enddo 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 & + pbot_anom(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 endif - call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) ! 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 @@ -431,18 +432,18 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ ! 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 @@ -820,10 +821,10 @@ 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) call post_data(CS%id_e_tide, e_sal+e_tidal_eq+e_tidal_sal, CS%diag) 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) @@ -858,14 +859,14 @@ 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]. + pbot_anom, & ! Bottom pressure (anomaly) in depth units, used for self-attraction and loading [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)) :: & @@ -1029,18 +1030,18 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm !$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) ! reference bottom pressure + pbot_anom(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) ! reference bottom pressure enddo do k=1,nz ; do i=Isq,Ieq+1 - SSH(i,j) = SSH(i,j) + h(i,j,k)*GV%H_to_Z + pbot_anom(i,j) = pbot_anom(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) - call calc_tidal_forcing_legacy(CS%Time, e_sal, e_sal_tide, e_tide_eq, e_tide_sal, & + call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + 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) = e(i,j,nz+1) - e_sal_tide(i,j) + e(i,j,nz+1) = e(i,j,nz+1) - e_sal_and_tide(i,j) enddo ; enddo endif @@ -1195,15 +1196,15 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm if (CS%sal_use_bpa) then !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - SSH(i,j) = pa(i,j,nz+1) * I_g_rho - e(i,j,nz+1) + pbot_anom(i,j) = pa(i,j,nz+1) * I_g_rho - e(i,j,nz+1) enddo ; enddo 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 topogrpahy above sea level + pbot_anom(i,j) = e(i,j,1) - max(-G%bathyT(i,j) - G%Z_ref, 0.0) ! Remove topogrpahy above sea level enddo ; enddo endif - call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) do K=1,nz+1 !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 @@ -1215,13 +1216,13 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm ! Tides if (CS%tides) 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 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_tide_eq(i,j) + e_tide_sal(i,j)) - pa(i,j,K) = pa(i,j,K) - GxRho * (e_tide_eq(i,j) + e_tide_sal(i,j)) + 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 @@ -1652,12 +1653,12 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm 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) = eta(i,j) + (e_tide_eq(i,j)+e_tide_sal(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) = eta(i,j) + e_sal_tide(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 @@ -1708,10 +1709,10 @@ 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) call post_data(CS%id_e_tide, e_sal+e_tidal_eq+e_tidal_sal, CS%diag) 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) @@ -1795,7 +1796,7 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, SAL_CSp, default=.false., do_not_log=.not.GV%Boussinesq) if (CS%tides .and. CS%tides_answer_date<=20230630 .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 20230630.") + "needs to be FALSE to recover tide answers before 20230701.") call get_param(param_file, "MOM", "USE_REGRIDDING", use_ALE, & "If True, use the ALE algorithm (regridding/remapping). "//& @@ -1880,9 +1881,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/parameterizations/lateral/MOM_self_attr_load.F90 b/src/parameterizations/lateral/MOM_self_attr_load.F90 index 09adb06730..fe4149d602 100644 --- a/src/parameterizations/lateral/MOM_self_attr_load.F90 +++ b/src/parameterizations/lateral/MOM_self_attr_load.F90 @@ -41,7 +41,7 @@ module MOM_self_attr_load integer :: sal_sht_Nd !< Maximum degree for spherical harmonic transforms [nodim] real, allocatable :: ebot_ref(:,:) - !< Reference bottom pressure normalized by Rho_0 and G_Earth[Z ~> m] + !< 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 L2 Z-1 T-2 ~> m s-2 or R-1 ~> m3 kg-1 or L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1] @@ -56,16 +56,15 @@ module MOM_self_attr_load !> 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 is always assume to have the unit of [Z ~> m], which can be either SSH or total bottom pressure -!! normalized by mean seawater density Rho_0 and earth gravity G_Earth. For spherical harmonic method, the mean -!! seawater density would be cancelled by the same parameter in Love Number scalings. If total bottom pressure is used -!! as input, bottom pressure anomaly is calculated in the subroutine by subtracting a reference pressure from the -!! input bottom pressure. -!! The output field is expressed as geopotential height anomaly, and therefore has the unit of [Z ~> m]. +!! The input field is always assume to have the unit of [Z ~> m], which can be either SSH or total bottom pressure +!! scaled by mean seawater density Rho_0 and earth gravity G_Earth. For spherical harmonics, the mean seawater density +!! would be cancelled by the same parameter in Love number scalings. If total bottom pressure is used as input, bottom +!! pressure anomaly is calculated in the subroutine by subtracting a reference pressure from the input bottom pressure. +!! 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. real, dimension(SZI_(G),SZJ_(G)), intent(in) :: eta !< The sea surface height anomaly from - !! a time-mean geoid or total bottom pressure normalized by mean density [Z ~> m]. + !! a time-mean geoid or total bottom pressure scaled by mean density and earth gravity [Z ~> m]. 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. From e17bfc11234439f8348b5305d97e91f9b5baf6b9 Mon Sep 17 00:00:00 2001 From: He Wang Date: Fri, 27 Sep 2024 11:55:51 -0400 Subject: [PATCH 4/6] Alternative method for SAL and tides in Boussinesq Add an alternative method for SAL and tides in Boussinesq mode. The current method adjusts interface heights with geopotential height anomaly for SAL and tides. For non-Boussinesq mode, the current method is algebraically the same as taking the gradient of SAL and tide geopotential (body forcing). For Boussinesq mode, there is a baroclinic component of tidal forcing and SAL. The alternative method is added to calculate the gradient of tidal forcing and SAL directly at the cost of additional multiplications. The new method is controlled by runtime parameter BOUSSINESQ_SAL_TIDES. --- src/core/MOM_PressureForce_FV.F90 | 120 ++++++++++++------ .../lateral/MOM_self_attr_load.F90 | 4 +- 2 files changed, 84 insertions(+), 40 deletions(-) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index f86ce5e57f..7a96de15ac 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -82,6 +82,8 @@ 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 + 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_tidal_eq = -1 !< Diagnostic identifier @@ -821,7 +823,12 @@ 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_tidal_eq+e_tidal_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_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) @@ -1189,43 +1196,39 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; enddo enddo - ! Self-attraction and loading (SAL) and tides (new answers) - if (CS%tides_answer_date>20230630) then - ! SAL - if (CS%calculate_SAL) then - if (CS%sal_use_bpa) then - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - pbot_anom(i,j) = pa(i,j,nz+1) * I_g_rho - e(i,j,nz+1) - enddo ; enddo - else - !$OMP parallel do default(shared) - do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - pbot_anom(i,j) = e(i,j,1) - max(-G%bathyT(i,j) - G%Z_ref, 0.0) ! Remove topogrpahy above sea level - enddo ; enddo - endif - call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) - 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 + ! Self-attraction and loading (SAL) (new answers) + if (CS%calculate_SAL .and. CS%tides_answer_date>20230630) then + if (CS%sal_use_bpa) then + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + pbot_anom(i,j) = pa(i,j,nz+1) * I_g_rho - e(i,j,nz+1) + enddo ; enddo + else + !$OMP parallel do default(shared) + do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 + pbot_anom(i,j) = e(i,j,1) - max(-G%bathyT(i,j) - G%Z_ref, 0.0) ! Remove topography above sea level + enddo ; enddo endif + call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + 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 - ! Tides - if (CS%tides) then - call calc_tidal_forcing(CS%Time, e_tidal_eq, e_tidal_sal, G, US, CS%tides_CSp) + ! Tides (new answers) + if (CS%tides .and. CS%tides_answer_date>20230630) 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 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 + 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 @@ -1604,6 +1607,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 @@ -1649,7 +1680,7 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm 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 + 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 @@ -1662,7 +1693,7 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; enddo endif endif - if (CS%calculate_SAL .and. (CS%tides_answer_date>20230630)) then + 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 @@ -1709,7 +1740,12 @@ 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+e_tidal_eq+e_tidal_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_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) @@ -1780,6 +1816,14 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, SAL_CSp, 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.) diff --git a/src/parameterizations/lateral/MOM_self_attr_load.F90 b/src/parameterizations/lateral/MOM_self_attr_load.F90 index fe4149d602..cde06505e6 100644 --- a/src/parameterizations/lateral/MOM_self_attr_load.F90 +++ b/src/parameterizations/lateral/MOM_self_attr_load.F90 @@ -45,8 +45,8 @@ module MOM_self_attr_load real, allocatable :: Love_scaling(:) !< Dimensional coefficients for harmonic SAL, which are functions of Love numbers !! [nondim or L2 Z-1 T-2 ~> m s-2 or R-1 ~> m3 kg-1 or L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1] - real, allocatable :: Snm_Re(:), Snm_Im(:) - !< Real and imaginary coefficients for harmonic SAL [Z ~> m or L2 T-2 ~> m2 s-2] + 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 integer :: id_clock_SAL !< CPU clock for self-attraction and loading From 8039cb8abb08965473f8857b12ca1d1834def8c4 Mon Sep 17 00:00:00 2001 From: He Wang Date: Mon, 7 Oct 2024 10:19:39 -0400 Subject: [PATCH 5/6] Remove rescaling bottom pressure in SAL * Instead of rescaling bottom pressure to height unit, calc_Loving_scaling is modified to be conditionally dimensional. When calculating self-attraction and loading, Love numbers are now dimensional when bottom pressure anomaly is used as an input. This change eliminate Love numbers' dependence on mean seawater density. * A new coefficient called linear_scaling is added to SAL CS for the same purpose, although to use bottom pressure anomaly for scalar approximation is not quite justifiable. A WARNING is given when users try to do that. * Two separate field, SSH (sea surface height anomaly) and pbot (total bottom pressure), are used for calculating self attraction and loading when SAL_USE_BPA = False and SAL_USE_BPA = True, respectively. The change is to enhance code readability. --- src/core/MOM_PressureForce_FV.F90 | 36 +++++---- .../lateral/MOM_self_attr_load.F90 | 80 ++++++++++--------- .../lateral/MOM_spherical_harmonics.F90 | 4 +- 3 files changed, 64 insertions(+), 56 deletions(-) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 7a96de15ac..7accb052a5 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -145,7 +145,10 @@ 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]. - pbot_anom, & ! Bottom pressure (anomaly) in depth units, used for self-attraction and loading [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_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 @@ -222,7 +225,6 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ real :: dp_neglect ! A thickness that is so small it is usually lost ! in roundoff and can be neglected [R L2 T-2 ~> Pa]. real :: I_gEarth ! The inverse of GV%g_Earth [T2 Z L-2 ~> s2 m-1] - real :: I_g_rho ! The inverse of the density times the gravitational acceleration [Z T2 L-2 R-1 ~> m Pa-1] real :: alpha_anom ! The in-situ specific volume, averaged over a ! layer, less alpha_ref [R-1 ~> m3 kg-1]. logical :: use_p_atm ! If true, use the atmospheric pressure. @@ -408,19 +410,19 @@ subroutine PressureForce_FV_nonBouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_ ! Calculate and add self-attraction and loading (SAL) geopotential height anomaly to interface height. if (CS%calculate_SAL) then if (CS%sal_use_bpa) then - I_g_rho = 1.0 / (GV%rho0*GV%g_Earth) !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - pbot_anom(i,j) = p(i,j,nz+1) * I_g_rho + 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 - pbot_anom(i,j) = (za(i,j,1) - alpha_ref*p(i,j,1)) * I_gEarth - G%Z_ref & + 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 - call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) ! 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 @@ -873,7 +875,10 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm 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] - pbot_anom, & ! Bottom pressure (anomaly) in depth units, used for self-attraction and loading [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)) :: & @@ -1018,7 +1023,6 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm G_Rho0 = GV%g_Earth / GV%Rho0 GxRho = GV%g_Earth * GV%Rho0 rho_ref = CS%Rho0 - I_g_rho = 1.0 / (CS%rho0*GV%g_Earth) ! I think it should be I_g_rho = 1.0 / (GV%rho0*GV%g_Earth) if ((CS%id_MassWt_u > 0) .or. (CS%id_MassWt_v > 0)) then MassWt_u(:,:,:) = 0.0 ; MassWt_v(:,:,:) = 0.0 @@ -1033,17 +1037,17 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm ! The following code is for recovering old answers only. The 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 method. - if (CS%tides_answer_date<=20230630) then + if (CS%tides .and. CS%tides_answer_date<=20230630) then !$OMP parallel do default(shared) do j=Jsq,Jeq+1 do i=Isq,Ieq+1 - pbot_anom(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) ! reference bottom pressure + SSH(i,j) = min(-G%bathyT(i,j) - G%Z_ref, 0.0) ! reference bottom pressure enddo do k=1,nz ; do i=Isq,Ieq+1 - pbot_anom(i,j) = pbot_anom(i,j) + h(i,j,k)*GV%H_to_Z + SSH(i,j) = SSH(i,j) + h(i,j,k)*GV%H_to_Z enddo ; enddo enddo - call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) + call calc_SAL(SSH, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) 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) @@ -1118,6 +1122,7 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm endif if (CS%use_SSH_in_Z0p .and. use_p_atm) then + I_g_rho = 1.0 / (CS%rho0*GV%g_Earth) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 Z_0p(i,j) = e(i,j,1) + p_atm(i,j) * I_g_rho enddo ; enddo @@ -1201,15 +1206,16 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm if (CS%sal_use_bpa) then !$OMP parallel do default(shared) do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - pbot_anom(i,j) = pa(i,j,nz+1) * I_g_rho - e(i,j,nz+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 - pbot_anom(i,j) = e(i,j,1) - max(-G%bathyT(i,j) - G%Z_ref, 0.0) ! Remove topography above sea level + 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 - call calc_SAL(pbot_anom, e_sal, G, CS%SAL_CSp, tmp_scale=US%Z_to_m) 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 diff --git a/src/parameterizations/lateral/MOM_self_attr_load.F90 b/src/parameterizations/lateral/MOM_self_attr_load.F90 index cde06505e6..1344ca1c04 100644 --- a/src/parameterizations/lateral/MOM_self_attr_load.F90 +++ b/src/parameterizations/lateral/MOM_self_attr_load.F90 @@ -34,8 +34,7 @@ module MOM_self_attr_load real :: eta_prop !< The partial derivative of eta_sal with the local value of eta [nondim]. real :: linear_scaling - !< A dimensional coefficient for scalar approximation SAL, equal to eta_prop * unit_convert - !! [nondim or L2 Z-1 T-2 ~> m s-2 or R-1 ~> m3 kg-1 or L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1] + !< 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 @@ -44,7 +43,7 @@ module MOM_self_attr_load !< 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 L2 Z-1 T-2 ~> m s-2 or R-1 ~> m3 kg-1 or L2 Z-1 T-2 R-1 ~> m4 s-2 kg-1] + !! [nondim or Z T2 L-2 R-1 ~> m Pa-1] 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 @@ -56,15 +55,13 @@ module MOM_self_attr_load !> 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 is always assume to have the unit of [Z ~> m], which can be either SSH or total bottom pressure -!! scaled by mean seawater density Rho_0 and earth gravity G_Earth. For spherical harmonics, the mean seawater density -!! would be cancelled by the same parameter in Love number scalings. If total bottom pressure is used as input, bottom -!! pressure anomaly is calculated in the subroutine by subtracting a reference pressure from the input bottom pressure. +!! 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 or total bottom pressure scaled by mean density and earth gravity [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. @@ -72,8 +69,8 @@ subroutine calc_SAL(eta, eta_sal, G, CS, tmp_scale) !! 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 - real, dimension(SZI_(G),SZJ_(G)) :: bpa ! [Z ~> m] integer :: Isq, Ieq, Jsq, Jeq integer :: i, j @@ -90,7 +87,7 @@ subroutine calc_SAL(eta, eta_sal, G, CS, tmp_scale) ! use the scalar approximation and/or iterative tidal SAL if (CS%use_sal_scalar .or. CS%use_tidal_sal_prev) then do j=Jsq,Jeq+1 ; do i=Isq,Ieq+1 - eta_sal(i,j) = CS%eta_prop * bpa(i,j) + eta_sal(i,j) = CS%linear_scaling * bpa(i,j) enddo ; enddo ! use the spherical harmonics method @@ -132,21 +129,21 @@ 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 spherical harmonic - !! transforms [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, 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 - real :: unit 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 " // & @@ -165,7 +162,11 @@ subroutine calc_love_scaling(nlm, rhoW, rhoE, Love_scaling) 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)) + if (CS%use_bpa) then + CS%Love_scaling(l+n-m) = (3.0 / real(2*n+1)) * (1.0 / (rhoE * grav)) * (1.0 + KDat(n+1) - HDat(n+1)) + else + CS%Love_scaling(l+n-m) = (3.0 / real(2*n+1)) * (rhoW / rhoE) * (1.0 + KDat(n+1) - HDat(n+1)) + endif enddo ; enddo end subroutine calc_love_scaling @@ -184,14 +185,12 @@ subroutine SAL_init(G, GV, US, param_file, CS) 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 - real :: I_g_rho ! The inverse of the density times the gravitational acceleration [Z T2 L-2 R-1 ~> m Pa-1] logical :: calculate_sal=.false. logical :: tides=.false., use_tidal_sal_file=.false., bq_sal_tides_bug=.false. integer :: tides_answer_date=99991203 ! Recover old answers with tides real :: sal_scalar_value, tide_sal_scalar_value ! Scaling SAL factors [nondim] - integer :: isd, ied, jsd, jed - integer :: i, j + isd = G%isd ; ied = G%ied ; jsd = G%jsd ; jed = G%jed ! Read all relevant parameters and write them to the model log. @@ -230,10 +229,6 @@ subroutine SAL_init(G, GV, US, param_file, CS) 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) - I_g_rho = 1.0 / (GV%g_Earth * GV%Rho0) - do j=jsd,jed ; do i=isd,ied - CS%ebot_ref(i,j) = CS%ebot_ref(i,j) * I_g_rho - enddo ; enddo call pass_var(CS%ebot_ref, G%Domain) endif if (tides_answer_date<=20230630 .and. CS%use_bpa) & @@ -242,9 +237,9 @@ subroutine SAL_init(G, GV, US, param_file, CS) 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.") + 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) & @@ -270,22 +265,29 @@ subroutine SAL_init(G, GV, US, param_file, CS) 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 .and. CS%use_tidal_sal_prev) then - CS%eta_prop = 2.0 * sal_scalar_value - elseif (CS%use_sal_scalar .or. CS%use_tidal_sal_prev) then - CS%eta_prop = sal_scalar_value + 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%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, GV%Rho0, 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) 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 From 29e5b82efc9e963fc966330dabb10a46fb40285a Mon Sep 17 00:00:00 2001 From: He Wang Date: Tue, 7 Jan 2025 14:50:07 -0500 Subject: [PATCH 6/6] Add answer date flag for tide/SAL A new date for TIDES_ANSWER_DATE is added to recover answers for tides in Boussinesq mode before 20250201. --- src/core/MOM_PressureForce_FV.F90 | 71 ++++++++++++------- .../lateral/MOM_self_attr_load.F90 | 6 +- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/core/MOM_PressureForce_FV.F90 b/src/core/MOM_PressureForce_FV.F90 index 7accb052a5..cccfa1c2e4 100644 --- a/src/core/MOM_PressureForce_FV.F90 +++ b/src/core/MOM_PressureForce_FV.F90 @@ -1032,28 +1032,49 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm e(i,j,nz+1) = -G%bathyT(i,j) enddo ; enddo - ! Self-attraction and loading (SAL) and tides (old answers) - ! SAL and tidal geopotential height anomalies are calculated and added as corrections to interface heights. - ! The following code is for recovering old answers only. The 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 method. - if (CS%tides .and. CS%tides_answer_date<=20230630) then + ! 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) ! reference bottom pressure + 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) - 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) = e(i,j,nz+1) - e_sal_and_tide(i,j) - enddo ; enddo + + 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_sal(i,j) + enddo ; enddo + endif + endif + + ! 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) = e(i,j,nz+1) - (e_tidal_eq(i,j) + e_tidal_sal(i,j)) + enddo ; enddo + 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) = e(i,j,nz+1) - e_sal_and_tide(i,j) + enddo ; enddo + endif endif !$OMP parallel do default(shared) @@ -1201,8 +1222,8 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; enddo enddo - ! Self-attraction and loading (SAL) (new answers) - if (CS%calculate_SAL .and. CS%tides_answer_date>20230630) then + ! 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 @@ -1225,8 +1246,8 @@ subroutine PressureForce_FV_Bouss(h, tv, PFu, PFv, G, GV, US, CS, ALE_CSp, p_atm enddo ; endif endif - ! Tides (new answers) - if (CS%tides .and. CS%tides_answer_date>20230630) then + ! 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) @@ -1812,11 +1833,13 @@ subroutine PressureForce_FV_init(Time, G, GV, US, param_file, diag, CS, SAL_CSp, 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. In both "//& - "Boussinesq and non-Boussinesq modes, 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, do_not_log=(.not.CS%tides)) + "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) & @@ -1844,9 +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<=20230630 .and. CS%use_SSH_in_Z0p) & + 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 20230701.") + "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). "//& diff --git a/src/parameterizations/lateral/MOM_self_attr_load.F90 b/src/parameterizations/lateral/MOM_self_attr_load.F90 index 1344ca1c04..fa5a46110c 100644 --- a/src/parameterizations/lateral/MOM_self_attr_load.F90 +++ b/src/parameterizations/lateral/MOM_self_attr_load.F90 @@ -187,7 +187,7 @@ subroutine SAL_init(G, GV, US, param_file, CS) 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=99991203 ! Recover old answers with tides + 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 @@ -231,9 +231,9 @@ subroutine SAL_init(G, GV, US, param_file, CS) scale=US%Pa_to_RL2_T2) call pass_var(CS%ebot_ref, G%Domain) endif - if (tides_answer_date<=20230630 .and. CS%use_bpa) & + 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 20230630.") + "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))