diff --git a/CHANGELOG.md b/CHANGELOG.md
index b78892e..b717aa3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,199 +1,201 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for final versions and [PEP440](https://www.python.org/dev/peps/pep-0440/) in case intermediate versions need to be released (e.g. development version `2.2.3.dev1` or release candidates `2.2.3rc1`), or individual commits are packaged.
## Unreleased
### Added
- Added `read()` method to dumpers to create a model from a dump file
- `getClusters()` can be called in MPI contact with partial contact maps
- Added a JSON encoder class for models and a JSON dumper
- CUDA compatibility is re-established, but has not been tested
### Changed
- Tamaas version numbers are now managed by
[versioneer](https://github.com/python-versioneer/python-versioneer). This
means that Git tags prefixed with `v` (e.g. `v2.2.3`) carry meaning and
determine the version. When no tag is set, versioneer uses the last tag,
specifies the commit short hash and the distance to the last tag (e.g.
`2.2.2+33.ge314b0e`). This version string is used in the compiled library, the
`setup.py` script and the `__version__` variable in the python module.
### Fixed
- Fixed a host of dump read/write issues when model type was not `volume_*d`.
Dumper tests are now streamlined and systematic.
- Fixed a bug where `Model::solveDirichlet` would not compute correctly
+- Fixed a bug where `Statistics::contact` would not normalize by the global
+ number of surface points
## v2.2.2 -- 2021-04-02
### Added
- Entry-point `tamaas` defines a grouped CLI for `examples/pipe_tools`. Try
executing `tamaas surface -h` from the command-line!
### Changed
- `CXXFLAGS` are now passed to the linker
- Added this changelog
- Using absolute paths for environmental variables when running `scons test`
- Reorganized documentation layout
- Gave the build system a facelift (docs are now generated directly with SCons
instead of a Makefile)
### Deprecated
- Python 2 support is discontinued. Version `v2.2.1` is the last PyPi build with
a Python 2 wheel.
- The scripts in `examples/pipe_tools` have been replaced by the `tamaas` command
### Fixed
- `UVWDumper` no longer imports `mpi4py` in sequential
- Compiling with different Thrust/FFTW backends
## v2.2.1 -- 2021-03-02
### Added
- Output registered fields and dumpers in `print(model)`
- Added `operator[]` to the C++ model class (for fields)
- Added `traction` and `displacement` properties to Python model bindings
- Added `operators` property to Python model bindings, which provides a
dict-like access to registered operators
- Added `shape` and `spectrum` to properties to Python surface generator
bindings
- Surface generator constructor accepts surface global shape as argument
- Choice of FFTW thread model
### Changed
- Tests use `/tmp` for temporary files
- Updated dependency versions (Thrust, Pybind11)
### Deprecated
- Most `get___()` and `set___()` in Python bindings have been deprecated. They
will generate a `DeprecationWarning`.
### Removed
- All legacy code
## v2.2.0 -- 2020-12-31
### Added
- More accurate function for computation of contact area
- Function to compute deviatoric of tensor fields
- MPI implementation
- Convenience `hdf5toVTK` function
- Readonly properties `shape`, `global_shape`, `boundary_shape` on model to give
shape information
### Changed
- Preprocessor defined macros are prefixed with `TAMAAS_`
- Moved `tamaas.to_voigt` to `tamaas.compute.to_voigt`
### Fixed
- Warning about deprecated constructors with recent GCC versions
- Wrong computation of grid strides
- Wrong computation of grid sizes in views
## v2.1.4 -- 2020-08-07
### Added
- Possibility to generate a static `libTamaas`
- C++ implementation of DFSANE solver
- Allowing compilation without OpenMP
### Changed
- NetCDF dumper writes frames to a single file
### Fixed
- Compatibility with SCons+Python 3
## v2.1.3 -- 2020-07-27
### Added
- Version number to `TamaasInfo`
### Changed
- Prepending root directory when generating archive
## v2.1.2 -- 2020-07-24
This release changes some core internals related to discrete Fourier transforms
for future MPI support.
### Added
- Caching `CXXFLAGS` in SCons build
- SCons shortcut to create code archive
- Test of the elastic-plastic contact solver
- Paraview data dumper (`.pvd` files)
- Compression for UVW dumper
- `__contains__` and `__iter__` Python bindings of model
- Warning message of possible overflow in Kelvin
### Changed
- Simplified `tamaas_info.cpp`, particularly the diff part
- Using a new class `FFTEngine` to manage discrete Fourier transforms. Plans are
re-used as much as possible with different data with the same shape. This is
in view of future MPI developments
- Redirecting I/O streams in solve functions so they can be used from Python
(e.g. in Jupyter notebooks)
- Calling `initialize()` and `finalize()` is no longer necessary
### Fixed
- Convergence issue with non-linear solvers
- Memory error in volume potentials
## v2.1.1 -- 2020-04-22
### Added
- SCons shortcut to run tests
### Fixed
- Correct `RPATH` for shared libraries
- Issues with SCons commands introduced in v2.1.0
- Tests with Python 2.7
## v2.1.0 -- 2020-04-17
### Added
- SCons shortcuts to build/install Tamaas and its components
- Selection of integration method for Kelvin operator
- Compilation option to remove the legacy part of Tamaas
- NetCDF dumper
### Fixed
- Link bug with clang
- NaNs in Kato saturated solver
## v2.0.0 -- 2019-11-11
First public release. Contains relatively mature elastic-plastic contact code.
diff --git a/tests/SConscript b/tests/SConscript
index 737f1ef..39a9cd4 100644
--- a/tests/SConscript
+++ b/tests/SConscript
@@ -1,215 +1,219 @@
# -*- mode:python; coding: utf-8 -*-
# vim: set ft=python:
# @file
# LICENSE
#
# Copyright (©) 2016-2021 EPFL (École Polytechnique Fédérale de Lausanne),
# Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
#
# 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
from SCons.Script import Split, Copy, Dir, Import
from detect import FindGTest, FindPybind11
# ------------------------------------------------------------------------------
def copyComStr(env, main):
if 'SHCXXCOMSTR' in main:
env['CXXCOMSTR'] = main['SHCXXCOMSTR']
if 'SHLINKCOMSTR' in main:
env['LINKCOMSTR'] = main['SHLINKCOMSTR']
# ------------------------------------------------------------------------------
def make_python_tests(env):
"""Copy python tests to build directory"""
test_env = env.Clone()
test_files = Split("""
test_hertz.py
test_westergaard.py
test_patch_westergaard.py
test_patch_plasticity.py
test_surface.py
test_hertz_disp.py
test_hertz_kato.py
test_saturated_pressure.py
test_flood_fill.py
test_integral_operators.py
test_dumper.py
test_tangential.py
test_boussinesq_surface.py
test_voigt.py
test_memory.py
test_epic.py
fftfreq.py
conftest.py
pytest.ini
""")
if env['use_mpi']:
test_files += ['test_mpi_routines.py', 'mpi_routines.py']
src_dir = "#/tests"
targets = [
test_env.Command(file, test_env.File(file, src_dir),
Copy("$TARGET", "$SOURCE"))
for file in test_files
]
test_env = env.Clone(tools=[pybind11])
# Helper module for integral operators
test_env['SHLIBPREFIX'] = ''
test_env.PrependUnique(LIBS=['Tamaas'])
register = test_env.Pybind11Module(
target="register_integral_operators",
source=["register_integral_operators.cpp"])
Import('libTamaas')
test_env.Depends(register, libTamaas)
targets.append(register)
return targets
# ------------------------------------------------------------------------------
def compile_google_test(env, gtest_path):
gtest_obj = env.Object('gtest.o',
[env.File("src/gtest-all.cc", gtest_path)])
return env.StaticLibrary('gtest', gtest_obj)
# ------------------------------------------------------------------------------
def make_google_tests(env):
gtest_dir = Dir(env['GTEST_ROOT'])
gtest_env = env.Clone(CPPPATH=[gtest_dir],
CXXFLAGS=['-pthread', '-isystem',
env.Dir('include', gtest_dir).path])
FindGTest(gtest_env)
libgtest = None
# Hugly hack to detect if we need to compile gtest submodule
if env['GTEST_ROOT'] == '#third-party/googletest/googletest':
gtest_path = str(gtest_dir)
libgtest = compile_google_test(gtest_env, gtest_path)
env.AppendUnique(CXXFLAGS=gtest_env['CXXFLAGS'])
env.PrependUnique(LIBS=['Tamaas', env.subst('python${py_version}')])
google_test_files = Split("""
- test_fft.cpp
test_grid.cpp
test_loop.cpp
test_model.cpp
test_static_types.cpp
test_integration.cpp
""")
+ if env['use_fftw']:
+ google_test_files += ['test_fftw.cpp']
+ if env['use_cuda']:
+ google_test_files += ['test_cufft.cpp']
+
# Necessary for the tests that use pybind11 calls to python
uses = []
if env['build_python']:
google_test_files.append('test_fftfreq.cpp')
uses = ['TAMAAS_USE_PYTHON']
if env['use_mpi']:
google_test_files.append('test_mpi.cpp')
defines = env['CPPDEFINES']
if type(defines) is not list:
defines = [defines]
gtest_main = env.Object("tamaas_gtest_main.o", 'tamaas_gtest_main.cc',
CPPDEFINES=defines + uses)
gtest_all = env.Program('test_gtest_all', google_test_files + [gtest_main],
LIBS=(env['LIBS'] + ['gtest']))
Import('libTamaas')
env.Depends(gtest_all, libTamaas)
env.Depends(gtest_all, libgtest)
return [gtest_all]
# ------------------------------------------------------------------------------
def make_bare_tests(env):
rough = env.Program("test_rough_surface.cpp")
Import('libTamaas')
env.Depends(rough, libTamaas)
return [rough]
# ------------------------------------------------------------------------------
Import('main_env')
# Setup of test environment
test_env = main_env.Clone()
test_env.AppendUnique(
LIBPATH=['.', '../src'],
RPATH=["'$$$$ORIGIN/../src'"]
)
test_env.PrependUnique(LIBS=['Tamaas'])
# Building tests that do not require any third party
targets = make_bare_tests(test_env)
# Build tests that required python bindings
if test_env['build_python']:
FindPybind11(test_env)
test_env.Tool(pybind11)
test_env.ParseConfig("${py_exec}-config --ldflags")
test_env['CCFLAGS'] = []
targets += make_python_tests(test_env)
# Building google tests
if test_env['use_googletest']:
targets += make_google_tests(test_env)
targets.append(test_env.Command('test_gtest.py', '#tests/test_gtest.py',
Copy('$TARGET', '$SOURCE')))
# Target alias to build tests
main_env.Alias('build-tests', targets)
# Check if pytest is installed
conf = Configure(test_env,
custom_tests={'CheckPythonModule': CheckPythonModule})
has_pytest = conf.CheckPythonModule('pytest')
conf.Finish()
# Define a command to execute tests
if has_pytest:
pytest_env = test_env.Clone()
test_env['pythonpath'] = '${build_dir}/python'
test_env['ld_library_path'] = '${build_dir}/src'
pytest_env.PrependENVPath('PYTHONPATH', '${pythonpath.abspath}')
pytest_env.PrependENVPath('LD_LIBRARY_PATH', '${ld_library_path.abspath}')
# Setting a moderate thread number
pytest_env['ENV']['OMP_NUM_THREADS'] = "1"
pytest_env['PYTESTOPTS'] = ['${"-v" if verbose else "-q"}']
test_target = pytest_env.Command(
'.phony_test', targets,
'${py_exec} -m pytest $PYTESTOPTS ${TARGET.dir}')
main_env.Alias('test', test_target)
else:
# We still define a target here so that `scons test` still works
dummy_command(main_env, 'test',
'Cannot run tests: pytest is not installed')
diff --git a/tests/test.hh b/tests/test.hh
index c36cc76..18c83c9 100644
--- a/tests/test.hh
+++ b/tests/test.hh
@@ -1,70 +1,72 @@
/**
*
* @author Lucas Frérot
*
* LICENSE
*
* Copyright (©) 2017 EPFL (Ecole Polytechnique Fédérale de
* Lausanne) Laboratory (LSMS - Laboratoire de Simulation en Mécanique des
* Solides)
*
* Tamaas is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* Tamaas 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Tamaas. If not, see .
*
*/
/* -------------------------------------------------------------------------- */
#include "fftw/interface.hh"
#include "tamaas.hh"
#include "gtest/gtest.h"
#include
#include
-#include
#include
+#include
#include
#include
template
bool compare(T&& a, U&& b) {
return std::mismatch(std::begin(a), std::end(a), std::begin(b)) ==
std::make_pair(std::end(a), std::end(b));
}
template
bool compare(T&& a, U&& b, Pred&& pred) {
return std::mismatch(std::begin(a), std::end(a), std::begin(b), pred) ==
std::make_pair(std::end(a), std::end(b));
}
struct AreFloatEqual {
tamaas::Real tolerance = 4 * std::numeric_limits::epsilon();
template
inline bool operator()(T&& x, U&& y) {
// T y = reinterpret_cast(z);
tamaas::Real abs_max = std::max>(std::abs(x), std::abs(y));
abs_max = std::max(abs_max, tamaas::Real(1.));
return std::abs(x - y) <= (tolerance * abs_max);
}
};
struct AreComplexEqual {
tamaas::Real tolerance = 4 * std::numeric_limits::epsilon();
+#ifdef TAMAAS_USE_FFTW
inline bool operator()(const tamaas::Complex& x,
const fftw::helper::complex& y) {
auto equal = AreFloatEqual{tolerance};
return equal(x.real(), y[0]) && equal(x.imag(), y[1]);
}
+#endif
inline bool operator()(const tamaas::Complex& x, const tamaas::Complex& y) {
auto equal = AreFloatEqual{tolerance};
return equal(x.real(), y.real()) && equal(x.imag(), y.imag());
}
};
diff --git a/tests/test_cufft.cpp b/tests/test_cufft.cpp
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_fft.cpp b/tests/test_fftw.cpp
similarity index 95%
rename from tests/test_fft.cpp
rename to tests/test_fftw.cpp
index 9333dfa..89f385c 100644
--- a/tests/test_fft.cpp
+++ b/tests/test_fftw.cpp
@@ -1,259 +1,237 @@
/**
* @file
* LICENSE
*
* Copyright (©) 2016-2021 EPFL (École Polytechnique Fédérale de Lausanne),
* Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
*
* 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 .
*
*/
/* -------------------------------------------------------------------------- */
#include "fftw/fftw_engine.hh"
#include "grid.hh"
#include "grid_hermitian.hh"
#include "grid_view.hh"
#include "test.hh"
using namespace tamaas;
using fft = fftw::helper;
/* -------------------------------------------------------------------------- */
template
struct span {
T* ptr;
std::size_t size;
~span() { fftw::free(ptr); }
const T* begin() const { return ptr; }
const T* end() const { return ptr + size; }
T* begin() { return ptr; }
T* end() { return ptr + size; }
operator T*() { return ptr; }
};
/* -------------------------------------------------------------------------- */
TEST(TestFFTEngine, FFT1D) {
constexpr UInt size = 1000;
FFTWEngine engine;
span data{fft::alloc_real(size), size};
span solution{fft::alloc_complex(size / 2 + 1), size / 2 + 1};
fftw::plan solution_plan{
fftw::plan_1d_forward(size, data, solution, engine.flags())};
std::iota(data.begin(), data.end(), 0);
fftw::execute(solution_plan);
Grid grid({size}, 1);
GridHermitian result({size / 2 + 1}, 1);
std::iota(grid.begin(), grid.end(), 0);
engine.forward(grid, result);
-#ifdef TAMAAS_USE_CUDA
- cudaDeviceSynchronize();
-#endif
-
ASSERT_TRUE(compare(result, solution, AreComplexEqual()))
<< "1D FFTW transform failed";
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFT2D) {
constexpr UInt size = 100;
constexpr UInt rsize = size * size;
constexpr UInt csize = size * (size / 2 + 1);
FFTWEngine engine;
span data{fft::alloc_real(rsize), rsize};
span solution{fft::alloc_complex(csize), csize};
fftw::plan solution_plan{
fftw::plan_2d_forward(size, size, data, solution, engine.flags())};
std::iota(data.begin(), data.end(), 0);
fftw::execute(solution_plan);
Grid grid({size, size}, 1);
GridHermitian result({size, size / 2 + 1}, 1);
std::iota(grid.begin(), grid.end(), 0);
engine.forward(grid, result);
-#ifdef TAMAAS_USE_CUDA
- cudaDeviceSynchronize();
-#endif
-
ASSERT_TRUE(compare(result, solution, AreComplexEqual()))
<< "2D FFTW transform failed";
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFT2DBackwards) {
const std::ptrdiff_t N0 = 20, N1 = 20;
Grid real({N0, N1}, 1);
GridHermitian spectral({N0, N1 / 2 + 1}, 1);
real = 1.;
FFTWEngine engine;
engine.forward(real, spectral);
real = 0;
engine.backward(real, spectral);
Grid reference({N0, N1}, 1);
reference = 1.;
ASSERT_TRUE(compare(real, reference, AreFloatEqual()));
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFT1D2Comp) {
constexpr UInt size = 20;
/// 1D single component FFT should be working here
Grid grid({size}, 2), data({size}, 1);
std::iota(grid.begin(), grid.end(), 0);
std::iota(data.begin(), data.end(), 0);
GridHermitian result({size / 2 + 1}, 2), solution({size / 2 + 1}, 1);
FFTWEngine engine;
engine.forward(grid, result);
-#ifdef TAMAAS_USE_CUDA
- cudaDeviceSynchronize();
-#endif
std::iota(data.begin(), data.end(), 0);
data *= 2;
engine.forward(data, solution);
const Real tol = 200 * std::numeric_limits::epsilon();
ASSERT_TRUE(
compare(make_component_view(result, 0), solution, AreComplexEqual{tol}))
<< "1D FFTW transform with 2 components failed on 1st component";
data += 1;
engine.forward(data, solution);
ASSERT_TRUE(
compare(make_component_view(result, 1), solution, AreComplexEqual{tol}))
<< "1D FFTW transform with 2 components failed on 2nd component";
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFT2D3Comp) {
constexpr UInt size = 20;
/// 2D single component FFT should be working here
Grid grid({size, size}, 3), data({size, size}, 1);
std::iota(grid.begin(), grid.end(), 0);
std::iota(data.begin(), data.end(), 0);
data *= 3;
GridHermitian result({size, size / 2 + 1}, 3),
solution({size, size / 2 + 1}, 1);
FFTWEngine engine;
engine.forward(grid, result);
-#ifdef TAMAAS_USE_CUDA
- cudaDeviceSynchronize();
-#endif
-
constexpr Real tol = 5000 * std::numeric_limits::epsilon();
for (UInt i = 0; i < 3; ++i) {
engine.forward(data, solution);
ASSERT_TRUE(
compare(make_component_view(result, i), solution, AreComplexEqual{tol}))
<< "2D FFTW transform with 3 components failed on " << i
<< "th component";
data += 1;
}
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFT2DViewTransform) {
constexpr UInt size = 20;
Grid data({size, size}, 1);
GridHermitian solution({size, size / 2 + 1}, 1);
std::iota(std::begin(data), std::end(data), 0);
FFTWEngine engine;
engine.forward(data, solution);
Grid grid({size, size}, 3);
auto view = make_component_view(grid, 1);
std::iota(view.begin(), view.end(), 0);
GridHermitian result({size, size / 2 + 1}, 1);
engine.forward(view, result);
constexpr Real tol = 5000 * std::numeric_limits::epsilon();
ASSERT_TRUE(compare(result, solution, AreComplexEqual{tol}))
<< "Fourier transform on component view fail";
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFTI1D2Comp) {
constexpr UInt size = 20;
Grid grid({size}, 2);
std::iota(grid.begin(), grid.end(), 0);
GridHermitian grid_hermitian({size / 2 + 1}, 2);
Grid result({size}, 2);
FFTWEngine engine;
engine.forward(grid, grid_hermitian);
engine.backward(result, grid_hermitian);
-#ifdef TAMAAS_USE_CUDA
- cudaDeviceSynchronize();
-#endif
-
ASSERT_TRUE(compare(grid, result, AreFloatEqual()))
<< "1D FFTI transform with 2 components failed";
}
/* -------------------------------------------------------------------------- */
TEST(TestFFTWEngine, FFTI2D3Comp) {
constexpr UInt size = 20;
Grid grid({size, size}, 3);
std::iota(grid.begin(), grid.end(), 0);
GridHermitian grid_hermitian({size, size / 2 + 1}, 3);
Grid result({size, size}, 3);
FFTWEngine engine;
engine.forward(grid, grid_hermitian);
engine.backward(result, grid_hermitian);
-#ifdef TAMAAS_USE_CUDA
- cudaDeviceSynchronize();
-#endif
ASSERT_TRUE(compare(grid, result, AreFloatEqual()))
<< "2D FFTI transform with 3 components failed";
}
diff --git a/tests/test_loop.cpp b/tests/test_loop.cpp
index 3e851ef..e9185ad 100644
--- a/tests/test_loop.cpp
+++ b/tests/test_loop.cpp
@@ -1,374 +1,401 @@
/**
* @file
* LICENSE
*
* Copyright (©) 2016-2021 EPFL (École Polytechnique Fédérale de Lausanne),
* Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
*
* 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 .
*
*/
/* -------------------------------------------------------------------------- */
#include "grid.hh"
#include "grid_view.hh"
#include "loop.hh"
#include "mpi_interface.hh"
#include "static_types.hh"
#include "test.hh"
/* -------------------------------------------------------------------------- */
/* WARNING: here we cannot use lambdas for tests because GoogleTest declares */
/* test functions as private members of classes which is incompatible with */
/* cuda's extended lambdas. I know... it's f*cking stupid */
/* -------------------------------------------------------------------------- */
using namespace tamaas;
template
struct AddOneInplace {
CUDA_LAMBDA void operator()(T& x) { x += 1; }
};
// Testing loops on one grid
TEST(TestLoops, OneArgument) {
Grid grid({20}, 1);
Grid solution({20}, 1);
- auto add_one = [](Real& x) { return x + 1; };
+ auto add_one = [](auto x) { return x + 1; };
std::iota(grid.begin(), grid.end(), 1);
// Makeing solution
std::transform(grid.begin(), grid.end(), solution.begin(), add_one);
auto add_one_inplace = AddOneInplace();
Loop::loop(add_one_inplace, grid);
ASSERT_TRUE(compare(grid, solution, AreFloatEqual()))
<< "One argument loop failed";
}
struct PrimalTest {
CUDA_LAMBDA void operator()(Int& primal, Int& val) {
val = (primal > 0) ? -1 : 1;
}
};
// Testing loops on two grids
TEST(TestLoops, TwoArguments) {
// Why no ints?
Grid grid({20, 20}, 1);
Grid primal({20, 20}, 1);
Grid solution({20, 20}, 1);
primal(0, 0) = 1;
primal(0, 1) = 1;
primal(1, 0) = 1;
primal(1, 1) = 1;
std::transform(primal.begin(), primal.end(), solution.begin(),
[](Int& primal) { return (primal > 0) ? -1 : 1; });
auto primal_test = PrimalTest();
Loop::loop(primal_test, primal, grid);
ASSERT_TRUE(compare(solution, grid)) << "Two argument loop failed";
}
struct AssignUInt {
CUDA_LAMBDA void operator()(UInt& x, UInt i) { x = i; }
};
// Testing an enumeration
TEST(TestLoops, Enumeration) {
Grid grid({100}, 1);
Grid solution({100}, 1);
std::iota(solution.begin(), solution.end(), 0);
auto assign_uint = AssignUInt();
Loop::loop(assign_uint, grid, Loop::range(100));
ASSERT_TRUE(compare(solution, grid)) << "Enumeration loop failed";
}
/* -------------------------------------------------------------------------- */
struct Identity {
CUDA_LAMBDA UInt operator()(UInt& x) const { return x; }
};
// Testing one grid reductions
TEST(TestReductions, OneArgument) {
Grid grid({6}, 1);
std::iota(grid.begin(), grid.end(), 1);
const auto id = Identity();
// Sum reduction
UInt sol = mpi::allreduce(
std::accumulate(grid.begin(), grid.end(), 0, std::plus<>()));
UInt red = Loop::reduce(id, grid);
ASSERT_TRUE(sol == red) << "Addition reduction failed on one argument";
// Product reduction
sol = mpi::allreduce(
std::accumulate(grid.begin(), grid.end(), 1, std::multiplies<>()));
red = Loop::reduce(id, grid);
ASSERT_TRUE(sol == red) << "Multiplication reduction failed on one argument";
// Min reduction
sol = mpi::allreduce(
*std::min_element(grid.begin(), grid.end()));
red = Loop::reduce(id, grid);
ASSERT_TRUE(sol == red) << "Min reduction failed on one argument";
// Max reduction
sol = mpi::allreduce(
*std::max_element(grid.begin(), grid.end()));
red = Loop::reduce(id, grid);
ASSERT_TRUE(sol == red) << "Max reduction failed on one argument";
}
+struct AssignReduce {
+ CUDA_LAMBDA UInt operator()(UInt& x, UInt i) {
+ x = i;
+ return x;
+ }
+};
+
TEST(TestReductions, ReduceAndTransform) {
UInt n = 20;
Grid grid({n}, 1), solution({n}, 1);
std::iota(solution.begin(), solution.end(), 0);
UInt sum_value = mpi::allreduce((n - 1) * n / 2);
- UInt res = Loop::reduce(
- [](UInt& x, UInt i) {
- x = i;
- return x;
- },
- grid, Loop::range(n));
+ auto assign_reduce = AssignReduce{};
+ UInt res =
+ Loop::reduce(assign_reduce, grid, Loop::range(n));
EXPECT_EQ(res, sum_value) << "Reduction failed";
EXPECT_TRUE(compare(grid, solution)) << "Assign failed";
}
struct PrimalReduce {
CUDA_LAMBDA UInt operator()(UInt& p, UInt& val) { return (p > 0) ? val : 0; }
};
TEST(TestReductions, TwoArguments) {
Grid grid({20}, 1);
Grid primal({20}, 1);
grid = 1;
primal(0) = 1;
primal(1) = 1;
auto primal_reduce = PrimalReduce();
// Reduce on values where primal > 0
UInt red = Loop::reduce(primal_reduce, primal, grid);
ASSERT_TRUE(red == mpi::allreduce(UInt{2}))
<< "Two args reduction failed";
}
/* -------------------------------------------------------------------------- */
TEST(TestRange, type_trait) {
Grid grid({1}, 1);
auto gridrange = range>(grid);
static_assert(decltype(gridrange)::is_valid_container>::value,
"is_valid_container Type trait is wrong");
static_assert(
not decltype(gridrange)::is_valid_container&>::value,
"is_valid_container Type trait is wrong");
static_assert(not Range, Real,
1>::is_valid_container::value,
"is_valid_container Type trait is wrong");
}
+struct AssignOne {
+ CUDA_LAMBDA void operator()(VectorProxy x) { x = 1; }
+ CUDA_LAMBDA void operator()(UInt& x) { x = 1; }
+ CUDA_LAMBDA void operator()(VectorProxy v) { v(2) = 1; }
+ CUDA_LAMBDA void operator()(VectorProxy v) { v = 1; }
+};
+
TEST(TestRange, headless) {
if (mpi::rank() != 0)
GTEST_SKIP() << "Skipping because not root process";
Grid grid({10}, 1), solution({10}, 1);
std::fill(++solution.begin(), solution.end(), 1);
auto gridrange = range>(grid).headless();
- Loop::loop([](auto x) { x = 1; }, gridrange);
+ auto assign_one = AssignOne{};
+ Loop::loop(assign_one, gridrange);
ASSERT_TRUE(compare(grid, solution)) << "Headless fail";
}
template
using WrapVector = VectorProxy;
struct AddOneVector {
CUDA_LAMBDA void operator()(WrapVector x) { x(0) += 1; }
};
TEST(TestStridedLoops, VectorStride) {
Grid grid({10, 10}, 2);
std::iota(grid.begin(), grid.end(), 1);
Grid solution({10, 10}, 2);
solution = grid;
std::for_each(solution.begin(), solution.end(), [](UInt& x) {
if (x % 2 == 1)
x += 1;
});
auto add_one_inplace = AddOneVector();
Loop::loop(add_one_inplace, range>(grid));
ASSERT_TRUE(compare(solution, grid)) << "Static vector strided loop failed";
}
template
using WrapMatrix = MatrixProxy;
struct SetOneMatrix {
CUDA_LAMBDA void operator()(WrapMatrix x) {
x(0, 0) = 1;
x(1, 1) = 1;
}
};
TEST(TestStridedLoops, MatrixStride) {
Grid grid({10, 10}, 4);
Grid solution({10, 10}, 4);
std::iota(solution.begin(), solution.end(), 0);
std::for_each(solution.begin(), solution.end(), [](UInt& x) {
if (x % 4 == 0 || x % 4 == 3)
x = 1;
else
x = 0;
});
auto set_one = SetOneMatrix();
Loop::loop(set_one, range>(grid));
ASSERT_TRUE(compare(solution, grid)) << "Static matrix strided loop failed";
}
struct VectorReduction {
CUDA_LAMBDA Vector operator()(VectorProxy v) const {
return v;
}
};
struct BroadcastSet123 {
CUDA_LAMBDA inline void operator()(VectorProxy v) const {
v(0) = 1;
v(1) = 2;
v(2) = 3;
}
};
TEST(TestStridedReduction, VectorReduce) {
Grid grid({10, 10}, 3);
Loop::loop(BroadcastSet123(), range>(grid));
auto res = Loop::reduce(VectorReduction(),
range>(grid));
auto reduce = [](UInt x) { return mpi::allreduce(x); };
ASSERT_EQ(res(0), reduce(100));
ASSERT_EQ(res(1), reduce(200));
ASSERT_EQ(res(2), reduce(300));
}
+struct ScalarReduce {
+ CUDA_LAMBDA UInt operator()(UInt& x) { return x; }
+};
+
TEST(TestViewReduction, ScalarReduce) {
Grid grid({10, 10}, 3);
Loop::loop(BroadcastSet123(), range>(grid));
auto view = make_component_view(grid, 2);
- UInt res = Loop::reduce([](UInt& x) { return x; }, view);
+ auto scalar_reduce = ScalarReduce{};
+ UInt res = Loop::reduce(scalar_reduce, view);
EXPECT_EQ(res, mpi::allreduce(UInt{300}))
<< "Reduce on component view fail";
}
TEST(TestViewReduction, VectorReduce) {
Grid grid({10, 10}, 3);
auto view2 = make_view(grid, 0);
Loop::loop(BroadcastSet123(), range>(view2));
auto res2 = Loop::reduce(VectorReduction(),
range>(view2));
auto reduce = [](UInt x) { return mpi::allreduce(x); };
EXPECT_EQ(res2(0), reduce(10));
EXPECT_EQ(res2(1), reduce(20));
EXPECT_EQ(res2(2), reduce(30));
}
TEST(TestViewLoop, ScalarLoop) {
Grid grid({10, 10}, 3), solution({10, 10}, 3);
auto view = make_component_view(grid, 2);
- Loop::loop([](auto& x) { x = 1; }, view);
-
- Loop::loop([](auto v) { v(2) = 1; }, range>(solution));
+ auto assign_one = AssignOne{};
+ Loop::loop(assign_one, view);
+ Loop::loop(assign_one, range>(solution));
ASSERT_TRUE(compare(grid, solution)) << "View loop fail";
}
TEST(TestLoopChecks, Components) {
Grid grid({10, 10}, 3);
- EXPECT_THROW(
- Loop::loop([](auto v) { v = 0; }, range>(grid)),
- Exception)
+ auto assign_one = AssignOne();
+ EXPECT_THROW(Loop::loop(assign_one, range>(grid)),
+ Exception)
<< "Broken check on number of components";
}
+struct CopyValues {
+ CUDA_LAMBDA auto operator()(UInt& x, UInt& y) { x = y; }
+ CUDA_LAMBDA auto operator()(VectorProxy x, VectorProxy y) {
+ x(0) = y(0);
+ }
+};
+
TEST(TestLoopChecks, LoopSize) {
Grid grid({10}, 2), other({10}, 1);
- EXPECT_THROW(Loop::loop([](auto& x, auto& y) { x = y; }, grid, other),
- Exception)
+ CopyValues func;
+
+ EXPECT_THROW(Loop::loop(func, grid, other), Exception)
<< "Check on loop size without ranges fail";
other.resize({11});
- EXPECT_THROW(Loop::loop([](auto x, auto y) { x(0) = y(0); },
- range>(grid),
+ EXPECT_THROW(Loop::loop(func, range>(grid),
range>(other)),
Exception)
<< "Check on loop size with ranges fail";
Grid twod({10, 11}, 2);
auto view = make_view(twod, 0);
- EXPECT_THROW(Loop::loop([](auto& x, auto& y) { x = y; }, grid, view),
- Exception)
+ EXPECT_THROW(Loop::loop(func, grid, view), Exception)
<< "Check on loop size with view fail";
}
+struct ReduceAndTransform {
+ CUDA_LAMBDA UInt operator()(VectorProxy x, UInt) {
+ x += 1;
+ return x(0) + x(1);
+ }
+};
+
TEST(TestReductions, ReduceAndTransformVector) {
UInt n = 20;
Grid grid({n}, 2), solution({n}, 2);
std::iota(solution.begin(), solution.end(), 1);
std::iota(grid.begin(), grid.end(), 0);
UInt sum_value = mpi::allreduce((2 * n + 1) * 2 * n / 2);
+ auto reduce_transform = ReduceAndTransform();
UInt res = Loop::reduce(
- [](auto x, UInt) {
- x += 1;
- return x(0) + x(1);
- },
- range>(grid), Loop::range(n));
+ reduce_transform, range>(grid), Loop::range(n));
EXPECT_EQ(res, sum_value) << "Reduction failed";
EXPECT_TRUE(compare(grid, solution)) << "Assign failed";
}