diff --git a/pySDC/implementations/convergence_controller_classes/adaptivity.py b/pySDC/implementations/convergence_controller_classes/adaptivity.py index 6fc0cc6528..b2ec90e5c7 100644 --- a/pySDC/implementations/convergence_controller_classes/adaptivity.py +++ b/pySDC/implementations/convergence_controller_classes/adaptivity.py @@ -52,7 +52,7 @@ def dependencies(self, controller, description, **kwargs): Returns: None """ - step_limiter_keys = ['dt_min', 'dt_max', 'dt_slope_min', 'dt_slope_max'] + step_limiter_keys = ['dt_min', 'dt_max', 'dt_slope_min', 'dt_slope_max', 'dt_rel_min_slope'] available_keys = [me for me in step_limiter_keys if me in self.params.__dict__.keys()] if len(available_keys) > 0: diff --git a/pySDC/implementations/convergence_controller_classes/step_size_limiter.py b/pySDC/implementations/convergence_controller_classes/step_size_limiter.py index b5101c60e7..cb090eebd3 100644 --- a/pySDC/implementations/convergence_controller_classes/step_size_limiter.py +++ b/pySDC/implementations/convergence_controller_classes/step_size_limiter.py @@ -39,7 +39,7 @@ def dependencies(self, controller, description, **kwargs): Returns: None """ - slope_limiter_keys = ['dt_slope_min', 'dt_slope_max'] + slope_limiter_keys = ['dt_slope_min', 'dt_slope_max', 'dt_rel_min_slope'] available_keys = [me for me in slope_limiter_keys if me in self.params.__dict__.keys()] if len(available_keys) > 0: @@ -90,7 +90,9 @@ class StepSizeSlopeLimiter(ConvergenceController): """ Class to set limits to adaptive step size computation during run time - Please supply dt_min or dt_max in the params to limit in either direction + Please supply `dt_slope_min` or `dt_slope_max` in the params to limit in either direction. + You can also supply `dt_rel_min_slope` in order to keep the old step size in case the relative change is smaller + than this minimum. """ def setup(self, controller, params, description, **kwargs): @@ -109,6 +111,7 @@ def setup(self, controller, params, description, **kwargs): "control_order": 91, "dt_slope_min": 0, "dt_slope_max": np.inf, + "dt_rel_min_slope": 0, } return {**defaults, **super().setup(controller, params, description, **kwargs)} @@ -143,5 +146,11 @@ def get_new_step_size(self, controller, S, **kwargs): S, ) L.status.dt_new = dt_new + elif abs(L.status.dt_new / L.params.dt - 1) < self.params.dt_rel_min_slope: + L.status.dt_new = L.params.dt + self.log( + f"Step size did not change sufficiently to warrant step size change, keeping {L.status.dt_new:.2e}", + S, + ) return None diff --git a/pySDC/tests/test_convergence_controllers/test_adaptivity.py b/pySDC/tests/test_convergence_controllers/test_adaptivity.py index 0fd347cfc3..10b478c667 100644 --- a/pySDC/tests/test_convergence_controllers/test_adaptivity.py +++ b/pySDC/tests/test_convergence_controllers/test_adaptivity.py @@ -13,7 +13,6 @@ def get_controller(dt, num_nodes, useMPI, adaptivity, adaptivity_params, **kwarg adaptivity_params (dict): Parameters for convergence controller Returns: - (dict): Stats object generated during the run (pySDC.Controller.controller): Controller used in the run """ from pySDC.implementations.problem_classes.polynomial_test_problem import polynomial_testequation diff --git a/pySDC/tests/test_convergence_controllers/test_step_size_limiter.py b/pySDC/tests/test_convergence_controllers/test_step_size_limiter.py new file mode 100644 index 0000000000..3c988976aa --- /dev/null +++ b/pySDC/tests/test_convergence_controllers/test_step_size_limiter.py @@ -0,0 +1,112 @@ +import pytest + + +def get_controller(step_size_limier_params): + """ + Runs a single advection problem with certain parameters + + Args: + step_size_limier_params (dict): Parameters for convergence controller + + Returns: + (pySDC.Controller.controller): Controller used in the run + """ + from pySDC.implementations.problem_classes.polynomial_test_problem import polynomial_testequation + from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI + from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit + from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeLimiter + + level_params = {} + level_params['dt'] = 1.0 + level_params['restol'] = 1.0 + + sweeper_params = {} + sweeper_params['quad_type'] = 'GAUSS' + sweeper_params['num_nodes'] = 1 + sweeper_params['do_coll_update'] = True + + problem_params = {'degree': 10} + + step_params = {} + step_params['maxiter'] = 0 + + controller_params = {} + controller_params['logger_level'] = 30 + + description = {} + description['problem_class'] = polynomial_testequation + description['problem_params'] = problem_params + description['sweeper_class'] = generic_implicit + description['sweeper_params'] = sweeper_params + description['level_params'] = level_params + description['step_params'] = step_params + description['convergence_controllers'] = {StepSizeLimiter: step_size_limier_params} + + controller = controller_nonMPI(num_procs=1, controller_params=controller_params, description=description) + + controller.add_convergence_controller(StepSizeLimiter, description, step_size_limier_params) + + return controller + + +@pytest.mark.base +def test_step_size_slope_limiter(): + from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeSlopeLimiter + + params = {'dt_slope_max': 2, 'dt_slope_min': 1e-3, 'dt_rel_min_slope': 1e-1} + controller = get_controller(params) + + limiter = controller.convergence_controllers[ + [type(me) for me in controller.convergence_controllers].index(StepSizeSlopeLimiter) + ] + + S = controller.MS[0] + S.status.slot = 0 + L = S.levels[0] + L.status.time = 0 + + L.params.dt = 1 + L.status.dt_new = 3 + limiter.get_new_step_size(controller, S) + assert L.status.dt_new == 2 + + L.params.dt = 1 + L.status.dt_new = 0 + limiter.get_new_step_size(controller, S) + assert L.status.dt_new == 1e-3 + + L.params.dt = 1 + L.status.dt_new = 1 + 1e-3 + limiter.get_new_step_size(controller, S) + assert L.status.dt_new == 1 + + +@pytest.mark.base +def test_step_size_limiter(): + from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeLimiter + + params = {'dt_max': 2, 'dt_min': 0.5} + controller = get_controller(params) + + limiter = controller.convergence_controllers[ + [type(me) for me in controller.convergence_controllers].index(StepSizeLimiter) + ] + + S = controller.MS[0] + S.status.slot = 0 + L = S.levels[0] + L.status.time = 0 + + L.params.dt = 1 + L.status.dt_new = 3 + limiter.get_new_step_size(controller, S) + assert L.status.dt_new == 2 + + L.params.dt = 1 + L.status.dt_new = 0 + limiter.get_new_step_size(controller, S) + assert L.status.dt_new == 0.5 + + +if __name__ == '__main__': + test_step_size_slope_limiter()