diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2bd286800..e2f731cb9 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -313,6 +313,7 @@ set(header_files model/watchdog.h model/realtime-simulator-impl.h model/wall-clock-synchronizer.h + model/val-array.h ) set(test_sources @@ -348,6 +349,7 @@ set(test_sources test/type-id-test-suite.cc test/type-traits-test-suite.cc test/watchdog-test-suite.cc + test/val-array-test-suite.cc ) # Build core lib diff --git a/src/core/model/val-array.h b/src/core/model/val-array.h new file mode 100644 index 000000000..3d4f13f1f --- /dev/null +++ b/src/core/model/val-array.h @@ -0,0 +1,743 @@ +/* + * Copyright (c) 2022 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Biljana Bojovic + */ +#ifndef VAL_ARRAY_H +#define VAL_ARRAY_H + +#include +#include + +#include +#include +#include + +namespace ns3 +{ + +/** + * \ingroup Matrices + * + * \brief ValArray is a class to efficiently store 3D array. The class is general + * enough to represent 1D array or 2D arrays. ValArray also provides basic + * algebra element-wise operations over the whole array (1D, 2D, 3D). + * + * Main characteristics of ValArray are the following: + * + * - ValArray uses std::valarray to efficiently store data. + * + * - In general, the elements are stored in memory as a sequence of consecutive + * 2D arrays. The dimensions of 2D arrays are defined by numRows and numCols, + * while the number of 2D arrays is defined by numPages. Notice that if we set + * the number of pages to 1, we will have only a single 2D array. If we + * additionally set numRows or numCols to 1, we will have 1D array. + * + * - All 2D arrays have the same dimensions, i.e. numRows and numCols. + * + * - 2D arrays are stored in column-major order, which is the default + * order in Eigen and Armadillo libraries, which allows a straightforward mapping + * of any page (2D array) within ValArray to Eigen or Armadillo matrices. + * + * Examples of column-major order: + * + * a) in the case of a 2D array, we will have in memory the following order of + * elements, assuming that the indexes are rowIndex, colIndex, pageIndex: + * + * a000 a100 a010 a110 a020 a120. + * + * b) in the case of a 3D array, e.g, if there are two 2D arrays of 2x3 dimensions + * we will have in memory the following order of elements, + * assuming that the indexes are rowIndex, colIndex, pageIndex: + * + * a000 a100 a010 a110 a020 a120 a001 a101 a011 a111 a021 a121. + * + * - The access to the elements is implemented in operators: + * - operator (rowIndex) and operator[] (rowIndex) for 1D array (assuming colIndex=0, pageIndex=0), + * - operator (rowIndex,colIndex) for 2D array (assuming pageIndex=0) and + * - operator(rowIndex, colIndex, pageIndex) for 3D array. + * + * Definition of ValArray as a template class allows using different numerical + * types as the elements of the vectors/matrices, e.g., complex numbers, double, + * int, etc. + */ + +template +class ValArray : public SimpleRefCount> +{ + public: + // instruct the compiler to generate the default constructor + ValArray() = default; + /** + * \brief Constructor that creates "numPages" number of 2D arrays that are of + * dimensions "numRows"x"numCols", and are initialized with all-zero elements. + * If only 1 parameter, numRows, is provided then a single 1D array is being created. + * \param numRows the number of rows + * \param numCols the number of columns + * \param numPages the number of pages + */ + ValArray(uint16_t numRows, uint16_t numCols = 1, uint16_t numPages = 1); + /** + * \brief Constructor creates a single 1D array of values.size () elements and 1 column, + * and uses std::valarray values to initialize the elements. + * \param values std::valarray that will be used to initialize elements of 1D array + */ + explicit ValArray(const std::valarray& values); + /** + * \brief Constructor creates a single 1D array of values.size () elements and 1 column, + * and moves std::valarray values to initialize the elements. + * \param values std::valarray that will be moved to initialize elements of 1D array + */ + ValArray(std::valarray&& values); + /** + * \brief Constructor creates a single 1D array of values.size () elements and 1 column, + * and uses values std::vector to initialize the elements. + * \param values std::vector that will be used to initialize elements of 1D array + */ + explicit ValArray(const std::vector& values); + /** + * \brief Constructor creates a single 2D array of numRows and numCols, and uses + * std::valarray values to initialize the elements. + * \param numRows the number of rows + * \param numCols the number of columns + * \param values valarray that will be used to initialize elements of 3D array + */ + ValArray(uint16_t numRows, uint16_t numCols, const std::valarray& values); + /** + * \brief Constructor creates a single 2D array of numRows and numCols, and moves + * std::valarray values to initialize the elements. + * \param numRows the number of rows + * \param numCols the number of columns + * \param values valarray that will be used to initialize elements of 3D array + */ + ValArray(uint16_t numRows, uint16_t numCols, std::valarray&& values); + /** + * \brief Constructor creates the 3D array of numRows x numCols x numPages dimensions, + * and uses std::valarray values to initialize all the 2D arrays, where first + * numRows*numCols elements will belong to the first 2D array. + * \param numRows the number of rows + * \param numCols the number of columns + * \param numPages the number of pages + * \param values valarray that will be used to initialize elements of 3D array + */ + ValArray(uint16_t numRows, + uint16_t numCols, + uint16_t numPages, + const std::valarray& values); + /** + * \brief Constructor creates the 3D array of numRows x numCols x numPages dimensions, + * and moves std::valarray values to initialize all the 2D arrays, where first + * numRows*numCols elements will belong to the first 2D array. + * \param numRows the number of rows + * \param numCols the number of columns + * \param numPages the number of pages + * \param values valarray that will be used to initialize elements of 3D array + */ + ValArray(uint16_t numRows, uint16_t numCols, uint16_t numPages, std::valarray&& values); + /** instruct the compiler to generate the implicitly declared destructor*/ + virtual ~ValArray() = default; + /** instruct the compiler to generate the implicitly declared copy constructor*/ + ValArray(const ValArray&) = default; + /** + * \brief Copy assignment operator. + * Instruct the compiler to generate the implicitly declared copy assignment operator. + * \return a reference to the assigned object + */ + ValArray& operator=(const ValArray&) = default; + /** instruct the compiler to generate the implicitly declared move constructor*/ + ValArray(ValArray&&) noexcept = default; + /** + * \brief Move assignment operator. + * Instruct the compiler to generate the implicitly declared move assignment operator. + * \return a reference to the assigned object + */ + ValArray& operator=(ValArray&&) noexcept = default; + /** + * \returns Number of rows + */ + uint16_t GetNumRows() const; + /** + * \returns Number of columns + */ + uint16_t GetNumCols() const; + /** + * \returns Number of pages, i.e., the number of 2D arrays + */ + uint16_t GetNumPages() const; + /** + * \returns Total number of elements + */ + size_t GetSize() const; + /** + * \brief Access operator, with bound-checking in debug profile + * \param rowIndex The index of the row + * \param colIndex The index of the column + * \param pageIndex The index of the page + * \returns A const reference to the element with with rowIndex, colIndex and pageIndex indices. + */ + T& operator()(uint16_t rowIndex, uint16_t colIndex, uint16_t pageIndex); + /** + * \brief Const access operator, with bound-checking in debug profile + * \param rowIndex The index of the row + * \param colIndex The index of the column + * \param pageIndex The index of the page + * \returns A const reference to the element with with rowIndex, colIndex and pageIndex indices. + */ + const T& operator()(uint16_t rowIndex, uint16_t colIndex, uint16_t pageIndex) const; + /** + * \brief Access operator for 2D ValArrays. + * Assuming that the third dimension is equal to 1, e.g. ValArray contains + * a single 2D array. + * Note: intentionally not implemented through three parameters access operator, + * to avoid accidental mistakes by user, e.g., providing 2 parameters when + * 3 are necessary, but access operator would return valid value if default + * value of pages provided is 0. + * \param rowIndex The index of the row + * \param colIndex The index of the column + * \returns A reference to the element with the specified indices + */ + T& operator()(uint16_t rowIndex, uint16_t colIndex); + /** + * \brief Const access operator for 2D ValArrays. + * Assuming that the third dimension is equal to 1, e.g. ValArray contains + * a single 2D array. + * \param rowIndex row index + * \param colIndex column index + * \returns a Const reference to the value with the specified row and column index. + */ + const T& operator()(uint16_t rowIndex, uint16_t colIndex) const; + /** + * \brief Single-element access operator() for 1D ValArrays. + * Assuming that the number of columns and pages is equal to 1, e.g. ValArray + * contains a single column or a single row. + * + * Note: intentionally not implemented through three parameters access operator, + * to avoid accidental mistakes by user, e.g., providing 1 parameters when + * 2 or 3 are necessary. + * \param index The index of the 1D ValArray. + * \returns A reference to the value with the specified index. + */ + T& operator()(uint16_t index); + /** + * \brief Single-element access operator() for 1D ValArrays. + * \param index The index of the 1D ValArray. + * \returns The const reference to the values with the specified index. + */ + const T& operator()(uint16_t index) const; + /** + * \brief Element-wise multiplication with a scalar value. + * \param rhs A scalar value of type T + * \returns ValArray in which each element has been multiplied by the given + * scalar value. + */ + ValArray operator*(const T& rhs) const; + /** + * \brief operator+ definition for ValArray. + * \param rhs The rhs ValArray to be added to this ValArray. + * \return the ValArray instance that holds the results of the operator+ + */ + ValArray operator+(const ValArray& rhs) const; + /** + * \brief binary operator- definition for ValArray. + * \param rhs The rhs ValArray to be subtracted from this ValArray. + * \return the ValArray instance that holds the results of the operator- + */ + ValArray operator-(const ValArray& rhs) const; + /** + * \brief unary operator- definition for ValArray. + * \return the ValArray instance that holds the results of the operator- + */ + ValArray operator-() const; + /** + * \brief operator+= definition for ValArray. + * \param rhs The rhs ValArray to be added to this ValArray. + * \return a reference to this ValArray instance + */ + ValArray& operator+=(const ValArray& rhs); + /** + * \brief operator-= definition for ValArray. + * \param rhs The rhs ValArray to be subtracted from this ValArray. + ** \return a reference to this ValArray instance + */ + ValArray& operator-=(const ValArray& rhs); + /** + * \brief operator== definition for ValArray. + * \param rhs The ValArray instance to be compared with lhs ValArray instance + * \return true if rhs ValArray is equal to this ValArray, otherwise it returns false + */ + bool operator==(const ValArray& rhs) const; + /** + * \brief operator!= definition for ValArray. + * \param rhs The ValArray instance to be compared with lhs ValArray instance + * \return true if rhs ValArray is not equal to this ValArray, otherwise it returns true + */ + bool operator!=(const ValArray& rhs) const; + /** + * \brief Compare Valarray up to a given absolute tolerance. This operation + * is element-wise operation, i.e., the elements with the same indices from + * the lhs and rhs ValArray are being compared, allowing the tolerance defined + * byt "tol" parameter. + * \param rhs The rhs ValArray + * \param tol The absolute tolerance + * \returns true if the differences in each element-wise comparison is less + * or equal to tol. + */ + bool IsAlmostEqual(const ValArray& rhs, T tol) const; + /** + * \brief Get a data pointer to a specific 2D array for use in linear + * algebra libraries + * \param pageIndex The index of the desired 2D array + * \returns a pointer to the data elements of the 2D array + */ + T* GetPagePtr(uint16_t pageIndex); + /** + * \brief Get a data pointer to a specific 2D array for use in linear + * algebra libraries + * \param pageIndex An index of the desired 2D array + * \returns a pointer to the data elements of the 2D array + */ + const T* GetPagePtr(uint16_t pageIndex) const; + /** + * \brief Checks whether rhs and lhs ValArray objects have the same dimensions. + * \param rhs The rhs ValArray + * \returns true if the dimensions of lhs and rhs are equal, otherwise it returns false + */ + bool EqualDims(const ValArray& rhs) const; + /** + * \brief Function that asserts if the dimensions of lhs and rhs ValArray are + * not equal and prints a message with the matrices dimensions. + * \param rhs the rhs ValArray + */ + void AssertEqualDims(const ValArray& rhs) const; + /** + * \brief Single-element access operator[] that can be used to access a specific + * element of 1D ValArray. It mimics operator[] from std::vector. + * This function is introduced for compatibility with ns-3 usage of 1D arrays, + * which are usually represented through std::vector operators in spectrum + * and antenna module. + * + * \param index The index of the element to be returned + * \returns A reference to a specific element from the underlying std::valarray. + */ + T& operator[](size_t index); + /** + * \brief Const access operator that can be used to access a specific element of + * 1D ValArray. + * + * \param index The index of the element to be returned + * \returns A const reference to a specific element from the underlying std::valarray. + */ + const T& operator[](size_t index) const; + /** + * \brief Returns underlying values. This function allows to directly work + * with the underlying values, which can be faster then using access operators. + * \returns A const reference to the underlying std::valarray. + */ + const std::valarray& GetValues() const; + /** + * \brief Alternative access operator to access a specific element. + * \param row the row index of the element to be obtained + * \param col the col index of the element to be obtained + * \param page the page index of the element to be obtained + * \return a reference to the element of this ValArray + */ + T& Elem(size_t row, size_t col, size_t page); + /** + * \brief Alternative const access operator to access a specific element. + * \param row the row index of the element to be obtained + * \param col the column index of the element to be obtained + * \param page the page index of the element to be obtained + * \return a const reference to the element of this ValArray + */ + const T& Elem(size_t row, size_t col, size_t page) const; + + protected: + uint16_t m_numRows = + 0; //!< The size of the first dimension, i.e., the number of rows of each 2D array + uint16_t m_numCols = + 0; //!< The size of the second dimension, i.e., the number of columns of each 2D array + uint16_t m_numPages = 0; //!< The size of the third dimension, i.e., the number of 2D arrays + std::valarray m_values; //!< The data values +}; + +/************************************************* + ** Class ValArray inline implementations + ************************************************/ + +template +inline uint16_t +ValArray::GetNumRows() const +{ + return m_numRows; +}; + +template +inline uint16_t +ValArray::GetNumCols() const +{ + return m_numCols; +}; + +template +inline uint16_t +ValArray::GetNumPages() const +{ + return m_numPages; +}; + +template +inline size_t +ValArray::GetSize() const +{ + return m_values.size(); +} + +template +inline T& +ValArray::operator()(uint16_t rowIndex, uint16_t colIndex, uint16_t pageIndex) +{ + NS_ASSERT_MSG(rowIndex < m_numRows, "Row index out of bounds"); + NS_ASSERT_MSG(colIndex < m_numCols, "Column index out of bounds"); + NS_ASSERT_MSG(pageIndex < m_numPages, "Pages index out of bounds"); + size_t index = (rowIndex + m_numRows * (colIndex + m_numCols * pageIndex)); + return m_values[index]; +}; + +template +inline const T& +ValArray::operator()(uint16_t rowIndex, uint16_t colIndex, uint16_t pageIndex) const +{ + NS_ASSERT_MSG(rowIndex < m_numRows, "Row index out of bounds"); + NS_ASSERT_MSG(colIndex < m_numCols, "Column index out of bounds"); + NS_ASSERT_MSG(pageIndex < m_numPages, "Pages index out of bounds"); + size_t index = (rowIndex + m_numRows * (colIndex + m_numCols * pageIndex)); + return m_values[index]; +}; + +template +inline T& +ValArray::operator()(uint16_t rowIndex, uint16_t colIndex) +{ + NS_ASSERT_MSG(m_numPages == 1, "Cannot use 2D access operator for 3D ValArray."); + return (*this)(rowIndex, colIndex, 0); +}; + +template +inline const T& +ValArray::operator()(uint16_t rowIndex, uint16_t colIndex) const +{ + NS_ASSERT_MSG(m_numPages == 1, "Cannot use 2D access operator for 3D ValArray."); + return (*this)(rowIndex, colIndex, 0); +}; + +template +inline T& +ValArray::operator()(uint16_t index) +{ + NS_ASSERT_MSG(index < m_values.size(), + "Invalid index to 1D ValArray. The size of the array should be set through " + "constructor."); + NS_ASSERT_MSG(((m_numRows == 1 || m_numCols == 1) && (m_numPages == 1)) || + (m_numRows == 1 && m_numCols == 1), + "Access operator allowed only for 1D ValArray."); + return m_values[index]; +}; + +template +inline const T& +ValArray::operator()(uint16_t index) const +{ + NS_ASSERT_MSG(index < m_values.size(), + "Invalid index to 1D ValArray.The size of the array should be set through " + "constructor."); + NS_ASSERT_MSG(((m_numRows == 1 || m_numCols == 1) && (m_numPages == 1)) || + (m_numRows == 1 && m_numCols == 1), + "Access operator allowed only for 1D ValArray."); + return m_values[index]; +}; + +template +inline ValArray +ValArray::operator*(const T& rhs) const +{ + return ValArray(m_numRows, + m_numCols, + m_numPages, + m_values * std::valarray(rhs, m_numRows * m_numCols * m_numPages)); +} + +template +inline ValArray +ValArray::operator+(const ValArray& rhs) const +{ + AssertEqualDims(rhs); + return ValArray(m_numRows, m_numCols, m_numPages, m_values + rhs.m_values); +} + +template +inline ValArray +ValArray::operator-(const ValArray& rhs) const +{ + AssertEqualDims(rhs); + return ValArray(m_numRows, m_numCols, m_numPages, m_values - rhs.m_values); +} + +template +inline ValArray +ValArray::operator-() const +{ + return ValArray(m_numRows, m_numCols, m_numPages, -m_values); +} + +template +inline ValArray& +ValArray::operator+=(const ValArray& rhs) +{ + AssertEqualDims(rhs); + m_values += rhs.m_values; + return *this; +} + +template +inline ValArray& +ValArray::operator-=(const ValArray& rhs) +{ + AssertEqualDims(rhs); + m_values -= rhs.m_values; + return *this; +} + +template +inline T* +ValArray::GetPagePtr(uint16_t pageIndex) +{ + NS_ASSERT_MSG(pageIndex < m_numPages, "Invalid page index."); + return &(m_values[m_numRows * m_numCols * pageIndex]); +}; + +template +inline const T* +ValArray::GetPagePtr(uint16_t pageIndex) const +{ + NS_ASSERT_MSG(pageIndex < m_numPages, "Invalid page index."); + return &(m_values[m_numRows * m_numCols * pageIndex]); +}; + +template +inline bool +ValArray::EqualDims(const ValArray& rhs) const +{ + return (m_numRows == rhs.m_numRows) && (m_numCols == rhs.m_numCols) && + (m_numPages == rhs.m_numPages); +} + +template +inline T& +ValArray::operator[](size_t index) +{ + return (*this)(index); +} + +template +inline const T& +ValArray::operator[](size_t index) const +{ + return (*this)(index); +} + +template +inline const std::valarray& +ValArray::GetValues() const +{ + return m_values; +} + +template +inline T& +ValArray::Elem(size_t row, size_t col, size_t page) +{ + return (*this)(row, col, page); +}; + +template +inline const T& +ValArray::Elem(size_t row, size_t col, size_t page) const +{ + return (*this)(row, col, page); +}; + +/************************************************* + ** Class ValArray non-inline implementations + ************************************************/ + +template +ValArray::ValArray(uint16_t numRows, uint16_t numCols, uint16_t numPages) + : m_numRows{numRows}, + m_numCols{numCols}, + m_numPages{numPages} +{ + m_values.resize(m_numRows * m_numCols * m_numPages); +}; + +template +ValArray::ValArray(const std::valarray& values) + : m_numRows{(uint16_t)values.size()}, + m_numCols{1}, + m_numPages{1}, + m_values{values} +{ +} + +template +ValArray::ValArray(std::valarray&& values) + : m_numRows{(uint16_t)values.size()}, + m_numCols{1}, + m_numPages{1}, + m_values{std::move(values)} +{ +} + +template +ValArray::ValArray(const std::vector& values) + : m_numRows{(uint16_t)values.size()}, + m_numCols{1}, + m_numPages{1} +{ + m_values.resize(values.size()); + std::copy(values.begin(), values.end(), std::begin(m_values)); +} + +template +ValArray::ValArray(uint16_t numRows, uint16_t numCols, const std::valarray& values) + : m_numRows{numRows}, + m_numCols{numCols}, + m_numPages{1}, + m_values{values} +{ + NS_ASSERT_MSG(m_numRows * m_numCols == values.size(), + "Dimensions and the initialization array size do not match."); +}; + +template +ValArray::ValArray(uint16_t numRows, uint16_t numCols, std::valarray&& values) + : m_numRows{numRows}, + m_numCols{numCols}, + m_numPages{1} +{ + NS_ASSERT_MSG(m_numRows * m_numCols == values.size(), + "Dimensions and the initialization array size do not match."); + m_values = std::move(values); +}; + +template +ValArray::ValArray(uint16_t numRows, + uint16_t numCols, + uint16_t numPages, + const std::valarray& values) + : m_numRows{numRows}, + m_numCols{numCols}, + m_numPages{numPages}, + m_values{values} +{ + NS_ASSERT_MSG(m_numRows * m_numCols * m_numPages == values.size(), + "Dimensions and the initialization array size do not match."); +}; + +template +ValArray::ValArray(uint16_t numRows, + uint16_t numCols, + uint16_t numPages, + std::valarray&& values) + : m_numRows{numRows}, + m_numCols{numCols}, + m_numPages{numPages} +{ + NS_ASSERT_MSG(m_numRows * m_numCols * m_numPages == values.size(), + "Dimensions and the initialization array size do not match."); + m_values = std::move(values); +}; + +template +bool +ValArray::operator==(const ValArray& rhs) const +{ + return EqualDims(rhs) && + std::equal(std::begin(m_values), std::end(m_values), std::begin(rhs.m_values)); +} + +template +bool +ValArray::operator!=(const ValArray& rhs) const +{ + return !((*this) == rhs); +} + +template +bool +ValArray::IsAlmostEqual(const ValArray& rhs, T tol) const +{ + return EqualDims(rhs) && std::equal(std::begin(m_values), + std::end(m_values), + std::begin(rhs.m_values), + [tol](T lhsValue, T rhsValue) { + return lhsValue == rhsValue || + std::abs(lhsValue - rhsValue) <= std::abs(tol); + }); +} + +template +void +ValArray::AssertEqualDims(const ValArray& rhs) const +{ + NS_ASSERT_MSG(EqualDims(rhs), + "Dimensions mismatch: " + "lhs (rows, cols, pages) = (" + << m_numRows << ", " << m_numCols << ", " << m_numPages + << ") and " + "rhs (rows, cols, pages) = (" + << rhs.m_numRows << ", " << rhs.m_numCols << ", " << rhs.m_numPages << ")"); +} + +/** + * \brief Overloads output stream operator. + * \tparam T the type of the ValArray for which will be called this function + * \param os a reference to the output stream + * \param a the ValArray instance using type T + * \return a reference to the output stream + */ +template +std::ostream& +operator<<(std::ostream& os, const ValArray& a) +{ + os << "\n"; + for (auto p = 0; p != a.GetNumPages(); ++p) + { + os << "Page " << p << ":\n"; + for (auto i = 0; i != a.GetNumRows(); ++i) + { + for (auto j = 0; j != a.GetNumCols(); ++j) + { + os << "\t" << a(i, j, p); + } + os << "\n"; + } + } + return os; +} + +} // namespace ns3 + +#endif // VAL_ARRAY_H diff --git a/src/core/test/val-array-test-suite.cc b/src/core/test/val-array-test-suite.cc new file mode 100644 index 000000000..77f77c93b --- /dev/null +++ b/src/core/test/val-array-test-suite.cc @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2022 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Biljana Bojovic + */ + +#include "ns3/log.h" +#include "ns3/test.h" +#include "ns3/val-array.h" + +/** + * \ingroup core-tests + */ + +namespace ns3 +{ +namespace tests +{ + +NS_LOG_COMPONENT_DEFINE("ValArrayTest"); + +/** + * @brief ValArray test case for testing ValArray class + * + * @tparam T the template parameter that can be a complex number, double or int + */ +template +class ValArrayTestCase : public TestCase +{ + public: + /** Default constructor*/ + ValArrayTestCase() = default; + /** + * Constructor + * + * \param [in] name reference name + */ + ValArrayTestCase(const std::string name); + + /** Destructor. */ + ~ValArrayTestCase() override; + /** + * \brief Copy constructor. + * Instruct the compiler to generate the implicitly declared copy constructor + */ + ValArrayTestCase(const ValArrayTestCase&) = default; + /** + * \brief Copy assignment operator. + * Instruct the compiler to generate the implicitly declared copy assignment operator. + * \return A reference to this ValArrayTestCase + */ + ValArrayTestCase& operator=(const ValArrayTestCase&) = default; + /** + * \brief Move constructor. + * Instruct the compiler to generate the implicitly declared move constructor + */ + ValArrayTestCase(ValArrayTestCase&&) noexcept = default; + /** + * \brief Move assignment operator. + * Instruct the compiler to generate the implicitly declared copy constructor + * \return A reference to this ValArrayTestCase + */ + ValArrayTestCase& operator=(ValArrayTestCase&&) noexcept = default; + + protected: + private: + void DoRun() override; +}; + +template +ValArrayTestCase::ValArrayTestCase(const std::string name) + : TestCase(name) +{ +} + +template +ValArrayTestCase::~ValArrayTestCase() +{ +} + +template +void +ValArrayTestCase::DoRun() +{ + ValArray v1 = ValArray(2, 3); + for (auto i = 0; i < v1.GetNumRows(); ++i) + { + for (auto j = 0; j < v1.GetNumCols(); ++j) + { + v1(i, j) = 1; + } + } + + ValArray v2 = ValArray(v1); + NS_TEST_ASSERT_MSG_EQ(v1.GetNumRows(), v2.GetNumRows(), "The number of rows are not equal."); + NS_TEST_ASSERT_MSG_EQ(v1.GetNumCols(), v2.GetNumCols(), "The number of cols are not equal."); + + // test copy constructor + for (auto i = 0; i < v1.GetNumRows(); ++i) + { + for (auto j = 0; j < v1.GetNumCols(); ++j) + { + NS_TEST_ASSERT_MSG_EQ(v1(i, j), v2(i, j), "The elements are not equal."); + } + } + + // test assign constructor + ValArray v3 = v1; + NS_TEST_ASSERT_MSG_EQ(v1.GetNumRows(), v3.GetNumRows(), "The number of rows are not equal."); + NS_TEST_ASSERT_MSG_EQ(v1.GetNumCols(), v3.GetNumCols(), "The number of cols are not equal."); + for (auto i = 0; i < v1.GetNumRows(); ++i) + { + for (auto j = 0; j < v1.GetNumCols(); ++j) + { + NS_TEST_ASSERT_MSG_EQ(v1(i, j), v2(i, j), "The elements are not equal."); + } + } + + // test move assignment operator + ValArray v4; + NS_LOG_INFO("v1 size before move: " << v1.GetSize()); + NS_LOG_INFO("v4 size before move: " << v4.GetSize()); + v4 = std::move(v1); + NS_LOG_INFO("v1 size after move: " << v1.GetSize()); + NS_LOG_INFO("v4 size after move: " << v4.GetSize()); + NS_TEST_ASSERT_MSG_NE(v1.GetSize(), v4.GetSize(), "The number of elements are equal."); + + // test move constructor + NS_LOG_INFO("v3 size before move: " << v3.GetSize()); + ValArray v5(std::move(v3)); + NS_LOG_INFO("v3 size after move: " << v3.GetSize()); + NS_TEST_ASSERT_MSG_NE(v3.GetSize(), v5.GetSize(), "The number of elements are equal."); + + // test constructor with initialization valArray + std::valarray initArray1{0, 1, 2, 3, 4, 5, 6, 7}; + std::valarray valArray1(initArray1.size()); // length is 8 elements + for (size_t i = 0; i < initArray1.size(); i++) + { + valArray1[i] = static_cast(initArray1[i]); + } + ValArray v6 = ValArray(2, 4, valArray1); + + // test constructro that moves valArray + NS_LOG_INFO("valarray1 size before move: " << valArray1.size()); + ValArray v11 = ValArray(2, 4, std::move(valArray1)); + NS_LOG_INFO("valarray1 size after move: " << valArray1.size()); + NS_LOG_INFO("v11 size after move: " << v11.GetSize()); + + // test whether column-major order was respected during the initialization and + // also in the access operator if we iterate over rows first we should find 0, 2, 4, 6, ... + std::valarray initArray2{0, 2, 4, 6, 1, 3, 5, 7}; + auto testIndex = 0; + for (auto i = 0; i < v6.GetNumRows(); ++i) + { + for (auto j = 0; j < v6.GetNumCols(); ++j) + { + NS_TEST_ASSERT_MSG_EQ(v6(i, j), + static_cast(initArray2[testIndex]), + "The values are not equal."); + testIndex++; + } + } + + // test constructor with initialization valArray for 3D array + std::valarray initArray3{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7}; + std::valarray valArray2(initArray3.size()); // length is 8 elements + for (size_t i = 0; i < initArray3.size(); i++) + { + valArray2[i] = static_cast(initArray3[i]); + } + + ValArray v7 = ValArray(2, 4, 2, valArray2); + // test whether column-major order was respected during the initialization and + // also in the access operator + // if we iterate over rows first we should find 0, 2, 4, 6, ... + std::valarray initArray4{0, 2, 4, 6, 1, 3, 5, 7, 0, 2, 4, 6, 1, 3, 5, 7}; + testIndex = 0; + for (auto p = 0; p < v7.GetNumPages(); ++p) + { + for (auto i = 0; i < v7.GetNumRows(); ++i) + { + for (auto j = 0; j < v7.GetNumCols(); ++j) + { + NS_TEST_ASSERT_MSG_EQ(v7(i, j, p), + static_cast(initArray4[testIndex]), + "The values are not equal."); + testIndex++; + } + } + } + + // multiplication with a scalar value with 3D array + ValArray v8 = v7 * (static_cast(5.0)); + for (auto p = 0; p < v8.GetNumPages(); ++p) + { + for (auto i = 0; i < v8.GetNumRows(); ++i) + { + for (auto j = 0; j < v8.GetNumCols(); ++j) + { + NS_TEST_ASSERT_MSG_EQ(v7(i, j, p) * (static_cast(5.0)), + v8(i, j, p), + "The values are not equal"); + } + } + } + + NS_LOG_INFO("v8 = v7 * 5:" << v8); + // test +, - (binary, unary) operators + NS_LOG_INFO("v8 + v8" << v8 + v8); + NS_LOG_INFO("v8 - v8" << v8 - v8); + NS_LOG_INFO("-v8" << -v8); + + // test += and -= assignment operators + ValArray v9(v8.GetNumRows(), v8.GetNumCols(), v8.GetNumPages()); + v9 += v8; + NS_LOG_INFO("v9 += v8" << v9); + ValArray v10(v8.GetNumRows(), v8.GetNumCols(), v8.GetNumPages()); + v10 -= v8; + NS_LOG_INFO("v10 -= v8" << v10); + + // test == and != operators + NS_TEST_ASSERT_MSG_EQ(bool(v9 == v8), true, "Matrices v8 and v9 should be equal"); + NS_TEST_ASSERT_MSG_EQ(bool(v10 == v8), false, "Matrices v8 and v10 should not be equal"); + NS_TEST_ASSERT_MSG_EQ(bool(v10 != v8), true, "Matrices v8 and v10 should not be equal"); + // test whether arrays are equal when they have different lengths + NS_TEST_ASSERT_MSG_NE(ValArray(std::valarray({1, 2, 3})), + ValArray(std::valarray({1, 2, 3, 4})), + "Arrays should not be equal, they have different dimensions."); + + // test the function IsAlmostEqual + v9(0, 0, 0) = v9(0, 0, 0) + static_cast(1); + NS_TEST_ASSERT_MSG_EQ(v9.IsAlmostEqual(v8, 2) && (v9 != v8), + true, + "Matrices should be almost equal, but not equal."); + + // test the inicialization with std::vector + ValArray v12 = ValArray(std::vector({1, 2, 3})); + NS_LOG_INFO("v12:" << v12); +} + +/** + * \ingroup valArray-tests + * ValArray test suite + * + * \brief The test checks the correct behaviour of ValArray class + */ +class ValArrayTestSuite : public TestSuite +{ + public: + /** Constructor. */ + ValArrayTestSuite(); +}; + +ValArrayTestSuite::ValArrayTestSuite() + : TestSuite("val-array-test") +{ + AddTestCase(new ValArrayTestCase("Test ValArray")); + AddTestCase(new ValArrayTestCase>("Test ValArray>")); + AddTestCase(new ValArrayTestCase("Test ValArray")); +} + +/** + * \ingroup valArray-tests + * ValArrayTestSuite instance variable. + */ +static ValArrayTestSuite g_valArrayTestSuite; + +} // namespace tests +} // namespace ns3