diff --git a/cscs-checks/apps/gromacs/gromacs_check.py b/cscs-checks/apps/gromacs/gromacs_check.py index a0fc0d0989..11e7b3358c 100644 --- a/cscs-checks/apps/gromacs/gromacs_check.py +++ b/cscs-checks/apps/gromacs/gromacs_check.py @@ -3,139 +3,240 @@ # # SPDX-License-Identifier: BSD-3-Clause -import contextlib -import os - import reframe as rfm -import reframe.utility.sanity as sn - - -class GromacsBaseCheck(rfm.RunOnlyRegressionTest): - def __init__(self, output_file): - if self.current_system.name in ['eiger', 'pilatus']: - self.valid_prog_environs = ['cpeGNU'] - else: - self.valid_prog_environs = ['builtin'] - - self.executable = 'gmx_mpi' +from hpctestlib.sciapps.gromacs.benchmarks import gromacs_check - # Reset sources dir relative to the SCS apps prefix - self.sourcesdir = os.path.join(self.current_system.resourcesdir, - 'Gromacs', 'herflat') - self.keep_files = [output_file] - energy = sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' - r'\s+Conserved En\.\s+Temperature\n' - r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' - r'\s+Pressure \(bar\)\s+Constr\. rmsd', - output_file, 'energy', float, item=-1) - energy_reference = -3270799.9 - - self.sanity_patterns = sn.all([ - sn.assert_found('Finished mdrun', output_file), - sn.assert_reference(energy, energy_reference, -0.001, 0.001) - ]) - - self.perf_patterns = { - 'perf': sn.extractsingle(r'Performance:\s+(?P\S+)', - output_file, 'perf', float) +@rfm.simple_test +class cscs_gromacs_check(gromacs_check): + modules = ['GROMACS'] + maintainers = ['VH', 'VK'] + use_multithreading = False + extra_resources = { + 'switches': { + 'num_switches': 1 } - - self.modules = ['GROMACS'] - self.maintainers = ['VH', 'SO'] - self.strict_check = False - self.use_multithreading = False - self.extra_resources = { - 'switches': { - 'num_switches': 1 - } + } + executable_opts += ['-dlb yes', '-ntomp 1', '-npme -1'] + valid_prog_environs = ['builtin'] + + # CSCS-specific parameterization + num_nodes = parameter([1, 2, 4, 6, 8, 16]) + allref = { + 1: { + 'sm_60': { + 'HECBioSim/Crambin': (195.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (78.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (8.5, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (9.2, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (3.0, None, None, 'ns/day'), + }, + 'broadwell': { + 'HECBioSim/Crambin': (116.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (38.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (4.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (8.0, None, None, 'ns/day'), + }, + 'zen2': { + 'HECBioSim/Crambin': (320.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (120.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (16.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (31.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (7.0, None, None, 'ns/day'), + }, + }, + 2: { + 'sm_60': { + 'HECBioSim/Crambin': (202.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (111.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (15.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (18.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (6.0, None, None, 'ns/day'), + }, + 'broadwell': { + 'HECBioSim/Crambin': (200.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (65.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (8.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (13.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (4.0, None, None, 'ns/day'), + }, + 'zen2': { + 'HECBioSim/Crambin': (355.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (210.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (31.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (53.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (13.0, None, None, 'ns/day'), + }, + }, + 4: { + 'sm_60': { + 'HECBioSim/Crambin': (200.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (133.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (22.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (28.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (10.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (5.0, None, None, 'ns/day'), + }, + 'broadwell': { + 'HECBioSim/Crambin': (260.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (111.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (15.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (23.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (7.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (3.0, None, None, 'ns/day'), + }, + 'zen2': { + 'HECBioSim/Crambin': (340.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (230.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (56.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (80.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (25.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (11.0, None, None, 'ns/day'), + }, + }, + 6: { + 'sm_60': { + 'HECBioSim/Crambin': (213.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (142.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (28.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (35.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (13.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (8.0, None, None, 'ns/day'), + }, + 'broadwell': { + 'HECBioSim/Crambin': (308.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (127.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (22.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (29.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (9.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (5.0, None, None, 'ns/day'), + }, + 'zen2': { + 'HECBioSim/Glutamine-Binding-Protein': (240.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (75.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (110.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (33.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (13.0, None, None, 'ns/day'), + }, + }, + 8: { + 'sm_60': { + 'HECBioSim/Crambin': (206.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (149.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (37.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (39.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (16.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (9.0, None, None, 'ns/day'), + }, + 'broadwell': { + 'HECBioSim/Crambin': (356.0, None, None, 'ns/day'), + 'HECBioSim/Glutamine-Binding-Protein': (158.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (28.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (39.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (11.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (6.0, None, None, 'ns/day'), + }, + 'zen2': { + 'HECBioSim/Glutamine-Binding-Protein': (250.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (80.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (104.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (43.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (20.0, None, None, 'ns/day'), + }, + }, + 16: { + 'sm_60': { + 'HECBioSim/Glutamine-Binding-Protein': (154.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (43.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (54.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (21.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (14.0, None, None, 'ns/day'), + }, + 'broadwell': { + 'HECBioSim/Glutamine-Binding-Protein': (200.0, None, None, 'ns/day'), # noqa: E501 + 'HECBioSim/hEGFRDimer': (44.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (54.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (19.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (10.0, None, None, 'ns/day'), + }, + 'zen2': { + 'HECBioSim/hEGFRDimer': (82.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerSmallerPL': (70.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRDimerPair': (49.0, None, None, 'ns/day'), + 'HECBioSim/hEGFRtetramerPair': (25.0, None, None, 'ns/day'), + }, } - self.tags = {'scs', 'external-resources'} - - -@rfm.parameterized_test(*([s, v] - for s in ['small', 'large'] - for v in ['prod', 'maint'])) -class GromacsGPUCheck(GromacsBaseCheck): - def __init__(self, scale, variant): - super().__init__('md.log') - self.valid_systems = ['daint:gpu'] - self.descr = 'GROMACS GPU check' - self.executable_opts = ['mdrun', '-dlb yes', '-ntomp 1', '-npme 0', - '-s herflat.tpr'] - self.variables = {'CRAY_CUDA_MPS': '1'} - self.num_gpus_per_node = 1 - if scale == 'small': - self.valid_systems += ['dom:gpu'] - self.num_tasks = 72 - self.num_tasks_per_node = 12 - else: - self.num_tasks = 192 - self.num_tasks_per_node = 12 - - references = { - 'maint': { - 'large': { - 'daint:gpu': {'perf': (63.0, -0.10, None, 'ns/day')} - } - }, - 'prod': { - 'small': { - 'dom:gpu': {'perf': (37.0, -0.05, None, 'ns/day')}, - 'daint:gpu': {'perf': (35.0, -0.10, None, 'ns/day')} - }, - 'large': { - 'daint:gpu': {'perf': (63.0, -0.20, None, 'ns/day')} - } + } + + @run_after('init') + def setup_filtering_criteria(self): + # Update test's description + self.descr += f' ({self.num_nodes} node(s))' + + # Setup system filtering + valid_systems = { + 'cpu': { + 1: ['daint:mc', 'dom:mc', 'eiger:mc', 'pilatus:mc'], + 2: ['daint:mc', 'dom:mc', 'eiger:mc', 'pilatus:mc'], + 4: ['daint:mc', 'dom:mc', 'eiger:mc', 'pilatus:mc'], + 6: ['daint:mc', 'dom:mc', 'eiger:mc', 'pilatus:mc'], + 8: ['daint:mc', 'eiger:mc'], + 16: ['daint:mc', 'eiger:mc'] }, + 'gpu': { + 1: ['daint:gpu', 'dom:gpu', 'eiger:gpu', 'pilatus:gpu'], + 2: ['daint:gpu', 'dom:gpu', 'eiger:gpu', 'pilatus:gpu'], + 4: ['daint:gpu', 'dom:gpu', 'eiger:gpu', 'pilatus:gpu'], + 6: ['daint:gpu', 'dom:gpu', 'eiger:gpu', 'pilatus:gpu'], + 8: ['daint:gpu', 'eiger:gpu'], + 16: ['daint:gpu', 'eiger:gpu'] + } } - with contextlib.suppress(KeyError): - self.reference = references[variant][scale] - - self.tags |= {'maintenance' if variant == 'maint' else 'production'} - - -@rfm.parameterized_test(*([s, v] - for s in ['small', 'large'] - for v in ['prod'])) -class GromacsCPUCheck(GromacsBaseCheck): - def __init__(self, scale, variant): - super().__init__('md.log') - self.valid_systems = ['daint:mc', 'eiger:mc', 'pilatus:mc'] - self.descr = 'GROMACS CPU check' - self.executable_opts = ['mdrun', '-dlb yes', '-ntomp 1', '-npme -1', - '-nb cpu', '-s herflat.tpr'] + try: + self.valid_systems = valid_systems[self.nb_impl][self.num_nodes] + except KeyError: + self.valid_systems = [] - if scale == 'small': - self.valid_systems += ['dom:mc'] - if (self.current_system.name in ['daint', 'dom']): - self.num_tasks = 216 - self.num_tasks_per_node = 36 - elif (self.current_system.name in ['eiger', 'pilatus']): - self.num_tasks = 768 - self.num_tasks_per_node = 128 - else: - if (self.current_system.name in ['daint', 'dom']): - self.num_tasks = 576 - self.num_tasks_per_node = 36 - elif (self.current_system.name in ['eiger', 'pilatus']): - self.num_tasks = 2048 - self.num_tasks_per_node = 128 + # Setup prog env. filtering + if self.current_system.name in ('eiger', 'pilatus'): + self.valid_prog_environs = ['cpeGNU'] - references = { - 'prod': { - 'small': { - 'dom:mc': {'perf': (40.0, -0.05, None, 'ns/day')}, - 'daint:mc': {'perf': (38.8, -0.10, None, 'ns/day')}, - 'eiger:mc': {'perf': (103.00, -0.10, None, 'ns/day')}, - 'pilatus:mc': {'perf': (103.00, -0.10, None, 'ns/day')} - }, - 'large': { - 'daint:mc': {'perf': (68.0, -0.20, None, 'ns/day')}, - 'eiger:mc': {'perf': (146.00, -0.20, None, 'ns/day')}, - 'pilatus:mc': {'perf': (146.00, -0.20, None, 'ns/day')} - } - }, + if self.num_nodes in (6, 16): + self.tags |= {'production'} + if (self.nb_impl == 'gpu' and + self.bench_name == 'HECBioSim/hEGFRDimerSmallerPL'): + self.tags |= {'maintenance'} + + @run_before('run') + def setup_run(self): + self.skip_if_no_procinfo() + + # Setup GPU run + if self.nb_impl == 'gpu': + self.num_gpus_per_node = 1 + self.variables = {'CRAY_CUDA_MPS': '1'} + + proc = self.current_partition.processor + + # Choose arch; we set explicitly the GPU arch, since there is no + # auto-detection + arch = proc.arch + if self.current_partition.fullname in ('daint:gpu', 'dom:gpu'): + arch = 'sm_60' + + try: + found = self.allref[self.num_nodes][arch][self.bench_name] + except KeyError: + self.skip(f'Configuration with {self.num_nodes} node(s) of ' + f'{self.bench_name!r} is not supported on {arch!r}') + + # Setup performance references + self.reference = { + '*': { + 'perf': self.allref[self.num_nodes][arch][self.bench_name] + } } - self.reference = references[variant][scale] - self.tags |= {'maintenance' if variant == 'maint' else 'production'} + + # Setup parallel run + self.num_tasks_per_node = proc.num_cores + self.num_tasks = self.num_nodes * self.num_tasks_per_node diff --git a/docs/hpctestlib.rst b/docs/hpctestlib.rst index 133bb34bc5..44215e20ef 100644 --- a/docs/hpctestlib.rst +++ b/docs/hpctestlib.rst @@ -11,6 +11,9 @@ Scientific Applications :members: :show-inheritance: +.. automodule:: hpctestlib.sciapps.gromacs.benchmarks + :members: + :show-inheritance: Data Analytics -------------- diff --git a/hpctestlib/sciapps/gromacs/benchmarks.py b/hpctestlib/sciapps/gromacs/benchmarks.py new file mode 100644 index 0000000000..51d5a7dd86 --- /dev/null +++ b/hpctestlib/sciapps/gromacs/benchmarks.py @@ -0,0 +1,165 @@ +# Copyright 2016-2021 Swiss National Supercomputing Centre (CSCS/ETH Zurich) +# ReFrame Project Developers. See the top-level LICENSE file for details. +# +# SPDX-License-Identifier: BSD-3-Clause + +import reframe as rfm +import reframe.utility as util +import reframe.utility.sanity as sn + + +@rfm.simple_test +class gromacs_check(rfm.RunOnlyRegressionTest): + '''GROMACS benchmark test. + + `GROMACS `__ is a versatile package to perform + molecular dynamics, i.e. simulate the Newtonian equations of motion for + systems with hundreds to millions of particles. + + The benchmarks consist on a set of different inputs files that vary in the + number of atoms and can be found in the following repository, which is + also versioned: https://github.com/victorusu/GROMACS_Benchmark_Suite/. + + Each test instance validates numerically its output and extracts and + reports a performance metric. + + ''' + + #: The version of the benchmark suite to use. + #: + #: :type: :class:`str` + #: :default: ``'1.0.0'`` + benchmark_version = variable(str, value='1.0.0') + + #: Parameter pack encoding the benchmark information. + #: + #: The first element of the tuple refers to the benchmark name, + #: the second is the energy reference and the third is the + #: tolerance threshold. + #: + #: :type: `Tuple[str, float, float]` + #: :values: + benchmark_info = parameter([ + ('HECBioSim/Crambin', -204107.0, 0.001), + ('HECBioSim/Glutamine-Binding-Protein', -724598.0, 0.001), + ('HECBioSim/hEGFRDimer', -3.32892e+06, 0.001), + ('HECBioSim/hEGFRDimerSmallerPL', -3.27080e+06, 0.001), + ('HECBioSim/hEGFRDimerPair', -1.20733e+07, 0.001), + ('HECBioSim/hEGFRtetramerPair', -2.09831e+07, 0.001) + ]) + + #: Parameter encoding the implementation of the non-bonded calculations + #: + #: :type: :class:`str` + #: :values: ``['cpu', 'gpu']`` + nb_impl = parameter(['cpu', 'gpu']) + + executable = 'gmx_mpi mdrun' + tags = {'sciapp', 'chemistry'} + keep_files = ['md.log'] + + @run_after('init') + def prepare_test(self): + self.__bench, self.__nrg_ref, self.__nrg_tol = self.benchmark_info + self.descr = f'GROMACS {self.__bench} benchmark (NB: {self.nb_impl})' + self.prerun_cmds = [ + f'curl -LJO https://github.com/victorusu/GROMACS_Benchmark_Suite/raw/{self.benchmark_version}/{self.__bench}/benchmark.tpr' # noqa: E501 + ] + self.executable_opts = ['-nb', self.nb_impl, '-s benchmark.tpr'] + + @property + def bench_name(self): + '''The benchmark name. + + :type: :class:`str` + ''' + + return self.__bench + + @property + def energy_ref(self): + '''The energy reference value for this benchmark. + + :type: :class:`str` + ''' + return self.__nrg_ref + + @property + def energy_tol(self): + '''The energy tolerance value for this benchmark. + + :type: :class:`str` + ''' + return self.__nrg_tol + + @performance_function('ns/day') + def perf(self): + return sn.extractsingle(r'Performance:\s+(?P\S+)', + 'md.log', 'perf', float) + + @deferrable + def energy_hecbiosim_crambin(self): + return sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' + r'\s+Conserved En\.\s+Temperature\n' + r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' + r'\s+Pressure \(bar\)\s+Constr\. rmsd', + 'md.log', 'energy', float, item=-1) + + @deferrable + def energy_hecbiosim_glutamine_binding_protein(self): + return sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' + r'\s+Conserved En\.\s+Temperature\n' + r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' + r'\s+Pressure \(bar\)\s+Constr\. rmsd', + 'md.log', 'energy', float, item=-1) + + @deferrable + def energy_hecbiosim_hegfrdimer(self): + return sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' + r'\s+Conserved En\.\s+Temperature\n' + r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' + r'\s+Pressure \(bar\)\s+Constr\. rmsd', + 'md.log', 'energy', float, item=-1) + + @deferrable + def energy_hecbiosim_hegfrdimersmallerpl(self): + return sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' + r'\s+Conserved En\.\s+Temperature\n' + r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' + r'\s+Pressure \(bar\)\s+Constr\. rmsd', + 'md.log', 'energy', float, item=-1) + + @deferrable + def energy_hecbiosim_hegfrdimerpair(self): + return sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' + r'\s+Conserved En\.\s+Temperature\n' + r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' + r'\s+Pressure \(bar\)\s+Constr\. rmsd', + 'md.log', 'energy', float, item=-1) + + @deferrable + def energy_hecbiosim_hegfrtetramerpair(self): + return sn.extractsingle(r'\s+Potential\s+Kinetic En\.\s+Total Energy' + r'\s+Conserved En\.\s+Temperature\n' + r'(\s+\S+){2}\s+(?P\S+)(\s+\S+){2}\n' + r'\s+Pressure \(bar\)\s+Constr\. rmsd', + 'md.log', 'energy', float, item=-1) + + @sanity_function + def assert_energy_readout(self): + '''Assert that the obtained energy meets the benchmark tolerances.''' + + energy_fn_name = f'energy_{util.toalphanum(self.__bench).lower()}' + energy_fn = getattr(self, energy_fn_name, None) + sn.assert_true( + energy_fn is not None, + msg=(f"cannot extract energy for benchmark {self.__bench!r}: " + f"please define a member function '{energy_fn_name}()'") + ).evaluate() + energy = energy_fn() + energy_diff = sn.abs(energy - self.energy_ref) + return sn.all([ + sn.assert_found('Finished mdrun', 'md.log'), + sn.assert_reference(energy, self.energy_ref, + -self.energy_tol, self.energy_tol) + ])