Repository: Parallel-in-Time/pySDC

Issue:
Default methods in convergence controller
It looks like the default methods of the `AdaptivityResidual` class are [never called](https://parallel-in-time.org/pySDC/coverage/d_c28af68f8f845bbd_adaptivity_py.html#t290). The same for the [`BasicRestartingMPI`](https://parallel-in-time.org/pySDC/coverage/d_c28af68f8f845bbd_basic_restarting_py.html#t232) class.  Would it make sense to make this an abstract class? Or add a test for the default case?

Discussion:

Patch:
diff --git a/pySDC/implementations/convergence_controller_classes/adaptivity.py b/pySDC/implementations/convergence_controller_classes/adaptivity.py
index b26fec527..06fc27f07 100644
--- a/pySDC/implementations/convergence_controller_classes/adaptivity.py
+++ b/pySDC/implementations/convergence_controller_classes/adaptivity.py
@@ -325,6 +325,7 @@ class AdaptivityResidual(AdaptivityBase):
     rule to update the step size. Instead of giving a local tolerance that we try to hit as closely as possible, we set
     two thresholds for the residual. When we exceed the upper one, we reduce the step size by a factor of 2 and if the
     residual falls below the lower threshold, we double the step size.
+    Please setup these parameters as "e_tol" and "e_tol_low".
     """
 
     def setup(self, controller, params, description, **kwargs):
@@ -349,7 +350,7 @@ class AdaptivityResidual(AdaptivityBase):
             "control_order": -45,
             "e_tol_low": 0,
             "e_tol": np.inf,
-            "max_restarts": 2 if "e_tol_low" in params else None,
+            "max_restarts": 99 if "e_tol_low" in params else None,
         }
         return {**defaults, **params}
 
diff --git a/pySDC/projects/Resilience/piline.py b/pySDC/projects/Resilience/piline.py
index 5ad160dc3..0d301cab5 100644
--- a/pySDC/projects/Resilience/piline.py
+++ b/pySDC/projects/Resilience/piline.py
@@ -128,8 +128,9 @@ def get_data(stats, recomputed=False):
         'v1': np.array([me[1][0] for me in get_sorted(stats, type='u', recomputed=recomputed)]),
         'v2': np.array([me[1][1] for me in get_sorted(stats, type='u', recomputed=recomputed)]),
         'p3': np.array([me[1][2] for me in get_sorted(stats, type='u', recomputed=recomputed)]),
-        't': np.array(get_sorted(stats, type='u', recomputed=recomputed))[:, 0],
-        'dt': np.array(get_sorted(stats, type='dt', recomputed=recomputed)),
+        't': np.array([me[0] for me in get_sorted(stats, type='u', recomputed=recomputed)]),
+        'dt': np.array([me[1] for me in get_sorted(stats, type='dt', recomputed=recomputed)]),
+        't_dt': np.array([me[0] for me in get_sorted(stats, type='dt', recomputed=recomputed)]),
         'e_em': np.array(get_sorted(stats, type='e_embedded', recomputed=recomputed))[:, 1],
         'e_ex': np.array(get_sorted(stats, type='e_extrapolated', recomputed=recomputed))[:, 1],
         'restarts': np.array(get_sorted(stats, type='restart', recomputed=None))[:, 1],
@@ -154,7 +155,7 @@ def plot_error(data, ax, use_adaptivity=True, plot_restarts=False):
         None
     """
     setup_mpl_from_accuracy_check()
-    ax.plot(data['dt'][:, 0], data['dt'][:, 1], color='black')
+    ax.plot(data['t_dt'], data['dt'], color='black')
 
     e_ax = ax.twinx()
     e_ax.plot(data['t'], data['e_em'], label=r'$\epsilon_\mathrm{embedded}$')
@@ -286,7 +287,7 @@ def check_solution(data, use_adaptivity, num_procs, generate_reference=False):
         'p3': data['p3'][-1],
         'e_em': data['e_em'][-1],
         'e_ex': data['e_ex'][data['e_ex'] != [None]][-1],
-        'dt': data['dt'][-1][1],
+        'dt': data['dt'][-1],
         'restarts': data['restarts'].sum(),
         'sweeps': data['sweeps'].sum(),
         't': data['t'][-1],
@@ -309,6 +310,37 @@ def check_solution(data, use_adaptivity, num_procs, generate_reference=False):
         ), f'{error_msg} Expected {k}={expected[k]:.4e}, got {k}={got[k]:.4e}'
 
 
+def residual_adaptivity(plot=False):
+    """
+    Make a run with adaptivity based on the residual.
+    """
+    from pySDC.implementations.convergence_controller_classes.adaptivity import AdaptivityResidual
+
+    max_res = 1e-8
+    custom_description = {'convergence_controllers': {}}
+    custom_description['convergence_controllers'][AdaptivityResidual] = {
+        'e_tol': max_res,
+        'e_tol_low': max_res / 10,
+    }
+    stats, _, _ = run_piline(custom_description, num_procs=1)
+
+    residual = get_sorted(stats, type='residual_post_step', recomputed=False)
+    dt = get_sorted(stats, type='dt', recomputed=False)
+
+    if plot:
+        fig, ax = plt.subplots()
+        dt_ax = ax.twinx()
+
+        ax.plot([me[0] for me in residual], [me[1] for me in residual])
+        dt_ax.plot([me[0] for me in dt], [me[1] for me in dt], color='black')
+        plt.show()
+
+    max_residual = max([me[1] for me in residual])
+    assert max_residual < max_res, f'Max. allowed residual is {max_res:.2e}, but got {max_residual:.2e}!'
+    dt_std = np.std([me[1] for me in dt])
+    assert dt_std != 0, f'Expected the step size to change, but standard deviation is {dt_std:.2e}!'
+
+
 def main():
     """
     Make a variety of tests to see if Hot Rod and Adaptivity work in serial as well as MSSDC.
@@ -342,4 +374,5 @@ def main():
 
 
 if __name__ == "__main__":
+    residual_adaptivity()
     main()


Test patch:
diff --git a/pySDC/tests/test_projects/test_resilience/test_piline.py b/pySDC/tests/test_projects/test_resilience/test_piline.py
index 62afc601f..5a2896de0 100644
--- a/pySDC/tests/test_projects/test_resilience/test_piline.py
+++ b/pySDC/tests/test_projects/test_resilience/test_piline.py
@@ -6,3 +6,10 @@ def test_main():
     from pySDC.projects.Resilience.piline import main
 
     main()
+
+
+@pytest.mark.base
+def test_residual_adaptivity():
+    from pySDC.projects.Resilience.piline import residual_adaptivity
+
+    residual_adaptivity()