diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 0f1b34edc19..ec4b8edd5b5 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -76,6 +76,9 @@ def extra_options(): 'ignore_plumed_version_check': [False, "Ignore the version compatibility check for PLUMED", CUSTOM], 'plumed': [None, "Try to apply PLUMED patches. None (default) is auto-detect. " + "True or False forces behaviour.", CUSTOM], + 'parallel_test': [None, "Number of tests to run in parallel. Can help with hanging tests caused by " + + "oversubscribed MPI processes. With None (default) the value of the parallel value " + + "is used.", CUSTOM], }) return extra_vars @@ -155,6 +158,73 @@ def is_double_precision_cuda_build(self): cuda = get_software_root('CUDA') return cuda and self.double_prec_pattern in self.cfg['configopts'] + def is_using_plumed_kernel(self): + gromacs_version = LooseVersion(self.version) + + return not gromacs_version < '2025' + + def is_cmake_shared_lib_plumed_patch_supported(self): + gromacs_version = LooseVersion(self.version) + + return gromacs_version >= '5.1' + + def configure_runtime_loading_of_plumed(self): + # Since 2025.0 GROMACS can use a subset of PLUMED interface by loading the PLUMED kernel library at runtime + + # 1. Enable building with the nativelly supported PLUMED functions: + # https://manual.gromacs.org/documentation/2025.0/install-guide/index.html#building-with-plumed-support + self.cfg.update('configopts', "-DGMX_USE_PLUMED=ON") + + # 2. Set the location of the runtime loaded PLUMED library: + # https://manual.gromacs.org/documentation/2025.0/reference-manual/special/plumed.html + self.cfg.update('modextrapaths', { + 'PLUMED_KERNEL': { + 'paths': '$EBROOTPLUMED/lib/libplumedKernel.so', + 'prepend': False + } + }) + + def check_plumed_version_supports_patching_gromacs_engine(self, engine): + res = run_shell_cmd("plumed-patch -l") + if not re.search(engine, res.output): + plumed_ver = get_software_version('PLUMED') + msg = "There is no support in PLUMED version %s for GROMACS %s: %s" % (plumed_ver, self.version, + res.output) + if self.cfg['ignore_plumed_version_check']: + self.log.warning(msg) + else: + raise EasyBuildError(msg) + + def set_plumed_cmd(self): + if self.is_using_plumed_kernel(): + self.configure_runtime_loading_of_plumed() + return + + engine = 'gromacs-%s' % self.version + + self.check_plumed_version_supports_patching_gromacs_engine(engine) + + # PLUMED patching must be done at different stages depending on + # version of GROMACS. Just prepare first part of cmd here + plumed_cmd = "plumed-patch -p -e %s" % engine + + if self.is_cmake_shared_lib_plumed_patch_supported(): + # Use shared or static patch depending on + # setting of self.cfg['build_shared_libs'] + # and adapt cmake flags accordingly as per instructions + # from "plumed patch -i" + if self.cfg['build_shared_libs']: + mode = 'shared' + else: + mode = 'static' + plumed_cmd = plumed_cmd + ' -m %s' % mode + + self.plumed_cmd = plumed_cmd + + def run_plumed_cmd(self): + if self.plumed_cmd: + run_shell_cmd(self.plumed_cmd) + def prepare_step(self, *args, **kwargs): """Custom prepare step for GROMACS.""" @@ -229,23 +299,7 @@ def configure_step(self): if plumed_root: self.log.info('PLUMED support has been enabled.') - - # Need to check if PLUMED has an engine for this version - engine = 'gromacs-%s' % self.version - - res = run_shell_cmd("plumed-patch -l") - if not re.search(engine, res.output): - plumed_ver = get_software_version('PLUMED') - msg = "There is no support in PLUMED version %s for GROMACS %s: %s" % (plumed_ver, self.version, - res.output) - if self.cfg['ignore_plumed_version_check']: - self.log.warning(msg) - else: - raise EasyBuildError(msg) - - # PLUMED patching must be done at different stages depending on - # version of GROMACS. Just prepare first part of cmd here - plumed_cmd = "plumed-patch -p -e %s" % engine + self.set_plumed_cmd() # Ensure that the GROMACS log files report how the code was patched # during the build, so that any problems are easier to diagnose. @@ -294,7 +348,7 @@ def configure_step(self): # Now patch GROMACS for PLUMED between configure and build if plumed_root: - run_shell_cmd(plumed_cmd) + self.run_plumed_cmd() else: if '-DGMX_MPI=ON' in self.cfg['configopts']: @@ -354,18 +408,7 @@ def configure_step(self): # Now patch GROMACS for PLUMED before cmake if plumed_root: - if gromacs_version >= '5.1': - # Use shared or static patch depending on - # setting of self.cfg['build_shared_libs'] - # and adapt cmake flags accordingly as per instructions - # from "plumed patch -i" - if self.cfg['build_shared_libs']: - mode = 'shared' - else: - mode = 'static' - plumed_cmd = plumed_cmd + ' -m %s' % mode - - run_shell_cmd(plumed_cmd) + self.run_plumed_cmd() # prefer static libraries, if available if self.cfg['build_shared_libs']: @@ -524,7 +567,10 @@ def test_step(self): # run 'make check' or whatever the easyconfig specifies # in parallel since it involves more compilation - self.cfg.update('runtest', f"-j {self.cfg.parallel}") + if self.parallel_test: + self.cfg.update('runtest', f"-j {self.parallel_test}") + else: + self.cfg.update('runtest', f"-j {self.cfg.parallel}") super().test_step() if build_option('rpath'):