diff --git a/cmake/FindCLANG_FORMAT.cmake b/cmake/FindCLANG_FORMAT.cmake index c4f42293..338cae3d 100644 --- a/cmake/FindCLANG_FORMAT.cmake +++ b/cmake/FindCLANG_FORMAT.cmake @@ -1,5 +1,5 @@ ############################################################################ -# Copyright (c) 2019 by the Cabana authors # +# Copyright (c) 2020 by the Cabana authors # # All rights reserved. # # # # This file is part of the Cabana library. Cabana is distributed under a # diff --git a/unit_test/CMakeLists.txt b/unit_test/CMakeLists.txt index a2c0853b..bbbae834 100644 --- a/unit_test/CMakeLists.txt +++ b/unit_test/CMakeLists.txt @@ -5,12 +5,16 @@ set(gtest_args --gtest_color=yes) ##--------------------------------------------------------------------------## macro(CabanaMD_add_tests) cmake_parse_arguments(CABANAMD_UNIT_TEST "MPI" "" "NAMES" ${ARGN}) - set(CABANAMD_UNIT_TEST_MPIEXEC_NUMPROCS 1 2) - if(MPIEXEC_MAX_NUMPROCS GREATER 2) - list(APPEND CABANAMD_UNIT_TEST_MPIEXEC_NUMPROCS ${MPIEXEC_MAX_NUMPROCS}) + set(CABANAMD_UNIT_TEST_MPIEXEC_NUMPROCS 1) + if(CABANAMD_UNIT_TEST_MPI) + list(APPEND CABANAMD_UNIT_TEST_MPIEXEC_NUMPROCS 2) + if(MPIEXEC_MAX_NUMPROCS GREATER 1) + list(APPEND CABANAMD_UNIT_TEST_MPIEXEC_NUMPROCS ${MPIEXEC_MAX_NUMPROCS}) + endif() endif() set(CABANAMD_UNIT_TEST_NUMTHREADS 1 2) - set(CABANAMD_UNIT_TEST_MAIN unit_test_main.cpp) + + set(CABANAMD_UNIT_TEST_MAIN mpi_unit_test_main.cpp) foreach(_device SERIAL OPENMP CUDA HIP) if(Kokkos_ENABLE_${_device}) @@ -46,4 +50,7 @@ macro(CabanaMD_add_tests) endforeach() endmacro() -CabanaMD_add_tests(MPI NAMES Integrator) +CabanaMD_add_tests(NAMES Integrator Neighbor) + +# TODO: +#CabanaMD_add_tests(MPI NAMES Comm) diff --git a/unit_test/TestCUDA_Category.hpp b/unit_test/TestCUDA_Category.hpp index 13bae5ed..cc4e0445 100644 --- a/unit_test/TestCUDA_Category.hpp +++ b/unit_test/TestCUDA_Category.hpp @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2018-2019 by the Cabana authors * + * Copyright (c) 2018-2020 by the Cabana authors * * All rights reserved. * * * * This file is part of the Cabana library. Cabana is distributed under a * diff --git a/unit_test/TestCUDA_UVM_Category.hpp b/unit_test/TestCUDA_UVM_Category.hpp index 0858b81b..8aa49f5a 100644 --- a/unit_test/TestCUDA_UVM_Category.hpp +++ b/unit_test/TestCUDA_UVM_Category.hpp @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2018-2019 by the Cabana authors * + * Copyright (c) 2018-2020 by the Cabana authors * * All rights reserved. * * * * This file is part of the Cabana library. Cabana is distributed under a * diff --git a/unit_test/TestOPENMP_Category.hpp b/unit_test/TestOPENMP_Category.hpp index b7d5c008..d1531280 100644 --- a/unit_test/TestOPENMP_Category.hpp +++ b/unit_test/TestOPENMP_Category.hpp @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2018-2019 by the Cabana authors * + * Copyright (c) 2018-2020 by the Cabana authors * * All rights reserved. * * * * This file is part of the Cabana library. Cabana is distributed under a * diff --git a/unit_test/TestSERIAL_Category.hpp b/unit_test/TestSERIAL_Category.hpp index e96803b0..e9337d84 100644 --- a/unit_test/TestSERIAL_Category.hpp +++ b/unit_test/TestSERIAL_Category.hpp @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2018-2019 by the Cabana authors * + * Copyright (c) 2018-2020 by the Cabana authors * * All rights reserved. * * * * This file is part of the Cabana library. Cabana is distributed under a * diff --git a/unit_test/unit_test_main.cpp b/unit_test/mpi_unit_test_main.cpp similarity index 94% rename from unit_test/unit_test_main.cpp rename to unit_test/mpi_unit_test_main.cpp index fa134dc2..64df86a1 100644 --- a/unit_test/unit_test_main.cpp +++ b/unit_test/mpi_unit_test_main.cpp @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2018-2019 by the Cabana authors * + * Copyright (c) 2018-2020 by the Cabana authors * * All rights reserved. * * * * This file is part of the Cabana library. Cabana is distributed under a * diff --git a/unit_test/tstIntegrator.hpp b/unit_test/tstIntegrator.hpp index e3ce1c00..2616a5fb 100644 --- a/unit_test/tstIntegrator.hpp +++ b/unit_test/tstIntegrator.hpp @@ -1,5 +1,5 @@ /**************************************************************************** - * Copyright (c) 2018-2019 by the Cabana authors * + * Copyright (c) 2018-2020 by the Cabana authors * * All rights reserved. * * * * This file is part of the Cabana library. Cabana is distributed under a * diff --git a/unit_test/tstNeighbor.hpp b/unit_test/tstNeighbor.hpp new file mode 100644 index 00000000..15322ba7 --- /dev/null +++ b/unit_test/tstNeighbor.hpp @@ -0,0 +1,375 @@ +/**************************************************************************** + * Copyright (c) 2018-2020 by the Cabana authors * + * All rights reserved. * + * * + * This file is part of the Cabana library. Cabana is distributed under a * + * BSD 3-clause license. For the licensing terms see the LICENSE file in * + * the top-level directory. * + * * + * SPDX-License-Identifier: BSD-3-Clause * + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#include + +#include +#include + +namespace Test +{ +//---------------------------------------------------------------------------// +// Test list implementation. +template +struct TestNeighborList +{ + Kokkos::View counts; + Kokkos::View neighbors; +}; +template +TestNeighborList +createTestListHostCopy( const TestNeighborList &test_list ) +{ + using data_layout = typename decltype( test_list.counts )::array_layout; + TestNeighborList list_host; + Kokkos::resize( list_host.counts, test_list.counts.extent( 0 ) ); + Kokkos::deep_copy( list_host.counts, test_list.counts ); + Kokkos::resize( list_host.neighbors, test_list.neighbors.extent( 0 ), + test_list.neighbors.extent( 1 ) ); + Kokkos::deep_copy( list_host.neighbors, test_list.neighbors ); + return list_host; +} + +//---------------------------------------------------------------------------// +// Copy into a host test list, extracted with the neighbor list interface. +template +TestNeighborList +copyListToHost( const ListType &list, const int total_atoms, const int max_n ) +{ + TestNeighborList list_host; + list_host.counts = + Kokkos::View( "counts", total_atoms ); + list_host.neighbors = + Kokkos::View( "neighbors", total_atoms, max_n ); + Kokkos::parallel_for( + "copy list", Kokkos::RangePolicy( 0, total_atoms ), + KOKKOS_LAMBDA( const int p ) { + list_host.counts( p ) = + Cabana::NeighborList::numNeighbor( list, p ); + for ( int n = 0; n < list_host.counts( p ); ++n ) + list_host.neighbors( p, n ) = + Cabana::NeighborList::getNeighbor( list, p, n ); + } ); + Kokkos::fence(); + return createTestListHostCopy( list_host ); +} + +//-------------------------------------------------------------------------// +// Build a neighbor list with a brute force n^2 implementation. +template +TestNeighborList +computeFullNeighborList( const PositionSlice &position, const double cutoff ) +{ + // Count first. + TestNeighborList list; + int total_atoms = position.size(); + double rsqr = cutoff * cutoff; + list.counts = Kokkos::View( "test_neighbor_count", + total_atoms ); + Kokkos::deep_copy( list.counts, 0 ); + auto count_op = KOKKOS_LAMBDA( const int i ) + { + for ( int j = 0; j < total_atoms; ++j ) + { + if ( i != j ) + { + double dsqr = 0.0; + for ( int d = 0; d < 3; ++d ) + dsqr += ( position( i, d ) - position( j, d ) ) * + ( position( i, d ) - position( j, d ) ); + if ( dsqr <= rsqr ) + list.counts( i ) += 1; + } + } + }; + Kokkos::RangePolicy exec_policy( 0, total_atoms ); + Kokkos::parallel_for( exec_policy, count_op ); + Kokkos::fence(); + + // Allocate. + auto max_op = KOKKOS_LAMBDA( const int i, int &max_val ) + { + if ( max_val < list.counts( i ) ) + max_val = list.counts( i ); + }; + int max_n; + Kokkos::parallel_reduce( exec_policy, max_op, Kokkos::Max( max_n ) ); + Kokkos::fence(); + list.neighbors = Kokkos::View( "test_neighbors", + total_atoms, max_n ); + + // Fill. + auto fill_op = KOKKOS_LAMBDA( const int i ) + { + int n_count = 0; + for ( int j = 0; j < total_atoms; ++j ) + { + if ( i != j ) + { + double dsqr = 0.0; + for ( int d = 0; d < 3; ++d ) + dsqr += ( position( i, d ) - position( j, d ) ) * + ( position( i, d ) - position( j, d ) ); + if ( dsqr <= rsqr ) + { + list.neighbors( i, n_count ) = j; + ++n_count; + } + } + } + }; + Kokkos::parallel_for( exec_policy, fill_op ); + Kokkos::fence(); + return list; +} + +//---------------------------------------------------------------------------// +template +void checkFullNeighborListPartialRange( const ListType &list, + const PositionSlice &position, + const double cutoff, + const int local_atoms ) +{ + // Build a full list to test with. + auto N2_list = computeFullNeighborList( position, cutoff ); + auto N2_list_host = createTestListHostCopy( N2_list ); + + // Copy to a consistent list format on the host. + auto list_host = copyListToHost( list, N2_list.neighbors.extent( 0 ), + N2_list.neighbors.extent( 1 ) ); + + // Check the results. + int total_atoms = position.size(); + for ( int p = 0; p < total_atoms; ++p ) + { + if ( p < local_atoms ) + { + // First check that the number of neighbors are the same. + EXPECT_EQ( list_host.counts( p ), N2_list_host.counts( p ) ); + + // Now extract the neighbors. + std::vector computed_neighbors( N2_list_host.counts( p ) ); + std::vector actual_neighbors( N2_list_host.counts( p ) ); + for ( int n = 0; n < N2_list_host.counts( p ); ++n ) + { + computed_neighbors[n] = list_host.neighbors( p, n ); + actual_neighbors[n] = N2_list_host.neighbors( p, n ); + } + + // Sort them because we have no guarantee of order. + std::sort( computed_neighbors.begin(), computed_neighbors.end() ); + std::sort( actual_neighbors.begin(), actual_neighbors.end() ); + + // Now compare directly. + for ( int n = 0; n < N2_list_host.counts( p ); ++n ) + EXPECT_EQ( computed_neighbors[n], actual_neighbors[n] ); + } + else + { + // Ghost atoms should have no neighbors + EXPECT_EQ( list_host.counts( p ), 0 ); + } + } +} + +//---------------------------------------------------------------------------// +template +void checkHalfNeighborListPartialRange( const ListType &list, + const PositionSlice &position, + const double cutoff, + const int local_atoms ) +{ + // First build a full list. + auto N2_list = computeFullNeighborList( position, cutoff ); + auto N2_list_host = createTestListHostCopy( N2_list ); + + // Copy to a consistent list format on the host. + auto list_host = copyListToHost( list, N2_list.neighbors.extent( 0 ), + N2_list.neighbors.extent( 1 ) ); + + // Check that the full list is nearly twice the size of the half list. + int total_atoms = position.size(); + int half_size = 0; + int full_size = 0; + for ( int p = 0; p < total_atoms; ++p ) + { + half_size += list_host.counts( p ); + full_size += N2_list_host.counts( p ); + } + // Ghost atoms mean this won't be exactly 2x. + EXPECT_LE( half_size, full_size ); + + for ( int p = 0; p < total_atoms; ++p ) + { + if ( p < local_atoms ) + { + // Atoms should have equal or fewer neighbors in half list. + EXPECT_LE( list_host.counts( p ), N2_list_host.counts( p ) ); + + // Check that there are no self neighbors. + for ( int n = 0; n < list_host.counts( p ); ++n ) + { + auto p_n = list_host.neighbors( p, n ); + for ( int m = 0; m < list_host.counts( p_n ); ++m ) + { + auto n_m = list_host.neighbors( p_n, m ); + EXPECT_NE( n_m, p ); + } + } + } + else + { + // Ghost atoms should have no neighbors + EXPECT_EQ( list_host.counts( p ), 0 ); + } + } +} + +//---------------------------------------------------------------------------// +// Create atoms. +template +t_System createAtoms( const int num_atom, const int num_ghost, + const double box_min, const double box_max ) +{ + t_System system; + system.init(); + + // Manually setup what would be done in input + system.dt = 0.005; + system.mvv2e = 1.0; + system.mass( 0 ) = 1.0; + + system.resize( num_atom ); + system.N_local = num_atom - num_ghost; + system.N_ghost = num_ghost; + + auto box = box_max - box_min; + system.domain_x = system.domain_y = system.domain_z = box; + system.domain_lo_x = system.domain_lo_y = system.domain_lo_z = box_min; + system.domain_hi_x = system.domain_hi_y = system.domain_hi_z = box_max; + system.sub_domain_lo_x = system.sub_domain_lo_y = system.sub_domain_lo_z = + box_min; + system.sub_domain_hi_x = system.sub_domain_hi_y = system.sub_domain_hi_z = + box_max; + + // Create random atom positions within the box. + system.slice_x(); + auto position = system.x; + using PoolType = Kokkos::Random_XorShift64_Pool; + using RandomType = Kokkos::Random_XorShift64; + PoolType pool( 342343901 ); + auto random_coord_op = KOKKOS_LAMBDA( const int p ) + { + auto gen = pool.get_state(); + for ( int d = 0; d < 3; ++d ) + position( p, d ) = + Kokkos::rand::draw( gen, box_min, box_max ); + pool.free_state( gen ); + }; + Kokkos::RangePolicy exec_policy( 0, num_atom ); + Kokkos::parallel_for( exec_policy, random_coord_op ); + Kokkos::fence(); + + return system; +} + +//---------------------------------------------------------------------------// +template +void testNeighborListPartialRange( bool half_neigh ) +{ + // Create the AoSoA and fill with random atom positions. + int num_atom = 1e3; + int num_ghost = 200; + int num_local = num_atom - num_ghost; + double cutoff = 2.32; + double box_min = -5.3 * cutoff; + double box_max = 4.7 * cutoff; + + t_System system = + createAtoms( num_atom, num_ghost, box_min, box_max ); + + // Create the neighbor list. + t_Neighbor neighbor( cutoff, half_neigh ); + neighbor.create( &system ); + + // Check the neighbor list. + system.slice_x(); + auto position = system.x; + auto neigh_list = neighbor.get(); + + if ( half_neigh ) + checkHalfNeighborListPartialRange( neigh_list, position, cutoff, + num_local ); + else + checkFullNeighborListPartialRange( neigh_list, position, cutoff, + num_local ); +} + +//---------------------------------------------------------------------------// +// TESTS +//---------------------------------------------------------------------------// +TEST( TEST_CATEGORY, verlet_full_test ) +{ + using DeviceType = Kokkos::Device; + using t_System = System; + { + using t_Neigh = NeighborVerlet; + testNeighborListPartialRange( false ); + } + { + using t_Neigh = NeighborVerlet; + testNeighborListPartialRange( false ); + } + { +#ifdef CabanaMD_ENABLE_ARBORX + using t_Neigh = NeighborTree; + testNeighborListPartialRange( false ); +#endif + } +} + +//---------------------------------------------------------------------------// +TEST( TEST_CATEGORY, verlet_half_test ) +{ + using DeviceType = Kokkos::Device; + using t_System = System; + { + using t_Neigh = NeighborVerlet; + testNeighborListPartialRange( true ); + } + { + using t_Neigh = NeighborVerlet; + testNeighborListPartialRange( true ); + } + { +#ifdef CabanaMD_ENABLE_ARBORX + using t_Neigh = NeighborTree; + testNeighborListPartialRange( true ); +#endif + } +} + +//---------------------------------------------------------------------------// + +} // end namespace Test