From d293c4f52c35da17eaf1d634dd51161bb9521996 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Fri, 31 Oct 2025 16:01:43 -0230 Subject: [PATCH 01/10] GROMACS: new build-option 'mpi_only' --- easybuild/easyblocks/g/gromacs.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index f5f0f5ac7a4..eb02858756d 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -70,6 +70,7 @@ def extra_options(): 'mpisuffix': ['_mpi', "Suffix to append to MPI-enabled executables (only for GROMACS < 4.6)", CUSTOM], 'mpiexec': ['mpirun', "MPI executable to use when running tests", CUSTOM], 'mpiexec_numproc_flag': ['-np', "Flag to introduce the number of MPI tasks when running tests", CUSTOM], + 'mpi_only': [False, "Only build for MPI and skip nompi.", CUSTOM], 'mpi_numprocs': [0, "Number of MPI tasks to use when running tests", CUSTOM], '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. " + @@ -655,8 +656,12 @@ def sanity_check_step(self): else: mpisuff = '_mpi' - mpi_bins.extend([binary + mpisuff for binary in mpi_bins]) - mpi_libnames.extend([libname + mpisuff for libname in mpi_libnames]) + if self.cfg['mpi_only']: + mpi_bins = [binary + mpisuff for binary in mpi_bins] + mpi_libnames = [libname + mpisuff for libname in mpi_libnames] + else: + mpi_bins.extend([binary + mpisuff for binary in mpi_bins]) + mpi_libnames.extend([libname + mpisuff for libname in mpi_libnames]) suffixes = [''] @@ -769,9 +774,12 @@ def run_all_steps(self, *args, **kwargs): if precisions == []: raise EasyBuildError("No precision selected. At least one of single/double_precision must be unset or True") - mpitypes = ['nompi'] - if self.toolchain.options.get('usempi', None): - mpitypes.append('mpi') + if self.cfg['mpi_only']: + mpitypes = ['mpi'] + else: + mpitypes = ['nompi'] + if self.toolchain.options.get('usempi', None): + mpitypes.append('mpi') # We need to count the number of variations to build. versions_built = [] From 17ad339ccf2cee7fed9d97ebb151a6937a0d11c3 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Fri, 31 Oct 2025 13:31:49 -0230 Subject: [PATCH 02/10] support 'X86-64-V?' style Microarchitecture levels in optarch --- easybuild/easyblocks/g/gromacs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index eb02858756d..9ec2d873eaa 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -118,13 +118,13 @@ def get_gromacs_arch(self): # http://manual.gromacs.org/documentation/2018/install-guide/index.html#simd-support if 'MIC-AVX512' in optarch and LooseVersion(self.version) >= LooseVersion('2016'): res = 'AVX_512_KNL' - elif 'AVX512' in optarch and LooseVersion(self.version) >= LooseVersion('2016'): + elif ('AVX512' in optarch or 'X86-64-V4' in optarch) and LooseVersion(self.version) >= LooseVersion('2016'): res = 'AVX_512' - elif 'AVX2' in optarch and LooseVersion(self.version) >= LooseVersion('5.0'): + elif ('AVX2' in optarch or 'X86-64-V3' in optarch)and LooseVersion(self.version) >= LooseVersion('5.0'): res = 'AVX2_256' elif 'AVX' in optarch: res = 'AVX_256' - elif 'SSE3' in optarch or 'SSE2' in optarch or 'MARCH=NOCONA' in optarch: + elif 'SSE3' in optarch or 'SSE2' in optarch or 'MARCH=NOCONA' in optarch or 'X86-64-V2' in optarch: # Gromacs doesn't have any GMX_SIMD=SSE3 but only SSE2 and SSE4.1 [1]. # According to [2] the performance difference between SSE2 and SSE4.1 is minor on x86 # and SSE4.1 is not supported by AMD Magny-Cours[1]. From a4e76508098629b4aeac261bef58ed994b79f39f Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Fri, 31 Oct 2025 15:10:59 -0230 Subject: [PATCH 03/10] EB_GROMACS: fix linter --- easybuild/easyblocks/g/gromacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 9ec2d873eaa..57eccffb589 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -120,7 +120,7 @@ def get_gromacs_arch(self): res = 'AVX_512_KNL' elif ('AVX512' in optarch or 'X86-64-V4' in optarch) and LooseVersion(self.version) >= LooseVersion('2016'): res = 'AVX_512' - elif ('AVX2' in optarch or 'X86-64-V3' in optarch)and LooseVersion(self.version) >= LooseVersion('5.0'): + elif ('AVX2' in optarch or 'X86-64-V3' in optarch) and LooseVersion(self.version) >= LooseVersion('5.0'): res = 'AVX2_256' elif 'AVX' in optarch: res = 'AVX_256' From 1d0f8e881e5a28999a7e495b73418aea9f453a5d Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Fri, 31 Oct 2025 15:08:07 -0230 Subject: [PATCH 04/10] EB_GROMACS: more control over building gmxapi Python package --- easybuild/easyblocks/g/gromacs.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 57eccffb589..604e90f178d 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -72,6 +72,8 @@ def extra_options(): 'mpiexec_numproc_flag': ['-np', "Flag to introduce the number of MPI tasks when running tests", CUSTOM], 'mpi_only': [False, "Only build for MPI and skip nompi.", CUSTOM], 'mpi_numprocs': [0, "Number of MPI tasks to use when running tests", CUSTOM], + 'python_pkg': [None, "Build gmxapi Python package. None (default) is auto-detect." + + "True or False forces behaviour.", CUSTOM], '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], @@ -331,7 +333,14 @@ def configure_step(self): if gromacs_version >= '2020': # build Python bindings if Python is loaded as a dependency python_root = get_software_root('Python') - if python_root: + if self.cfg['python_pkg'] is True and not python_root: + msg = "Building Python gmxapi has been requested but Python is not listed as a dependency." + raise EasyBuildError(msg) + elif python_root and self.cfg['python_pkg'] is False: + msg = "Python was found, but compilation without Python gmxapi has been requested." + self.log.info(msg) + self.cfg.update('configopts', "-DGMX_PYTHON_PACKAGE=OFF") + elif python_root: self.cfg.update('configopts', "-DGMX_PYTHON_PACKAGE=ON") bin_python = os.path.join(python_root, 'bin', 'python') # For find_package(PythonInterp) From 49e82b133b76b4b4e45fb45f3ab385ed2e14be15 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Fri, 31 Oct 2025 15:40:32 -0230 Subject: [PATCH 05/10] EB_GROMACS: finer control over PLUMED support (internal or patching) --- easybuild/easyblocks/g/gromacs.py | 53 +++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 604e90f178d..589abea1f29 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -75,7 +75,8 @@ def extra_options(): 'python_pkg': [None, "Build gmxapi Python package. None (default) is auto-detect." + "True or False forces behaviour.", CUSTOM], '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. " + + 'plumed': [None, "Try to enable PLUMED support. None (default) is auto-detect. " + + "'patch' apply PLUMED patches even for GROMACS 2025 and newer." + "True or False forces behaviour.", CUSTOM], }) return extra_vars @@ -220,33 +221,50 @@ def configure_step(self): # PLUMED detection # enable PLUMED support if PLUMED is listed as a dependency # and PLUMED support is either explicitly enabled (plumed = True) or unspecified ('plumed' not defined) + # For GROMACS 2025 and newer that means enabling GROMACS' native PLUMED support ('-DGMX_USE_PLUMED=ON') + # unless PLUMED patches are requested with plumed = 'patch'. + # For older versions of GROMACS patches are applied to enable PLUMED support. plumed_root = get_software_root('PLUMED') + plumed_patches = False if self.cfg['plumed'] and not plumed_root: msg = "PLUMED support has been requested but PLUMED is not listed as a dependency." raise EasyBuildError(msg) elif plumed_root and self.cfg['plumed'] is False: self.log.info('PLUMED was found, but compilation without PLUMED has been requested.') plumed_root = None + elif plumed_root and gromacs_version >= '2025' and self.cfg['plumed'] == 'patch': + self.log.info('PLUMED was found, and PLUMED patching has been requested.') + plumed_patches = True + elif plumed_root and gromacs_version >= '2025' and self.cfg['plumed'] is True: + self.log.info('PLUMED was found, and PLUMED support has been requested.') + elif plumed_root and gromacs_version < '2025' and self.cfg['plumed'] is True: + self.log.info('PLUMED was found, and PLUMED support has been requested.') + plumed_patches = True 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 + if gromacs_version >= '2025' and plumed_patches is False: + self.log.info('Native PLUMED support has been enabled.') + self.cfg.update('configopts', '-DGMX_USE_PLUMED=ON') - 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) + else: + # 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 + # 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 # Ensure that the GROMACS log files report how the code was patched # during the build, so that any problems are easier to diagnose. @@ -254,7 +272,7 @@ def configure_step(self): if (gromacs_version >= '2020' and '-DGMX_VERSION_STRING_OF_FORK=' not in self.cfg['configopts']): gromacs_version_string_suffix = 'EasyBuild-%s' % EASYBUILD_VERSION - if plumed_root: + if plumed_root and plumed_patches: gromacs_version_string_suffix += '-PLUMED-%s' % get_software_version('PLUMED') self.cfg.update('configopts', '-DGMX_VERSION_STRING_OF_FORK=%s' % gromacs_version_string_suffix) @@ -294,7 +312,8 @@ def configure_step(self): ConfigureMake.configure_step(self) # Now patch GROMACS for PLUMED between configure and build - if plumed_root: + if plumed_root and plumed_patches: + self.log.info('Applying PLUMED patches to GROMACS.') run_shell_cmd(plumed_cmd) else: From 9fe6942b632c3aebea2dbf42ffc62e2a90b3efd8 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Tue, 4 Nov 2025 16:10:29 -0330 Subject: [PATCH 06/10] GROMACS: improve PLUMED patching logic --- easybuild/easyblocks/g/gromacs.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 589abea1f29..8660eddb9fd 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -232,14 +232,20 @@ def configure_step(self): elif plumed_root and self.cfg['plumed'] is False: self.log.info('PLUMED was found, but compilation without PLUMED has been requested.') plumed_root = None - elif plumed_root and gromacs_version >= '2025' and self.cfg['plumed'] == 'patch': + elif plumed_root and self.cfg['plumed'] == 'patch': self.log.info('PLUMED was found, and PLUMED patching has been requested.') + print('PLUMED was found, and PLUMED patching has been requested.') plumed_patches = True - elif plumed_root and gromacs_version >= '2025' and self.cfg['plumed'] is True: - self.log.info('PLUMED was found, and PLUMED support has been requested.') - elif plumed_root and gromacs_version < '2025' and self.cfg['plumed'] is True: - self.log.info('PLUMED was found, and PLUMED support has been requested.') - plumed_patches = True + elif plumed_root and self.cfg['plumed'] is True: + msg = 'PLUMED was found, and PLUMED support has been requested.' + if gromacs_version >= '2025': + msg += ' Will use native PLUMED support.' + plumed_patches = False + else: + msg += ' Will apply PLUMED patches.' + plumed_patches = True + self.log.info(msg) + print(msg) if plumed_root: self.log.info('PLUMED support has been enabled.') @@ -272,7 +278,7 @@ def configure_step(self): if (gromacs_version >= '2020' and '-DGMX_VERSION_STRING_OF_FORK=' not in self.cfg['configopts']): gromacs_version_string_suffix = 'EasyBuild-%s' % EASYBUILD_VERSION - if plumed_root and plumed_patches: + if plumed_patches: gromacs_version_string_suffix += '-PLUMED-%s' % get_software_version('PLUMED') self.cfg.update('configopts', '-DGMX_VERSION_STRING_OF_FORK=%s' % gromacs_version_string_suffix) From 5aabcf9ed65ae8fdf5642d1f2e7629f5de58c8f2 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Tue, 4 Nov 2025 16:20:27 -0330 Subject: [PATCH 07/10] remove print statements added for debugging --- easybuild/easyblocks/g/gromacs.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 8660eddb9fd..03237ac0583 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -234,7 +234,6 @@ def configure_step(self): plumed_root = None elif plumed_root and self.cfg['plumed'] == 'patch': self.log.info('PLUMED was found, and PLUMED patching has been requested.') - print('PLUMED was found, and PLUMED patching has been requested.') plumed_patches = True elif plumed_root and self.cfg['plumed'] is True: msg = 'PLUMED was found, and PLUMED support has been requested.' @@ -245,7 +244,6 @@ def configure_step(self): msg += ' Will apply PLUMED patches.' plumed_patches = True self.log.info(msg) - print(msg) if plumed_root: self.log.info('PLUMED support has been enabled.') From 1677da6df3e86ab14564f59a570d47a14f711ce5 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Wed, 19 Nov 2025 13:41:17 -0330 Subject: [PATCH 08/10] GROMACS: add option plumed = 'native' --- easybuild/easyblocks/g/gromacs.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 03237ac0583..94250f1ed1c 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -76,8 +76,9 @@ def extra_options(): "True or False forces behaviour.", CUSTOM], 'ignore_plumed_version_check': [False, "Ignore the version compatibility check for PLUMED", CUSTOM], 'plumed': [None, "Try to enable PLUMED support. None (default) is auto-detect. " + - "'patch' apply PLUMED patches even for GROMACS 2025 and newer." + - "True or False forces behaviour.", CUSTOM], + "'native' enables native PLUMED support for GROMACS 2025 and newer." + + "'patch' (or True) applies PLUMED patches." + + "False disables PLUMED support.", CUSTOM], }) return extra_vars @@ -219,11 +220,12 @@ def configure_step(self): self.cfg.update('configopts', "-DGMX_GPU=OFF") # PLUMED detection - # enable PLUMED support if PLUMED is listed as a dependency - # and PLUMED support is either explicitly enabled (plumed = True) or unspecified ('plumed' not defined) - # For GROMACS 2025 and newer that means enabling GROMACS' native PLUMED support ('-DGMX_USE_PLUMED=ON') - # unless PLUMED patches are requested with plumed = 'patch'. - # For older versions of GROMACS patches are applied to enable PLUMED support. + # enable PLUMED support if PLUMED is listed as a dependency. + # plumed = 'native' will enable GROMACS' native PLUMED support ('-DGMX_USE_PLUMED=ON') + # for GROMACS 2025 and newer. plumed = 'patch' specifically requests PLUMED patches. + # In auto-detect ('plumed = None' or not defined) will prefer native support for 2025 + # and newer. Older versions of GROMACS patches are applied to enable PLUMED support. + # plumed = True behaves like plumed = 'patch' for backwards compatibility. plumed_root = get_software_root('PLUMED') plumed_patches = False if self.cfg['plumed'] and not plumed_root: @@ -235,8 +237,22 @@ def configure_step(self): elif plumed_root and self.cfg['plumed'] == 'patch': self.log.info('PLUMED was found, and PLUMED patching has been requested.') plumed_patches = True + elif plumed_root and self.cfg['plumed'] == 'native': + msg = 'PLUMED was found, and native PLUMED support has been requested.' + if gromacs_version >= '2025': + msg += ' Will use native PLUMED support.' + plumed_patches = False + self.log.info(msg) + else: + msg += " Native PLUMED support is only available with GROMACS 2025 and newer." + raise EasyBuildError(msg) elif plumed_root and self.cfg['plumed'] is True: msg = 'PLUMED was found, and PLUMED support has been requested.' + msg += ' Will apply PLUMED patches.' + plumed_patches = True + self.log.info(msg) + elif plumed_root and self.cfg['plumed'] is None: + msg = 'PLUMED was found.' if gromacs_version >= '2025': msg += ' Will use native PLUMED support.' plumed_patches = False From 83647f5f839378be25c16a141f5a4420fa886f73 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Wed, 10 Dec 2025 11:44:05 -0330 Subject: [PATCH 09/10] use native PLUMED by default with GROMACS 2026+ GROMACS 2025 was released on Feb 11, 2025. Changing EB's default behavior of whether to apply patches or use GROMACS' native PLUMED support would be surprising to users. Changing the default with the yet unreleased GROMACS 2026 is safer. In any case PLUMED patches for GROMACS 2025 and newer are based on GROMACS' native support, which means that some PLUMED features that were available with older GROMACS releases are not supported until GROMACS' MDModule and the plugin mechansims have caught up again. See also discussion in easybuilders/easybuild-easyblocks#3984 --- easybuild/easyblocks/g/gromacs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index 94250f1ed1c..b473eb70b01 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -223,7 +223,7 @@ def configure_step(self): # enable PLUMED support if PLUMED is listed as a dependency. # plumed = 'native' will enable GROMACS' native PLUMED support ('-DGMX_USE_PLUMED=ON') # for GROMACS 2025 and newer. plumed = 'patch' specifically requests PLUMED patches. - # In auto-detect ('plumed = None' or not defined) will prefer native support for 2025 + # In auto-detect ('plumed = None' or not defined) will prefer native support for 2026 # and newer. Older versions of GROMACS patches are applied to enable PLUMED support. # plumed = True behaves like plumed = 'patch' for backwards compatibility. plumed_root = get_software_root('PLUMED') @@ -253,7 +253,9 @@ def configure_step(self): self.log.info(msg) elif plumed_root and self.cfg['plumed'] is None: msg = 'PLUMED was found.' - if gromacs_version >= '2025': + if gromacs_version >= '2026': + # Even though native support is available since GROMACS 2025, we'll only use + # it as default with 2026 and newer to avoid a sudden change in behaviour. msg += ' Will use native PLUMED support.' plumed_patches = False else: From 5f7317b53d5ad2f3d0bd88f4713b0e31f1e755e8 Mon Sep 17 00:00:00 2001 From: Oliver Stueker Date: Tue, 16 Dec 2025 15:55:33 -0330 Subject: [PATCH 10/10] GROMACS: fix native PLUMED support instead of patching --- easybuild/easyblocks/g/gromacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easybuild/easyblocks/g/gromacs.py b/easybuild/easyblocks/g/gromacs.py index b473eb70b01..7a5d195dd72 100644 --- a/easybuild/easyblocks/g/gromacs.py +++ b/easybuild/easyblocks/g/gromacs.py @@ -391,7 +391,7 @@ def configure_step(self): self.cfg.update('configopts', "-DPython3_FIND_VIRTUALENV=STANDARD") # Now patch GROMACS for PLUMED before cmake - if plumed_root: + if plumed_root and plumed_patches: if gromacs_version >= '5.1': # Use shared or static patch depending on # setting of self.cfg['build_shared_libs']