diff --git a/python/setup.cfg.in b/python/setup.cfg.in index 0a9c381..17d7a67 100644 --- a/python/setup.cfg.in +++ b/python/setup.cfg.in @@ -1,47 +1,47 @@ [metadata] name = tamaas url = https://gitlab.com/tamaas version = @version@ author = @author_list@ author_email = @email@ long_description = file: pypi.md long_description_content_type = text/markdown project_urls = Bug Tracker = https://gitlab.com/tamaas/tamaas/-/issues Documentation = https://tamaas.readthedocs.io/en/latest/ Source Code = https://gitlab.com/tamaas/tamaas Changes = https://gitlab.com/tamaas/tamaas/-/blob/master/CHANGELOG.md platforms = Linux classifiers = Intended Audience :: Science/Research License :: OSI Approved :: GNU Affero General Public License v3 Programming Language :: C++ Programming Language :: Python :: 3 Topic :: Scientific/Engineering Intended Audience :: Science/Research Operating System :: POSIX :: Linux [options] packages = find: include_package_data = True -install_requires = numpy +install_requires = oldest-supported-numpy [options.entry_points] console_scripts: tamaas = tamaas.__main__:main [options.extras_require] all = uvw h5py netCDF4 scipy mpi4py dumpers = uvw h5py netCDF4 solvers = scipy mpi = mpi4py \ No newline at end of file diff --git a/site_scons/detect.py b/site_scons/detect.py index 05f5cb0..71c8820 100644 --- a/site_scons/detect.py +++ b/site_scons/detect.py @@ -1,322 +1,321 @@ # -*- coding: utf-8 -*- # # Copyright (©) 2016-2024 EPFL (École Polytechnique Fédérale de Lausanne), # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) # Copyright (©) 2020-2024 Lucas Frérot # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from SCons.Script import Configure, Dir from SCons.Errors import StopError # ------------------------------------------------------------------------------ def _get_path(env, ext: str, module_var: str): path = "" if module_var in env and env[module_var] != "": root = Dir(env[module_var]) if not root.exists() or not root.isdir(): raise RuntimeError("{} is set to a non-existing path '{}'".format( module_var, root)) path = root.Dir(ext) if not path.exists() or not path.isdir(): raise RuntimeError("{} does not contain '{}' directory".format( module_var, ext)) return [path] # ------------------------------------------------------------------------------ def FindFFTW(env, components=None, precision='double', module_var='FFTW_ROOT'): """Find FFTW3 and set relevant environment variables""" if not env.get('should_configure', True): return if components is None: components = [] fftw_vars = {} fftw_vars['CPPPATH'] = _get_path(env, 'include', module_var) fftw_vars['LIBPATH'] = _get_path(env, 'lib', module_var) try: fftw_vars['LIBPATH'] += _get_path(env, 'lib64', module_var) except RuntimeError: pass fftw_vars['RPATH'] = fftw_vars['LIBPATH'] if 'threads' in components: fftw_vars['LIBS'] = ['pthread'] # Setting up FFTW wishes = ['main'] + components fftw_name = "fftw3{}" # Components lib_names = {'main': fftw_name, 'threads': fftw_name + '_threads', 'omp': fftw_name + '_omp', 'mpi': fftw_name + '_mpi'} # Checking list of wishes try: libs = [lib_names[i].format("") for i in wishes] except KeyError: raise StopError( 'Incompatible FFTW wishlist {0}'.format(wishes)) # Add long precision libraries if precision == 'long double': libs += [lib_names[i].format("l") for i in wishes] elif precision == 'float': libs += [lib_names[i].format("f") for i in wishes] conf_env = env.Clone(**fftw_vars) conf = Configure(conf_env) for lib in libs: inc_names = ['fftw3.h'] if 'mpi' in lib: inc_names.append('fftw3-mpi.h') if not conf.CheckLibWithHeader(lib, inc_names, 'c++'): raise StopError( ('Failed to find library {0} or ' 'headers {1}. Check the build options "fftw_threads"' ' and "FFTW_ROOT".').format(lib, str(inc_names))) conf_env = conf.Finish() fftw_vars['LIBS'] = fftw_vars.get('LIBS', []) + libs # Update modified variables env.AppendUnique(**fftw_vars) # ------------------------------------------------------------------------------ def FindBoost(env, headers=['boost/version.hpp'], module_var='BOOST_ROOT'): """Find Boost and set relevant environment variables""" if not env.get('should_configure', True): return boost_vars = {} boost_vars['CPPPATH'] = _get_path(env, 'include', module_var) conf_env = env.Clone(**boost_vars) conf = Configure(conf_env) if not conf.CheckCXXHeader(headers): raise StopError( 'Failed to find Boost headers {}'.format(headers)) conf_env = conf.Finish() # Update modified variables env.AppendUnique(**boost_vars) # ------------------------------------------------------------------------------ def FindThrust(env, backend='omp', module_var='THRUST_ROOT'): """Find Thrust and set relevant environment variables""" if not env.get('should_configure', True): return if backend not in ('cpp', 'omp', 'cuda', 'tbb'): raise StopError( 'Unknown thrust backend "{}"'.format(backend)) thrust_vars = {} try: thrust_vars['CPPPATH'] = _get_path(env, 'include', module_var) except RuntimeError: thrust_vars['CPPPATH'] = _get_path(env, '', module_var) if "clang++" in env['CXX']: thrust_vars['CXXFLAGS'] = ["-Wno-unused-local-typedef"] thrust_vars['CPPDEFINES'] = [ "THRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_{}".format(backend.upper()) ] if backend == 'cuda': thrust_vars['CPPDEFINES'].append("TAMAAS_USE_CUDA") # Detect thrust conf_env = env.Clone(**thrust_vars) conf = Configure(conf_env) if not conf.CheckCXXHeader('thrust/version.h'): raise StopError( 'Failed to find Thrust') conf_env = conf.Finish() # Update modified variables env.AppendUnique(**thrust_vars) def FindLibCUDACXX(env, module_var='LIBCUDACXX_ROOT'): - # Recent versions of thrust need libcudacxx libcuda_vars = {} # If root of Thrust is specified but not libcudacxx, look in the thrust repo if env["THRUST_ROOT"] != "" and env["LIBCUDACXX_ROOT"] == "": try: default_path = "dependencies/libcudacxx/include" path = _get_path(env, default_path, "THRUST_ROOT") libcuda_vars["CPPPATH"] = path except RuntimeError as e: print(e) print("You may want to clone Thrust with its submodules") else: libcuda_vars["CPPPATH"] = \ _get_path(env, "include", module_var) existing_cpppath = env["CPPPATH"] conf_env = env.Clone(**libcuda_vars) conf_env.AppendUnique(CPPPATH=existing_cpppath) conf = Configure(conf_env) # Check for a header in libcudacxx, fail is ok for backward compatibility conf.CheckCXXHeader("cuda/std/type_traits") # We only have an error if this include does not work if not conf.CheckCXXHeader("thrust/pair.h"): raise StopError("Thrust versions >= 2.x need libcudacxx " "(use LIBCUDACXX_ROOT to specify path)") conf_env = conf.Finish() env.AppendUnique(**libcuda_vars) # ------------------------------------------------------------------------------ def FindPybind11(env, module_var='PYBIND11_ROOT'): """Detech Pybind11 and set appropriate build variables""" if not env.get('should_configure', True) \ or env.get("PYBIND11_FOUND", False): return pybind11_vars = {} clone = env.Clone(CPPPATH=[]) clone.ParseConfig('${py_exec}-config --includes') clone.AppendUnique(CPPPATH=_get_path(env, 'include', module_var)) pybind11_vars['CPPPATH'] = clone['CPPPATH'] conf = Configure(clone) if not conf.CheckCXXHeader('pybind11/pybind11.h'): raise StopError( "Failed to find pybind11 header.\n" "Install pybind11 headers or " "set PYBIND11_ROOT='#third-party/pybind11' " "to use vendored dependency." ) conf.Finish() pybind11_vars['PYBIND11_FOUND'] = True # Update variables env.AppendUnique(**pybind11_vars) # ------------------------------------------------------------------------------ def FindCuda(env, module_var="CUDA_ROOT"): """Detect cuda on clusters""" if not env.get('should_configure', True): return if 'CUDA_ROOT' in env: env['CUDA_TOOLKIT_PATH'] = _get_path(env, '', module_var)[0] else: env['CUDA_TOOLKIT_PATH'] = '/opt/cuda' env['CUDA_COMPONENTS'] = ['cufft'] env['CUDA_ARCH_FLAG'] = '-arch=sm_60' colors = env['COLOR_DICT'] if not env['verbose']: env['SHCXX'] = 'nvcc' env['NVCCCOMSTR'] = env['SHCXXCOMSTR'] env['SHLINKCOMSTR'] = \ u'{0}[Linking (cuda)] {1}$TARGET'.format(colors['purple'], colors['end']) env.AppendUnique(CXXFLAGS=[ "-expt-extended-lambda", # experimental lambda support "-expt-relaxed-constexpr", # experimental constexpr ]) if env['build_type'] == 'debug': env.AppendUnique(CXXFLAGS="-G") env.Tool('nvcc') # ------------------------------------------------------------------------------ def FindGTest(env): """A tool to configure GoogleTest""" if not env.get('should_configure', True): return conf = Configure(env) env['has_gtest'] = conf.CheckCXXHeader('gtest/gtest.h') env = conf.Finish() # ------------------------------------------------------------------------------ def FindExpolit(env): """A tool to configure Expolit""" if not env.get('should_configure', True): return expolit_vars = {"CPPPATH": "#/third-party/expolit/include"} conf_env = env.Clone() conf_env.AppendUnique(**expolit_vars) conf = Configure(conf_env) if not conf.CheckCXXHeader('expolit/expolit'): raise StopError( 'Failed to find Expolit header\n' + "Run 'git submodule update --init " + "third-party/expolit'") conf_env = conf.Finish() env.AppendUnique(**expolit_vars) # ------------------------------------------------------------------------------ def FindPETSc(env, module_var='PETSC_ROOT'): """A tool to configure PETSc""" petsc_vars = {} conf_env = env.Clone() petsc_vars['CPPPATH'] = _get_path(env, 'include', module_var) petsc_vars['LIBPATH'] = _get_path(env, 'lib', module_var) try: petsc_vars['LIBPATH'] += _get_path(env, 'lib64', module_var) except RuntimeError: pass petsc_vars['LIBS'] = 'petsc' petsc_vars['RPATH'] = petsc_vars['LIBPATH'] conf_env = env.Clone() conf_env.AppendUnique(**petsc_vars) conf = Configure(conf_env) if not conf.CheckLibWithHeader('petsc', 'petscmat.h', language='c++'): raise StopError('Failed to find PETSc, or PETSc with MPI') conf_env = conf.Finish() env.AppendUnique(**petsc_vars) diff --git a/tests/test_mpi_routines.py b/tests/test_mpi_routines.py index 69f79db..02b4089 100644 --- a/tests/test_mpi_routines.py +++ b/tests/test_mpi_routines.py @@ -1,90 +1,92 @@ # -*- coding: utf-8 -*- # # Copyright (©) 2016-2024 EPFL (École Polytechnique Fédérale de Lausanne), # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides) # Copyright (©) 2020-2024 Lucas Frérot # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . from __future__ import print_function import inspect import sys import re import os import numpy as np import tamaas import pytest +try: + from mpi4py import MPI +except ImportError: + pytest.skip('could not import mpi4py', allow_module_level=True) + PROG = ''' import sys import numpy as np sys.path = ['{}'] + sys.path import mpi_routines from mpi4py import MPI res = 0 try: mpi_routines.{}() except Exception as e: print(e) res = 1 comm = MPI.Comm.Get_parent() comm.Reduce(np.array(res, 'i'), None, op=MPI.SUM, root=0) comm.Free() sys.exit(0) ''' HOSTFILE_CONTENT = 'localhost slots=24\n' @pytest.mark.skipif(not tamaas.TamaasInfo.has_mpi, reason='tamaas compiled w/o MPI') def test_mpi_routines(): - pytest.importorskip("mpi4py") - - from mpi4py import MPI import mpi_routines me_path = os.path.realpath(__file__) current_dir = os.path.dirname(me_path) hostfile = os.path.join(current_dir, 'hostfile-mpi') with open(hostfile, 'w') as fh: fh.write(HOSTFILE_CONTENT) mpi_matcher = re.compile('^mpi_') for name, _ in inspect.getmembers(mpi_routines, inspect.isfunction): if mpi_matcher.match(name): try: info = MPI.Info.Create() info['add-hostfile'] = hostfile print('Spawning "{}"'.format(name)) comm = MPI.COMM_SELF.Spawn( sys.executable, args=[ '-c', PROG.format(os.path.abspath(current_dir), name), ], maxprocs=2, info=info) res = np.array(0, 'i') comm.Reduce(None, res, op=MPI.SUM, root=MPI.ROOT) except MPI.Exception as exc: print(exc) raise RuntimeError('Could not spawn MPI processes for "{}"' .format(name)) assert res == 0, name