include/maths/common/CLinearAlgebra.h (944 lines of code) (raw):
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the following additional limitation. Functionality enabled by the
* files subject to the Elastic License 2.0 may only be used in production when
* invoked by an Elasticsearch process with a license key installed that permits
* use of machine learning features. You may not use this file except in
* compliance with the Elastic License 2.0 and the foregoing additional
* limitation.
*/
#ifndef INCLUDED_ml_maths_common_CLinearAlgebra_h
#define INCLUDED_ml_maths_common_CLinearAlgebra_h
#include <core/CHashing.h>
#include <core/CLogger.h>
#include <core/CMemoryFwd.h>
#include <core/CSmallVectorFwd.h>
#include <maths/common/CChecksum.h>
#include <maths/common/CLinearAlgebraFwd.h>
#include <maths/common/ImportExport.h>
#include <maths/common/MathsTypes.h>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/adapted/std_array.hpp>
#include <boost/operators.hpp>
#include <cmath>
#include <cstddef>
#include <numeric>
BOOST_GEOMETRY_REGISTER_STD_ARRAY_CS(cs::cartesian)
namespace ml {
namespace maths {
namespace common {
namespace linear_algebra_detail {
//! SFINAE check that \p N is at least 1.
struct CEmpty {};
template<std::size_t N>
struct CBoundsCheck {
using InRange = CEmpty;
};
template<>
struct CBoundsCheck<0> {};
//! \brief Common vector functionality for variable storage type.
template<typename STORAGE>
struct SSymmetricMatrix {
using Type = typename STORAGE::value_type;
//! Get read only reference.
inline const SSymmetricMatrix& base() const { return *this; }
//! Get writable reference.
inline SSymmetricMatrix& base() { return *this; }
//! Set this vector equal to \p other.
template<typename OTHER_STORAGE>
void assign(const SSymmetricMatrix<OTHER_STORAGE>& other) {
std::copy(other.m_LowerTriangle.begin(), other.m_LowerTriangle.end(),
m_LowerTriangle.begin());
}
//! Create from a delimited string.
bool fromDelimited(const std::string& str);
//! Convert to a delimited string.
std::string toDelimited() const;
//! Get the i,j 'th component (no bounds checking).
inline Type element(std::size_t i, std::size_t j) const {
if (i < j) {
std::swap(i, j);
}
return m_LowerTriangle[i * (i + 1) / 2 + j];
}
//! Get the i,j 'th component (no bounds checking).
inline Type& element(std::size_t i, std::size_t j) {
if (i < j) {
std::swap(i, j);
}
return m_LowerTriangle[i * (i + 1) / 2 + j];
}
//! Componentwise negative.
void negative() {
for (std::size_t i = 0; i < m_LowerTriangle.size(); ++i) {
m_LowerTriangle[i] = -m_LowerTriangle[i];
}
}
//! Matrix subtraction.
void minusEquals(const SSymmetricMatrix& rhs) {
for (std::size_t i = 0; i < m_LowerTriangle.size(); ++i) {
m_LowerTriangle[i] -= rhs.m_LowerTriangle[i];
}
}
//! Matrix addition.
void plusEquals(const SSymmetricMatrix& rhs) {
for (std::size_t i = 0; i < m_LowerTriangle.size(); ++i) {
m_LowerTriangle[i] += rhs.m_LowerTriangle[i];
}
}
//! Componentwise multiplication.
void multiplyEquals(const SSymmetricMatrix& rhs) {
for (std::size_t i = 0; i < m_LowerTriangle.size(); ++i) {
m_LowerTriangle[i] *= rhs.m_LowerTriangle[i];
}
}
//! Scalar multiplication.
void multiplyEquals(Type scale) {
for (std::size_t i = 0; i < m_LowerTriangle.size(); ++i) {
m_LowerTriangle[i] *= scale;
}
}
//! Scalar division.
void divideEquals(Type scale) {
for (std::size_t i = 0; i < m_LowerTriangle.size(); ++i) {
m_LowerTriangle[i] /= scale;
}
}
//! Check if two matrices are identically equal.
bool equal(const SSymmetricMatrix& other) const {
return m_LowerTriangle == other.m_LowerTriangle;
}
//! Lexicographical total ordering.
bool less(const SSymmetricMatrix& rhs) const {
return m_LowerTriangle < rhs.m_LowerTriangle;
}
//! Check if this is zero.
bool isZero() const {
return std::find_if(m_LowerTriangle.begin(), m_LowerTriangle.end(),
[](double ei) { return ei != 0.0; }) ==
m_LowerTriangle.end();
}
//! Get the matrix diagonal.
template<typename VECTOR>
VECTOR diagonal(std::size_t d) const {
VECTOR result(d);
for (std::size_t i = 0; i < d; ++i) {
result[i] = this->element(i, i);
}
return result;
}
//! Get the trace.
Type trace(std::size_t d) const {
Type result(0);
for (std::size_t i = 0; i < d; ++i) {
result += this->element(i, i);
}
return result;
}
//! The Frobenius norm.
double frobenius(std::size_t d) const {
double result = 0.0;
for (std::size_t i = 0, i_ = 0; i < d; ++i, ++i_) {
for (std::size_t j = 0; j < i; ++j, ++i_) {
result += 2.0 * m_LowerTriangle[i_] * m_LowerTriangle[i_];
}
result += m_LowerTriangle[i_] * m_LowerTriangle[i_];
}
return std::sqrt(result);
}
//! Get the mean of the matrix elements.
double mean(std::size_t d) const {
return (2.0 * std::accumulate(m_LowerTriangle.begin(), m_LowerTriangle.end(), 0.0) -
this->trace(d)) /
static_cast<double>(d * d);
}
//! Convert to the MATRIX representation.
template<typename MATRIX>
inline MATRIX& toType(std::size_t d, MATRIX& result) const {
for (std::size_t i = 0, i_ = 0; i < d; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
result(i, j) = result(j, i) = m_LowerTriangle[i_];
}
}
return result;
}
//! Get a checksum of the elements of this matrix.
std::uint64_t checksum() const {
return CChecksum::calculate(0, m_LowerTriangle);
}
STORAGE m_LowerTriangle;
};
} // linear_algebra_detail::
// ************************ STACK SYMMETRIC MATRIX ************************
//! \brief A stack based lightweight dense symmetric matrix class.
//!
//! DESCRIPTION:\n
//! This implements a stack based mathematical symmetric matrix object.
//! The idea is to provide a few simple to implement utility functions,
//! however it is primarily intended for storage and is not an alternative
//! to a good linear analysis package implementation. In fact, all utilities
//! for doing any serious linear algebra should convert this to the Eigen
//! library self adjoint representation, an implicit conversion operator
//! for doing this has been supplied. Commonly used operations on matrices
//! for example computing the inverse quadratic product or determinant
//! should be added to this header.
//!
//! IMPLEMENTATION DECISIONS:\n
//! This uses the best possible encoding for space, i.e. packed into a
//! N * (N+1) / 2 length array. This is not the best representation to use
//! for speed as it cuts down on vectorization opportunities. The Eigen
//! library does not support a packed representation for exactly this
//! reason. Our requirements are somewhat different, i.e. we potentially
//! want to store a lot of small matrices with lowest possible space
//! overhead.
//!
//! This also provides a convenience constructor to initialize to a
//! multiple of the ones matrix. Any bounds checking in matrix matrix and
//! matrix vector operations is compile time since the size is a template
//! parameter. The floating point type is templated so that one can use
//! float when space really at a premium.
//!
//! \tparam T The floating point type.
//! \tparam N The matrix dimension.
// clang-format off
template<typename T, std::size_t N>
class CSymmetricMatrixNxN : private boost::equality_comparable< CSymmetricMatrixNxN<T, N>,
boost::partially_ordered< CSymmetricMatrixNxN<T, N>,
boost::addable< CSymmetricMatrixNxN<T, N>,
boost::subtractable< CSymmetricMatrixNxN<T, N>,
boost::multipliable< CSymmetricMatrixNxN<T, N>,
boost::multipliable2< CSymmetricMatrixNxN<T, N>, T,
boost::dividable2< CSymmetricMatrixNxN<T, N>, T > > > > > > >,
private linear_algebra_detail::SSymmetricMatrix<std::array<T, N * (N + 1) / 2> >,
private linear_algebra_detail::CBoundsCheck<N>::InRange {
// clang-format on
private:
using TBase = linear_algebra_detail::SSymmetricMatrix<std::array<T, N*(N + 1) / 2>>;
template<typename U, std::size_t>
friend class CSymmetricMatrixNxN;
public:
using TArray = T[N][N];
using TVec = std::vector<T>;
using TVecVec = std::vector<TVec>;
using TConstIterator = typename std::array<T, N*(N + 1) / 2>::const_iterator;
public:
//! See core::CMemory.
static constexpr bool dynamicSizeAlwaysZero() {
return core::memory_detail::SDynamicSizeAlwaysZero<T>::value();
}
public:
//! Set to multiple of ones matrix.
explicit CSymmetricMatrixNxN(T v = T(0)) {
std::fill_n(&TBase::m_LowerTriangle[0], N * (N + 1) / 2, v);
}
//! Construct from C-style array of arrays.
explicit CSymmetricMatrixNxN(const TArray& m) {
for (std::size_t i = 0, i_ = 0; i < N; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = m[i][j];
}
}
}
//! Construct from a vector of vectors.
explicit CSymmetricMatrixNxN(const TVecVec& m) {
for (std::size_t i = 0, i_ = 0; i < N; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = m[i][j];
}
}
}
//! Construct from a small vector of small vectors.
template<std::size_t M>
explicit CSymmetricMatrixNxN(const core::CSmallVectorBase<core::CSmallVector<T, M>>& m) {
for (std::size_t i = 0, i_ = 0; i < N; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = m[i][j];
}
}
}
//! Construct from a forward iterator.
//!
//! \warning The user must ensure that the range iterated has
//! at least N (N+1) / 2 items.
template<typename ITR>
CSymmetricMatrixNxN(ITR begin, ITR end) {
for (std::size_t i = 0; i < N * (N + 1) / 2 && begin != end; ++i, ++begin) {
TBase::m_LowerTriangle[i] = static_cast<T>(*begin);
}
}
explicit CSymmetricMatrixNxN(ESymmetricMatrixType type, const CVectorNx1<T, N>& x);
//! Construct from a dense matrix.
template<typename MATRIX>
CSymmetricMatrixNxN(const CDenseMatrixInitializer<MATRIX>& m);
//! Copy construction if the underlying type is implicitly
//! convertible.
template<typename U>
CSymmetricMatrixNxN(const CSymmetricMatrixNxN<U, N>& other) {
this->operator=(other);
}
//! Assignment if the underlying type is implicitly convertible.
template<typename U>
CSymmetricMatrixNxN& operator=(const CSymmetricMatrixNxN<U, N>& other) {
this->assign(other.base());
return *this;
}
//! \name Persistence
//@{
//! Create from a delimited string.
bool fromDelimited(const std::string& str) {
return this->TBase::fromDelimited(str);
}
//! Convert to a delimited string.
std::string toDelimited() const { return this->TBase::toDelimited(); }
//@}
//! Get the number of rows.
std::size_t rows() const { return N; }
//! Get the number of columns.
std::size_t columns() const { return N; }
//! Get the i,j 'th component (no bounds checking).
inline T operator()(std::size_t i, std::size_t j) const {
return this->element(i, j);
}
//! Get the i,j 'th component (no bounds checking).
inline T& operator()(std::size_t i, std::size_t j) {
return this->element(i, j);
}
//! Get an iterator over the elements.
TConstIterator begin() const { return TBase::m_LowerTriangle.begin(); }
//! Get an iterator to the end of the elements.
TConstIterator end() const { return TBase::m_LowerTriangle.end(); }
//! Componentwise negation.
CSymmetricMatrixNxN operator-() const {
CSymmetricMatrixNxN result(*this);
result.negative();
return result;
}
//! Matrix subtraction.
const CSymmetricMatrixNxN& operator-=(const CSymmetricMatrixNxN& rhs) {
this->minusEquals(rhs.base());
return *this;
}
//! Matrix addition.
const CSymmetricMatrixNxN& operator+=(const CSymmetricMatrixNxN& rhs) {
this->plusEquals(rhs.base());
return *this;
}
//! Componentwise multiplication.
//!
//! \note This is handy in some cases and since symmetric matrices
//! are not closed under regular matrix multiplication we use
//! multiplication operator for implementing the Hadamard product.
const CSymmetricMatrixNxN& operator*=(const CSymmetricMatrixNxN& rhs) {
this->multiplyEquals(rhs);
return *this;
}
//! Scalar multiplication.
const CSymmetricMatrixNxN& operator*=(T scale) {
this->multiplyEquals(scale);
return *this;
}
//! Scalar division.
const CSymmetricMatrixNxN& operator/=(T scale) {
this->divideEquals(scale);
return *this;
}
// Matrix multiplication doesn't necessarily produce a symmetric
// matrix because matrix multiplication is non-commutative.
// Matrix division requires computing the inverse and is not
// supported.
//! Check if two matrices are identically equal.
bool operator==(const CSymmetricMatrixNxN& other) const {
return this->equal(other.base());
}
//! Lexicographical total ordering.
bool operator<(const CSymmetricMatrixNxN& rhs) const {
return this->less(rhs.base());
}
//! Check if this is zero.
bool isZero() const { return this->TBase::isZero(); }
//! Get the matrix diagonal.
template<typename VECTOR>
VECTOR diagonal() const {
return this->TBase::template diagonal<VECTOR>(N);
}
//! Get the trace.
T trace() const { return this->TBase::trace(N); }
//! Get the Frobenius norm.
double frobenius() const { return this->TBase::frobenius(N); }
//! Get the mean of the matrix elements.
double mean() const { return this->TBase::mean(N); }
//! Convert to a vector of vectors.
template<typename VECTOR_OF_VECTORS>
inline VECTOR_OF_VECTORS toVectors() const {
VECTOR_OF_VECTORS result(N);
for (std::size_t i = 0; i < N; ++i) {
result[i].resize(N);
}
for (std::size_t i = 0; i < N; ++i) {
result[i][i] = this->operator()(i, i);
for (std::size_t j = 0; j < i; ++j) {
result[i][j] = result[j][i] = this->operator()(i, j);
}
}
return result;
}
//! Convert to the specified matrix representation.
//!
//! \note The copy should be avoided by RVO.
template<typename MATRIX>
inline MATRIX toType() const {
MATRIX result(N, N);
return this->TBase::toType(N, result);
}
//! Get a checksum for the matrix.
std::uint64_t checksum() const { return this->TBase::checksum(); }
};
//! \brief Gets a constant symmetric matrix with specified dimension.
template<typename T, std::size_t N>
struct SConstant<CSymmetricMatrixNxN<T, N>> {
static CSymmetricMatrixNxN<T, N> get(std::size_t /*dimension*/, T constant) {
return CSymmetricMatrixNxN<T, N>(constant);
}
};
//! \brief Gets the identity matrix with specified dimesion.
template<typename T, std::size_t N>
struct SIdentity<CSymmetricMatrixNxN<T, N>> {
static CSymmetricMatrixNxN<T, N> get(std::size_t /*dimension*/) {
CSymmetricMatrixNxN<T, N> result(T{0});
for (std::size_t i = 0; i < N; ++i) {
result(i, i) = T{1};
}
return result;
}
};
// ************************ HEAP SYMMETRIC MATRIX ************************
//! \brief A heap based lightweight dense symmetric matrix class.
//!
//! DESCRIPTION:\n
//! This implements a heap based mathematical symmetric matrix object.
//! The idea is to provide a few simple to implement utility functions,
//! however it is primarily intended for storage and is not an alternative
//! to a good linear analysis package implementation. In fact, all utilities
//! for doing any serious linear algebra should convert this to the Eigen
//! library self adjoint representation, an explicit conversion operator
//! for doing this has been supplied. Commonly used operations on matrices
//! for example computing the inverse quadratic product or determinant
//! should be added to this header.
//!
//! IMPLEMENTATION DECISIONS:\n
//! This uses the best possible encoding for space, i.e. packed into a
//! D * (D+1) / 2 length vector where D is the dimension. This is not the
//! best representation to use for speed as it cuts down on vectorization
//! opportunities. The Eigen library does not support a packed representation
//! for exactly this reason. Our requirements are somewhat different, i.e.
//! we potentially want to store a lot of small(ish) matrices with lowest
//! possible space overhead.
//!
//! This also provides a convenience constructor to initialize to a
//! multiple of the ones matrix. There is no bounds checking in matrix
//! matrix and matrix vector operations for speed. The floating point
//! type is templated so that one can use float when space really at a
//! premium.
//!
//! \tparam T The floating point type.
// clang-format off
template<typename T>
class CSymmetricMatrix : private boost::equality_comparable< CSymmetricMatrix<T>,
boost::partially_ordered< CSymmetricMatrix<T>,
boost::addable< CSymmetricMatrix<T>,
boost::subtractable< CSymmetricMatrix<T>,
boost::multipliable< CSymmetricMatrix<T>,
boost::multipliable2< CSymmetricMatrix<T>, T,
boost::dividable2< CSymmetricMatrix<T>, T > > > > > > >,
private linear_algebra_detail::SSymmetricMatrix<std::vector<T> > {
// clang-format on
private:
using TBase = linear_algebra_detail::SSymmetricMatrix<std::vector<T>>;
template<typename U>
friend class CSymmetricMatrix;
public:
using TArray = std::vector<std::vector<T>>;
using TConstIterator = typename std::vector<T>::const_iterator;
public:
//! Set to multiple of ones matrix.
explicit CSymmetricMatrix(std::size_t d = 0, T v = T(0)) : m_D(d) {
if (d > 0) {
TBase::m_LowerTriangle.resize(d * (d + 1) / 2, v);
}
}
//! Construct from C-style array of arrays.
explicit CSymmetricMatrix(const TArray& m) : m_D(m.size()) {
TBase::m_LowerTriangle.resize(m_D * (m_D + 1) / 2);
for (std::size_t i = 0, i_ = 0; i < m_D; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = m[i][j];
}
}
}
//! Construct from a small vector of small vectors.
template<std::size_t M>
explicit CSymmetricMatrix(const core::CSmallVectorBase<core::CSmallVector<T, M>>& m)
: m_D(m.size()) {
TBase::m_LowerTriangle.resize(m_D * (m_D + 1) / 2);
for (std::size_t i = 0, i_ = 0; i < m_D; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = m[i][j];
}
}
}
//! Construct from a forward iterator.
//!
//! \warning The user must ensure that the range iterated has
//! at least N (N+1) / 2 items.
template<typename ITR>
CSymmetricMatrix(ITR begin, ITR end) {
m_D = this->dimension(std::distance(begin, end));
TBase::m_LowerTriangle.resize(m_D * (m_D + 1) / 2);
for (std::size_t i = 0; i < m_D * (m_D + 1) / 2 && begin != end; ++i, ++begin) {
TBase::m_LowerTriangle[i] = static_cast<T>(*begin);
}
}
//! Construct from the outer product of a vector with itself.
explicit CSymmetricMatrix(ESymmetricMatrixType type, const CVector<T>& x);
//! Construct from a dense matrix.
template<typename MATRIX>
CSymmetricMatrix(const CDenseMatrixInitializer<MATRIX>& m);
//! Copy construction if the underlying type is implicitly
//! convertible.
//!
//! \note Because this is template it is *not* an copy constructor
//! so this class has implicit move semantics.
template<typename U>
CSymmetricMatrix(const CSymmetricMatrix<U>& other) : m_D(other.m_D) {
this->operator=(other);
}
//! Assignment if the underlying type is implicitly convertible.
//!
//! \note Because this is template it is *not* an copy assignment
//! operator so this class has implicit move semantics.
template<typename U>
CSymmetricMatrix& operator=(const CSymmetricMatrix<U>& other) {
m_D = other.m_D;
TBase::m_LowerTriangle.resize(m_D * (m_D + 1) / 2);
this->assign(other.base());
return *this;
}
//! Efficiently swap the contents of two matrices.
void swap(CSymmetricMatrix& other) {
std::swap(m_D, other.m_D);
TBase::m_LowerTriangle.swap(other.TBase::m_LowerTriangle);
}
//! \name Persistence
//@{
//! Create from a delimited string.
bool fromDelimited(const std::string& str) {
if (this->TBase::fromDelimited(str)) {
m_D = this->dimension(TBase::m_X.size());
return true;
}
return false;
}
//! Convert to a delimited string.
std::string toDelimited() const { return this->TBase::toDelimited(); }
//@}
//! Get the number of rows.
std::size_t rows() const { return m_D; }
//! Get the number of columns.
std::size_t columns() const { return m_D; }
//! Get the i,j 'th component (no bounds checking).
inline T operator()(std::size_t i, std::size_t j) const {
return this->element(i, j);
}
//! Get the i,j 'th component (no bounds checking).
inline T& operator()(std::size_t i, std::size_t j) {
return this->element(i, j);
}
//! Get an iterator over the elements.
TConstIterator begin() const { return TBase::m_X.begin(); }
//! Get an iterator to the end of the elements.
TConstIterator end() const { return TBase::m_X.end(); }
//! Componentwise negation.
CSymmetricMatrix operator-() const {
CSymmetricMatrix result(*this);
result.negative();
return result;
}
//! Matrix subtraction.
const CSymmetricMatrix& operator-=(const CSymmetricMatrix& rhs) {
this->minusEquals(rhs.base());
return *this;
}
//! Matrix addition.
const CSymmetricMatrix& operator+=(const CSymmetricMatrix& rhs) {
this->plusEquals(rhs.base());
return *this;
}
//! Componentwise multiplication.
//!
//! \note This is handy in some cases and since symmetric matrices
//! are not closed under regular matrix multiplication we use
//! multiplication operator for implementing the Hadamard product.
const CSymmetricMatrix& operator*=(const CSymmetricMatrix& rhs) {
this->multiplyEquals(rhs);
return *this;
}
//! Scalar multiplication.
const CSymmetricMatrix& operator*=(T scale) {
this->multiplyEquals(scale);
return *this;
}
//! Scalar division.
const CSymmetricMatrix& operator/=(T scale) {
this->divideEquals(scale);
return *this;
}
// Matrix multiplication doesn't necessarily produce a symmetric
// matrix because matrix multiplication is non-commutative.
// Matrix division requires computing the inverse and is not
// supported.
//! Check if two matrices are identically equal.
bool operator==(const CSymmetricMatrix& other) const {
return this->equal(other.base());
}
//! Lexicographical total ordering.
bool operator<(const CSymmetricMatrix& rhs) const {
return this->less(rhs.base());
}
//! Check if this is zero.
bool isZero() const { return this->TBase::isZero(); }
//! Get the matrix diagonal.
template<typename VECTOR>
VECTOR diagonal() const {
return this->TBase::template diagonal<VECTOR>(m_D);
}
//! Get the trace.
T trace() const { return this->TBase::trace(m_D); }
//! The Frobenius norm.
double frobenius() const { return this->TBase::frobenius(m_D); }
//! Get the mean of the matrix elements.
double mean() const { return this->TBase::mean(m_D); }
//! Convert to a vector of vectors.
template<typename VECTOR_OF_VECTORS>
inline VECTOR_OF_VECTORS toVectors() const {
VECTOR_OF_VECTORS result(m_D);
for (std::size_t i = 0; i < m_D; ++i) {
result[i].resize(m_D);
}
for (std::size_t i = 0; i < m_D; ++i) {
result[i][i] = this->operator()(i, i);
for (std::size_t j = 0; j < i; ++j) {
result[i][j] = result[j][i] = this->operator()(i, j);
}
}
return result;
}
//! Convert to the specified matrix representation.
//!
//! \note The copy should be avoided by RVO.
template<typename MATRIX>
inline MATRIX toType() const {
MATRIX result(m_D, m_D);
return this->TBase::toType(m_D, result);
}
//! Get a checksum for the matrix.
std::uint64_t checksum() const {
return core::CHashing::hashCombine(this->TBase::checksum(),
static_cast<std::uint64_t>(m_D));
}
private:
//! Compute the dimension from the number of elements.
std::size_t dimension(std::size_t n) const {
return static_cast<std::size_t>(
(std::sqrt(8.0 * static_cast<double>(n) + 1.0) - 1.0) / 2.0 + 0.5);
}
private:
//! The rows (and columns) of this matrix.
std::size_t m_D;
};
//! \brief Gets a constant symmetric matrix with specified dimension.
template<typename T>
struct SConstant<CSymmetricMatrix<T>> {
static CSymmetricMatrix<T> get(std::size_t dimension, T constant) {
return CSymmetricMatrix<T>(dimension, constant);
}
};
//! \brief Gets the identity matrix with specified dimesion.
template<typename T>
struct SIdentity<CSymmetricMatrix<T>> {
static CSymmetricMatrix<T> get(std::size_t dimension) {
CSymmetricMatrix<T> result(dimension, T{0});
for (std::size_t i = 0; i < dimension; ++i) {
result(i, i) = T{1};
}
return result;
}
};
namespace linear_algebra_detail {
//! \brief Common vector functionality for variable storage type.
template<typename STORAGE>
struct SVector {
using Type = typename STORAGE::value_type;
//! Get read only reference.
inline const SVector& base() const { return *this; }
//! Get writable reference.
inline SVector& base() { return *this; }
//! Set this vector equal to \p other.
template<typename OTHER_STORAGE>
void assign(const SVector<OTHER_STORAGE>& other) {
std::copy(other.m_X.begin(), other.m_X.end(), m_X.begin());
}
//! Create from delimited values.
bool fromDelimited(const std::string& str);
//! Convert to a delimited string.
std::string toDelimited() const;
//! Componentwise negative.
void negative() {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] = -m_X[i];
}
}
//! Vector subtraction.
void minusEquals(const SVector& rhs) {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] -= rhs.m_X[i];
}
}
//! Vector addition.
void plusEquals(const SVector& rhs) {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] += rhs.m_X[i];
}
}
//! Componentwise multiplication.
void multiplyEquals(const SVector& scale) {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] *= scale.m_X[i];
}
}
//! Scalar multiplication.
void multiplyEquals(Type scale) {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] *= scale;
}
}
//! Componentwise division.
void divideEquals(const SVector& scale) {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] /= scale.m_X[i];
}
}
//! Scalar division.
void divideEquals(Type scale) {
for (std::size_t i = 0; i < m_X.size(); ++i) {
m_X[i] /= scale;
}
}
//! Compare this and \p other for equality.
bool equal(const SVector& other) const { return m_X == other.m_X; }
//! Lexicographical total ordering.
bool less(const SVector& rhs) const { return m_X < rhs.m_X; }
//! Check if this is zero.
bool isZero() const {
return std::find_if(m_X.begin(), m_X.end(),
[](double xi) { return xi != 0.0; }) == m_X.end();
}
//! Inner product.
double inner(const SVector& covector) const {
double result = 0.0;
for (std::size_t i = 0; i < m_X.size(); ++i) {
result += m_X[i] * covector.m_X[i];
}
return result;
}
//! Inner product.
template<typename VECTOR>
double inner(const VECTOR& covector) const {
double result = 0.0;
for (std::size_t i = 0; i < m_X.size(); ++i) {
result += m_X[i] * covector(i);
}
return result;
}
//! The L1 norm of the vector.
double L1() const {
double result = 0.0;
for (std::size_t i = 0; i < m_X.size(); ++i) {
result += std::fabs(static_cast<double>(m_X[i]));
}
return result;
}
//! Get the mean of the vector components.
double mean() const {
return std::accumulate(m_X.begin(), m_X.end(), 0.0) /
static_cast<double>(m_X.size());
}
//! Convert to the VECTOR representation.
template<typename VECTOR>
inline VECTOR& toType(VECTOR& result) const {
for (std::size_t i = 0; i < m_X.size(); ++i) {
result(i) = m_X[i];
}
return result;
}
//! Get a checksum of the components of this vector.
std::uint64_t checksum() const { return CChecksum::calculate(0, m_X); }
//! The components
STORAGE m_X;
};
} // linear_algebra_detail::
// ************************ STACK VECTOR ************************
//! \brief A stack based lightweight dense vector class.
//!
//! DESCRIPTION:\n
//! This implements a stack based mathematical vector object. The idea
//! is to provide utility functions and operators which mean that it
//! works with other ml::maths:: classes, such as the symmetric
//! matrix object and the sample (co)variance accumulators, and keep the
//! memory footprint as small as possible. This is not meant to be an
//! alternative to a good linear analysis package implementation. For
//! example, if you want to any serious linear algebra use the Eigen
//! library. An implicit conversion operator for doing this has been
//! supplied.
//!
//! IMPLEMENTATION DECISIONS:\n
//! Operators follow the Matlab componentwise convention. This provides
//! a constructor to initialize to a multiple of the 1 vector. Bounds
//! checking for vector vector and matrix vector operations is compile
//! time since the size is a template parameter. The floating point type
//! is templated so that one can use float when space is really at a
//! premium.
//!
//! \tparam T The floating point type.
//! \tparam N The vector dimension.
// clang-format off
template<typename T, std::size_t N>
class CVectorNx1 : private boost::equality_comparable< CVectorNx1<T, N>,
boost::partially_ordered< CVectorNx1<T, N>,
boost::addable< CVectorNx1<T, N>,
boost::subtractable< CVectorNx1<T, N>,
boost::multipliable< CVectorNx1<T, N>,
boost::multipliable2< CVectorNx1<T, N>, T,
boost::dividable< CVectorNx1<T, N>,
boost::dividable2< CVectorNx1<T, N>, T > > > > > > > >,
private linear_algebra_detail::SVector<std::array<T, N> >,
private linear_algebra_detail::CBoundsCheck<N>::InRange {
// clang-format on
private:
using TBase = linear_algebra_detail::SVector<std::array<T, N>>;
template<typename U, std::size_t>
friend class CVectorNx1;
public:
using TArray = T[N];
using TStdArray = std::array<T, N>;
using TConstIterator = typename TStdArray::const_iterator;
public:
//! See core::CMemory.
static constexpr bool dynamicSizeAlwaysZero() {
return core::memory_detail::SDynamicSizeAlwaysZero<T>::value();
}
public:
//! Set to multiple of ones vector.
explicit CVectorNx1(T v = T(0)) { std::fill_n(&TBase::m_X[0], N, v); }
//! Construct from a C-style array.
explicit CVectorNx1(const TArray& v) {
for (std::size_t i = 0; i < N; ++i) {
TBase::m_X[i] = v[i];
}
}
//! Construct from a boost array.
template<typename U>
explicit CVectorNx1(const std::array<U, N>& a) {
for (std::size_t i = 0; i < N; ++i) {
TBase::m_X[i] = a[i];
}
}
//! Construct from a vector.
template<typename U>
explicit CVectorNx1(const std::vector<U>& v) {
for (std::size_t i = 0; i < N; ++i) {
TBase::m_X[i] = v[i];
}
}
//! Construct from a vector.
template<typename U>
explicit CVectorNx1(const core::CSmallVectorBase<U>& v) {
for (std::size_t i = 0; i < N; ++i) {
TBase::m_X[i] = v[i];
}
}
//! Construct from a forward iterator.
//!
//! \warning The user must ensure that the range iterated has
//! at least N items.
template<typename ITR>
CVectorNx1(ITR begin, ITR end) {
if (std::distance(begin, end) != N) {
LOG_ERROR(<< "Bad range");
return;
}
std::copy(begin, end, &TBase::m_X[0]);
}
//! Construct from a dense vector.
template<typename VECTOR>
CVectorNx1(const CDenseVectorInitializer<VECTOR>& v);
//! Copy construction if the underlying type is implicitly
//! convertible.
template<typename U>
CVectorNx1(const CVectorNx1<U, N>& other) {
this->operator=(other);
}
//! Assignment if the underlying type is implicitly convertible.
template<typename U>
CVectorNx1& operator=(const CVectorNx1<U, N>& other) {
this->assign(other.base());
return *this;
}
//! \name Persistence
//@{
//! Create from a delimited string.
bool fromDelimited(const std::string& str) {
return this->TBase::fromDelimited(str);
}
//! Convert to a delimited string.
std::string toDelimited() const { return this->TBase::toDelimited(); }
//@}
//! Get the dimension.
std::size_t dimension() const { return N; }
//! Get the i'th component (no bounds checking).
inline T operator()(std::size_t i) const { return TBase::m_X[i]; }
//! Get the i'th component (no bounds checking).
inline T& operator()(std::size_t i) { return TBase::m_X[i]; }
//! Get an iterator over the elements.
TConstIterator begin() const { return TBase::m_X.begin(); }
//! Get an iterator to the end of the elements.
TConstIterator end() const { return TBase::m_X.end(); }
//! Componentwise negation.
CVectorNx1 operator-() const {
CVectorNx1 result(*this);
result.negative();
return result;
}
//! Vector subtraction.
const CVectorNx1& operator-=(const CVectorNx1& lhs) {
this->minusEquals(lhs.base());
return *this;
}
//! Vector addition.
const CVectorNx1& operator+=(const CVectorNx1& lhs) {
this->plusEquals(lhs.base());
return *this;
}
//! Componentwise multiplication.
const CVectorNx1& operator*=(const CVectorNx1& scale) {
this->multiplyEquals(scale.base());
return *this;
}
//! Scalar multiplication.
const CVectorNx1& operator*=(T scale) {
this->multiplyEquals(scale);
return *this;
}
//! Componentwise division.
const CVectorNx1& operator/=(const CVectorNx1& scale) {
this->divideEquals(scale.base());
return *this;
}
//! Scalar division.
const CVectorNx1& operator/=(T scale) {
this->divideEquals(scale);
return *this;
}
//! Check if two vectors are identically equal.
bool operator==(const CVectorNx1& other) const {
return this->equal(other.base());
}
//! Lexicographical total ordering.
bool operator<(const CVectorNx1& rhs) const {
return this->less(rhs.base());
}
//! Check if this is zero.
bool isZero() const { return this->TBase::isZero(); }
//! Inner product.
double inner(const CVectorNx1& covector) const {
return this->TBase::inner(covector.base());
}
//! Inner product.
template<typename VECTOR>
double inner(const VECTOR& covector) const {
return this->TBase::template inner<VECTOR>(covector);
}
//! Outer product.
//!
//! \note The copy should be avoided by RVO.
CSymmetricMatrixNxN<T, N> outer() const {
return CSymmetricMatrixNxN<T, N>(E_OuterProduct, *this);
}
//! Get as a diagonal matrix.
//!
//! \note The copy should be avoided by RVO.
CSymmetricMatrixNxN<T, N> asDiagonal() const {
return CSymmetricMatrixNxN<T, N>(E_Diagonal, *this);
}
//! L1 norm.
double L1() const { return this->TBase::L1(); }
//! Euclidean norm.
double euclidean() const { return std::sqrt(this->inner(*this)); }
//! Get the mean of the vector components.
double mean() const { return this->TBase::mean(); }
//! Convert to a vector on a different underlying type.
template<typename U>
inline CVectorNx1<U, N> to() const {
return CVectorNx1<U, N>(*this);
}
//! Convert to a vector.
template<typename VECTOR>
inline VECTOR toVector() const {
return VECTOR(this->begin(), this->end());
}
//! Convert to a boost array.
inline TStdArray toArray() const { return TBase::m_X; }
//! Convert to the specified vector representation.
//!
//! \note The copy should be avoided by RVO.
template<typename VECTOR>
inline VECTOR toType() const {
VECTOR result(N);
return this->TBase::toType(result);
}
//! Get a checksum of this vector's components.
std::uint64_t checksum() const { return this->TBase::checksum(); }
//! Get the smallest possible vector.
static const CVectorNx1& smallest() {
static const CVectorNx1 result(std::numeric_limits<T>::lowest());
return result;
}
//! Get the largest possible vector.
static const CVectorNx1& largest() {
static const CVectorNx1 result(std::numeric_limits<T>::max());
return result;
}
};
//! Construct from the outer product of a vector with itself.
template<typename T, std::size_t N>
CSymmetricMatrixNxN<T, N>::CSymmetricMatrixNxN(ESymmetricMatrixType type,
const CVectorNx1<T, N>& x) {
switch (type) {
case E_OuterProduct:
for (std::size_t i = 0, i_ = 0; i < N; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = x(i) * x(j);
}
}
break;
case E_Diagonal:
for (std::size_t i = 0, i_ = 0; i < N; ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = i == j ? x(i) : T(0);
}
}
break;
}
}
//! \brief Gets a constant vector with specified dimension.
template<typename T, std::size_t N>
struct SConstant<CVectorNx1<T, N>> {
static CVectorNx1<T, N> get(std::size_t /*dimension*/, T constant) {
return CVectorNx1<T, N>(constant);
}
};
// ************************ HEAP VECTOR ************************
//! \brief A heap based lightweight dense vector class.
//!
//! DESCRIPTION:\n
//! This implements a heap based mathematical vector object. The idea
//! is to provide utility functions and operators which mean that it
//! works with other ml::maths:: classes, such as the symmetric
//! matrix object and the sample (co)variance accumulators, and keep the
//! memory footprint as small as possible. This is not meant to be an
//! alternative to a good linear analysis package implementation. For
//! example, if you want to any serious linear algebra use the Eigen
//! library. An implicit conversion operator for doing this has been
//! supplied.
//!
//! IMPLEMENTATION DECISIONS:\n
//! Operators follow the Matlab componentwise convention. This provides
//! a constructor to initialize to a multiple of the 1 vector. There is
//! no bounds checking for efficiency. The floating point type is templated
//! so that one can use float when space is really at a premium.
//!
//! \tparam T The floating point type.
// clang-format off
template<typename T>
class CVector : private boost::equality_comparable< CVector<T>,
boost::partially_ordered< CVector<T>,
boost::addable< CVector<T>,
boost::subtractable< CVector<T>,
boost::multipliable< CVector<T>,
boost::multipliable2< CVector<T>, T,
boost::dividable< CVector<T>,
boost::dividable2< CVector<T>, T > > > > > > > >,
private linear_algebra_detail::SVector<std::vector<T> > {
// clang-format on
private:
using TBase = linear_algebra_detail::SVector<std::vector<T>>;
template<typename U>
friend class CVector;
public:
using TArray = std::vector<T>;
using TConstIterator = typename TArray::const_iterator;
public:
//! Set to multiple of ones vector.
explicit CVector(std::size_t d = 0, T v = T(0)) {
if (d > 0) {
TBase::m_X.resize(d, v);
}
}
//! Construct from a boost array.
template<std::size_t N>
explicit CVector(const std::array<T, N>& a) {
for (std::size_t i = 0; i < N; ++i) {
TBase::m_X[i] = a[i];
}
}
//! Construct from a vector.
template<typename U>
explicit CVector(const std::vector<U>& v) {
TBase::m_X = v;
}
//! Construct from a vector.
template<typename U>
explicit CVector(const core::CSmallVectorBase<U>& v) {
TBase::m_X.assign(v.begin(), v.end());
}
//! Construct from the range [\p begin, \p end).
template<typename ITR>
CVector(ITR begin, ITR end) {
TBase::m_X.assign(begin, end);
}
//! Construct from a dense vector.
template<typename VECTOR>
CVector(const CDenseVectorInitializer<VECTOR>& v);
//! Copy construction if the underlying type is implicitly
//! convertible.
//!
//! \note Because this is template it is *not* an copy constructor
//! so this class has implicit move semantics.
template<typename U>
CVector(const CVector<U>& other) {
this->operator=(other);
}
//! Assignment if the underlying type is implicitly convertible.
//!
//! \note Because this is template it is *not* an copy assignment
//! operator so this class has implicit move semantics.
template<typename U>
CVector& operator=(const CVector<U>& other) {
TBase::m_X.resize(other.dimension());
this->TBase::assign(other.base());
return *this;
}
//! Efficiently swap the contents of two vectors.
void swap(CVector& other) { TBase::m_X.swap(other.TBase::m_X); }
//! Reserve enough memory to hold \p d components.
void reserve(std::size_t d) { TBase::m_X.reserve(d); }
//! Assign the components from the range [\p begin, \p end).
template<typename ITR>
void assign(ITR begin, ITR end) {
TBase::m_X.assign(begin, end);
}
//! Extend the vector to dimension \p d adding components
//! initialized to \p v.
void extend(std::size_t d, T v = T(0)) {
TBase::m_X.resize(this->dimension() + d, v);
}
//! Extend the vector adding components initialized to \p v.
template<typename ITR>
void extend(ITR begin, ITR end) {
TBase::m_X.insert(TBase::m_X.end(), begin, end);
}
//! \name Persistence
//@{
//! Create from a delimited string.
bool fromDelimited(const std::string& str) {
return this->TBase::fromDelimited(str);
}
//! Persist state to delimited values.
std::string toDelimited() const { return this->TBase::toDelimited(); }
//@}
//! Get the dimension.
std::size_t dimension() const { return TBase::m_X.size(); }
//! Get the i'th component (no bounds checking).
inline T operator()(std::size_t i) const { return TBase::m_X[i]; }
//! Get the i'th component (no bounds checking).
inline T& operator()(std::size_t i) { return TBase::m_X[i]; }
//! Get an iterator over the elements.
TConstIterator begin() const { return TBase::m_X.begin(); }
//! Get an iterator to the end of the elements.
TConstIterator end() const { return TBase::m_X.end(); }
//! Componentwise negation.
CVector operator-() const {
CVector result(*this);
result.negative();
return result;
}
//! Vector subtraction.
const CVector& operator-=(const CVector& lhs) {
this->minusEquals(lhs.base());
return *this;
}
//! Vector addition.
const CVector& operator+=(const CVector& lhs) {
this->plusEquals(lhs.base());
return *this;
}
//! Componentwise multiplication.
const CVector& operator*=(const CVector& scale) {
this->multiplyEquals(scale.base());
return *this;
}
//! Scalar multiplication.
const CVector& operator*=(T scale) {
this->multiplyEquals(scale);
return *this;
}
//! Componentwise division.
const CVector& operator/=(const CVector& scale) {
this->divideEquals(scale.base());
return *this;
}
//! Scalar division.
const CVector& operator/=(T scale) {
this->divideEquals(scale);
return *this;
}
//! Check if two vectors are identically equal.
bool operator==(const CVector& other) const {
return this->equal(other.base());
}
//! Lexicographical total ordering.
bool operator<(const CVector& rhs) const { return this->less(rhs.base()); }
//! Check if this is zero.
bool isZero() const { return this->TBase::isZero(); }
//! Inner product.
double inner(const CVector& covector) const {
return this->TBase::inner(covector.base());
}
//! Inner product.
template<typename VECTOR>
double inner(const VECTOR& covector) const {
return this->TBase::template inner<VECTOR>(covector);
}
//! Outer product.
//!
//! \note The copy should be avoided by RVO.
CSymmetricMatrix<T> outer() const {
return CSymmetricMatrix<T>(E_OuterProduct, *this);
}
//! Get as a diagonal matrix.
//!
//! \note The copy should be avoided by RVO.
CSymmetricMatrix<T> asDiagonal() const {
return CSymmetricMatrix<T>(E_Diagonal, *this);
}
//! L1 norm.
double L1() const { return this->TBase::L1(); }
//! Euclidean norm.
double euclidean() const { return std::sqrt(this->inner(*this)); }
//! Get the mean of the vector components.
double mean() const { return this->TBase::mean(); }
//! Convert to a vector on a different underlying type.
template<typename U>
inline CVector<U> to() const {
return CVector<U>(*this);
}
//! Convert to a vector.
template<typename VECTOR>
inline VECTOR toVector() const {
return VECTOR(this->begin(), this->end());
}
//! Convert to the specified vector representation.
//!
//! \note The copy should be avoided by RVO.
template<typename VECTOR>
inline VECTOR toType() const {
VECTOR result(this->dimension());
return this->TBase::toType(result);
}
//! Get a checksum of this vector's components.
std::uint64_t checksum() const { return this->TBase::checksum(); }
//! Get the smallest possible vector.
static const CVector& smallest(std::size_t d) {
static const CVector result(d, std::numeric_limits<T>::lowest());
return result;
}
//! Get the largest possible vector.
static const CVector& largest(std::size_t d) {
static const CVector result(d, std::numeric_limits<T>::max());
return result;
}
};
template<typename T>
CSymmetricMatrix<T>::CSymmetricMatrix(ESymmetricMatrixType type, const CVector<T>& x) {
m_D = x.dimension();
TBase::m_LowerTriangle.resize(m_D * (m_D + 1) / 2);
switch (type) {
case E_OuterProduct:
for (std::size_t i = 0, i_ = 0; i < x.dimension(); ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = x(i) * x(j);
}
}
break;
case E_Diagonal:
for (std::size_t i = 0, i_ = 0; i < x.dimension(); ++i) {
for (std::size_t j = 0; j <= i; ++j, ++i_) {
TBase::m_LowerTriangle[i_] = i == j ? x(i) : T(0);
}
}
break;
}
}
//! \brief Gets a constant vector with specified dimension.
template<typename T>
struct SConstant<CVector<T>> {
static CVector<T> get(std::size_t dimension, T constant) {
return CVector<T>(dimension, constant);
}
};
// ************************ FREE FUNCTIONS ************************
//! Free swap picked up by std:: algorithms etc.
template<typename T>
void swap(CSymmetricMatrix<T>& lhs, CSymmetricMatrix<T>& rhs) {
lhs.swap(rhs);
}
//! Free swap picked up by std:: algorithms etc.
template<typename T>
void swap(CVector<T>& lhs, CVector<T>& rhs) {
lhs.swap(rhs);
}
//! Compute the matrix vector product
//! <pre class="fragment">
//! \(M x\)
//! </pre>
//!
//! \param[in] m The matrix.
//! \param[in] x The vector.
template<typename T, std::size_t N>
CVectorNx1<T, N> operator*(const CSymmetricMatrixNxN<T, N>& m, const CVectorNx1<T, N>& x) {
CVectorNx1<T, N> result;
for (std::size_t i = 0; i < N; ++i) {
double component = 0.0;
for (std::size_t j = 0; j < N; ++j) {
component += m(i, j) * x(j);
}
result(i) = component;
}
return result;
}
//! Compute the matrix vector product
//! <pre class="fragment">
//! \(M x\)
//! </pre>
//!
//! \param[in] m The matrix.
//! \param[in] x The vector.
template<typename T>
CVector<T> operator*(const CSymmetricMatrix<T>& m, const CVector<T>& x) {
CVector<T> result(x.dimension());
for (std::size_t i = 0; i < m.rows(); ++i) {
double component = 0.0;
for (std::size_t j = 0; j < m.columns(); ++j) {
component += m(i, j) * x(j);
}
result(i) = component;
}
return result;
}
}
}
}
#endif // INCLUDED_ml_maths_common_CLinearAlgebra_h