diff --git a/homework_2/include/config.h b/homework_2/include/config.h index 182cb9c..ab6cabc 100644 --- a/homework_2/include/config.h +++ b/homework_2/include/config.h @@ -24,7 +24,18 @@ #define CODE_VERSION BITONIC #endif -// Value type selection +/*! + * Value type selection + * + * We support the following compiler types or the that translate to them: + * char - unsigned char + * short - unsigned short + * int - unsigned int + * long - unsigned long + * long long - unsigned long long + * float + * double + */ using distValue_t = uint32_t; /*! diff --git a/homework_2/include/distsort.hpp b/homework_2/include/distsort.hpp index 23bcf2d..5eb385a 100644 --- a/homework_2/include/distsort.hpp +++ b/homework_2/include/distsort.hpp @@ -267,26 +267,26 @@ void minmax(RangeT& local, const RangeT& remote, bool keepSmall) noexcept { * @param Processes [mpi_id_t] The total number of MPI processes */ template -void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { +void distBubbletonic(ShadowedT& data, mpi_id_t Processes, mpi_id_t rank) { // Initially sort to create a half part of a bitonic sequence - fullSort(data, ascending(mpi.rank(), 0)); + fullSort(data, ascending(rank, 0)); // Sort network (O(N) iterations) - for (size_t step = 0; step < static_cast(Processes-1); ++step) { + for (size_t step = 0; step < static_cast(Processes); ++step) { // Find out exchange configuration - auto part = partner(mpi.rank(), step); - auto ks = keepSmall(mpi.rank(), part, Processes); - if ( isActive(mpi.rank(), Processes) && + auto part = partner(rank, step); + auto ks = keepSmall(rank, part, Processes); + if ( isActive(rank, Processes) && isActive(part, Processes) ) { // Exchange with partner, keep nim-or-max and sort - O(N) mpi.exchange(part, data.getActive(), data.getShadow(), step); minmax(data.getActive(), data.getShadow(), ks); - elbowSort(data, ascending(mpi.rank(), Processes)); + elbowSort(data, ascending(rank, Processes)); } } // Invert if the node was descending. - if (!ascending(mpi.rank(), 0)) + if (!ascending(rank, 0)) elbowSort(data, true); } @@ -304,9 +304,9 @@ void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { * @param Processes [mpi_id_t] The total number of MPI processes */ template -void distBitonic(ShadowedT& data, mpi_id_t Processes) { +void distBitonic(ShadowedT& data, mpi_id_t Processes, mpi_id_t rank) { // Initially sort to create a half part of a bitonic sequence - fullSort(data, ascending(mpi.rank(), 0)); + fullSort(data, ascending(rank, 0)); // Run through sort network using elbow-sort ( O(LogN * LogN) iterations ) auto p = static_cast(std::log2(Processes)); @@ -314,14 +314,14 @@ void distBitonic(ShadowedT& data, mpi_id_t Processes) { for (size_t step = depth; step > 0;) { --step; // Find out exchange configuration - auto part = partner(mpi.rank(), step); - auto ks = keepSmall(mpi.rank(), part, depth); + auto part = partner(rank, step); + auto ks = keepSmall(rank, part, depth); // Exchange with partner, keep nim-or-max mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); minmax(data.getActive(), data.getShadow(), ks); } // sort - O(N) - elbowSort (data, ascending(mpi.rank(), depth)); + elbowSort (data, ascending(rank, depth)); } } diff --git a/homework_2/include/utils.hpp b/homework_2/include/utils.hpp index 10a6f2e..2ececf8 100644 --- a/homework_2/include/utils.hpp +++ b/homework_2/include/utils.hpp @@ -15,68 +15,94 @@ #include #include -//#include "matrix.hpp" #include "config.h" -template struct MPI_TypeMapper; -// Specializations for supported types -template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_CHAR; } }; -template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; +/* + * MPI_ dispatcher mechanism + */ +template struct MPI_TypeMapper { }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_CHAR; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_SHORT; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_INT; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_LONG; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; +template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; template <> struct MPI_TypeMapper{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } }; - template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_FLOAT; } }; template <> struct MPI_TypeMapper { static MPI_Datatype getType() { return MPI_DOUBLE; } }; +/*! + * MPI wrapper type to provide MPI functionality and RAII to MPI as a resource + * + * @tparam TID The MPI type for process id [default: int] + */ template struct MPI_t { using ID_t = TID; // Export TID type (currently int defined by the standard) + /*! + * Initializes the MPI environment, must called from each process + * + * @param argc [int*] POINTER to main's argc argument + * @param argv [car***] POINTER to main's argv argument + */ void init(int *argc, char ***argv) { // Initialize the MPI environment - MPI_Init(argc, argv); + int err; + if ((err = MPI_Init(argc, argv)) != MPI_SUCCESS) + mpi_throw(err, "(MPI) MPI_Init() - "); initialized_ = true; // Get the number of processes int size_value, rank_value; - MPI_Comm_size(MPI_COMM_WORLD, &size_value); - MPI_Comm_rank(MPI_COMM_WORLD, &rank_value); + if ((err = MPI_Comm_size(MPI_COMM_WORLD, &size_value)) != MPI_SUCCESS) + mpi_throw(err, "(MPI) MPI_Comm_size() - "); + if ((err = MPI_Comm_rank(MPI_COMM_WORLD, &rank_value)) != MPI_SUCCESS) + mpi_throw(err, "(MPI) MPI_Comm_rank() - "); size_ = static_cast(size_value); rank_ = static_cast(rank_value); // Get the name of the processor char processor_name[MPI_MAX_PROCESSOR_NAME]; int name_len; - MPI_Get_processor_name(processor_name, &name_len); + if ((err = MPI_Get_processor_name(processor_name, &name_len)) != MPI_SUCCESS) + mpi_throw(err, "(MPI) MPI_Get_processor_name() - "); name_ = std::string (processor_name, name_len); } + /*! + * Exchange data with partner as part of the sorting network of both bubbletonic or bitonic + * sorting algorithms. + * + * This function matches a transmit and a receive in order for fully exchanged data between + * current node and partner. + * + * @tparam T The inner valur type used in buffer + * + * @param partner [mpi_id_t] The partner for the exchange + * @param send_data [std::vector] Reference to local data to send + * @param recv_data [std::vector] Reference to buffer to receive data from partner + * @param tag [int] The tag to use for the MPI communication + */ template void exchange(ID_t partner, const std::vector& send_data, std::vector& recv_data, int tag) { using namespace std::string_literals; - MPI_Status status; MPI_Datatype datatype = MPI_TypeMapper::getType(); int send_count = static_cast(send_data.size()); - int err = MPI_Sendrecv( + MPI_Status status; + int err; + if ((err = MPI_Sendrecv( send_data.data(), send_count, datatype, partner, tag, recv_data.data(), send_count, datatype, partner, tag, MPI_COMM_WORLD, &status - ); - if (err != MPI_SUCCESS) { - char err_msg[MPI_MAX_ERROR_STRING]; - int msg_len; - MPI_Error_string(err, err_msg, &msg_len); - throw std::runtime_error("(MPI) MPI_Sendrecv() - " + std::string (err_msg) + '\n'); - } + )) != MPI_SUCCESS) + mpi_throw(err, "(MPI) MPI_Sendrecv() - "); } // Accessors @@ -84,26 +110,65 @@ struct MPI_t { [[nodiscard]] ID_t size() const noexcept { return size_; } [[nodiscard]] const std::string& name() const noexcept { return name_; } + /*! + * Finalized the MPI + */ void finalize() { // Finalize the MPI environment initialized_ = false; MPI_Finalize(); } + + //! RAII MPI finalization ~MPI_t() { // Finalize the MPI environment even on unexpected errors if (initialized_) MPI_Finalize(); } + + + // Local functionality +private: + /*! + * Throw exception helper. It bundles the prefix with the MPI error string retrieved by + * MPI API. + * + * @param err The MPI error code + * @param prefixMsg The prefix text for the exception error message + */ + void mpi_throw(int err, const char* prefixMsg) { + char err_msg[MPI_MAX_ERROR_STRING]; + int msg_len; + MPI_Error_string(err, err_msg, &msg_len); + throw std::runtime_error(prefixMsg + std::string (err_msg) + '\n'); + } + +#if !defined TESTING private: - ID_t rank_{}; - ID_t size_{}; - std::string name_{}; - bool initialized_{}; +#else +public: +#endif + ID_t rank_{}; //!< MPI rank of the process + ID_t size_{}; //!< MPI total size of the execution + std::string name_{}; //!< The name of the local machine + bool initialized_{}; //!< RAII helper flag }; +/* + * Exported data types + */ extern MPI_t<> mpi; using mpi_id_t = MPI_t<>::ID_t; + + +/*! + * A std::vector wrapper with 2 vectors, an active and a shadow. This type exposes the standard vector + * functionality of the active vector. The shadow can be used when we need to use the vector as mutable + * data in algorithms that can not support "in-place" editing (like elbow-sort for example) + * + * @tparam Value_t the inner data type of the vectors + */ template struct ShadowedVec_t { // STL requirements @@ -148,7 +213,18 @@ struct ShadowedVec_t { return *this; } - // Dispatch to active vector + // Type accessors + const std::vector& getNorth() const { return North; } + const std::vector& getSouth() const { return South; } + std::vector& getActive() { return (active == north) ? North : South; } + std::vector& getShadow() { return (active == north) ? South : North; } + const std::vector& getActive() const { return (active == north) ? North : South; } + const std::vector& getShadow() const { return (active == north) ? South : North; } + + // Switching vectors + void switch_active() { active = (active == north) ? south : north; } + + // Dispatch to active vector functionality Value_t& operator[](size_type index) { return getActive()[index]; } const Value_t& operator[](size_type index) const { return getActive()[index]; } @@ -156,12 +232,12 @@ struct ShadowedVec_t { const Value_t& at(size_type index) const { return getActive().at(index); } void push_back(const Value_t& value) { getActive().push_back(value); } - void push_back(Value_t&& value) { getActive().push_back(std::move(value)); } - void pop_back() { getActive().pop_back(); } + void push_back(Value_t&& value) { getActive().push_back(std::move(value)); } + void pop_back() { getActive().pop_back(); } Value_t& front() { return getActive().front(); } + Value_t& back() { return getActive().back(); } const Value_t& front() const { return getActive().front(); } - Value_t& back() { return getActive().back(); } - const Value_t& back() const { return getActive().back(); } + const Value_t& back() const { return getActive().back(); } iterator begin() { return getActive().begin(); } const_iterator begin() const { return getActive().begin(); } @@ -182,46 +258,20 @@ struct ShadowedVec_t { [[nodiscard]] bool empty() const { return getActive().empty(); } void clear() { getActive().clear(); } - void swap(std::vector& other) { getActive().swap(other); } - // Switching vectors - void switch_active() { active = (active == north) ? south : north; } - - // Accessors - const std::vector& getNorth() const { return North; } - const std::vector& getSouth() const { return South; } - std::vector& getActive() { - return (active == north) ? North : South; - } - const std::vector& getActive() const { - return (active == north) ? North : South; - } - std::vector& getShadow() { - return (active == north) ? South : North; - } - const std::vector& getShadow() const { - return (active == north) ? South : North; - } - // Comparisons - bool operator== (const ShadowedVec_t& other) { - return getActive() == other.getActive(); - } - bool operator!= (const ShadowedVec_t& other) { - return getActive() != other.getActive(); - } - bool operator== (const std::vector& other) { - return getActive() == other; - } - bool operator!= (const std::vector& other) { - return getActive() != other; - } + bool operator== (const ShadowedVec_t& other) { return getActive() == other.getActive(); } + bool operator!= (const ShadowedVec_t& other) { return getActive() != other.getActive(); } + bool operator== (const std::vector& other) { return getActive() == other; } + bool operator!= (const std::vector& other) { return getActive() != other; } private: - std::vector North{}; - std::vector South{}; - enum { north, south } active{north}; + std::vector North{}; //!< Actual buffer to be used either as active or shadow + std::vector South{}; //!< Actual buffer to be used either as active or shadow + enum { + north, south + } active{north}; //!< Flag to select between North and South buffer }; using distBuffer_t = ShadowedVec_t; diff --git a/homework_2/src/main.cpp b/homework_2/src/main.cpp index e4fecfe..779c2f1 100644 --- a/homework_2/src/main.cpp +++ b/homework_2/src/main.cpp @@ -136,9 +136,9 @@ int main(int argc, char* argv[]) try { logger << "Starting distributed sorting ... "; timer.start(); #if CODE_VERSION == BUBBLETONIC - distBubbletonic(Data, mpi.size()); + distBubbletonic(Data, mpi.size(), mpi.rank()); #else - distBitonic (Data, mpi.size()); + distBitonic (Data, mpi.size(), mpi.rank()); #endif timer.stop(); if (mpi.rank() == 0) diff --git a/homework_2/test/tests_Bitonic.cpp b/homework_2/test/tests_Bitonic.cpp index 4e4eb12..33e3b39 100644 --- a/homework_2/test/tests_Bitonic.cpp +++ b/homework_2/test/tests_Bitonic.cpp @@ -328,67 +328,3 @@ TEST(TdistBitonic_UT, keepsmall_test7) { EXPECT_EQ(ts_expected[node], keepSmall(node, ts_partner[node], ts_depth)); } } - -#if 0 -TEST(TdistBitonic_UT, distBitonic_test1) { - AllData_t ts_Data { - ShadowedVec_t (8), ShadowedVec_t (8) - }; -(unsigned(std::time(nullptr))); - for (auto - std::srand& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBitonic(2, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - -TEST(TdistBitonic_UT, distBitonic_test2) { - AllData_t ts_Data { - ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBitonic(4, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - -TEST(TdistBitonic_UT, distBitonic_test3) { - AllData_t ts_Data { - ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), - ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBitonic(8, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - -#endif \ No newline at end of file diff --git a/homework_2/test/tests_Bubbletonic.cpp b/homework_2/test/tests_Bubbletonic.cpp index cb1535f..f83e959 100644 --- a/homework_2/test/tests_Bubbletonic.cpp +++ b/homework_2/test/tests_Bubbletonic.cpp @@ -142,69 +142,3 @@ TEST(TdistBubbletonic_UT, isActive_test2) { EXPECT_EQ(isActive(8, 8), false); EXPECT_EQ(isActive(9, 8), false); } - - - -#if 0 -TEST(TdistBubbletonic_UT, distBubbletonic_test1) { - AllData_t ts_Data { - ShadowedVec_t (8), ShadowedVec_t (8) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBubbletonic(2, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - - -TEST(TdistBubbletonic_UT, distBubbletonic_test2) { - AllData_t ts_Data { - ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBubbletonic(4, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - -TEST(TdistBubbletonic_UT, distBubbletonic_test3) { - AllData_t ts_Data { - ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), - ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBubbletonic(8, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} -#endif \ No newline at end of file diff --git a/homework_2/test/tests_Common.cpp b/homework_2/test/tests_Common.cpp index 31df90c..5f14989 100644 --- a/homework_2/test/tests_Common.cpp +++ b/homework_2/test/tests_Common.cpp @@ -91,66 +91,3 @@ TEST(TdistCommonUT, elbowSort_test3) { EXPECT_EQ((ts_data == ts_expected_des), true); } -#if 0 -TEST(TdistBubbletonic_UT, distBubbletonic_test1) { - AllData_t ts_Data { - ShadowedVec_t (8), ShadowedVec_t (8) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBubbletonic(2, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - - -TEST(TdistBubbletonic_UT, distBubbletonic_test2) { - AllData_t ts_Data { - ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBubbletonic(4, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} - -TEST(TdistBubbletonic_UT, distBubbletonic_test3) { - AllData_t ts_Data { - ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), - ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32), ShadowedVec_t (32) - }; - - std::srand(unsigned(std::time(nullptr))); - for (auto& v : ts_Data) { - std::generate(v.begin(), v.end(), std::rand); - } - - distBubbletonic(8, ts_Data); - - auto max = std::numeric_limits::min(); - for (auto& v : ts_Data) { - EXPECT_EQ((max <= v[0]), true); - EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); - max = v.back(); - } -} -#endif \ No newline at end of file diff --git a/homework_2/test/tests_MPI.cpp b/homework_2/test/tests_MPI.cpp new file mode 100644 index 0000000..1713f2e --- /dev/null +++ b/homework_2/test/tests_MPI.cpp @@ -0,0 +1,205 @@ +/** + * \file + * \brief PDS HW2 tests + * + * \author + * Christos Choutouridis AEM:8997 + * + */ + +#include +#include +#include +#include "distsort.hpp" + +MPI_t<> ts_mpi; + +// Mersenne seeded from hw if possible. range: [type_min, type_max] +std::random_device rd; +std::mt19937 gen(rd()); + +class TMPIdistSort : public ::testing::Test { +protected: + static void SetUpTestSuite() { + int argc = 0; + char** argv = nullptr; + MPI_Init(&argc, &argv); + + int rank, size; + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + ts_mpi.rank_ = rank; + ts_mpi.size_ = size; + } + + static void TearDownTestSuite() { + MPI_Finalize(); + } +}; + + +/* + * To run thiese test execute: + * mpirun -np ./bit/tests + */ +TEST_F(TMPIdistSort, distBubbletonic_test1) { + // Create and fill vector + using tsValue_t = uint8_t; // Test parameters + size_t ts_buffer_size = 16; + + ShadowedVec_t ts_Data; + std::uniform_int_distribution dis( + std::numeric_limits::min(), + std::numeric_limits::max() + ); + ts_Data.resize(ts_buffer_size); + std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); + + // Execute function under test in all processes + distBubbletonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); + + // Local min and max + auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); + auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); + + // Gather min/max to rank 0 + std::vector global_mins(ts_mpi.size()); + std::vector global_maxes(ts_mpi.size()); + MPI_Datatype datatype = MPI_TypeMapper::getType(); + + MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); + MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); + + // Check results + EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); + if (ts_mpi.rank() == 0) { + for (size_t i = 1; i < global_mins.size(); ++i) { + EXPECT_LE(global_maxes[i - 1], global_mins[i]); + } + } +} + +/* + * To run thiese test execute: + * mpirun -np ./bit/tests + */ +TEST_F(TMPIdistSort, distBubbletonic_test2) { + // Create and fill vector + using tsValue_t = uint32_t; // Test parameters + size_t ts_buffer_size = 1 << 16; + + ShadowedVec_t ts_Data; + std::uniform_int_distribution dis( + std::numeric_limits::min(), + std::numeric_limits::max() + ); + ts_Data.resize(ts_buffer_size); + std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); + + // Execute function under test in all processes + distBubbletonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); + + // Local min and max + auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); + auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); + + // Gather min/max to rank 0 + std::vector global_mins(ts_mpi.size()); + std::vector global_maxes(ts_mpi.size()); + MPI_Datatype datatype = MPI_TypeMapper::getType(); + + MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); + MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); + + // Check results + EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); + if (ts_mpi.rank() == 0) { + for (size_t i = 1; i < global_mins.size(); ++i) { + EXPECT_LE(global_maxes[i - 1], global_mins[i]); + } + } +} + + +/* + * To run thiese test execute: + * mpirun -np ./bit/tests + */ +TEST_F(TMPIdistSort, distBitonic_test1) { + // Create and fill vector + using tsValue_t = uint8_t; // Test parameters + size_t ts_buffer_size = 16; + + ShadowedVec_t ts_Data; + std::uniform_int_distribution dis( + std::numeric_limits::min(), + std::numeric_limits::max() + ); + ts_Data.resize(ts_buffer_size); + std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); + + // Execute function under test in all processes + distBitonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); + + // Local min and max + auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); + auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); + + // Gather min/max to rank 0 + std::vector global_mins(ts_mpi.size()); + std::vector global_maxes(ts_mpi.size()); + MPI_Datatype datatype = MPI_TypeMapper::getType(); + + MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); + MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); + + // Check results + EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); + if (ts_mpi.rank() == 0) { + for (size_t i = 1; i < global_mins.size(); ++i) { + EXPECT_LE(global_maxes[i - 1], global_mins[i]); + } + } +} + +/* + * To run thiese test execute: + * mpirun -np ./bit/tests + */ +TEST_F(TMPIdistSort, distBitonic_test2) { + // Create and fill vector + using tsValue_t = uint32_t; // Test parameters + size_t ts_buffer_size = 1 << 16; + + ShadowedVec_t ts_Data; + std::uniform_int_distribution dis( + std::numeric_limits::min(), + std::numeric_limits::max() + ); + ts_Data.resize(ts_buffer_size); + std::generate(ts_Data.begin(), ts_Data.end(), [&]() { return dis(gen); }); + + // Execute function under test in all processes + distBitonic(ts_Data, ts_mpi.size(), ts_mpi.rank()); + + // Local min and max + auto local_min = *std::min_element(ts_Data.begin(), ts_Data.end()); + auto local_max = *std::max_element(ts_Data.begin(), ts_Data.end()); + + // Gather min/max to rank 0 + std::vector global_mins(ts_mpi.size()); + std::vector global_maxes(ts_mpi.size()); + MPI_Datatype datatype = MPI_TypeMapper::getType(); + + MPI_Gather(&local_min, 1, datatype, global_mins.data(), 1, datatype, 0, MPI_COMM_WORLD); + MPI_Gather(&local_max, 1, datatype, global_maxes.data(), 1, datatype, 0, MPI_COMM_WORLD); + + // Check results + EXPECT_EQ(std::is_sorted(ts_Data.begin(), ts_Data.end()), true); + if (ts_mpi.rank() == 0) { + for (size_t i = 1; i < global_mins.size(); ++i) { + EXPECT_LE(global_maxes[i - 1], global_mins[i]); + } + } +} +