Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 95 additions & 20 deletions easybuild/easyblocks/l/lammps.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
from easybuild.tools.filetools import copy_dir, copy_file, mkdir, read_file
from easybuild.tools.modules import get_software_root, get_software_version
from easybuild.tools.run import run_shell_cmd
from easybuild.tools.systemtools import AARCH64, get_cpu_architecture, get_shared_lib_ext
from easybuild.tools.systemtools import AARCH64, get_cpu_architecture, get_shared_lib_ext, get_avail_core_count
from easybuild.tools.toolchain.compiler import OPTARCH_GENERIC

from easybuild.easyblocks.generic.cmakemake import CMakeMake
Expand Down Expand Up @@ -359,7 +359,7 @@ def prepare_step(self, *args, **kwargs):
# version 1.3.2 is used in the test suite to check easyblock can be initialised
if self.version != '1.3.2':
# take into account that build directory may not be available (in case of --module-only)
if os.path.exists(self.start_dir) and os.listdir(self.start_dir):
if self.start_dir and os.path.exists(self.start_dir) and os.listdir(self.start_dir):
self.cur_version = translate_lammps_version(self.version, path=self.start_dir)
else:
self.cur_version = translate_lammps_version(self.version, path=self.installdir)
Expand Down Expand Up @@ -471,30 +471,19 @@ def configure_step(self, **kwargs):
# https://docs.lammps.org/Build_basics.html
# https://docs.lammps.org/Build_settings.html
# https://docs.lammps.org/Build_package.html
# https://docs.lammps.org/Build_extras.html
if self.cfg['general_packages']:
for package in self.cfg['general_packages']:
self.cfg.update('configopts', '-D%s%s=on' % (self.pkg_prefix, package))

if self.cfg['user_packages']:
for package in self.cfg['user_packages']:
self.cfg.update('configopts', '-D%s%s=on' % (self.pkg_user_prefix, package))

# Optimization settings
# OPT package
pkg_opt = '-D%sOPT=' % self.pkg_prefix
if pkg_opt not in self.cfg['configopts']:
self.cfg.update('configopts', pkg_opt + 'on')

# grab the architecture so we can check if we have Intel hardware (also used for Kokkos below)
processor_arch, gpu_arch = self.get_kokkos_arch(cuda_cc, self.cfg['kokkos_arch'])

if processor_arch in INTEL_PACKAGE_ARCH_LIST or \
(processor_arch == 'NATIVE' and self.kokkos_cpu_mapping.get(get_cpu_arch()) in INTEL_PACKAGE_ARCH_LIST):
# USER-INTEL enables optimizations on Intel processors. GCC has also partial support for some of them.
pkg_user_intel = '-D%sINTEL=' % self.pkg_user_prefix
if pkg_user_intel not in self.cfg['configopts']:
if self.toolchain.comp_family() in [toolchain.GCC, toolchain.INTELCOMP]:
self.cfg.update('configopts', pkg_user_intel + 'on')

# MPI/OpenMP
if self.toolchain.options.get('usempi', None):
self.cfg.update('configopts', '-DBUILD_MPI=yes')
Expand All @@ -517,11 +506,27 @@ def configure_step(self, **kwargs):
if '-DFFT_PACK=' not in self.cfg['configopts']:
self.cfg.update('configopts', '-DFFT_PACK=array')

# https://lammps.sandia.gov/doc/Build_extras.html
# KOKKOS
# detect the CPU and GPU architecture (used for Intel and Kokkos packages belos)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# detect the CPU and GPU architecture (used for Intel and Kokkos packages belos)
# detect the CPU and GPU architecture (used for Intel and Kokkos packages below)

processor_arch, gpu_arch = self.get_kokkos_arch(cuda_cc, self.cfg['kokkos_arch'])

