Skip to content

Commit bff761b

Browse files
committed
+*Fix 3-equation ice-ocean flux iteration
Fix the 3-equation iteration for the buoyancy flux between the ocean and an overlying ice-shelf when ICE_SHELF_BUOYANCY_FLUX_ITT_BUGFIX is true and SHELF_3EQ_GAMMA it false. This code now uses proper bounding of the self-consistent solution, avoiding further amplifying the fluxes in the cases when the differences between the diffusivities of heat and salt to make the buoyancy flux destabilizing for finite turbulent mixing. Both the false-position iterations and the (appropriately chosen) Newton's method iterations have been extensively examined and determined to be working correctly via print statements that have subsequently been removed for efficiency. Previously, the code to determine the 3-equation solution for the buoyancy flux between the ocean and an ice shelf had been skipping iteration altogether or doing un-bounded Newton's method iterations with a sign error in part of the derivative, including taking the square root of negative numbers, leading to the issue described at #945. That issue has now been corrected and can be closed once this commit has been merged into the dev/gfdl branch of MOM6. This commit also changes the names of the runtime parameters to correct the ice shelf flux iteration bugs from ICE_SHELF_BUOYANCY_FLUX_ITT_BUG and ICE_SHELF_SALT_FLUX_ITT_BUG to ICE_SHELF_BUOYANCY_FLUX_ITT_BUGFIX and ICE_SHELF_SALT_FLUX_ITT_BUGFIX to avoid confusion with other ..._BUG parameters where `true` is to turn the bugs on, whereas here `true` fixes them. The old names are retained via `old_name` arguments to the `get_param()` calls, so no existing configurations will be disrupted by these changes. Additionally, an expression to determine a scaling factor to limit ice-shelf bottom slopes in `calc_shelf_driving_stress()` was refactored to avoid the possibility of division by zero. This commit will change (and correct) answers for cases with ICE_SHELF_BUOYANCY_FLUX_ITT_BUGFIX set to true, but as these would often fail with a NaN from taking the square root of a negative value, it is very unlikely that any such configurations are actively being used, and there seems little point in retaining the previous answers. No answers are changed in cases that do not use an active ice shelf.
1 parent 83a1048 commit bff761b

File tree

2 files changed

+155
-65
lines changed

2 files changed

+155
-65
lines changed

src/ice_shelf/MOM_ice_shelf.F90

Lines changed: 154 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ module MOM_ice_shelf
197197
!! divided by the von Karman constant VK. Was 1/8.
198198
real :: Vk !< Von Karman's constant - dimensionless
199199
real :: Rc !< critical flux Richardson number.
200-
logical :: buoy_flux_itt_bug !< If true, fixes buoyancy iteration bug
201-
logical :: salt_flux_itt_bug !< If true, fixes salt iteration bug
200+
logical :: buoy_flux_itt_bugfix !< If true, fixes buoyancy iteration bug
201+
logical :: salt_flux_itt_bugfix !< If true, fixes salt iteration bug
202202
real :: buoy_flux_itt_threshold !< Buoyancy iteration threshold for convergence
203203

