core: Add ValArray and ValArrayTestSuite

core: Improve operator<< implementation, to not represent all numbers with the real and the imaginary parts, instead
use the operator<< of each of the types (Thanks to Peter Barnes)
core: Improve operator<< to remove unnecessary "\t" and remove "std::endl" flush from each row of the matrix (Thanks to Peter Barnes)
core: Improve constructor that accepts std::vector and copies it into the std::valarray, use std::copy operator (Thanks to Peter Barnes)
core: Move out inline implementations from ValArray class outside from the class declaration (Thanks to Peter Barnes)
core: Move implementations from val-array.cc file into val-array.h file to have achieve truely template ValArray (Thanks to Peter Barnes)
core: Use stl library and std::equal for the operator == (Thanks to Peter Barnes)
core: Use stl library and std::equal and lambda expression in IsAlmostEqual (Thanks to Peter Barnes)
core: Use size_t for the index in the ValArray instead of uint16_t (Thanks to Peter Barnes)
Use auto in ValArrayTestCase to be more consistent
Improve Doxygen
Remove cast to (uint16_t)
core: Remove definition of ValArray for int, double, complex,they are not needed any more after moving all implementation to the header file
Remove accidentally added new lines in core/CMakeLists.
This commit is contained in:
Biljana Bojovic
2023-02-27 20:22:34 +01:00
parent f3f09ddf46
commit abcee41e3d
3 changed files with 1027 additions and 0 deletions

View File

@@ -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

743
src/core/model/val-array.h Normal file
View File

