MODULE stopping_crit !----------------------------------------------------------------------- ! NAME ! stopping_crit ! ! DESCRIPTION ! Stopping criteria for PEM. ! ! AUTHORS & DATE ! JB Clement, 2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DEPENDENCIES ! ------------ use numerics, only: dp, qp, di, k4, minieps_qp ! DECLARATION ! ----------- implicit none ! PARAMETERS ! ---------- real(dp), protected :: h2oice_crit ! Change of the surface of H2O ice sublimating before stopping the PEM real(dp), protected :: co2ice_crit ! Change of the surface of CO2 ice sublimating before stopping the PEM real(dp), protected :: ps_crit ! Change of averaged surface pressure before stopping the PEM ! VARIABLES ! --------- type :: stopFlags logical(k4) :: surface_h2oice_change = .false. logical(k4) :: zero_dh2oice = .false. logical(k4) :: surface_co2ice_change = .false. logical(k4) :: pressure_change = .false. logical(k4) :: nmax_yr_run_reached = .false. logical(k4) :: nmax_yr_runorb_reached = .false. logical(k4) :: nmax_yr_sim_reached = .false. logical(k4) :: time_limit_reached = .false. contains procedure :: is_any_set procedure :: stop_code procedure :: stop_message end type contains !+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ !======================================================================= SUBROUTINE set_stopping_crit_config(h2oice_crit_in,co2ice_crit_in,ps_crit_in) !----------------------------------------------------------------------- ! NAME ! set_stopping_crit_config ! ! DESCRIPTION ! Setter for 'stopping_crit' configuration parameters. ! ! AUTHORS & DATE ! JB Clement, 02/2026 ! ! NOTES ! !----------------------------------------------------------------------- ! DEPENDENCIES ! ------------ use utility, only: real2str use display, only: print_msg ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- real(dp), intent(in) :: h2oice_crit_in, co2ice_crit_in, ps_crit_in ! CODE ! ---- h2oice_crit = h2oice_crit_in co2ice_crit = co2ice_crit_in ps_crit = ps_crit_in call print_msg('h2oice_crit = '//real2str(h2oice_crit)) call print_msg('co2ice_crit = '//real2str(co2ice_crit)) call print_msg('ps_crit = '//real2str(ps_crit)) END SUBROUTINE set_stopping_crit_config !======================================================================= !======================================================================= FUNCTION is_any_set(this) RESULT(stopPEM) !----------------------------------------------------------------------- ! NAME ! is_any_set ! ! DESCRIPTION ! Detect if any stopping flag is set to true. ! ! AUTHORS & DATE ! JB Clement, 2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- class(stopFlags), intent(in) :: this ! Stopping flags object logical(k4) :: stopPEM ! True if any flag is set ! CODE ! ---- stopPEM = this%surface_h2oice_change .or. & this%zero_dh2oice .or. & this%surface_co2ice_change .or. & this%pressure_change .or. & this%nmax_yr_run_reached .or. & this%nmax_yr_runorb_reached .or. & this%nmax_yr_sim_reached .or. & this%time_limit_reached END FUNCTION is_any_set !======================================================================= !======================================================================= FUNCTION stop_code(this) result(code) !----------------------------------------------------------------------- ! NAME ! stop_code ! ! DESCRIPTION ! Return digit code corresponding to the active stopping flag. ! ! AUTHORS & DATE ! JB Clement, 2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- class(StopFlags), intent(in) :: this ! Stopping flags object integer(di) :: code ! Code corresponding to active flag (0 if none) ! CODE ! ---- code = 0 if (this%surface_h2oice_change) then; code = 1 elseif (this%zero_dh2oice) then; code = 2 elseif (this%surface_co2ice_change) then; code = 3 elseif (this%pressure_change) then; code = 4 elseif (this%nmax_yr_run_reached) then; code = 5 elseif (this%nmax_yr_runorb_reached) then; code = 6 elseif (this%nmax_yr_sim_reached) then; code = 7 elseif (this%time_limit_reached) then; code = 8 end if END FUNCTION stop_code !======================================================================= !======================================================================= FUNCTION stop_message(this) result(msg) !----------------------------------------------------------------------- ! NAME ! stop_message ! ! DESCRIPTION ! Return descriptive message corresponding to the active stopping flag. ! ! AUTHORS & DATE ! JB Clement, 2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- class(StopFlags), intent(in) :: this ! Stopping flags object character(:), allocatable :: msg ! Descriptive message for active flag ! CODE ! ---- if (this%surface_h2oice_change) then; msg = "**** STOPPING because surface of H2O ice has changed too much (see message above)." elseif (this%zero_dh2oice) then; msg = "**** STOPPING because tendencies on H2O ice = 0 (see message above)." elseif (this%surface_co2ice_change) then; msg = "**** STOPPING because surface of CO2 ice has changed too much (see message above)." elseif (this%pressure_change) then; msg = "**** STOPPING because surface global pressure has changed too much (see message above)." elseif (this%nmax_yr_run_reached) then; msg = "**** STOPPING because maximum number of years is reached." elseif (this%nmax_yr_runorb_reached) then; msg = "**** STOPPING because maximum number of years due to orbital parameters is reached." elseif (this%nmax_yr_sim_reached) then; msg = "**** STOPPING because the number of years to be simulated is reached." elseif (this%time_limit_reached) then; msg = "**** STOPPING because the job time limit is going to be reached." end if END FUNCTION stop_message !======================================================================= !======================================================================= SUBROUTINE stopping_crit_h2oice(h2oice_sublim_coverage_ini,h2o_ice,d_h2oice,stopcrit) !----------------------------------------------------------------------- ! NAME ! stopping_crit_h2oice ! ! DESCRIPTION ! Check if H2O ice surface has changed too much. ! ! AUTHORS & DATE ! R. Vandemeulebrouck ! L. Lange, 2023 ! JB Clement, 2023-2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DEPENDENCIES ! ------------ use slopes, only: subslope_dist, def_slope_mean use geometry, only: ngrid, nslope, cell_area use maths, only: pi use display, only: print_msg use utility, only: real2str ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- real(dp), dimension(:,:), intent(in) :: h2o_ice, d_h2oice real(dp), intent(in) :: h2oice_sublim_coverage_ini type(stopFlags), intent(inout) :: stopcrit ! LOCAL VARIABLES ! --------------- integer(di) :: i, islope real(dp) :: h2oice_now_surf ! CODE ! ---- ! Check to escape the subroutine (if the PEM should stop already) if (stopcrit%stop_code() > 0) return ! Computation of the present surface of sublimating H2O ice h2oice_now_surf = 0._dp do i = 1,ngrid do islope = 1,nslope if (h2o_ice(i,islope) > 0._dp .and. d_h2oice(i,islope) < 0._dp) h2oice_now_surf = h2oice_now_surf + cell_area(i)*subslope_dist(i,islope)/cos(pi*def_slope_mean(islope)/180._dp) end do end do ! Check of the criterion if (h2oice_now_surf < h2oice_sublim_coverage_ini*(1._dp - h2oice_crit) .or. h2oice_now_surf > h2oice_sublim_coverage_ini*(1. + h2oice_crit)) then stopcrit%surface_h2oice_change = .true. call print_msg("Surface of H2O ice sublimating (ini|now) [m2] = "//real2str(h2oice_sublim_coverage_ini)//' | '//real2str(h2oice_now_surf)) call print_msg("Change (accepted|current) [%] = +/- "//real2str(h2oice_crit*100._dp)//' | '//real2str(100._dp*(h2oice_now_surf - h2oice_sublim_coverage_ini)/h2oice_sublim_coverage_ini)) end if END SUBROUTINE stopping_crit_h2oice !======================================================================= !======================================================================= SUBROUTINE stopping_crit_co2ice(co2ice_sublim_coverage_ini,co2_ice,d_co2ice,stopcrit) !----------------------------------------------------------------------- ! NAME ! stopping_crit_co2ice ! ! DESCRIPTION ! Check if CO2 ice surface has changed too much. ! ! AUTHORS & DATE ! R. Vandemeulebrouck ! L. Lange, 2023 ! JB Clement, 2023-2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DEPENDENCIES ! ------------ use slopes, only: subslope_dist, def_slope_mean use geometry, only: ngrid, nslope, cell_area use maths, only: pi use display, only: print_msg use utility, only: real2str ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- real(dp), dimension(:,:), intent(in) :: co2_ice, d_co2ice real(dp), intent(in) :: co2ice_sublim_coverage_ini type(stopFlags), intent(inout) :: stopcrit ! LOCAL VARIABLES ! --------------- integer(di) :: i, islope real(dp) :: co2ice_now_surf ! Current surface of CO2 ice ! CODE ! ---- ! Check to escape the subroutine (not relevant in 1D or if the PEM should stop already) if (ngrid == 1 .or. stopcrit%stop_code() > 0) return ! Computation of the present surface of CO2 ice still sublimating co2ice_now_surf = 0. do i = 1,ngrid do islope = 1,nslope if (co2_ice(i,islope) > 0._dp .and. d_co2ice(i,islope) < 0._dp) co2ice_now_surf = co2ice_now_surf + cell_area(i)*subslope_dist(i,islope)/cos(pi*def_slope_mean(islope)/180._dp) end do end do ! Check of the criterion if (co2ice_now_surf < co2ice_sublim_coverage_ini*(1. - co2ice_crit) .or. co2ice_now_surf > co2ice_sublim_coverage_ini*(1._dp + co2ice_crit)) then stopcrit%surface_co2ice_change = .true. call print_msg("Initial surface of CO2 ice sublimating (ini|now) [m2] = "//real2str(co2ice_sublim_coverage_ini)//' | '//real2str(co2ice_now_surf)) call print_msg("Change (accepted|current) [%] = +/- "//real2str(co2ice_crit*100._dp)//' | '//real2str(100._dp*(co2ice_now_surf - co2ice_sublim_coverage_ini)/co2ice_sublim_coverage_ini)) end if END SUBROUTINE stopping_crit_co2ice !======================================================================= !======================================================================= SUBROUTINE stopping_crit_pressure(ps_avg_glob_ini,ps_avg_global,stopcrit) !----------------------------------------------------------------------- ! NAME ! stopping_crit_pressure ! ! DESCRIPTION ! Check if pressure has changed too much. ! ! AUTHORS & DATE ! R. Vandemeulebrouck ! L. Lange, 2023 ! JB Clement, 2023-2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DEPENDENCIES ! ------------ use display, only: print_msg use utility, only: real2str ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- real(dp), intent(in) :: ps_avg_glob_ini, ps_avg_global type(stopFlags), intent(inout) :: stopcrit ! CODE ! ---- ! Check to escape the subroutine (not relevant if the PEM should stop already) if (stopcrit%stop_code() > 0) return if (ps_avg_global < ps_avg_glob_ini*(1._dp - ps_crit) .or. ps_avg_global > ps_avg_glob_ini*(1._dp + ps_crit)) then stopcrit%pressure_change = .true. call print_msg("Initial global pressure (ini|now) [Pa] = "//real2str(ps_avg_glob_ini)//' | '//real2str(ps_avg_global)) call print_msg("Change (accepted|current) [%] = +/- "//real2str(ps_crit*100._dp)//' | '//real2str(100._dp*(ps_avg_global - ps_avg_glob_ini)/ps_avg_glob_ini)) end if END SUBROUTINE stopping_crit_pressure !======================================================================= !======================================================================= SUBROUTINE stopping_crit_h2o(delta_h2o_ads,delta_icetable,h2o_ice,d_h2oice,S_atm_2_h2o,S_h2o_2_atm,S_atm_2_h2oice,S_h2oice_2_atm,stopcrit) !----------------------------------------------------------------------- ! NAME ! stopping_crit_h2o ! ! DESCRIPTION ! Check if PEM oscillates infinitely with H2O (no net exchange). ! ! AUTHORS & DATE ! L. Lange, 2024 ! JB Clement, 2025 ! ! NOTES ! !----------------------------------------------------------------------- ! DEPENDENCIES ! ------------ use geometry, only: ngrid, nslope, cell_area use evolution, only: dt use slopes, only: subslope_dist, def_slope_mean use maths, only: pi use display, only: print_msg use utility, only: real2str ! DECLARATION ! ----------- implicit none ! ARGUMENTS ! --------- real(dp), dimension(:), intent(in) :: delta_h2o_ads ! Mass of H2O adsorbed/desorbded in the soil (kg/m^2) real(dp), dimension(:), intent(in) :: delta_icetable ! Mass of H2O that condensed/sublimated at the ice table real(dp), dimension(:,:), intent(in) :: h2o_ice, d_h2oice real(qp), intent(out) :: S_atm_2_h2o, S_h2o_2_atm, S_atm_2_h2oice, S_h2oice_2_atm type(stopFlags), intent(inout) :: stopcrit ! LOCAL VARIABLES ! --------------- integer(di) :: i, islope ! CODE ! ---- ! Check to escape the subroutine (not relevant in 1D or if the PEM should stop already) if (ngrid == 1 .or. stopcrit%stop_code() > 0) then S_atm_2_h2o = 1._qp S_h2o_2_atm = S_atm_2_h2o S_atm_2_h2oice = 1._qp S_h2oice_2_atm = S_atm_2_h2oice return end if ! We compute the amount of water going out from the atmosphere (S_atm_2_h2o) and going into the atmophere (S_h2o_2_atm) S_atm_2_h2o = 0._qp S_h2o_2_atm = 0._qp S_atm_2_h2oice = 0._qp S_h2oice_2_atm = 0._qp do i = 1,ngrid if (delta_h2o_ads(i) > 0._dp) then S_atm_2_h2o = S_atm_2_h2o + delta_h2o_ads(i)*cell_area(i) else S_h2o_2_atm = S_h2o_2_atm + delta_h2o_ads(i)*cell_area(i) end if if (delta_icetable(i) > 0._dp) then S_atm_2_h2o = S_atm_2_h2o + delta_icetable(i)*cell_area(i) else S_h2o_2_atm = S_h2o_2_atm + delta_icetable(i)*cell_area(i) end if do islope = 1,nslope if (d_h2oice(i,islope) > 0._dp) then S_atm_2_h2o = S_atm_2_h2o + d_h2oice(i,islope)*dt*cell_area(i)*subslope_dist(i,islope)/cos(def_slope_mean(islope)*pi/180._dp) S_atm_2_h2oice = S_atm_2_h2oice + d_h2oice(i,islope)*dt*cell_area(i)*subslope_dist(i,islope)/cos(def_slope_mean(islope)*pi/180._dp) else if (d_h2oice(i,islope) < 0._dp .and. h2o_ice(i,islope) > 0._dp) then S_h2o_2_atm = S_h2o_2_atm - d_h2oice(i,islope)*dt*cell_area(i)*subslope_dist(i,islope)/cos(def_slope_mean(islope)*pi/180._dp) S_h2oice_2_atm = S_h2oice_2_atm - d_h2oice(i,islope)*dt*cell_area(i)*subslope_dist(i,islope)/cos(def_slope_mean(islope)*pi/180._dp) end if end do end do ! Since relative atmospheric water is kept constant, we need to equate condensing reservoirs to the sublimating ones. ! It is not possible if one of them is missing. if ((abs(S_atm_2_h2oice) < minieps_qp .or. abs(S_h2oice_2_atm) < minieps_qp)) then call print_msg("There is no sublimating or condensing H2O ice! This can be due to the absence of H2O ice in the PCM run.") call print_msg("Amount of condensing ice [kg] = "//real2str(S_atm_2_h2oice)) call print_msg("Amount of sublimating ice [kg] = "//real2str(S_h2oice_2_atm)) stopcrit%zero_dh2oice = .true. end if END SUBROUTINE stopping_crit_h2o !======================================================================= END MODULE stopping_crit