# INTEL package
self.pkg_intel = False
if processor_arch in INTEL_PACKAGE_ARCH_LIST or \
(processor_arch == 'NATIVE' and self.kokkos_cpu_mapping.get(get_cpu_arch()) in INTEL_PACKAGE_ARCH_LIST):
# USER-INTEL enables optimizations on Intel processors. GCC has also partial support for some of them.
pkg_user_intel = '-D%sINTEL=' % self.pkg_user_prefix
if pkg_user_intel not in self.cfg['configopts']:
if self.toolchain.comp_family() in [toolchain.GCC, toolchain.INTELCOMP]:
self.cfg.update('configopts', pkg_user_intel + 'on')
self.pkg_intel = True
else:
self.pkg_intel = True

# KOKKOS package
if self.cfg['kokkos']:
print_msg("Using Kokkos package with arch: CPU - %s, GPU - %s" % (processor_arch, gpu_arch))
self.cfg.update('configopts', '-D%sKOKKOS=on' % self.pkg_prefix)
self.cfg.update('configopts', '-D%s_ENABLE_SERIAL=yes' % self.kokkos_prefix)

if self.toolchain.options.get('openmp', None):
self.cfg.update('configopts', '-D%s_ENABLE_OPENMP=yes' % self.kokkos_prefix)
Expand Down Expand Up @@ -557,7 +562,7 @@ def configure_step(self, **kwargs):
elif get_software_root("FFTW"):
self.cfg.update('configopts', '-DFFT_KOKKOS=FFTW3')

# CUDA only
# GPU package (cannot be built with KOKKOS+CUDA)
elif self.cuda:
print_msg("Using GPU package (not Kokkos) with arch: CPU - %s, GPU - %s" % (processor_arch, gpu_arch))
self.cfg.update('configopts', '-D%sGPU=on' % self.pkg_prefix)
Expand All @@ -568,6 +573,7 @@ def configure_step(self, **kwargs):
# to lib64)
self.cfg.update('configopts', '-DCMAKE_INSTALL_LIBDIR=lib')

# Python interface
# avoid that pip (ab)uses $HOME/.cache/pip
# cfr. https://pip.pypa.io/en/stable/reference/pip_install/#caching
env.setvar('XDG_CACHE_HOME', tempfile.gettempdir())
Expand Down Expand Up @@ -602,6 +608,23 @@ def configure_step(self, **kwargs):
else:
raise EasyBuildError("Expected to find a Python dependency as sanity check commands rely on it!")

# Testing (PyYAML must be installed)
if self.cfg['runtest'] is None or self.cfg['runtest']:
if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('29Aug2024')):
# Testing of KOKKOS+CUDA builds does not work for version < 22Jul2025
# See: https://github.com/lammps/lammps/issues/405
if LooseVersion(self.cur_version) < LooseVersion(translate_lammps_version('22Jul2025')) \
and self.cfg['kokkos'] and self.cuda:
print_warning("Skipping tests: KOKKOS+CUDA builds have broken testing in LAMMPS < 22Jul2025.")
self.cfg['runtest'] = False
else:
self.cfg['runtest'] = True
if 'PyYAML' not in (dep['name'] for dep in self.cfg.builddependencies()):
raise EasyBuildError("PyYAML not included as build dependency: cannot run tests.")
self.cfg.update('configopts', '-DENABLE_TESTING=on')
else:
self.cfg['runtest'] = False

return super().configure_step()

def install_step(self):
Expand Down Expand Up @@ -648,13 +671,27 @@ def install_step(self):

run_shell_cmd(cmd)

def test_step(self):
"""Filter the ctests that should be run"""
# add flags to test_cmd to ignore some tests
if self.cfg.get('runtest') is True and not self.cfg.get('test_cmd'):
test_cmd = 'ctest'
if LooseVersion(self.cmake_version) >= '3.17.0':
test_cmd += ' --no-tests=error'
test_cmd += ' -LE unstable -E "TestMliapPyUnified|AtomicPairStyle:meam_spline|KSpaceStyle:scafacos.*"'
self.log.debug(f"Running tests using test_cmd = '{test_cmd}' as test_cmd")
self.cfg['test_cmd'] = test_cmd

super().test_step()

def sanity_check_step(self, *args, **kwargs):
"""Run custom sanity checks for LAMMPS files, dirs and commands."""

