eigen/Eigen/src/Sparse/BasicSparseCholesky.h
Gael Guennebaud 068ff3370d Sparse module:
* several fixes (transpose, matrix product, etc...)
 * Added a basic cholesky factorization
 * Added a low level hybrid dense/sparse vector class
   to help writing code involving intensive read/write
   in a fixed vector. It is currently used to implement
   the matrix product itself as well as in the Cholesky
   factorization.
2008-10-04 14:23:00 +00:00

441 lines
14 KiB
C++

// This file is part of Eigen, a lightweight C++ template library
// for linear algebra. Eigen itself is part of the KDE project.
//
// Copyright (C) 2008 Gael Guennebaud <g.gael@free.fr>
//
// Eigen is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// Alternatively, you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// Eigen is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// Eigen. If not, see <http://www.gnu.org/licenses/>.
#ifndef EIGEN_BASICSPARSECHOLESKY_H
#define EIGEN_BASICSPARSECHOLESKY_H
/** \ingroup Sparse_Module
*
* \class BasicSparseCholesky
*
* \brief Standard Cholesky decomposition of a matrix and associated features
*
* \param MatrixType the type of the matrix of which we are computing the Cholesky decomposition
*
* \sa class Cholesky, class CholeskyWithoutSquareRoot
*/
template<typename MatrixType> class BasicSparseCholesky
{
private:
typedef typename MatrixType::Scalar Scalar;
typedef typename NumTraits<typename MatrixType::Scalar>::Real RealScalar;
typedef Matrix<Scalar, MatrixType::ColsAtCompileTime, 1> VectorType;
enum {
PacketSize = ei_packet_traits<Scalar>::size,
AlignmentMask = int(PacketSize)-1
};
public:
BasicSparseCholesky(const MatrixType& matrix)
: m_matrix(matrix.rows(), matrix.cols())
{
compute(matrix);
}
inline const MatrixType& matrixL(void) const { return m_matrix; }
/** \returns true if the matrix is positive definite */
inline bool isPositiveDefinite(void) const { return m_isPositiveDefinite; }
// template<typename Derived>
// typename Derived::Eval solve(const MatrixBase<Derived> &b) const;
void compute(const MatrixType& matrix);
protected:
/** \internal
* Used to compute and store L
* The strict upper part is not used and even not initialized.
*/
MatrixType m_matrix;
bool m_isPositiveDefinite;
struct ListEl
{
int next;
int index;
Scalar value;
};
};
/** Computes / recomputes the Cholesky decomposition A = LL^* = U^*U of \a matrix
*/
#ifdef IGEN_BASICSPARSECHOLESKY_H
template<typename MatrixType>
void BasicSparseCholesky<MatrixType>::compute(const MatrixType& a)
{
assert(a.rows()==a.cols());
const int size = a.rows();
m_matrix.resize(size, size);
const RealScalar eps = ei_sqrt(precision<Scalar>());
// allocate a temporary vector for accumulations
AmbiVector<Scalar> tempVector(size);
// TODO estimate the number of nnz
m_matrix.startFill(a.nonZeros()*2);
for (int j = 0; j < size; ++j)
{
// std::cout << j << "\n";
Scalar x = ei_real(a.coeff(j,j));
int endSize = size-j-1;
// TODO estimate the number of non zero entries
// float ratioLhs = float(lhs.nonZeros())/float(lhs.rows()*lhs.cols());
// float avgNnzPerRhsColumn = float(rhs.nonZeros())/float(cols);
// float ratioRes = std::min(ratioLhs * avgNnzPerRhsColumn, 1.f);
// let's do a more accurate determination of the nnz ratio for the current column j of res
//float ratioColRes = std::min(ratioLhs * rhs.innerNonZeros(j), 1.f);
// FIXME find a nice way to get the number of nonzeros of a sub matrix (here an inner vector)
// float ratioColRes = ratioRes;
// if (ratioColRes>0.1)
// tempVector.init(IsSparse);
tempVector.init(IsDense);
tempVector.setBounds(j+1,size);
tempVector.setZero();
// init with current matrix a
{
typename MatrixType::InnerIterator it(a,j);
++it; // skip diagonal element
for (; it; ++it)
tempVector.coeffRef(it.index()) = it.value();
}
for (int k=0; k<j+1; ++k)
{
typename MatrixType::InnerIterator it(m_matrix, k);
while (it && it.index()<j)
++it;
if (it && it.index()==j)
{
Scalar y = it.value();
x -= ei_abs2(y);
++it; // skip j-th element, and process remaing column coefficients
tempVector.restart();
for (; it; ++it)
{
tempVector.coeffRef(it.index()) -= it.value() * y;
}
}
}
// copy the temporary vector to the respective m_matrix.col()
// while scaling the result by 1/real(x)
RealScalar rx = ei_sqrt(ei_real(x));
m_matrix.fill(j,j) = rx;
Scalar y = Scalar(1)/rx;
for (typename AmbiVector<Scalar>::Iterator it(tempVector); it; ++it)
{
m_matrix.fill(it.index(), j) = it.value() * y;
}
}
m_matrix.endFill();
}
#else
template<typename MatrixType>
void BasicSparseCholesky<MatrixType>::compute(const MatrixType& a)
{
assert(a.rows()==a.cols());
const int size = a.rows();
m_matrix.resize(size, size);
const RealScalar eps = ei_sqrt(precision<Scalar>());
// allocate a temporary buffer
Scalar* buffer = new Scalar[size*2];
m_matrix.startFill(a.nonZeros()*2);
// RealScalar x;
// x = ei_real(a.coeff(0,0));
// m_isPositiveDefinite = x > eps && ei_isMuchSmallerThan(ei_imag(a.coeff(0,0)), RealScalar(1));
// m_matrix.fill(0,0) = ei_sqrt(x);
// m_matrix.col(0).end(size-1) = a.row(0).end(size-1).adjoint() / ei_real(m_matrix.coeff(0,0));
for (int j = 0; j < size; ++j)
{
// std::cout << j << " " << std::flush;
// Scalar tmp = ei_real(a.coeff(j,j));
// if (j>0)
// tmp -= m_matrix.row(j).start(j).norm2();
// x = ei_real(tmp);
// std::cout << "x = " << x << "\n";
// if (x < eps || (!ei_isMuchSmallerThan(ei_imag(tmp), RealScalar(1))))
// {
// m_isPositiveDefinite = false;
// return;
// }
// m_matrix.fill(j,j) = x = ei_sqrt(x);
Scalar x = ei_real(a.coeff(j,j));
// if (j>0)
// x -= m_matrix.row(j).start(j).norm2();
// RealScalar rx = ei_sqrt(ei_real(x));
// m_matrix.fill(j,j) = rx;
int endSize = size-j-1;
/*if (endSize>0)*/ {
// Note that when all matrix columns have good alignment, then the following
// product is guaranteed to be optimal with respect to alignment.
// m_matrix.col(j).end(endSize) =
// (m_matrix.block(j+1, 0, endSize, j) * m_matrix.row(j).start(j).adjoint()).lazy();
// FIXME could use a.col instead of a.row
// m_matrix.col(j).end(endSize) = (a.row(j).end(endSize).adjoint()
// - m_matrix.col(j).end(endSize) ) / x;
// make sure to call innerSize/outerSize since we fake the storage order.
// estimate the number of non zero entries
// float ratioLhs = float(lhs.nonZeros())/float(lhs.rows()*lhs.cols());
// float avgNnzPerRhsColumn = float(rhs.nonZeros())/float(cols);
// float ratioRes = std::min(ratioLhs * avgNnzPerRhsColumn, 1.f);
// for (int j1=0; j1<cols; ++j1)
{
// let's do a more accurate determination of the nnz ratio for the current column j of res
//float ratioColRes = std::min(ratioLhs * rhs.innerNonZeros(j), 1.f);
// FIXME find a nice way to get the number of nonzeros of a sub matrix (here an inner vector)
// float ratioColRes = ratioRes;
// if (ratioColRes>0.1)
if (true)
{
// dense path, the scalar * columns products are accumulated into a dense column
Scalar* __restrict__ tmp = buffer;
// set to zero
for (int k=j+1; k<size; ++k)
tmp[k] = 0;
// init with current matrix a
{
typename MatrixType::InnerIterator it(a,j);
++it;
for (; it; ++it)
tmp[it.index()] = it.value();
}
for (int k=0; k<j+1; ++k)
{
// Scalar y = m_matrix.coeff(j,k);
// if (!ei_isMuchSmallerThan(ei_abs(y),Scalar(1)))
// {
typename MatrixType::InnerIterator it(m_matrix, k);
while (it && it.index()<j)
++it;
if (it && it.index()==j)
{
Scalar y = it.value();
x -= ei_abs2(y);
// if (!ei_isMuchSmallerThan(ei_abs(y),Scalar(0.1)))
{
++it;
for (; it; ++it)
{
tmp[it.index()] -= it.value() * y;
}
}
}
}
// copy the temporary to the respective m_matrix.col()
RealScalar rx = ei_sqrt(ei_real(x));
m_matrix.fill(j,j) = rx;
Scalar y = Scalar(1)/rx;
for (int k=j+1; k<size; ++k)
//if (tmp[k]!=0)
if (!ei_isMuchSmallerThan(ei_abs(tmp[k]),Scalar(0.01)))
m_matrix.fill(k, j) = tmp[k]*y;
}
else
{
ListEl* __restrict__ tmp = reinterpret_cast<ListEl*>(buffer);
// sparse path, the scalar * columns products are accumulated into a linked list
int tmp_size = 0;
int tmp_start = -1;
{
int tmp_el = tmp_start;
typename MatrixType::InnerIterator it(a,j);
if (it)
{
++it;
for (; it; ++it)
{
Scalar v = it.value();
int id = it.index();
if (tmp_size==0)
{
tmp_start = 0;
tmp_el = 0;
tmp_size++;
tmp[0].value = v;
tmp[0].index = id;
tmp[0].next = -1;
}
else if (id<tmp[tmp_start].index)
{
tmp[tmp_size].value = v;
tmp[tmp_size].index = id;
tmp[tmp_size].next = tmp_start;
tmp_start = tmp_size;
tmp_el = tmp_start;
tmp_size++;
}
else
{
int nextel = tmp[tmp_el].next;
while (nextel >= 0 && tmp[nextel].index<=id)
{
tmp_el = nextel;
nextel = tmp[nextel].next;
}
if (tmp[tmp_el].index==id)
{
tmp[tmp_el].value = v;
}
else
{
tmp[tmp_size].value = v;
tmp[tmp_size].index = id;
tmp[tmp_size].next = tmp[tmp_el].next;
tmp[tmp_el].next = tmp_size;
tmp_size++;
}
}
}
}
}
// for (typename Rhs::InnerIterator rhsIt(rhs, j); rhsIt; ++rhsIt)
for (int k=0; k<j+1; ++k)
{
// Scalar y = m_matrix.coeff(j,k);
// if (!ei_isMuchSmallerThan(ei_abs(y),Scalar(1)))
// {
int tmp_el = tmp_start;
typename MatrixType::InnerIterator it(m_matrix, k);
while (it && it.index()<j)
++it;
if (it && it.index()==j)
{
Scalar y = it.value();
x -= ei_abs2(y);
for (; it; ++it)
{
Scalar v = -it.value() * y;
int id = it.index();
if (tmp_size==0)
{
// std::cout << "insert because size==0\n";
tmp_start = 0;
tmp_el = 0;
tmp_size++;
tmp[0].value = v;
tmp[0].index = id;
tmp[0].next = -1;
}
else if (id<tmp[tmp_start].index)
{
// std::cout << "insert because not in (0) " << id << " " << tmp[tmp_start].index << " " << tmp_start << "\n";
tmp[tmp_size].value = v;
tmp[tmp_size].index = id;
tmp[tmp_size].next = tmp_start;
tmp_start = tmp_size;
tmp_el = tmp_start;
tmp_size++;
}
else
{
int nextel = tmp[tmp_el].next;
while (nextel >= 0 && tmp[nextel].index<=id)
{
tmp_el = nextel;
nextel = tmp[nextel].next;
}
if (tmp[tmp_el].index==id)
{
tmp[tmp_el].value -= v;
}
else
{
// std::cout << "insert because not in (1)\n";
tmp[tmp_size].value = v;
tmp[tmp_size].index = id;
tmp[tmp_size].next = tmp[tmp_el].next;
tmp[tmp_el].next = tmp_size;
tmp_size++;
}
}
}
}
}
RealScalar rx = ei_sqrt(ei_real(x));
m_matrix.fill(j,j) = rx;
Scalar y = Scalar(1)/rx;
int k = tmp_start;
while (k>=0)
{
if (!ei_isMuchSmallerThan(ei_abs(tmp[k].value),Scalar(0.01)))
{
// std::cout << "fill " << tmp[k].index << "," << j << "\n";
m_matrix.fill(tmp[k].index, j) = tmp[k].value * y;
}
k = tmp[k].next;
}
}
}
}
}
m_matrix.endFill();
}
#endif
/** \returns the solution of \f$ A x = b \f$ using the current decomposition of A.
* In other words, it returns \f$ A^{-1} b \f$ computing
* \f$ {L^{*}}^{-1} L^{-1} b \f$ from right to left.
* \param b the column vector \f$ b \f$, which can also be a matrix.
*
* Example: \include Cholesky_solve.cpp
* Output: \verbinclude Cholesky_solve.out
*
* \sa MatrixBase::cholesky(), CholeskyWithoutSquareRoot::solve()
*/
// template<typename MatrixType>
// template<typename Derived>
// typename Derived::Eval Cholesky<MatrixType>::solve(const MatrixBase<Derived> &b) const
// {
// const int size = m_matrix.rows();
// ei_assert(size==b.rows());
//
// return m_matrix.adjoint().template part<Upper>().solveTriangular(matrixL().solveTriangular(b));
// }
#endif // EIGEN_BASICSPARSECHOLESKY_H