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