# Set cur_version when running --sanity-check-only
if self.cur_version is None:
self.cur_version = translate_lammps_version(self.version, path=self.installdir)

# Test some LAMMPS examples
# Output files need to go somewhere (and has to work for --module-only as well)
execution_dir = tempfile.mkdtemp()

Expand All @@ -676,15 +713,53 @@ def sanity_check_step(self, *args, **kwargs):
for check_file in sanity_check_test_inputs
]

# add accelerator-specific tests
if self.pkg_intel: # INTEL package
custom_commands.append(
'from lammps import lammps; l=lammps(cmdargs=["-sf", "intel"]); l.file("%s")' %
os.path.join(self.installdir, "examples", "msst", "in.msst")
)
if self.cfg['kokkos']: # KOKKOS package
if self.cuda: # NOTE: requires a GPU to run
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a difficult requirement to meet, we should be able to support a cross-compilation. In EESSI, for example, this is going to cause problems as we must cross-compile for a lot of architectures. You could make it conditional on the nvidia-smi command running successfully?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See for example

if not which('nvidia-smi', on_error=IGNORE):
print_warning('Could not find nvidia-smi. Assuming a system without GPUs and skipping GPU tests!')
num_gpus_to_use = 0
elif os.environ.get('CUDA_VISIBLE_DEVICES') == '-1':
print_warning('GPUs explicitely disabled via CUDA_VISIBLE_DEVICES. Skipping GPU tests!')
num_gpus_to_use = 0
else:

custom_commands.append(
'from lammps import lammps; l=lammps(cmdargs=["-sf", "kk", "-k", "on", "g", "1"]); l.file("%s")' %
os.path.join(self.installdir, "examples", "msst", "in.msst")
)
else:
custom_commands.append(
'from lammps import lammps; l=lammps(cmdargs=["-sf", "kk", "-k", "on"]); l.file("%s")' %
os.path.join(self.installdir, "examples", "msst", "in.msst")
)
elif self.cuda: # GPU package
custom_commands.append(
'from lammps import lammps; l=lammps(cmdargs=["-sf", "gpu", "-pk", "gpu", "1"]); l.file("%s")' %
os.path.join(self.installdir, "examples", "msst", "in.msst")
)
if self.toolchain.options.get('openmp', None): # OPENMP package
custom_commands.append(
'from lammps import lammps; l=lammps(cmdargs=["-sf", "omp", "-pk", "omp", "2"]); l.file("%s")' %
os.path.join(self.installdir, "examples", "msst", "in.msst")
)
# OPT package
custom_commands.append(
'from lammps import lammps; l=lammps(cmdargs=["-sf", "opt"]); l.file("%s")' %
os.path.join(self.installdir, "examples", "msst", "in.msst")
)

# mpirun command needs an l.finalize() in the sanity check from LAMMPS 29Sep2021
if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('29Sep2021')):
# This is actually not needed if mpi4py is installed, and can cause a crash in version 2025+
if LooseVersion(self.cur_version) >= LooseVersion(translate_lammps_version('29Sep2021')) and \
LooseVersion(self.cur_version) < LooseVersion(translate_lammps_version('22Jul2025')):
custom_commands = [cmd + '; l.finalize()' for cmd in custom_commands]

custom_commands = ["""python -c '%s'""" % cmd for cmd in custom_commands]

# Execute sanity check commands within an initialized MPI in MPI enabled toolchains
if self.toolchain.options.get('usempi', None):
custom_commands = [self.toolchain.mpi_cmd_for(cmd, 1) for cmd in custom_commands]
# use up to 4 cores, to speed up tests
test_core_cnt = min(self.cfg.parallel, get_avail_core_count(), 4)
self.log.info("Using %s cores for the MPI tests" % test_core_cnt)
custom_commands = [self.toolchain.mpi_cmd_for(cmd, test_core_cnt) for cmd in custom_commands]

custom_commands = ["cd %s && " % execution_dir + cmd for cmd in custom_commands]

Expand Down
Loading