Merge pull request #2420 from rbberger/mpi_tests

Add MPI-enabled unit tests (C/C++ only)
This commit is contained in:
Richard Berger
2020-11-23 10:35:55 -05:00
committed by GitHub
13 changed files with 777 additions and 55 deletions

View File

@ -62,15 +62,18 @@ used.
**-in file**
Specify a file to use as an input script. This is an optional switch
when running LAMMPS in one-partition mode. If it is not specified,
LAMMPS reads its script from standard input, typically from a script
via I/O redirection; e.g. lmp_linux < in.run. I/O redirection should
also work in parallel, but if it does not (in the unlikely case that
an MPI implementation does not support it), then use the -in flag.
Specify a file to use as an input script. This is an optional but
recommended switch when running LAMMPS in one-partition mode. If it
is not specified, LAMMPS reads its script from standard input, typically
from a script via I/O redirection; e.g. lmp_linux < in.run.
With many MPI implementations I/O redirection also works in parallel,
but using the -in flag will always work.
Note that this is a required switch when running LAMMPS in
multi-partition mode, since multiple processors cannot all read from
stdin.
stdin concurrently. The file name may be "none" for starting
multi-partition calculations without reading an initial input file
from the library interface.
----------
@ -296,7 +299,7 @@ command-line option.
**-pscreen file**
Specify the base name for the partition screen file, so partition N
writes screen information to file.N. If file is none, then no
writes screen information to file.N. If file is "none", then no
partition screen files are created. This overrides the filename
specified in the -screen command-line option. This option is useful
when working with large numbers of partitions, allowing the partition

View File

@ -2979,6 +2979,8 @@ Subclassed
subcutoff
subcycle
subcycling
subhi
sublo
Subramaniyan
subscripted
subscripting

View File

