diff --git a/pySDC/projects/Resilience/GS.py b/pySDC/projects/Resilience/GS.py new file mode 100644 index 0000000000000000000000000000000000000000..5fdc47d5414656ea06dc7d06b0d44c13bc2045db --- /dev/null +++ b/pySDC/projects/Resilience/GS.py @@ -0,0 +1,176 @@ +# script to run a Gray-Scott problem +from pySDC.implementations.problem_classes.GrayScott_MPIFFT import grayscott_imex_diffusion +from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI +from pySDC.projects.Resilience.hook import hook_collection, LogData +from pySDC.projects.Resilience.strategies import merge_descriptions +from pySDC.projects.Resilience.sweepers import imex_1st_order_efficient +from pySDC.core.convergence_controller import ConvergenceController +from pySDC.implementations.convergence_controller_classes.estimate_extrapolation_error import ( + EstimateExtrapolationErrorNonMPI, +) +from pySDC.implementations.convergence_controller_classes.check_convergence import CheckConvergence +from pySDC.projects.Resilience.reachTendExactly import ReachTendExactly + +from pySDC.core.errors import ConvergenceError + +import numpy as np + + +def u_exact(self, t, u_init=None, t_init=None, recompute=False, _t0=None): + import pickle + import os + + path = f'data/stats/GS-u_init-{t:.8f}.pickle' + if os.path.exists(path) and not recompute and t_init is None: + with open(path, 'rb') as file: + data = pickle.load(file) + else: + from pySDC.helpers.stats_helper import get_sorted + from pySDC.implementations.convergence_controller_classes.adaptivity import Adaptivity + + convergence_controllers = { + Adaptivity: {'e_tol': 1e-8}, + } + desc = {'convergence_controllers': convergence_controllers} + + u0 = self._u_exact(0) if u_init is None else u_init + t0 = 0 if t_init is None else t_init + + if t == t0: + return u0 + else: + u0 = u0 if _t0 is None else self.u_exact(_t0) + _t0 = t0 if _t0 is None else _t0 + + stats, _, _ = run_GS(Tend=t, u0=u0, t0=_t0, custom_description=desc) + + u = get_sorted(stats, type='u', recomputed=False)[-1] + data = u[1] + + if t0 == 0: + with open(path, 'wb') as file: + pickle.dump(data, file) + + return data + + +if not hasattr(grayscott_imex_diffusion, '_u_exact'): + grayscott_imex_diffusion._u_exact = grayscott_imex_diffusion.u_exact + grayscott_imex_diffusion.u_exact = u_exact + + +def run_GS( + custom_description=None, + num_procs=1, + Tend=1e2, + hook_class=LogData, + fault_stuff=None, + custom_controller_params=None, + u0=None, + t0=0, + use_MPI=False, + **kwargs, +): + """ + Args: + custom_description (dict): Overwrite presets + num_procs (int): Number of steps for MSSDC + Tend (float): Time to integrate to + hook_class (pySDC.Hook): A hook to store data + fault_stuff (dict): A dictionary with information on how to add faults + custom_controller_params (dict): Overwrite presets + u0 (dtype_u): Initial value + t0 (float): Starting time + use_MPI (bool): Whether or not to use MPI + + Returns: + dict: The stats object + controller: The controller + bool: If the code crashed + """ + + level_params = {} + level_params['dt'] = 1e0 + level_params['restol'] = -1 + + sweeper_params = {} + sweeper_params['quad_type'] = 'RADAU-RIGHT' + sweeper_params['num_nodes'] = 3 + sweeper_params['QI'] = 'MIN-SR-S' + sweeper_params['QE'] = 'PIC' + sweeper_params['initial_guess'] = 'copy' + + from mpi4py import MPI + + problem_params = { + 'comm': MPI.COMM_SELF, + 'num_blobs': -48, + 'L': 2, + 'nvars': (128,) * 2, + 'A': 0.062, + 'B': 0.1229, + 'Du': 2e-5, + 'Dv': 1e-5, + } + + step_params = {} + step_params['maxiter'] = 5 + + convergence_controllers = {} + convergence_controllers[ReachTendExactly] = {'Tend': Tend} + + controller_params = {} + controller_params['logger_level'] = 15 + controller_params['hook_class'] = hook_collection + (hook_class if type(hook_class) == list else [hook_class]) + controller_params['mssdc_jac'] = False + + if custom_controller_params is not None: + controller_params = {**controller_params, **custom_controller_params} + + description = {} + description['problem_class'] = grayscott_imex_diffusion + description['problem_params'] = problem_params + description['sweeper_class'] = imex_1st_order_efficient + description['sweeper_params'] = sweeper_params + description['level_params'] = level_params + description['step_params'] = step_params + description['convergence_controllers'] = convergence_controllers + + if custom_description is not None: + description = merge_descriptions(description, custom_description) + + controller_args = { + 'controller_params': controller_params, + 'description': description, + } + if use_MPI: + from mpi4py import MPI + from pySDC.implementations.controller_classes.controller_MPI import controller_MPI + + comm = kwargs.get('comm', MPI.COMM_WORLD) + controller = controller_MPI(**controller_args, comm=comm) + P = controller.S.levels[0].prob + else: + controller = controller_nonMPI(**controller_args, num_procs=num_procs) + P = controller.MS[0].levels[0].prob + + t0 = 0.0 if t0 is None else t0 + uinit = P.u_exact(t0) if u0 is None else u0 + + if fault_stuff is not None: + from pySDC.projects.Resilience.fault_injection import prepare_controller_for_faults + + prepare_controller_for_faults(controller, fault_stuff) + + crash = False + try: + uend, stats = controller.run(u0=uinit, t0=t0, Tend=Tend) + except ConvergenceError as e: + print(f'Warning: Premature termination!: {e}') + stats = controller.return_stats() + crash = True + return stats, controller, crash + + +if __name__ == '__main__': + stats, _, _ = run_GS() diff --git a/pySDC/projects/Resilience/Lorenz.py b/pySDC/projects/Resilience/Lorenz.py index 817ad46be48ad2c9ff8317bc8f4631340df7f22a..c9cf9ebe3034a857fdef30f4f17da283a2b47cf6 100644 --- a/pySDC/projects/Resilience/Lorenz.py +++ b/pySDC/projects/Resilience/Lorenz.py @@ -49,6 +49,7 @@ def run_Lorenz( sweeper_params['quad_type'] = 'RADAU-RIGHT' sweeper_params['num_nodes'] = 3 sweeper_params['QI'] = 'IE' + sweeper_params['initial_guess'] = 'copy' problem_params = { 'newton_tol': 1e-9, diff --git a/pySDC/projects/Resilience/RBC.py b/pySDC/projects/Resilience/RBC.py index 46366c5d562d44c360f07a18380c4827308135b8..94b898cdff2ecd64cc0628c0c4685e9d8e5af679 100644 --- a/pySDC/projects/Resilience/RBC.py +++ b/pySDC/projects/Resilience/RBC.py @@ -4,17 +4,18 @@ from pySDC.implementations.problem_classes.RayleighBenard import RayleighBenard from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI from pySDC.projects.Resilience.hook import hook_collection, LogData from pySDC.projects.Resilience.strategies import merge_descriptions -from pySDC.implementations.sweeper_classes.imex_1st_order import imex_1st_order -from pySDC.core.convergence_controller import ConvergenceController +from pySDC.projects.Resilience.sweepers import imex_1st_order_efficient from pySDC.implementations.convergence_controller_classes.estimate_extrapolation_error import ( EstimateExtrapolationErrorNonMPI, ) -from pySDC.implementations.convergence_controller_classes.check_convergence import CheckConvergence +from pySDC.projects.Resilience.reachTendExactly import ReachTendExactly from pySDC.core.errors import ConvergenceError import numpy as np +PROBLEM_PARAMS = {'Rayleigh': 3.2e5, 'nx': 256, 'nz': 128, 'max_cached_factorizations': 30} + def u_exact(self, t, u_init=None, t_init=None, recompute=False, _t0=None): import pickle @@ -54,45 +55,9 @@ def u_exact(self, t, u_init=None, t_init=None, recompute=False, _t0=None): return data -RayleighBenard._u_exact = RayleighBenard.u_exact -RayleighBenard.u_exact = u_exact - -PROBLEM_PARAMS = {'Rayleigh': 2e4, 'nx': 256, 'nz': 128} - - -class ReachTendExactly(ConvergenceController): - """ - This convergence controller will adapt the step size of (hopefully) the last step such that `Tend` is reached very closely. - Please pass the same `Tend` that you pass to the controller to the params for this to work. - """ - - def setup(self, controller, params, description, **kwargs): - defaults = { - "control_order": +94, - "Tend": None, - 'min_step_size': 1e-10, - } - return {**defaults, **super().setup(controller, params, description, **kwargs)} - - def get_new_step_size(self, controller, step, **kwargs): - L = step.levels[0] - - if not CheckConvergence.check_convergence(step): - return None - - dt = L.status.dt_new if L.status.dt_new else L.params.dt - time_left = self.params.Tend - L.time - L.dt - if time_left <= dt + self.params.min_step_size and not step.status.restart and time_left > 0: - dt_new = ( - min([dt + self.params.min_step_size, max([time_left, self.params.min_step_size])]) - + np.finfo('float').eps * 10 - ) - if dt_new != L.status.dt_new: - L.status.dt_new = dt_new - self.log( - f'Reducing step size from {dt:12e} to {L.status.dt_new:.12e} because there is only {time_left:.12e} left.', - step, - ) +if not hasattr(RayleighBenard, '_u_exact'): + RayleighBenard._u_exact = RayleighBenard.u_exact + RayleighBenard.u_exact = u_exact def run_RBC( @@ -105,6 +70,7 @@ def run_RBC( u0=None, t0=20.0, use_MPI=False, + step_size_rounding=False, **kwargs, ): """ @@ -135,6 +101,7 @@ def run_RBC( sweeper_params['num_nodes'] = 3 sweeper_params['QI'] = 'LU' sweeper_params['QE'] = 'PIC' + sweeper_params['initial_guess'] = 'copy' from mpi4py import MPI @@ -147,22 +114,23 @@ def run_RBC( convergence_controllers[ReachTendExactly] = {'Tend': Tend} from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeRounding - convergence_controllers[StepSizeRounding] = {} + if step_size_rounding: + convergence_controllers[StepSizeRounding] = {} controller_params = {} - controller_params['logger_level'] = 30 + controller_params['logger_level'] = 15 controller_params['hook_class'] = hook_collection + (hook_class if type(hook_class) == list else [hook_class]) controller_params['mssdc_jac'] = False if custom_controller_params is not None: controller_params = {**controller_params, **custom_controller_params} - imex_1st_order.compute_residual = compute_residual_DAE + imex_1st_order_efficient.compute_residual = compute_residual_DAE description = {} description['problem_class'] = RayleighBenard description['problem_params'] = problem_params - description['sweeper_class'] = imex_1st_order + description['sweeper_class'] = imex_1st_order_efficient description['sweeper_params'] = sweeper_params description['level_params'] = level_params description['step_params'] = step_params @@ -204,14 +172,15 @@ def run_RBC( return stats, controller, crash -def generate_data_for_fault_stats(): +def generate_data_for_fault_stats(Tend): prob = RayleighBenard(**PROBLEM_PARAMS) - _ts = np.linspace(0, 22, 220, dtype=float) + _ts = np.linspace(0, Tend, Tend * 10 + 1, dtype=float) for i in range(len(_ts) - 1): - prob.u_exact(_ts[i + 1], _t0=_ts[i]) + print(f'Generating reference solution from {_ts[i]:.4e} to {_ts[i+1]:.4e}') + prob.u_exact(_ts[i + 1], _t0=_ts[i], recompute=False) -def plot_order(t, dt, steps, num_nodes, e_tol=1e-9, restol=1e-9, ax=None, recompute=False): +def plot_order(t, dt, steps, num_nodes, e_tol=1e-9, restol=1e-9, ax=None, recompute=False): # pragma: no cover from pySDC.implementations.hooks.log_errors import LogGlobalErrorPostRun from pySDC.implementations.hooks.log_work import LogSDCIterations, LogWork from pySDC.implementations.convergence_controller_classes.crash import StopAtNan @@ -294,7 +263,7 @@ def check_order(t=14, dt=1e-1, steps=6): plt.show() -def plot_step_size(t0=0, Tend=30, e_tol=1e-3, recompute=False): +def plot_step_size(t0=0, Tend=30, e_tol=1e-3, recompute=False): # pragma: no cover import matplotlib.pyplot as plt import pickle import os @@ -327,8 +296,86 @@ def plot_step_size(t0=0, Tend=30, e_tol=1e-3, recompute=False): plt.show() +def plot_factorizations_over_time(t0=0, Tend=50, e_tol=1e-3, recompute=False, adaptivity_mode='dt'): # pragma: no cover + import matplotlib.pyplot as plt + import pickle + import os + from pySDC.helpers.stats_helper import get_sorted + from pySDC.implementations.hooks.log_work import LogWork + from pySDC.implementations.convergence_controller_classes.adaptivity import Adaptivity, AdaptivityPolynomialError + from pySDC.helpers.plot_helper import figsize_by_journal, setup_mpl + from pySDC.projects.Resilience.paper_plots import savefig + + setup_mpl() + + fig, axs = plt.subplots(1, 2, figsize=figsize_by_journal('TUHH_thesis', 1.0, 0.4)) + + if adaptivity_mode == 'dt': + adaptivity = Adaptivity + elif adaptivity_mode == 'dt_k': + adaptivity = AdaptivityPolynomialError + + dt_controllers = { + 'basic': { + adaptivity: { + 'e_tol': e_tol, + } + }, + 'min slope': {adaptivity: {'e_tol': e_tol, 'beta': 0.5, 'dt_rel_min_slope': 2}}, + 'fixed': {}, + # 'rounding': {adaptivity: {'e_tol': e_tol, 'beta': 0.5, 'dt_rel_min_slope': 2}, StepSizeRounding: {}}, + } + + for name, params in dt_controllers.items(): + if adaptivity_mode == 'dt': + path = f'data/stats/RBC-u-{t0:.8f}-{Tend:.8f}-{e_tol:.2e}-{name}.pickle' + elif adaptivity_mode == 'dt_k': + path = f'data/stats/RBC-u-{t0:.8f}-{Tend:.8f}-{e_tol:.2e}-{name}-dtk.pickle' + + if os.path.exists(path) and not recompute: + with open(path, 'rb') as file: + stats = pickle.load(file) + else: + + convergence_controllers = { + **params, + } + desc = {'convergence_controllers': convergence_controllers} + + if name == 'fixed': + if adaptivity_mode == 'dt': + desc['level_params'] = {'dt': 2e-2} + elif adaptivity_mode == 'dt_k': + desc['level_params'] = {'dt': 2e-3} + elif adaptivity_mode == 'dt_k': + desc['level_params'] = {'restol': 1e-7} + + stats, _, _ = run_RBC( + Tend=Tend, t0=t0, custom_description=desc, hook_class=LogWork, step_size_rounding=False + ) + + with open(path, 'wb') as file: + pickle.dump(stats, file) + + factorizations = get_sorted(stats, type='work_factorizations') + rhs_evals = get_sorted(stats, type='work_rhs') + axs[0].plot([me[0] for me in factorizations], np.cumsum([me[1] for me in factorizations]), label=name) + axs[1].plot([me[0] for me in rhs_evals], np.cumsum([me[1] for me in rhs_evals]), label=name) + + axs[0].set_ylabel(r'matrix factorizations') + axs[1].set_ylabel(r'right hand side evaluations') + axs[0].set_xlabel(r'$t$') + axs[1].set_xlabel(r'$t$') + axs[0].set_yscale('log') + axs[1].set_yscale('log') + axs[0].legend(frameon=False) + savefig(fig, f'RBC_step_size_controller_{adaptivity_mode}') + + if __name__ == '__main__': - # plot_step_size(0, 3) - generate_data_for_fault_stats() + # plot_step_size(0, 30) + generate_data_for_fault_stats(Tend=30) + # plot_factorizations_over_time(e_tol=1e-3, adaptivity_mode='dt') + # plot_factorizations_over_time(recompute=False, e_tol=1e-5, adaptivity_mode='dt_k') # check_order(t=20, dt=1., steps=7) # stats, _, _ = run_RBC() diff --git a/pySDC/projects/Resilience/README.rst b/pySDC/projects/Resilience/README.rst index 3092c023b21dc46831ad3ee6159aad5093341b0d..75a5e44083a3878320dfeaa6ecc2ee6d4a79cdc1 100644 --- a/pySDC/projects/Resilience/README.rst +++ b/pySDC/projects/Resilience/README.rst @@ -43,13 +43,26 @@ You can see the results below, except for the solution, which looks the same as Reproduction of the plots in the adaptive SDC paper --------------------------------------------------- -To reproduce the plots you need to install pySDC with all packages in the mpi4py environment. -Then, navigate to this directory, `pySDC/projects/Resilience/` and run the following commands: - +To reproduce the plots you need to install pySDC using this project's `environment.yml` file, which is in the same directory as this README. +Then, run the following commands in the same directory: .. code-block:: bash - mpirun -np 4 python work_precision.py + mpirun -np 4 python work_precision.py --mode=compare_strategies --problem=vdp + mpirun -np 4 python work_precision.py --mode=compare_strategies --problem=quench + mpirun -np 4 python work_precision.py --mode=compare_strategies --problem=Schroedinger + mpirun -np 4 python work_precision.py --mode=compare_strategies --problem=AC + + mpirun -np 12 python work_precision.py --mode=parallel_efficiency --problem=vdp + mpirun -np 12 python work_precision.py --mode=parallel_efficiency --problem=quench + mpirun -np 12 python work_precision.py --mode=parallel_efficiency --problem=Schroedinger + mpirun -np 12 python work_precision.py --mode=parallel_efficiency --problem=AC + + mpirun -np 4 python work_precision.py --mode=RK_comp --problem=vdp + mpirun -np 4 python work_precision.py --mode=RK_comp --problem=quench + mpirun -np 4 python work_precision.py --mode=RK_comp --problem=Schroedinger + mpirun -np 4 python work_precision.py --mode=RK_comp --problem=AC + python paper_plots.py --target=adaptivity Possibly, you need to create some directories in this one to store and load things, if path errors occur. @@ -57,10 +70,10 @@ Possibly, you need to create some directories in this one to store and load thin Reproduction of the plots in the resilience paper ------------------------------------------------- To reproduce the plots you need to install pySDC using this project's `environment.yml` file, which is in the same directory as this README. +Then, run the following commands in the same directory: .. code-block:: bash - mpirun -np 4 python work_precision.py mpirun -np 4 python fault_stats.py prob run_Lorenz mpirun -np 4 python fault_stats.py prob run_Schroedinger mpirun -np 4 python fault_stats.py prob run_AC @@ -68,3 +81,34 @@ To reproduce the plots you need to install pySDC using this project's `environme python paper_plots.py --target=resilience Please be aware that generating the fault data for Rayleigh-Benard requires generating reference solutions, which may take several hours. + +Reproduction of the plots in Thomas Baumann's thesis +---------------------------------------------------- +To reproduce the plots you need to install pySDC using this project's `environment.yml` file, which is in the same directory as this README. + +To run the resilience experiments, run + +.. code-block:: bash + + mpirun -np 4 python fault_stats.py prob run_Lorenz + mpirun -np 4 python fault_stats.py prob run_vdp + mpirun -np 4 python fault_stats.py prob run_GS + mpirun -np 4 python fault_stats.py prob run_RBC + +Please be aware that generating the fault data for Rayleigh-Benard requires generating reference solutions, which may take several hours. +To run the experiments on computational efficiency, run for the choices of problems `vdp`, `Lorenz``, `GS`, `RBC` the following commands + +.. code-block:: bash + + mpirun -np 4 python work_precision.py --mode=compare_strategies --problem=<problem> + mpirun -np 12 python work_precision.py --mode=parallel_efficiency_dt_k --problem=<problem> + mpirun -np 12 python work_precision.py --mode=parallel_efficiency_dt --problem=<problem> + mpirun -np 12 python work_precision.py --mode=RK_comp --problem=<problem> + +After you ran all experiments, you can generate the plots with the following command: + +.. code-block:: bash + + python paper_plots.py --target=thesis + +As this plotting script also runs a few additional smaller experiments, do not be surprised if it takes a while. diff --git a/pySDC/projects/Resilience/fault_stats.py b/pySDC/projects/Resilience/fault_stats.py index b75e47ded873f9e3dbdd4f552f3e6ba8d151f5f4..2acdfb2243da58b7a10b508d5c740b0d97a89c3a 100644 --- a/pySDC/projects/Resilience/fault_stats.py +++ b/pySDC/projects/Resilience/fault_stats.py @@ -22,6 +22,7 @@ from pySDC.projects.Resilience.Schroedinger import run_Schroedinger from pySDC.projects.Resilience.quench import run_quench from pySDC.projects.Resilience.AC import run_AC from pySDC.projects.Resilience.RBC import run_RBC +from pySDC.projects.Resilience.GS import run_GS from pySDC.projects.Resilience.strategies import BaseStrategy, AdaptivityStrategy, IterateStrategy, HotRodStrategy import logging @@ -108,7 +109,7 @@ class FaultStats: Returns: float: Tend to put into the run ''' - return self.strategies[0].get_Tend(self.prob, self.num_procs) + return self.strategies[0].get_Tend(self.prob, self.num_procs, resilience_experiment=True) def run_stats_generation( self, runs=1000, step=None, comm=None, kwargs_range=None, faults=None, _reload=False, _runs_partial=0 @@ -637,6 +638,8 @@ class FaultStats: prob_name = 'Allen-Cahn' elif self.prob.__name__ == 'run_RBC': prob_name = 'Rayleigh-Benard' + elif self.prob.__name__ == 'run_GS': + prob_name = 'Gray-Scott' else: raise NotImplementedError(f'Name not implemented for problem {self.prob}') @@ -717,7 +720,7 @@ class FaultStats: else: try: with_faults = self.load(faults=True, **kwargs) - with_faults['recovered'] = with_faults['error'] < self.get_thresh(kwargs['strategy']) + with_faults['recovered'] = with_faults['error'] <= self.get_thresh(kwargs['strategy']) self.store(faults=True, dat=with_faults, **kwargs) except KeyError as error: print( @@ -836,10 +839,10 @@ class FaultStats: me_recovered = np.zeros_like(me) for i in range(len(me)): - _mask = (dat[thingB] == admissable_thingB[i]) & mask + _mask = np.logical_and((dat[thingB] == admissable_thingB[i]), mask) if _mask.any(): me[i] = op(dat, no_faults, thingA, _mask) - me_recovered[i] = op(dat, no_faults, thingA, _mask & dat['recovered']) + me_recovered[i] = op(dat, no_faults, thingA, np.logical_and(_mask, dat['recovered'])) if recovered: ax.plot( @@ -965,7 +968,7 @@ class FaultStats: if recoverable_only: recoverable_mask = self.get_fixable_faults_only(strategy) else: - recoverable_mask = self.get_mask() + recoverable_mask = self.get_mask(strategy=strategy) for thresh_idx in range(len(thresh_range)): rec_mask = self.get_mask( @@ -1514,7 +1517,7 @@ class FaultStats: HR_strategy = HotRodStrategy(useMPI=self.use_MPI) - description = HR_strategy.get_custom_description(self.prob, self.num_procs) + description = HR_strategy.get_custom_description_for_faults(self.prob, self.num_procs) description['convergence_controllers'][HotRod]['HotRod_tol'] = 1e2 stats, _, _ = self.single_run(HR_strategy, force_params=description) @@ -1578,6 +1581,8 @@ def parse_args(): kwargs['prob'] = run_AC elif sys.argv[i + 1] == 'run_RBC': kwargs['prob'] = run_RBC + elif sys.argv[i + 1] == 'run_GS': + kwargs['prob'] = run_GS else: raise NotImplementedError elif 'num_procs' in sys.argv[i]: @@ -1667,11 +1672,11 @@ def compare_adaptivity_modes(): def main(): kwargs = { - 'prob': run_RBC, + 'prob': run_vdp, 'num_procs': 1, 'mode': 'default', - 'runs': 2000, - 'reload': False, + 'runs': 4000, + 'reload': True, **parse_args(), } @@ -1690,12 +1695,14 @@ def main(): AdaptivityPolynomialError(**strategy_args), ], faults=[False, True], - recovery_thresh=1.15, + recovery_thresh=1.1, recovery_thresh_abs=RECOVERY_THRESH_ABS.get(kwargs.get('prob', None), 0), stats_path='data/stats-jusuf', **kwargs, ) - stats_analyser.run_stats_generation(runs=kwargs['runs'], step=25) + stats_analyser.get_recovered() + + stats_analyser.run_stats_generation(runs=kwargs['runs'], step=8) if MPI.COMM_WORLD.rank > 0: # make sure only one rank accesses the data return None diff --git a/pySDC/projects/Resilience/paper_plots.py b/pySDC/projects/Resilience/paper_plots.py index 67f9aca486846800b8b44460bf08360a0887b6b9..69d176236567ba41acc860f3ea8a7805b4f38964 100644 --- a/pySDC/projects/Resilience/paper_plots.py +++ b/pySDC/projects/Resilience/paper_plots.py @@ -10,6 +10,7 @@ from pySDC.projects.Resilience.fault_stats import ( run_quench, run_AC, run_RBC, + run_GS, RECOVERY_THRESH_ABS, ) from pySDC.projects.Resilience.strategies import ( @@ -45,9 +46,9 @@ def get_stats(problem, path='data/stats-jusuf', num_procs=1, strategy_type='SDC' FaultStats: Object to analyse resilience statistics from """ if strategy_type == 'SDC': - strategies = [BaseStrategy(), AdaptivityStrategy(), IterateStrategy()] + strategies = [BaseStrategy(), AdaptivityStrategy(), IterateStrategy(), AdaptivityPolynomialError()] if JOURNAL not in ['JSC_beamer']: - strategies += [HotRodStrategy(), AdaptivityPolynomialError()] + strategies += [HotRodStrategy()] elif strategy_type == 'RK': strategies = [DIRKStrategy()] if problem.__name__ in ['run_Lorenz', 'run_vdp']: @@ -142,7 +143,8 @@ def plot_recovery_rate(stats_analyser, **kwargs): # pragma: no cover None """ my_setup_mpl() - fig, axs = plt.subplots(1, 2, figsize=(TEXTWIDTH, 5 * cm), sharex=True, sharey=True) + # fig, axs = plt.subplots(1, 2, figsize=(TEXTWIDTH, 5 * cm), sharex=True, sharey=True) + fig, axs = plt.subplots(1, 2, figsize=figsize_by_journal(JOURNAL, 1, 0.4), sharex=True) stats_analyser.plot_things_per_things( 'recovered', 'bit', @@ -185,7 +187,7 @@ def plot_recovery_rate_recoverable_only(stats_analyser, fig, ax, **kwargs): # p ax=ax, fig=fig, strategies=[stats_analyser.strategies[i]], - plotting_args={'markevery': 5}, + plotting_args={'markevery': 10 if stats_analyser.prob.__name__ in ['run_RBC', 'run_Schroedinger'] else 5}, ) @@ -200,9 +202,9 @@ def compare_recovery_rate_problems(target='resilience', **kwargs): # pragma: no if target == 'resilience': problems = [run_Lorenz, run_Schroedinger, run_AC, run_RBC] titles = ['Lorenz', r'Schr\"odinger', 'Allen-Cahn', 'Rayleigh-Benard'] - elif target == 'thesis': - problems = [run_vdp, run_Lorenz, run_AC, run_RBC] # TODO: swap in Gray-Scott - titles = ['Van der Pol', 'Lorenz', 'Allen-Cahn', 'Rayleigh-Benard'] + elif target in ['thesis', 'talk']: + problems = [run_vdp, run_Lorenz, run_GS, run_RBC] # TODO: swap in Gray-Scott + titles = ['Van der Pol', 'Lorenz', 'Gray-Scott', 'Rayleigh-Benard'] else: raise NotImplementedError() @@ -219,13 +221,17 @@ def compare_recovery_rate_problems(target='resilience', **kwargs): # pragma: no ax.get_legend().remove() if kwargs.get('strategy_type', 'SDC') == 'SDC': - axs[1, 1].legend(frameon=False, loc="lower right") + axs[1, 0].legend(frameon=False, loc="lower right") else: axs[0, 1].legend(frameon=False, loc="lower right") axs[0, 0].set_ylim((-0.05, 1.05)) axs[1, 0].set_ylabel('recovery rate') axs[0, 0].set_ylabel('recovery rate') + if target == 'talk': + axs[0, 0].set_xlabel('') + axs[0, 1].set_xlabel('') + name = '' for key, val in kwargs.items(): name = f'{name}_{key}-{val}' @@ -233,6 +239,37 @@ def compare_recovery_rate_problems(target='resilience', **kwargs): # pragma: no savefig(fig, f'compare_equations{name}.pdf') +def plot_recovery_rate_detailed_Lorenz(target='resilience'): # pragma: no cover + stats = get_stats(run_Lorenz, num_procs=1, strategy_type='SDC') + stats.get_recovered() + mask = None + + for x in ['node', 'iteration', 'bit']: + if target == 'talk': + fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.6, 0.6)) + else: + fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.8, 0.5)) + + stats.plot_things_per_things( + 'recovered', + x, + False, + op=stats.rec_rate, + mask=mask, + args={'ylabel': 'recovery rate'}, + ax=ax, + plotting_args={'markevery': 5 if x == 'bit' else 1}, + ) + ax.set_ylim((-0.05, 1.05)) + + if x == 'node': + ax.set_xticks([0, 1, 2, 3]) + elif x == 'iteration': + ax.set_xticks([1, 2, 3, 4, 5]) + + savefig(fig, f'recovery_rate_Lorenz_{x}') + + def plot_adaptivity_stuff(): # pragma: no cover """ Plot the solution for a van der Pol problem as well as the local error and cost associated with the base scheme and @@ -393,7 +430,7 @@ def plot_fault_vdp(bit=0): # pragma: no cover savefig(fig, f'fault_bit_{bit}') -def plot_fault_Lorenz(bit=0): # pragma: no cover +def plot_fault_Lorenz(bit=0, target='resilience'): # pragma: no cover """ Make a plot showing the impact of a fault on the Lorenz attractor without any resilience. The faults are inserted in the last iteration in the last node in x such that you can best see the impact. @@ -423,12 +460,15 @@ def plot_fault_Lorenz(bit=0): # pragma: no cover strategy = BaseStrategy() my_setup_mpl() - fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.8, 0.5)) + if target == 'resilience': + fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.4, 0.6)) + else: + fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.8, 0.5)) colors = ['grey', strategy.color, 'magenta'] ls = ['--', '-'] markers = [None, strategy.marker] do_faults = [False, True] - superscripts = ['*', ''] + superscripts = [r'\mathrm{no~faults}', ''] labels = ['x', 'x'] run = 19 + 20 * bit @@ -447,7 +487,7 @@ def plot_fault_Lorenz(bit=0): # pragma: no cover [me[1][0] for me in u], ls=ls[i], color=colors[i], - label=rf'${{{labels[i]}}}^{{{superscripts[i]}}}$', + label=rf'${{{labels[i]}}}_{{{superscripts[i]}}}$', marker=markers[i], markevery=500, ) @@ -458,8 +498,19 @@ def plot_fault_Lorenz(bit=0): # pragma: no cover ) ax.set_title(f'Fault in bit {faults[idx][1][4]}') - ax.legend(frameon=True, loc='lower left') ax.set_xlabel(r'$t$') + + h, l = ax.get_legend_handles_labels() + fig.legend( + h, + l, + loc='outside lower center', + ncols=3, + frameon=False, + fancybox=True, + borderaxespad=0.01, + ) + savefig(fig, f'fault_bit_{bit}') @@ -521,7 +572,7 @@ def plot_quench_solution(): # pragma: no cover savefig(fig, 'quench_sol') -def plot_RBC_solution(): # pragma: no cover +def plot_RBC_solution(setup='resilience'): # pragma: no cover """ Plot solution of Rayleigh-Benard convection """ @@ -529,13 +580,14 @@ def plot_RBC_solution(): # pragma: no cover from mpl_toolkits.axes_grid1 import make_axes_locatable + nplots = 3 if setup == 'thesis_intro' else 2 + aspect = 0.8 if nplots == 3 else 0.5 plt.rcParams['figure.constrained_layout.use'] = True - fig, axs = plt.subplots(2, 1, sharex=True, sharey=True, figsize=figsize_by_journal(JOURNAL, 1.0, 0.45)) + fig, axs = plt.subplots(nplots, 1, sharex=True, sharey=True, figsize=figsize_by_journal(JOURNAL, 1.0, aspect)) caxs = [] - divider = make_axes_locatable(axs[0]) - caxs += [divider.append_axes('right', size='3%', pad=0.03)] - divider2 = make_axes_locatable(axs[1]) - caxs += [divider2.append_axes('right', size='3%', pad=0.03)] + for ax in axs: + divider = make_axes_locatable(ax) + caxs += [divider.append_axes('right', size='3%', pad=0.03)] from pySDC.projects.Resilience.RBC import RayleighBenard, PROBLEM_PARAMS @@ -547,14 +599,67 @@ def plot_RBC_solution(): # pragma: no cover im = ax.pcolormesh(prob.X, prob.Z, u[prob.index('T')], rasterized=True, cmap='plasma') fig.colorbar(im, cax, label=f'$T(t={{{t}}})$') - _plot(0, axs[0], caxs[0]) - _plot(21, axs[1], caxs[1]) + if setup == 'resilience': + _plot(0, axs[0], caxs[0]) + _plot(21, axs[1], caxs[1]) + elif setup == 'work-precision': + _plot(10, axs[0], caxs[0]) + _plot(16, axs[1], caxs[1]) + elif setup == 'resilience_thesis': + _plot(20, axs[0], caxs[0]) + _plot(21, axs[1], caxs[1]) + elif setup == 'thesis_intro': + _plot(0, axs[0], caxs[0]) + _plot(18, axs[1], caxs[1]) + _plot(30, axs[2], caxs[2]) - axs[1].set_xlabel('$x$') - axs[0].set_ylabel('$z$') - axs[1].set_ylabel('$z$') + for ax in axs: + ax.set_ylabel('$z$') + ax.set_aspect(1) + axs[-1].set_xlabel('$x$') + + savefig(fig, f'RBC_sol_{setup}', tight_layout=False) + + +def plot_GS_solution(tend=500): # pragma: no cover + my_setup_mpl() + + fig, axs = plt.subplots(1, 2, figsize=figsize_by_journal(JOURNAL, 1.0, 0.45), sharex=True, sharey=True) + + from mpl_toolkits.axes_grid1 import make_axes_locatable + + plt.rcParams['figure.constrained_layout.use'] = True + cax = [] + divider = make_axes_locatable(axs[0]) + cax += [divider.append_axes('right', size='5%', pad=0.05)] + divider2 = make_axes_locatable(axs[1]) + cax += [divider2.append_axes('right', size='5%', pad=0.05)] - savefig(fig, 'RBC_sol', tight_layout=False) + from pySDC.projects.Resilience.GS import grayscott_imex_diffusion + + problem_params = { + 'num_blobs': -48, + 'L': 2, + 'nvars': (128,) * 2, + 'A': 0.062, + 'B': 0.1229, + 'Du': 2e-5, + 'Dv': 1e-5, + } + P = grayscott_imex_diffusion(**problem_params) + Tend = tend + im = axs[0].pcolormesh(*P.X, P.u_exact(0)[1], rasterized=True, cmap='binary') + im1 = axs[1].pcolormesh(*P.X, P.u_exact(Tend)[1], rasterized=True, cmap='binary') + + fig.colorbar(im, cax=cax[0]) + fig.colorbar(im1, cax=cax[1]) + axs[0].set_title(r'$v(t=0)$') + axs[1].set_title(rf'$v(t={{{Tend}}})$') + for ax in axs: + ax.set_aspect(1) + ax.set_xlabel('$x$') + ax.set_ylabel('$y$') + savefig(fig, f'GrayScott_sol{f"_{tend}" if tend != 500 else ""}') def plot_Schroedinger_solution(): # pragma: no cover @@ -630,7 +735,7 @@ def plot_AC_solution(): # pragma: no cover savefig(fig, 'AC_sol') -def plot_vdp_solution(): # pragma: no cover +def plot_vdp_solution(setup='adaptivity'): # pragma: no cover """ Plot the solution of van der Pol problem over time to illustrate the varying time scales. @@ -645,24 +750,41 @@ def plot_vdp_solution(): # pragma: no cover else: fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 1.0, 0.33)) - custom_description = { - 'convergence_controllers': {Adaptivity: {'e_tol': 1e-7, 'dt_max': 1e0}}, - 'problem_params': {'mu': 1000, 'crash_at_maxiter': False}, - 'level_params': {'dt': 1e-3}, - } - - stats, _, _ = run_vdp(custom_description=custom_description, Tend=2000) + if setup == 'adaptivity': + custom_description = { + 'convergence_controllers': {Adaptivity: {'e_tol': 1e-7, 'dt_max': 1e0}}, + 'problem_params': {'mu': 1000, 'crash_at_maxiter': False}, + 'level_params': {'dt': 1e-3}, + } + Tend = 2000 + elif setup == 'resilience': + custom_description = { + 'convergence_controllers': {Adaptivity: {'e_tol': 1e-7, 'dt_max': 1e0}}, + 'problem_params': {'mu': 5, 'crash_at_maxiter': False}, + 'level_params': {'dt': 1e-3}, + } + Tend = 50 + + stats, _, _ = run_vdp(custom_description=custom_description, Tend=Tend) u = get_sorted(stats, type='u', recomputed=False) _u = np.array([me[1][0] for me in u]) _x = np.array([me[0] for me in u]) - x1 = _x[abs(_u - 1.1) < 1e-2][0] - ax.plot(_x, _u, color='black') - ax.axvspan(x1, x1 + 20, alpha=0.4) + name = '' + if setup == 'adaptivity': + x1 = _x[abs(_u - 1.1) < 1e-2][0] + ax.plot(_x, _u, color='black') + ax.axvspan(x1, x1 + 20, alpha=0.4) + elif setup == 'resilience': + x1 = _x[abs(_u - 2.0) < 1e-2][0] + ax.plot(_x, _u, color='black') + ax.axvspan(x1, x1 + 11.5, alpha=0.4) + name = '_resilience' + ax.set_ylabel(r'$u$') ax.set_xlabel(r'$t$') - savefig(fig, 'vdp_sol') + savefig(fig, f'vdp_sol{name}') def work_precision(): # pragma: no cover @@ -683,10 +805,19 @@ def work_precision(): # pragma: no cover all_problems(**{**all_params, 'work_key': 'param'}, mode='compare_strategies') -def plot_recovery_rate_per_acceptance_threshold(problem): # pragma no cover +def plot_recovery_rate_per_acceptance_threshold(problem, target='resilience'): # pragma no cover stats_analyser = get_stats(problem) - stats_analyser.plot_recovery_thresholds(thresh_range=np.linspace(0.5, 1.5, 1000), recoverable_only=True) + if target == 'talk': + fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.4, 0.6)) + else: + fig, ax = plt.subplots(figsize=figsize_by_journal(JOURNAL, 0.8, 0.5)) + + stats_analyser.plot_recovery_thresholds(thresh_range=np.logspace(-1, 4, 500), recoverable_only=False, ax=ax) + ax.set_xscale('log') + ax.set_ylim((-0.05, 1.05)) + ax.set_xlabel('relative threshold') + savefig(fig, 'recovery_rate_per_thresh') def make_plots_for_TIME_X_website(): # pragma: no cover @@ -739,13 +870,20 @@ def make_plots_for_adaptivity_paper(): # pragma: no cover def make_plots_for_resilience_paper(): # pragma: no cover + global JOURNAL + JOURNAL = 'Springer_proceedings' + plot_Lorenz_solution() + plot_RBC_solution() + plot_AC_solution() + plot_Schroedinger_solution() + plot_fault_Lorenz(0) plot_fault_Lorenz(20) - plot_RBC_solution() compare_recovery_rate_problems(target='resilience', num_procs=1, strategy_type='SDC') - # plot_recovery_rate(get_stats(run_Lorenz)) - # plot_recovery_rate_per_acceptance_threshold(run_Lorenz) + plot_recovery_rate(get_stats(run_Lorenz)) + plot_recovery_rate_detailed_Lorenz() + plot_recovery_rate_per_acceptance_threshold(run_Lorenz) plt.show() @@ -764,12 +902,77 @@ def make_plots_for_notes(): # pragma: no cover def make_plots_for_thesis(): # pragma: no cover global JOURNAL JOURNAL = 'TUHH_thesis' + for setup in ['thesis_intro', 'resilience_thesis', 'work_precision']: + plot_RBC_solution(setup) - plot_RBC_solution() - # plot_vdp_solution() + from pySDC.projects.Resilience.RBC import plot_factorizations_over_time + + plot_factorizations_over_time(t0=0, Tend=50) + + from pySDC.projects.Resilience.work_precision import all_problems, single_problem - # plot_adaptivity_stuff() + all_params = { + 'record': False, + 'work_key': 't', + 'precision_key': 'e_global_rel', + 'plotting': True, + 'base_path': 'data/paper', + 'target': 'thesis', + } + + for mode in ['compare_strategies', 'parallel_efficiency_dt_k', 'parallel_efficiency_dt', 'RK_comp']: + all_problems(**all_params, mode=mode) + all_problems(**{**all_params, 'work_key': 'param'}, mode='compare_strategies') + single_problem(**all_params, mode='RK_comp_high_order_RBC', problem=run_RBC) + + for tend in [500, 2000]: + plot_GS_solution(tend=tend) + for setup in ['resilience', 'adaptivity']: + plot_vdp_solution(setup=setup) + + plot_adaptivity_stuff() + + plot_fault_Lorenz(0) + plot_fault_Lorenz(20) compare_recovery_rate_problems(target='thesis', num_procs=1, strategy_type='SDC') + plot_recovery_rate_per_acceptance_threshold(run_Lorenz) + plot_recovery_rate(get_stats(run_Lorenz)) + plot_recovery_rate_detailed_Lorenz() + + +def make_plots_for_TUHH_seminar(): # pragma: no cover + global JOURNAL + JOURNAL = 'JSC_beamer' + + from pySDC.projects.Resilience.work_precision import ( + all_problems, + ) + + all_params = { + 'record': False, + 'work_key': 't', + 'precision_key': 'e_global_rel', + 'plotting': True, + 'base_path': 'data/paper', + 'target': 'talk', + } + + for mode in ['compare_strategies', 'parallel_efficiency_dt_k', 'parallel_efficiency_dt', 'RK_comp']: + all_problems(**all_params, mode=mode) + all_problems(**{**all_params, 'work_key': 'param'}, mode='compare_strategies') + + plot_GS_solution() + for setup in ['resilience_thesis', 'work_precision']: + plot_RBC_solution(setup) + for setup in ['resilience', 'adaptivity']: + plot_vdp_solution(setup=setup) + + plot_adaptivity_stuff() + + plot_fault_Lorenz(20, target='talk') + compare_recovery_rate_problems(target='talk', num_procs=1, strategy_type='SDC') + plot_recovery_rate_per_acceptance_threshold(run_Lorenz, target='talk') + plot_recovery_rate_detailed_Lorenz(target='talk') if __name__ == "__main__": @@ -777,7 +980,9 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument( - '--target', choices=['adaptivity', 'resilience', 'thesis', 'notes', 'SIAM_CSE23', 'TIME_X_website'], type=str + '--target', + choices=['adaptivity', 'resilience', 'thesis', 'notes', 'SIAM_CSE23', 'TIME_X_website', 'TUHH_seminar'], + type=str, ) args = parser.parse_args() @@ -793,5 +998,7 @@ if __name__ == "__main__": make_plots_for_SIAM_CSE23() elif args.target == 'TIME_X_website': make_plots_for_TIME_X_website() + elif args.target == 'TUHH_seminar': + make_plots_for_TUHH_seminar() else: raise NotImplementedError(f'Don\'t know how to make plots for target {args.target}') diff --git a/pySDC/projects/Resilience/reachTendExactly.py b/pySDC/projects/Resilience/reachTendExactly.py new file mode 100644 index 0000000000000000000000000000000000000000..4f4c394ed03df923dad2b4444cfae1c9be5071d1 --- /dev/null +++ b/pySDC/projects/Resilience/reachTendExactly.py @@ -0,0 +1,46 @@ +import numpy as np +from pySDC.core.convergence_controller import ConvergenceController +from pySDC.implementations.convergence_controller_classes.check_convergence import CheckConvergence + + +class ReachTendExactly(ConvergenceController): + """ + This convergence controller will adapt the step size of (hopefully) the last step such that `Tend` is reached very closely. + Please pass the same `Tend` that you pass to the controller to the params for this to work. + """ + + def setup(self, controller, params, description, **kwargs): + defaults = { + "control_order": +94, + "Tend": None, + 'min_step_size': 1e-10, + } + return {**defaults, **super().setup(controller, params, description, **kwargs)} + + def get_new_step_size(self, controller, step, **kwargs): + L = step.levels[0] + time_size = step.status.time_size + + if not CheckConvergence.check_convergence(step): + return None + + dt = L.status.dt_new if L.status.dt_new else L.params.dt + time_left = self.params.Tend - L.time - L.dt + + if ( + time_left <= (dt + self.params.min_step_size) * time_size + and not step.status.restart + and time_left > 0 + and step.status.last + ): + dt_new = ( + min([(dt + self.params.min_step_size) * time_size, max([time_left, self.params.min_step_size])]) + + time_size * np.finfo('float').eps * 10 + ) / time_size + + if dt_new != L.status.dt_new: + L.status.dt_new = dt_new + self.log( + f'Changing step size from {dt:12e} to {L.status.dt_new:.12e} because there is only {time_left:.12e} left.', + step, + ) diff --git a/pySDC/projects/Resilience/strategies.py b/pySDC/projects/Resilience/strategies.py index 406e63995d14da2822dd6dc2ad10e65b959fda42..134356331d951ca3ed96a14117c0c27dbf083659 100644 --- a/pySDC/projects/Resilience/strategies.py +++ b/pySDC/projects/Resilience/strategies.py @@ -121,7 +121,7 @@ class Strategy: args['target'] = 0 if problem.__name__ == "run_vdp": - args['time'] = 5.25 + args['time'] = 5.5 # 25 elif problem.__name__ == "run_Schroedinger": args['time'] = 0.3 elif problem.__name__ == "run_quench": @@ -132,6 +132,8 @@ class Strategy: args['time'] = 1e-2 elif problem.__name__ == "run_RBC": args['time'] = 20.19 + elif problem.__name__ == "run_GS": + args['time'] = 100.0 return args @@ -152,7 +154,7 @@ class Strategy: rnd_params['iteration'] = base_params['step_params']['maxiter'] rnd_params['rank'] = num_procs - if problem.__name__ in ['run_Schroedinger', 'run_quench', 'run_AC', 'run_RBC']: + if problem.__name__ in ['run_Schroedinger', 'run_quench', 'run_AC', 'run_RBC', 'run_GS']: rnd_params['min_node'] = 1 if problem.__name__ == "run_quench": @@ -161,6 +163,8 @@ class Strategy: rnd_params['iteration'] = 5 elif problem.__name__ == 'run_RBC': rnd_params['problem_pos'] = [3, 16, 16] + elif problem.__name__ == 'run_vdp': + rnd_params['iteration'] = 5 return rnd_params @@ -188,7 +192,7 @@ class Strategy: return self.name @classmethod - def get_Tend(cls, problem, num_procs=1): + def get_Tend(cls, problem, num_procs=1, resilience_experiment=False): ''' Get the final time of runs for fault stats based on the problem @@ -200,7 +204,10 @@ class Strategy: float: Tend to put into the run ''' if problem.__name__ == "run_vdp": - return 20 # 11.5 + if resilience_experiment: + return 11.5 + else: + return 20 elif problem.__name__ == "run_piline": return 20.0 elif problem.__name__ == "run_Lorenz": @@ -213,6 +220,8 @@ class Strategy: return 0.025 elif problem.__name__ == "run_RBC": return 21 + elif problem.__name__ == "run_GS": + return 500 else: raise NotImplementedError('I don\'t have a final time for your problem!') @@ -277,7 +286,10 @@ class Strategy: } custom_description['level_params'] = {'restol': -1, 'dt': 0.1 * eps**2} elif problem.__name__ == 'run_RBC': - custom_description['level_params']['dt'] = 5e-2 + custom_description['level_params']['dt'] = 2.5e-2 if num_procs == 4 else 5e-2 + custom_description['step_params'] = {'maxiter': 5} + elif problem.__name__ == 'run_GS': + custom_description['level_params']['dt'] = 1.0 custom_description['step_params'] = {'maxiter': 5} custom_description['convergence_controllers'] = { @@ -298,6 +310,7 @@ class Strategy: 'run_quench': 150, 'run_AC': 150, 'run_RBC': 1000, + 'run_GS': 100, } custom_description['convergence_controllers'][StopAtMaxRuntime] = { @@ -319,14 +332,25 @@ class Strategy: custom_description = self.get_base_parameters(problem, num_procs) return merge_descriptions(custom_description, self.custom_description) - def get_custom_description_for_faults(self, *args, **kwargs): + def get_custom_description_for_faults(self, problem, *args, **kwargs): ''' Get a custom description based on the problem to run the fault stuff Returns: dict: Custom description ''' - return self.get_custom_description(*args, **kwargs) + custom_description = self.get_custom_description(problem, *args, **kwargs) + if problem.__name__ == "run_vdp": + custom_description['step_params'] = {'maxiter': 5} + custom_description['problem_params'] = { + 'u0': np.array([2.0, 0], dtype=np.float64), + 'mu': 5, + 'crash_at_maxiter': False, + 'newton_tol': 1e-11, + 'stop_at_nan': False, + } + custom_description['level_params'] = {'dt': 1e-2} + return custom_description def get_reference_value(self, problem, key, op, num_procs=1): """ @@ -367,7 +391,9 @@ class InexactBaseStrategy(Strategy): def get_custom_description(self, problem, num_procs=1): from pySDC.implementations.convergence_controller_classes.inexactness import NewtonInexactness - preconditioner = 'MIN-SR-NS' if problem.__name__ in ['run_Lorenz'] else 'MIN-SR-S' + preconditioner = { + 'run_Lorenz': 'MIN-SR-NS', + }.get(problem.__name__, 'MIN-SR-S') desc = {} desc['sweeper_params'] = {'QI': preconditioner} @@ -384,7 +410,7 @@ class InexactBaseStrategy(Strategy): 'maxiter': 15, } - if self.newton_inexactness and problem.__name__ not in ['run_Schroedinger', 'run_AC', 'run_RBC']: + if self.newton_inexactness and problem.__name__ not in ['run_Schroedinger', 'run_AC', 'run_RBC', 'run_GS']: if problem.__name__ == 'run_quench': inexactness_params['ratio'] = 1e-1 inexactness_params['min_tol'] = 1e-11 @@ -441,12 +467,24 @@ class BaseStrategy(Strategy): desc['level_params']['dt'] = 1e-2 * desc['problem_params']['eps'] ** 2 return desc - def get_custom_description_for_faults(self, problem, *args, **kwargs): - desc = self.get_custom_description(problem, *args, **kwargs) + def get_custom_description_for_faults(self, problem, num_procs, *args, **kwargs): + desc = self.get_custom_description(problem, num_procs, *args, **kwargs) if problem.__name__ == "run_quench": desc['level_params']['dt'] = 5.0 elif problem.__name__ == "run_AC": - desc['level_params']['dt'] = 8e-5 + desc['level_params']['dt'] = 4e-5 if num_procs == 4 else 8e-5 + elif problem.__name__ == "run_GS": + desc['level_params']['dt'] = 4e-1 + elif problem.__name__ == "run_vdp": + desc['step_params'] = {'maxiter': 5} + desc['problem_params'] = { + 'u0': np.array([2.0, 0], dtype=np.float64), + 'mu': 5, + 'crash_at_maxiter': False, + 'newton_tol': 1e-11, + 'stop_at_nan': False, + } + desc['level_params'] = {'dt': 4.5e-2} return desc def get_reference_value(self, problem, key, op, num_procs=1): @@ -495,24 +533,24 @@ class AdaptivityStrategy(Strategy): def label(self): return r'$\Delta t$-adaptivity' - def get_fixable_params(self, maxiter, **kwargs): - """ - Here faults occurring in the last iteration cannot be fixed. - - Args: - maxiter (int): Max. iterations until convergence is declared - - Returns: - (list): Contains dictionaries of keyword arguments for `FaultStats.get_mask` - """ - self.fixable += [ - { - 'key': 'iteration', - 'op': 'lt', - 'val': maxiter, - } - ] - return self.fixable + # def get_fixable_params(self, maxiter, **kwargs): + # """ + # Here faults occurring in the last iteration cannot be fixed. + + # Args: + # maxiter (int): Max. iterations until convergence is declared + + # Returns: + # (list): Contains dictionaries of keyword arguments for `FaultStats.get_mask` + # """ + # self.fixable += [ + # { + # 'key': 'iteration', + # 'op': 'lt', + # 'val': maxiter, + # } + # ] + # return self.fixable def get_custom_description(self, problem, num_procs): ''' @@ -535,13 +573,14 @@ class AdaptivityStrategy(Strategy): dt_max = np.inf dt_slope_max = np.inf dt_slope_min = 0 + beta = 0.9 if problem.__name__ == "run_piline": e_tol = 1e-7 elif problem.__name__ == "run_vdp": e_tol = 2e-5 elif problem.__name__ == "run_Lorenz": - e_tol = 1e-7 + e_tol = 1e-6 if num_procs == 4 else 1e-7 elif problem.__name__ == "run_Schroedinger": e_tol = 4e-7 elif problem.__name__ == "run_quench": @@ -560,8 +599,14 @@ class AdaptivityStrategy(Strategy): e_tol = 1e-7 # dt_max = 0.1 * base_params['problem_params']['eps'] ** 2 elif problem.__name__ == 'run_RBC': - e_tol = 1e-4 + if num_procs == 4: + e_tol = 2e-2 + else: + e_tol = 1e-4 dt_slope_min = 1 + beta = 0.5 + elif problem.__name__ == 'run_GS': + e_tol = 1e-5 else: raise NotImplementedError( @@ -573,6 +618,7 @@ class AdaptivityStrategy(Strategy): 'e_tol': e_tol, 'dt_slope_max': dt_slope_max, 'dt_rel_min_slope': dt_slope_min, + 'beta': beta, } custom_description['convergence_controllers'][StepSizeLimiter] = { 'dt_max': dt_max, @@ -603,6 +649,8 @@ class AdaptivityStrategy(Strategy): def get_custom_description_for_faults(self, problem, num_procs, *args, **kwargs): from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeLimiter from pySDC.implementations.convergence_controller_classes.adaptivity import Adaptivity + from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeSlopeLimiter + from pySDC.projects.Resilience.RBC import ReachTendExactly desc = self.get_custom_description(problem, num_procs, *args, **kwargs) if problem.__name__ == "run_quench": @@ -610,6 +658,23 @@ class AdaptivityStrategy(Strategy): desc['convergence_controllers'][Adaptivity]['e_tol'] = 1e-6 elif problem.__name__ == "run_AC": desc['convergence_controllers'][Adaptivity]['e_tol'] = 1e-5 + elif problem.__name__ == "run_GS": + desc['convergence_controllers'][Adaptivity]['e_tol'] = 2e-6 + elif problem.__name__ == "run_vdp": + desc['step_params'] = {'maxiter': 5} + desc['sweeper_params'] = {'num_nodes': 3, 'QI': 'LU'} + desc['problem_params'] = { + 'u0': np.array([2.0, 0], dtype=np.float64), + 'mu': 5, + 'crash_at_maxiter': True, + 'newton_tol': 1e-8, + 'stop_at_nan': True, + 'relative_tolerance': False, + } + desc['level_params'] = {'dt': 8e-3} + desc['convergence_controllers'][Adaptivity]['e_tol'] = 2e-7 + desc['convergence_controllers'][ReachTendExactly] = {'Tend': 11.5} + # desc['convergence_controllers'][StepSizeSlopeLimiter] = {'dt_slope_min': 1/4, 'dt_slope_max': 4} return desc @@ -778,6 +843,8 @@ class IterateStrategy(Strategy): restol = 1e-11 elif problem.__name__ == "run_RBC": restol = 1e-4 + elif problem.__name__ == "run_GS": + restol = 1e-4 else: raise NotImplementedError( 'I don\'t have a residual tolerance for your problem. Please add one to the \ @@ -851,17 +918,34 @@ class kAdaptivityStrategy(IterateStrategy): desc['level_params']['dt'] = 0.4 * desc['problem_params']['eps'] ** 2 / 8.0 elif problem.__name__ == "run_RBC": desc['level_params']['dt'] = 7e-2 + desc['level_params']['restol'] = 1e-6 + desc['level_params']['e_tol'] = 1e-7 + elif problem.__name__ == "run_GS": + desc['level_params']['dt'] = 1.0 desc['level_params']['restol'] = 1e-9 return desc - def get_custom_description_for_faults(self, problem, *args, **kwargs): - desc = self.get_custom_description(problem, *args, **kwargs) + def get_custom_description_for_faults(self, problem, num_procs, *args, **kwargs): + desc = self.get_custom_description(problem, num_procs, *args, **kwargs) if problem.__name__ == 'run_quench': desc['level_params']['dt'] = 5.0 elif problem.__name__ == 'run_AC': - desc['level_params']['dt'] = 5e-4 + desc['level_params']['dt'] = 4e-4 if num_procs == 4 else 5e-4 + desc['level_params']['restol'] = 1e-5 if num_procs == 4 else 1e-11 elif problem.__name__ == 'run_RBC': - desc['level_params']['restol'] = 1e-6 + desc['level_params']['restol'] = 1e-3 if num_procs == 4 else 1e-6 + elif problem.__name__ == 'run_Lorenz': + desc['level_params']['dt'] = 8e-3 + elif problem.__name__ == "run_vdp": + desc['sweeper_params'] = {'num_nodes': 3} + desc['problem_params'] = { + 'u0': np.array([2.0, 0], dtype=np.float64), + 'mu': 5, + 'crash_at_maxiter': False, + 'newton_tol': 1e-11, + 'stop_at_nan': False, + } + desc['level_params'] = {'dt': 4.0e-2, 'restol': 1e-7} return desc def get_reference_value(self, problem, key, op, num_procs=1): @@ -920,14 +1004,15 @@ class HotRodStrategy(Strategy): base_params = super().get_custom_description(problem, num_procs) if problem.__name__ == "run_vdp": - if num_procs == 4: - HotRod_tol = 1.800804e-04 - elif num_procs == 5: - HotRod_tol = 9.329361e-05 - else: # 1 process - HotRod_tol = 1.347949e-06 - HotRod_tol = 7e-6 if num_procs > 1 else 5e-7 - maxiter = 4 + # if num_procs == 4: + # HotRod_tol = 1.800804e-04 + # elif num_procs == 5: + # HotRod_tol = 9.329361e-05 + # else: # 1 process + # HotRod_tol = 1.347949e-06 + # HotRod_tol = 7e-6 if num_procs > 1 else 5e-7 + HotRod_tol = 7.2e-05 + maxiter = 6 elif problem.__name__ == "run_Lorenz": if num_procs == 5: HotRod_tol = 9.539348e-06 @@ -956,7 +1041,10 @@ class HotRodStrategy(Strategy): HotRod_tol = 9.564437e-06 maxiter = 6 elif problem.__name__ == 'run_RBC': - HotRod_tol = 6.34e-6 + HotRod_tol = 3e-4 if num_procs == 4 else 6.34e-6 + maxiter = 6 + elif problem.__name__ == 'run_GS': + HotRod_tol = 3.22e-5 maxiter = 6 else: raise NotImplementedError( @@ -986,6 +1074,20 @@ class HotRodStrategy(Strategy): desc = self.get_custom_description(problem, *args, **kwargs) if problem.__name__ == "run_quench": desc['level_params']['dt'] = 5.0 + elif problem.__name__ == "run_AC": + desc['level_params']['dt'] = 8e-5 + elif problem.__name__ == "run_GS": + desc['level_params']['dt'] = 4e-1 + elif problem.__name__ == "run_vdp": + desc['step_params'] = {'maxiter': 6} + desc['problem_params'] = { + 'u0': np.array([2.0, 0], dtype=np.float64), + 'mu': 5, + 'crash_at_maxiter': False, + 'newton_tol': 1e-11, + 'stop_at_nan': False, + } + desc['level_params'] = {'dt': 4.5e-2} return desc def get_reference_value(self, problem, key, op, num_procs=1): @@ -1383,6 +1485,63 @@ class ARKStrategy(AdaptivityStrategy): super().get_reference_value(problem, key, op, num_procs) +class ARK3_CFL_Strategy(BaseStrategy): + """ + This is special for RBC with CFL number for accuracy + """ + + def __init__(self, **kwargs): + ''' + Initialization routine + ''' + from pySDC.implementations.problem_classes.RayleighBenard import CFLLimit + + super().__init__(**kwargs) + self.color = 'maroon' + self.marker = '<' + self.name = 'ARK3' + self.bar_plot_x_label = 'ARK3' + self.precision_parameter = 'cfl' + self.precision_parameter_loc = ['convergence_controllers', CFLLimit, 'cfl'] + self.max_steps = 1e5 + + @property + def label(self): + return 'ARK3' + + def get_custom_description(self, problem, num_procs): + ''' + Args: + problem: A function that runs a pySDC problem, see imports for available problems + num_procs (int): Number of processes you intend to run with + + Returns: + The custom descriptions you can supply to the problem when running it + ''' + from pySDC.implementations.convergence_controller_classes.basic_restarting import BasicRestarting + from pySDC.implementations.sweeper_classes.Runge_Kutta import ARK3 + from pySDC.implementations.problem_classes.RayleighBenard import CFLLimit + from pySDC.implementations.convergence_controller_classes.step_size_limiter import StepSizeSlopeLimiter + + desc = super().get_custom_description(problem, num_procs) + + rk_params = { + 'step_params': {'maxiter': 1}, + 'sweeper_class': ARK3, + 'convergence_controllers': { + CFLLimit: { + 'cfl': 0.5, + 'dt_max': 1.0, + }, + StepSizeSlopeLimiter: {'dt_rel_min_slope': 0.2}, + }, + } + + custom_description = merge_descriptions(desc, rk_params) + + return custom_description + + class ESDIRKStrategy(AdaptivityStrategy): ''' ESDIRK5(3) @@ -1515,7 +1674,7 @@ class ERKStrategy(DIRKStrategy): @property def label(self): - return 'CP5(4)' + return 'CK5(4)' def get_random_params(self, problem, num_procs): ''' @@ -1905,6 +2064,7 @@ class AdaptivityPolynomialError(InexactBaseStrategy): abort_at_growing_residual = True level_params = {} problem_params = {} + beta = 0.9 if problem.__name__ == "run_vdp": e_tol = 6e-4 @@ -1926,16 +2086,23 @@ class AdaptivityPolynomialError(InexactBaseStrategy): restol_rel = 1e-1 elif problem.__name__ == "run_AC": e_tol = 1.0e-4 - restol_rel = 1e-3 + restol_rel = 1e-3 if num_procs == 4 else 1e-3 # dt_max = 0.1 * base_params['problem_params']['eps'] ** 2 elif problem.__name__ == "run_RBC": - e_tol = 5e-3 + e_tol = 5e-2 if num_procs == 4 else 5e-3 dt_slope_min = 1.0 abort_at_growing_residual = False - restol_rel = 1e-3 + restol_rel = 1e-2 if num_procs == 4 else 1e-4 restol_max = 1e-1 - restol_min = 5e-7 + restol_min = 5e-8 self.max_slope = 4 + beta = 0.5 + level_params['e_tol'] = 1e-5 + elif problem.__name__ == 'run_GS': + e_tol = 1e-4 + restol_rel = 4e-3 + restol_max = 1e-4 + restol_min = 1e-9 else: raise NotImplementedError( 'I don\'t have a tolerance for adaptivity for your problem. Please add one to the\ @@ -1952,6 +2119,7 @@ class AdaptivityPolynomialError(InexactBaseStrategy): 'factor_if_not_converged': self.max_slope, 'interpolate_between_restarts': self.interpolate_between_restarts, 'abort_at_growing_residual': abort_at_growing_residual, + 'beta': beta, }, StepSizeLimiter: { 'dt_max': dt_max, @@ -1962,10 +2130,11 @@ class AdaptivityPolynomialError(InexactBaseStrategy): } custom_description['level_params'] = level_params custom_description['problem_params'] = problem_params + return merge_descriptions(base_params, custom_description) - def get_custom_description_for_faults(self, problem, *args, **kwargs): - desc = self.get_custom_description(problem, *args, **kwargs) + def get_custom_description_for_faults(self, problem, num_procs, *args, **kwargs): + desc = self.get_custom_description(problem, num_procs, *args, **kwargs) if problem.__name__ == "run_quench": from pySDC.implementations.convergence_controller_classes.adaptivity import AdaptivityPolynomialError @@ -1974,7 +2143,31 @@ class AdaptivityPolynomialError(InexactBaseStrategy): elif problem.__name__ == "run_AC": from pySDC.implementations.convergence_controller_classes.adaptivity import AdaptivityPolynomialError - desc['convergence_controllers'][AdaptivityPolynomialError]['e_tol'] = 1e-3 + desc['convergence_controllers'][AdaptivityPolynomialError]['e_tol'] = 6e-3 if num_procs == 4 else 1e-3 + if num_procs == 4: + desc['step_params'] = {'maxiter': 50} + elif problem.__name__ == "run_Lorenz": + from pySDC.implementations.convergence_controller_classes.adaptivity import AdaptivityPolynomialError + + desc['convergence_controllers'][AdaptivityPolynomialError]['e_tol'] = 2e-4 + desc['convergence_controllers'][AdaptivityPolynomialError]['restol_min'] = 1e-11 + desc['convergence_controllers'][AdaptivityPolynomialError]['restol_rel'] = 1e-11 + elif problem.__name__ == "run_vdp": + from pySDC.implementations.convergence_controller_classes.adaptivity import AdaptivityPolynomialError + from pySDC.implementations.convergence_controller_classes.inexactness import NewtonInexactness + + desc['step_params'] = {'maxiter': 16} + desc['problem_params'] = { + 'u0': np.array([2.0, 0], dtype=np.float64), + 'mu': 5, + 'crash_at_maxiter': False, + 'newton_tol': 1e-11, + 'stop_at_nan': False, + } + desc['convergence_controllers'][AdaptivityPolynomialError]['e_tol'] = 5e-4 + desc['convergence_controllers'][AdaptivityPolynomialError]['restol_rel'] = 8e-5 + desc['convergence_controllers'].pop(NewtonInexactness) + desc['level_params'] = {'dt': 4.5e-2} return desc def get_random_params(self, problem, num_procs): @@ -1993,7 +2186,7 @@ class AdaptivityPolynomialError(InexactBaseStrategy): if problem.__name__ == "run_quench": rnd_params['iteration'] = 1 elif problem.__name__ == 'run_Lorenz': - rnd_params['iteration'] = 4 + rnd_params['iteration'] = 5 return rnd_params def get_reference_value(self, problem, key, op, num_procs=1): diff --git a/pySDC/projects/Resilience/tests/test_ReachTendExactly.py b/pySDC/projects/Resilience/tests/test_ReachTendExactly.py new file mode 100644 index 0000000000000000000000000000000000000000..1c60d574e12b3a5df9c549f9681091ed70056949 --- /dev/null +++ b/pySDC/projects/Resilience/tests/test_ReachTendExactly.py @@ -0,0 +1,73 @@ +import pytest + + +@pytest.mark.mpi4py +@pytest.mark.parametrize('Tend', [1.2345, 1, 4.0 / 3.0]) +@pytest.mark.parametrize('dt', [0.1, 1.0 / 3.0, 0.999999]) +@pytest.mark.parametrize('num_procs', [1, 2]) +def test_ReachTendExactly(Tend, dt, num_procs): + if dt * num_procs > Tend: + return None + + from pySDC.projects.Resilience.reachTendExactly import ReachTendExactly + from pySDC.implementations.hooks.log_solution import LogSolution + from pySDC.implementations.problem_classes.Van_der_Pol_implicit import vanderpol + from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit + from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI + from pySDC.helpers.stats_helper import get_sorted + import numpy as np + + level_params = {} + level_params['dt'] = dt + + sweeper_params = {} + sweeper_params['quad_type'] = 'RADAU-RIGHT' + sweeper_params['num_nodes'] = 2 + sweeper_params['QI'] = 'LU' + + problem_params = { + 'mu': 0.0, + 'newton_tol': 1e-1, + 'newton_maxiter': 9, + 'u0': np.array([2.0, 0.0]), + 'relative_tolerance': True, + } + + step_params = {} + step_params['maxiter'] = 2 + + convergence_controllers = {ReachTendExactly: {'Tend': Tend}} + + controller_params = {} + controller_params['logger_level'] = 15 + controller_params['hook_class'] = LogSolution + controller_params['mssdc_jac'] = False + + description = {} + description['problem_class'] = vanderpol + 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'] = convergence_controllers + + t0 = 0.0 + + controller = controller_nonMPI(num_procs=num_procs, controller_params=controller_params, description=description) + + P = controller.MS[0].levels[0].prob + uinit = P.u_exact(t0) + + uend, stats = controller.run(u0=uinit, t0=t0, Tend=Tend) + + u = get_sorted(stats, type='u') + t_last = u[-1][0] + + assert np.isclose( + t_last, Tend, atol=1e-10 + ), f'Expected {Tend=}, but got {t_last}, which is off by {t_last - Tend:.8e}' + + +if __name__ == '__main__': + test_ReachTendExactly(1.2345, 1.0 / 3.0, 2) diff --git a/pySDC/projects/Resilience/work_precision.py b/pySDC/projects/Resilience/work_precision.py index 86f8600ed849943924d1d9503fcbfb4119004020..2a1c448aa304669cc008ec14951ea7fc6e24cbab 100644 --- a/pySDC/projects/Resilience/work_precision.py +++ b/pySDC/projects/Resilience/work_precision.py @@ -13,6 +13,7 @@ from pySDC.projects.Resilience.Schroedinger import run_Schroedinger from pySDC.projects.Resilience.quench import run_quench from pySDC.projects.Resilience.AC import run_AC from pySDC.projects.Resilience.RBC import run_RBC +from pySDC.projects.Resilience.GS import run_GS from pySDC.helpers.stats_helper import get_sorted, filter_stats from pySDC.helpers.plot_helper import setup_mpl, figsize_by_journal @@ -23,6 +24,11 @@ LOG_TO_FILE = False logging.getLogger('matplotlib.texmanager').setLevel(90) +Tends = {'run_RBC': 16.0, 'run_Lorenz': 2.0} +t0s = { + 'run_RBC': 10.0, +} + def std_log(x): return np.std(np.log(x)) @@ -72,14 +78,6 @@ def get_forbidden_combinations(problem, strategy, **kwargs): return False -Tends = { - 'run_RBC': 16, -} -t0s = { - 'run_RBC': 0.2, -} - - def single_run( problem, strategy, @@ -161,7 +159,16 @@ def single_run( t_last = perf_counter() # record all the metrics - stats_all = filter_stats(stats, comm=comm_sweep) + if comm_sweep.size > 1: + try: + stats_all = filter_stats(stats, comm=comm_sweep) + except MPI.Exception: + for key in MAPPINGS.keys(): + data[key] += [np.nan] + return stats + + else: + stats_all = stats comm_sweep.Free() for key, mapping in MAPPINGS.items(): @@ -289,7 +296,13 @@ def record_work_precision( else: exponents = [-3, -2, -1, 0, 0.2, 0.8, 1][::-1] if problem.__name__ == 'run_RBC': - exponents = [1, 0, -1, -2, -3, -4] + exponents = [1, 0, -0.5, -1, -2] + if problem.__name__ == 'run_GS': + exponents = [-2, -1, 0, 1, 2, 3][::-1] + if problem.__name__ == 'run_Lorenz': + exponents = [0, 1, 2, 3][::-1] + if type(strategy).__name__ in ["AdaptivityStrategy"]: + exponents = [0, 1, 2, 3, 4, 5][::-1] elif param == 'dt': power = 2.0 exponents = [-1, 0, 1, 2, 3][::-1] @@ -298,6 +311,9 @@ def record_work_precision( exponents = [-2, -1, 0, 1, 2, 3] if problem.__name__ == 'run_vdp': exponents = [-4, -3, -2, -1, 0, 1] + elif param == 'cfl': + power = 2 + exponents = [-3, -2, -1, 0, 1] else: raise NotImplementedError(f"I don't know how to get default value for parameter \"{param}\"") @@ -312,7 +328,13 @@ def record_work_precision( param_range = [1.25, 2.5, 5.0, 10.0, 20.0][::-1] if problem.__name__ == 'run_RBC': if param == 'dt': - param_range = [2e-1, 1e-1, 8e-2, 6e-2] + param_range = [8e-2, 6e-2, 4e-2, 3e-2, 2e-2] + if problem.__name__ == 'run_GS': + if param == 'dt': + param_range = [2, 1, 0.5, 0.1] + if problem.__name__ == 'run_Lorenz': + if param == 'dt': + param_range = [5e-2, 2e-2, 1e-2, 5e-3] # run multiple times with different parameters for i in range(len(param_range)): @@ -495,6 +517,69 @@ def plot_work_precision( if meta.get('runs', None) == 1: ax.text(0.1, 0.2, "No sampling!", transform=ax.transAxes) + if problem.__name__ == 'run_vdp': + if mode == 'parallel_efficiency': + # ax.set_xticks([6e-1, 2e0]) + ax.set_xticks( + ticks=[ + 0.4, + 5e-1, + 6e-1, + 7e-1, + 8e-1, + 9e-1, + 2e0, + ], + labels=[''] + + [r'$5\times 10^{-1}$'] + + [ + '', + ] + * 4 + + [r'$2\times 10^0$'], + minor=True, + ) + elif mode == 'RK_comp': + ax.set_xticks( + ticks=[ + 5e-1, + 6e-1, + 7e-1, + 8e-1, + 9e-1, + 2e0, + ], + labels=[r'$5\times 10^{-1}$'] + + [ + '', + ] + * 4 + + [r'$2\times 10^0$'], + minor=True, + ) + elif problem.__name__ == 'run_quench': + if mode == 'RK_comp': + ax.set_xticks( + ticks=[ + 0.2, + 0.3, + 0.4, + 5e-1, + 6e-1, + 7e-1, + 8e-1, + 9e-1, + 2e0, + ], + labels=[''] + + [r'$3\times 10^{-1}$'] + + [ + '', + ] + * 7, + minor=True, + ) + def plot_parallel_efficiency_diagonalSDC( ax, work_key, precision_key, num_procs_sweeper, num_procs=1, **kwargs @@ -589,6 +674,8 @@ def decorate_panel(ax, problem, work_key, precision_key, num_procs=1, title_only 'run_Schroedinger': r'Schr\"odinger', 'run_quench': 'Quench', 'run_AC': 'Allen-Cahn', + 'run_RBC': 'Rayleigh-Benard', + 'run_GS': 'Gray-Scott', } ax.set_title(titles.get(problem.__name__, '')) @@ -788,8 +875,8 @@ def get_configs(mode, problem): 'sweeper_params': {'num_nodes': 3, 'quad_type': 'RADAU-RIGHT'}, }, 'strategies': [ - AdaptivityStrategy(useMPI=True), BaseStrategy(useMPI=True), + AdaptivityStrategy(useMPI=True), ], } @@ -803,55 +890,166 @@ def get_configs(mode, problem): ESDIRKStrategy, ARKStrategy, AdaptivityPolynomialError, + ARK3_CFL_Strategy, ) - if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_RBC']: + if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_RBC', 'run_GS']: from pySDC.implementations.sweeper_classes.imex_1st_order_MPI import imex_1st_order_MPI as parallel_sweeper else: from pySDC.implementations.sweeper_classes.generic_implicit_MPI import ( generic_implicit_MPI as parallel_sweeper, ) - newton_inexactness = False if problem.__name__ in ['run_vdp', 'run_RBC'] else True + newton_inexactness = False if problem.__name__ in ['run_vdp', 'run_RBC', 'run_GS'] else True desc = {} desc['sweeper_params'] = {'num_nodes': 3, 'QI': 'IE', 'QE': "EE"} desc['step_params'] = {'maxiter': 5} + num_procs_dt = { + 'run_RBC': 1, + }.get(problem.__name__, 4) desc_poly = {} desc_poly['sweeper_class'] = parallel_sweeper + num_procs_dt_k = 3 ls = { - 1: '-', + 1: '--', 2: '--', - 3: '-.', - 4: ':', - 5: ':', + 3: '-', + 4: '-', + 5: '-', + 12: ':', } RK_strategies = [] if problem.__name__ in ['run_Lorenz']: RK_strategies.append(ERKStrategy(useMPI=True)) - if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_RBC']: + desc_poly['sweeper_params'] = {'QI': 'MIN-SR-S', 'QE': 'PIC'} + desc['sweeper_params']['QI'] = 'MIN-SR-S' + desc['sweeper_params']['QE'] = 'PIC' + if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_GS']: RK_strategies.append(ARKStrategy(useMPI=True)) + elif problem.__name__ == 'run_RBC': + RK_strategies.append(ARK3_CFL_Strategy(useMPI=True)) + desc['sweeper_params']['num_nodes'] = 2 + desc['sweeper_params']['QI'] = 'LU' + desc['sweeper_params']['QE'] = 'PIC' + desc['step_params']['maxiter'] = 3 + + desc_poly['sweeper_params'] = {'num_nodes': 2, 'QI': 'MIN-SR-S'} + num_procs_dt_k = 2 else: RK_strategies.append(ESDIRKStrategy(useMPI=True)) - configurations[3] = { - 'custom_description': desc_poly, - 'strategies': [AdaptivityPolynomialError(useMPI=True, newton_inexactness=newton_inexactness)], + configurations[-1] = { + 'strategies': RK_strategies, 'num_procs': 1, - 'num_procs_sweeper': 3, - 'plotting_params': {'label': r'$\Delta t$-$k$-adaptivity $N$=1x3'}, } + if problem.__name__ == 'run_Lorenz': + configurations[3] = { + 'custom_description': desc_poly, + 'strategies': [AdaptivityPolynomialError(useMPI=True, newton_inexactness=newton_inexactness)], + 'num_procs': 4, + 'num_procs_sweeper': num_procs_dt_k, + 'plotting_params': { + 'label': rf'$\Delta t$-$k$-adaptivity $N$=4x{num_procs_dt_k}', + 'ls': ls[num_procs_dt_k * 4], + }, + } + else: + configurations[3] = { + 'custom_description': desc_poly, + 'strategies': [AdaptivityPolynomialError(useMPI=True, newton_inexactness=newton_inexactness)], + 'num_procs': 1, + 'num_procs_sweeper': num_procs_dt_k, + 'plotting_params': { + 'label': rf'$\Delta t$-$k$-adaptivity $N$=1x{num_procs_dt_k}', + 'ls': ls[num_procs_dt_k], + }, + } + if problem.__name__ in ['run_Lorenz']: + configurations[2] = { + 'strategies': [AdaptivityStrategy(useMPI=True)], + 'custom_description': {**desc, 'sweeper_class': parallel_sweeper}, + 'num_procs': num_procs_dt, + 'num_procs_sweeper': num_procs_dt_k, + 'plotting_params': { + 'label': rf'$\Delta t$-adaptivity $N$={num_procs_dt}x3', + 'ls': ls[num_procs_dt * num_procs_dt_k], + }, + } + else: + configurations[2] = { + 'strategies': [AdaptivityStrategy(useMPI=True)], + 'custom_description': desc, + 'num_procs': num_procs_dt, + 'plotting_params': {'label': rf'$\Delta t$-adaptivity $N$={num_procs_dt}x1', 'ls': ls[num_procs_dt]}, + } + + elif mode == 'RK_comp_high_order_RBC': + """ + Compare parallel adaptive SDC to Runge-Kutta at order five for RBC problem + """ + from pySDC.projects.Resilience.strategies import ( + AdaptivityStrategy, + ERKStrategy, + ESDIRKStrategy, + ARKStrategy, + AdaptivityPolynomialError, + ARK3_CFL_Strategy, + ) + + assert problem.__name__ == 'run_RBC' + + from pySDC.implementations.sweeper_classes.imex_1st_order_MPI import imex_1st_order_MPI as parallel_sweeper + + newton_inexactness = False + + desc = {} + desc['sweeper_params'] = {'num_nodes': 3, 'QI': 'IE', 'QE': "EE"} + desc['step_params'] = {'maxiter': 5} + num_procs_dt = 1 + + desc_poly = {} + desc_poly['sweeper_class'] = parallel_sweeper + num_procs_dt_k = 3 + + ls = { + 1: '--', + 2: '--', + 3: '-', + 4: '-', + 5: '-', + 12: ':', + } + RK_strategies = [ARK3_CFL_Strategy(useMPI=True)] + desc['sweeper_params']['num_nodes'] = 3 + desc['sweeper_params']['QI'] = 'LU' + desc['sweeper_params']['QE'] = 'PIC' + desc['step_params']['maxiter'] = 5 + + desc_poly['sweeper_params'] = {'num_nodes': 3, 'QI': 'MIN-SR-S'} + num_procs_dt_k = 3 + configurations[-1] = { 'strategies': RK_strategies, 'num_procs': 1, } + configurations[3] = { + 'custom_description': desc_poly, + 'strategies': [AdaptivityPolynomialError(useMPI=True, newton_inexactness=newton_inexactness)], + 'num_procs': 1, + 'num_procs_sweeper': num_procs_dt_k, + 'plotting_params': { + 'label': rf'$\Delta t$-$k$-adaptivity $N$=1x{num_procs_dt_k}', + 'ls': ls[num_procs_dt_k], + }, + } configurations[2] = { 'strategies': [AdaptivityStrategy(useMPI=True)], 'custom_description': desc, - 'num_procs': 4, - 'plotting_params': {'label': r'$\Delta t$-adaptivity $N$=4x1'}, + 'num_procs': num_procs_dt, + 'plotting_params': {'label': rf'$\Delta t$-adaptivity $N$={num_procs_dt}x1', 'ls': ls[num_procs_dt]}, } elif mode == 'parallel_efficiency': @@ -860,7 +1058,7 @@ def get_configs(mode, problem): """ from pySDC.projects.Resilience.strategies import AdaptivityStrategy, AdaptivityPolynomialError - if problem.__name__ in ['run_Schroedinger', 'run_AC']: + if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_GS', 'run_RBC']: from pySDC.implementations.sweeper_classes.imex_1st_order_MPI import imex_1st_order_MPI as parallel_sweeper else: from pySDC.implementations.sweeper_classes.generic_implicit_MPI import ( @@ -871,6 +1069,10 @@ def get_configs(mode, problem): desc['sweeper_params'] = {'num_nodes': 3, 'QI': 'IE', 'QE': 'EE'} desc['step_params'] = {'maxiter': 5} + if problem.__name__ in ['run_RBC']: + desc['sweeper_params']['QE'] = 'PIC' + desc['sweeper_params']['QI'] = 'LU' + ls = { 1: '-', 2: '--', @@ -907,13 +1109,137 @@ def get_configs(mode, problem): }, } - configurations[num_procs * 200 + 79] = { + configurations[200 + 79] = { 'strategies': [ AdaptivityPolynomialError(useMPI=True, newton_inexactness=newton_inexactness, linear_inexactness=True) ], 'num_procs': 1, } + elif mode == 'parallel_efficiency_dt': + """ + Compare parallel runs of the step size adaptive SDC + """ + from pySDC.projects.Resilience.strategies import AdaptivityStrategy + if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_GS', 'run_RBC']: + from pySDC.implementations.sweeper_classes.imex_1st_order_MPI import imex_1st_order_MPI as parallel_sweeper + else: + from pySDC.implementations.sweeper_classes.generic_implicit_MPI import ( + generic_implicit_MPI as parallel_sweeper, + ) + + desc = {} + desc['sweeper_params'] = {'num_nodes': 3, 'QI': 'IE', 'QE': 'EE'} + desc['step_params'] = {'maxiter': 5} + + if problem.__name__ in ['run_RBC']: + desc['sweeper_params']['QE'] = 'PIC' + desc['sweeper_params']['QI'] = 'LU' + + desc_serial = { + 'step_params': {'maxiter': 5}, + 'sweeper_params': {'num_nodes': 3, 'quad_type': 'RADAU-RIGHT'}, + } + + ls = { + 1: '-', + 2: '--', + 3: '-.', + 4: '--', + 5: ':', + 12: ':', + } + + newton_inexactness = False if problem.__name__ in ['run_vdp'] else True + + for num_procs in [4, 1]: + configurations[num_procs] = { + 'strategies': [AdaptivityStrategy(useMPI=True)], + 'custom_description': desc.copy() if num_procs > 1 else desc_serial, + 'num_procs': num_procs, + 'plotting_params': { + 'ls': ls.get(num_procs, '-'), + 'label': rf'$\Delta t$-adaptivity $N$={num_procs}x1', + }, + } + configurations[num_procs * 200 + 79] = { + 'custom_description': { + 'sweeper_class': parallel_sweeper, + 'sweeper_params': {'QI': 'MIN-SR-S', 'QE': 'PIC'}, + 'step_params': {'maxiter': 5}, + }, + 'strategies': [AdaptivityStrategy(useMPI=True)], + 'num_procs_sweeper': 3, + 'num_procs': num_procs, + 'plotting_params': { + 'ls': ls.get(num_procs * 3, '-'), + 'label': rf'$\Delta t$-adaptivity $N$={num_procs}x3', + }, + } + elif mode == 'parallel_efficiency_dt_k': + """ + Compare parallel runs of the step size adaptive SDC + """ + from pySDC.projects.Resilience.strategies import AdaptivityPolynomialError + + if problem.__name__ in ['run_Schroedinger', 'run_AC', 'run_GS', 'run_RBC']: + from pySDC.implementations.sweeper_classes.imex_1st_order_MPI import imex_1st_order_MPI as parallel_sweeper + else: + from pySDC.implementations.sweeper_classes.generic_implicit_MPI import ( + generic_implicit_MPI as parallel_sweeper, + ) + + ls = { + 1: '-', + 2: '--', + 3: '-.', + 4: '--', + 5: ':', + 12: ':', + } + + QI = { + (1, 3, 'run_Lorenz'): 'MIN-SR-NS', + (1, 1, 'run_Lorenz'): 'MIN-SR-NS', + (4, 1, 'run_Lorenz'): 'IE', + } + + newton_inexactness = False if problem.__name__ in ['run_vdp'] else True + + for num_procs in [4, 1]: + configurations[num_procs * 100 + 79] = { + 'custom_description': { + 'sweeper_class': parallel_sweeper, + 'sweeper_params': {'QI': QI.get((num_procs, 3, problem.__name__), 'MIN-SR-S'), 'QE': 'PIC'}, + }, + 'strategies': [ + AdaptivityPolynomialError( + useMPI=True, newton_inexactness=newton_inexactness, linear_inexactness=True + ) + ], + 'num_procs_sweeper': 3, + 'num_procs': num_procs, + 'plotting_params': { + 'ls': ls.get(num_procs * 3, '-'), + 'label': rf'$\Delta t$-$k$-adaptivity $N$={num_procs}x3', + }, + } + configurations[num_procs * 200 + 79] = { + 'custom_description': { + 'sweeper_params': {'QI': QI.get((num_procs, 1, problem.__name__), 'MIN-SR-S'), 'QE': 'PIC'}, + }, + 'strategies': [ + AdaptivityPolynomialError( + useMPI=True, newton_inexactness=newton_inexactness, linear_inexactness=True + ) + ], + 'num_procs_sweeper': 1, + 'num_procs': num_procs, + 'plotting_params': { + 'ls': ls.get(num_procs, '-'), + 'label': rf'$\Delta t$-$k$-adaptivity $N$={num_procs}x1', + }, + } elif mode == 'interpolate_between_restarts': """ Compare adaptivity with interpolation between restarts and without @@ -1298,21 +1624,31 @@ def get_configs(mode, problem): return configurations -def get_fig(x=1, y=1, **kwargs): # pragma: no cover +def get_fig(x=1, y=1, target='adaptivity', **kwargs): # pragma: no cover """ Get a figure to plot in. Args: x (int): How many panels in horizontal direction you want y (int): How many panels in vertical direction you want + target (str): Where the plot is supposed to end up Returns: matplotlib.pyplot.Figure """ width = 1.0 ratio = 1.0 if y == 2 else 0.5 + if target == 'adaptivity': + journal = 'Springer_Numerical_Algorithms' + elif target == 'thesis': + journal = 'TUHH_thesis' + elif target == 'talk': + journal = 'JSC_beamer' + else: + raise NotImplementedError + keyword_arguments = { - 'figsize': figsize_by_journal('Springer_Numerical_Algorithms', width, ratio), + 'figsize': figsize_by_journal(journal, width, ratio), 'layout': 'constrained', **kwargs, } @@ -1363,7 +1699,9 @@ def save_fig( print(f'Stored figure \"{path}\"') -def all_problems(mode='compare_strategies', plotting=True, base_path='data', **kwargs): # pragma: no cover +def all_problems( + mode='compare_strategies', plotting=True, base_path='data', target='adaptivity', **kwargs +): # pragma: no cover """ Make a plot comparing various strategies for all problems. @@ -1375,7 +1713,10 @@ def all_problems(mode='compare_strategies', plotting=True, base_path='data', **k None """ - fig, axs = get_fig(2, 2) + if target == 'talk': + fig, axs = get_fig(4, 1, target=target) + else: + fig, axs = get_fig(2, 2, target=target) shared_params = { 'work_key': 'k_SDC', @@ -1388,7 +1729,12 @@ def all_problems(mode='compare_strategies', plotting=True, base_path='data', **k **kwargs, } - problems = [run_vdp, run_quench, run_Schroedinger, run_AC] + if target == 'adaptivity': + problems = [run_vdp, run_quench, run_Schroedinger, run_AC] + elif target in ['thesis', 'talk']: + problems = [run_vdp, run_Lorenz, run_GS, run_RBC] + else: + raise NotImplementedError logger.log(26, f"Doing for all problems {mode}") for i in range(len(problems)): @@ -1404,17 +1750,18 @@ def all_problems(mode='compare_strategies', plotting=True, base_path='data', **k if plotting and shared_params['comm_world'].rank == 0: ncols = { 'parallel_efficiency': 2, + 'parallel_efficiency_dt': 2, + 'parallel_efficiency_dt_k': 2, 'RK_comp': 2, } - y_right_dt_fixed = [1e18, 4e1, 5, 1e8] - y_right_dt = [1e-1, 1e4, 1, 2e-2] - y_right_dtk = [1e-4, 1e4, 1e-2, 1e-3] + if target == 'talk': + _ncols = 4 + else: + _ncols = ncols.get(mode, None) if shared_params['work_key'] == 'param': - for ax, yRfixed, yRdt, yRdtk in zip(fig.get_axes(), y_right_dt_fixed, y_right_dt, y_right_dtk): - add_order_line(ax, 1, '--', yRdt, marker=None) - add_order_line(ax, 5 / 4, ':', yRdtk, marker=None) - add_order_line(ax, 5, '-.', yRfixed, marker=None) + for ax, prob in zip(fig.get_axes(), problems): + add_param_order_lines(ax, prob) save_fig( fig=fig, name=mode, @@ -1422,10 +1769,46 @@ def all_problems(mode='compare_strategies', plotting=True, base_path='data', **k precision_key=shared_params['precision_key'], legend=True, base_path=base_path, - ncols=ncols.get(mode, None), + ncols=_ncols, ) +def add_param_order_lines(ax, problem): + if problem.__name__ == 'run_vdp': + yRfixed = 1e18 + yRdt = 1e-1 + yRdtk = 1e-4 + elif problem.__name__ == 'run_quench': + yRfixed = 4e1 + yRdt = 1e4 + yRdtk = 1e4 + elif problem.__name__ == 'run_Schroedinger': + yRfixed = 5 + yRdt = 1 + yRdtk = 1e-2 + elif problem.__name__ == 'run_AC': + yRfixed = 1e8 + yRdt = 2e-2 + yRdtk = 1e-3 + elif problem.__name__ == 'run_Lorenz': + yRfixed = 1e1 + yRdt = 2e-2 + yRdtk = 7e-4 + elif problem.__name__ == 'run_RBC': + yRfixed = 1e-6 + yRdt = 4e-5 + yRdtk = 8e-6 + elif problem.__name__ == 'run_GS': + yRfixed = 4e-3 + yRdt = 5e0 + yRdtk = 8e-1 + else: + return None + add_order_line(ax, 1, '--', yRdt, marker=None) + add_order_line(ax, 5 / 4, ':', yRdtk, marker=None) + add_order_line(ax, 5, '-.', yRfixed, marker=None) + + def ODEs(mode='compare_strategies', plotting=True, base_path='data', **kwargs): # pragma: no cover """ Make a plot comparing various strategies for the two ODEs. @@ -1473,7 +1856,7 @@ def ODEs(mode='compare_strategies', plotting=True, base_path='data', **kwargs): ) -def single_problem(mode, problem, plotting=True, base_path='data', **kwargs): # pragma: no cover +def single_problem(mode, problem, plotting=True, base_path='data', target='thesis', **kwargs): # pragma: no cover """ Make a plot for a single problem @@ -1481,7 +1864,10 @@ def single_problem(mode, problem, plotting=True, base_path='data', **kwargs): # mode (str): What you want to look at problem (function): A problem to run """ - fig, ax = get_fig(1, 1, figsize=figsize_by_journal('Springer_Numerical_Algorithms', 1, 0.8)) + if target == 'thesis': + fig, ax = get_fig(1, 1, figsize=figsize_by_journal('TUHH_thesis', 0.7, 0.6)) + else: + fig, ax = get_fig(1, 1, figsize=figsize_by_journal('Springer_Numerical_Algorithms', 1, 0.8)) params = { 'work_key': 'k_SDC', @@ -1507,6 +1893,7 @@ def single_problem(mode, problem, plotting=True, base_path='data', **kwargs): # precision_key=params['precision_key'], legend=False, base_path=base_path, + squares=target != 'thesis', ) @@ -1603,38 +1990,42 @@ def aggregate_parallel_efficiency_plot(): # pragma: no cover if __name__ == "__main__": comm_world = MPI.COMM_WORLD - record = comm_world.size > 1 - for mode in [ - # 'compare_strategies', - # 'RK_comp', - # 'parallel_efficiency', - ]: - params = { - 'mode': mode, - 'runs': 1, - 'plotting': comm_world.rank == 0, - } - params_single = { - **params, - 'problem': run_RBC, - } - single_problem(**params_single, work_key='t', precision_key='e_global_rel', record=record) - - all_params = { - 'record': True, - 'runs': 5, - 'work_key': 't', - 'precision_key': 'e_global_rel', - 'plotting': comm_world.rank == 0, + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument('--mode', type=str, default='compare_strategies') + parser.add_argument('--record', type=str, choices=['True', 'False'], default='True') + parser.add_argument('--plotting', type=str, choices=['True', 'False'], default='True') + parser.add_argument('--runs', type=int, default=5) + parser.add_argument( + '--problem', type=str, choices=['vdp', 'RBC', 'AC', 'quench', 'Lorenz', 'Schroedinger', 'GS'], default='vdp' + ) + parser.add_argument('--work_key', type=str, default='t') + parser.add_argument('--precision_key', type=str, default='e_global_rel') + parser.add_argument('--logger_level', type=int, default='25') + + args = parser.parse_args() + + problems = { + 'Lorenz': run_Lorenz, + 'vdp': run_vdp, + 'Schroedinger': run_Schroedinger, + 'quench': run_quench, + 'AC': run_AC, + 'RBC': run_RBC, + 'GS': run_GS, + } + + params = { + **vars(args), + 'record': args.record == 'True', + 'plotting': args.plotting == 'True' and comm_world.rank == 0, + 'problem': problems[args.problem], } - for mode in [ - 'RK_comp', - 'parallel_efficiency', - 'compare_strategies', - ]: - all_problems(**all_params, mode=mode) - comm_world.Barrier() + LOGGER_LEVEL = params.pop('logger_level') + + single_problem(**params) if comm_world.rank == 0: plt.show()