204204
!>@{ Diagnostic handles
@@ -298,7 +298,7 @@ subroutine shelf_calc_flux(sfc_state_in, fluxes_in, Time, time_step_in, CS)
298298
real :: ZETA_N !< This is the stability constant xi_N = 0.052 from Holland & Jenkins '99
299299
!! divided by the von Karman constant VK. Was 1/8. [nondim]
300300
real :: RC !< critical flux Richardson number.
301-
real :: I_ZETA_N !< The inverse of ZETA_N [nondim].
301+
real :: I_2Zeta_N !< Half the inverse of Zeta_N [nondim].
302302
real :: I_LF !< The inverse of the latent heat of fusion [Q-1 ~> kg J-1].
303303
real :: I_VK !< The inverse of the Von Karman constant [nondim].
304304
real :: PR, SC !< The Prandtl number and Schmidt number [nondim].
@@ -327,30 +327,38 @@ subroutine shelf_calc_flux(sfc_state_in, fluxes_in, Time, time_step_in, CS)
327327
real :: dS_ustar ! The difference between the salinity at the ice-ocean interface and the ocean
328328
! boundary layer salinity times the friction velocity [S Z T-1 ~> ppt m s-1]
329329
real :: ustar_h ! The friction velocity in the water below the ice shelf [Z T-1 ~> m s-1]
330-
real :: Gam_turb ! [nondim]
330+
real :: Gam_turb ! A relative turbluent diffusivity [nondim]
331331
real :: Gam_mol_t, Gam_mol_s ! Relative coefficients of molecular diffusivities [nondim]
332332
real :: RhoCp ! A typical ocean density times the heat capacity of water [Q R C-1 ~> J m-3 degC-1]
333-
real :: ln_neut
333+
real :: ln_neut ! The log of the ratio of the neutral boundary layer thickness to the molecular
334+
! boundary layer thickness if it is greater than 1 or 0 otherwise [nondim]
334335
real :: mass_exch ! A mass exchange rate [R Z T-1 ~> kg m-2 s-1]
335336
real :: Sb_min, Sb_max ! Minimum and maximum boundary salinities [S ~> ppt]
336337
real :: dS_min, dS_max ! Minimum and maximum salinity changes [S ~> ppt]
337338
! Variables used in iterating for wB_flux.
338-
real :: wB_flux_new, dDwB_dwB_in
339-
real :: I_Gam_T, I_Gam_S
339+
real :: wB_flux_next ! The next interation's guess for wB_flux [Z2 T-3 ~> m2 s-2]
340+
real :: wB_flux_new ! An updated value of wB_flux when Gam_turb is based on wB_flux [Z2 T-3 ~> m2 s-2]
341+
real :: wB_flux_max ! The upper bound on wB_flux [Z2 T-3 ~> m2 s-2]
342+
real :: wB_flux_min ! The lower bound on wB_flux [Z2 T-3 ~> m2 s-2]
343+
real :: dDwB_dwB_in ! The slope of the change in wB_flux between iterations with wB_flux [nondim]
344+
real :: DwB_max ! The change in wB_flux when it is wB_flux_max [Z2 T-3 ~> m2 s-2]
345+
real :: DwB_min ! The change in wB_flux when it is wB_flux_min [Z2 T-3 ~> m2 s-2]
346+
real :: I_Gam_T, I_Gam_S ! Terms that vary inversely with Gam_mol_T or Gam_mol_S and Gam_turb [nondim]
340347
real :: dG_dwB ! The derivative of Gam_turb with wB [T3 Z-2 ~> s3 m-2]
341348
real :: taux2, tauy2 ! The squared surface stresses [R2 L2 Z2 T-4 ~> Pa2].
342349
real :: u2_av, v2_av ! The ice-area weighted average squared ocean velocities [L2 T-2 ~> m2 s-2]
343-
real :: asu1, asu2 ! Ocean areas covered by ice shelves at neighboring u-
344-
real :: asv1, asv2 ! and v-points [L2 ~> m2].
350+
real :: asu1, asu2 ! Ocean areas covered by ice shelves at neighboring u-points [L2 ~> m2]
351+
real :: asv1, asv2 ! Ocean areas covered by ice shelves at neighboring v-points [L2 ~> m2]
345352
real :: I_au, I_av ! The Adcroft reciprocals of the ice shelf areas at adjacent points [L-2 ~> m-2]
346353
real :: Irho0 ! The inverse of the mean density times a unit conversion factor [R-1 L Z-1 ~> m3 kg-1]
347354
logical :: Sb_min_set, Sb_max_set
355+
logical :: root_found
348356
logical :: update_ice_vel ! If true, it is time to update the ice shelf velocities.
349357
logical :: coupled_GL ! If true, the grounding line position is determined based on
350358
! coupled ice-ocean dynamics.
351359

352360
real, parameter :: c2_3 = 2.0/3.0
353-
character(len=160) :: mesg ! The text of an error message
361+
character(len=320) :: mesg ! The text of an error message
354362
integer, dimension(2) :: EOSdom ! The i-computational domain for the equation of state
355363
integer :: i, j, is, ie, js, je, ied, jed, it1, it3
356364
real :: vaf0, vaf0_A, vaf0_G !The previous volumes above floatation [Z L2 ~> m3]
@@ -395,7 +403,7 @@ subroutine shelf_calc_flux(sfc_state_in, fluxes_in, Time, time_step_in, CS)
395403
ZETA_N = CS%Zeta_N
396404
VK = CS%Vk
397405
RC = CS%Rc
398-
I_ZETA_N = 1.0 / ZETA_N
406+
I_2Zeta_N = 0.5 / CS%Zeta_N
399407
I_LF = 1.0 / CS%Lat_fusion
400408
SC = CS%kv_molec/CS%kd_molec_salt
401409
PR = CS%kv_molec/CS%kd_molec_temp
@@ -556,68 +564,150 @@ subroutine shelf_calc_flux(sfc_state_in, fluxes_in, Time, time_step_in, CS)
556564
dT_ustar = (ISS%tfreeze(i,j) - sfc_state%sst(i,j)) * ustar_h
557565
dS_ustar = (Sbdry(i,j) - sfc_state%sss(i,j)) * ustar_h
558566

559-
! First, determine the buoyancy flux assuming no effects of stability
560-
! on the turbulence. Following H & J '99, this limit also applies
561-
! when the buoyancy flux is destabilizing.
562-
563-
if (CS%const_gamma) then ! if using a constant gamma_T
564-
! note the different form, here I_Gam_T is NOT 1/Gam_T!
567+
if (CS%const_gamma) then
568+
! If using a constant gamma_T, there are no effects of the buoyancy flux on the turbulence.
565569
I_Gam_T = CS%Gamma_T_3EQ
566570
I_Gam_S = CS%Gamma_S_3EQ
567-
else
568-
Gam_turb = I_VK * (ln_neut + (0.5 * I_ZETA_N - 1.0))
571+
wT_flux = dT_ustar * CS%Gamma_T_3EQ
572+
wB_flux = dB_dS * (dS_ustar * CS%Gamma_S_3EQ) + dB_dT * wT_flux
573+
else ! gamma_T and gamma_S are a function of the buoyancy flux.
574+
! Find the root where wB_flux is consistent with the values of gamma with that flux.
575+
576+
! First, determine the buoyancy flux assuming no effects of stability
577+
! on the turbulence. Following H & J '99, this limit also applies
578+
! when the buoyancy flux is destabilizing.
579+
Gam_turb = I_VK * (ln_neut + (I_2Zeta_N - 1.0))
569580
I_Gam_T = 1.0 / (Gam_mol_t + Gam_turb)
570581
I_Gam_S = 1.0 / (Gam_mol_s + Gam_turb)
571-
endif
582+
wB_flux = dB_dS * (dS_ustar * I_Gam_S) + dB_dT * (dT_ustar * I_Gam_T)
572583

573-
wT_flux = dT_ustar * I_Gam_T
574-
wB_flux = dB_dS * (dS_ustar * I_Gam_S) + dB_dT * wT_flux
584+
if (wB_flux < 0.0) then
585+
! The buoyancy flux is stabilizing and will reduce the turbulent
586+
! fluxes, and iteration is required.
575587

576-
if (wB_flux < 0.0) then
577-
! The buoyancy flux is stabilizing and will reduce the turbulent
578-
! fluxes, and iteration is required.
579-
n_star_term = (ZETA_N * hBL_neut * VK) / (RC * ustar_h**3)
580-
do it3 = 1,30
581-
! n_star <= 1.0 is the ratio of working boundary layer thickness
582-
! to the neutral thickness.
583-
! hBL = n_star*hBL_neut ; hSub = 1/8*n_star*hBL
588+
! n_star <= 1.0 is the ratio of working boundary layer thickness
589+
! to the neutral thickness.
590+
! hBL = n_star*hBL_neut ; hSub = 1/8*n_star*hBL
591+
n_star_term = (ZETA_N * hBL_neut * VK) / (RC * ustar_h**3)
584592

585593
I_n_star = sqrt(1.0 - n_star_term * wB_flux)
586-
dIns_dwB = 0.5 * n_star_term / I_n_star
587594
if (hBL_neut_h_molec > I_n_star**2) then
588-
Gam_turb = I_VK * ((ln_neut - 2.0*log(I_n_star)) + &
589-
(0.5*I_ZETA_N*I_n_star - 1.0))
590-
dG_dwB = I_VK * ( -2.0 / I_n_star + (0.5 * I_ZETA_N)) * dIns_dwB
591-
else
592-
! The layer dominated by molecular viscosity is smaller than
593-
! the assumed boundary layer. This should be rare!
594-
Gam_turb = I_VK * (0.5 * I_ZETA_N*I_n_star - 1.0)
595-
dG_dwB = I_VK * (0.5 * I_ZETA_N) * dIns_dwB
595+
Gam_turb = I_VK * ((ln_neut - 2.0*log(I_n_star)) + (I_2Zeta_N*I_n_star - 1.0))
596+
else ! The layer dominated by molecular viscosity is smaller than the boundary layer.
597+
Gam_turb = I_VK * (I_2Zeta_N*I_n_star - 1.0)
596598
endif
597-
598-
if (CS%const_gamma) then ! if using a constant gamma_T
599-
! note the different form, here I_Gam_T is NOT 1/Gam_T!
600-
I_Gam_T = CS%Gamma_T_3EQ
601-
I_Gam_S = CS%Gamma_S_3EQ
602-
else
603-
I_Gam_T = 1.0 / (Gam_mol_t + Gam_turb)
604-
I_Gam_S = 1.0 / (Gam_mol_s + Gam_turb)
599+
I_Gam_T = 1.0 / (Gam_mol_t + Gam_turb)
600+
I_Gam_S = 1.0 / (Gam_mol_s + Gam_turb)
601+
602+
if (CS%buoy_flux_itt_bugfix) then
603+
wB_flux_new = dB_dS * (dS_ustar * I_Gam_S) + dB_dT * (dT_ustar * I_Gam_T)
604+
root_found = (abs(wB_flux_new - wB_flux) < &
605+
CS%buoy_flux_itt_threshold*(abs(wB_flux_new) + abs(wB_flux)))
606+
! Do not update the flux if its maagnitude would be increased by the otherwise
607+
! stabilizing buoyancy fluxes. This can happen when the buoyancy flux
608+
! is stabilizing when one of the heat or salt fluxes are destabilizing due
609+
! to their different molecular properties.
610+
if (wB_flux_new <= wB_flux) root_found = .true.
611+
! With the line above, the root is always bracketed.
612+
else ! The bug is never to update the buoyancy flux.
613+
root_found = .true.
605614
endif
606615

607-
wT_flux = dT_ustar * I_Gam_T
608-
wB_flux_new = dB_dS * (dS_ustar * I_Gam_S) + dB_dT * wT_flux
616+
if (.not.root_found) then
617+
wB_flux_max = 0.0 ; DwB_max = wB_flux
618+
wB_flux_min = wB_flux ; DwB_min = wB_flux_new - wB_flux
619+
! The test about 10 lines above ensures that that (DwB_max <= 0) and (DwB_min >= 0),
620+
621+
if (wB_flux_min*n_star_term <= (1.0 - hBL_neut_h_molec)) then
622+
! The solution is in the limit where abs(wB_flux) is small and
623+
! Gam_turb = I_VK * ((ln_neut - 2.0*log(I_n_star)) + (I_2Zeta_N*I_n_star - 1.0))
624+
elseif (wB_flux_max*n_star_term >= (1.0 - hBL_neut_h_molec)) then
625+
! The solution is in the limit where abs(wB_flux) is large and
626+
! Gam_turb = I_VK * (I_2Zeta_N*I_n_star - 1.0)
627+
else
628+
! The derivative of Gam_turb with wB_flux has a discontinuous change within the
629+
! bracketed range of values. For a first guess, take this discontinous slope
630+
! value, as Newton's method and the false position method do not converge well
631+
! when this discontinuity is between a guess and the solution.
632+
633+
! Set wB_flux so that I_n_star**2 = hBL_neut_h_molec
634+
! I_n_star = sqrt(1.0 - n_star_term * wB_flux)
635+
wB_flux = (1.0 - hBL_neut_h_molec) / n_star_term
636+
I_n_star = sqrt(hBL_neut_h_molec)
637+
Gam_turb = I_VK * (I_2Zeta_N*I_n_star - 1.0)
638+
I_Gam_T = 1.0 / (Gam_mol_t + Gam_turb)
639+
I_Gam_S = 1.0 / (Gam_mol_s + Gam_turb)
640+
641+
wB_flux_new = dB_dS * (dS_ustar * I_Gam_S) + dB_dT * (dT_ustar * I_Gam_T)
642+
if (abs(wB_flux_new - wB_flux) <= CS%buoy_flux_itt_threshold*(abs(wB_flux_new) + abs(wB_flux))) then
643+
! The root has been found to within the tolerance at the kink. This should be very rare.
644+
root_found = .true.
645+
elseif (wB_flux_new > wB_flux) then
646+
! The solution is in the limit where abs(wB_flux) is small and
647+
! Gam_turb = I_VK * ((ln_neut - 2.0*log(I_n_star)) + (I_2Zeta_N*I_n_star - 1.0))
648+
wB_flux_min = wB_flux ; DwB_min = wB_flux_new - wB_flux
649+
else
650+
! The solution is in the limt where abs(wB_flux) is large and
651+
! Gam_turb = I_VK * (I_2Zeta_N*I_n_star - 1.0)
652+
wB_flux_max = wB_flux ; DwB_max = wB_flux_new - wB_flux
653+
endif
654+
endif
655+
endif
609656

610-
! Find the root where wB_flux_new = wB_flux.
611-
if (abs(wB_flux_new - wB_flux) < CS%buoy_flux_itt_threshold*(abs(wB_flux_new) + abs(wB_flux))) exit
657+
if (.not.root_found) then
658+
! Use the false position for the next guess.
659+
wB_flux = wB_flux_min + (wB_flux_max-wB_flux_min) * (DwB_min / (DwB_min - DwB_max))
660+
661+
do it3 = 1,30
662+
! Iterate using Newton's method with bounds or the false position method to find the root.
663+
664+
I_n_star = sqrt(1.0 - n_star_term * wB_flux)
665+
dIns_dwB = -0.5 * n_star_term / I_n_star
666+
if (hBL_neut_h_molec > I_n_star**2) then
667+
Gam_turb = I_VK * ((ln_neut - 2.0*log(I_n_star)) + (I_2Zeta_N*I_n_star - 1.0))
668+
dG_dwB = I_VK * (( -2.0 / I_n_star + I_2Zeta_N) * dIns_dwB)
669+
else
670+
! The layer dominated by molecular viscosity is smaller than the boundary layer.
671+
Gam_turb = I_VK * (I_2Zeta_N*I_n_star - 1.0)
672+
dG_dwB = I_VK * (I_2Zeta_N * dIns_dwB)
673+
endif
674+
I_Gam_T = 1.0 / (Gam_mol_t + Gam_turb)
675+
I_Gam_S = 1.0 / (Gam_mol_s + Gam_turb)
676+
wB_flux_new = dB_dS * (dS_ustar * I_Gam_S) + dB_dT * (dT_ustar * I_Gam_T)
677+
678+
! Test for convergence to within tolerance at the point where wB_flux_new = wB_flux.
679+
if (abs(wB_flux_new - wB_flux) <= CS%buoy_flux_itt_threshold*(abs(wB_flux_new) + abs(wB_flux))) &
680+
root_found = .true.
681+
if (root_found) exit
682+
683+
dDwB_dwB_in = -dG_dwB * (dB_dS * (dS_ustar * I_Gam_S**2) + &
684+
dB_dT * (dT_ustar * I_Gam_T**2)) - 1.0
685+
if ((dDwB_dwB_in >= 0.0) .or. &
686+
( wB_flux - wB_flux_new >= abs(dDwB_dwB_in)*(wB_flux_max - wB_flux)) .or. &
687+
( wB_flux - wB_flux_new <= abs(dDwB_dwB_in)*(wB_flux_min - wB_flux)) ) then
688+
! Use the False position method to determine the guess for the next iteration when
689+
! Newton's method would go out of bounds
690+
wB_flux_next = wB_flux_min + (wB_flux_max-wB_flux_min) * (DwB_min / (DwB_min - DwB_max))
691+
else
692+
! Use Newton's method for the next guess.
693+
wB_flux_next = wB_flux - (wB_flux_new - wB_flux) / dDwB_dwB_in
694+
endif
695+
696+
! Reset one of the bounds inward.
697+
if (wB_flux_new - wB_flux > 0) then
698+
wB_flux_min = wB_flux ; DwB_min = wB_flux_new - wB_flux
699+
else
700+
wB_flux_max = wB_flux ; DwB_max = wB_flux_new - wB_flux
701+
endif
702+
703+
! Update wB_flux
704+
wB_flux = wB_flux_next
705+
enddo ! it3
706+
endif
612707

613-
dDwB_dwB_in = dG_dwB * (dB_dS * (dS_ustar * I_Gam_S**2) + &
614-
dB_dT * (dT_ustar * I_Gam_T**2)) - 1.0
615-
! This is Newton's method without any bounds. Should bounds be needed?
616-
wB_flux_new = wB_flux - (wB_flux_new - wB_flux) / dDwB_dwB_in
617-
! Update wB_flux
618-
if (CS%buoy_flux_itt_bug) wB_flux = wB_flux_new
619-
enddo !it3
620-
endif
708+
endif ! End of test for first guess of wB_flux < 0.
709+
wT_flux = dT_ustar * I_Gam_T
710+
endif ! End of test for CS%const_gamma
621711

622712
ISS%tflux_ocn(i,j) = RhoCp * wT_flux
623713
exch_vel_t(i,j) = ustar_h * I_Gam_T
@@ -688,7 +778,7 @@ subroutine shelf_calc_flux(sfc_state_in, fluxes_in, Time, time_step_in, CS)
688778
Sbdry(i,j) = Sbdry_it
689779
endif ! Sb_min_set
690780

691-
if (.not.CS%salt_flux_itt_bug) Sbdry(i,j) = Sbdry_it
781+
if (.not.CS%salt_flux_itt_bugfix) Sbdry(i,j) = Sbdry_it
692782

693783
endif ! CS%find_salt_root
694784

@@ -1686,10 +1776,10 @@ subroutine initialize_ice_shelf(param_file, ocn_grid, Time, CS, diag, Time_init,
16861776
call get_param(param_file, mdl, "ICE_SHELF_RC", CS%Rc, &
16871777
"Critical flux Richardson number for ice melt ", &
16881778
units="nondim", default=0.20)
1689-
call get_param(param_file, mdl, "ICE_SHELF_BUOYANCY_FLUX_ITT_BUG", CS%buoy_flux_itt_bug, &
1690-
"Bug fix of buoyancy iteration", default=.true.)
1691-
call get_param(param_file, mdl, "ICE_SHELF_SALT_FLUX_ITT_BUG", CS%salt_flux_itt_bug, &
1692-
"Bug fix of salt iteration", default=.true.)
1779+
call get_param(param_file, mdl, "ICE_SHELF_BUOYANCY_FLUX_ITT_BUGFIX", CS%buoy_flux_itt_bugfix, &
1780+
"Bug fix of buoyancy iteration", default=.true., old_name="ICE_SHELF_BUOYANCY_FLUX_ITT_BUG")
1781+
call get_param(param_file, mdl, "ICE_SHELF_SALT_FLUX_ITT_BUGFIX", CS%salt_flux_itt_bugfix, &
1782+
"Bug fix of salt iteration", default=.true., old_name="ICE_SHELF_SALT_FLUX_ITT_BUG")
16931783
call get_param(param_file, mdl, "ICE_SHELF_BUOYANCY_FLUX_ITT_THRESHOLD", CS%buoy_flux_itt_threshold, &
16941784
"Convergence criterion of Newton's method for ice shelf "//&
16951785
"buoyancy iteration.", units="nondim", default=1.0e-4)

src/ice_shelf/MOM_ice_shelf_dynamics.F90

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2540,7 +2540,7 @@ subroutine calc_shelf_driving_stress(CS, ISS, G, US, taudx, taudy, OD)
25402540
endif
25412541

25422542
if (CS%max_surface_slope>0) then
2543-
scale = min(CS%max_surface_slope/sqrt((sx**2)+(sy**2)),1.0)
2543+
scale = CS%max_surface_slope / max( sqrt((sx**2) + (sy**2)), CS%max_surface_slope )
25442544
sx = scale*sx; sy = scale*sy
25452545
endif
25462546

0 commit comments

Comments
 (0)