@@ -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 <bbojovic@cttc.es>
*/
#ifndef VAL_ARRAY_H
#define VAL_ARRAY_H
#include <ns3/assert.h>
#include <ns3/simple-ref-count.h>
#include <complex>
#include <valarray>
#include <vector>
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 T>
class ValArray : public SimpleRefCount<ValArray<T>>
{
public:
// instruct the compiler to generate the default constructor
ValArray<T>() = 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<T>(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<T> values to initialize the elements.
* \param values std::valarray<T> that will be used to initialize elements of 1D array
*/
explicit ValArray<T>(const std::valarray<T>& values);
/**
* \brief Constructor creates a single 1D array of values.size () elements and 1 column,
* and moves std::valarray<T> values to initialize the elements.
* \param values std::valarray<T> that will be moved to initialize elements of 1D array
*/
ValArray<T>(std::valarray<T>&& values);
/**
* \brief Constructor creates a single 1D array of values.size () elements and 1 column,
* and uses values std::vector<T> to initialize the elements.
* \param values std::vector<T> that will be used to initialize elements of 1D array
*/
explicit ValArray<T>(const std::vector<T>& values);
/**
* \brief Constructor creates a single 2D array of numRows and numCols, and uses
* std::valarray<T> values to initialize the elements.
* \param numRows the number of rows
* \param numCols the number of columns
* \param values valarray<T> that will be used to initialize elements of 3D array
*/
ValArray<T>(uint16_t numRows, uint16_t numCols, const std::valarray<T>& values);
/**
* \brief Constructor creates a single 2D array of numRows and numCols, and moves
* std::valarray<T> values to initialize the elements.
* \param numRows the number of rows
* \param numCols the number of columns
* \param values valarray<T> that will be used to initialize elements of 3D array
*/
ValArray<T>(uint16_t numRows, uint16_t numCols, std::valarray<T>&& values);
/**
* \brief Constructor creates the 3D array of numRows x numCols x numPages dimensions,
* and uses std::valarray<T> 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<T> that will be used to initialize elements of 3D array
*/
ValArray<T>(uint16_t numRows,
uint16_t numCols,
uint16_t numPages,
const std::valarray<T>& values);
/**
* \brief Constructor creates the 3D array of numRows x numCols x numPages dimensions,
* and moves std::valarray<T> 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<T> that will be used to initialize elements of 3D array
*/
ValArray<T>(uint16_t numRows, uint16_t numCols, uint16_t numPages, std::valarray<T>&& values);
/** instruct the compiler to generate the implicitly declared destructor*/
virtual ~ValArray<T>() = default;
/** instruct the compiler to generate the implicitly declared copy constructor*/
ValArray<T>(const ValArray<T>&) = default;
/**
* \brief Copy assignment operator.
* Instruct the compiler to generate the implicitly declared copy assignment operator.
* \return a reference to the assigned object
*/
ValArray<T>& operator=(const ValArray<T>&) = default;
/** instruct the compiler to generate the implicitly declared move constructor*/
ValArray<T>(ValArray<T>&&) 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<T>& operator=(ValArray<T>&&) 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<T> operator*(const T& rhs) const;
/**
* \brief operator+ definition for ValArray<T>.
* \param rhs The rhs ValArray to be added to this ValArray.
* \return the ValArray instance that holds the results of the operator+
*/
ValArray<T> operator+(const ValArray<T>& rhs) const;
/**
* \brief binary operator- definition for ValArray<T>.
* \param rhs The rhs ValArray to be subtracted from this ValArray.
* \return the ValArray instance that holds the results of the operator-
*/
ValArray<T> operator-(const ValArray<T>& rhs) const;
/**
* \brief unary operator- definition for ValArray<T>.
* \return the ValArray instance that holds the results of the operator-
*/
ValArray<T> operator-() const;
/**
* \brief operator+= definition for ValArray<T>.
* \param rhs The rhs ValArray to be added to this ValArray.
* \return a reference to this ValArray instance
*/
ValArray<T>& operator+=(const ValArray<T>& rhs);
/**
* \brief operator-= definition for ValArray<T>.
* \param rhs The rhs ValArray to be subtracted from this ValArray.
** \return a reference to this ValArray instance
*/
ValArray<T>& operator-=(const ValArray<T>& rhs);
/**
* \brief operator== definition for ValArray<T>.
* \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<T>& rhs) const;
/**
* \brief operator!= definition for ValArray<T>.
* \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<T>& 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<T>& 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<T>& 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<T>& 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<T>.
*/
const std::valarray<T>& 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<T> m_values; //!< The data values
};
/*************************************************
** Class ValArray inline implementations
************************************************/
template <class T>
inline uint16_t
ValArray<T>::GetNumRows() const
{
return m_numRows;
};
template <class T>
inline uint16_t
ValArray<T>::GetNumCols() const
{
return m_numCols;
};
template <class T>
inline uint16_t
ValArray<T>::GetNumPages() const
{
return m_numPages;
};
template <class T>
inline size_t
ValArray<T>::GetSize() const
{
return m_values.size();
}
template <class T>
inline T&
ValArray<T>::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 <class T>
inline const T&
ValArray<T>::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 <class T>
inline T&
ValArray<T>::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 <class T>
inline const T&
ValArray<T>::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 <class T>
inline T&
ValArray<T>::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 <class T>
inline const T&
ValArray<T>::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 <class T>
inline ValArray<T>
ValArray<T>::operator*(const T& rhs) const
{
return ValArray<T>(m_numRows,
m_numCols,
m_numPages,
m_values * std::valarray<T>(rhs, m_numRows * m_numCols * m_numPages));
}
template <class T>
inline ValArray<T>
ValArray<T>::operator+(const ValArray<T>& rhs) const
{
AssertEqualDims(rhs);
return ValArray<T>(m_numRows, m_numCols, m_numPages, m_values + rhs.m_values);
}
template <class T>
inline ValArray<T>
ValArray<T>::operator-(const ValArray<T>& rhs) const
{
AssertEqualDims(rhs);
return ValArray<T>(m_numRows, m_numCols, m_numPages, m_values - rhs.m_values);
}
template <class T>
inline ValArray<T>
ValArray<T>::operator-() const
{
return ValArray<T>(m_numRows, m_numCols, m_numPages, -m_values);
}
template <class T>
inline ValArray<T>&
ValArray<T>::operator+=(const ValArray<T>& rhs)
{
AssertEqualDims(rhs);
m_values += rhs.m_values;
return *this;
}
template <class T>
inline ValArray<T>&
ValArray<T>::operator-=(const ValArray<T>& rhs)
{
AssertEqualDims(rhs);
m_values -= rhs.m_values;
return *this;
}
template <class T>
inline T*
ValArray<T>::GetPagePtr(uint16_t pageIndex)
{
NS_ASSERT_MSG(pageIndex < m_numPages, "Invalid page index.");
return &(m_values[m_numRows * m_numCols * pageIndex]);
};
template <class T>
inline const T*
ValArray<T>::GetPagePtr(uint16_t pageIndex) const
{
NS_ASSERT_MSG(pageIndex < m_numPages, "Invalid page index.");
return &(m_values[m_numRows * m_numCols * pageIndex]);
};
template <class T>
inline bool
ValArray<T>::EqualDims(const ValArray<T>& rhs) const
{
return (m_numRows == rhs.m_numRows) && (m_numCols == rhs.m_numCols) &&
(m_numPages == rhs.m_numPages);
}
template <class T>
inline T&
ValArray<T>::operator[](size_t index)
{
return (*this)(index);
}
template <class T>
inline const T&
ValArray<T>::operator[](size_t index) const
{
return (*this)(index);
}
template <class T>
inline const std::valarray<T>&
ValArray<T>::GetValues() const
{
return m_values;
}
template <class T>
inline T&
ValArray<T>::Elem(size_t row, size_t col, size_t page)
{
return (*this)(row, col, page);
};
template <class T>
inline const T&
ValArray<T>::Elem(size_t row, size_t col, size_t page) const
{
return (*this)(row, col, page);
};
/*************************************************
** Class ValArray non-inline implementations
************************************************/
template <class T>
ValArray<T>::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 <class T>
ValArray<T>::ValArray(const std::valarray<T>& values)
: m_numRows{(uint16_t)values.size()},
m_numCols{1},
m_numPages{1},
m_values{values}
{
}
template <class T>
ValArray<T>::ValArray(std::valarray<T>&& values)
: m_numRows{(uint16_t)values.size()},
m_numCols{1},
m_numPages{1},
m_values{std::move(values)}
{
}
template <class T>
ValArray<T>::ValArray(const std::vector<T>& 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 <class T>
ValArray<T>::ValArray(uint16_t numRows, uint16_t numCols, const std::valarray<T>& 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 <class T>
ValArray<T>::ValArray(uint16_t numRows, uint16_t numCols, std::valarray<T>&& 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 <class T>
ValArray<T>::ValArray(uint16_t numRows,
uint16_t numCols,
uint16_t numPages,
const std::valarray<T>& 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 <class T>
ValArray<T>::ValArray(uint16_t numRows,
uint16_t numCols,
uint16_t numPages,
std::valarray<T>&& 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 <class T>
bool
ValArray<T>::operator==(const ValArray<T>& rhs) const
{
return EqualDims(rhs) &&
std::equal(std::begin(m_values), std::end(m_values), std::begin(rhs.m_values));
}
template <class T>
bool
ValArray<T>::operator!=(const ValArray<T>& rhs) const
{
return !((*this) == rhs);
}
template <class T>
bool
ValArray<T>::IsAlmostEqual(const ValArray<T>& 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 <class T>
void
ValArray<T>::AssertEqualDims(const ValArray<T>& 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 <class T>
std::ostream&
operator<<(std::ostream& os, const ValArray<T>& 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

View File

@@ -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 <bbojovic@cttc.es>
*/
#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 T>
class ValArrayTestCase : public TestCase
{
public:
/** Default constructor*/
ValArrayTestCase<T>() = default;
/**
* Constructor
*
* \param [in] name reference name
*/
ValArrayTestCase<T>(const std::string name);
/** Destructor. */
~ValArrayTestCase<T>() override;
/**
* \brief Copy constructor.
* Instruct the compiler to generate the implicitly declared copy constructor
*/
ValArrayTestCase<T>(const ValArrayTestCase<T>&) = default;
/**
* \brief Copy assignment operator.
* Instruct the compiler to generate the implicitly declared copy assignment operator.
* \return A reference to this ValArrayTestCase
*/
ValArrayTestCase<T>& operator=(const ValArrayTestCase<T>&) = default;
/**
* \brief Move constructor.
* Instruct the compiler to generate the implicitly declared move constructor
*/
ValArrayTestCase<T>(ValArrayTestCase<T>&&) noexcept = default;
/**
* \brief Move assignment operator.
* Instruct the compiler to generate the implicitly declared copy constructor
* \return A reference to this ValArrayTestCase
*/
ValArrayTestCase<T>& operator=(ValArrayTestCase<T>&&) noexcept = default;
protected:
private:
void DoRun() override;
};
template <class T>
ValArrayTestCase<T>::ValArrayTestCase(const std::string name)
: TestCase(name)
{
}
template <class T>
ValArrayTestCase<T>::~ValArrayTestCase<T>()
{
}
template <class T>
void
ValArrayTestCase<T>::DoRun()
{
ValArray<T> v1 = ValArray<T>(2, 3);
for (auto i = 0; i < v1.GetNumRows(); ++i)
{
for (auto j = 0; j < v1.GetNumCols(); ++j)
{
v1(i, j) = 1;
}
}
ValArray<T> v2 = ValArray<T>(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<T> 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<T> 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<T> 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<int> initArray1{0, 1, 2, 3, 4, 5, 6, 7};
std::valarray<T> valArray1(initArray1.size()); // length is 8 elements
for (size_t i = 0; i < initArray1.size(); i++)
{
valArray1[i] = static_cast<T>(initArray1[i]);
}
ValArray<T> v6 = ValArray<T>(2, 4, valArray1);
// test constructro that moves valArray
NS_LOG_INFO("valarray1 size before move: " << valArray1.size());
ValArray<T> v11 = ValArray<T>(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<int> 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<T>(initArray2[testIndex]),
"The values are not equal.");
testIndex++;
}
}
// test constructor with initialization valArray for 3D array
std::valarray<int> initArray3{0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4, 5, 6, 7};
std::valarray<T> valArray2(initArray3.size()); // length is 8 elements
for (size_t i = 0; i < initArray3.size(); i++)
{
valArray2[i] = static_cast<T>(initArray3[i]);
}
ValArray<T> v7 = ValArray<T>(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<int> 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<T>(initArray4[testIndex]),
"The values are not equal.");
testIndex++;
}
}
}
// multiplication with a scalar value with 3D array
ValArray<T> v8 = v7 * (static_cast<T>(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<T>(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<T> v9(v8.GetNumRows(), v8.GetNumCols(), v8.GetNumPages());
v9 += v8;
NS_LOG_INFO("v9 += v8" << v9);
ValArray<T> 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<int>(std::valarray({1, 2, 3})),
ValArray<int>(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<T>(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<T> v12 = ValArray(std::vector<T>({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<double>("Test ValArray<double>"));
AddTestCase(new ValArrayTestCase<std::complex<double>>("Test ValArray<std::complex<double>>"));
AddTestCase(new ValArrayTestCase<int>("Test ValArray<int>"));
}
/**
* \ingroup valArray-tests
* ValArrayTestSuite instance variable.
*/
static ValArrayTestSuite g_valArrayTestSuite;
} // namespace tests
} // namespace ns3