diff --git a/README.md b/README.md index f1e4fc1..5211aab 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# dev.eessi.io-espresso \ No newline at end of file +# dev.eessi.io-espresso + +Repository for pre-release builds of [ESPResSo](https://github.com/espressomd/espresso). +Builds are deployed to the [`dev.eessi.io`](https://www.eessi.io/docs/repositories/dev.eessi.io/) repository. diff --git a/easyconfigs/ESPResSo-foss-2023b-software-commit.eb b/easyconfigs/ESPResSo-foss-2023b-software-commit.eb new file mode 100644 index 0000000..cd9dce9 --- /dev/null +++ b/easyconfigs/ESPResSo-foss-2023b-software-commit.eb @@ -0,0 +1,142 @@ +easyblock = 'EB_ESPResSo' + +name = 'ESPResSo' +version = '%(software_commit)s' + +homepage = 'https://espressomd.org/wordpress' +description = """A software package for Molecular Dynamics and Monte Carlo +simulations of bead-spring models, with electrostatics, magnetostatics, +hydrodynamics and reaction-diffusion-advection solvers.""" + +sources = [ +{ + 'source_urls': ['https://github.com/espressomd/espresso/archive/'], + 'filename': 'espresso-%(software_commit)s.tar.gz', + 'download_filename': '%(software_commit)s.tar.gz', +}, +{ + 'source_urls': ['https://i10git.cs.fau.de/api/v4/projects/walberla%2Fwalberla/repository/archive?sha=59c9b8b1#'], + 'filename': 'walberla-59c9b8b1.tar.gz', +}, +{ + 'source_urls': ['https://github.com/icl-utk-edu/heffte/archive/'], + 'filename': 'heffte-2.4.1.tar.gz', + 'download_filename': 'v2.4.1.tar.gz', +}, +{ + 'source_urls': ['https://github.com/kokkos/kokkos/archive/'], + 'filename': 'kokkos-18b830e3360dff9f44a9a9c729ca9e74c037e354.tar.gz', + 'download_filename': '18b830e3360dff9f44a9a9c729ca9e74c037e354.tar.gz', +}, +{ + 'source_urls': ['https://github.com/ECP-copa/Cabana/archive/'], + 'filename': 'Cabana-ebfaa51.tar.gz', + 'download_filename': 'ebfaa51.tar.gz', +}, +{ + 'source_urls': ['https://github.com/highfive-devs/highfive/archive/'], + 'filename': 'highfive-0103467c3b609628fe3b892c6a85915610f2f3d2.tar.gz', + 'download_filename': '0103467.tar.gz', +}, +] +checksums = [{ + 'espresso-%(software_commit)s.tar.gz': '73d71511eca932adcff4f33ac778c77279e97cec91ea1ac53987ba488fc1288d', + 'walberla-59c9b8b1.tar.gz': 'a709d7299f2c06143946d9bb8033d4ddbb0cf8b9142e4bbaa15fecc78463038f', + 'heffte-2.4.1.tar.gz': 'de2cf26df5d61baac7841525db3f393cb007f79612ac7534fd4757f154ba3e6c', + 'kokkos-18b830e.tar.gz': 'dc0127134f47752f61e74c77237bd9ec560535c4283fef8c9643f947b3733063', + 'Cabana-ebfaa51.tar.gz': 'fe5b1b1d419662b29a80cbd6703994b28c942ccbee3b201eca87e48a13836350', + 'highfive-0103467.tar.gz': 'd0ce87abd07cdd676c6d6069d3b05f869138d115d8e22b935c9bd43c872c1f3d', +}] +patches = [ + 'espresso.patch', +] + +toolchain = {'name': 'foss', 'version': '2023b'} +toolchainopts = {'usempi': True, 'pic': True} + +builddependencies = [ + ('CMake', '3.27.6'), + ('Ninja', '1.11.1'), +] + +dependencies = [ + ('Python', '3.11.5'), + ('SciPy-bundle', '2023.11'), + ('Boost.MPI', '1.83.0'), + ('Mesa', '23.1.9'), + ('GSL', '2.7'), + ('IPython', '8.17.2'), + ('Pint', '0.24'), + ('HDF5', '1.14.3'), + #('VTK', '9.3.0'), # error: libpython3.10.so.1.0: file not found + #('PFFT', '20181230'), # not available in this toolchain + #('CUDA', '12.1.1', '', SYSTEM), # deferred for now +] + +# default CUDA compute capabilities to use (override via --cuda-compute-capabilities) +if any(x[0] == 'CUDA' for x in dependencies): + cuda_compute_capabilities = ['5.2', '6.0', '7.0', '7.5', '8.0', '8.6', '9.0'] + +configopts = f' -DESPRESSO_BUILD_TESTS=ON ' +# make sure the right Python is used (note: -DPython3_EXECUTABLE or -DPython_EXECUTABLE does not work!) +configopts += ' -D PYTHON_EXECUTABLE=$EBROOTPYTHON/bin/python ' +configopts += ' -DCMAKE_INSTALL_LIBDIR:PATH=lib ' +# workaround for https://gitlab.kitware.com/cmake/cmake/-/issues/22678 +# (this only affects testsuite executable files in the build folder) +_exe_linker_flags = ':'.join(f'%(builddir)s/easybuild_obj/_deps/{path}' + for path in ['kokkos-build/containers/src', + 'kokkos-build/core/src', + 'kokkos-build/simd/src', + 'heffte-build']) +configopts += f' -DCMAKE_EXE_LINKER_FLAGS="-Wl,-rpath-link,{_exe_linker_flags}" ' + +test_cmd = 'ninja' +runtest = f'check_unit_tests && {test_cmd} check_python' + +modextrapaths = {'PYTHONPATH': ['lib/python%(pyshortver)s/site-packages']} + +_binaries = ['ipypresso', 'pypresso'] +_libs = [ + # ESPResSo + 'espresso_core', 'espresso_shapes', 'espresso_walberla', + 'espresso_script_interface', 'script_interface', 'utils', '_init', + # waLBerla + 'libwalberla_core', 'libwalberla_executiontree', 'libwalberla_timeloop', + 'libwalberla_field', 'libwalberla_blockforest', 'libwalberla_geometry', + 'libwalberla_lbm', 'libwalberla_vtk', 'libwalberla_domain_decomposition', + 'libwalberla_boundary', 'liblodepng', + # Kokkos + 'libkokkoscontainers', 'libkokkoscore', 'libkokkossimd', + # heFFte + 'libheffte', +] +_python_modules = [ + '__init__.py', 'system.py', 'version.py', 'collision_detection.py', 'lb.py', + 'accumulators.py', 'constraints.py', 'observables.py', 'particle_data.py', +] +if any(x[0] == 'PFFT' for x in dependencies): + _libs.append('libwalberla_fft') +if any(x[0] == 'HDF5' for x in dependencies): + _libs.append('espresso_hdf5') + _python_modules.append('io/writer/h5md.py') +if any(x[0] == 'CUDA' for x in dependencies): + _libs += [ + 'espresso_cuda', 'espresso_walberla_cuda', 'libwalberla_gpu', + ] + _python_modules.append('cuda_init.py') + +_lib_path = 'lib/python%(pyshortver)s/site-packages/espressomd' +sanity_check_paths = { + 'files': [f'bin/{x}' for x in _binaries] + + [f'{_lib_path}/{x}.{SHLIB_EXT}' for x in _libs] + + [f'{_lib_path}/{x}' for x in _python_modules], + 'dirs': ['bin', 'lib'] +} + +sanity_check_commands = [ + 'pypresso -h', 'ipypresso -h', + 'pypresso -c "import espressomd.version;print(espressomd.version.friendly())"', + 'python3 -c "import espressomd.version;print(espressomd.version.friendly())"', +] + +moduleclass = 'chem' diff --git a/easyconfigs/e/ESPResSo/espresso.patch b/easyconfigs/e/ESPResSo/espresso.patch new file mode 100644 index 0000000..ddb35ba --- /dev/null +++ b/easyconfigs/e/ESPResSo/espresso.patch @@ -0,0 +1,15 @@ +# Cython 3.0.4 is supported, although it generates many warnings +--- CMakeLists.txt ++++ CMakeLists.txt +@@ -245,3 +245,3 @@ + find_package(Python 3.11 REQUIRED COMPONENTS Interpreter Development NumPy) +- find_package(Cython 3.0.8...<3.1.0 REQUIRED) ++ find_package(Cython 3.0.4...<3.1.0 REQUIRED) + find_program(IPYTHON_EXECUTABLE NAMES jupyter ipython3 ipython) +# skip fragile test which may crash hdf5 depending on how it was built +--- testsuite/python/CMakeLists.txt ++++ testsuite/python/CMakeLists.txt +@@ -386,3 +386,2 @@ + python_test(FILE h5md.py MAX_NUM_PROC 2) +-python_test(FILE h5md.py MAX_NUM_PROC 1 SUFFIX 1_core) + python_test(FILE p3m_fft.py MAX_NUM_PROC 6) diff --git a/easyconfigs/e/ESPResSo/espresso.py b/easyconfigs/e/ESPResSo/espresso.py new file mode 100644 index 0000000..35ebab9 --- /dev/null +++ b/easyconfigs/e/ESPResSo/espresso.py @@ -0,0 +1,154 @@ +# +# Copyright 2025 Jean-Noël Grad +# +# This code is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This code is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +""" +EasyBuild support for ESPResSo, implemented as an easyblock. + +@author: Jean-Noël Grad (University of Stuttgart) +""" + +import os +import re +import shutil +from easybuild.easyblocks.generic.cmakeninja import CMakeNinja +from easybuild.tools.systemtools import get_cpu_architecture, get_cpu_features +from easybuild.tools.systemtools import X86_64 +from easybuild.tools.utilities import trace_msg +from easybuild.tools.build_log import print_error + + +class EB_ESPResSo(CMakeNinja): + """Support for building and installing ESPResSo.""" + + def _get_extracted_tarball_paths(self): + """ + Locate the source code of all dependencies. + """ + extracted_paths = {} + for src in self.src: + name = src['name'].split('-', 1)[0] + # process main software + if name == 'espresso': + extracted_paths['espresso'] = src['finalpath'] + continue + # process dependencies + tarball = src['name'] + if not tarball.endswith('.tar.gz'): + raise ValueError(tarball + ' is not a tar.gz file') + prefix = tarball.rsplit('.', 2)[0] + matches = [x for x in os.listdir(src['finalpath']) if x.startswith(prefix)] + if len(matches) == 0: + raise RuntimeError(tarball + ' was not extracted') + if len(matches) > 1: + raise RuntimeError(tarball + ' matches multiple folders: ' + str(matches)) + extracted_paths[name] = os.path.join(src['finalpath'], matches[0]) + return extracted_paths + + def _patch_fetchcontent(self): + """ + Modify CMake ``FetchContent_Declare`` blocks to point to the folders + containing the already-downloaded dependencies rather than to URLs. + This avoids a download step during configuration. + """ + extracted_paths = self._get_extracted_tarball_paths() + cmakelists_path = os.path.join(extracted_paths['espresso'], 'CMakeLists.txt') + with open(cmakelists_path, 'r') as f: + content = f.read() + for name, local_uri in extracted_paths.items(): + if name == 'espresso': + continue + pattern = fr'FetchContent_Declare\(\s*{name}\s+GIT_REPOSITORY\s+\S+\s+GIT_TAG\s+\S+(?=\s|\))' + m = re.search(pattern, content, flags=re.IGNORECASE) + if m is None: + raise RuntimeError(f'{name} is not part of the ESPResSo FetchContent workflow') + content = re.sub(pattern, f'FetchContent_Declare({name} URL {local_uri}', content, flags=re.IGNORECASE) + with open(cmakelists_path, 'w') as f: + f.write(content) + + def configure_step(self): + # patch FetchContent to avoid re-downloading dependencies + self._patch_fetchcontent() + + configopts = self.cfg.get('configopts', '') + dependencies = self.cfg.get('dependencies', []) + + cpu_features = get_cpu_features() + with_cuda = any(x.get('name', '') == 'CUDA' for x in dependencies) + with_pfft = any(x.get('name', '') == 'PFFT' for x in dependencies) + with_hdf5 = any(x.get('name', '') == 'HDF5' for x in dependencies) + with_gsl = any(x.get('name', '') == 'GSL' for x in dependencies) + + if with_cuda: + configopts += ' -DESPRESSO_BUILD_WITH_CUDA=ON' + else: + configopts += ' -DESPRESSO_BUILD_WITH_CUDA=OFF' + if with_hdf5: + configopts += ' -DESPRESSO_BUILD_WITH_HDF5=ON' + else: + configopts += ' -DESPRESSO_BUILD_WITH_HDF5=OFF' + if with_gsl: + configopts += ' -DESPRESSO_BUILD_WITH_GSL=ON' + else: + configopts += ' -DESPRESSO_BUILD_WITH_GSL=OFF' + + configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA=ON' + if with_pfft: + configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA_FFT=ON' + else: + configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA_FFT=OFF' + if get_cpu_architecture() == X86_64 and 'avx2' in cpu_features: + configopts += ' -DESPRESSO_BUILD_WITH_WALBERLA_AVX=ON' + + configopts += ' -DESPRESSO_BUILD_WITH_SHARED_MEMORY_PARALLELISM=ON' + configopts += ' -DESPRESSO_BUILD_WITH_FFTW=ON' + + self.cfg['configopts'] = configopts + + return super(EB_ESPResSo, self).configure_step() + + def _cleanup_aux_files(self): + """ + Remove files automatically installed by CMake outside the ESPResSo + main directory: header files, config files, duplicated shared objects. + """ + def delete_dir(path): + if os.path.isdir(path): + trace_msg(f'removing directory \'%s\'' % path.replace(f'{self.installdir}/', '')) + shutil.rmtree(path) + + def delete_file(path): + if os.path.isfile(path) or os.path.islink(path): + trace_msg(f'removing file \'%s\'' % path.replace(f'{self.installdir}/', '')) + os.remove(path) + + lib_dir = f'{self.installdir}/lib' + if os.path.isdir(f'{self.installdir}/lib64'): + lib_dir = f'{self.installdir}/lib64' + delete_dir(f'{self.installdir}/include') + delete_dir(f'{self.installdir}/share') + delete_dir(f'{self.installdir}/walberla') + delete_dir(f'{lib_dir}/cmake') + for path in os.listdir(lib_dir): + if '.so' in path: + delete_file(f'{lib_dir}/{path}') + + def post_processing_step(self): + try: + self._cleanup_aux_files() + except Exception as err: + print_error("Failed to remove some auxiliary files (easyblock: %s): %s" % (self.__class__.__name__, str(err))) + return super(EB_ESPResSo, self).post_processing_step() diff --git a/easystacks/software.eessi.io/2023.06/espresso-eb-5.1.0-dev.yml b/easystacks/software.eessi.io/2023.06/espresso-eb-5.1.0-dev.yml new file mode 100644 index 0000000..1755a44 --- /dev/null +++ b/easystacks/software.eessi.io/2023.06/espresso-eb-5.1.0-dev.yml @@ -0,0 +1,5 @@ +easyconfigs: + - ESPResSo-foss-2023b-software-commit.eb: + options: + software-commit: 8aa60cecd56cdd10ab62042c567552f347374f36 # waLBerla coupling and OpenMP support + include-easyblocks: easyconfigs/*/*/*.py