@ -189,8 +189,15 @@ void Input::file()
// if line ends in continuation char '&', concatenate next line
if (me == 0) {
m = 0;
while (1) {
if (infile == nullptr) {
n = 0;
break;
}
if (maxline-m < 2) reallocate(line,maxline,0);
// end of file reached, so break

View File

@ -456,6 +456,7 @@ LAMMPS::LAMMPS(int narg, char **arg, MPI_Comm communicator) :
if (universe->me == 0) {
if (inflag == 0) infile = stdin;
else if (strcmp(arg[inflag], "none") == 0) infile = stdin;
else infile = fopen(arg[inflag],"r");
if (infile == nullptr)
error->one(FLERR,fmt::format("Cannot open input script {}: {}",
@ -530,10 +531,12 @@ LAMMPS::LAMMPS(int narg, char **arg, MPI_Comm communicator) :
str, utils::getsyserror()));
}
infile = fopen(arg[inflag],"r");
if (infile == nullptr)
error->one(FLERR,fmt::format("Cannot open input script {}: {}",
arg[inflag], utils::getsyserror()));
if (strcmp(arg[inflag], "none") != 0) {
infile = fopen(arg[inflag],"r");
if (infile == nullptr)
error->one(FLERR,fmt::format("Cannot open input script {}: {}",
arg[inflag], utils::getsyserror()));
}
}
// screen and logfile messages for universe and world
@ -1103,7 +1106,7 @@ void _noopt LAMMPS::help()
"List of command line options supported by this LAMMPS executable:\n\n"
"-echo none/screen/log/both : echoing of input script (-e)\n"
"-help : print this help message (-h)\n"
"-in filename : read input from file, not stdin (-i)\n"
"-in none/filename : read input from file or stdin (default) (-i)\n"
"-kokkos on/off ... : turn KOKKOS mode on or off (-k)\n"
"-log none/filename : where to send log output (-l)\n"
"-mpicolor color : which exe in a multi-exe mpirun cmd (-m)\n"

View File

@ -839,9 +839,19 @@ not recognized, the function returns -1.
* - box_exist
- 1 if the simulation box is defined, 0 if not.
See :doc:`create_box`.
* - nthreads
- Number of requested OpenMP threads for LAMMPS' execution
* - triclinic
- 1 if the the simulation box is triclinic, 0 if orthogonal.
See :doc:`change_box`.
* - universe_rank
- MPI rank on LAMMPS' universe communicator (0 <= universe_rank < universe_size)
* - universe_size
- Number of ranks on LAMMPS' universe communicator (world_size <= universe_size)
* - world_rank
- MPI rank on LAMMPS' world communicator (0 <= world_rank < world_size)
* - world_size
- Number of ranks on LAMMPS' world communicator
.. _extract_system_sizes:
@ -925,6 +935,12 @@ int lammps_extract_setting(void *handle, const char *keyword)
if (strcmp(keyword,"box_exist") == 0) return lmp->domain->box_exist;
if (strcmp(keyword,"triclinic") == 0) return lmp->domain->triclinic;
if (strcmp(keyword,"universe_rank") == 0) return lmp->universe->me;
if (strcmp(keyword,"universe_size") == 0) return lmp->universe->nprocs;
if (strcmp(keyword,"world_rank") == 0) return lmp->comm->me;
if (strcmp(keyword,"world_size") == 0) return lmp->comm->nprocs;
if (strcmp(keyword,"nthreads") == 0) return lmp->comm->nthreads;
if (strcmp(keyword,"nlocal") == 0) return lmp->atom->nlocal;
if (strcmp(keyword,"nghost") == 0) return lmp->atom->nghost;
if (strcmp(keyword,"nall") == 0) return lmp->atom->nlocal+lmp->atom->nghost;
@ -979,6 +995,10 @@ int lammps_extract_global_datatype(void *handle, const char *name)
if (strcmp(name,"boxlo") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"boxhi") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"sublo") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"subhi") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"sublo_lambda") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"subhi_lambda") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"boxxlo") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"boxxhi") == 0) return LAMMPS_DOUBLE;
if (strcmp(name,"boxylo") == 0) return LAMMPS_DOUBLE;
@ -1143,6 +1163,22 @@ report the "native" data type. The following tables are provided:
- double
- 1
- upper box boundary in z-direction. See :doc:`create_box`.
* - sublo
- double
- 3
- subbox lower boundaries
* - subhi
- double
- 3
- subbox upper boundaries
* - sublo_lambda
- double
- 3
- subbox lower boundaries in fractional coordinates (for triclinic cells)
* - subhi_lambda
- double
- 3
- subbox upper boundaries in fractional coordinates (for triclinic cells)
* - periodicity
- int
- 3
@ -1334,6 +1370,10 @@ void *lammps_extract_global(void *handle, const char *name)
if (strcmp(name,"boxlo") == 0) return (void *) lmp->domain->boxlo;
if (strcmp(name,"boxhi") == 0) return (void *) lmp->domain->boxhi;
if (strcmp(name,"sublo") == 0) return (void *) lmp->domain->sublo;
if (strcmp(name,"subhi") == 0) return (void *) lmp->domain->subhi;
if (strcmp(name,"sublo_lambda") == 0) return (void *) lmp->domain->sublo_lamda;
if (strcmp(name,"subhi_lambda") == 0) return (void *) lmp->domain->subhi_lamda;
if (strcmp(name,"boxxlo") == 0) return (void *) &lmp->domain->boxlo[0];
if (strcmp(name,"boxxhi") == 0) return (void *) &lmp->domain->boxhi[0];
if (strcmp(name,"boxylo") == 0) return (void *) &lmp->domain->boxlo[1];
@ -1809,36 +1849,44 @@ void *lammps_extract_fix(void *handle, char *id, int style, int type,
\verbatim embed:rst
This function returns a pointer to data from a LAMMPS :doc:`variable`
identified by its name. The variable must be either an *equal*\ -style
compatible or an *atom*\ -style variable. Variables of style *internal*
identified by its name. When the variable is either an *equal*\ -style
compatible or an *atom*\ -style variable the variable is evaluated and
the corresponding value(s) returned. Variables of style *internal*
are compatible with *equal*\ -style variables and so are *python*\
-style variables, if they return a numeric value. The function returns
-style variables, if they return a numeric value. For other
variable styles their string value is returned. The function returns
``NULL`` when a variable of the provided *name* is not found or of an
incompatible style. The *group* argument is only used for *atom*\
-style variables and ignored otherwise. If set to ``NULL`` when
extracting data from and *atom*\ -style variable, the group is assumed
to be "all".
.. note::
When requesting data from an *equal*\ -style or compatible variable
this function allocates storage for a single double value, copies the
returned value to it, and returns a pointer to the location of the
copy. Therefore the allocated storage needs to be freed after its
use to avoid a memory leak. Example:
When requesting data from an *equal*\ -style or compatible variable
this function allocates storage for a single double value, copies the
returned value to it, and returns a pointer to the location of the
copy. Therefore the allocated storage needs to be freed after its
use to avoid a memory leak. Example:
.. code-block:: c
.. code-block:: c
double *dptr = (double *) lammps_extract_variable(handle,name,NULL);
double value = *dptr;
lammps_free((void *)dptr);
double *dptr = (double *) lammps_extract_variable(handle,name,NULL);
double value = *dptr;
lammps_free((void *)dptr);
For *atom*\ -style variables the data returned is a pointer to an
allocated block of storage of double of the length ``atom->nlocal``.
Since the data is returned a copy, the location will persist, but its
content will not be updated, in case the variable is re-evaluated.
To avoid a memory leak this pointer needs to be freed after use in
the calling program.
For *atom*\ -style variables the data returned is a pointer to an
allocated block of storage of double of the length ``atom->nlocal``.
To avoid a memory leak, also this pointer needs to be freed after use.
For other variable styles the returned pointer needs to be cast to
a char pointer.
Since the data is returned as copies, the location will persist, but its
values will not be updated, in case the variable is re-evaluated.
.. code-block:: c
const char *cptr = (const char *) lammps_extract_variable(handle,name,NULL);
printf("The value of variable %s is %s\n", name, cptr);
.. note::
@ -1856,7 +1904,7 @@ values will not be updated, in case the variable is re-evaluated.
* \return pointer (cast to ``void *``) to the location of the
* requested data or ``NULL`` if not found. */
void *lammps_extract_variable(void *handle, char *name, char *group)
void *lammps_extract_variable(void *handle, const char *name, const char *group)
{
LAMMPS *lmp = (LAMMPS *) handle;
@ -1869,9 +1917,7 @@ void *lammps_extract_variable(void *handle, char *name, char *group)
double *dptr = (double *) malloc(sizeof(double));
*dptr = lmp->input->variable->compute_equal(ivar);
return (void *) dptr;
}
if (lmp->input->variable->atomstyle(ivar)) {
} else if (lmp->input->variable->atomstyle(ivar)) {
if (group == nullptr) group = (char *)"all";
int igroup = lmp->group->find(group);
if (igroup < 0) return nullptr;
@ -1879,6 +1925,8 @@ void *lammps_extract_variable(void *handle, char *name, char *group)
double *vector = (double *) malloc(nlocal*sizeof(double));
lmp->input->variable->compute_atom(ivar,igroup,vector,1,0);
return (void *) vector;
} else {
return lmp->input->variable->retrieve(name);
}
}
END_CAPTURE

