diff --git a/src/mpi/partitioner.hh b/src/mpi/partitioner.hh index 75632d3..85b8cb2 100644 --- a/src/mpi/partitioner.hh +++ b/src/mpi/partitioner.hh @@ -1,64 +1,80 @@ /** * @file * @section LICENSE * * Copyright (©) 2016-2020 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 . * */ /* -------------------------------------------------------------------------- */ #ifndef PARTITIONER_HH #define PARTITIONER_HH /* -------------------------------------------------------------------------- */ #include "fftw_mpi_interface.hh" #include "mpi_interface.hh" #include "tamaas.hh" #include "grid.hh" #include #include #include /* -------------------------------------------------------------------------- */ namespace tamaas { template struct Partitioner { static decltype(auto) global_size(std::array local) { local.front() = mpi::allreduce(local.front()); return local; } + template + static decltype(auto) global_size(const Grid& grid) { + return global_size(grid.sizes()); + } + static decltype(auto) local_size(std::array global) { auto tup = fftw::mpi::local_size_many(dim, cast_size(global).data(), 1); global.front() = static_cast(std::get<1>(tup)); return global; } + template + static decltype(auto) local_size(const Grid& grid) { + return local_size(grid.sizes()); + } + static decltype(auto) local_offset(const std::array& global) { auto tup = fftw::mpi::local_size_many(dim, cast_size(global).data(), 1); - return static_cast(std::get<2>(tup)); + return static_cast(std::get<2>(tup)); + } + + template + static decltype(auto) local_offset(const Grid& grid) { + auto offset = local_offset(global_size(grid.sizes())); + return offset * grid.getStrides().front(); } static decltype(auto) cast_size(const std::array& s) { std::array n; std::copy(s.begin(), s.end(), n.begin()); return n; } }; } // namespace tamaas #endif diff --git a/tests/SConscript b/tests/SConscript index 6c1ceba..c6b9cc7 100644 --- a/tests/SConscript +++ b/tests/SConscript @@ -1,214 +1,217 @@ # -*- mode:python; coding: utf-8 -*- # vim: set ft=python: # @file # @section LICENSE # # Copyright (©) 2016-2020 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 os.path import join 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['legacy_bem']: test_files += ['test_bem_grid.py', 'test_autocorrelation.py'] src_dir = "#/tests" targets = [ test_env.Command(file, join(src_dir, file), Copy("$TARGET", "$SOURCE")) for file in test_files ] test_env = env.Clone(tools=[pybind11]) # Helper module for integral operators test_env['SHLIBPREFIX'] = '' register = test_env.Pybind11Module( target="register_integral_operators", source=["register_integral_operators.cpp"], LIBS=['Tamaas']) 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', [join(gtest_path, "src/gtest-all.cc")]) 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', join(str(gtest_dir), "include")]) colors = env['COLOR_DICT'] if not env['verbose']: gtest_env['ARCOMSTR'] = u'{}[Ar]{} $TARGET'.format(colors['purple'], colors['end']) gtest_env['RANLIBCOMSTR'] = \ u'{}[Randlib]{} $TARGET'.format(colors['purple'], colors['end']) 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(LIBS=['Tamaas'], CXXFLAGS=gtest_env['CXXFLAGS']) google_test_files = Split(""" test_fft.cpp test_grid.cpp test_loop.cpp test_model.cpp test_static_types.cpp test_integration.cpp """) # Necessary for the tests that use pybind11 calls to python - use_python = [] + uses = [] if env['build_python']: google_test_files.append('test_fftfreq.cpp') - use_python = ['USE_PYTHON'] + uses.append('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 + use_python) + 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(LIBS=['Tamaas'], LIBPATH=['.']) # 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(main_env, custom_tests={'CheckPythonModule': CheckPythonModule}) has_pytest = conf.CheckPythonModule('pytest') conf.Finish() # Define a command to execute tests if has_pytest: pytest_env = main_env.Clone() pytest_env.PrependENVPath('PYTHONPATH', pytest_env.subst('${build_dir}/python')) pytest_env.PrependENVPath('LD_LIBRARY_PATH', pytest_env.subst('${build_dir}/src')) test_target = pytest_env.Command('#/.phony_test', targets, '${py_exec} -m pytest ${build_dir}/tests') 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_mpi.cpp b/tests/test_mpi.cpp new file mode 100644 index 0000000..85d55f4 --- /dev/null +++ b/tests/test_mpi.cpp @@ -0,0 +1,88 @@ +/** + * @file + * @section LICENSE + * + * Copyright (©) 2016-2020 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 "fft_mpi_engine.hh" +#include "grid.hh" +#include "grid_hermitian.hh" +#include "grid_view.hh" +#include "partitioner.hh" +#include "test.hh" + +#include + +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(TestFFTWInterface, MPISizes) { + const std::ptrdiff_t N0 = 100, N1 = 100; + std::ptrdiff_t local_n0, local_n0_start; + auto alloc_local = fftw_mpi_local_size_2d(N0, N1 / 2 + 1, MPI_COMM_WORLD, + &local_n0, &local_n0_start); + + const std::ptrdiff_t N[] = {N0, N1 / 2 + 1}; + auto sizes = fftw::mpi::local_size_many(2, N, 1); + + ASSERT_EQ(std::get<0>(sizes), alloc_local); + ASSERT_EQ(std::get<1>(sizes), local_n0); + ASSERT_EQ(std::get<2>(sizes), local_n0_start); +} + +/* -------------------------------------------------------------------------- */ +TEST(TestPartitioner, LocalSizes) { + const std::ptrdiff_t N0 = 100, N1 = 100; + std::ptrdiff_t local_n0, local_n0_start; + fftw_mpi_local_size_2d(N0, N1 / 2 + 1, MPI_COMM_WORLD, &local_n0, + &local_n0_start); + + auto local_size = Partitioner<2>::local_size({N0, N1 / 2 + 1}); + ASSERT_EQ(local_size[0], local_n0); + ASSERT_EQ(local_size[1], N1 / 2 + 1); + ASSERT_EQ(Partitioner<2>::local_offset({N0, N1 / 2 + 1}), local_n0_start); +} + +/* -------------------------------------------------------------------------- */ +TEST(TestPartitioner, LocalOffsetInGrid) { + std::array N = {10, 10}; + std::array Nglobal = {mpi::allreduce(N.front()), N.back()}; + + Grid local(N, 1), global(Nglobal, 1); + auto offset = Partitioner<2>::local_offset(local); + auto local_n0_start = Partitioner<2>::local_offset(Nglobal); + ASSERT_EQ(offset, &global(local_n0_start, 0) - &global(0, 0)); +}