@@ -10,8 +10,7 @@ | |||||
#ifndef CONFIG_H_ | #ifndef CONFIG_H_ | ||||
#define CONFIG_H_ | #define CONFIG_H_ | ||||
#include <iostream> | |||||
#include <string> | |||||
#include <cstdint> | |||||
/* | /* | ||||
* Defines for different version of the exercise | * Defines for different version of the exercise | ||||
@@ -25,13 +24,17 @@ | |||||
#define CODE_VERSION BITONIC | #define CODE_VERSION BITONIC | ||||
#endif | #endif | ||||
// Value type selection | |||||
using distValue_t = uint8_t; | |||||
/*! | /*! | ||||
* Session option for each invocation of the executable | * Session option for each invocation of the executable | ||||
*/ | */ | ||||
struct session_t { | struct session_t { | ||||
bool timing{false}; | |||||
bool verbose{false}; //!< Flag to enable verbose output to stdout | |||||
size_t arraySize{0}; | |||||
bool ndebug{false}; | |||||
bool timing{false}; | |||||
bool verbose{false}; //!< Flag to enable verbose output to stdout | |||||
}; | }; | ||||
extern session_t session; | extern session_t session; | ||||
@@ -1,73 +0,0 @@ | |||||
/*! | |||||
* \file | |||||
* \brief Distributed bitonic implementation header | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#ifndef DISTBITONIC_H_ | |||||
#define DISTBITONIC_H_ | |||||
#include <cstdint> | |||||
#include "utils.hpp" | |||||
/*! | |||||
* Enumerator for the different versions of the sorting method | |||||
*/ | |||||
enum class SortMode { | |||||
Bubbletonic, //!< The v0.5 of the algorithm where we use a bubble-sort like approach | |||||
Bitonic //!< The v1.0 of the algorithm where we use the bitonic data-exchange approach | |||||
}; | |||||
using Data_t = std::vector<uint8_t>; | |||||
using AllData_t = std::vector<Data_t>; | |||||
/* | |||||
* ============================== Sort utilities ============================== | |||||
*/ | |||||
/*! | |||||
* The primary function template of ascending(). It is DISABLED since , it is explicitly specialized | |||||
* for each of the \c SortMode | |||||
*/ | |||||
template <SortMode Mode> bool ascending(mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; | |||||
template <> bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept; | |||||
template <> bool ascending<SortMode::Bitonic>(mpi_id_t node, size_t depth) noexcept; | |||||
/*! | |||||
* The primary function template of partner(). It is DISABLED since , it is explicitly specialized | |||||
* for each of the \c SortMode | |||||
*/ | |||||
template <SortMode Mode> mpi_id_t partner(mpi_id_t, size_t) noexcept = delete; | |||||
template <> mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept; | |||||
template <> mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept; | |||||
/*! | |||||
* The primary function template of keepSmall(). It is DISABLED since , it is explicitly specialized | |||||
* for each of the \c SortMode | |||||
*/ | |||||
template<SortMode Mode> bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; | |||||
template<> bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept; | |||||
template<> bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept; | |||||
bool isActive(mpi_id_t node, mpi_id_t nodes) noexcept; | |||||
/* | |||||
* ============================== Data utilities ============================== | |||||
*/ | |||||
void exchange(mpi_id_t node, mpi_id_t partner); | |||||
void minmax(AllData_t& data, mpi_id_t node, mpi_id_t partner, bool keepsmall); | |||||
/* | |||||
* ============================== Sort algorithms ============================== | |||||
*/ | |||||
void bubbletonic_network(AllData_t& data, mpi_id_t nodes); | |||||
void distBubbletonic(mpi_id_t P, AllData_t& data); | |||||
void bitonic_network(AllData_t& data, mpi_id_t nodes, mpi_id_t depth); | |||||
void distBitonic(mpi_id_t P, AllData_t& data); | |||||
#endif //DISTBITONIC_H_ |
@@ -0,0 +1,288 @@ | |||||
/*! | |||||
* \file | |||||
* \brief Distributed sort implementation header | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#ifndef DISTBITONIC_H_ | |||||
#define DISTBITONIC_H_ | |||||
#include <vector> | |||||
#include <algorithm> | |||||
#include <cmath> | |||||
#include <cstdint> | |||||
#if !defined DEBUG | |||||
#define NDEBUG | |||||
#endif | |||||
#include <cassert> | |||||
#include "utils.hpp" | |||||
#include "config.h" | |||||
/*! | |||||
* Enumerator for the different versions of the sorting method | |||||
*/ | |||||
enum class SortMode { | |||||
Bubbletonic, //!< The v0.5 of the algorithm where we use a bubble-sort like approach | |||||
Bitonic //!< The v1.0 of the algorithm where we use the bitonic data-exchange approach | |||||
}; | |||||
/* | |||||
* ============================== Sort utilities ============================== | |||||
*/ | |||||
/*! | |||||
* The primary function template of ascending(). It is DISABLED since , it is explicitly specialized | |||||
* for each of the \c SortMode | |||||
*/ | |||||
template <SortMode Mode> inline bool ascending(mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; | |||||
/*! | |||||
* Returns the ascending or descending configuration of the node's sequence based on | |||||
* the current node (MPI process) and the depth of the sorting network | |||||
* | |||||
* @param node The current node (MPI process) | |||||
* @return True if we need ascending configuration, false otherwise | |||||
*/ | |||||
template <> inline | |||||
bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept { | |||||
return (node % 2) == 0; | |||||
} | |||||
/*! | |||||
* Returns the ascending or descending configuration of the node's sequence based on | |||||
* the current node (MPI process) and the depth of the sorting network | |||||
* | |||||
* @param node The current node (MPI process) | |||||
* @param depth The total depth of the sorting network (same for each step for a given network) | |||||
* | |||||
* @return True if we need ascending configuration, false otherwise | |||||
*/ | |||||
template <> inline | |||||
bool ascending<SortMode::Bitonic>(mpi_id_t node, size_t depth) noexcept { | |||||
return !(node & (1 << depth)); | |||||
} | |||||
/*! | |||||
* The primary function template of partner(). It is DISABLED since , it is explicitly specialized | |||||
* for each of the \c SortMode | |||||
*/ | |||||
template <SortMode Mode> inline mpi_id_t partner(mpi_id_t, size_t) noexcept = delete; | |||||
/*! | |||||
* Returns the node's partner for data exchange during the sorting network iterations | |||||
* of Bubbletonic | |||||
* | |||||
* @param node The current node | |||||
* @param step The step of the sorting network | |||||
* @return The node id of the partner for data exchange | |||||
*/ | |||||
template <> inline | |||||
mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept { | |||||
//return (node % 2 == step % 2) ? node + 1 : node - 1; | |||||
return (((node+step) % 2) == 0) ? node + 1 : node - 1; | |||||
} | |||||
/*! | |||||
* Returns the node's partner for data exchange during the sorting network iterations | |||||
* of Bitonic | |||||
* | |||||
* @param node The current node | |||||
* @param step The step of the sorting network | |||||
* @return The node id of the partner for data exchange | |||||
*/ | |||||
template <> inline | |||||
mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept { | |||||
return (node ^ (1 << step)); | |||||
} | |||||
/*! | |||||
* The primary function template of keepSmall(). It is DISABLED since , it is explicitly specialized | |||||
* for each of the \c SortMode | |||||
*/ | |||||
template<SortMode Mode> inline bool keepSmall(mpi_id_t, mpi_id_t, [[maybe_unused]] size_t) noexcept = delete; | |||||
/*! | |||||
* Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. | |||||
* | |||||
* @param node The node for which we check | |||||
* @param partner The partner of the data exchange | |||||
* @return True if the node should keep the small values, false otherwise | |||||
*/ | |||||
template <> inline | |||||
bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept { | |||||
assert(node != partner); | |||||
return (node < partner); | |||||
} | |||||
/*! | |||||
* Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. | |||||
* | |||||
* @param node The node for which we check | |||||
* @param partner The partner of the data exchange | |||||
* @param depth The total depth of the sorting network (same for each step for a given network) | |||||
* @return True if the node should keep the small values, false otherwise | |||||
*/ | |||||
template <> inline | |||||
bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept { | |||||
assert(node != partner); | |||||
return ascending<SortMode::Bitonic>(node, depth) == (node < partner); | |||||
} | |||||
/*! | |||||
* Predicate to check if the node is active in the current iteration of the bubbletonic | |||||
* sort exchange. | |||||
* | |||||
* @param node The node to check | |||||
* @param nodes The total number of nodes | |||||
* @return True if the node is active, false otherwise | |||||
*/ | |||||
bool isActive(mpi_id_t node, size_t nodes) noexcept; | |||||
/* | |||||
* ============================== Data utilities ============================== | |||||
*/ | |||||
/*! | |||||
* | |||||
* @tparam RangeT | |||||
* @param data | |||||
* @param ascending | |||||
*/ | |||||
template<typename RangeT> | |||||
void fullSort(RangeT& data, bool ascending) { | |||||
// Use introsort from stdlib++ here, unless ... | |||||
if (ascending) | |||||
std::sort(data.begin(), data.end(), std::less<>()); | |||||
else | |||||
std::sort(data.begin(), data.end(), std::greater<>()); | |||||
} | |||||
/*! | |||||
* | |||||
* @tparam ShadowedT | |||||
* @tparam CompT | |||||
* @param data | |||||
* @param comp | |||||
*/ | |||||
template<typename ShadowedT, typename CompT> | |||||
void elbowSortCore(ShadowedT& data, CompT comp) { | |||||
size_t N = data.size(); | |||||
auto active = data.getActive(); | |||||
auto shadow = data.getShadow(); | |||||
size_t left = std::distance( | |||||
active.begin(), | |||||
std::min_element(active.begin(), active.end()) | |||||
); | |||||
size_t right = (left == N-1) ? 0 : left + 1; | |||||
for (size_t i = 0 ; i<N ; ++i) { | |||||
if (comp(active[left], active[right])) { | |||||
shadow[i] = active[left]; | |||||
left = (left == 0) ? N-1 : left -1; | |||||
} | |||||
else { | |||||
shadow[i] = active[right]; | |||||
right = (right + 1) % N; | |||||
} | |||||
} | |||||
data.switch_active(); | |||||
} | |||||
/*! | |||||
* | |||||
* @tparam ShadowedT | |||||
* @param data | |||||
* @param ascending | |||||
*/ | |||||
template<typename ShadowedT> | |||||
void elbowSort(ShadowedT& data, bool ascending) { | |||||
if (ascending) | |||||
elbowSortCore(data, std::less<>()); | |||||
else | |||||
elbowSortCore(data, std::greater<>()); | |||||
} | |||||
/*! | |||||
* | |||||
* @tparam RangeT | |||||
* @param local | |||||
* @param remote | |||||
* @param keepsmall | |||||
*/ | |||||
template<typename RangeT> | |||||
void minmax(RangeT& local, RangeT& remote, bool keepsmall) { | |||||
using value_t = typename RangeT::value_type; | |||||
std::transform( | |||||
local.begin(), local.end(), | |||||
remote.begin(), | |||||
local.begin(), | |||||
[keepsmall](const value_t& a, const value_t& b){ | |||||
return (keepsmall) ? std::min(a, b) : std::max(a, b); | |||||
}); | |||||
} | |||||
/* | |||||
* ============================== Sort algorithms ============================== | |||||
*/ | |||||
/*! | |||||
* | |||||
* @tparam ShadowedT | |||||
* @param data | |||||
* @param Processes | |||||
*/ | |||||
template<typename ShadowedT> | |||||
void distBubbletonic(ShadowedT& data, mpi_id_t Processes) { | |||||
// Initially sort to create a half part of a bitonic sequence | |||||
fullSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), 0)); | |||||
// Sort network | |||||
for (size_t step = 0; step < Processes-1; ++step) { | |||||
auto part = partner<SortMode::Bubbletonic>(mpi.rank(), step); | |||||
auto ks = keepSmall<SortMode::Bubbletonic>(mpi.rank(), part, Processes); | |||||
if (isActive(mpi.rank(), Processes)) { | |||||
mpi.exchange(part, data.getActive(), data.getShadow(), step); | |||||
minmax(data.getActive(), data.getShadow(), ks); | |||||
elbowSort(data, ascending<SortMode::Bubbletonic>(mpi.rank(), Processes)); | |||||
} | |||||
} | |||||
if (!ascending<SortMode::Bubbletonic>(mpi.rank(), 0)) | |||||
elbowSort(data, true); | |||||
} | |||||
/*! | |||||
* | |||||
* @tparam ShadowedT | |||||
* @param data | |||||
* @param Processes | |||||
*/ | |||||
template<typename ShadowedT> | |||||
void distBitonic(ShadowedT& data, mpi_id_t Processes) { | |||||
auto p = static_cast<uint32_t>(std::log2(Processes)); | |||||
// Initially sort to create a half part of a bitonic sequence | |||||
fullSort(data, ascending<SortMode::Bitonic>(mpi.rank(), 0)); | |||||
// Run through sort network using elbow-sort | |||||
for (size_t depth = 1; depth <= p; ++depth) { | |||||
for (size_t step = depth; step > 0;) { | |||||
--step; | |||||
auto part = partner<SortMode::Bitonic>(mpi.rank(), step); | |||||
auto ks = keepSmall<SortMode::Bitonic>(mpi.rank(), part, depth); | |||||
mpi.exchange(part, data.getActive(), data.getShadow(), (depth << 8) | step); | |||||
minmax(data.getActive(), data.getShadow(), ks); | |||||
} | |||||
elbowSort (data, ascending<SortMode::Bitonic>(mpi.rank(), depth)); | |||||
} | |||||
} | |||||
#endif //DISTBITONIC_H_ |
@@ -1,14 +0,0 @@ | |||||
/*! | |||||
* \file | |||||
* \brief The distributed bitonic implementation header | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#ifndef IMPL_H_ | |||||
#define IMPL_H_ | |||||
#endif //IMPL_H_ |
@@ -1,804 +0,0 @@ | |||||
/** | |||||
* \file matrix.hpp | |||||
* \brief A matrix abstraction implementation | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#ifndef MATRIX_HPP_ | |||||
#define MATRIX_HPP_ | |||||
#include <type_traits> | |||||
#include <utility> | |||||
#include <algorithm> | |||||
#include <vector> | |||||
#include <tuple> | |||||
namespace mtx { | |||||
using std::size_t; | |||||
/* | |||||
* Small helper to strip types | |||||
*/ | |||||
template<typename T> | |||||
struct remove_cvref { | |||||
typedef std::remove_cv_t<std::remove_reference_t<T>> type; | |||||
}; | |||||
template<typename T> | |||||
using remove_cvref_t = typename remove_cvref<T>::type; | |||||
/*! | |||||
* Enumerator to denote the storage type of the array to use. | |||||
*/ | |||||
enum class MatrixType { | |||||
DENSE, /*!< Matrix is dense */ | |||||
SPARSE, /*!< Matrix is sparse */ | |||||
}; | |||||
/*! | |||||
* Enumerator to denote the storage type of the array to use. | |||||
*/ | |||||
enum class MatrixOrder { | |||||
COLMAJOR, /*!< Matrix is column major */ | |||||
ROWMAJOR, /*!< Matrix is row major */ | |||||
}; | |||||
/* | |||||
* Forward type declarations | |||||
*/ | |||||
template<typename MatrixType> struct MatCol; | |||||
template<typename MatrixType> struct MatRow; | |||||
template<typename MatrixType> struct MatVal; | |||||
/*! | |||||
* A 2-D matrix functionality over a 1-D array | |||||
* | |||||
* This is a very thin abstraction layer over a native array. | |||||
* This is tested using compiler explorer and our template produce | |||||
* almost identical assembly. | |||||
* | |||||
* The penalty hit we have is due to the fact that we use a one dimension array | |||||
* and we have to calculate the actual position from an (i,j) pair. | |||||
* The use of 1D array was our intention from the beginning, so the penalty | |||||
* was pretty much unavoidable. | |||||
* | |||||
* \tparam DataType The underling data type of the array | |||||
* \tparam IndexType The underling type for the index variables and sizes | |||||
* \tparam Type The storage type of the array | |||||
* \arg FULL For full matrix | |||||
* \arg SYMMETRIC For symmetric matrix (we use only the lower part) | |||||
*/ | |||||
template<typename DataType, | |||||
typename IndexType = size_t, | |||||
MatrixType Type = MatrixType::DENSE, | |||||
MatrixOrder Order = MatrixOrder::ROWMAJOR, | |||||
bool Symmetric = false> | |||||
struct Matrix { | |||||
using dataType = DataType; //!< meta:export of underling data type | |||||
using indexType = IndexType; //!< meta:export of underling index type | |||||
static constexpr MatrixOrder matrixOrder = Order; //!< meta:export of array order | |||||
static constexpr MatrixType matrixType = Type; //!< meta:export of array type | |||||
static constexpr bool symmetric = Symmetric; //!< meta:export symmetric flag | |||||
/*! | |||||
* \name Obj lifetime | |||||
*/ | |||||
//! @{ | |||||
//! Construct an empty matrix with dimensions rows x columns | |||||
Matrix(IndexType rows = IndexType{}, IndexType columns = IndexType{}) noexcept | |||||
: vector_storage_(capacity(rows, columns)), | |||||
raw_storage_(nullptr), | |||||
use_vector_(true), | |||||
rows_(rows), | |||||
cols_(columns) { | |||||
data_ = vector_storage_.data(); | |||||
} | |||||
//! Construct a matrix by copying existing data with dimensions rows x columns | |||||
Matrix(DataType* data, IndexType major_start, IndexType major_length, IndexType minor_length) noexcept | |||||
: vector_storage_(), | |||||
raw_storage_ (data + major_start * minor_length), | |||||
use_vector_ (false) { | |||||
if constexpr (Order == MatrixOrder::ROWMAJOR) { | |||||
rows_ = major_length; | |||||
cols_ = minor_length; | |||||
} | |||||
else { | |||||
rows_ = minor_length; | |||||
cols_ = major_length; | |||||
} | |||||
data_ = raw_storage_; | |||||
} | |||||
//! Construct a matrix using an initializer list | |||||
Matrix(IndexType rows, IndexType columns, std::initializer_list<DataType> list) | |||||
: vector_storage_(list), | |||||
raw_storage_(nullptr), | |||||
use_vector_(true), | |||||
rows_(rows), | |||||
cols_(columns) { | |||||
if (list.size() != capacity(rows, columns)) { | |||||
throw std::invalid_argument("Matrix initializer list size does not match matrix dimensions."); | |||||
} | |||||
data_ = vector_storage_.data(); | |||||
} | |||||
//! move ctor | |||||
Matrix(Matrix&& m) noexcept { moves(std::move(m)); } | |||||
//! move | |||||
Matrix& operator=(Matrix&& m) noexcept { moves(std::move(m)); return *this; } | |||||
Matrix(const Matrix& m) = delete; //!< No copy ctor | |||||
Matrix& operator=(const Matrix& m) = delete; //!< No copy | |||||
//Matrix(const Matrix& m); | |||||
//Matrix& operator=(const Matrix& m) { copy(m); } | |||||
//! @} | |||||
//! \name Data exposure | |||||
//! @{ | |||||
//! Get/Set the size of each dimension | |||||
IndexType rows() const noexcept { return rows_; } | |||||
IndexType columns() const noexcept { return cols_; } | |||||
//! Get the interface size of the Matrix (what appears to be the size) | |||||
IndexType size() const { | |||||
return rows_ * cols_; | |||||
} | |||||
//! Set the interface size of the Matrix (what appears to be the size) | |||||
IndexType resize(IndexType rows, IndexType columns) { | |||||
if (use_vector_) { | |||||
rows_ = rows; | |||||
cols_ = columns; | |||||
vector_storage_.resize(capacity(rows_, cols_)); | |||||
data_ = vector_storage_.data(); | |||||
} | |||||
return capacity(rows_, cols_); | |||||
} | |||||
//! Actual memory capacity of the symmetric matrix | |||||
static constexpr IndexType capacity(IndexType M, IndexType N) { | |||||
if constexpr (Symmetric) | |||||
return (M+1)*N/2; | |||||
else | |||||
return M*N; | |||||
} | |||||
/* | |||||
* virtual 2D accessors | |||||
*/ | |||||
DataType get (IndexType i, IndexType j) { | |||||
if constexpr (Symmetric) { | |||||
auto T = [](size_t i)->size_t { return i*(i+1)/2; }; // Triangular number of i | |||||
if constexpr (Order == MatrixOrder::COLMAJOR) { | |||||
// In column major we use the lower triangle of the matrix | |||||
if (i>=j) return data_[j*rows_ - T(j) + i]; // Lower, use our notation | |||||
else return data_[i*rows_ - T(i) + j]; // Upper, use opposite index | |||||
} | |||||
else { | |||||
// In row major we use the upper triangle of the matrix | |||||
if (i<=j) return data_[i*cols_ - T(i) + j]; // Upper, use our notation | |||||
else return data_[j*cols_ - T(j) + i]; // Lower, use opposite index | |||||
} | |||||
} | |||||
else { | |||||
if constexpr (Order == MatrixOrder::COLMAJOR) | |||||
return data_[i + j*rows_]; | |||||
else | |||||
return data_[i*cols_ + j]; | |||||
} | |||||
} | |||||
/*! | |||||
* \fn DataType set(DataType, IndexType, IndexType) | |||||
* \param v | |||||
* \param i | |||||
* \param j | |||||
* \return | |||||
*/ | |||||
DataType set (DataType v, IndexType i, IndexType j) { | |||||
if constexpr (Symmetric) { | |||||
auto T = [](size_t i)->size_t { return i*(i+1)/2; }; // Triangular number of i | |||||
if constexpr (Order == MatrixOrder::COLMAJOR) { | |||||
// In column major we use the lower triangle of the matrix | |||||
if (i>=j) return data_[j*rows_ - T(j) + i] = v; // Lower, use our notation | |||||
else return data_[i*rows_ - T(i) + j] = v; // Upper, use opposite index | |||||
} | |||||
else { | |||||
// In row major we use the upper triangle of the matrix | |||||
if (i<=j) return data_[i*cols_ - T(i) + j] = v; // Upper, use our notation | |||||
else return data_[j*cols_ - T(j) + i] = v; // Lower, use opposite index | |||||
} | |||||
} | |||||
else { | |||||
if constexpr (Order == MatrixOrder::COLMAJOR) | |||||
return data_[i + j*rows_] = v; | |||||
else | |||||
return data_[i*cols_ + j] = v; | |||||
} | |||||
} | |||||
// DataType operator()(IndexType i, IndexType j) { return get(i, j); } | |||||
/*! | |||||
* Return a proxy MatVal object with read and write capabilities. | |||||
* @param i The row number | |||||
* @param j The column number | |||||
* @return tHE MatVal object | |||||
*/ | |||||
MatVal<Matrix> operator()(IndexType i, IndexType j) noexcept { | |||||
return MatVal<Matrix>(this, get(i, j), i, j); | |||||
} | |||||
// a basic serial iterator support | |||||
DataType* data() noexcept { return data_; } | |||||
DataType* begin() noexcept { return data_; } | |||||
const DataType* begin() const noexcept { return data_; } | |||||
DataType* end() noexcept { return data_ + capacity(rows_, cols_); } | |||||
const DataType* end() const noexcept { return data_ + capacity(rows_, cols_); } | |||||
// IndexType begin_idx() noexcept { return 0; } | |||||
// IndexType end_idx() noexcept { return capacity(rows_, cols_); } | |||||
const DataType* data() const noexcept { return data_; } | |||||
const IndexType begin_idx() const noexcept { return 0; } | |||||
const IndexType end_idx() const noexcept { return capacity(rows_, cols_); } | |||||
//! @} | |||||
/*! | |||||
* \name Safe iteration API | |||||
* | |||||
* This api automates the iteration over the array based on | |||||
* MatrixType | |||||
*/ | |||||
//! @{ | |||||
template<typename F, typename... Args> | |||||
void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) { | |||||
for (IndexType it=begin ; it<end ; ++it) { | |||||
std::forward<F>(lambda)(std::forward<Args>(args)..., it); | |||||
} | |||||
} | |||||
//! @} | |||||
// | |||||
void swap(Matrix& src) noexcept { | |||||
std::swap(vector_storage_, src.vector_storage_); | |||||
std::swap(raw_storage_, src.raw_storage_); | |||||
std::swap(data_, src.data_); | |||||
std::swap(use_vector_, src.use_vector_); | |||||
std::swap(rows_, src.rows_); | |||||
std::swap(cols_, src.cols_); | |||||
} | |||||
private: | |||||
//! move helper | |||||
void moves(Matrix&& src) noexcept { | |||||
vector_storage_ = std::move(src.vector_storage_); | |||||
raw_storage_ = std::move(src.raw_storage_); | |||||
data_ = std::move(src.data_); | |||||
use_vector_ = std::move(src.use_vector_); | |||||
rows_ = std::move(src.rows_); | |||||
cols_ = std::move(src.cols_); | |||||
} | |||||
// Storage | |||||
std::vector<DataType> | |||||
vector_storage_; //!< Internal storage (if used). | |||||
DataType* raw_storage_; //!< External storage (if used). | |||||
DataType* data_; //!< Pointer to active storage. | |||||
bool use_vector_; //!< True if using vector storage, false for raw pointer. | |||||
IndexType rows_{}; //!< the virtual size of rows. | |||||
IndexType cols_{}; //!< the virtual size of columns. | |||||
}; | |||||
/** | |||||
* A simple sparse matrix specialization. | |||||
* | |||||
* We use CSC format and provide get/set functionalities for each (i,j) item | |||||
* on the matrix. We also provide a () overload using a proxy MatVal object. | |||||
* This way the user can: | |||||
* \code | |||||
* auto v = A(3,4); | |||||
* A(3, 4) = 7; | |||||
* \endcode | |||||
* | |||||
* We also provide getCol() and getRow() functions witch return a viewer/iterator to rows and | |||||
* columns of the matrix. In the case of a symmetric matrix instead of a row we return the | |||||
* equivalent column. This way we gain speed due to CSC format nature. | |||||
* | |||||
* @tparam DataType The type for values | |||||
* @tparam IndexType The type for indexes | |||||
* @tparam Type The Matrix type (FULL or SYMMETRIC) | |||||
*/ | |||||
template<typename DataType, typename IndexType, | |||||
MatrixOrder Order, | |||||
bool Symmetric> | |||||
struct Matrix<DataType, IndexType, MatrixType::SPARSE, Order, Symmetric> { | |||||
using dataType = DataType; //!< meta:export of underling data type | |||||
using indexType = IndexType; //!< meta:export of underling index type | |||||
static constexpr MatrixOrder matrixOrder = Order; //!< meta:export of array order | |||||
static constexpr MatrixType matrixType = MatrixType::SPARSE; //!< meta:export of array type | |||||
static constexpr bool symmetric = Symmetric; //!< meta:export symmetric flag | |||||
friend struct MatCol<Matrix>; | |||||
friend struct MatRow<Matrix>; | |||||
friend struct MatVal<Matrix>; | |||||
/*! | |||||
* \name Obj lifetime | |||||
*/ | |||||
//! @{ | |||||
//! Default ctor with optional memory allocations | |||||
Matrix(IndexType n=IndexType{}) noexcept: | |||||
values{}, | |||||
rows{}, | |||||
col_ptr((n)? n+1:2, IndexType{}), | |||||
N(n), | |||||
NNZ(0) { } | |||||
//! A ctor using csc array data | |||||
Matrix(IndexType n, IndexType nnz, const IndexType* row, const IndexType* col) noexcept: | |||||
values(nnz, 1), | |||||
rows(row, row+nnz), | |||||
col_ptr(col, col+n+1), | |||||
N(n), | |||||
NNZ(nnz) { } | |||||
//! ctor using csc array data with value array | |||||
Matrix(IndexType n, IndexType nnz, const DataType* v, const IndexType* row, const IndexType* col) noexcept: | |||||
values(v, v+nnz), | |||||
rows(row, row+nnz), | |||||
col_ptr(col, col+n+1), | |||||
N(n), | |||||
NNZ(nnz) { } | |||||
//! ctor vectors of row/col and default value for values array | |||||
Matrix(IndexType n, IndexType nnz, const DataType v, | |||||
const std::vector<IndexType>& row, const std::vector<IndexType>& col) noexcept: | |||||
values(nnz, v), | |||||
rows (row), | |||||
col_ptr(col), | |||||
N(n), | |||||
NNZ(nnz) { } | |||||
//! move ctor | |||||
Matrix(Matrix&& m) noexcept { moves(std::move(m)); } | |||||
//! move | |||||
Matrix& operator=(Matrix&& m) noexcept { moves(std::move(m)); return *this; } | |||||
Matrix(const Matrix& m) = delete; //!< make sure there are no copies | |||||
Matrix& operator=(const Matrix& m) = delete; //!< make sure there are no copies | |||||
//! @} | |||||
//! \name Data exposure | |||||
//! @{ | |||||
//! \return the dimension of the matrix | |||||
IndexType size() noexcept { return N; } | |||||
//! After construction size configuration tool | |||||
IndexType resize(IndexType n) { | |||||
col_ptr.resize(n+1); | |||||
return N = n; | |||||
} | |||||
//! \return the NNZ of the matrix | |||||
IndexType capacity() noexcept { return NNZ; } | |||||
//! After construction NNZ size configuration tool | |||||
IndexType capacity(IndexType nnz) noexcept { | |||||
values.reserve(nnz); | |||||
rows.reserve(nnz); | |||||
return NNZ; | |||||
} | |||||
// getters for row arrays of the struct (unused) | |||||
std::vector<DataType>& getValues() noexcept { return values; } | |||||
std::vector<IndexType>& getRows() noexcept { return rows; } | |||||
std::vector<IndexType>& getCols() noexcept { return col_ptr; } | |||||
/*! | |||||
* Return a proxy MatVal object with read and write capabilities. | |||||
* @param i The row number | |||||
* @param j The column number | |||||
* @return tHE MatVal object | |||||
*/ | |||||
MatVal<Matrix> operator()(IndexType i, IndexType j) noexcept { | |||||
return MatVal<Matrix>(this, get(i, j), i, j); | |||||
} | |||||
/*! | |||||
* A read item functionality using binary search to find the correct row | |||||
* | |||||
* @param i The row number | |||||
* @param j The column number | |||||
* @return The value of the item or DataType{} if is not present. | |||||
*/ | |||||
DataType get(IndexType i, IndexType j) noexcept { | |||||
IndexType idx; bool found; | |||||
std::tie(idx, found) =find_idx(rows, col_ptr[j], col_ptr[j+1], i); | |||||
return (found) ? values[idx] : 0; | |||||
} | |||||
/*! | |||||
* A write item functionality. | |||||
* | |||||
* First we search if the matrix has already a value in (i, j) position. | |||||
* If so we just change it to a new value. If not we add the item on the matrix. | |||||
* | |||||
* @note | |||||
* When change a value, we don't increase the NNZ value of the struct. We expect the user has already | |||||
* change the NNZ value to the right one using @see capacity() function. When adding a value we | |||||
* increase the NNZ. | |||||
* | |||||
* @param i The row number | |||||
* @param j The column number | |||||
* @return The new value of the item . | |||||
*/ | |||||
DataType set(DataType v, IndexType i, IndexType j) { | |||||
IndexType idx; bool found; | |||||
std::tie(idx, found) = find_idx(rows, col_ptr[j], col_ptr[j+1], i); | |||||
if (found) | |||||
return values[idx] = v; // we don't change NNZ even if we write "0" | |||||
else { | |||||
values.insert(values.begin()+idx, v); | |||||
rows.insert(rows.begin()+idx, i); | |||||
std::transform(col_ptr.begin()+j+1, col_ptr.end(), col_ptr.begin()+j+1, [](IndexType it) { | |||||
return ++it; | |||||
}); | |||||
++NNZ; // we increase the NNZ even if we write "0" | |||||
return v; | |||||
} | |||||
} | |||||
/*! | |||||
* Get a view of a CSC column | |||||
* @param j The column to get | |||||
* @return The MatCol object @see MatCol | |||||
*/ | |||||
MatCol<Matrix> getCol(IndexType j) noexcept { | |||||
return MatCol<Matrix>(this, col_ptr[j], col_ptr[j+1]); | |||||
} | |||||
/*! | |||||
* Get a view of a CSC row | |||||
* | |||||
* In case of a SYMMETRIC matrix we can return a column instead. | |||||
* | |||||
* @param j The row to get | |||||
* @return On symmetric matrix MatCol otherwise a MatRow | |||||
*/ | |||||
MatCol<Matrix> getRow(IndexType i) noexcept { | |||||
if constexpr (Symmetric) | |||||
return getCol(i); | |||||
else | |||||
return MatRow<Matrix>(this, i); | |||||
} | |||||
// values only iterator support | |||||
DataType* begin() noexcept { return values.begin(); } | |||||
DataType* end() noexcept { return values.end(); } | |||||
//! @} | |||||
//! A small iteration helper | |||||
template<typename F, typename... Args> | |||||
void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) { | |||||
for (IndexType it=begin ; it<end ; ++it) { | |||||
std::forward<F>(lambda)(std::forward<Args>(args)..., it); | |||||
} | |||||
} | |||||
private: | |||||
/*! | |||||
* A small binary search implementation using index for begin-end instead of iterators. | |||||
* | |||||
* \param v Reference to vector to search | |||||
* \param begin The vector's index to begin | |||||
* \param end The vector's index to end | |||||
* \param match What to search | |||||
* \return An <index, status> pair. | |||||
* index is the index of the item or end if not found | |||||
* status is true if found, false otherwise | |||||
*/ | |||||
std::pair<IndexType, bool> find_idx(const std::vector<IndexType>& v, IndexType begin, IndexType end, IndexType match) { | |||||
if (v.capacity() != 0 && begin < end) { | |||||
IndexType b = begin, e = end-1; | |||||
while (b <= e) { | |||||
IndexType m = (b+e)/2; | |||||
if (v[m] == match) return std::make_pair(m, true); | |||||
else if (b >= e) return std::make_pair(end, false); | |||||
else { | |||||
if (v[m] < match) b = m +1; | |||||
else e = m -1; | |||||
} | |||||
} | |||||
} | |||||
return std::make_pair(end, false); | |||||
} | |||||
// move helper | |||||
void moves(Matrix&& src) noexcept { | |||||
values = std::move(src.values); | |||||
rows = std::move(src.rows); | |||||
col_ptr = std::move(src.col_ptr); | |||||
N = std::move(src.N); // redundant for primitives | |||||
NNZ = std::move(src.NNZ); // | |||||
} | |||||
//! \name Data | |||||
//! @{ | |||||
std::vector<DataType> values {}; //!< vector to store the values of the matrix | |||||
std::vector<IndexType> rows{}; //!< vector to store the row information | |||||
std::vector<IndexType> col_ptr{1,0}; //!< vector to store the column pointers | |||||
IndexType N{0}; //!< The dimension of the matrix (square) | |||||
IndexType NNZ{0}; //!< The NNZ (capacity of the matrix) | |||||
//! @} | |||||
}; | |||||
/*! | |||||
* A view/iterator hybrid object for Matrix columns. | |||||
* | |||||
* This object provides access to a column of a Matrix. The public functionalities | |||||
* allow data access using indexes instead of iterators. We prefer indexes over iterators | |||||
* because we can apply the same index to different inner vector of Matrix without conversion. | |||||
* | |||||
* @tparam DataType | |||||
* @tparam IndexType | |||||
*/ | |||||
template<typename MatrixType> | |||||
struct MatCol { | |||||
using owner_t = MatrixType; | |||||
using DataType = typename MatrixType::dataType; | |||||
using IndexType = typename MatrixType::indexType; | |||||
/*! | |||||
* ctor using column pointers for begin-end. own is pointer to Matrix. | |||||
*/ | |||||
MatCol(owner_t* own, const IndexType begin, const IndexType end) noexcept : | |||||
owner_(own), index_(begin), begin_(begin), end_(end) { | |||||
vindex_ = vIndexCalc(index_); | |||||
} | |||||
MatCol() = default; | |||||
MatCol(const MatCol&) = delete; //!< make sure there are no copies | |||||
MatCol& operator=(const MatCol&)= delete; //!< make sure there are no copies | |||||
MatCol(MatCol&&) = default; | |||||
MatCol& operator=(MatCol&&) = default; | |||||
//! a simple dereference operator, like an iterator | |||||
DataType operator* () { | |||||
return get(); | |||||
} | |||||
//! Increment operator acts on index(), like an iterator | |||||
MatCol& operator++ () { advance(); return *this; } | |||||
MatCol& operator++ (int) { MatCol& p = *this; advance(); return p; } | |||||
//! () operator acts as member access (like a view) | |||||
DataType operator()(IndexType x) { | |||||
return (x == index())? get() : DataType{}; | |||||
} | |||||
//! = operator acts as member assignment (like a view) | |||||
DataType operator= (DataType v) { return owner_->values[index_] = v; } | |||||
// iterator like handlers | |||||
// these return a virtual index value based on the items position on the full matrix | |||||
// but the move of the index is just a ++ away. | |||||
IndexType index() noexcept { return vindex_; } | |||||
const IndexType index() const noexcept { return vindex_; } | |||||
IndexType begin() noexcept { return vIndexCalc(begin_); } | |||||
const IndexType begin() const noexcept { return vIndexCalc(begin_); } | |||||
IndexType end() noexcept { return owner_->N; } | |||||
const IndexType end() const noexcept { return owner_->N; } | |||||
/*! | |||||
* Multiplication operator | |||||
* | |||||
* We follow only the non-zero values and multiply only the common indexes. | |||||
* | |||||
* @tparam C Universal reference for the type right half site column | |||||
* | |||||
* @param c The right hand site matrix | |||||
* @return The value of the inner product of two vectors | |||||
* @note The time complexity is \$ O(nnz1+nnz2) \$. | |||||
* Where the nnz is the max NNZ elements of the column of the matrix | |||||
*/ | |||||
template <typename C> | |||||
DataType operator* (C&& c) { | |||||
static_assert(std::is_same<remove_cvref_t<C>, MatCol<MatrixType>>(), ""); | |||||
DataType v{}; | |||||
while (index() != end() && c.index() != c.end()) { | |||||
if (index() < c.index()) advance(); // advance me | |||||
else if (index() > c.index()) ++c; // advance other | |||||
else { //index() == c.index() | |||||
v += get() * *c; // multiply and advance both | |||||
++c; | |||||
advance(); | |||||
} | |||||
} | |||||
return v; | |||||
} | |||||
private: | |||||
//! small tool to increase the index pointers to Matrix | |||||
void advance() noexcept { | |||||
++index_; | |||||
vindex_ = vIndexCalc(index_); | |||||
} | |||||
//! tool to translate between col_ptr indexes and Matrix "virtual" full matrix indexes | |||||
IndexType vIndexCalc(IndexType idx) { | |||||
return (idx < end_) ? owner_->rows[idx] : end(); | |||||
} | |||||
//! small get tool | |||||
DataType get() { return owner_->values[index_]; } | |||||
owner_t* owner_ {nullptr}; //!< Pointer to owner Matrix. MatCol is just a view | |||||
IndexType vindex_ {IndexType{}}; //!< Virtual index of full matrix | |||||
IndexType index_ {IndexType{}}; //!< index to Matrix::rows | |||||
IndexType begin_ {IndexType{}}; //!< beginning index of the column in Matrix::rows | |||||
IndexType end_ {IndexType{}}; //!< ending index of the column in Matrix::rows | |||||
}; | |||||
/*! | |||||
* A view/iterator hybrid object for Matrix rows. | |||||
* | |||||
* This object provides access to a column of a Matrix. The public functionalities | |||||
* allow data access using indexes instead of iterators. We prefer indexes over iterators | |||||
* because we can apply the same index to different inner vector of Matrix without conversion. | |||||
* | |||||
* @tparam DataType | |||||
* @tparam IndexType | |||||
*/ | |||||
template<typename MatrixType> | |||||
struct MatRow { | |||||
using owner_t = MatrixType; | |||||
using DataType = typename MatrixType::dataType; | |||||
using IndexType = typename MatrixType::indexType; | |||||
/*! | |||||
* ctor using virtual full matrix row index. own is pointer to Matrix. | |||||
*/ | |||||
MatRow(owner_t* own, const IndexType row) noexcept : | |||||
owner_(own), vindex_(IndexType{}), row_(row), index_(IndexType{}), | |||||
begin_(IndexType{}), end_(owner_->NNZ) { | |||||
// place begin | |||||
while(begin_ != end_ && owner_->rows[begin_] != row_) | |||||
++begin_; | |||||
// place index_ and vindex_ | |||||
if (owner_->rows[index_] != row_) | |||||
advance(); | |||||
} | |||||
MatRow() = default; | |||||
MatRow(const MatRow&) = delete; //!< make sure there are no copies | |||||
MatRow& operator=(const MatRow&)= delete; //!< make sure there are no copies | |||||
MatRow(MatRow&&) = default; | |||||
MatRow& operator=(MatRow&&) = default; | |||||
//! a simple dereference operator, like an iterator | |||||
DataType operator* () { | |||||
return get(); | |||||
} | |||||
//! Increment operator acts on index(), like an iterator | |||||
//! here the increment is a O(N) process. | |||||
MatRow& operator++ () { advance(); return *this; } | |||||
MatRow& operator++ (int) { MatRow& p = *this; advance(); return p; } | |||||
//! () operator acts as member access (like a view) | |||||
DataType operator()(IndexType x) { | |||||
return (x == index())? get() : DataType{}; | |||||
} | |||||
//! = operator acts as member assignment (like a view) | |||||
DataType operator= (DataType v) { return owner_->values[index_] = v; } | |||||
// iterator like handlers | |||||
// these return a virtual index value based on the items position on the full matrix | |||||
// but the move of the index is just a ++ away. | |||||
IndexType index() noexcept { return vindex_; } | |||||
const IndexType index() const noexcept { return vindex_; } | |||||
IndexType begin() noexcept { return vIndexCalc(begin_); } | |||||
const IndexType begin() const noexcept { return vIndexCalc(begin_); } | |||||
IndexType end() noexcept { return owner_->N; } | |||||
const IndexType end() const noexcept { return owner_->N; } | |||||
/*! | |||||
* Multiplication operator | |||||
* | |||||
* We follow only the non-zero values and multiply only the common indexes. | |||||
* | |||||
* @tparam C Universal reference for the type right half site column | |||||
* | |||||
* @param c The right hand site matrix | |||||
* @return The value of the inner product of two vectors | |||||
* @note The time complexity is \$ O(N+nnz2) \$ and way heavier the ColxCol multiplication. | |||||
* Where the nnz is the max NNZ elements of the column of the matrix | |||||
*/ | |||||
template <typename C> | |||||
DataType operator* (C&& c) { | |||||
static_assert(std::is_same<remove_cvref_t<C>, MatCol<MatrixType>>(), ""); | |||||
DataType v{}; | |||||
while (index() != end() && c.index() != c.end()) { | |||||
if (index() < c.index()) advance(); // advance me | |||||
else if (index() > c.index()) ++c; // advance other | |||||
else { //index() == c.index() | |||||
v += get() * *c; // multiply and advance both | |||||
++c; | |||||
advance(); | |||||
} | |||||
} | |||||
return v; | |||||
} | |||||
private: | |||||
//! small tool to increase the index pointers to Matrix matrix | |||||
//! We have to search the entire rows vector in Matrix to find the next | |||||
//! virtual row position. | |||||
//! time complexity O(N) | |||||
void advance() noexcept { | |||||
do | |||||
++index_; | |||||
while(index_ != end_ && owner_->rows[index_] != row_); | |||||
vindex_ = vIndexCalc(index_); | |||||
} | |||||
//! tool to translate between col_ptr indexes and Matrix "virtual" full matrix indexes | |||||
IndexType vIndexCalc(IndexType idx) { | |||||
for(IndexType i =0 ; i<(owner_->N+1) ; ++i) | |||||
if (idx < owner_->col_ptr[i]) | |||||
return i-1; | |||||
return end(); | |||||
} | |||||
//! small get tool | |||||
DataType get() { return owner_->values[index_]; } | |||||
owner_t* owner_ {nullptr}; //!< Pointer to owner Matrix. MatCol is just a view | |||||
IndexType vindex_ {IndexType{}}; //!< Virtual index of full matrix | |||||
IndexType row_ {IndexType{}}; //!< The virtual full matrix row of the object | |||||
IndexType index_ {IndexType{}}; //!< index to Matrix::rows | |||||
IndexType begin_ {IndexType{}}; //!< beginning index of the column in Matrix::rows | |||||
IndexType end_ {IndexType{}}; //!< ending index of the column in Matrix::rows | |||||
}; | |||||
/*! | |||||
* A proxy Matrix value object/view. | |||||
* | |||||
* This object acts as proxy to provide read/write access to an Matrix item. | |||||
* | |||||
* @tparam DataType The type of the values of the Matrix matrix | |||||
* @tparam IndexType The type of the indexes of the Matrix matrix | |||||
*/ | |||||
template<typename MatrixType> | |||||
struct MatVal { | |||||
using owner_t = MatrixType; | |||||
using DataType = typename MatrixType::dataType; | |||||
using IndexType = typename MatrixType::indexType; | |||||
//!< ctor using all value-row-column data, plus a pointer to owner Matrix object | |||||
MatVal(owner_t* own, DataType v, IndexType i, IndexType j) : | |||||
owner_(own), v_(v), i_(i), j_(j) { } | |||||
MatVal() = default; | |||||
MatVal(const MatVal&) = delete; //!< make sure there are no copies | |||||
MatVal& operator=(const MatVal&) = delete; //!< make sure there are no copies | |||||
MatVal(MatVal&&) = default; | |||||
MatVal& operator=(MatVal&&) = default; | |||||
//! Operator to return the DataType value implicitly | |||||
operator DataType() { return v_; } | |||||
//! Operator to write back to owner the assigned value | |||||
//! for ex: A(2,3) = 5; | |||||
MatVal& operator=(DataType v) { | |||||
v_ = v; | |||||
owner_->set(v_, i_, j_); | |||||
return *this; | |||||
} | |||||
private: | |||||
owner_t* owner_{nullptr}; //!< Pointer to owner Matrix. MatVal is just a view. | |||||
DataType v_{DataType{}}; //!< The value of the row-column pair (for speed) | |||||
IndexType i_{IndexType{}}; //!< The row | |||||
IndexType j_{IndexType{}}; //!< the column | |||||
}; | |||||
} // namespace mtx | |||||
#endif /* MATRIX_HPP_ */ |
@@ -1,5 +1,5 @@ | |||||
/** | /** | ||||
* \file utils.hpp | |||||
* \file | |||||
* \brief Utilities header | * \brief Utilities header | ||||
* | * | ||||
* \author | * \author | ||||
@@ -9,6 +9,7 @@ | |||||
#ifndef UTILS_HPP_ | #ifndef UTILS_HPP_ | ||||
#define UTILS_HPP_ | #define UTILS_HPP_ | ||||
#include <vector> | |||||
#include <iostream> | #include <iostream> | ||||
#include <chrono> | #include <chrono> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
@@ -17,6 +18,18 @@ | |||||
//#include "matrix.hpp" | //#include "matrix.hpp" | ||||
#include "config.h" | #include "config.h" | ||||
template <typename T> struct MPI_TypeMapper; | |||||
// Specializations for supported types | |||||
template <> struct MPI_TypeMapper<char> { static MPI_Datatype getType() { return MPI_CHAR; } }; | |||||
template <> struct MPI_TypeMapper<unsigned char> { static MPI_Datatype getType() { return MPI_UNSIGNED_CHAR; } }; | |||||
template <> struct MPI_TypeMapper<short> { static MPI_Datatype getType() { return MPI_SHORT; } }; | |||||
template <> struct MPI_TypeMapper<int> { static MPI_Datatype getType() { return MPI_INT; } }; | |||||
template <> struct MPI_TypeMapper<long> { static MPI_Datatype getType() { return MPI_LONG; } }; | |||||
template <> struct MPI_TypeMapper<long long> { static MPI_Datatype getType() { return MPI_LONG_LONG; } }; | |||||
template <> struct MPI_TypeMapper<unsigned short>{ static MPI_Datatype getType() { return MPI_UNSIGNED_SHORT; } }; | |||||
template <> struct MPI_TypeMapper<unsigned long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG; } }; | |||||
template <> struct MPI_TypeMapper<unsigned long long> { static MPI_Datatype getType() { return MPI_UNSIGNED_LONG_LONG; } }; | |||||
template<typename TID = int> | template<typename TID = int> | ||||
struct MPI_t { | struct MPI_t { | ||||
@@ -28,8 +41,10 @@ struct MPI_t { | |||||
// Get the number of processes | // Get the number of processes | ||||
int size_value, rank_value; | int size_value, rank_value; | ||||
size_ = static_cast<ID_t>(MPI_Comm_size(MPI_COMM_WORLD, &size_value)); | |||||
rank_ = static_cast<ID_t>(MPI_Comm_rank(MPI_COMM_WORLD, &rank_value)); | |||||
MPI_Comm_size(MPI_COMM_WORLD, &size_value); | |||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank_value); | |||||
size_ = static_cast<ID_t>(size_value); | |||||
rank_ = static_cast<ID_t>(rank_value); | |||||
// Get the name of the processor | // Get the name of the processor | ||||
char processor_name[MPI_MAX_PROCESSOR_NAME]; | char processor_name[MPI_MAX_PROCESSOR_NAME]; | ||||
@@ -43,18 +58,24 @@ struct MPI_t { | |||||
MPI_Finalize(); | MPI_Finalize(); | ||||
} | } | ||||
bool exchange(ID_t partner, const void *send_data, void *recv_data, int data_count, MPI_Datatype datatype) { | |||||
bool ret = true; | |||||
template<typename T> | |||||
void exchange(ID_t partner, const std::vector<T>& send_data, std::vector<T>& recv_data, int tag) { | |||||
using namespace std::string_literals; | |||||
MPI_Status status; | MPI_Status status; | ||||
MPI_Sendrecv( | |||||
send_data, data_count, datatype, partner, 0, | |||||
recv_data, data_count, datatype, partner, 0, | |||||
MPI_Datatype datatype = MPI_TypeMapper<T>::getType(); | |||||
int send_count = static_cast<int>(send_data.size()); | |||||
int err = MPI_Sendrecv( | |||||
send_data.data(), send_count, datatype, partner, tag, | |||||
recv_data.data(), send_count, datatype, partner, tag, | |||||
MPI_COMM_WORLD, &status | MPI_COMM_WORLD, &status | ||||
); | ); | ||||
if (status.MPI_ERROR != MPI_SUCCESS) | |||||
ret = false; | |||||
return ret; | |||||
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'); | |||||
} | |||||
} | } | ||||
// Accessors | // Accessors | ||||
@@ -71,12 +92,84 @@ private: | |||||
extern MPI_t<> mpi; | extern MPI_t<> mpi; | ||||
using mpi_id_t = MPI_t<>::ID_t; | using mpi_id_t = MPI_t<>::ID_t; | ||||
template <typename Value_t> | |||||
struct ShadowedVec_t { | |||||
// STL requirements | |||||
using value_type = Value_t; | |||||
using iterator = typename std::vector<Value_t>::iterator; | |||||
using const_iterator = typename std::vector<Value_t>::const_iterator; | |||||
using size_type = typename std::vector<Value_t>::size_type; | |||||
// Dispatch to active vector | |||||
Value_t& operator[](size_type index) { return getActive()[index]; } | |||||
const Value_t& operator[](size_type index) const { return getActive()[index]; } | |||||
Value_t& at(size_type index) { return getActive().at(index); } | |||||
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(); } | |||||
Value_t& front() { return getActive().front(); } | |||||
const Value_t& front() const { return getActive().front(); } | |||||
Value_t& back() { return getActive().back(); } | |||||
const Value_t& back() const { return getActive().back(); } | |||||
iterator begin() { return getActive().begin(); } | |||||
const_iterator begin() const { return getActive().begin(); } | |||||
iterator end() { return getActive().end(); } | |||||
const_iterator end() const { return getActive().end(); } | |||||
size_type size() const { return getActive().size(); } | |||||
void resize(size_t new_size) { | |||||
North.resize(new_size); | |||||
South.resize(new_size); | |||||
} | |||||
void reserve(size_t new_capacity) { | |||||
North.reserve(new_capacity); | |||||
South.reserve(new_capacity); | |||||
} | |||||
[[nodiscard]] size_t capacity() const { return getActive().capacity(); } | |||||
[[nodiscard]] bool empty() const { return getActive().empty(); } | |||||
void clear() { getActive().clear(); } | |||||
void swap(std::vector<Value_t>& other) { getActive().swap(other); } | |||||
// Switching vectors | |||||
void switch_active() { active = (active == north) ? south : north; } | |||||
// Accessors | |||||
const std::vector<Value_t>& getNorth() const { return North; } | |||||
const std::vector<Value_t>& getSouth() const { return South; } | |||||
std::vector<Value_t>& getActive() { | |||||
return (active == north) ? North : South; | |||||
} | |||||
const std::vector<Value_t>& getActive() const { | |||||
return (active == north) ? North : South; | |||||
} | |||||
std::vector<Value_t>& getShadow() { | |||||
return (active == north) ? South : North; | |||||
} | |||||
const std::vector<Value_t>& getShadow() const { | |||||
return (active == north) ? South : North; | |||||
} | |||||
private: | |||||
enum { north, south } active{north}; | |||||
std::vector<Value_t> North{}; | |||||
std::vector<Value_t> South{}; | |||||
}; | |||||
using distBuffer_t = ShadowedVec_t<distValue_t>; | |||||
extern distBuffer_t Data; | |||||
/*! | /*! | ||||
* A Logger for entire program. | * A Logger for entire program. | ||||
*/ | */ | ||||
struct Log { | struct Log { | ||||
struct Endl { | |||||
} endl; //!< a tag object to to use it as a new line request. | |||||
struct Endl {} endl; //!< a tag object to to use it as a new line request. | |||||
//! We provide logging via << operator | //! We provide logging via << operator | ||||
template<typename T> | template<typename T> | ||||
@@ -1,212 +0,0 @@ | |||||
/*! | |||||
* \file | |||||
* \brief Distributed bitonic implementation. | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#include <vector> | |||||
#include <algorithm> | |||||
#include <cmath> | |||||
#if !defined DEBUG | |||||
#define NDEBUG | |||||
#endif | |||||
#include <cassert> | |||||
#include "distbitonic.hpp" | |||||
/*! | |||||
* Returns the ascending or descending configuration of the node's sequence based on | |||||
* the current node (MPI process) and the depth of the sorting network | |||||
* | |||||
* @param node The current node (MPI process) | |||||
* @return True if we need ascending configuration, false otherwise | |||||
*/ | |||||
template <> | |||||
bool ascending<SortMode::Bubbletonic>(mpi_id_t node, [[maybe_unused]] size_t depth) noexcept { | |||||
return (node % 2) == 0; | |||||
} | |||||
/*! | |||||
* Returns the ascending or descending configuration of the node's sequence based on | |||||
* the current node (MPI process) and the depth of the sorting network | |||||
* | |||||
* @param node The current node (MPI process) | |||||
* @param depth The total depth of the sorting network (same for each step for a given network) | |||||
* | |||||
* @return True if we need ascending configuration, false otherwise | |||||
*/ | |||||
template <> | |||||
bool ascending<SortMode::Bitonic>(mpi_id_t node, size_t depth) noexcept { | |||||
return !(node & (1 << depth)); | |||||
} | |||||
/*! | |||||
* Returns the node's partner for data exchange during the sorting network iterations | |||||
* of Bubbletonic | |||||
* | |||||
* @param node The current node | |||||
* @param step The step of the sorting network | |||||
* @return The node id of the partner for data exchange | |||||
*/ | |||||
template <> | |||||
mpi_id_t partner<SortMode::Bubbletonic>(mpi_id_t node, size_t step) noexcept { | |||||
// return (node % 2 == step % 2) ? node + 1 : node - 1; | |||||
return (((node+step) % 2) == 0) ? node + 1 : node - 1; | |||||
} | |||||
/*! | |||||
* Returns the node's partner for data exchange during the sorting network iterations | |||||
* of Bitonic | |||||
* | |||||
* @param node The current node | |||||
* @param step The step of the sorting network | |||||
* @return The node id of the partner for data exchange | |||||
*/ | |||||
template <> | |||||
mpi_id_t partner<SortMode::Bitonic>(mpi_id_t node, size_t step) noexcept { | |||||
return (node ^ (1 << step)); | |||||
} | |||||
/*! | |||||
* Predicate to check if a node keeps the small numbers during the bubbletonic sort network exchange. | |||||
* | |||||
* @param node The node for which we check | |||||
* @param partner The partner of the data exchange | |||||
* @return True if the node should keep the small values, false otherwise | |||||
*/ | |||||
template <> | |||||
bool keepSmall<SortMode::Bubbletonic>(mpi_id_t node, mpi_id_t partner, [[maybe_unused]] size_t depth) noexcept { | |||||
assert(node != partner); | |||||
return (node < partner); | |||||
} | |||||
/*! | |||||
* Predicate to check if a node keeps the small numbers during the bitonic sort network exchange. | |||||
* | |||||
* @param node The node for which we check | |||||
* @param partner The partner of the data exchange | |||||
* @param depth The total depth of the sorting network (same for each step for a given network) | |||||
* @return True if the node should keep the small values, false otherwise | |||||
*/ | |||||
template <> | |||||
bool keepSmall<SortMode::Bitonic>(mpi_id_t node, mpi_id_t partner, size_t depth) noexcept { | |||||
assert(node != partner); | |||||
return ascending<SortMode::Bitonic>(node, depth) == (node < partner); | |||||
} | |||||
/*! | |||||
* Predicate to check if the node is active in the current iteration of the bubbletonic | |||||
* sort exchange. | |||||
* | |||||
* @param node The node to check | |||||
* @param nodes The total number of nodes | |||||
* @return True if the node is active, false otherwise | |||||
*/ | |||||
bool isActive(mpi_id_t node, mpi_id_t nodes) noexcept { | |||||
return (node >= 0) && (node < (nodes-1)); | |||||
} | |||||
void exchange(mpi_id_t node, mpi_id_t partner) { | |||||
assert(node != partner); | |||||
} | |||||
void minmax(AllData_t& data, mpi_id_t node, mpi_id_t partner, bool keepsmall) { | |||||
for (size_t i = 0; i < data[node].size(); ++i) { | |||||
if (keepsmall && data[node][i] > data[partner][i]) | |||||
std::swap(data[node][i], data[partner][i]); | |||||
if (!keepsmall && data[node][i] < data[partner][i]) | |||||
std::swap(data[node][i], data[partner][i]); | |||||
} | |||||
} | |||||
void bubbletonic_network(AllData_t& data, mpi_id_t nodes, size_t depth) { | |||||
for (mpi_id_t node = 0 ; node < nodes ; ++node) { // Currently we do all nodes here! | |||||
auto part = partner<SortMode::Bubbletonic>(node, depth); | |||||
auto ks = keepSmall<SortMode::Bubbletonic>(node, part, 0); | |||||
if (isActive(node, nodes) && node < part) { | |||||
exchange(node, part); | |||||
minmax(data, node, part, ks); | |||||
// elbow-sort here | |||||
if (ascending<SortMode::Bubbletonic>(node, 0)) | |||||
std::sort(data[node].begin(), data[node].end(), std::less<>()); | |||||
else | |||||
std::sort(data[node].begin(), data[node].end(), std::greater<>()); | |||||
if (ascending<SortMode::Bubbletonic>(part, 0)) | |||||
std::sort(data[part].begin(), data[part].end(), std::less<>()); | |||||
else | |||||
std::sort(data[part].begin(), data[part].end(), std::greater<>()); | |||||
} | |||||
} | |||||
} | |||||
void distBubbletonic(mpi_id_t P, AllData_t& data) { | |||||
for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! | |||||
// Initially sort to create the half part of a bitonic | |||||
if (ascending<SortMode::Bubbletonic>(node, 0)) | |||||
std::sort(data[node].begin(), data[node].end(), std::less<>()); | |||||
else | |||||
std::sort(data[node].begin(), data[node].end(), std::greater<>()); | |||||
} | |||||
for (size_t depth = 0; depth < P-1; ++depth) { | |||||
bubbletonic_network(data, P, depth); | |||||
} | |||||
// Invert the descending ones | |||||
for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! | |||||
if (!ascending<SortMode::Bubbletonic>(node, 0)) | |||||
std::sort(data[node].begin(), data[node].end(), std::less<>()); | |||||
} | |||||
} | |||||
void bitonic_network(AllData_t& data, mpi_id_t nodes, size_t depth) { | |||||
for (size_t step = depth; step > 0;) { | |||||
--step; | |||||
for (mpi_id_t node = 0; node < nodes; ++node) { // Currently we do all nodes here! | |||||
auto part = partner<SortMode::Bitonic>(node, step); | |||||
auto ks = keepSmall<SortMode::Bitonic>(node, part, depth); | |||||
if (node < part) { | |||||
exchange(node, part); | |||||
minmax(data, node, part, ks); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void distBitonic(mpi_id_t P, AllData_t& data) { | |||||
auto p = static_cast<uint32_t>(std::log2(P)); | |||||
for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! | |||||
// Initially sort to create the half part of a bitonic | |||||
if (ascending<SortMode::Bitonic>(node, 0)) | |||||
std::sort(data[node].begin(), data[node].end(), std::less<>()); | |||||
else | |||||
std::sort(data[node].begin(), data[node].end(), std::greater<>()); | |||||
} | |||||
// Run through sort network using elbow-sort | |||||
for (size_t depth = 1; depth <= p; ++depth) { | |||||
bitonic_network(data, P, depth); | |||||
for (mpi_id_t node = 0 ; node < P ; ++node) { // Currently we do all nodes here! | |||||
// elbow-sort here | |||||
if (ascending<SortMode::Bitonic>(node, depth)) | |||||
std::sort(data[node].begin(), data[node].end(), std::less<>()); | |||||
else | |||||
std::sort(data[node].begin(), data[node].end(), std::greater<>()); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
/*! | |||||
* \file | |||||
* \brief Distributed sort implementation. | |||||
* | |||||
* \author | |||||
* Christos Choutouridis AEM:8997 | |||||
* <cchoutou@ece.auth.gr> | |||||
*/ | |||||
#if !defined DEBUG | |||||
#define NDEBUG | |||||
#endif | |||||
#include <cassert> | |||||
#include "utils.hpp" | |||||
#include "distsort.hpp" | |||||
/*! | |||||
* Predicate to check if the node is active in the current iteration of the bubbletonic | |||||
* sort exchange. | |||||
* | |||||
* @param node The node to check | |||||
* @param nodes The total number of nodes | |||||
* @return True if the node is active, false otherwise | |||||
*/ | |||||
bool isActive(mpi_id_t node, size_t nodes) noexcept { | |||||
assert(nodes > 0); | |||||
return (node >= 0) && (node < (nodes-1)); | |||||
} | |||||
@@ -9,15 +9,19 @@ | |||||
#include <exception> | #include <exception> | ||||
#include <iostream> | #include <iostream> | ||||
#include <algorithm> | |||||
#include "utils.hpp" | #include "utils.hpp" | ||||
#include "config.h" | #include "config.h" | ||||
#include "distsort.hpp" | |||||
// Global session data | // Global session data | ||||
session_t session; | |||||
MPI_t<> mpi; | |||||
session_t session; | |||||
MPI_t<> mpi; | |||||
distBuffer_t Data; | |||||
Log logger; | |||||
Timing timer; | |||||
/*! | /*! | ||||
* A small command line argument parser | * A small command line argument parser | ||||
@@ -30,29 +34,46 @@ bool get_options(int argc, char* argv[]){ | |||||
for (int i=1 ; i<argc ; ++i) { | for (int i=1 ; i<argc ; ++i) { | ||||
std::string arg(argv[i]); // get current argument | std::string arg(argv[i]); // get current argument | ||||
if (arg == "-x" || arg == "--xxxxx") { | |||||
if (i+2 < argc) { | |||||
// session.corpusMtxFile = std::string(argv[++i]); | |||||
// session.corpusDataSet = std::string(argv[++i]); | |||||
if (arg == "-q" || arg == "--array-size") { | |||||
if (i+1 < argc) { | |||||
session.arraySize = 1 << atoi(argv[++i]); | |||||
} | } | ||||
else | |||||
else { | |||||
status = false; | status = false; | ||||
} | |||||
} | } | ||||
else if (arg == "-v" || arg == "--verbose") | |||||
else if (arg == "--ndebug") { | |||||
session.ndebug = true; | |||||
} | |||||
else if (arg == "-t" || arg == "--timing") { | |||||
session.timing = true; | |||||
} | |||||
else if (arg == "-v" || arg == "--verbose") { | |||||
session.verbose = true; | session.verbose = true; | ||||
} | |||||
else if (arg == "-h" || arg == "--help") { | else if (arg == "-h" || arg == "--help") { | ||||
std::cout << "distBitonic - A distributed bitonic sort\n\n"; | |||||
std::cout << "distBitonic -x <> [-v]\n"; | |||||
std::cout << "distbitonic/distbubbletonic - A distributed bitonic sort\n\n"; | |||||
std::cout << "distbitonic -q <> [--ndebug] [-v]\n"; | |||||
std::cout << "distbitonic -h\n"; | |||||
std::cout << "distbubbletonic -q <> [--ndebug] [-v]\n"; | |||||
std::cout << "distbubbletonic -h\n"; | |||||
std::cout << '\n'; | std::cout << '\n'; | ||||
std::cout << "Options:\n\n"; | std::cout << "Options:\n\n"; | ||||
std::cout << " -q | --array-size <size>\n"; | |||||
std::cout << " Selects the array size according to size = 2^q\n\n"; | |||||
std::cout << " --ndebug\n"; | |||||
std::cout << " Skip debug breakpoint when on debug build.\n\n"; | |||||
std::cout << " -t | --timing\n"; | |||||
std::cout << " Request timing measurements output to stdout.\n\n"; | |||||
std::cout << " -v | --verbose\n"; | std::cout << " -v | --verbose\n"; | ||||
std::cout << " Request a more verbose output to stdout.\n\n"; | std::cout << " Request a more verbose output to stdout.\n\n"; | ||||
std::cout << " -h | --help\n"; | std::cout << " -h | --help\n"; | ||||
std::cout << " Prints this and exit.\n\n"; | std::cout << " Prints this and exit.\n\n"; | ||||
std::cout << "Examples:\n\n"; | std::cout << "Examples:\n\n"; | ||||
std::cout << " ...Example case...:\n"; | |||||
std::cout << " > distBitonic -x <xxxxx> \n\n"; | |||||
std::cout << " mpirun -np 4 distbitonic -q 24\n"; | |||||
std::cout << " Runs distbitonic in 4 MPI processes with 2^24 array points each\n\n"; | |||||
std::cout << " mpirun -np 16 distbubbletonic -q 20\n"; | |||||
std::cout << " Runs distbubbletonic in 16 MPI processes with 2^20 array points each\n\n"; | |||||
exit(0); | exit(0); | ||||
} | } | ||||
@@ -66,7 +87,6 @@ bool get_options(int argc, char* argv[]){ | |||||
} | } | ||||
#if !defined TESTING | #if !defined TESTING | ||||
int main(int argc, char* argv[]) try { | int main(int argc, char* argv[]) try { | ||||
// Initialize MPI environment | // Initialize MPI environment | ||||
@@ -76,34 +96,50 @@ int main(int argc, char* argv[]) try { | |||||
if (!get_options(argc, argv)) | if (!get_options(argc, argv)) | ||||
exit(1); | exit(1); | ||||
#if defined DEBUG | |||||
/* | |||||
* In case of a debug build we will wait here until sleep_wait | |||||
* will reset via debugger. In order to do that the user must attach | |||||
* debugger to all processes. For example: | |||||
* $> mpirun -np 2 ./<program path> | |||||
* $> ps aux | grep <program> | |||||
* $> gdb <program> <PID1> | |||||
* $> gdb <program> <PID2> | |||||
*/ | |||||
#if defined TESTING | |||||
volatile bool sleep_wait = false; | |||||
#else | |||||
logger << "MPI environment initialized." << | |||||
" Rank: " << mpi.rank() << | |||||
" Size: " << mpi.size() << | |||||
logger.endl; | |||||
#if defined DEBUG | |||||
#if defined TESTING | |||||
/* | |||||
* In case of a debug build we will wait here until sleep_wait | |||||
* will reset via debugger. In order to do that the user must attach | |||||
* debugger to all processes. For example: | |||||
* $> mpirun -np 2 ./<program path> | |||||
* $> ps aux | grep <program> | |||||
* $> gdb <program> <PID1> | |||||
* $> gdb <program> <PID2> | |||||
*/ | |||||
volatile bool sleep_wait = false; | |||||
#else | |||||
volatile bool sleep_wait = true; | volatile bool sleep_wait = true; | ||||
#endif | |||||
while (sleep_wait) | |||||
#endif | |||||
while (sleep_wait && !session.ndebug) | |||||
sleep(1); | sleep(1); | ||||
#endif | |||||
logger << "Initialize local array of " << session.arraySize << " elements" << logger.endl; | |||||
std::srand(unsigned(std::time(nullptr))); | |||||
Data.resize(session.arraySize); | |||||
std::generate(Data.begin(), Data.end(), std::rand); | |||||
if (mpi.rank() == 0) | |||||
logger << "Starting distributed sorting ... "; | |||||
timer.start(); | |||||
#if CODE_VERSION == BUBBLETONIC | |||||
distBubbletonic(Data, mpi.size()); | |||||
#else | |||||
distBitonic (Data, mpi.size()); | |||||
#endif | #endif | ||||
timer.stop(); | |||||
if (mpi.rank() == 0) | |||||
logger << " Done." << logger.endl; | |||||
std::string timeMsg = "rank " + std::to_string(mpi.rank()); | |||||
timer.print_dt(timeMsg.c_str()); | |||||
// Print off a hello world message | |||||
// std::cout << "Hello world from processor: " << mpi.processor_name | |||||
// << " rank " << mpi.world_rank | |||||
// << " out of " << mpi.world_size << " processors\n"; | |||||
// distBitonic (2, Data); | |||||
// distBitonic (4, Data); | |||||
std::cout << "[Data]: Rank " << mpi.rank() << ": [" << (int)Data.front() << " .. " << (int)Data.back() << "]" << std::endl; | |||||
mpi.finalize(); | mpi.finalize(); | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -11,7 +11,7 @@ | |||||
#include <algorithm> // rand/srand | #include <algorithm> // rand/srand | ||||
#include <ctime> // rand/srand | #include <ctime> // rand/srand | ||||
#include "distbitonic.hpp" | |||||
#include "distsort.hpp" | |||||
@@ -245,8 +245,8 @@ TEST(TdistBitonic_UT, keepsmall_test1) { | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test2) { | TEST(TdistBitonic_UT, keepsmall_test2) { | ||||
size_t ts_depth = 1; | size_t ts_depth = 1; | ||||
mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; | |||||
bool ts_expected[] = {1, 0, 0, 1, 1, 0, 0, 1}; | |||||
mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; | |||||
bool ts_expected[] = {1, 0, 0, 1, 1, 0, 0, 1}; | |||||
for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | ||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
@@ -260,8 +260,8 @@ TEST(TdistBitonic_UT, keepsmall_test2) { | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test3) { | TEST(TdistBitonic_UT, keepsmall_test3) { | ||||
size_t ts_depth = 2; | size_t ts_depth = 2; | ||||
mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; | |||||
bool ts_expected[] = {1, 1, 0, 0, 0, 0, 1, 1}; | |||||
mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; | |||||
bool ts_expected[] = {1, 1, 0, 0, 0, 0, 1, 1}; | |||||
for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | ||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
@@ -275,8 +275,8 @@ TEST(TdistBitonic_UT, keepsmall_test3) { | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test4) { | TEST(TdistBitonic_UT, keepsmall_test4) { | ||||
size_t ts_depth = 2; | size_t ts_depth = 2; | ||||
mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; | |||||
bool ts_expected[] = {1, 0, 1, 0, 0, 1, 0, 1}; | |||||
mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; | |||||
bool ts_expected[] = {1, 0, 1, 0, 0, 1, 0, 1}; | |||||
for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | ||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
@@ -290,8 +290,8 @@ TEST(TdistBitonic_UT, keepsmall_test4) { | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test5) { | TEST(TdistBitonic_UT, keepsmall_test5) { | ||||
size_t ts_depth = 3; | size_t ts_depth = 3; | ||||
mpi_id_t ts_partner[] = {4, 5, 6, 7, 0, 1, 2, 3}; | |||||
bool ts_expected[] = {1, 1, 1, 1, 0, 0, 0, 0}; | |||||
mpi_id_t ts_partner[] = {4, 5, 6, 7, 0, 1, 2, 3}; | |||||
bool ts_expected[] = {1, 1, 1, 1, 0, 0, 0, 0}; | |||||
for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | ||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
@@ -305,8 +305,8 @@ TEST(TdistBitonic_UT, keepsmall_test5) { | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test6) { | TEST(TdistBitonic_UT, keepsmall_test6) { | ||||
size_t ts_depth = 3; | size_t ts_depth = 3; | ||||
mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; | |||||
bool ts_expected[] = {1, 1, 0, 0, 1, 1, 0, 0}; | |||||
mpi_id_t ts_partner[] = {2, 3, 0, 1, 6, 7, 4, 5}; | |||||
bool ts_expected[] = {1, 1, 0, 0, 1, 1, 0, 0}; | |||||
for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | ||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
@@ -320,27 +320,28 @@ TEST(TdistBitonic_UT, keepsmall_test6) { | |||||
*/ | */ | ||||
TEST(TdistBitonic_UT, keepsmall_test7) { | TEST(TdistBitonic_UT, keepsmall_test7) { | ||||
size_t ts_depth = 3; | size_t ts_depth = 3; | ||||
mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; | |||||
bool ts_expected[] = {1, 0, 1, 0, 1, 0, 1, 0}; | |||||
mpi_id_t ts_partner[] = {1, 0, 3, 2, 5, 4, 7, 6}; | |||||
bool ts_expected[] = {1, 0, 1, 0, 1, 0, 1, 0}; | |||||
for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | for (mpi_id_t node = 0 ; node < 8 ; ++node ) { | ||||
EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | EXPECT_EQ(ts_expected[node], keepSmall<SortMode::Bitonic>(node, ts_partner[node], ts_depth)); | ||||
} | } | ||||
} | } | ||||
#if 0 | |||||
TEST(TdistBitonic_UT, distBitonic_test1) { | TEST(TdistBitonic_UT, distBitonic_test1) { | ||||
AllData_t ts_Data { | AllData_t ts_Data { | ||||
Data_t (8), Data_t (8) | |||||
ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | }; | ||||
std::srand(unsigned(std::time(nullptr))); | |||||
for (auto& v : ts_Data) { | |||||
(unsigned(std::time(nullptr))); | |||||
for (auto | |||||
std::srand& v : ts_Data) { | |||||
std::generate(v.begin(), v.end(), std::rand); | std::generate(v.begin(), v.end(), std::rand); | ||||
} | } | ||||
distBitonic(2, ts_Data); | distBitonic(2, ts_Data); | ||||
auto max = std::numeric_limits<Data_t::value_type>::min(); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | for (auto& v : ts_Data) { | ||||
EXPECT_EQ((max <= v[0]), true); | EXPECT_EQ((max <= v[0]), true); | ||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | ||||
@@ -350,7 +351,7 @@ TEST(TdistBitonic_UT, distBitonic_test1) { | |||||
TEST(TdistBitonic_UT, distBitonic_test2) { | TEST(TdistBitonic_UT, distBitonic_test2) { | ||||
AllData_t ts_Data { | AllData_t ts_Data { | ||||
Data_t (8), Data_t (8), Data_t (8), Data_t (8) | |||||
ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | }; | ||||
std::srand(unsigned(std::time(nullptr))); | std::srand(unsigned(std::time(nullptr))); | ||||
@@ -360,7 +361,7 @@ TEST(TdistBitonic_UT, distBitonic_test2) { | |||||
distBitonic(4, ts_Data); | distBitonic(4, ts_Data); | ||||
auto max = std::numeric_limits<Data_t::value_type>::min(); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | for (auto& v : ts_Data) { | ||||
EXPECT_EQ((max <= v[0]), true); | EXPECT_EQ((max <= v[0]), true); | ||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | ||||
@@ -370,8 +371,8 @@ TEST(TdistBitonic_UT, distBitonic_test2) { | |||||
TEST(TdistBitonic_UT, distBitonic_test3) { | TEST(TdistBitonic_UT, distBitonic_test3) { | ||||
AllData_t ts_Data { | AllData_t ts_Data { | ||||
Data_t (32), Data_t (32), Data_t (32), Data_t (32), | |||||
Data_t (32), Data_t (32), Data_t (32), Data_t (32) | |||||
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))); | std::srand(unsigned(std::time(nullptr))); | ||||
@@ -381,10 +382,12 @@ TEST(TdistBitonic_UT, distBitonic_test3) { | |||||
distBitonic(8, ts_Data); | distBitonic(8, ts_Data); | ||||
auto max = std::numeric_limits<Data_t::value_type>::min(); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | for (auto& v : ts_Data) { | ||||
EXPECT_EQ((max <= v[0]), true); | EXPECT_EQ((max <= v[0]), true); | ||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | ||||
max = v.back(); | max = v.back(); | ||||
} | } | ||||
} | |||||
} | |||||
#endif |
@@ -11,7 +11,7 @@ | |||||
#include <algorithm> // rand/srand | #include <algorithm> // rand/srand | ||||
#include <ctime> // rand/srand | #include <ctime> // rand/srand | ||||
#include "distbitonic.hpp" | |||||
#include "distsort.hpp" | |||||
@@ -120,10 +120,10 @@ TEST(TdistBubbletonic_UT, keepsmall_test2) { | |||||
} | } | ||||
#if 0 | |||||
TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | ||||
AllData_t ts_Data { | AllData_t ts_Data { | ||||
Data_t (8), Data_t (8) | |||||
ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | }; | ||||
std::srand(unsigned(std::time(nullptr))); | std::srand(unsigned(std::time(nullptr))); | ||||
@@ -133,7 +133,7 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | |||||
distBubbletonic(2, ts_Data); | distBubbletonic(2, ts_Data); | ||||
auto max = std::numeric_limits<Data_t::value_type>::min(); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | for (auto& v : ts_Data) { | ||||
EXPECT_EQ((max <= v[0]), true); | EXPECT_EQ((max <= v[0]), true); | ||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | ||||
@@ -144,7 +144,7 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test1) { | |||||
TEST(TdistBubbletonic_UT, distBubbletonic_test2) { | TEST(TdistBubbletonic_UT, distBubbletonic_test2) { | ||||
AllData_t ts_Data { | AllData_t ts_Data { | ||||
Data_t (8), Data_t (8), Data_t (8), Data_t (8) | |||||
ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8), ShadowedVec_t (8) | |||||
}; | }; | ||||
std::srand(unsigned(std::time(nullptr))); | std::srand(unsigned(std::time(nullptr))); | ||||
@@ -154,7 +154,7 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test2) { | |||||
distBubbletonic(4, ts_Data); | distBubbletonic(4, ts_Data); | ||||
auto max = std::numeric_limits<Data_t::value_type>::min(); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | for (auto& v : ts_Data) { | ||||
EXPECT_EQ((max <= v[0]), true); | EXPECT_EQ((max <= v[0]), true); | ||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | ||||
@@ -164,8 +164,8 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test2) { | |||||
TEST(TdistBubbletonic_UT, distBubbletonic_test3) { | TEST(TdistBubbletonic_UT, distBubbletonic_test3) { | ||||
AllData_t ts_Data { | AllData_t ts_Data { | ||||
Data_t (32), Data_t (32), Data_t (32), Data_t (32), | |||||
Data_t (32), Data_t (32), Data_t (32), Data_t (32) | |||||
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))); | std::srand(unsigned(std::time(nullptr))); | ||||
@@ -175,10 +175,11 @@ TEST(TdistBubbletonic_UT, distBubbletonic_test3) { | |||||
distBubbletonic(8, ts_Data); | distBubbletonic(8, ts_Data); | ||||
auto max = std::numeric_limits<Data_t::value_type>::min(); | |||||
auto max = std::numeric_limits<ShadowedVec_t::value_type>::min(); | |||||
for (auto& v : ts_Data) { | for (auto& v : ts_Data) { | ||||
EXPECT_EQ((max <= v[0]), true); | EXPECT_EQ((max <= v[0]), true); | ||||
EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | EXPECT_EQ(std::is_sorted(v.begin(), v.end()), true); | ||||
max = v.back(); | max = v.back(); | ||||
} | } | ||||
} | } | ||||
#endif |