diff --git a/testing/adios2/engine/bp/TestBPWriteReadMultiblock.cpp b/testing/adios2/engine/bp/TestBPWriteReadMultiblock.cpp index 479f4ed594..a43c0e20df 100644 --- a/testing/adios2/engine/bp/TestBPWriteReadMultiblock.cpp +++ b/testing/adios2/engine/bp/TestBPWriteReadMultiblock.cpp @@ -2093,6 +2093,139 @@ TEST_F(BPWriteReadMultiblockTest, MultiblockPerformDataWrite) } } +//****************************************************************************** +// Test reading data where some processes do not contribute to the data +// and some blocks are null +//****************************************************************************** + +TEST_F(BPWriteReadMultiblockTest, MultiblockNullBlocks) +{ + // Each process would write a 2x8 array and all processes would + // form a mpiSize * Nx 1D array + const std::string fname("MultiblockNullBlocks.bp"); + + int mpiRank = 0, mpiSize = 1; + // Number of elements per blocks (blocksize) + const size_t Nx = 8; + // Number of blocks per process (= number of flushes) + const size_t Nblocks = 3; + // Number of steps + const size_t NSteps = 3; + +#if ADIOS2_USE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank); + MPI_Comm_size(MPI_COMM_WORLD, &mpiSize); +#endif + +#if ADIOS2_USE_MPI + adios2::ADIOS adios(MPI_COMM_WORLD); +#else + adios2::ADIOS adios; +#endif + /* Write */ + { + adios2::IO io = adios.DeclareIO("TestIO"); + adios2::Dims shape{static_cast(mpiSize), static_cast(Nx * (Nblocks - 1))}; + adios2::Dims start{static_cast(mpiRank), 0}; + adios2::Dims count{1, Nx}; + + auto var_i32 = io.DefineVariable("i32", shape, start, count); + + if (!engineName.empty()) + { + io.SetEngine(engineName); + } + + adios2::Engine bpWriter = io.Open(fname, adios2::Mode::Write); + + for (size_t step = 0; step < NSteps; ++step) + { + bpWriter.BeginStep(); + + size_t nb = 0; + for (size_t b = 0; b < Nblocks; ++b) + { + // Generate test data for each process / block uniquely + int t = static_cast(step * Nblocks + b); + SmallTestData currentTestData = + generateNewSmallTestData(m_TestData, t, mpiRank, mpiSize); + + // the first block does not contribute to the variable's data + if (b == 0) + { + std::array I32_empty; + var_i32.SetSelection( + adios2::Box({(size_t)mpiRank, b * Nx}, {0, 0})); + bpWriter.Put(var_i32, I32_empty.data()); + } + else + { + ++nb; + start = {static_cast(mpiRank), static_cast(Nx * (nb - 1))}; + count = {1, Nx}; + var_i32.SetSelection({start, count}); + bpWriter.Put(var_i32, currentTestData.I32.data(), adios2::Mode::Sync); + } + + bpWriter.PerformDataWrite(); + } + bpWriter.EndStep(); + } + bpWriter.Close(); + } + // Read and check correctness + { + adios2::IO io = adios.DeclareIO("ReadIO"); + + if (!engineName.empty()) + { + io.SetEngine(engineName); + } + + adios2::Engine bpReader = io.Open(fname, adios2::Mode::ReadRandomAccess); + + auto var_i32 = io.InquireVariable("i32"); + EXPECT_TRUE(var_i32); + EXPECT_EQ(var_i32.ShapeID(), adios2::ShapeID::GlobalArray); + EXPECT_EQ(var_i32.Steps(), NSteps); + EXPECT_EQ(var_i32.Shape()[0], mpiSize); + EXPECT_EQ(var_i32.Shape()[1], Nx * (Nblocks - 1)); + + SmallTestData testData; + std::array I32; + + const auto i32AllInfo = bpReader.AllStepsBlocksInfo(var_i32); + EXPECT_EQ(i32AllInfo.size(), NSteps); + + for (size_t step = 0; step < NSteps; step++) + { + var_i32.SetStepSelection({step, 1}); + for (size_t b = 1; b < Nblocks; ++b) + { + std::cout << "Read step " << step << " block=" << b << std::endl; + // Generate test data for each process / block uniquely + int t = static_cast(step * Nblocks + b); + SmallTestData currentTestData = + generateNewSmallTestData(m_TestData, t, mpiRank, mpiSize); + + // Block 0 was not written so all blocks are shifted back + const adios2::Box sel({(size_t)mpiRank, (b - 1) * Nx}, {1, Nx}); + var_i32.SetSelection(sel); + bpReader.Get(var_i32, I32.data(), adios2::Mode::Sync); + + for (size_t i = 0; i < Nx; ++i) + { + std::stringstream ss; + ss << "step=" << step << " block=" << b << " i=" << i << " rank=" << mpiRank; + std::string msg = ss.str(); + EXPECT_EQ(I32[i], currentTestData.I32[i]) << msg; + } + } + } + bpReader.Close(); + } +} + //****************************************************************************** // main //****************************************************************************** diff --git a/testing/adios2/engine/bp/operations/TestBPWriteReadBlosc2.cpp b/testing/adios2/engine/bp/operations/TestBPWriteReadBlosc2.cpp index 7295eff60f..727c467d13 100644 --- a/testing/adios2/engine/bp/operations/TestBPWriteReadBlosc2.cpp +++ b/testing/adios2/engine/bp/operations/TestBPWriteReadBlosc2.cpp @@ -858,6 +858,118 @@ void Blosc2Accuracy3DSel(const std::string accuracy, const std::string threshold } } +void Blosc2NullBlocks(const std::string accuracy, const std::string threshold, + const std::string doshuffle) +{ + // Null blocks only work for BP4 and BP5 + if (engineName == "BP3") return; + + // Each process would write a 1x8 array and all processes would + // form a mpiSize * Nx 1D array + const std::string fname("BPWRBlosc2NullBlock_" + accuracy + "_" + threshold + threshold + "_" + + doshuffle + ".bp"); + + int mpiRank = 0, mpiSize = 1; + // Number of rows + const size_t Nx = 1000; + // Number of steps + const size_t NSteps = 1; + + std::vector r32s(Nx); + // range 0 to 999 + std::iota(r32s.begin(), r32s.end(), 0.f); + +#if ADIOS2_USE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank); + MPI_Comm_size(MPI_COMM_WORLD, &mpiSize); +#endif + +#if ADIOS2_USE_MPI + adios2::ADIOS adios(MPI_COMM_WORLD); +#else + adios2::ADIOS adios; +#endif + { + adios2::IO io = adios.DeclareIO("TestIO"); + + if (!engineName.empty()) + { + io.SetEngine(engineName); + } + + const adios2::Dims shape{static_cast(Nx * mpiSize)}; + const adios2::Dims start{static_cast(Nx * mpiRank)}; + const adios2::Dims count{Nx}; + + auto var_r32 = io.DefineVariable("r32", shape, start, count); + + // add operations + adios2::Operator Blosc2Op = + adios.DefineOperator("Blosc2Compressor", adios2::ops::LosslessBlosc); + + var_r32.AddOperation(Blosc2Op, {{adios2::ops::blosc::key::clevel, accuracy}, + {adios2::ops::blosc::key::threshold, threshold}, + {adios2::ops::blosc::key::doshuffle, doshuffle}}); + adios2::Engine bpWriter = io.Open(fname, adios2::Mode::Write); + + for (size_t step = 0; step < NSteps; ++step) + { + bpWriter.BeginStep(); + var_r32.SetSelection(adios2::Box({mpiRank * Nx}, {Nx})); + bpWriter.Put("r32", r32s.data()); + var_r32.SetSelection(adios2::Box({mpiRank * Nx}, {0})); + std::vector r32_empty; + bpWriter.Put("r32", r32_empty.data()); + bpWriter.EndStep(); + } + + bpWriter.Close(); + } + + { + adios2::IO io = adios.DeclareIO("ReadIO"); + + if (!engineName.empty()) + { + io.SetEngine(engineName); + } + + adios2::Engine bpReader = io.Open(fname, adios2::Mode::Read); + + unsigned int t = 0; + std::vector decompressedR32s; + + while (bpReader.BeginStep() == adios2::StepStatus::OK) + { + auto var_r32 = io.InquireVariable("r32"); + EXPECT_TRUE(var_r32); + ASSERT_EQ(var_r32.ShapeID(), adios2::ShapeID::GlobalArray); + ASSERT_EQ(var_r32.Steps(), NSteps); + ASSERT_EQ(var_r32.Shape()[0], mpiSize * Nx); + + const adios2::Dims start{mpiRank * Nx + Nx / 2}; + const adios2::Dims count{Nx / 2}; + const adios2::Box sel(start, count); + var_r32.SetSelection(sel); + bpReader.Get(var_r32, decompressedR32s); + bpReader.EndStep(); + + for (size_t i = 0; i < Nx / 2; ++i) + { + std::stringstream ss; + ss << "t=" << t << " i=" << i << " rank=" << mpiRank; + std::string msg = ss.str(); + + ASSERT_EQ(decompressedR32s[i], r32s[Nx / 2 + i]) << msg; + } + ++t; + } + + EXPECT_EQ(t, NSteps); + + bpReader.Close(); + } +} class BPWriteReadBlosc2 : public ::testing::TestWithParam> { @@ -891,6 +1003,10 @@ TEST_P(BPWriteReadBlosc2, ADIOS2BPWriteReadBlosc23DSel) { Blosc2Accuracy3DSel(std::get<0>(GetParam()), std::get<1>(GetParam()), std::get<2>(GetParam())); } +TEST_P(BPWriteReadBlosc2, ADIOS2BPWriteReadBlosc2Null) +{ + Blosc2NullBlocks(std::get<0>(GetParam()), std::get<1>(GetParam()), std::get<2>(GetParam())); +} INSTANTIATE_TEST_SUITE_P( Blosc2Accuracy, BPWriteReadBlosc2, diff --git a/testing/adios2/engine/bp/operations/TestBPWriteReadMGARD.cpp b/testing/adios2/engine/bp/operations/TestBPWriteReadMGARD.cpp index 85b96063c6..3135ab6feb 100644 --- a/testing/adios2/engine/bp/operations/TestBPWriteReadMGARD.cpp +++ b/testing/adios2/engine/bp/operations/TestBPWriteReadMGARD.cpp @@ -891,6 +891,119 @@ void MGARDAccuracy3DSel(const std::string tolerance) } } +void MGARDNullBlocks(const std::string tolerance) +{ + // Null blocks only work for BP4 and BP5 + if (engineName == "BP3") return; + + // Each process would write a 1x8 array and all processes would + // form a mpiSize * Nx 1D array + const std::string fname("BPWRMGARDNull_" + tolerance + ".bp"); + + int mpiRank = 0, mpiSize = 1; + // Number of rows + const size_t Nx = 200; + const size_t Ny = 500; + + // Number of steps + const size_t NSteps = 1; + + std::vector r32s(Nx * Ny); + std::iota(r32s.begin(), r32s.end(), 0.f); + +#if ADIOS2_USE_MPI + MPI_Comm_rank(MPI_COMM_WORLD, &mpiRank); + MPI_Comm_size(MPI_COMM_WORLD, &mpiSize); +#endif + +#if ADIOS2_USE_MPI + adios2::ADIOS adios(MPI_COMM_WORLD); +#else + adios2::ADIOS adios; +#endif + { + adios2::IO io = adios.DeclareIO("TestIO"); + + if (!engineName.empty()) + { + io.SetEngine(engineName); + } + + const adios2::Dims shape{static_cast(Nx * mpiSize), Ny}; + const adios2::Dims start{static_cast(Nx * mpiRank), 0}; + const adios2::Dims count{Nx, Ny}; + + auto var_r32 = io.DefineVariable("r32", shape, start, count); + + // add operations + adios2::Operator mgardOp = adios.DefineOperator("mgardCompressor", adios2::ops::LossyMGARD); + var_r32.AddOperation(mgardOp, {{adios2::ops::mgard::key::tolerance, tolerance}, + {adios2::ops::mgard::key::s, "inf"}}); + + adios2::Engine bpWriter = io.Open(fname, adios2::Mode::Write); + + for (size_t step = 0; step < NSteps; ++step) + { + bpWriter.BeginStep(); + var_r32.SetSelection(adios2::Box({Nx * mpiRank, 0}, {Nx, Ny})); + bpWriter.Put("r32", r32s.data()); + var_r32.SetSelection(adios2::Box({Nx * mpiRank, 0}, {0, 0})); + std::vector r32_empty; + bpWriter.Put("r32", r32_empty.data()); + bpWriter.EndStep(); + } + + bpWriter.Close(); + } + { + adios2::IO io = adios.DeclareIO("ReadIO"); + + if (!engineName.empty()) + { + io.SetEngine(engineName); + } + + adios2::Engine bpReader = io.Open(fname, adios2::Mode::Read); + + unsigned int t = 0; + std::vector decompressedR32s; + while (bpReader.BeginStep() == adios2::StepStatus::OK) + { + auto var_r32 = io.InquireVariable("r32"); + EXPECT_TRUE(var_r32); + ASSERT_EQ(var_r32.ShapeID(), adios2::ShapeID::GlobalArray); + ASSERT_EQ(var_r32.Steps(), NSteps); + ASSERT_EQ(var_r32.Shape()[0], mpiSize * Nx); + ASSERT_EQ(var_r32.Shape()[1], Ny); + + const adios2::Dims start{mpiRank * Nx + Nx / 2, 0}; + const adios2::Dims count{Nx / 2, Ny}; + const adios2::Box sel(start, count); + var_r32.SetSelection(sel); + + bpReader.Get(var_r32, decompressedR32s); + bpReader.EndStep(); + auto r32s_Max = std::max_element(r32s.begin(), r32s.end()); + + for (size_t i = 0; i < Nx / 2 * Ny; ++i) + { + std::stringstream ss; + ss << "t=" << t << " i=" << i << " rank=" << mpiRank; + std::string msg = ss.str(); + + ASSERT_LT(std::abs(decompressedR32s[i] - r32s[Nx / 2 * Ny + i]) / *r32s_Max, + std::stod(tolerance)) + << msg; + } + ++t; + } + + EXPECT_EQ(t, NSteps); + + bpReader.Close(); + } +} + class BPWriteReadMGARD : public ::testing::TestWithParam { public: @@ -911,6 +1024,8 @@ TEST_P(BPWriteReadMGARD, BPWRMGARDSel2D) { MGARDAccuracy2DSel(GetParam()); } TEST_P(BPWriteReadMGARD, BPWRMGARDSel3D) { MGARDAccuracy3DSel(GetParam()); } +TEST_P(BPWriteReadMGARD, BPWRMGARDNullBlocks) { MGARDNullBlocks(GetParam()); } + INSTANTIATE_TEST_SUITE_P(MGARDAccuracy, BPWriteReadMGARD, ::testing::Values("0.01", "0.001", "0.0001", "0.00001"));