View File

@ -140,7 +140,7 @@ void *lammps_extract_atom(void *handle, const char *name);
void *lammps_extract_compute(void *handle, char *id, int, int);
void *lammps_extract_fix(void *handle, char *, int, int, int, int);
void *lammps_extract_variable(void *handle, char *, char *);
void *lammps_extract_variable(void *handle, const char *, const char *);
int lammps_set_variable(void *, char *, char *);
/* ----------------------------------------------------------------------

View File

@ -158,7 +158,6 @@ void Universe::reorder(char *style, char *arg)
void Universe::add_world(char *str)
{
int n,nper;
char *ptr;
n = 1;
nper = 0;
@ -171,28 +170,23 @@ void Universe::add_world(char *str)
// str may not be empty and may only consist of digits or 'x'
size_t len = strlen(str);
if (len < 1) valid = false;
for (size_t i=0; i < len; ++i)
if (isdigit(str[i]) || str[i] == 'x') continue;
else valid = false;
std::string part(str);
if (part.size() == 0) valid = false;
if (part.find_first_not_of("0123456789x") != std::string::npos) valid = false;
if (valid) {
if ((ptr = strchr(str,'x')) != nullptr) {
std::size_t found = part.find_first_of("x");
// 'x' may not be the first or last character
// 'x' may not be the first or last character
if (ptr == str) {
valid = false;
} else if (strlen(str) == len-1) {
valid = false;
} else {
*ptr = '\0';
n = atoi(str);
nper = atoi(ptr+1);
*ptr = 'x';
}
} else nper = atoi(str);
if ((found == 0) || (found == (part.size() - 1))) {
valid = false;
} else if (found == std::string::npos) {
nper = atoi(part.c_str());
} else {
n = atoi(part.substr(0,found).c_str());
nper = atoi(part.substr(found-1).c_str());\
}
}
// require minimum of 1 partition with 1 processor

View File

@ -1,5 +1,21 @@
include(GTest)
if(BUILD_MPI)
function(add_mpi_test)
set(MPI_TEST_NUM_PROCS 1)
set(MPI_TEST_WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
cmake_parse_arguments(MPI_TEST "" "NAME;NUM_PROCS;WORKING_DIRECTORY" "COMMAND" ${ARGN})
list(GET MPI_TEST_COMMAND 0 EXECUTABLE)
list(REMOVE_AT MPI_TEST_COMMAND 0)
set(ARGS ${MPI_TEST_COMMAND})
add_test(NAME ${MPI_TEST_NAME}
WORKING_DIRECTORY ${MPI_TEST_WORKING_DIRECTORY}
COMMAND ${MPIEXEC_EXECUTABLE} ${MPIEXEC_NUMPROC_FLAG} ${MPI_TEST_NUM_PROCS} ${MPIEXEC_PREFLAGS}
${EXECUTABLE} ${MPIEXEC_POSTFLAGS} ${ARGS}
)
endfunction()
endif()
add_subdirectory(utils)
add_subdirectory(formats)
add_subdirectory(commands)

View File

@ -55,3 +55,10 @@ add_executable(test_library_config test_library_config.cpp test_main.cpp)
target_link_libraries(test_library_config PRIVATE lammps GTest::GTest GTest::GMock)
target_compile_definitions(test_library_config PRIVATE ${TEST_CONFIG_DEFS})
add_test(LibraryConfig test_library_config)
if (BUILD_MPI)
add_executable(test_library_mpi test_library_mpi.cpp)
target_link_libraries(test_library_mpi PRIVATE lammps GTest::GTest GTest::GMock)
target_compile_definitions(test_library_mpi PRIVATE ${TEST_CONFIG_DEFS})
add_mpi_test(NAME LibraryMPI NUM_PROCS 4 COMMAND $<TARGET_FILE:test_library_mpi>)
endif()

View File

@ -0,0 +1,341 @@
// unit tests for checking LAMMPS configuration settings through the library interface
#define LAMMPS_LIB_MPI 1
#include "lammps.h"
#include "library.h"
#include "timer.h"
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "../testing/test_mpi_main.h"
using ::testing::ExitedWithCode;
using ::testing::HasSubstr;
using ::testing::StartsWith;
using ::testing::StrEq;
TEST(MPI, global_box)
{
int nprocs, me;
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &me);
EXPECT_EQ(nprocs, 4);
EXPECT_GT(me, -1);
EXPECT_LT(me, 5);
double boxlo[3];
double boxhi[3];
double xy = 0.0;
double yz = 0.0;
double xz = 0.0;
int pflags[3];
int boxflag;
::testing::internal::CaptureStdout();
const char *args[] = {"LAMMPS_test", "-log", "none", "-echo", "screen", "-nocite"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
void *lmp = lammps_open(argc, argv, MPI_COMM_WORLD, nullptr);
lammps_command(lmp, "units lj");
lammps_command(lmp, "atom_style atomic");
lammps_command(lmp, "region box block 0 2 0 2 0 2");
lammps_command(lmp, "create_box 1 box");
lammps_extract_box(lmp, boxlo, boxhi, &xy, &yz, &xz, pflags, &boxflag);
::testing::internal::GetCapturedStdout();
EXPECT_EQ(boxlo[0], 0.0);
EXPECT_EQ(boxlo[1], 0.0);
EXPECT_EQ(boxlo[2], 0.0);
EXPECT_EQ(boxhi[0], 2.0);
EXPECT_EQ(boxhi[1], 2.0);
EXPECT_EQ(boxhi[2], 2.0);
::testing::internal::CaptureStdout();
lammps_close(lmp);
::testing::internal::GetCapturedStdout();
};
TEST(MPI, sub_box)
{
int nprocs, me;
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &me);
EXPECT_EQ(nprocs, 4);
EXPECT_GT(me, -1);
EXPECT_LT(me, 5);
double boxlo[3];
double boxhi[3];
double xy = 0.0;
double yz = 0.0;
double xz = 0.0;
int pflags[3];
int boxflag;
::testing::internal::CaptureStdout();
const char *args[] = {"LAMMPS_test", "-log", "none", "-echo", "screen", "-nocite"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
void *lmp = lammps_open(argc, argv, MPI_COMM_WORLD, nullptr);
lammps_command(lmp, "units lj");
lammps_command(lmp, "atom_style atomic");
lammps_command(lmp, "region box block 0 2 0 2 0 2");
lammps_command(lmp, "create_box 1 box");
lammps_extract_box(lmp, boxlo, boxhi, &xy, &yz, &xz, pflags, &boxflag);
::testing::internal::GetCapturedStdout();
EXPECT_EQ(boxlo[0], 0.0);
EXPECT_EQ(boxlo[1], 0.0);
EXPECT_EQ(boxlo[2], 0.0);
EXPECT_EQ(boxhi[0], 2.0);
EXPECT_EQ(boxhi[1], 2.0);
EXPECT_EQ(boxhi[2], 2.0);
double *sublo = (double *)lammps_extract_global(lmp, "sublo");
double *subhi = (double *)lammps_extract_global(lmp, "subhi");
ASSERT_NE(sublo, nullptr);
ASSERT_NE(subhi, nullptr);
EXPECT_GE(sublo[0], boxlo[0]);
EXPECT_GE(sublo[1], boxlo[1]);
EXPECT_GE(sublo[2], boxlo[2]);
EXPECT_LE(subhi[0], boxhi[0]);
EXPECT_LE(subhi[1], boxhi[1]);
EXPECT_LE(subhi[2], boxhi[2]);
::testing::internal::CaptureStdout();
lammps_command(lmp, "change_box all triclinic");
::testing::internal::GetCapturedStdout();
sublo = (double *)lammps_extract_global(lmp, "sublo_lambda");
subhi = (double *)lammps_extract_global(lmp, "subhi_lambda");
ASSERT_NE(sublo, nullptr);
ASSERT_NE(subhi, nullptr);
EXPECT_GE(sublo[0], 0.0);
EXPECT_GE(sublo[1], 0.0);
EXPECT_GE(sublo[2], 0.0);
EXPECT_LE(subhi[0], 1.0);
EXPECT_LE(subhi[1], 1.0);
EXPECT_LE(subhi[2], 1.0);
::testing::internal::CaptureStdout();
lammps_close(lmp);
::testing::internal::GetCapturedStdout();
};
TEST(MPI, split_comm)
{
int nprocs, me, color, key;
MPI_Comm newcomm;
lammps_mpi_init();
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &me);
color = me % 2;
key = me;
MPI_Comm_split(MPI_COMM_WORLD, color, key, &newcomm);
const char *args[] = {"LAMMPS_test", "-log", "none", "-echo", "screen", "-nocite"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
void *lmp = lammps_open(argc, argv, newcomm, nullptr);
lammps_command(lmp, "units lj");
lammps_command(lmp, "atom_style atomic");
lammps_command(lmp, "region box block 0 2 0 2 0 2");
lammps_command(lmp, "create_box 1 box");
MPI_Comm_size(newcomm, &nprocs);
MPI_Comm_rank(newcomm, &me);
EXPECT_EQ(nprocs, 2);
EXPECT_GT(me, -1);
EXPECT_LT(me, 2);
EXPECT_EQ(lammps_extract_setting(lmp, "universe_size"), nprocs);
EXPECT_EQ(lammps_extract_setting(lmp, "universe_rank"), me);
EXPECT_EQ(lammps_extract_setting(lmp, "world_size"), nprocs);
EXPECT_EQ(lammps_extract_setting(lmp, "world_rank"), me);
lammps_close(lmp);
};
TEST(MPI, multi_partition)
{
FILE *fp;
int nprocs, me;
lammps_mpi_init();
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &me);
const char *args[] = {"LAMMPS_test", "-log", "none", "-partition", "2x2",
"-echo", "screen", "-nocite", "-in", "none"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
void *lmp = lammps_open(argc, argv, MPI_COMM_WORLD, nullptr);
lammps_command(lmp, "units lj");
lammps_command(lmp, "atom_style atomic");
lammps_command(lmp, "region box block 0 2 0 2 0 2");
lammps_command(lmp, "create_box 1 box");
lammps_command(lmp, "variable partition universe 1 2");
EXPECT_EQ(lammps_extract_setting(lmp, "universe_size"), nprocs);
EXPECT_EQ(lammps_extract_setting(lmp, "universe_rank"), me);
EXPECT_EQ(lammps_extract_setting(lmp, "world_size"), nprocs / 2);
EXPECT_EQ(lammps_extract_setting(lmp, "world_rank"), me % 2);
char *part_id = (char *)lammps_extract_variable(lmp, "partition", nullptr);
if (me < 2) {
ASSERT_THAT(part_id, StrEq("1"));
} else {
ASSERT_THAT(part_id, StrEq("2"));
}
lammps_close(lmp);
};
class MPITest : public ::testing::Test {
public:
void command(const std::string &line) { lammps_command(lmp, line.c_str()); }
protected:
const char *testbinary = "LAMMPSTest";
void *lmp;
void SetUp() override
{
const char *args[] = {testbinary, "-log", "none", "-echo", "screen", "-nocite"};
char **argv = (char **)args;
int argc = sizeof(args) / sizeof(char *);
if (!verbose) ::testing::internal::CaptureStdout();
lmp = lammps_open(argc, argv, MPI_COMM_WORLD, nullptr);
InitSystem();
if (!verbose) ::testing::internal::GetCapturedStdout();
}
virtual void InitSystem()
{
command("units lj");
command("atom_style atomic");
command("atom_modify map yes");
command("lattice fcc 0.8442");
command("region box block 0 2 0 2 0 2");
command("create_box 1 box");
command("create_atoms 1 box");
command("mass 1 1.0");
command("velocity all create 3.0 87287");
command("pair_style lj/cut 2.5");
command("pair_coeff 1 1 1.0 1.0 2.5");
command("neighbor 0.3 bin");
command("neigh_modify every 20 delay 0 check no");
}
void TearDown() override
{
if (!verbose) ::testing::internal::CaptureStdout();
lammps_close(lmp);
lmp = nullptr;
if (!verbose) ::testing::internal::GetCapturedStdout();
}
};
TEST_F(MPITest, size_rank)
{
int nprocs, me;
MPI_Comm_size(MPI_COMM_WORLD, &nprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &me);
EXPECT_EQ(nprocs, lammps_extract_setting(lmp, "world_size"));
EXPECT_EQ(me, lammps_extract_setting(lmp, "world_rank"));
}
#if !defined(LAMMPS_BIGBIG)
TEST_F(MPITest, gather)
{
int64_t natoms = (int64_t)lammps_get_natoms(lmp);
ASSERT_EQ(natoms, 32);
int *p_nlocal = (int *)lammps_extract_global(lmp, "nlocal");
int nlocal = *p_nlocal;
EXPECT_LT(nlocal, 32);
EXPECT_EQ(nlocal, 8);
// get the entire x on all procs
double *x = new double[natoms * 3];
lammps_gather(lmp, (char *)"x", 1, 3, x);
int *tag = (int *)lammps_extract_atom(lmp, "id");
double **x_local = (double **)lammps_extract_atom(lmp, "x");
// each proc checks its local atoms
for (int i = 0; i < nlocal; i++) {
int64_t j = tag[i] - 1;
double *x_i = x_local[i];
double *x_g = &x[j * 3];
EXPECT_DOUBLE_EQ(x_g[0], x_i[0]);
EXPECT_DOUBLE_EQ(x_g[1], x_i[1]);
EXPECT_DOUBLE_EQ(x_g[2], x_i[2]);
}
delete[] x;
}
TEST_F(MPITest, scatter)
{
int *p_nlocal = (int *)lammps_extract_global(lmp, "nlocal");
int nlocal = *p_nlocal;
double *x_orig = new double[3 * nlocal];
double **x_local = (double **)lammps_extract_atom(lmp, "x");
// make copy of original local x vector
for (int i = 0; i < nlocal; i++) {
int j = 3 * i;
x_orig[j] = x_local[i][0];
x_orig[j + 1] = x_local[i][1];
x_orig[j + 2] = x_local[i][2];
}
// get the entire x on all procs
int64_t natoms = (int64_t)lammps_get_natoms(lmp);
double *x = new double[natoms * 3];
lammps_gather(lmp, (char *)"x", 1, 3, x);
// shift all coordinates by 0.001
const double delta = 0.001;
for (int64_t i = 0; i < 3 * natoms; i++)
x[i] += delta;
// update positions of all atoms
lammps_scatter(lmp, (char *)"x", 1, 3, x);
delete[] x;
x = nullptr;
// get new nlocal and x_local
p_nlocal = (int *)lammps_extract_global(lmp, "nlocal");
nlocal = *p_nlocal;
x_local = (double **)lammps_extract_atom(lmp, "x");
ASSERT_EQ(nlocal, 8);
// each proc checks its local atoms for shift
for (int i = 0; i < nlocal; i++) {
double *x_a = x_local[i];
double *x_b = &x_orig[i * 3];
EXPECT_DOUBLE_EQ(x_a[0], x_b[0] + delta);
EXPECT_DOUBLE_EQ(x_a[1], x_b[1] + delta);
EXPECT_DOUBLE_EQ(x_a[2], x_b[2] + delta);
}
delete[] x_orig;
}
#endif

View File

@ -199,6 +199,12 @@ TEST_F(LibraryProperties, setting)
lammps_command(lmp, "dimension 3");
if (!verbose) ::testing::internal::GetCapturedStdout();
EXPECT_EQ(lammps_extract_setting(lmp, "world_size"), 1);
EXPECT_EQ(lammps_extract_setting(lmp, "world_rank"), 0);
EXPECT_EQ(lammps_extract_setting(lmp, "universe_size"), 1);
EXPECT_EQ(lammps_extract_setting(lmp, "universe_rank"), 0);
EXPECT_GT(lammps_extract_setting(lmp, "nthreads"), 0);
EXPECT_EQ(lammps_extract_setting(lmp, "ntypes"), 0);
EXPECT_EQ(lammps_extract_setting(lmp, "nbondtypes"), 0);
EXPECT_EQ(lammps_extract_setting(lmp, "nangletypes"), 0);

View File

@ -0,0 +1,228 @@
/* ----------------------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
http://lammps.sandia.gov, Sandia National Laboratories
Steve Plimpton, sjplimp@sandia.gov
Copyright (2003) Sandia Corporation. Under the terms of Contract
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
certain rights in this software. This software is distributed under
the GNU General Public License.
See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <deque>
#include <mpi.h>
using ::testing::TestEventListener;
using ::testing::TestCase;
using ::testing::TestSuite;
using ::testing::UnitTest;
using ::testing::TestPartResult;
using ::testing::TestInfo;
class MPIPrinter : public TestEventListener {
MPI_Comm comm;
TestEventListener * default_listener;
int me;
int nprocs;
char * buffer;
size_t buffer_size;
std::deque<TestPartResult> results;
bool finalize_test;
public:
MPIPrinter(TestEventListener * default_listener) : default_listener(default_listener) {
comm = MPI_COMM_WORLD;
MPI_Comm_rank(comm, &me);
MPI_Comm_size(comm, &nprocs);
buffer_size = 1024;
buffer = new char[buffer_size];
finalize_test = false;
}
~MPIPrinter() override {
delete default_listener;
default_listener = nullptr;
delete [] buffer;
buffer = nullptr;
buffer_size = 0;
}
virtual void OnTestProgramStart(const UnitTest& unit_test) override {
if(me == 0) default_listener->OnTestProgramStart(unit_test);
}
virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration) override {
if(me == 0) default_listener->OnTestIterationStart(unit_test, iteration);
}
virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) override {
if(me == 0) default_listener->OnEnvironmentsSetUpStart(unit_test);
}
virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) override {
if(me == 0) default_listener->OnEnvironmentsSetUpEnd(unit_test);
}
virtual void OnTestSuiteStart(const TestSuite& test_suite) override {
if(me == 0) default_listener->OnTestSuiteStart(test_suite);
}
// Legacy API is deprecated but still available
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
virtual void OnTestCaseStart(const TestCase& test_case) override {
if(me == 0) default_listener->OnTestSuiteStart(test_case);
}
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
virtual void OnTestStart(const TestInfo& test_info) override {
// Called before a test starts.
if(me == 0) default_listener->OnTestStart(test_info);
results.clear();
finalize_test = false;
}
virtual void OnTestPartResult(const TestPartResult& test_part_result) override {
// Called after a failed assertion or a SUCCESS().
// test_part_result()
if (me == 0 && finalize_test) {
default_listener->OnTestPartResult(test_part_result);
} else {
std::stringstream proc_message;
std::istringstream msg(test_part_result.message());
std::string line;
while(std::getline(msg, line)) {
proc_message << "[Rank " << me << "] " << line << std::endl;
}
results.push_back(TestPartResult(test_part_result.type(), test_part_result.file_name(), test_part_result.line_number(), proc_message.str().c_str()));
}
}
virtual void OnTestEnd(const TestInfo& test_info) override {
// Called after a test ends.
MPI_Barrier(comm);
// other procs send their test part results
if(me != 0) {
int nresults = results.size();
MPI_Send(&nresults, 1, MPI_INT, 0, 0, comm);
for(auto& test_part_result : results) {
int type = test_part_result.type();
MPI_Send(&type, 1, MPI_INT, 0, 0, comm);
const char * str = test_part_result.file_name();
int length = 0;
if(str) length = strlen(str)+1;
MPI_Send(&length, 1, MPI_INT, 0, 0, comm);
if(str) MPI_Send(str, length, MPI_CHAR, 0, 0, comm);
int lineno = test_part_result.line_number();
MPI_Send(&lineno, 1, MPI_INT, 0, 0, comm);
str = test_part_result.message();
length = 0;
if(str) length = strlen(str)+1;
MPI_Send(&length, 1, MPI_INT, 0, 0, comm);
if(str) MPI_Send(str, length, MPI_CHAR, 0, 0, comm);
}
}
if(me == 0) {
// collect results from other procs
for(int p = 1; p < nprocs; p++) {
int nresults = 0;
MPI_Recv(&nresults, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE);
for(int r = 0; r < nresults; r++) {
int type;
MPI_Recv(&type, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE);
int length = 0;
MPI_Recv(&length, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE);
std::string file_name;
if (length > 0) {
if (length > buffer_size) {
delete [] buffer;
buffer = new char[length];
buffer_size = length;
}
MPI_Recv(buffer, length, MPI_CHAR, p, 0, comm, MPI_STATUS_IGNORE);
file_name = buffer;
}
int lineno;
MPI_Recv(&lineno, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE);
MPI_Recv(&length, 1, MPI_INT, p, 0, comm, MPI_STATUS_IGNORE);
std::string message;
if (length > 0) {
if (length > buffer_size) {
delete [] buffer;
buffer = new char[length];
buffer_size = length;
}
MPI_Recv(buffer, length, MPI_CHAR, p, 0, comm, MPI_STATUS_IGNORE);
message = std::string(buffer);
}
results.push_back(TestPartResult((TestPartResult::Type)type, file_name.c_str(), lineno, message.c_str()));
}
}
// ensure failures are reported
finalize_test = true;
// add all failures
while(!results.empty()) {
auto result = results.front();
if(result.failed()) {
ADD_FAILURE_AT(result.file_name(), result.line_number()) << result.message();
} else {
default_listener->OnTestPartResult(result);
}
results.pop_front();
}
default_listener->OnTestEnd(test_info);
}
}
virtual void OnTestSuiteEnd(const TestSuite& test_suite) override {
if(me == 0) default_listener->OnTestSuiteEnd(test_suite);
}
#ifndef GTEST_REMOVE_LEGACY_TEST_CASEAPI_
virtual void OnTestCaseEnd(const TestCase& test_case) override {
if(me == 0) default_listener->OnTestCaseEnd(test_case);
}
#endif // GTEST_REMOVE_LEGACY_TEST_CASEAPI_
virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) override {
if(me == 0) default_listener->OnEnvironmentsTearDownStart(unit_test);
}
virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) override {
if(me == 0) default_listener->OnEnvironmentsTearDownEnd(unit_test);
}
virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration) override {
if(me == 0) default_listener->OnTestIterationEnd(unit_test, iteration);
}
virtual void OnTestProgramEnd(const UnitTest& unit_test) override {
if(me == 0) default_listener->OnTestProgramEnd(unit_test);
}
};

View File

@ -0,0 +1,67 @@
/* -*- c++ -*- ----------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
http://lammps.sandia.gov, Sandia National Laboratories
Steve Plimpton, sjplimp@sandia.gov
Copyright (2003) Sandia Corporation. Under the terms of Contract
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
certain rights in this software. This software is distributed under
the GNU General Public License.
See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */
#include "utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "mpitesting.h"
#include <iostream>
#include <mpi.h>
// whether to print verbose output (i.e. not capturing LAMMPS screen output).
bool verbose = false;
int main(int argc, char **argv)
{
MPI_Init(&argc, &argv);
::testing::InitGoogleMock(&argc, argv);
if (argc < 1) {
return 1;
}
// handle arguments passed via environment variable
if (const char *var = getenv("TEST_ARGS")) {
std::vector<std::string> env = LAMMPS_NS::utils::split_words(var);
for (auto arg : env) {
if (arg == "-v") {
verbose = true;
}
}
}
int iarg = 1;
while (iarg < argc) {
if (strcmp(argv[iarg], "-v") == 0) {
verbose = true;
++iarg;
} else {
std::cerr << "unknown option: " << argv[iarg] << "\n\n";
MPI_Finalize();
return 1;
}
}
auto & listeners = UnitTest::GetInstance()->listeners();
// Remove default listener
auto default_listener = listeners.Release(listeners.default_result_printer());
// Adds a listener to the end. googletest takes the ownership.
listeners.Append(new MPIPrinter(default_listener));
int rv = RUN_ALL_TESTS();
MPI_Finalize();
return rv;
}