/*------------------------------------------------------------------------------ * Copyright (c) 2023 by Bai Bing (seread@163.com) * See COPYING file for copying and redistribution conditions. * * Alians IT Studio. *----------------------------------------------------------------------------*/ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "_Define.h" #include "core/ComplexOperators.h" #include "core/Endian.h" #include "core/Enums.h" #include "core/Error.h" #include "core/FileSystem.h" #include "core/StaticAsserts.h" #include "core/String.h" #include "core/TypeTraits.h" #include "ASIterators.h" #include "ASShape.h" #include "ASSlice.h" #include "core/FunCheck.h" namespace ais { template > class AIS_EXPORT Matrix { private: STATIC_ASSERT_VALID_DTYPE(dtype); static_assert(is_same_v, "value_type and Allocator::value_type must match"); using AllocType = typename std::allocator_traits::template rebind_alloc; using AllocTraits = std::allocator_traits; using self_type = Matrix; public: using value_type = dtype; using allocator_type = Allocator; using pointer = typename AllocTraits::pointer; using const_pointer = typename AllocTraits::const_pointer; using reference = dtype &; using const_reference = const dtype &; using difference_type = typename AllocTraits::difference_type; using iterator = Iterator; using const_iterator = ConstIterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; using column_iterator = ColumnIterator; using const_column_iterator = ConstColumnIterator; using reverse_column_iterator = std::reverse_iterator; using const_reverse_column_iterator = std::reverse_iterator; //============================================================================ // Method Description: /// Default Constructor, not very useful... /// Matrix() = default; //============================================================================ // Method Description: /// Constructor /// /// @param squareSize: square number of rows and columns /// explicit Matrix(size_t squareSize) : shape_(squareSize, squareSize), size_(squareSize * squareSize) { new_matrix(); } //============================================================================ // Method Description: /// Constructor /// /// @param rows /// @param columns /// Matrix(size_t rows, size_t columns) : shape_(rows, columns), size_(rows * columns) { new_matrix(); } //============================================================================ // Method Description: /// Constructor /// /// @param shape /// explicit Matrix(const Shape &shape) : shape_(shape), size_(shape_.size()) { new_matrix(); } //============================================================================ // Method Description: /// Constructor /// /// @param list /// Matrix(std::initializer_list list) : shape_(1, (list.size())), size_(shape_.size()) { new_matrix(); // copy data from shape if (size_ > 0) { std::copy(std::execution::par_unseq, list.begin(), list.end(), begin()); } } //============================================================================ // Method Description: /// Constructor /// /// @param list: 2D initializer list /// Matrix(const std::initializer_list> &list) : shape_((list.size()), 0) { for (const auto &li : list) { if (shape_.cols == 0) { shape_.cols = (li.size()); } else if (li.size() != shape_.cols) { THROW_INVALID_ARGUMENT("All rows of the initializer list needs to have the same number of elements"); } } size_ = shape_.size(); new_matrix(); size_t row = 0; for (const auto &li : list) { const auto ptr = begin() += row * shape_.cols; std::copy(std::execution::par_unseq, li.begin(), li.end(), ptr); ++row; } } //============================================================================ // Method Description: /// Constructor /// /// @param array /// @param copy: (optional) boolean for whether to make a copy and own the data, or /// act as a non-owning shell. Default true. /// template , int> = 0> Matrix(std::array &array, bool copy = true) : shape_(1, arraySize), size_(shape_.size()) { if (copy) { new_matrix(); if (size_ > 0) { std::copy(std::execution::par_unseq, array.begin(), array.end(), begin()); } } else { array_ = array.data(); ownsPtr_ = false; } } //============================================================================ // Method Description: /// Constructor /// /// @param array2D /// @param copy: (optional) boolean for whether to make a copy and own the data, or /// act as a non-owning shell. Default true. /// template Matrix(std::array, d0Size> &array2D, bool copy = true) : shape_(d0Size, d1Size), size_(shape_.size()) { if (copy) { new_matrix(); if (size_ > 0) { const auto start = array2D.front().begin(); std::copy(std::execution::par_unseq, start, start + size_, begin()); } } else { array_ = array2D.front().data(); ownsPtr_ = false; } } //============================================================================ // Method Description: /// Constructor /// /// @param vector /// @param copy: (optional) boolean for whether to make a copy and own the data, or /// act as a non-owning shell. Default true. /// template , int> = 0> Matrix(std::vector &vector, bool copy = true) : shape_(1, vector.size()), size_(shape_.size()) { if (copy) { new_matrix(); if (size_ > 0) { std::copy(std::execution::par_unseq, vector.begin(), vector.end(), begin()); } } else { array_ = vector.data(); ownsPtr_ = false; } } //============================================================================ // Method Description: /// Constructor /// /// @param vector2D /// explicit Matrix(const std::vector> &vector2D) : shape_(vector2D.size(), 0) { for (const auto &row : vector2D) { if (shape_.cols == 0) { shape_.cols = row.size(); } else if (row.size() != shape_.cols) { THROW_INVALID_ARGUMENT("All rows of the 2d vector need to have the same number of elements"); } } size_ = shape_.size(); new_matrix(); auto currentPosition = begin(); for (const auto &row : vector2D) { std::copy(std::execution::par_unseq, row.begin(), row.end(), currentPosition); currentPosition += shape_.cols; } } //============================================================================ // Method Description: /// Constructor /// /// @param list /// explicit Matrix(const std::list &list) : shape_(1, list.size()), size_(shape_.size()) { new_matrix(); if (size_ > 0) { std::copy(std::execution::par_unseq, list.begin(), list.end(), begin()); } } //============================================================================ // Method Description: /// Constructor /// /// @param begin /// @param end /// template ::value_type, dtype>, int> = 0> Matrix(Iterator begin, Iterator end) : shape_(1, (std::distance(begin, end))), size_(shape_.size()) { new_matrix(); if (size_ > 0) { std::copy(std::execution::par_unseq, begin, end, begin()); } } //============================================================================ // Method Description: /// Constructor. Copies the contents of the buffer into /// the array. /// /// @param ptr: const_pointer to beginning of buffer /// @param size: number of elements in buffer /// Matrix(const_pointer ptr, size_t size) : shape_(1, size), size_(size) { new_matrix(); if (ptr != nullptr && size_ > 0) { std::copy(std::execution::par_unseq, ptr, ptr + size_, begin()); } } //============================================================================ // Method Description: /// Constructor. Copies the contents of the buffer into /// the array. /// /// @param ptr: const_pointer to beginning of buffer /// @param rows: number of rows of the buffer /// @param columns: number of cols of the buffer /// template , int> = 0, std::enable_if_t, int> = 0> Matrix(const_pointer ptr, UIntType1 rows, UIntType2 columns) : shape_(rows, columns), size_(shape_.size()) { new_matrix(); if (ptr != nullptr && size_ > 0) { std::copy(std::execution::par_unseq, ptr, ptr + size_, begin()); } } //============================================================================ // Method Description: /// Constructor. Operates as a shell around an already existing /// array of data. /// /// @param ptr: pointer to beginning of the array /// @param size: the number of elements in the array /// @param takeOwnership: whether or not to take ownership of the data /// and call delete[] in the destructor. /// Matrix(pointer ptr, size_t size, bool takeOwnership) noexcept : shape_(1, size), size_(size), array_(ptr), ownsPtr_(takeOwnership) { } //============================================================================ // Method Description: /// Constructor. Operates as a shell around an already existing /// array of data. /// /// @param ptr: pointer to beginning of the array /// @param rows: the number of rows in the array /// @param columns: the number of column in the array /// @param takeOwnership: whether or not to take ownership of the data /// and call delete[] in the destructor. /// Matrix(pointer ptr, size_t rows, size_t columns, bool takeOwnership) noexcept : shape_(rows, columns), size_(rows * columns), array_(ptr), ownsPtr_(takeOwnership) { } //============================================================================ // Method Description: /// Copy Constructor /// /// @param otherArray /// Matrix(const Matrix &otherArray) : shape_(otherArray.shape_), size_(otherArray.size_), endianess_(otherArray.endianess_) { new_matrix(); if (size_ > 0) { std::copy(std::execution::par_unseq, otherArray.cbegin(), otherArray.cend(), begin()); } } //============================================================================ // Method Description: /// Move Constructor /// /// @param otherArray /// Matrix(Matrix &&otherArray) noexcept : shape_(otherArray.shape_), size_(otherArray.size_), endianess_(otherArray.endianess_), array_(otherArray.array_), ownsPtr_(otherArray.ownsPtr_) { otherArray.shape_.rows = otherArray.shape_.cols = 0; otherArray.size_ = 0; otherArray.ownsPtr_ = false; otherArray.array_ = nullptr; } //============================================================================ // Method Description: /// Destructor /// ~Matrix() noexcept { clear(); } //============================================================================ // Method Description: /// Assignment operator, performs a deep copy /// /// @param rhs /// @return Matrix /// Matrix &operator=(const Matrix &rhs) { if (&rhs != this) { if (rhs.size_ > 0) { new_matrix(rhs.shape_); endianess_ = rhs.endianess_; std::copy(std::execution::par_unseq, rhs.cbegin(), rhs.cend(), begin()); } } return *this; } //============================================================================ // Method Description: /// Assignment operator, sets the entire array to a single /// scaler value. /// /// @param value /// @return Matrix /// Matrix &operator=(value_type value) noexcept { if (array_ != nullptr) { std::fill(std::execution::par_unseq, begin(), end(), value); } return *this; } //============================================================================ // Method Description: /// Move operator, performs a deep move /// /// @param rhs /// @return Matrix /// Matrix &operator=(Matrix &&rhs) noexcept { if (&rhs != this) { clear(); shape_ = rhs.shape_; size_ = rhs.size_; endianess_ = rhs.endianess_; array_ = rhs.array_; ownsPtr_ = rhs.ownsPtr_; rhs.shape_.rows = rhs.shape_.cols = rhs.size_ = 0; rhs.array_ = nullptr; rhs.ownsPtr_ = false; } return *this; } //============================================================================ // Method Description: /// 1D access operator with no bounds checking /// /// @param pos /// @return value /// inline reference operator[](int64_t pos) { //if ((size_t)std::abs(pos) >= size_) //{ // THROW_INVALID_ARGUMENT("pos out of data range"); //} if (pos < 0) { pos += size_; } return array_[pos]; } //============================================================================ // Method Description: /// const 1D access operator with no bounds checking /// /// @param pos /// @return value /// inline const_reference operator[](int64_t pos) const { //if ((size_t)std::abs(pos) >= size_) //{ // THROW_INVALID_ARGUMENT("pos out of data range"); //} if (pos < 0) { // return value from backward, just like python [-1] pos += size_; } return array_[pos]; } //============================================================================ // Method Description: /// 2D access operator with no bounds checking /// /// @param rowPos /// @param columnPos /// @return value /// inline reference operator()(int64_t rowPos, int64_t columnPos) { if (rowPos < 0) { rowPos += shape_.rows; } if (columnPos < 0) { columnPos += shape_.cols; } auto pos = rowPos * shape_.cols + columnPos; //if (pos >= size_) //{ // THROW_INVALID_ARGUMENT("pos out of data range"); //} return array_[pos]; } //============================================================================ // Method Description: /// const 2D access operator with no bounds checking /// /// @param rowPos /// @param columnPos /// @return value /// inline const_reference operator()(int64_t rowPos, int64_t columnPos) const { if (rowPos < 0) { rowPos += shape_.rows; } if (columnPos < 0) { columnPos += shape_.cols; } auto pos = rowPos * shape_.cols + columnPos; //if (pos >= size_) //{ // THROW_INVALID_ARGUMENT("pos out of data range"); //} return array_[pos]; } //============================================================================ // Method Description: /// 1D Slicing access operator with bounds checking. /// returned array is of the range [start, stop). /// /// @param slice /// @return Matrix /// inline Matrix operator[](const Slice &slice) const { Slice sliceCopy(slice); size_t counter = 0; Matrix returnArray(1, sliceCopy.element_number(size_)); for (size_t i = sliceCopy.start; i < sliceCopy.stop; i += sliceCopy.step) { returnArray[counter++] = at(i); } return returnArray; } //============================================================================ // Method Description: /// Returns the values from the input mask /// /// @param mask /// @return Matrix /// inline Matrix operator[](const Matrix &mask) const { if (mask.shape() != shape_) { THROW_INVALID_ARGUMENT( "input mask must have the same shape as the matrix it will be masking."); } auto indices = mask.flatnonzero(); auto outArray = Matrix(1, indices.size()); for (size_t i = 0; i < indices.size(); ++i) { outArray[i] = operator[](indices[i]); } return outArray; } //============================================================================ // Method Description: /// Returns the values from the input indices /// /// @param indices /// @return Matrix /// /// template >, int> = 0> inline Matrix operator[](const Indices &indices) const { if (indices.max().item() > size_ - 1) { THROW_INVALID_ARGUMENT("input indices must be less than the array size."); } auto outArray = Matrix(1, static_cast(indices.size())); size_t i = 0; for (auto &index : indices) { outArray[i++] = operator[](index); } return outArray; } //============================================================================ // Method Description: /// 2D Slicing access operator with bounds checking. /// returned array is of the range [start, stop). /// /// @param rowSlice /// @param columnSlice /// @return Matrix /// inline Matrix operator()(Slice rowSlice, Slice columnSlice) const { Matrix returnArray(rowSlice.element_number(shape_.rows), columnSlice.element_number(shape_.cols)); size_t rowCounter = 0; size_t columnCounter = 0; for (size_t row = rowSlice.start; row < rowSlice.stop; row += rowSlice.step) { for (size_t col = columnSlice.start; col < columnSlice.stop; col += columnSlice.step) { returnArray(rowCounter, columnCounter++) = at(row, col); } columnCounter = 0; ++rowCounter; } return returnArray; } //============================================================================ // Method Description: /// 2D Slicing access operator with bounds checking. /// returned array is of the range [start, stop). /// /// @param rowSlice /// @param columnPos /// @return Matrix /// inline Matrix operator()(Slice rowSlice, size_t columnPos) const { Matrix returnArray(rowSlice.element_number(shape_.rows), 1); size_t rowCounter = 0; for (size_t row = rowSlice.start; row < rowSlice.stop; row += rowSlice.step) { returnArray(rowCounter++, 0) = at(row, columnPos); } return returnArray; } //============================================================================ // Method Description: /// 2D Slicing access operator with bounds checking. /// returned array is of the range [start, stop). /// /// @param rowPos /// @param columnSlice /// @return Matrix /// inline Matrix operator()(size_t rowPos, Slice columnSlice) const { Matrix returnArray(1, columnSlice.element_number(shape_.cols)); size_t columnCounter = 0; for (size_t col = columnSlice.start; col < columnSlice.stop; col += columnSlice.step) { returnArray(0, columnCounter++) = at(rowPos, col); } return returnArray; } //============================================================================ // Method Description: /// 2D index access operator with bounds checking. /// returned array is of the range. /// /// @param rowIndices /// @param columnPos /// @return Matrix /// template > || is_same_v>, int> = 0> inline Matrix operator()(const Indices &rowIndices, size_t columnPos) const { const Matrix columnIndices = {columnPos}; return operator()(rowIndices, columnIndices); } //============================================================================ // Method Description: /// 2D index access operator with bounds checking. /// returned array is of the range. /// /// @param rowIndices /// @param columnSlice /// @return Matrix /// template > || is_same_v>, int> = 0> inline Matrix operator()(const Indices &rowIndices, Slice columnSlice) const { return operator()(rowIndices, to_indices(columnSlice, Axis::COLUMN)); } //============================================================================ // Method Description: /// 2D index access operator with bounds checking. /// returned array is of the range. /// /// @param rowIndex /// @param columnIndices /// @return Matrix /// template > || is_same_v>, int> = 0> inline Matrix operator()(size_t rowIndex, const Indices &columnIndices) const { const Matrix rowIndices = {rowIndex}; return operator()(rowIndices, columnIndices); } //============================================================================ // Method Description: /// 2D index access operator with bounds checking. /// returned array is of the range. /// /// @param rowSlice /// @param columnIndices /// @return Matrix /// template > || is_same_v>, int> = 0> inline Matrix operator()(Slice rowSlice, const Indices &columnIndices) const { return operator()(to_indices(rowSlice, Axis::ROW), columnIndices); } //============================================================================ // Method Description: /// 2D index access operator with bounds checking. /// returned array is of the range. /// /// @param rowIndices /// @param columnIndices /// @return Matrix /// template > || is_same_v>, int> = 0, enable_if_t> || is_same_v>, int> = 0> inline Matrix operator()(RowIndices rowIndices, ColumnIndices columnIndices) const { rowIndices.sort(); columnIndices.sort(); std::vector rowIndicesUnique(rowIndices.size()); std::vector columnIndicesUnique(columnIndices.size()); const auto lastRow = std::unique_copy(std::execution::par_unseq, rowIndices.begin(), rowIndices.end(), rowIndicesUnique.begin()); const auto lastColumn = std::unique_copy(std::execution::par_unseq, columnIndices.begin(), columnIndices.end(), columnIndicesUnique.begin()); Matrix returnArray((lastRow - rowIndicesUnique.begin()), (lastColumn - columnIndicesUnique.begin())); size_t rowCounter = 0; for (auto rowIter = rowIndicesUnique.begin(); rowIter != lastRow; ++rowIter) { size_t columnCounter = 0; for (auto columnIter = columnIndicesUnique.begin(); columnIter != lastColumn; ++columnIter) { returnArray(rowCounter, columnCounter++) = at(*rowIter, *columnIter); } ++rowCounter; } return returnArray; } //============================================================================ // Method Description: /// Returns a Slice object for slicing a row to the end of /// array. /// /// @param startIndex (default 0) /// @param step (default 1) /// @return Slice /// Slice column_slice(size_t startIndex = 0, size_t step = 1) const noexcept { return Slice(startIndex, shape_.cols, step); } //============================================================================ // Method Description: /// Returns a Slice object for slicing a column to the end /// of the array. /// /// @param startIndex (default 0) /// @param step (default 1) /// @return Slice /// Slice row_slice(size_t startIndex = 0, size_t step = 1) const noexcept { return Slice(startIndex, shape_.rows, step); } //============================================================================ // Method Description: /// 1D access method with bounds checking /// /// @param pos /// @return value /// inline reference at(int64_t pos) { if ((size_t)std::abs(pos) >= size_) { std::string errStr = "Input index " + std::to_string(pos); errStr += " is out of bounds for array of size " + std::to_string(size_) + "."; THROW_INVALID_ARGUMENT(errStr); } return operator[](pos); } //============================================================================ // Method Description: /// const 1D access method with bounds checking /// /// @param pos /// @return value /// inline const_reference at(int64_t pos) const { if (std::abs(pos) >= size_) { std::string errStr = "Input index " + std::to_string(pos); errStr += " is out of bounds for array of size " + std::to_string(size_) + "."; THROW_INVALID_ARGUMENT(errStr); } return operator[](pos); } //============================================================================ // Method Description: /// 2D access method with bounds checking /// /// @param rowPos /// @param columnPos /// @return value /// inline reference at(int64_t rowPos, int64_t columnPos) { // this doesn't allow for calling the first element as -size_... // but why would you really want to do that anyway? if ((size_t)std::abs(rowPos) >= shape_.rows) { std::string errStr = "Row index " + std::to_string(rowPos); errStr += " is out of bounds for array of size " + std::to_string(shape_.rows) + "."; THROW_INVALID_ARGUMENT(errStr); } // this doesn't allow for calling the first element as -size_... // but why would you really want to that anyway? if ((size_t)std::abs(columnPos) >= shape_.cols) { std::string errStr = "Column index " + std::to_string(columnPos); errStr += " is out of bounds for array of size " + std::to_string(shape_.cols) + "."; THROW_INVALID_ARGUMENT(errStr); } return operator()(rowPos, columnPos); } //============================================================================ // Method Description: /// const 2D access method with bounds checking /// /// @param rowPos /// @param columnPos /// @return value /// inline const_reference at(int64_t rowPos, int64_t columnPos) const { // this doesn't allow for calling the first element as -size_... // but why would you really want to do that anyway? if ((size_t)std::abs(rowPos) >= shape_.rows) { std::string errStr = "Row index " + std::to_string(rowPos); errStr += " is out of bounds for array of size " + std::to_string(shape_.rows) + "."; THROW_INVALID_ARGUMENT(errStr); } // this doesn't allow for calling the first element as -size_... // but why would you really want to do that anyway? if ((size_t)std::abs(columnPos) >= shape_.cols) { std::string errStr = "Column index " + std::to_string(columnPos); errStr += " is out of bounds for array of size " + std::to_string(shape_.cols) + "."; THROW_INVALID_ARGUMENT(errStr); } return operator()(rowPos, columnPos); } //============================================================================ // Method Description: /// 2D access method with bounds checking /// /// @param rowPos /// @param columnPos /// @return value /// std::pair get_pos(iterator ci) const { if (size_ == 0) THROW_INVALID_ARGUMENT("Matrix is empty."); auto dis = std::distance(iterator(array_), ci); auto r = int32_t(dis / shape_.cols); auto c = int32_t(dis % shape_.cols); return std::make_pair(r, c); } //============================================================================ // Method Description: /// get from index /// /// @return value /// std::pair get_pos(size_t i) const { if (size_ == 0) THROW_INVALID_ARGUMENT("Matrix is empty."); auto r = int32_t(i / shape_.cols); auto c = int32_t(i % shape_.cols); return std::make_pair(r, c); } //============================================================================ // Method Description: /// Return index of specials element /// /// @param rowPos /// @param columnPos /// @return index /// const size_t index(int64_t rowPos, int64_t columnPos) const { if (rowPos < 0) { rowPos += shape_.rows; } if (columnPos < 0) { columnPos += shape_.cols; } auto pos = rowPos * shape_.cols + columnPos; if (pos >= size_) { THROW_INVALID_ARGUMENT("pos out of data range"); } return pos; } //============================================================================ // Method Description:i /// const 1D access method with bounds checking /// /// @param slice /// @return Matrix /// Matrix at(const Slice &slice) const { // the slice operator already provides bounds checking. just including // the at method for completeness return operator[](slice); } //============================================================================ // Method Description: /// const 2D access method with bounds checking /// /// @param rowSlice /// @param columnSlice /// @return Matrix /// Matrix at(const Slice &rowSlice, const Slice &columnSlice) const { // the slice operator already provides bounds checking. just including // the at method for completeness return operator()(rowSlice, columnSlice); } //============================================================================ // Method Description: /// const 2D access method with bounds checking /// /// @param rowSlice /// @param columnPos /// @return Matrix /// Matrix at(const Slice &rowSlice, size_t columnPos) const { // the slice operator already provides bounds checking. just including // the at method for completeness return operator()(rowSlice, columnPos); } //============================================================================ // Method Description: /// const 2D access method with bounds checking /// /// @param rowPos /// @param columnSlice /// @return Matrix /// Matrix at(size_t rowPos, const Slice &columnSlice) const { // the slice operator already provides bounds checking. just including // the at method for completeness return operator()(rowPos, columnSlice); } //============================================================================ // Method Description: /// const 2D access method with bounds checking /// /// @param rowIndices /// @param columnIndices /// @return Matrix /// Matrix at(const Matrix &rowIndices, const Matrix &columnIndices) const { // the slice operator already provides bounds checking. just including // the at method for completeness return operator()(rowIndices, columnIndices); } //============================================================================ // Method Description: /// iterator to the beginning of the flattened array /// @return iterator /// iterator begin() noexcept { return iterator(array_); } //============================================================================ // Method Description: /// iterator to the beginning of the input row /// /// @param row /// @return iterator /// iterator begin(size_t row) { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return begin() += (row * shape_.cols); } //============================================================================ // Method Description: /// const iterator to the beginning of the flattened array /// @return const_iterator /// const_iterator begin() const noexcept { return cbegin(); } //============================================================================ // Method Description: /// const iterator to the beginning of the input row /// /// @param row /// @return const_iterator /// const_iterator begin(size_t row) const { return cbegin(row); } //============================================================================ // Method Description: /// const iterator to the beginning of the flattened array /// /// @return const_iterator /// const_iterator cbegin() const noexcept { return const_iterator(array_); } //============================================================================ // Method Description: /// const iterator to the beginning of the input row /// /// @param row /// @return const_iterator /// const_iterator cbegin(size_t row) const { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return cbegin() += (row * shape_.cols); } //============================================================================ // Method Description: /// column_iterator to the beginning of the flattened array /// @return column_iterator /// column_iterator colbegin() noexcept { return column_iterator(array_, shape_.rows, shape_.cols); } //============================================================================ // Method Description: /// column_iterator to the beginning of the input column /// /// @param column /// @return column_iterator /// column_iterator colbegin(size_t column) { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return colbegin() += (column * shape_.rows); } //============================================================================ // Method Description: /// const column_iterator to the beginning of the flattened array /// @return const_column_iterator /// const_column_iterator colbegin() const noexcept { return ccolbegin(); } //============================================================================ // Method Description: /// const column_iterator to the beginning of the input column /// /// @param column /// @return const_column_iterator /// const_column_iterator colbegin(size_t column) const { return ccolbegin(column); } //============================================================================ // Method Description: /// const_column_iterator to the beginning of the flattened array /// /// @return const_column_iterator /// const_column_iterator ccolbegin() const noexcept { return const_column_iterator(array_, shape_.rows, shape_.cols); } //============================================================================ // Method Description: /// const_column_iterator to the beginning of the input column /// /// @param column /// @return const_column_iterator /// const_column_iterator ccolbegin(size_t column) const { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return ccolbegin() += (column * shape_.rows); } //============================================================================ // Method Description: /// reverse_iterator to the beginning of the flattened array /// @return reverse_iterator /// reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } //============================================================================ // Method Description: /// reverse_iterator to the beginning of the input row /// /// @param row /// @return reverse_iterator /// reverse_iterator rbegin(size_t row) { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return rbegin() += (shape_.rows - row - 1) * shape_.cols; } //============================================================================ // Method Description: /// const iterator to the beginning of the flattened array /// @return const_iterator /// const_reverse_iterator rbegin() const noexcept { return crbegin(); } //============================================================================ // Method Description: /// const iterator to the beginning of the input row /// /// @param row /// @return const_iterator /// const_reverse_iterator rbegin(size_t row) const { return crbegin(row); } //============================================================================ // Method Description: /// const_reverse_iterator to the beginning of the flattened array /// /// @return const_reverse_iterator /// const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(cend()); } //============================================================================ // Method Description: /// const_reverse_iterator to the beginning of the input row /// /// @param row /// @return const_reverse_iterator /// const_reverse_iterator crbegin(size_t row) const { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return crbegin() += (shape_.rows - row - 1) * shape_.cols; } //============================================================================ // Method Description: /// reverse_column_iterator to the beginning of the flattened array /// @return reverse_column_iterator /// reverse_column_iterator rcolbegin() noexcept { return reverse_column_iterator(colend()); } //============================================================================ // Method Description: /// reverse_column_iterator to the beginning of the input column /// /// @param column /// @return reverse_column_iterator /// reverse_column_iterator rcolbegin(size_t column) { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return rcolbegin() += (shape_.cols - column - 1) * shape_.rows; } //============================================================================ // Method Description: /// const iterator to the beginning of the flattened array /// @return const_iterator /// const_reverse_column_iterator rcolbegin() const noexcept { return crcolbegin(); } //============================================================================ // Method Description: /// const iterator to the beginning of the input column /// /// @param column /// @return const_iterator /// const_reverse_column_iterator rcolbegin(size_t column) const { return crcolbegin(column); } //============================================================================ // Method Description: /// const_reverse_column_iterator to the beginning of the flattened array /// /// @return const_reverse_column_iterator /// const_reverse_column_iterator crcolbegin() const noexcept { return const_reverse_column_iterator(ccolend()); } //============================================================================ // Method Description: /// const_reverse_column_iterator to the beginning of the input column /// /// @param column /// @return const_reverse_column_iterator /// const_reverse_column_iterator crcolbegin(size_t column) const { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return crcolbegin() += (shape_.cols - column - 1) * shape_.rows; } //============================================================================ // Method Description: /// iterator to 1 past the end of the flattened array /// @return iterator /// iterator end() noexcept { return begin() += size_; } //============================================================================ // Method Description: /// iterator to the 1 past end of the row /// /// @param row /// @return iterator /// iterator end(size_t row) { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return begin(row) += shape_.cols; } //============================================================================ // Method Description: /// const iterator to 1 past the end of the flattened array /// @return const_iterator /// const_iterator end() const noexcept { return cend(); } //============================================================================ // Method Description: /// const iterator to the 1 past end of the row /// /// @param row /// @return const_iterator /// const_iterator end(size_t row) const { return cend(row); } //============================================================================ // Method Description: /// const iterator to 1 past the end of the flattened array /// /// @return const_iterator /// const_iterator cend() const noexcept { return cbegin() += size_; } //============================================================================ // Method Description: /// const iterator to 1 past the end of the input row /// /// @param row /// @return const_iterator /// const_iterator cend(size_t row) const { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return cbegin(row) += shape_.cols; } //============================================================================ // Method Description: /// reverse_iterator to 1 past the end of the flattened array /// @return reverse_iterator /// reverse_iterator rend() noexcept { return rbegin() += size_; } //============================================================================ // Method Description: /// reverse_iterator to the 1 past end of the row /// /// @param row /// @return reverse_iterator /// reverse_iterator rend(size_t row) { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return rbegin(row) += shape_.cols; } //============================================================================ // Method Description: /// const_reverse_iterator to 1 past the end of the flattened array /// @return const_reverse_iterator /// const_reverse_iterator rend() const noexcept { return crend(); } //============================================================================ // Method Description: /// const_reverse_iterator to the 1 past end of the row /// /// @param row /// @return const_reverse_iterator /// const_reverse_iterator rend(size_t row) const { return crend(row); } //============================================================================ // Method Description: /// const_reverse_iterator to 1 past the end of the flattened array /// /// @return const_reverse_iterator /// const_reverse_iterator crend() const noexcept { return crbegin() += size_; } //============================================================================ // Method Description: /// const_reverse_iterator to 1 past the end of the input row /// /// @param row /// @return const_reverse_iterator /// const_reverse_iterator crend(size_t row) const { if (row >= shape_.rows) { THROW_INVALID_ARGUMENT("input row is greater than the number of rows in the array."); } return crbegin(row) += shape_.cols; } //============================================================================ // Method Description: /// column_iterator to 1 past the end of the flattened array /// @return column_iterator /// column_iterator colend() noexcept { return colbegin() += size_; } //============================================================================ // Method Description: /// column_iterator to the 1 past end of the column /// /// @param column /// @return column_iterator /// column_iterator colend(size_t column) { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return colbegin(column) += shape_.rows; } //============================================================================ // Method Description: /// const column_iterator to 1 past the end of the flattened array /// @return const_column_iterator /// const_column_iterator colend() const noexcept { return ccolend(); } //============================================================================ // Method Description: /// const column_iterator to the 1 past end of the column /// /// @param column /// @return const_column_iterator /// const_column_iterator colend(size_t column) const { return ccolend(column); } //============================================================================ // Method Description: /// const_column_iterator to 1 past the end of the flattened array /// /// @return const_column_iterator /// const_column_iterator ccolend() const noexcept { return ccolbegin() += size_; } //============================================================================ // Method Description: /// const_column_iterator to 1 past the end of the input col /// /// @param column /// @return const_column_iterator /// const_column_iterator ccolend(size_t column) const { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return ccolbegin(column) += shape_.rows; } //============================================================================ // Method Description: /// reverse_column_iterator to 1 past the end of the flattened array /// @return reverse_column_iterator /// reverse_column_iterator rcolend() noexcept { return rcolbegin() += size_; } //============================================================================ // Method Description: /// reverse_column_iterator to the 1 past end of the column /// /// @param column /// @return reverse_column_iterator /// reverse_column_iterator rcolend(size_t column) { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return rcolbegin(column) += shape_.rows; } //============================================================================ // Method Description: /// const_reverse_column_iterator to 1 past the end of the flattened array /// @return const_reverse_column_iterator /// const_reverse_column_iterator rcolend() const noexcept { return crcolend(); } //============================================================================ // Method Description: /// const_reverse_column_iterator to the 1 past end of the column /// /// @param column /// @return const_reverse_column_iterator /// const_reverse_column_iterator rcolend(size_t column) const { return crcolend(column); } //============================================================================ // Method Description: /// const_reverse_column_iterator to 1 past the end of the flattened array /// /// @return const_reverse_column_iterator /// const_reverse_column_iterator crcolend() const noexcept { return crcolbegin() += size_; } //============================================================================ // Method Description: /// const_reverse_column_iterator to 1 past the end of the input col /// /// @param column /// @return const_reverse_column_iterator /// const_reverse_column_iterator crcolend(size_t column) const { if (column >= shape_.cols) { THROW_INVALID_ARGUMENT("input col is greater than the number of cols in the array."); } return crcolbegin(column) += shape_.rows; } //============================================================================ // Method Description: /// Returns True if all elements evaluate to True or non zero /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix all(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto function = [](dtype i) -> bool { return i != dtype{0}; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {std::all_of(std::execution::par_unseq, cbegin(), cend(), function)}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::all_of(std::execution::par_unseq, cbegin(row), cend(row), function); } return returnArray; } case Axis::ROW: { return transpose().all(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Returns True if all elements evaluate to True or non zero /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix all(size_t begin_index, size_t end_index, Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto function = [](dtype i) -> bool { return i != dtype{0}; }; if ((cbegin() + begin_index) > cend() || (cbegin() + end_index) > cend()) { return {}; } switch (axis) { case Axis::NONE: { Matrix returnArray = {std::all_of(std::execution::par_unseq, cbegin() + begin_index, cend() + end_index, function)}; return returnArray; } case Axis::COLUMN: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); } case Axis::ROW: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); } } } //============================================================================ // Method Description: /// Returns True if any elements evaluate to True or non zero /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix any(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto function = [](dtype i) -> bool { return i != dtype{0}; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {std::any_of(std::execution::par_unseq, cbegin(), cend(), function)}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::any_of(std::execution::par_unseq, cbegin(row), cend(row), function); } return returnArray; } case Axis::ROW: { return transpose().any(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return indices of the maximum values along the given axis. /// Only the first index is returned. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix argmax(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {( std::max_element(std::execution::par_unseq, cbegin(), cend(), comparator) - cbegin())}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = (std::max_element(std::execution::par_unseq, cbegin(row), cend(row), comparator) - cbegin(row)); } return returnArray; } case Axis::ROW: { return transpose().argmax(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return indices of the minimum values along the given axis. /// Only the first index is returned. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix argmin(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {( std::min_element(std::execution::par_unseq, cbegin(), cend(), comparator) - cbegin())}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = (std::min_element(std::execution::par_unseq, cbegin(row), cend(row), comparator) - cbegin(row)); } return returnArray; } case Axis::ROW: { return transpose().argmin(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Returns the indices that would sort this array. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix argsort(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); switch (axis) { case Axis::NONE: { std::vector idx(size_); std::iota(idx.begin(), idx.end(), 0); const auto function = [this](size_t i1, size_t i2) noexcept -> bool { return (*this)[i1] < (*this)[i2]; }; std::stable_sort(idx.begin(), idx.end(), function); return Matrix(idx); } case Axis::COLUMN: { Matrix returnArray(shape_); std::vector idx(shape_.cols); for (size_t row = 0; row < shape_.rows; ++row) { std::iota(idx.begin(), idx.end(), 0); const auto function = [this, row](size_t i1, size_t i2) noexcept -> bool { return operator()(row, i1) < operator()(row, i2); }; std::stable_sort(idx.begin(), idx.end(), function); for (size_t col = 0; col < shape_.cols; ++col) { returnArray(row, col) = idx[col]; } } return returnArray; } case Axis::ROW: { return transpose().argsort(Axis::COLUMN).transpose(); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Returns a copy of the array, cast to a specified type. /// Arithmetic to Arithmetic /// /// @return Matrix /// template , int> = 0, enable_if_t, int> = 0, enable_if_t, int> = 0> Matrix astype() const { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), cend(), outArray.begin(), [](dtype value) -> dtypeOut { return static_cast(value); }); return outArray; } //============================================================================ // Method Description: /// Returns a copy of the array, cast to a specified type. /// Arithmetic to Complex /// /// @return Matrix /// template , int> = 0, enable_if_t, int> = 0, enable_if_t, int> = 0> Matrix astype() const { Matrix outArray(shape_); const auto function = [](const_reference value) -> dtypeOut { return std::complex(value); }; std::transform(std::execution::par_unseq, cbegin(), cend(), outArray.begin(), function); return outArray; } //============================================================================ // Method Description: /// Returns a copy of the array, cast to a specified type. /// Complex to Complex /// /// @return Matrix /// template , int> = 0, enable_if_t, int> = 0, enable_if_t, int> = 0> Matrix astype() const { Matrix outArray(shape_); if (is_same_v) { std::copy(std::execution::par_unseq, cbegin(), cend(), outArray.begin()); } else { const auto function = [](const_reference value) noexcept -> dtypeOut { return complex_cast(value); }; std::transform(std::execution::par_unseq, cbegin(), cend(), outArray.begin(), function); } return outArray; } //============================================================================ // Method Description: /// Returns a copy of the array, cast to a specified type. /// Complex to Arithmetic /// /// @return Matrix /// template , int> = 0, enable_if_t, int> = 0, enable_if_t, int> = 0> Matrix astype() const { Matrix outArray(shape_); const auto function = [](const_reference value) -> dtypeOut { return static_cast(value.real()); }; std::transform(std::execution::par_unseq, cbegin(), cend(), outArray.begin(), function); return outArray; } //============================================================================ // Method Description: /// Returns a copy of the last element of the flattened array. /// /// @return dtype /// const_reference back() const noexcept { return *(cend() - 1); } //============================================================================ // Method Description: /// Returns a reference the last element of the flattened array. /// /// @return dtype /// reference back() noexcept { return *(end() - 1); } //============================================================================ // Method Description: /// Returns a copy of the last element of the input row. /// /// @return dtype /// const_reference back(size_t row) const { return *(cend(row) - 1); } //============================================================================ // Method Description: /// Returns a reference the last element of the input row. /// /// @return dtype /// reference back(size_t row) { return *(end(row) - 1); } //============================================================================ // Method Description: /// Swap the bytes of the array elements in place /// /// @return Matrix /// Matrix &byteswap() noexcept { STATIC_ASSERT_INTEGER(dtype); std::for_each(std::execution::par_unseq, begin(), end(), [](dtype &value) noexcept -> void { value = endian::byteSwap(value); }); switch (endianess_) { case Endian::NATIVE: { endianess_ = endian::isLittleEndian() ? Endian::BIG : Endian::LITTLE; break; } case Endian::LITTLE: { endianess_ = Endian::BIG; break; } case Endian::BIG: { endianess_ = Endian::LITTLE; break; } } return *this; } //============================================================================ // Method Description: /// Returns an array whose values are limited to [min, max]. /// /// @param min: min value to clip to /// @param max: max value to clip to /// @return clipped value /// Matrix clip(value_type min, value_type max) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), cend(), outArray.begin(), [min, max](dtype value) noexcept -> dtype { #ifdef __cpp_lib_clamp const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; return std::clamp(value, min, max, comparator); #else if (value < min) { return min; } else if (value > max) { return max; } return value; #endif }); return outArray; } //============================================================================ // Method Description: /// Returns the full column of the array /// /// /// @return Shape /// Matrix column(size_t column) { return operator()(row_slice(), column); } //============================================================================ // Method Description: /// returns whether or not a value is included the array /// /// @param value /// @param axis (Optional, default NONE) /// @return bool /// Matrix contains(value_type value, Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); switch (axis) { case Axis::NONE: { Matrix returnArray = {std::find(std::execution::par_unseq, cbegin(), cend(), value) != cend()}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::find(std::execution::par_unseq, cbegin(row), cend(row), value) != cend(row); } return returnArray; } case Axis::ROW: { return transpose().contains(value, Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// returns whether or not a value is included the array /// /// @param value /// @param axis (Optional, default NONE) /// @return bool /// bool containsB(value_type value, Axis axis = Axis::NONE) const { Matrix returnArray = contains(value, axis); return std::any_of(std::execution::par_unseq, returnArray.begin(), returnArray.end(), [](bool v) { return v; }); } //============================================================================ // Method Description: /// Return a copy of the array /// /// @return Matrix /// Matrix copy() const { return Matrix(*this); } //============================================================================ // Method Description: /// Return the cumulative product of the elements along the given axis. /// /// [[1,2], [3,4]] =(Axis::NONE)=> [1,2,4,12] /// [[1,2], [3,4]] =(Axis::0)=> [[1,2],[3,8]] cumprod by row /// [[1,2], [3,4]] =(Axis::1)=> [[1,2],[3,12]] cumprod by column /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix cumprod(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); switch (axis) { case Axis::NONE: { Matrix returnArray(1, size_); returnArray[0] = front(); for (size_t i = 1; i < size_; ++i) { returnArray[i] = returnArray[i - 1] * array_[i]; } return returnArray; } case Axis::COLUMN: { Matrix returnArray(shape_); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(row, 0) = operator()(row, 0); for (size_t col = 1; col < shape_.cols; ++col) { returnArray(row, col) = returnArray(row, col - 1) * operator()(row, col); } } return returnArray; } case Axis::ROW: { return transpose().cumprod(Axis::COLUMN).transpose(); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return the cumulative sum of the elements along the given axis. /// /// [[1,2], [3,4]] =(Axis::NONE)=> [1,3,6,10] /// [[1,2], [3,4]] =(Axis::0)=> [[1,2],[4,6]] cumsum by row /// [[1,2], [3,4]] =(Axis::1)=> [[1,3],[3,7]] cumsum by column /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix cumsum(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); switch (axis) { case Axis::NONE: { Matrix returnArray(1, size_); returnArray[0] = front(); for (size_t i = 1; i < size_; ++i) { returnArray[i] = returnArray[i - 1] + array_[i]; } return returnArray; } case Axis::COLUMN: { Matrix returnArray(shape_); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(row, 0) = operator()(row, 0); for (size_t col = 1; col < shape_.cols; ++col) { returnArray(row, col) = returnArray(row, col - 1) + operator()(row, col); } } return returnArray; } case Axis::ROW: { return transpose().cumsum(Axis::COLUMN).transpose(); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Returns the raw pointer to the underlying data /// @return pointer /// pointer data() noexcept { return array_; } //============================================================================ // Method Description: /// Returns the raw pointer to the underlying data /// @return const_pointer /// const_pointer data() const noexcept { return array_; } //============================================================================ // Method Description: /// Releases the internal data pointer so that the destructor /// will not call delete on it, and returns the raw pointer /// to the underlying data. /// @return pointer /// pointer release_data() noexcept { ownsPtr_ = false; return data(); } //============================================================================ // Method Description: /// Return specified diagonals. /// /// @param offset: Offset of the diagonal from the main diagonal. Can be both positive and negative. Defaults /// to 0. /// @param axis: (Optional, default ROW) axis the offset is applied to /// @return Matrix /// Matrix diagonal(int64_t offset = 0, Axis axis = Axis::ROW) const { switch (axis) { case Axis::COLUMN: { std::vector diagonalValues; size_t col = 0; for (size_t row = offset; row < shape_.rows; ++row) { if (row < 0) { ++col; continue; } if (col >= shape_.cols) { break; } diagonalValues.push_back(operator()(row, col)); ++col; } return Matrix(diagonalValues); } case Axis::ROW: { return transpose().diagonal(offset, Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Dot product of two arrays. /// /// For 2-D arrays it is equivalent to matrix multiplication, /// and for 1-D arrays to inner product of vectors. /// /// [1, 2], dot [1, 2], ==> [1*1 + 2*2, 1*1 + 2*2], = [5, 4], /// [3, 4] [2, 1] [3*1 + 4*2, 3*2 + 4*1] [11, 10] /// /// @param otherArray /// @return dot product /// Matrix dot(const Matrix &otherArray) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); if (shape_ == otherArray.shape_ && (shape_.rows == 1 || shape_.cols == 1)) { dtype dotProduct = std::inner_product(cbegin(), cend(), otherArray.cbegin(), dtype{0}); Matrix returnArray = {dotProduct}; return returnArray; } if (shape_.cols == otherArray.shape_.rows) { // 2D array, use matrix multiplication Matrix returnArray(shape_.rows, otherArray.shape_.cols); auto otherArrayT = otherArray.transpose(); for (size_t i = 0; i < shape_.rows; ++i) { for (size_t j = 0; j < otherArrayT.shape_.rows; ++j) { returnArray(i, j) = std::inner_product(otherArrayT.cbegin(j), otherArrayT.cend(j), cbegin(i), dtype{0}); } } return returnArray; } std::string errStr = "shapes of [" + std::to_string(shape_.rows) + ", " + std::to_string(shape_.cols) + "]"; errStr += " and [" + std::to_string(otherArray.shape_.rows) + ", " + std::to_string(otherArray.shape_.cols) + "]"; errStr += " are not consistent."; THROW_INVALID_ARGUMENT(errStr); return Matrix(); // get rid of compiler warning } //============================================================================ // Method Description: /// Dump a binary file of the array to the specified file. /// The array can be read back with ais::load. /// /// @param filename /// void dump(const std::string &filename) const { filesystem::File f(filename); if (!f.hasExt()) { f.withExt(".bin"); } std::ofstream ofile(f.fullname().c_str(), std::ios::binary); if (!ofile.good()) { THROW_RUNTIME_ERROR("Unable to open the input file:\n\t" + filename); } if (array_ != nullptr) { ofile.write(reinterpret_cast(array_), size_ * sizeof(dtype)); } ofile.close(); } //============================================================================ // Method Description: /// Return the matrix's endianess /// /// @return Endian /// Endian endianess() const noexcept { STATIC_ASSERT_ARITHMETIC(dtype); return endianess_; } //============================================================================ // Method Description: /// Fill the array with a scaler value. /// /// @param fillValue /// @return *this /// Matrix &fill(value_type fillValue) noexcept { std::fill(std::execution::par_unseq, begin(), end(), fillValue); return std::forward &>(*this); } //============================================================================ // Method Description: /// Return the indices of the flattened array of the /// elements that are non-zero. /// /// @return Matrix /// Matrix flatnonzero() const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); std::vector indices; size_t idx = 0; for (auto value : *this) { if (value != dtype{0}) { indices.push_back(idx); } ++idx; } return Matrix(indices); } //============================================================================ // Method Description: /// Return a copy of the array collapsed into one dimension. /// /// @return Matrix /// Matrix flatten() const { Matrix outArray(1, size_); std::copy(std::execution::par_unseq, cbegin(), cend(), outArray.begin()); return outArray; } //============================================================================ // Method Description: /// Returns a copy of the first element of the flattened array. /// /// @return dtype /// const_reference front() const noexcept { return *cbegin(); } //============================================================================ // Method Description: /// Returns a reference to the first element of the flattened array. /// /// @return dtype /// reference front() noexcept { return *begin(); } //============================================================================ // Method Description: /// Returns a copy of the first element of the input row. /// /// @return dtype /// const_reference front(size_t row) const { return *cbegin(row); } //============================================================================ // Method Description: /// Returns a reference to the first element of the input row. /// /// @return dtype /// reference front(size_t row) { return *begin(row); } //============================================================================ // Method Description: /// Returns a new flat array with the givin flat input indices. /// /// @param indices /// @return values /// Matrix get_by_indices(const Matrix &indices) const { return operator[](indices); } //============================================================================ // Method Description: /// Takes in a boolean mask the same size as the array /// and returns a flattened array with the values corresponding /// to the input mask. /// /// @param mask /// @return values /// Matrix get_by_mask(const Matrix &mask) const { return operator[](mask); } //============================================================================ // Method Description: /// Return if the Matrix is empty. ie the default constructor /// was used. /// /// @return boolean /// bool is_empty() const noexcept { return size_ == 0; } //============================================================================ // Method Description: /// Return if the Matrix is flat. ie the number of columns or /// rows is equal to one. /// /// @return boolean /// bool is_flat() const noexcept { return shape_.rows == 1 || shape_.cols == 1; } //============================================================================ // Method Description: /// Return if the Matrix is sorted. /// /// @param axis /// @return boolean /// Matrix is_sorted(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { return {std::is_sorted(std::execution::par_unseq, cbegin(), cend(), comparator)}; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::is_sorted(std::execution::par_unseq, cbegin(row), cend(row), comparator); } return returnArray; } case Axis::ROW: { return transpose().is_sorted(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return if the Matrix is square. /// /// @return boolean /// bool is_square() const noexcept { return shape_.is_square(); } //============================================================================ // Method Description: /// Copy an element of an array to a standard C++ scaler and return it. /// /// @return array element /// value_type item() const { if (size_ != 1) { THROW_INVALID_ARGUMENT("Can only convert an array of size 1 to a C++ scaler"); } return front(); } //============================================================================ // Method Description: /// Return the maximum along a given axis. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix max(Axis axis = Axis::NONE) const { // STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {*std::max_element(std::execution::par_unseq, cbegin(), cend(), comparator)}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = *std::max_element(std::execution::par_unseq, cbegin(row), cend(row), comparator); } return returnArray; } case Axis::ROW: { return transpose().max(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return the minimum along a given axis. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix min(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {*std::min_element(std::execution::par_unseq, cbegin(), cend(), comparator)}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = *std::min_element(std::execution::par_unseq, cbegin(row), cend(row), comparator); } return returnArray; } case Axis::ROW: { return transpose().min(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return the median along a given axis. /// If the dtype is floating point then the middle elements will be /// averaged for arrays of even number of elements. /// If the dtype is integral then the middle elements will be integer /// averaged (rounded down to integer) for arrays of even number of elements. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix median(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; if (size_ == 0) { THROW_RUNTIME_ERROR("Median is undefined for an array of size = 0."); } switch (axis) { case Axis::NONE: { Matrix copyArray(*this); const size_t middleIdx = size_ / 2; // integer division std::nth_element(std::execution::par_unseq, copyArray.begin(), copyArray.begin() + middleIdx, copyArray.end(), comparator); dtype medianValue = copyArray.array_[middleIdx]; if (size_ % 2 == 0) { const size_t lhsIndex = middleIdx - 1; std::nth_element(std::execution::par_unseq, copyArray.begin(), copyArray.begin() + lhsIndex, copyArray.end(), comparator); medianValue = (medianValue + copyArray.array_[lhsIndex]) / dtype{2}; // potentially integer division, ok } return {medianValue}; } case Axis::COLUMN: { Matrix copyArray(*this); Matrix returnArray(1, shape_.rows); const bool isEven = shape_.cols % 2 == 0; for (size_t row = 0; row < shape_.rows; ++row) { const size_t middleIdx = shape_.cols / 2; // integer division std::nth_element(std::execution::par_unseq, copyArray.begin(row), copyArray.begin(row) + middleIdx, copyArray.end(row), comparator); dtype medianValue = copyArray(row, middleIdx); if (isEven) { const size_t lhsIndex = middleIdx - 1; std::nth_element(std::execution::par_unseq, copyArray.begin(row), copyArray.begin(row) + lhsIndex, copyArray.end(row), comparator); medianValue = (medianValue + copyArray(row, lhsIndex)) / dtype{2}; // potentially integer division, ok } returnArray(0, row) = medianValue; } return returnArray; } case Axis::ROW: { return transpose().median(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Fills the array with nans. /// Matrix &nans() noexcept { STATIC_ASSERT_FLOAT(dtype); fill(std::nan("1")); return *this; } //============================================================================ // Method Description: /// Fills the array with empty. /// Matrix &emptys() noexcept { fill({}); return *this; } //============================================================================ // Method Description: /// Returns the number of bytes held by the array /// /// @return number of bytes /// uint64_t nbytes() const noexcept { return static_cast(sizeof(dtype) * size_); } //============================================================================ // Method Description: /// Return the array with the same data viewed with a /// different byte order. only works for integer types. /// /// @param endianess /// @return Matrix /// Matrix new_byte_order(Endian endianess) const { STATIC_ASSERT_INTEGER(dtype); const bool nativeIsLittle = endian::isLittleEndian(); switch (endianess_) { case Endian::NATIVE: { switch (endianess) { case Endian::NATIVE: { return Matrix(*this); } case Endian::BIG: { if (nativeIsLittle) { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), end(), outArray.begin(), endian::byteSwap); outArray.endianess_ = Endian::BIG; return outArray; } else { auto outArray = Matrix(*this); outArray.endianess_ = Endian::BIG; return outArray; } } case Endian::LITTLE: { if (nativeIsLittle) { auto outArray = Matrix(*this); outArray.endianess_ = Endian::LITTLE; return outArray; } else { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), end(), outArray.begin(), endian::byteSwap); outArray.endianess_ = Endian::LITTLE; return outArray; } } default: { THROW_INVALID_ARGUMENT("Unimplemented endian type."); return {}; // get rid of compiler warning } } break; } case Endian::BIG: { switch (endianess) { case Endian::NATIVE: { if (nativeIsLittle) { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), end(), outArray.begin(), endian::byteSwap); outArray.endianess_ = Endian::NATIVE; return outArray; } else { auto outArray = Matrix(*this); outArray.endianess_ = Endian::NATIVE; return outArray; } } case Endian::BIG: { return Matrix(*this); } case Endian::LITTLE: { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), end(), outArray.begin(), endian::byteSwap); outArray.endianess_ = Endian::LITTLE; return outArray; } default: { THROW_INVALID_ARGUMENT("Unimplemented endian type."); return {}; // get rid of compiler warning } } break; } case Endian::LITTLE: { switch (endianess) { case Endian::NATIVE: { if (nativeIsLittle) { auto outArray = Matrix(*this); outArray.endianess_ = Endian::NATIVE; return outArray; } else { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), end(), outArray.begin(), endian::byteSwap); outArray.endianess_ = Endian::NATIVE; return outArray; } } case Endian::BIG: { Matrix outArray(shape_); std::transform(std::execution::par_unseq, cbegin(), end(), outArray.begin(), endian::byteSwap); outArray.endianess_ = Endian::BIG; return outArray; } case Endian::LITTLE: { return Matrix(*this); } default: { THROW_INVALID_ARGUMENT("Unimplemented endian type."); return {}; // get rid of compiler warning } } break; } default: { THROW_INVALID_ARGUMENT("Unimplemented endian type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Returns True if none elements evaluate to True or non zero /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix none(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto function = [](dtype i) -> bool { return i != dtype{0}; }; switch (axis) { case Axis::NONE: { Matrix returnArray = {std::none_of(std::execution::par_unseq, cbegin(), cend(), function)}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::none_of(std::execution::par_unseq, cbegin(row), cend(row), function); } return returnArray; } case Axis::ROW: { return transpose().none(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Return the row/col indices of the array of the /// elements that are non-zero. /// /// @return std::pair where first is the row indices and second is the /// column indices /// std::pair, Matrix> nonzero() const; //============================================================================ // Method Description: /// Return the row/col indices of the array of the /// elements that are non-zero. /// /// @return std::pair where first is the row indices and second is the /// column indices /// std::pair, Matrix> nonempty() const; //============================================================================ // Method Description: /// Return the row/col indices of the array of the /// elements that are not nan. /// /// @return std::pair where first is the row indices and second is the /// column indices /// std::pair, Matrix> data_area() const; //============================================================================ // Method Description: /// Returns the number of columns in the array /// /// /// @return size_t /// size_t columns() const noexcept { return shape_.cols; } //============================================================================ // Method Description: /// Returns the number of rows in the array /// /// /// @return size_t /// size_t rows() const noexcept { return shape_.rows; } //============================================================================ // Method Description: /// Fills the array with ones /// /// Matrix &ones() noexcept { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); fill(dtype{1}); return *this; } //============================================================================ // Method Description: /// Returns whether or not the array object owns the underlying data /// /// @return bool /// bool owns_internal_data() noexcept { return ownsPtr_; } //============================================================================ // Method Description: /// Rearranges the elements in the array in such a way that /// value of the element in kth position is in the position it /// would be in a sorted array. All elements smaller than the kth /// element are moved before this element and all equal or greater /// are moved behind it. The ordering of the elements in the two /// partitions is undefined. /// /// @param kth: kth element /// @param axis (Optional, default NONE) /// @return None /// Matrix &partition(size_t kth, Axis axis = Axis::NONE) { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { if (kth >= size_) { std::string errStr = "kth(=" + std::to_string(kth); errStr += ") out of bounds (" + std::to_string(size_) + ")"; THROW_INVALID_ARGUMENT(errStr); } std::nth_element(std::execution::par_unseq, begin(), begin() + kth, end(), comparator); break; } case Axis::COLUMN: { if (kth >= shape_.cols) { std::string errStr = "kth(=" + std::to_string(kth); errStr += ") out of bounds (" + std::to_string(shape_.cols) + ")"; THROW_INVALID_ARGUMENT(errStr); } for (size_t row = 0; row < shape_.rows; ++row) { std::nth_element(std::execution::par_unseq, begin(row), begin(row) + kth, end(row), comparator); } break; } case Axis::ROW: { if (kth >= shape_.rows) { std::string errStr = "kth(=" + std::to_string(kth); errStr += ") out of bounds (" + std::to_string(shape_.rows) + ")"; THROW_INVALID_ARGUMENT(errStr); } Matrix transposedArray = transpose(); for (size_t row = 0; row < transposedArray.shape_.rows; ++row) { std::nth_element(std::execution::par_unseq, transposedArray.begin(row), transposedArray.begin(row) + kth, transposedArray.end(row), comparator); } *this = transposedArray.transpose(); break; } } return *this; } //============================================================================ // Method Description: /// Prints the array to the console. /// /// void print() const { std::cout << *this; } //============================================================================ // Method Description: /// Return the product of the array elements over the given axis /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix prod(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); switch (axis) { case Axis::NONE: { dtype product = std::accumulate(cbegin(), cend(), dtype{1}, std::multiplies()); Matrix returnArray = {product}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::accumulate(cbegin(row), cend(row), dtype{1}, std::multiplies()); } return returnArray; } case Axis::ROW: { return transpose().prod(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Peak to peak (maximum - minimum) value along a given axis. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix ptp(Axis axis = Axis::NONE) const { STATIC_ASSERT_ARITHMETIC_OR_COMPLEX(dtype); const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { const auto result = std::max_element(std::execution::par_unseq, cbegin(), cend(), comparator); Matrix returnArray = {*result.second - *result.first}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { const auto result = std::max_element(std::execution::par_unseq, cbegin(row), cend(row), comparator); returnArray(0, row) = *result.second - *result.first; } return returnArray; } case Axis::ROW: { return transpose().ptp(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// set the flat index element to the value /// /// @param pos /// @param value /// Matrix &put(size_t pos, value_type value) { at(pos) = value; return *this; } //============================================================================ // Method Description: /// set the 2D row/col index element to the value /// /// @param row /// @param column /// @param value /// Matrix &put(size_t row, size_t column, value_type value) { at(row, column) = value; return *this; } //============================================================================ // Method Description: /// set the flat index elements to the values /// /// @param pos /// @param values /// template Matrix &putMany(size_t pos, const ct &list) { if ((pos + list.size()) > size_) { THROW_INVALID_ARGUMENT("pos out of data range"); } const auto ptr = begin() + pos; std::copy(std::execution::par_unseq, list.begin(), list.end(), ptr); return *this; } //============================================================================ // Method Description: /// set the 2D row/col index element to the value /// /// @param row /// @param column /// @param values /// template Matrix &putMany(size_t row, size_t column, const ct &list) { auto pos = row * shape_.cols + column; return putMany(pos, list); } //============================================================================ // Method Description: /// Set a.flat[n] = values for all n in indices. /// /// @param indices /// @param value /// Matrix &put(const Matrix &indices, value_type value) { if (indices.size() > 0) { for (auto index : indices) { put(index, value); } } return *this; } //============================================================================ // Method Description: /// Set a.flat[n] = values[n] for all n in indices. /// /// @param indices /// @param values /// Matrix &put(const Matrix &indices, const Matrix &values) { if (indices.size() != values.size()) { THROW_INVALID_ARGUMENT("Input indices do not match values dimensions."); } size_t counter = 0; if (indices.size() > 0) { for (auto index : indices) { put(index, values[counter++]); } } return *this; } //============================================================================ // Method Description: /// Set the slice indices to the input value. /// /// @param slice /// @param value /// Matrix &put(const Slice &slice, value_type value) { Slice sliceCopy(slice); sliceCopy.make_positive_and_validate(size_); for (size_t i = sliceCopy.start; i < sliceCopy.stop; i += sliceCopy.step) { put(i, value); } return *this; } //============================================================================ // Method Description: /// Set the slice indices to the input values. /// /// @param slice /// @param values /// Matrix &put(const Slice &slice, const Matrix &values) { Slice sliceCopy(slice); sliceCopy.make_positive_and_validate(size_); std::vector indices; for (size_t i = sliceCopy.start; i < sliceCopy.stop; i += sliceCopy.step) { indices.push_back(i); } return put(Matrix(indices), values); } //============================================================================ // Method Description: /// Set the slice indices to the input value. /// /// @param rowSlice /// @param columnSlice /// @param value /// Matrix &put(const Slice &rowSlice, const Slice &columnSlice, value_type value) { Slice rowSliceCopy(rowSlice); Slice columnSliceCopy(columnSlice); rowSliceCopy.make_positive_and_validate(shape_.rows); columnSliceCopy.make_positive_and_validate(shape_.cols); std::vector indices; for (size_t row = rowSliceCopy.start; row < rowSliceCopy.stop; row += rowSliceCopy.step) { for (size_t col = columnSliceCopy.start; col < columnSliceCopy.stop; col += columnSliceCopy.step) { put(row, col, value); } } return *this; } //============================================================================ // Method Description: /// Set the slice indices to the input value. /// /// @param rowSlice /// @param columnPos /// @param value /// Matrix &put(const Slice &rowSlice, size_t columnPos, value_type value) { Slice rowSliceCopy(rowSlice); rowSliceCopy.make_positive_and_validate(shape_.rows); std::vector indices; for (size_t row = rowSliceCopy.start; row < rowSliceCopy.stop; row += rowSliceCopy.step) { put(row, columnPos, value); } return *this; } //============================================================================ // Method Description: /// Set the slice indices to the input value. /// /// @param rowPos /// @param columnSlice /// @param value /// Matrix &put(size_t rowPos, const Slice &columnSlice, value_type value) { Slice columnSliceCopy(columnSlice); columnSliceCopy.make_positive_and_validate(shape_.cols); std::vector indices; for (size_t col = columnSliceCopy.start; col < columnSliceCopy.stop; col += columnSliceCopy.step) { put(rowPos, col, value); } return *this; } //============================================================================ // Method Description: /// Set the slice indices to the input values. /// /// @param rowSlice /// @param columnSlice /// @param values /// Matrix &put(const Slice &rowSlice, const Slice &columnSlice, const Matrix &values) { Slice rowSliceCopy(rowSlice); Slice columnSliceCopy(columnSlice); rowSliceCopy.make_positive_and_validate(shape_.rows); columnSliceCopy.make_positive_and_validate(shape_.cols); std::vector indices; for (size_t row = rowSliceCopy.start; row < rowSliceCopy.stop; row += rowSliceCopy.step) { for (size_t col = columnSliceCopy.start; col < columnSliceCopy.stop; col += columnSliceCopy.step) { const size_t index = row * shape_.cols + col; indices.push_back(index); } } return put(Matrix(indices), values); } //============================================================================ // Method Description: /// Set the slice indices to the input values. /// /// @param rowSlice /// @param columnPos /// @param values /// Matrix &put(const Slice &rowSlice, size_t columnPos, const Matrix &values) { Slice rowSliceCopy(rowSlice); rowSliceCopy.make_positive_and_validate(shape_.rows); std::vector indices; for (size_t row = rowSliceCopy.start; row < rowSliceCopy.stop; row += rowSliceCopy.step) { const size_t index = row * shape_.cols + columnPos; indices.push_back(index); } return put(Matrix(indices), values); } //============================================================================ // Method Description: /// Set the slice indices to the input values. /// /// @param rowPos /// @param columnSlice /// @param values /// Matrix &put(size_t rowPos, const Slice &columnSlice, const Matrix &values) { Slice columnSliceCopy(columnSlice); columnSliceCopy.make_positive_and_validate(shape_.cols); std::vector indices; for (size_t col = columnSliceCopy.start; col < columnSliceCopy.stop; col += columnSliceCopy.step) { const size_t index = rowPos * shape_.cols + col; indices.push_back(index); } return put(Matrix(indices), values); } //============================================================================ // Method Description: /// Set the mask indices to the input value. /// /// @param mask /// @param value /// Matrix &put_mask(const Matrix &mask, value_type value) { if (mask.shape() != shape_) { THROW_INVALID_ARGUMENT("input mask must be the same shape as the array it is masking."); } return put(mask.flatnonzero(), value); } //============================================================================ // Method Description: /// Set the mask indices to the input values. /// /// @param mask /// @param values /// Matrix &put_mask(const Matrix &mask, const Matrix &values) { if (mask.shape() != shape_) { THROW_INVALID_ARGUMENT("input mask must be the same shape as the array it is masking."); } return put(mask.flatnonzero(), values); } //============================================================================ // Method Description: /// Flattens the array but does not make a copy. /// /// @return Matrix /// Matrix &ravel() noexcept { reshape(size_); return *this; } //============================================================================ // Method Description: /// Repeat elements of an array. /// /// @param rows /// @param columns /// @return Matrix /// Matrix repeat(size_t rows, size_t columns) const { Matrix returnArray(shape_.rows * rows, shape_.cols * columns); for (size_t row = 0; row < rows; ++row) { for (size_t col = 0; col < columns; ++col) { std::vector indices(shape_.size()); const size_t rowStart = row * shape_.rows; const size_t colStart = col * shape_.cols; const size_t rowEnd = (row + 1) * shape_.rows; const size_t colEnd = (col + 1) * shape_.cols; size_t counter = 0; for (size_t rowIdx = rowStart; rowIdx < rowEnd; ++rowIdx) { for (size_t colIdx = colStart; colIdx < colEnd; ++colIdx) { indices[counter++] = rowIdx * returnArray.shape_.cols + colIdx; } } returnArray.put(Matrix(indices), *this); } } return returnArray; } //============================================================================ // Method Description: /// Repeat elements of an array. /// /// @param repeatShape /// @return Matrix /// Matrix repeat(const Shape &repeatShape) const { return repeat(repeatShape.rows, repeatShape.cols); } //============================================================================ // Method Description: /// Replaces a value of the array with another value /// /// @param oldValue: the value to replace /// @param newValue: the value to replace with /// inline void replace(value_type oldValue, value_type newValue) { if (std::isnan(oldValue)) std::replace_if(std::execution::par_unseq, begin(), end(), [](double v) { return std::isnan(v); }, newValue); else std::replace(std::execution::par_unseq, begin(), end(), oldValue, newValue); } //============================================================================ // Method Description: /// The new shape should be compatible with the original shape. If an single integer, /// then the result will be a 1-D array of that length. One shape dimension /// can be -1. In this case, the value is inferred from the length of the /// array and remaining dimensions. /// /// @param size /// Matrix &reshape(size_t size) { if (size != size_) { std::string errStr = "Cannot reshape array of size " + std::to_string(size_) + " into shape "; errStr += "[" + std::to_string(1) + ", " + std::to_string(size) + "]"; THROW_RUNTIME_ERROR(errStr); } shape_.rows = 1; shape_.cols = size; return *this; } //============================================================================ // Method Description: /// The new shape should be compatible with the original shape. If an single integer, /// then the result will be a 1-D array of that length. One shape dimension /// can be -1. In this case, the value is inferred from the length of the /// array and remaining dimensions. /// /// @param rows /// @param columns /// Matrix &reshape(int64_t rows, int64_t columns) { if (rows < 0) { if (size_ % columns == 0) { return reshape(size_ / columns, columns); } std::string errStr = "Cannot reshape array of size " + std::to_string(size_) + " into a shape "; errStr += "with " + std::to_string(columns) + " columns"; THROW_INVALID_ARGUMENT(errStr); } if (columns < 0) { if (size_ % rows == 0) { return reshape(rows, size_ / rows); } std::string errStr = "Cannot reshape array of size " + std::to_string(size_) + " into a shape "; errStr += "with " + std::to_string(rows) + " rows"; THROW_INVALID_ARGUMENT(errStr); } if ((rows * columns) != size_) { std::string errStr = "Cannot reshape array of size " + std::to_string(size_) + " into shape "; errStr += "[" + std::to_string(rows) + ", " + std::to_string(columns) + "]"; THROW_INVALID_ARGUMENT(errStr); } shape_.rows = (rows); shape_.cols = (columns); return *this; } //============================================================================ // Method Description: /// The new shape should be compatible with the original shape. If an single integer, /// then the result will be a 1-D array of that length. One shape dimension /// can be -1. In this case, the value is inferred from the length of the /// array and remaining dimensions. /// /// @param shape /// Matrix &reshape(const Shape &shape) { return reshape(shape.rows, shape.cols); } //============================================================================ // Method Description: /// Change shape and size of array in-place. All previous /// data of the array is lost. /// /// @param rows /// @param columns /// Matrix &resize_fast(size_t rows, size_t columns) { new_matrix(Shape(rows, columns)); return *this; } //============================================================================ // Method Description: /// Change shape and size of array in-place. All previous /// data of the array is lost. /// /// @param shape /// Matrix &resize_fast(const Shape &shape) { return resize_fast(shape.rows, shape.cols); } //============================================================================ // Method Description: /// Return a new array with the specified shape. If new shape /// is larger than old shape then array will be padded with zeros. /// If new shape is smaller than the old shape then the data will /// be discarded. /// /// @param rows /// @param columns /// Matrix &resize_slow(size_t rows, size_t columns) { std::vector oldData(size_); std::copy(std::execution::par_unseq, begin(), end(), oldData.begin()); const Shape shape(rows, columns); const Shape oldShape = shape_; new_matrix(shape); for (size_t row = 0; row < shape.rows; ++row) { for (size_t col = 0; col < shape.cols; ++col) { if (row >= oldShape.rows || col >= oldShape.cols) { operator()(row, col) = dtype{0}; // zero fill } else { operator()(row, col) = oldData[row * oldShape.cols + col]; } } } return *this; } //============================================================================ // Method Description: /// Return a new array with the specified shape. If new shape /// is larger than old shape then array will be padded with zeros. /// If new shape is smaller than the old shape then the data will /// be discarded. /// /// @param shape /// Matrix &resize_slow(const Shape &shape) { return resize_slow(shape.rows, shape.cols); } //============================================================================ // Method Description: /// Return a with each element rounded to the given number /// of decimals. /// /// @param number_decimals (default 0) /// @return Matrix /// Matrix round(uint8_t number_decimals = 0) const { STATIC_ASSERT_FLOAT(dtype); Matrix returnArray(shape_); const double multFactor = std::pow(10., number_decimals); const auto function = [multFactor](dtype value) noexcept -> dtype { return static_cast(std::nearbyint(static_cast(value) * multFactor) / multFactor); }; std::transform(std::execution::par_unseq, cbegin(), cend(), returnArray.begin(), function); return returnArray; } //============================================================================ // Method Description: /// Returns the full row of the array /// /// /// @return Shape /// Matrix row(size_t row) { return Matrix(cbegin(row), cend(row)); } //============================================================================ // Method Description: /// Return the shape of the array /// /// @return Shape /// Shape shape() const noexcept { return shape_; } //============================================================================ // Method Description: /// Return the size of the array /// /// @return size /// size_t size() const noexcept { return size_; } //============================================================================ // Method Description: /// Sort an array, in-place. /// /// @param axis (Optional, default NONE) /// @return size /// Matrix &sort(Axis axis = Axis::NONE) { const auto comparator = [](dtype lhs, dtype rhs) noexcept -> bool { return lhs < rhs; }; switch (axis) { case Axis::NONE: { std::sort(std::execution::par_unseq, begin(), end(), comparator); break; } case Axis::COLUMN: { for (size_t row = 0; row < shape_.rows; ++row) { std::sort(std::execution::par_unseq, begin(row), end(row), comparator); } break; } case Axis::ROW: { Matrix transposedArray = transpose(); for (size_t row = 0; row < transposedArray.shape_.rows; ++row) { std::sort(std::execution::par_unseq, transposedArray.begin(row), transposedArray.end(row), comparator); } *this = transposedArray.transpose(); break; } } return *this; } //============================================================================ // Method Description: /// returns the Matrix as a string representation /// /// @return string /// std::string str() const { std::string out; out += "[\n"; for (size_t row = 0; row < shape_.rows; ++row) { out += "["; // for (size_t col = 0; col < shape_.cols; ++col) // { // out += utils::value2str(operator()(row, col)) + ", "; // } auto ib = begin() + row * shape_.cols; auto ie = ib + shape_.cols; out += str_join(ib, ie, ", "); if (row == shape_.rows - 1) { out += "]\n"; } else { out += "],\n"; } } out += "]\n"; return out; } //============================================================================ // Method Description: /// Return the sum of the array elements over the given axis. /// /// @param axis (Optional, default NONE) /// @return Matrix /// Matrix sum(Axis axis = Axis::NONE) const { switch (axis) { case Axis::NONE: { Matrix returnArray = {std::accumulate(cbegin(), cend(), dtype{0})}; return returnArray; } case Axis::COLUMN: { Matrix returnArray(1, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { returnArray(0, row) = std::accumulate(cbegin(row), cend(row), dtype{0}); } return returnArray; } case Axis::ROW: { return transpose().sum(Axis::COLUMN); } default: { THROW_INVALID_ARGUMENT("Unimplemented axis type."); return {}; // get rid of compiler warning } } } //============================================================================ // Method Description: /// Interchange two axes of an array. Equivalent to transpose... /// /// @return Matrix /// Matrix swap_axes() const { return transpose(); } //============================================================================ // Method Description: /// Swaps rows of the array /// /// @param colIdx1 /// @param colIdx2 /// void swap_columns(size_t colIdx1, size_t colIdx2) { for (size_t row = 0; row < static_cast(shape_.rows); ++row) { std::swap(operator()(row, colIdx1), operator()(row, colIdx2)); } } //============================================================================ // Method Description: /// Swaps rows of the array /// /// @param rowIdx1 /// @param rowIdx2 /// void swap_rows(size_t rowIdx1, size_t rowIdx2) { for (size_t col = 0; col < static_cast(shape_.cols); ++col) { std::swap(operator()(rowIdx1, col), operator()(rowIdx2, col)); } } //============================================================================ // Method Description: /// Write array to a file as binary. /// The data produced by this method can be recovered /// using the function fromfile(). /// /// @param filename /// @return None /// void tofile(const std::string &filename) const { dump(filename); } //============================================================================ // Method Description: /// Write array to a file as text. /// The data produced by this method can be recovered /// using the function fromfile(). /// /// @param filename /// @param separator: Separator between array items for text output. /// @return None /// void tofile(const std::string &filename, const char separator) const { filesystem::File f(filename); if (!f.hasExt()) { f.withExt("txt"); } std::ofstream ofile(f.fullname().c_str()); if (!ofile.good()) { THROW_RUNTIME_ERROR("Input file could not be opened:\n\t" + filename); } size_t counter = 0; for (auto value : *this) { ofile << value; if (counter++ != size_ - 1) { ofile << separator; } } ofile.close(); } //============================================================================ // Method Description: /// Converts the slice object to an Matrix of indices for this array /// /// @param slice: the slice object /// @param axis: the array axis /// /// @return Matrix /// Matrix to_indices(Slice slice, Axis axis = Axis::ROW) const { size_t element_number = 0; switch (axis) { case Axis::NONE: { element_number = slice.element_number(size_); break; } case Axis::ROW: { element_number = slice.element_number(shape_.rows); break; } case Axis::COLUMN: { element_number = slice.element_number(shape_.cols); break; } default: { // not actually possible, getting rid of compiler warning THROW_INVALID_ARGUMENT("Invalid 'axis' option"); } } if (element_number == 0) { return {}; } Matrix indices(1, element_number); indices[0] = slice.start; for (size_t i = 1; i < indices.size(); ++i) { indices[i] = (indices[i - 1] + slice.step); } return indices; } //============================================================================ // Method Description: /// Write flattened array to an STL vector /// /// @return std::vector /// std::vector to_vector() const { return std::vector(cbegin(), cend()); } //============================================================================ // Method Description: /// Return the sum along diagonals of the array. /// /// @param offset: Offset of the diagonal from the main diagonal. Can be both positive and negative. Defaults /// to 0. /// @param axis: (Optional, default ROW) Axis to offset from /// /// @return value /// value_type trace(size_t offset = 0, Axis axis = Axis::ROW) const { size_t rowStart = 0; size_t colStart = 0; switch (axis) { case Axis::ROW: { rowStart += offset; break; } case Axis::COLUMN: { colStart += offset; break; } default: { // if the user input NONE, override back to ROW axis = Axis::ROW; break; } } if (rowStart >= shape_.rows || colStart >= shape_.cols) { return dtype{0}; } size_t col = colStart; dtype sum = 0; for (size_t row = rowStart; row < shape_.rows; ++row) { if (col >= shape_.cols) { break; } sum += operator()(row, col++); } return sum; } //============================================================================ // Method Description: /// Transpose the rows and columns of an array /// /// @return Matrix /// Matrix transpose() const { Matrix transArray(shape_.cols, shape_.rows); for (size_t row = 0; row < shape_.rows; ++row) { for (size_t col = 0; col < shape_.cols; ++col) { transArray(col, row) = operator()(row, col); } } return transArray; } //============================================================================ // Method Description: /// Fills the array with zeros /// /// Matrix &zeros() noexcept { fill(dtype{0}); return *this; } //============================================================================ // Method Description: /// Return random integer from low (inclusive) to high (exclusive), /// with the given shape. If no high value is input then the range will /// go from [0, 100). /// /// @param begin position for filling area, default 0 /// @param end position for filling area, default size /// @param low default 0 /// @param high default 100. /// @return NdArray /// Matrix &fill_random(size_t begin, size_t end, dtype low = 0, dtype high = 100) { STATIC_ASSERT_INTEGER(dtype); if (low == high) { THROW_INVALID_ARGUMENT("input low value must be less than the input high value."); } else if (low > high) { std::swap(low, high); } std::uniform_int_distribution dist(low, high - 1); std::mt19937_64 generator{}; auto b = this->begin() + begin; auto e = this->begin() + end; auto f = [&dist, &generator](dtype &value) noexcept -> void { value = int(dist(generator)); }; std::for_each(std::execution::par_unseq, b, e, f); return *this; } private: //====================================Attributes============================== allocator_type allocator_{}; Shape shape_{0, 0}; size_t size_{0}; Endian endianess_{Endian::NATIVE}; pointer array_{nullptr}; bool ownsPtr_{false}; //============================================================================ // Method Description: /// Return iterator of specials index /// /// @param rowPos /// @param columnPos /// @return iterator /// iterator pos_iterator(int64_t rowPos, int64_t columnPos = 0) { // this doesn't allow for calling the first element as -size_... // but why would you really want to do that anyway? if (std::abs(rowPos) >= shape_.rows) { std::string errStr = "Row index " + std::to_string(rowPos); errStr += " is out of bounds for array of size " + std::to_string(shape_.rows) + "."; THROW_INVALID_ARGUMENT(errStr); } // this doesn't allow for calling the first element as -size_... // but why would you really want to that anyway? if (std::abs(columnPos) >= shape_.cols) { std::string errStr = "Column index " + std::to_string(columnPos); errStr += " is out of bounds for array of size " + std::to_string(shape_.cols) + "."; THROW_INVALID_ARGUMENT(errStr); } rowPos = rowPos >= 0 ? rowPos : rowPos + shape_.rows; columnPos = columnPos >= 0 ? columnPos : shape_.cols + columns(); iterator out_iter = begin() + rowPos * columns() + columnPos; return out_iter; } //============================================================================ // Method Description: /// Deletes the internal array /// void clear() noexcept { if (ownsPtr_ && array_ != nullptr) { allocator_.deallocate(array_, size_); } array_ = nullptr; shape_.rows = shape_.cols = 0; size_ = 0; ownsPtr_ = false; endianess_ = Endian::NATIVE; } //============================================================================ // Method Description: /// Creates a new internal array /// void new_matrix() { if (size_ > 0) { array_ = allocator_.allocate(size_); ownsPtr_ = true; } } //============================================================================ // Method Description: /// Creates a new internal array /// /// @param shape /// void new_matrix(const Shape &shape) { clear(); shape_ = shape; size_ = shape.size(); new_matrix(); } friend std::ostream &operator<<(std::ostream &stream, const self_type &matrix) { stream << matrix.str(); return stream; } }; // NOTE: this needs to be defined outside of the class to get rid of a compiler // error in Visual Studio template AIS_EXPORT std::pair, Matrix> Matrix::nonzero() const { std::vector rowIndices; std::vector columnIndices; for (size_t row = 0; row < shape_.rows; ++row) { for (size_t col = 0; col < shape_.cols; ++col) { auto d = operator()(row, col); if (d != dtype{0}) { rowIndices.push_back(row); columnIndices.push_back(col); } } } return std::make_pair(Matrix(rowIndices), Matrix(columnIndices)); }; template AIS_EXPORT std::pair, Matrix> Matrix::nonempty() const { std::vector rowIndices; std::vector columnIndices; for (size_t row = 0; row < shape_.rows; ++row) { for (size_t col = 0; col < shape_.cols; ++col) { auto d = operator()(row, col); if (!ais::isnan(d)) { rowIndices.push_back(row); columnIndices.push_back(col); } } } return std::make_pair(Matrix(rowIndices), Matrix(columnIndices)); }; template AIS_EXPORT std::pair, Matrix> Matrix::data_area() const { std::vector rowIndices; std::vector columnIndices; for (size_t row = 0; row < shape_.rows; ++row) { for (size_t col = 0; col < shape_.cols; ++col) { auto d = operator()(row, col); if (!ais::isnan(d)) { rowIndices.push_back(row); columnIndices.push_back(col); } } } return std::make_pair(Matrix(rowIndices), Matrix(columnIndices)); } using FMatrix = AIS_EXPORT Matrix; } // namespace ais