include/maths/common/COrthogonaliser.h (128 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_COrthogonaliser_h
#define INCLUDED_ml_maths_common_COrthogonaliser_h
#include <core/CLogger.h>
#include <maths/common/CLinearAlgebraEigen.h>
#include <maths/common/CLinearAlgebraShims.h>
#include <array>
#include <vector>
namespace ml {
namespace maths {
namespace common {
//! \brief Orthogonalizes a set of vectors via Househlder Orthogonalisation.
class COrthogonaliser {
public:
//! Get an orthonormal basis spanning \p vectors. Note that the result is written
//! back to \p vectors.
//!
//! \param[in,out] vectors The vectors for which to compute an orthonormal basis.
//! Note if there linear dependenices this will resized to the size of the spanning
//! basis.
//! \return True if there is an orthonormal basis.
template<typename VECTOR>
static bool orthonormalBasis(std::vector<VECTOR>& vectors) {
return orthonormalBasisImpl(vectors);
}
//! Get an orthonormal basis spanning \p vectors. Note that the result is written
//! back to \p vectors.
//!
//! \param[in,out] vectors The linearly independent vectors for which to compute
//! an orthonormal basis.
//! \return True if and only if there is an orthonormal basis of size N.
template<typename VECTOR, std::size_t N>
static bool orthonormalBasis(std::array<VECTOR, N>& vectors) {
return orthonormalBasisImpl(vectors);
}
private:
template<typename VECTORS>
static bool orthonormalBasisImpl(VECTORS& vectors) {
if (vectors.empty()) {
return true;
}
int dimension{COrthogonaliser::dimension(vectors)};
if (dimension < 0) {
return false;
}
int rows{dimension};
int cols{static_cast<int>(vectors.size())};
CDenseMatrix<double> x(rows, cols);
for (std::size_t j = 0; j < vectors.size(); ++j) {
writeColumn(j, vectors[j], x);
}
auto qr = x.colPivHouseholderQr();
auto rank = qr.rank();
CDenseMatrix<double> thinQ{CDenseMatrix<double>::Identity(rows, rank)};
thinQ = qr.householderQ() * thinQ;
if (resize(vectors, rank) == false) {
return false;
}
for (long int j = 0; j < rank; ++j) {
readColumn(j, thinQ, vectors[j]);
}
return true;
}
template<typename VECTORS>
static int dimension(const VECTORS& vectors) {
std::size_t result{las::dimension(vectors[0])};
for (std::size_t i = 1; i < vectors.size(); ++i) {
if (las::dimension(vectors[i]) != result) {
LOG_ERROR(<< "Mismatched dimensions "
<< las::dimension(vectors[i]) << " != " << result);
return -1;
}
}
return static_cast<int>(result);
}
template<typename SCALAR>
static int dimension(const std::vector<std::vector<SCALAR>>& vectors) {
std::size_t result{vectors[0].size()};
for (std::size_t i = 1; i < vectors.size(); ++i) {
if (vectors[i].size() != result) {
LOG_ERROR(<< "Mismatched dimensions " << vectors[i].size() << " != " << result);
return -1;
}
}
return static_cast<int>(result);
}
template<typename VECTOR>
static bool resize(std::vector<VECTOR>& vectors, std::size_t rank) {
if (vectors.size() > rank) {
vectors.resize(rank);
}
return true;
}
template<typename VECTOR, std::size_t N>
static bool resize(std::array<VECTOR, N>&, std::size_t rank) {
if (N > rank) {
LOG_TRACE(<< "Found linear dependencies but require none");
return false;
}
return true;
}
template<typename VECTOR>
static void writeColumn(std::size_t j, const VECTOR& vector, CDenseMatrix<double>& m) {
for (std::size_t i = 0; i < las::dimension(vector); ++i) {
m(i, j) = vector(i);
}
}
template<typename SCALAR>
static void writeColumn(std::size_t j,
const std::vector<SCALAR>& vector,
CDenseMatrix<double>& m) {
for (std::size_t i = 0; i < vector.size(); ++i) {
m(i, j) = vector[i];
}
}
static void writeColumn(std::size_t j,
const CDenseVector<double>& vector,
CDenseMatrix<double>& m) {
m.col(j) = vector;
}
template<typename VECTOR>
static void readColumn(std::size_t j, const CDenseMatrix<double>& m, VECTOR& vector) {
for (std::size_t i = 0; i < las::dimension(vector); ++i) {
vector(i) = m(i, j);
}
}
static void readColumn(std::size_t j,
const CDenseMatrix<double>& m,
CDenseVector<double>& vector) {
vector = m.col(j);
}
template<typename SCALAR>
static void
readColumn(std::size_t j, const CDenseMatrix<double>& m, std::vector<SCALAR>& vector) {
for (std::size_t i = 0; i < vector.size(); ++i) {
vector[i] = m(i, j);
}
}
};
}
}
}
#endif // INCLUDED_ml_maths_common_COrthogonaliser_h