Commit 247373eb authored by Jason Rhinelander's avatar Jason Rhinelander

Removed Matrix layer in favour of just using Eigen

The Matrix layer worked, but is too slow (creativity default runs took
about 2.5x as long with the Matrix layer).  It's also sort of a hassle:
any new matrix features that are needed require adding new glue layer
instead of just using Eigen as is.

This means, however, that eris now requires Eigen to compile.

This commit is basically the old creativity code, but with some minor
cleanups to the restriction code, and a modification to use jacobiSvd
for least-squares calculation (instead of using X'X.solve(X'y) with a
QR solver).
parent 85c5bc98
......@@ -6,6 +6,7 @@ flags = [
'-Wextra',
'-std=c++11',
'-I', 'include',
'-I', '/usr/include/eigen3',
'-DERIS_TESTS' # Needed for test scripts to compile
]
......
......@@ -58,6 +58,9 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -DERIS_DEBUG")
find_package(Threads REQUIRED)
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})
find_package(Boost REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
......
......@@ -21,8 +21,9 @@ The library name, Eris, is the name of the Greek goddess of chaos.
## Requirements
- [boost](http://www.boost.org/) for compilation; only the Math component is
needed.
- [boost](http://www.boost.org/); only the Math component is needed (and only
during compilation).
- [Eigen](http://eigen.tuxfamily.org/)
- A C++ compiler supporting the C++11 standard, such as
[clang](http://clang.llvm.org/) (3.3+) or [g++](https://gcc.gnu.org/) (4.9+)
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
#pragma once
#include <memory>
namespace eris {
class Matrix; // Forward declaration
namespace matrix {
/** This class is the companion of the Matrix class and serves as a base class for any
* matrix-implementing class. Classes implementing this base class may safely assume that arguments
* passed to methods (which are formally MatrixImpl objects) are actually instances of the derived
* class, and thus may safely use `static_cast<Derived&>` to cast the passed MatrixImpl object
* into the Derived object without requiring the runtime overhead of using `dynamic_cast`.
*
* Classes using this base class for matrix operations must take care to ensure that different
* Matrix objects are never combined (so that the above `static_cast` is safe). In other words,
* using two Matrix objects with two different implementations is not generally allowed (unless
* explicitly supported by the two implementing classes).
*
* The methods of this class should never be evoked externally except by the Matrix class. As such,
* all methods are declared protected, with Matrix itself being declared a friend.
*/
class MatrixImpl {
public:
/// Ref is an alias for std::shared_ptr<MatrixImpl> for convenience.
using Ref = std::shared_ptr<MatrixImpl>;
/// Default virtual destructor
virtual ~MatrixImpl() = default;
protected:
friend class eris::Matrix;
/** Returns false. Subclasses other than NullImpl should not override this: this is used to
* detect when an implementation is actually the NullImpl.
*/
virtual bool null() const { return false; }
/** Clone creates a duplicate of the matrix, with the same size and coefficients. The
* default implementation calls create() then invokes the = operator to set the created
* matrix equal to the current one; subclasses should override if this can be done more
* efficiently.
*/
virtual Ref clone() const;
/** Returns the number of rows of this matrix. */
virtual unsigned int rows() const = 0;
/** Returns the number of columns of this matrix. */
virtual unsigned int cols() const = 0;
/** Read-only access to coefficients of this matrix. */
virtual const double& get(unsigned int r, unsigned int c) const = 0;
/** Mutating access to coefficients of this matrix. */
virtual void set(unsigned int r, unsigned int c, double d) = 0;
/** Creates a new matrix of the given size using the same implementation as the current
* object. The initial values of the matrix do not need to be initialized to any particular
* value.
*/
virtual Ref create(unsigned int rows, unsigned int cols) const = 0;
/** Creates a new matrix of the given size using the same implementation as the current
* object. The initial values of the matrix must all be set to the given value.
*/
virtual Ref create(unsigned int rows, unsigned int cols, double initial) const = 0;
/** Resizes the matrix to the given size. This will only be called on a matrix that is
* actually a matrix, not a matrix block view.
*
* Implementing classes are not required to retain consistency of blocks derived from this
* matrix.
*/
virtual void resize(unsigned int rows, unsigned int cols) = 0;
/** Creates a new square identity matrix of the given size using the same implementation as
* the current object.
*/
virtual Ref identity(unsigned int size) const = 0;
/** Returns a block view of the matrix. This is not a copy of the matrix, but an actual
* reference to a block of the matrix that can be modified to adjust the original matrix and
* vice versa.
*
* \param rowOffset the row index of this matrix corresponding to row 0 of the returned
* matrix view.
* \param colOffset the column index of this matrix corresponding to row 0 of the returned
* matrix view.
* \param nRows the number of rows the matrix view should have.
* \param nCols the number of columns the matrix view should have.
*
* Note that this method, when called from Matrix, has already checked that the parameters
* conform with the size of the matrix.
*/
virtual Ref block(unsigned int rowOffset, unsigned int colOffset, unsigned int nRows, unsigned int nCols) const = 0;
/// Assigns the values of matrix `b` to this matrix.
virtual void operator=(const MatrixImpl &b) = 0;
/// Adds a matrix `b` to this matrix.
virtual void operator+=(const MatrixImpl &b) = 0;
/// Subtracts a matrix `b` from this matrix.
virtual void operator-=(const MatrixImpl &b) = 0;
/** Adds two matrices together. The default implementation clones and then invokes the
* mutator version of this operator on the cloned object, which is then returned.
* Implementing classes may wish to override this when a more efficient implementation is
* available.
*/
virtual Ref operator+(const MatrixImpl &b) const;
/** Subtracts `b` from this matrix, returning the result in a new matrix. The default
* implementation clones and then invokes the mutator version of this operator on the cloned
* object, which is then returned. Implementing classes may wish to override this when a
* more efficient implementation is available.
*/
virtual Ref operator-(const MatrixImpl &b) const;
/// Multiplies this matrix by the matrix `b`, returning the result in a new matrix.
virtual Ref operator*(const MatrixImpl &b) const = 0;
/// Scales a matrix by the given scalar value.
virtual void operator*=(double d) = 0;
/** Scales a matrix by the given scalar value, returning the result in a new matrix. The
* default implementation clones the object then invokes the mutator version of this
* operator.
*/
virtual Ref operator*(double d) const;
/// Returns the transpose of this matrix.
virtual Ref transpose() const = 0;
/** Calculates the rank of the matrix. It is up to the implementing class to determine the
* numerical tolerance for rank calculations.
*/
virtual unsigned int rank() const = 0;
/** Returns the vector \f$x\f$ that solves \f$Ax = b\f$, where the method is invoked on
* matrix `A`. This is, notionally, \f$A^{-1}b\f$, but most matrix libraries offer more
* efficient solution methods than calculating an inverse.
*/
virtual Ref solve(const MatrixImpl &b) const = 0;
/** Returns the vector \f$x\f$ that solves the least-squares problem, that is, it is the
* \f$x\f$ that minimizes \f$||Ax - b||\f$, where the calling matrix is `A`. (In the normal
* least squares terminology, `A` is \f$X\f$, `b` is \f$y\f$, and `x` is \f$\beta\f$).
*
* The default implementation simply returns `(A.transpose() * A).solve(A.transpose() * b)`
* (except that A.tranpose() is called only once), but implementing classes should
* override with some more efficient version if available (for example, the Eigen library
* has an excellent such solver in its JacobiSVD class).
*/
virtual Ref solveLeastSquares(const MatrixImpl &b) const;
/** Returns the squared norm of the matrix. If a vector, this is the squared L2-norm; for a
* matrix, the squared Frobenius norm.
*/
virtual double squaredNorm() const = 0;
/** Returns true if the matrix is invertible, false if not. */
virtual bool invertible() const = 0;
/** Returns the inverse of the matrix. Note that calling `solve()` is preferable when the
* inverse is only a part of a calculation and not the intended value. Calling this on a
* non-invertible matrix could raise an exception, but might also return a matrix with
* undefined coefficients, so you should generally check invertible() first.
*/
virtual Ref inverse() const = 0;
/** Returns the matrix `L` from the Cholesky decomposition of the matrix, where `L` is
* lower-triangular and \f$LL^\top\f$ equals the called-upon matrix.
*/
virtual Ref choleskyL() const = 0;
/** Converts the matrix to a string representation using the given formatting parameters.
*
* \param precision the precision of values (as in `cout << std::setprecision(x)`).
* \param coeffSeparator the separator between values on the same row.
* \param rowSeparator the separator between two rows.
* \param rowPrefix the prefix to print at the beginning of every row.
*
* The default values are defined in the Matrix class.
*/
virtual std::string str(int precision, const std::string &coeffSeparator, const std::string &rowSeparator, const std::string &rowPrefix) const;
};
}}
#include <eris/matrix/MatrixImpl.hpp>
namespace eris { namespace matrix {
#define NULLIMPL_THROW { throw std::logic_error("Error: Attempt to use null (default-constructed) matrix via " + std::string(__func__)); }
/** Null matrix implementation. The implementation simply throws exceptions if attempted to be used
* in any way. The only thing that doesn't throw an exception is calling rows() or cols(): both
* return 0. This class is simply intended so that methods can contain "null" default arguments.
*/
class NullImpl : public MatrixImpl {
protected:
/// Returns 0
virtual unsigned int rows() const override { return 0; }
/// Returns 0
virtual unsigned int cols() const override { return 0; }
/// Returns true
virtual bool null() const override { return true; }
/// Returns a new NullImpl object
virtual Ref clone() const override { return Ref(static_cast<MatrixImpl*>(new NullImpl)); }
// Hide everything here from doxygen; these are really methods that shouldn't ever be
// called.
/// \cond
virtual const double& get(unsigned, unsigned) const override NULLIMPL_THROW
virtual void set(unsigned, unsigned, double) override NULLIMPL_THROW
virtual Ref create(unsigned, unsigned) const override NULLIMPL_THROW
virtual Ref create(unsigned, unsigned, double) const override NULLIMPL_THROW
virtual void resize(unsigned, unsigned) override NULLIMPL_THROW
virtual Ref identity(unsigned) const override NULLIMPL_THROW
virtual Ref block(unsigned, unsigned, unsigned, unsigned) const override NULLIMPL_THROW
virtual void operator=(const MatrixImpl&) override NULLIMPL_THROW
virtual void operator+=(const MatrixImpl&) override NULLIMPL_THROW
virtual void operator-=(const MatrixImpl&) override NULLIMPL_THROW
virtual Ref operator*(const MatrixImpl&) const override NULLIMPL_THROW
virtual void operator*=(double) override NULLIMPL_THROW
virtual Ref transpose() const override NULLIMPL_THROW
virtual unsigned int rank() const override NULLIMPL_THROW
virtual Ref solve(const MatrixImpl&) const override NULLIMPL_THROW
virtual double squaredNorm() const override NULLIMPL_THROW
virtual bool invertible() const override NULLIMPL_THROW
virtual Ref inverse() const override NULLIMPL_THROW
virtual Ref choleskyL() const override NULLIMPL_THROW
/// \endcond
};
}}
#undef NULLIMPL_THROW
This diff is collapsed.
#include <eris/matrix/MatrixImpl.hpp>
#include <sstream>
#include <iomanip>
#include <vector>
namespace eris { namespace matrix {
MatrixImpl::Ref MatrixImpl::clone() const {
auto c = create(rows(), cols());
*c = *this;
return c;
}
MatrixImpl::Ref MatrixImpl::operator+(const MatrixImpl &b) const {
auto c = clone();
*c += b;
return c;
}
MatrixImpl::Ref MatrixImpl::operator-(const MatrixImpl &b) const {
auto c = clone();
*c -= b;
return c;
}
MatrixImpl::Ref MatrixImpl::operator*(double d) const {
auto c = clone();
*c *= d;
return c;
}
MatrixImpl::Ref MatrixImpl::solveLeastSquares(const MatrixImpl &b) const {
auto t = transpose();
return (*t * *this)->solve(*(*t * b));
}
std::string MatrixImpl::str(int precision, const std::string &coeffSeparator, const std::string &rowSeparator, const std::string &rowPrefix) const {
std::ostringstream out;
// We need two passes: the first finds the maximum width of each column
std::vector<unsigned int> widths(cols(), 1);
for (unsigned int c = 0; c < cols(); c++) {
unsigned int max = 1;
for (unsigned int r = 0; r < rows(); r++) {
std::ostringstream test;
test.precision(precision);
test << get(r, c);
unsigned int len = test.str().length();
if (len > max) max = len;
}
widths[c] = max;
}
for (unsigned int r = 0; r < rows(); r++) {
if (r > 0) out << rowSeparator;
out << rowPrefix;
for (unsigned int c = 0; c < cols(); c++) {
if (c > 0) out << coeffSeparator;
out << std::setw(widths[c]) << std::setprecision(precision) << get(r, c);
}
}
return out.str();
}
}}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment