diff --git a/oggm/core/dynamic_spinup.py b/oggm/core/dynamic_spinup.py index bcdf21dd8..b6d9262a6 100644 --- a/oggm/core/dynamic_spinup.py +++ b/oggm/core/dynamic_spinup.py @@ -2626,8 +2626,7 @@ def get_mismatch(melt_f): # step away from the limits until we are at the initial guess or we # found an error free run tmp_mismatch = None - while ((current_min_error | current_max_error | iteration == 0) & - (iteration < max_iterations)): + while tmp_mismatch is None and iteration < max_iterations: try: tmp_mismatch = fct_to_minimise(melt_f) except RuntimeError as e: @@ -2686,7 +2685,7 @@ def get_mismatch(melt_f): iteration += 1 - if iteration >= max_iterations: + if tmp_mismatch is None: # ok we were not able to find an mismatch without error if current_min_error: raise RuntimeError('Not able to find new lower limit for ' diff --git a/oggm/tests/test_models.py b/oggm/tests/test_models.py index 6afeffe59..0d7178107 100644 --- a/oggm/tests/test_models.py +++ b/oggm/tests/test_models.py @@ -50,6 +50,7 @@ dynamic_melt_f_run_with_dynamic_spinup_fallback, dynamic_melt_f_run, dynamic_melt_f_run_fallback, + define_new_melt_f_in_gdir, _get_spinup_periods_to_run) FluxBasedModel = partial(FluxBasedModel, inplace=True) @@ -7285,6 +7286,62 @@ def reset_melt_f(): run_function=dynamic_melt_f_run, fallback_function=dynamic_melt_f_run_fallback) + def test_run_dynamic_melt_f_calibration_recovers_from_error_at_limit( + self, hef_gdir): + # if run_function fails with melt_f at a limit, calibration + # should retry with a different limit instead of falling back. + gdir = hef_gdir + melt_f_initial = gdir.settings['melt_f'] + ref_mb = -500. + ref_mb_err = 100. + + calls = [] + + def fake_run_function(gdir, melt_f=None, set_local_variables=False, + **kwargs): + if set_local_variables: + return + define_new_melt_f_in_gdir(gdir, melt_f) + calls.append(melt_f) + if len(calls) == 1: + # large negative mismatch pushes the second guess above the + # upper melt_f limit + return None, ref_mb - 10 * ref_mb_err + elif len(calls) == 2: + # fail while melt_f sits at the upper limit + raise RuntimeError('planned error at melt_f limit') + return None, ref_mb + + def fake_fallback_function(*args, **kwargs): + raise AssertionError('fallback_function must not be called') + + try: + run_dynamic_melt_f_calibration( + gdir, observations_filesuffix='_recovery_test', + ref_mb=ref_mb, ref_mb_err=ref_mb_err, + ref_mb_period='1980-01-01_2000-01-01', + ys=1980, ye=2000, + melt_f_min=1, melt_f_max=1000 * 12 / 365, + melt_f_max_step_length_minimum=1., + run_function=fake_run_function, + fallback_function=fake_fallback_function, + output_filesuffix='_dyn_melt_f_recovery') + + # first guess, failing run at the upper limit, successful retry + assert len(calls) == 3 + assert np.isclose(calls[0], melt_f_initial) + # second guess was clipped to the upper limit + # (melt_f_max_step_length is pinned to 1 in this setup) + assert np.isclose(calls[1], melt_f_initial + 1.) + # retry failing threshold in steps of melt_f_max_step_length / 10 + assert np.isclose(calls[2], + np.round(melt_f_initial + 1. - 0.1, decimals=1)) + assert gdir.settings['used_spinup_option'] == \ + 'dynamic melt_f calibration (full success)' + finally: + # restore for other tests sharing the fixture + gdir.settings['melt_f'] = melt_f_initial + @pytest.mark.usefixtures('with_class_wd') @pytest.mark.test_env("models_dynamics")