gov.sandia.cognition.math.matrix.custom.DenseMatrix Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cognitive-foundry Show documentation
Show all versions of cognitive-foundry Show documentation
A single jar with all the Cognitive Foundry components.
/*
* File: DenseMatrix.java
* Authors: Jeremy D. Wendt
* Company: Sandia National Laboratories
* Project: Cognitive Foundry
*
* Copyright 2015, Sandia Corporation. Under the terms of Contract
* DE-AC04-94AL85000, there is a non-exclusive license for use of this work by
* or on behalf of the U.S. Government. Export of this program may require a
* license from the United States Government. See CopyrightHistory.txt for
* complete details.
*/
package gov.sandia.cognition.math.matrix.custom;
import com.github.fommil.netlib.BLAS;
import gov.sandia.cognition.annotation.PublicationReference;
import gov.sandia.cognition.annotation.PublicationReferences;
import gov.sandia.cognition.annotation.PublicationType;
import gov.sandia.cognition.math.ComplexNumber;
import gov.sandia.cognition.math.matrix.Matrix;
import gov.sandia.cognition.math.matrix.MatrixFactory;
import gov.sandia.cognition.math.matrix.Vector;
import gov.sandia.cognition.util.ArgumentChecker;
import gov.sandia.cognition.util.ObjectUtil;
import java.util.ArrayList;
import java.util.List;
import org.netlib.util.intW;
/**
* A dense matrix implementation. Wherever possible, computation is pushed into
* BLAS or LAPACK expecting that those will be considerably faster than code
* written by me.
*
* @author Jeremy D. Wendt
* @since 3.4.3
*/
public class DenseMatrix
extends BaseMatrix
{
/**
* Optimal block size returned from QR decomposition. This is assumed to be
* hardware-specific, so static across different instances of dense matrix.
* By saving a static member, this means that only the first QR
* decomposition of any instance uses the less-than-optimal block size.
*/
private static int QR_OPTIMAL_BLOCK_SIZE = 1024;
/**
* This handler calls out to native BLAS if native BLAS is installed
* properly on the computer at program start. If not, this calls jBLAS.
*/
private static NativeBlasHandler blasHandler = null;
/**
* The rows of the matrix stored as dense vectors
*/
private DenseVector[] rows;
/**
* Creates a zero matrix of the specified dimensions
*
* @param numRows The number of rows in the matrix
* @param numCols The number of columns in the matrix
*/
public DenseMatrix(
final int numRows,
final int numCols)
{
super();
this.rows = new DenseVector[numRows];
for (int i = 0; i < numRows; i++)
{
this.rows[i] = new DenseVector(numCols);
}
initBlas();
}
/**
* Creates a matrix of default val in all cells of the specified dimensions
*
* @param numRows The number of rows in the matrix
* @param numCols The number of columns in the matrix
* @param defaultVal The value to set all elements to
*/
public DenseMatrix(
final int numRows,
final int numCols,
final double defaultVal)
{
super();
this.rows = new DenseVector[numRows];
for (int i = 0; i < numRows; i++)
{
this.rows[i] = new DenseVector(numCols, defaultVal);
}
initBlas();
}
/**
* Copy constructor that creates a deep copy of the input matrix. Optimized
* for copying DenseMatrices.
*
* @param m The matrix to copy
*/
public DenseMatrix(
final DenseMatrix m)
{
super();
final int numRows = m.getNumRows();
this.rows = new DenseVector[numRows];
for (int i = 0; i < numRows; i++)
{
this.rows[i] = new DenseVector(m.rows[i]);
}
initBlas();
}
/**
* Copy constructor that copies any input Matrix -- creating a deep copy of
* the matrix.
*
* @param m The matrix to copy.
*/
public DenseMatrix(
final Matrix m)
{
super();
final int numRows = m.getNumRows();
final int numColumns = m.getNumColumns();
this.rows = new DenseVector[numRows];
for (int i = 0; i < numRows; ++i)
{
this.rows[i] = new DenseVector(numColumns);
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] = m.get(i, j);
}
}
initBlas();
}
/**
* Package private optimized constructor that allows creation of a matrix
* from the given rows.
*
* This is an optimized constructor for within package only. Don't call this
* unless you are -super- careful.
*
* @param rows The array of rows for the matrix. Each row must be the same
* length.
*/
DenseMatrix(
final DenseVector[] rows)
{
super();
final int numRows = rows.length;
if (numRows > 0)
{
final int numColumns = rows[0].getDimensionality();
for (int i = 1; i < numRows; i++)
{
rows[i].assertDimensionalityEquals(numColumns);
}
}
this.rows = rows;
initBlas();
}
/**
* Constructor for creating a dense matrix from a 2-d double array. Copies
* the values from arr. Input is treated as row-major. While it would be
* possible to pass in a different length list for each row, that would be
* bad form. The length of the first row is assumed as the number of columns
* desired.
*
* @param values The 2-d double array that specifies the dimensions and values
* to be stored in the new matrix.
*/
public DenseMatrix(
final double[][] values)
{
int numRows = values.length;
int numCols = (numRows == 0) ? 0 : values[0].length;
this.rows = new DenseVector[numRows];
for (int i = 0; i < numRows; ++i)
{
final DenseVector v = new DenseVector(numCols);
for (int j = 0; j < numCols; ++j)
{
v.values[j] = values[i][j];
}
this.rows[i] = v;
}
initBlas();
}
/**
* Constructor for creating a dense matrix from a 2-d double List. Copies
* the values from arr. Input is treated as row-major. While it would be
* possible to pass in a different length list for each row, that would be
* bad form. The length of the first row is assumed as the number of columns
* desired.
*
* @param values The 2-d double array that specifies the dimensions and values
* to be stored in the matrix.
*/
public DenseMatrix(
final List> values)
{
int numRows = values.size();
int numCols = (numRows == 0) ? 0 : values.get(0).size();
this.rows = new DenseVector[numRows];
for (int i = 0; i < numRows; ++i)
{
final DenseVector v = new DenseVector(numCols);
final List list = values.get(i);
for (int j = 0; j < numCols; ++j)
{
v.values[j] = list.get(j);
}
this.rows[i] = v;
}
initBlas();
}
/**
* This should never be called by anything or anyone other than Java's
* serialization code.
*/
protected DenseMatrix()
{
super();
// NOTE: This doesn't initialize anything
}
/**
* Creates the NativeBlasHandler and tests to see if native BLAS is
* available.
*/
private static void initBlas()
{
if (blasHandler != null)
{
return;
}
blasHandler = new NativeBlasHandler();
if (NativeBlasHandler.nativeBlasAvailable())
{
blasHandler.setToNativeBlas();
}
else
{
blasHandler.setToJBlas();
}
}
/**
* Reads the input double array (dense, column-major as output by BLAS) and
* creates a new dense matrix with those values.
*
* @param d The BLAS representation of the matrix (must be length
* numRows*numCols)
* @param numRows The number of rows in d
* @param numCols The number of columns in d
* @return A new DenseMatrix with the specified size and elements stored in
* d (column major order)
*/
static DenseMatrix createFromBlas(
final double d[],
final int numRows,
final int numCols)
{
DenseMatrix result = new DenseMatrix(numRows, numCols);
result.fromBlas(d, numRows, numCols);
return result;
}
/**
* Loads the input values from the BLAS-ordered (dense, column-major) vector
* d into this.
*
* @param d The values to load into this
* @param numRows The number of rows in d
* @param numCols The number of columns in d
* @throws IllegalArgumentException if the input dimensions don't match
* this's dimensions (that's why this is a private method -- it should only
* be called by people who know what they're doing).
*/
private void fromBlas(
final double d[],
final int numRows,
final int numCols)
{
if ((getNumRows() != numRows) || (getNumColumns() != numCols))
{
throw new IllegalArgumentException("Unable to convert from BLAS of "
+ "different size: (" + getNumRows() + " != " + numRows
+ ") || (" + getNumColumns() + " != " + numCols + ")");
}
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numCols; ++j)
{
this.rows[i].values[j] = blasElement(i, j, d, numRows, numCols);
}
}
}
/**
* Converts this into a 1-d, dense, column-major vector (the layout required
* by BLAS and LAPACK).
*
* @return a 1-d, dense, column-major vector representation of this
*/
final double[] toBlas()
{
double[] result = new double[getNumRows() * getNumColumns()];
for (int i = 0; i < getNumRows(); ++i)
{
for (int j = 0; j < getNumColumns(); ++j)
{
result[i + (j * getNumRows())] = this.rows[i].get(j);
}
}
return result;
}
/**
* Tests if BLAS can be called and if the multiplication will be within the
* scale allowed by 1-d arrays that are indexed by integers (as Java
* requires). If any of those are false, this is multiplied in the O(n^3)
* method.
*
* NOTE: This is a problem whether you have native BLAS or jBLAS on your
* system. The problem arises because I must first create the 1-d array
* version of the matrices in Java itself.
*
* @param numRows1 The number of rows in the left matrix
* @param numCols1 The number of columns in the left matrix
* @param numRows2 The number of rows in the right matrix
* @param numCols2 The number of columns in the right matrix
* @return True if BLAS can be used, else false.
*/
private static boolean canUseBlasForMult(
final int numRows1,
final int numCols1,
final int numRows2,
final int numCols2)
{
long mat1Size = ((long) numRows1) * ((long) numCols1);
long mat2Size = ((long) numRows2) * ((long) numCols2);
long matOutSize = ((long) numRows1) * ((long) numCols2);
return (NativeBlasHandler.blasAvailable()) && (mat1Size
== (int) mat1Size) && (mat2Size == (int) mat2Size) && (matOutSize
== (int) matOutSize);
}
/**
* The O(n^3) multiplication algorithm that is here for if BLAS isn't
* available, or if the dimensions wouldn't allow 1-d array indexing.
*
* @param m The right matrix to multiply by this
* @return The result of multiplying this times m
*/
private Matrix slowMult(
final DenseMatrix m)
{
DenseMatrix result = new DenseMatrix(getNumRows(), m.getNumColumns());
for (int i = 0; i < getNumRows(); i++)
{
for (int j = 0; j < m.getNumColumns(); j++)
{
double val = 0;
for (int k = 0; k < getNumColumns(); k++)
{
val += get(i, k) * m.get(k, j);
}
result.setElement(i, j, val);
}
}
return result;
}
/**
* Returns a deep copy of this.
*
* @return a deep copy of this
*/
@Override
final public Matrix clone()
{
final DenseMatrix result = (DenseMatrix) super.clone();
result.rows = ObjectUtil.cloneSmartArrayAndElements(this.rows);
return result;
}
@Override
public void scaledPlusEquals(
final SparseMatrix other,
final double scaleFactor)
{
this.assertSameDimensions(other);
if (!other.isCompressed())
{
other.compress();
}
double[] ovals = other.getValues();
int[] ocolIdxs = other.getColumnIndices();
int[] ofirstRows = other.getFirstInRows();
int rownum = 0;
for (int i = 0; i < ovals.length; ++i)
{
while (ofirstRows[rownum + 1] <= i)
{
++rownum;
}
this.rows[rownum].values[ocolIdxs[i]] += ovals[i] * scaleFactor;
}
}
@Override
public void scaledPlusEquals(
final DenseMatrix other,
final double scaleFactor)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] += other.rows[i].values[j] * scaleFactor;
}
}
}
@Override
public void scaledPlusEquals(
final DiagonalMatrix other,
final double scaleFactor)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
for (int i = 0; i < numRows; ++i)
{
this.rows[i].values[i] += other.get(i, i) * scaleFactor;
}
}
@Override
public final void plusEquals(
final SparseMatrix other)
{
this.assertSameDimensions(other);
if (!other.isCompressed())
{
other.compress();
}
double[] ovals = other.getValues();
int[] ocolIdxs = other.getColumnIndices();
int[] ofirstRows = other.getFirstInRows();
int rownum = 0;
for (int i = 0; i < ovals.length; ++i)
{
while (ofirstRows[rownum + 1] <= i)
{
++rownum;
}
this.rows[rownum].values[ocolIdxs[i]] += ovals[i];
}
}
@Override
public final void plusEquals(
final DenseMatrix other)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] += other.rows[i].values[j];
}
}
}
@Override
public final void plusEquals(
final DiagonalMatrix other)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
for (int i = 0; i < numRows; ++i)
{
this.rows[i].values[i] += other.get(i, i);
}
}
@Override
public final void minusEquals(
final SparseMatrix other)
{
this.assertSameDimensions(other);
if (!other.isCompressed())
{
other.compress();
}
double[] ovals = other.getValues();
int[] ocolIdxs = other.getColumnIndices();
int[] ofirstRows = other.getFirstInRows();
int rownum = 0;
for (int i = 0; i < ovals.length; ++i)
{
while (ofirstRows[rownum + 1] <= i)
{
++rownum;
}
this.rows[rownum].values[ocolIdxs[i]] -= ovals[i];
}
}
@Override
public final void minusEquals(
final DenseMatrix other)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] -= other.rows[i].values[j];
}
}
}
@Override
public final void minusEquals(
final DiagonalMatrix other)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
for (int i = 0; i < numRows; ++i)
{
this.rows[i].values[i] -= other.get(i, i);
}
}
/**
* {@inheritDoc}
*
* NOTE: Calling this method is a bad idea because you end up storing a
* sparse matrix in a dense representation.
*/
@Override
public final void dotTimesEquals(
final SparseMatrix other)
{
this.assertSameDimensions(other);
if (!other.isCompressed())
{
other.compress();
}
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
int idx = 0;
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
// If j matches the current column and we're still within the
// correct row in other
if ((idx < other.getValues().length) && (j
== other.getColumnIndices()[idx]) && (idx
< other.getFirstInRows()[i + 1]))
{
// Multiply the values and advance to the next value in other
this.rows[i].values[j] *= other.getValues()[idx];
++idx;
}
else
{
// Otherwise, there is no value in the other matrix here
this.rows[i].values[j] = 0;
}
}
}
}
@Override
public final void dotTimesEquals(
final DenseMatrix other)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] *= other.rows[i].values[j];
}
}
}
/**
* {@inheritDoc}
*
* NOTE: Calling this method is a really bad idea because you end up storing
* a diagonal matrix in a dense representation.
*/
@Override
public final void dotTimesEquals(
final DiagonalMatrix other)
{
this.assertSameDimensions(other);
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
if (i == j)
{
this.rows[i].values[i] *= other.get(i, i);
}
else
{
this.rows[i].values[j] = 0;
}
}
}
}
@Override
public final Matrix times(
final SparseMatrix other)
{
this.assertMultiplicationDimensions(other);
if (!other.isCompressed())
{
other.compress();
}
final int numRows = this.getNumRows();
final DenseVector[] resultRows = new DenseVector[numRows];
for (int i = 0; i < numRows; ++i)
{
resultRows[i] = (DenseVector) other.preTimes(this.rows[i]);
}
return new DenseMatrix(resultRows);
}
@Override
public final Matrix times(
final DenseMatrix other)
{
this.assertMultiplicationDimensions(other);
// TODO: Make sure this BLAS is truly faster than slow version
if (canUseBlasForMult(getNumRows(), getNumColumns(), other.getNumRows(),
other.getNumColumns()))
{
double[] output = new double[getNumRows() * other.getNumColumns()];
BLAS.getInstance().dgemm("N", "N", getNumRows(),
other.getNumColumns(), getNumColumns(), 1.0, this.toBlas(),
getNumRows(), other.toBlas(), other.getNumRows(), 0.0, output,
getNumRows());
return createFromBlas(output, getNumRows(), other.getNumColumns());
}
else
{
return slowMult(other);
}
}
@Override
public final Matrix times(
final DiagonalMatrix other)
{
this.assertMultiplicationDimensions(other);
final int numRows = this.getNumRows();
final DenseVector[] resultRows = new DenseVector[numRows];
for (int i = 0; i < numRows; ++i)
{
resultRows[i] = (DenseVector) other.preTimes(this.rows[i]);
}
return new DenseMatrix(resultRows);
}
/**
* Helper method that handles all vector-on-the-right multiplies because we
* depend on the vector dotProduct optimization here.
*
* @param vector The vector to multiply
* @return The vector resulting from multiplying this * vector
*/
private Vector timesInternal(
final Vector vector)
{
vector.assertDimensionalityEquals(this.getNumColumns());
final int numRows = this.getNumRows();
DenseVector result = new DenseVector(numRows);
for (int i = 0; i < numRows; ++i)
{
result.setElement(i, vector.dotProduct(rows[i]));
}
return result;
}
@Override
public final Vector times(
final SparseVector vector)
{
// It's the same for sparse and dense vectors (the difference is handled
// in the vector's dotProduct code
return timesInternal(vector);
}
@Override
public final Vector times(
final DenseVector vector)
{
// It's the same for sparse and dense vectors (the difference is handled
// in the vector's dotProduct code
return timesInternal(vector);
}
@Override
public final void scaleEquals(
final double scaleFactor)
{
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] *= scaleFactor;
}
}
}
@Override
final public int getNumRows()
{
return this.rows.length;
}
@Override
final public int getNumColumns()
{
return (rows == null) ? 0 : (rows.length == 0) ? 0
: rows[0].getDimensionality();
}
@Override
public double get(
final int rowIndex,
final int columnIndex)
{
return this.rows[rowIndex].get(columnIndex);
}
@Override
final public double getElement(
final int rowIndex,
final int columnIndex)
{
return this.rows[rowIndex].get(columnIndex);
}
@Override
public void set(int rowIndex,
int columnIndex,
double value)
{
setElement(rowIndex, columnIndex, value);
}
@Override
final public void setElement(int rowIndex,
int columnIndex,
double value)
{
rows[rowIndex].setElement(columnIndex, value);
}
/**
* {@inheritDoc}
*
* NOTE: This is inclusive on both end points.
* @param minRow {@inheritDoc}
* @param maxRow {@inheritDoc}
* @param minColumn {@inheritDoc}
* @param maxColumn {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
final public Matrix getSubMatrix(
final int minRow,
final int maxRow,
final int minColumn,
final int maxColumn)
{
checkSubmatrixRange(minRow, maxRow, minColumn, maxColumn);
DenseMatrix result = new DenseMatrix(
maxRow - minRow + 1,
maxColumn - minColumn + 1);
for (int i = minRow; i <= maxRow; ++i)
{
for (int j = minColumn; j <= maxColumn; ++j)
{
result.set(i - minRow, j - minColumn, this.rows[i].values[j]);
}
}
return result;
}
@Override
final public boolean isSymmetric(
final double effectiveZero)
{
ArgumentChecker.assertIsNonNegative("effectiveZero", effectiveZero);
// If it's not square, it's not symmetric
if (getNumRows() != getNumColumns())
{
return false;
}
// Now check that all upper triangular values equal their corresponding
// lower triangular vals (within effectiveZero range)
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = i + 1; j < numColumns; ++j)
{
if (Math.abs(this.rows[i].values[j] - rows[j].values[i])
> effectiveZero)
{
return false;
}
}
}
// If you make it here, they're equal.
return true;
}
@Override
final public Matrix transpose()
{
// It's the transpose of me
int m = getNumColumns();
int n = getNumRows();
final DenseVector[] resultRows = new DenseVector[m];
for (int i = 0; i < m; ++i)
{
DenseVector row = new DenseVector(n);
for (int j = 0; j < n; ++j)
{
row.setElement(j, get(j, i));
}
resultRows[i] = row;
}
return new DenseMatrix(resultRows);
}
@Override
final public Matrix inverse()
{
if (!isSquare())
{
throw new IllegalStateException("Unable to compute inverse of non-"
+ "square matrix.");
}
Matrix I = new DiagonalMatrix(getNumRows());
I.identity();
return solve(I);
}
@Override
final public Matrix pseudoInverse(
final double effectiveZero)
{
ArgumentChecker.assertIsNonNegative("effectiveZero", effectiveZero);
SVD svd = svdDecompose();
int min = Math.min(getNumRows(), getNumColumns());
for (int i = 0; i < min; ++i)
{
if (Math.abs(svd.Sigma.get(i, i)) <= effectiveZero)
{
svd.Sigma.setElement(i, i, 0);
}
else
{
svd.Sigma.setElement(i, i, 1.0 / svd.Sigma.get(i, i));
}
}
return svd.V.times(svd.Sigma.transpose()).times(svd.U.transpose());
}
@PublicationReferences(references =
{
@PublicationReference(author = "Wikipedia",
title = "Triagular Matrix / Special Properties",
type = PublicationType.WebPage,
year = 2013,
url
= "http://en.wikipedia.org/wiki/Triangular_matrix#Special_properties"),
@PublicationReference(
author = "Wikipedia",
title = "Determinant / Relation to Eigenvalues and Trace",
type = PublicationType.WebPage,
year = 2013,
url
= "http://en.wikipedia.org/wiki/Determinant#Relation_to_eigenvalues_and_trace"),
@PublicationReference(author = "Wikipedia",
title = "Logarithm",
type = PublicationType.WebPage,
year = 2013,
url = "http://en.wikipedia.org/wiki/Logarithm")
})
@Override
final public ComplexNumber logDeterminant()
{
if (!isSquare())
{
throw new IllegalStateException("Matrix must be square");
}
LU lu = luDecompose();
// The "L" matrix has a determinant of one, so the real action is
// in the "U" matrix, which is upper (right) triangular and any row
// swaps (each one flips the sign of the determinant).
// NOTE: The determinant of a triangular matrix is the product of the
// diagonal entries (see
// http://en.wikipedia.org/wiki/Triangular_matrix#Special_properties)
// NOTE: Product of eigenvalues = determinant (see
// http://en.wikipedia.org/wiki/Determinant#Relation_to_eigenvalues_and_trace)
// NOTE: The logarithm of the product of two numbers is the sum of the
// logarightm of each of the two numbers (see
// http://en.wikipedia.org/wiki/Logarithm)
//
// The diagonal elements of U will be REAL, but they may be negative.
// The logarithm of a negative number is the logarithm of the absolute
// value of the number, with an imaginary part of PI. The exponential
// is all that matters, so the log-determinant is equivalent, MODULO
// PI (3.14...), so we just toggle this sign bit.
int sign = 1;
int M = lu.U.getNumRows();
double logsum = 0.0;
for (int i = 0; i < M; i++)
{
double eigenvalue = lu.U.get(i, i);
if (eigenvalue < 0.0)
{
sign = -sign;
logsum += Math.log(-eigenvalue);
}
else
{
logsum += Math.log(eigenvalue);
}
if (lu.P.get(i) != i)
{
sign = -sign;
}
}
return new ComplexNumber(logsum, (sign < 0) ? Math.PI : 0.0);
}
@Override
final public int rank(
final double effectiveZero)
{
ArgumentChecker.assertIsNonNegative("effectiveZero", effectiveZero);
QR qr = qrDecompose();
int min = Math.min(getNumRows(), getNumColumns());
int result = 0;
for (int i = 0; i < min; ++i)
{
if (Math.abs(qr.R.get(i, i)) > effectiveZero)
{
++result;
}
}
return result;
}
@Override
public double normFrobeniusSquared()
{
double result = 0;
for (int i = 0; i < rows.length; ++i)
{
for (int j = 0; j < this.rows[i].values.length; ++j)
{
result += this.rows[i].values[j] * this.rows[i].values[j];
}
}
return result;
}
@Override
final public double normFrobenius()
{
return Math.sqrt(normFrobeniusSquared());
}
@Override
public boolean isSparse()
{
return false;
}
@Override
public int getEntryCount()
{
return this.getNumRows() * this.getNumColumns();
}
/**
* Simple container class for Singular Value Decomposition (SVD) results.
* Stores three matrices U, Sigma, and V as public members. NOTE: This
* includes V not V^T.
*/
final public static class SVD
{
/**
* The left basis matrix. Due to an issue within LAPACK, this may be a
* left or right (proper) basis.
*/
public Matrix U;
/**
* * The singular value diagonal matrix.
*/
public Matrix Sigma;
/**
* The right basis matrix. Due to an issue within LAPACK, this may be a
* left or right (proper) basis.
*/
public Matrix V;
/**
* Creates the U, Sigma, and V matrices to their correct sizes, but
* leaves them as zero matrices.
*
* @param A The matrix that will be decomposed and so it specifies the
* sizes for U, Sigma, and V.
*/
private SVD(
final DenseMatrix A)
{
U = new DenseMatrix(A.getNumRows(), A.getNumRows());
Sigma = new SparseMatrix(A.getNumRows(), A.getNumColumns());
V = new DenseMatrix(A.getNumColumns(), A.getNumColumns());
}
}
/**
* Leverages LAPACK to compute the Singular Value Decomposition (SVD) of
* this. NOTE: Due to LAPACK issues, U and V may be left or right (proper)
* basis matrices. It they don't count and report the number of row-swaps
* used when computing the SVD so there's no way to know from the outside
* without computing the determinant of these matrices (not a cheap
* operation). Also, note that V is returned, not V^t.
*
* @return The three matrices U, Sigma, and V from the SVD of this
* @throws IllegalStateException if LAPACK fails to decompose this for an
* unknown reason.
*/
final public SVD svdDecompose()
{
SVD result = new SVD(this);
// Initialize the results;
result.U.identity();
result.Sigma.zero();
result.V.identity();
// Prepare for calling LAPACK
String jobz = "A";
int m = getNumRows();
int n = getNumColumns();
if ((m == 0) || (n == 0))
{
return result;
}
double[] A = this.toBlas();
int lda = m;
int mindim = Math.min(m, n);
double[] s = new double[mindim];
int ldu = m;
int ucol = m;
double[] u = new double[ldu * ucol];
int ldvt = n;
double[] vt = new double[ldvt * n];
int lwork = 3 * mindim * mindim + Math.max(Math.max(m, n), 4 * mindim
* mindim + 4 * mindim) * 2;
double[] work = new double[lwork];
int[] iwork = new int[8 * mindim];
intW info = new intW(1);
// Call LAPACK
com.github.fommil.netlib.LAPACK.getInstance().dgesdd(jobz, m, n, A, 0, lda, s, 0, u, 0, ldu,
vt, 0, ldvt, work, 0, lwork, iwork, 0, info);
if (info.val < 0)
{
throw new IllegalStateException(
"LAPACK failed on SVD-decomposition "
+ "reporting an error at the " + (-1 * info.val) + "th input");
}
else if (info.val > 0)
{
throw new IllegalStateException("LAPACK failed to converge for "
+ "SVD-decomposition.");
}
// Pull out the values
((DenseMatrix) result.U).fromBlas(u, ldu, ucol);
((DenseMatrix) result.V).fromBlas(vt, ldvt, n);
result.V = result.V.transpose();
for (int i = 0; i < mindim; ++i)
{
result.Sigma.setElement(i, i, s[i]);
}
return result;
}
/**
* Simple container class for LU decompositions. LU decomposition results in
* a lower triangular matrix (L) and an upper triangular matrix (U). This
* decomposition can be used for matrix inversion, determinant computation,
* etc.
*/
public static class LU
{
/**
* The 0-based list of row swaps used in the factorization. NOTE: If row
* 0 is swapped with row 1, P will contain [1, 1, 2, ...] (because if it
* said [1, 0, 2, ...] that would mean that it swapped row 0 with 1 and
* then row 1 with 0 (swapping back the original rows)).
*
* If this seems too confusing, just use the getPivotMatrix helper
* method.
*/
public List P;
/**
* The lower triangular matrix resulting from the factorization.
*/
public Matrix L;
/**
* The upper triangular matrix resulting from the factorization.
*/
public Matrix U;
/**
* Creates the P, L, and U data structures based on the size of the A
* matrix that will be factored and stored into this.
*
* @param A The matrix to be factored -- specifies the dimensions of the
* parts of this.
*/
private LU(
final DenseMatrix A)
{
int m = A.getNumRows();
int n = A.getNumColumns();
if (m <= n)
{
P = new ArrayList<>(m);
L = new DenseMatrix(m, m);
U = new DenseMatrix(m, n);
}
else
{
P = new ArrayList<>(n);
L = new DenseMatrix(m, n);
U = new DenseMatrix(n, n);
}
}
/**
* Helper method converts P into a pivot matrix that can pre-multiply
* L*U.
*
* @return an identity matrix with the appropriate row swaps to
* re-generate the original matrix A when P * L * U is calculated.
*/
public Matrix getPivotMatrix()
{
SparseMatrix pivot
= new SparseMatrix(L.getNumRows(), L.getNumRows());
pivot.identity();
for (int i = 0; i < P.size(); ++i)
{
if (P.get(i) != i)
{
Vector tmp = pivot.getRow(i);
pivot.setRow(i, pivot.getRow(P.get(i)));
pivot.setRow(P.get(i), tmp);
}
}
return pivot;
}
}
/**
* Leverages LAPACK to create the LU decomposition of this. Note: In this
* case, the row swaps performed when computing the LU factorization are
* preserved in LU.P so that any -1 scalings of the determinant can be
* determined.
*
* @return the LU decomposition of this
*/
final public LU luDecompose()
{
LU result = new LU(this);
// Initialize the results
result.L.zero();
result.U.zero();
// Prepare for calling LAPACK
int m = getNumRows();
int n = getNumColumns();
if ((m == 0) || (n == 0))
{
return result;
}
int lda = m;
double[] A = this.toBlas();
int ipivdim = Math.min(m, n);
int[] ipiv = new int[ipivdim];
intW info = new intW(1);
// Call LAPACK
com.github.fommil.netlib.LAPACK.getInstance().dgetrf(m, n, A, 0, lda, ipiv, 0, info);
if (info.val < 0)
{
throw new IllegalStateException("LAPACK failed on LU-decomposition "
+ "reporting an error at the " + (-1 * info.val) + "th input");
}
//Copy out L, U, and P
for (int i = 0; i < m; ++i)
{
for (int j = i; j < n; ++j)
{
result.U.setElement(i, j, blasElement(i, j, A, m, n));
}
for (int j = 0; j < Math.min(i, n); ++j)
{
result.L.setElement(i, j, blasElement(i, j, A, m, n));
}
// Only as many row swaps and diagonals as there are rows and
// columns (whichever is smaller)
if (i < n)
{
result.L.setElement(i, i, 1);
// Fortran is 1-based, Java is 0-based
result.P.add(ipiv[i] - 1);
}
}
return result;
}
/**
* Container class that stores the Q and R matrices formed by the QR
* decomposition of this. Q is an orthonormal basis matrix and R is an upper
* triangular matrix. Due to the fact that LAPACK does not return the row
* swaps for QR factorization, Q may be a left or right (proper) basis.
*/
public static class QR
{
/**
* The orthonormal basis matrix
*/
public Matrix Q;
/**
* The upper-triangular matrix
*/
public Matrix R;
/**
* Initializes Q and R to the correct size (based on A), but leaves them
* as zeroes.
*
* @param A The matrix to be factored into the Q and R stored herein.
*/
private QR(
final DenseMatrix A)
{
Q = new DenseMatrix(A.getNumRows(), A.getNumRows());
R = new DenseMatrix(A.getNumRows(), A.getNumColumns());
}
}
/**
* Leverage LAPACK to compute the QR Decomposition of this. Since LAPACK
* doesn't return any record of row swaps used to factor this, Q may be a
* left or right (proper) basis matrix.
*
* @return the QR decomposition (Q is orthonormal basis, R is upper
* triangular)
*/
@PublicationReference(author = "NetLib",
title = "DGEQRF",
type = PublicationType.WebPage,
year = 2013,
url
= "http://icl.cs.utk.edu/projectsfiles/f2j/javadoc/org/netlib/lapack/Dgeqrf.html")
final public QR qrDecompose()
{
QR result = new QR(this);
// Initialize the results
result.Q.identity();
result.R.zero();
// Prepare for calling LAPACK
int m = getNumRows();
if (m == 0)
{
return result;
}
int n = getNumColumns();
if (n == 0)
{
return result;
}
double[] A = this.toBlas();
int lda = m;
double[] tau = new double[Math.min(m, n)];
int lwork = QR_OPTIMAL_BLOCK_SIZE * m;
double[] work = new double[lwork];
intW info = new intW(100);
// Call LAPACK
com.github.fommil.netlib.LAPACK.getInstance().dgeqrf(m, n, A, 0, lda, tau, 0, work, 0,
lwork, info);
if (info.val != 0)
{
throw new IllegalStateException("LAPACK failed on QR-decomposition "
+ "reporting an error at the " + (-1 * info.val) + "th input");
}
// After calling the method, the optimal block size is in work[0] (see
// LAPACK documentation). This is for if QR is ever called again
QR_OPTIMAL_BLOCK_SIZE = (int) Math.round(work[0]);
// Copy out R
for (int i = 0; i < m; ++i)
{
for (int j = i; j < n; ++j)
{
result.R.setElement(i, j, blasElement(i, j, A, m, n));
}
}
// Calculate Q
// See http://icl.cs.utk.edu/projectsfiles/f2j/javadoc/org/netlib/lapack/Dgeqrf.html
// specifically their crazy way of storing Q in tau and the lower
// portion of A
DiagonalMatrix I = new DiagonalMatrix(m);
I.identity();
DenseVector v = new DenseVector(m);
int imax = Math.min(m, n);
for (int i = 0; i < imax; ++i)
{
for (int j = 0; j < i; ++j)
{
v.values[j] = 0;
}
v.values[i] = 1;
for (int j = i + 1; j < m; ++j)
{
v.values[j] = blasElement(j, i, A, m, n);
}
// Mult to Q
result.Q = result.Q.times(I.minus(
v.outerProduct(v).scale(tau[i])));
}
return result;
}
/**
* Simple helper that computes the offset in a 1-d, dense, column-major
* (BLAS-order) matrix representation for the input i, j, m, and n matrix A.
*
* @param i The row index (0-based)
* @param j The column index (0-based)
* @param A The 1-d, dense, column-major (BLAS-order) matrix representation
* @param m The number of rows in A
* @param n The number of columns in A
* @return The value stored in A(i, j)
*/
private double blasElement(
final int i,
final int j,
final double[] A,
final int m,
final int n)
{
return A[i + m * j];
}
/**
* Solves Rx = QtransB requiring that R be upper triangular and square.
* Solves by back-subsititution from the bottom-most corner of the
* upper-triangular matrix.
*
* @param R The upper triangular right-side matrix
* @param QtransB The Q-transformed right-side vector
* @return The x from Rx = Qtrans * b.
* @throws IllegalArgumentException if R is not square or if R is not
* upper-triangular (has values below the diagonal).
* @throws UnsupportedOperationException if any diagonal element of R is
* zero (as it does not span the space).
*/
private static Vector upperTriangularSolve(
final Matrix R,
final Vector QtransB)
{
// This should be tested in the solve methods, this is here just to
// make sure no one else calls this w/o checking
if (!R.isSquare())
{
throw new IllegalArgumentException("Called with a non-square "
+ "matrix.");
}
// Test bottom triangle is zero
// And test that all diagonals are non-zero (else you can't solve)
final int numRows = R.getNumRows();
final int numColumns = R.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
// diagonals
if (R.get(i, i) == 0)
{
throw new UnsupportedOperationException("Can't invert matrix "
+ "because it does not span the columns");
}
// lower triangle
for (int j = 0; j < i; ++j)
{
if (R.get(i, j) != 0)
{
throw new IllegalArgumentException("upperTriangleSolve "
+ "passed a non-upper-triangle matrix");
}
}
}
DenseVector result = new DenseVector(numColumns);
// Start from the bottom of the triangle
for (int i = numColumns - 1; i >= 0; --i)
{
// Start w/ the value in B
double v = QtransB.get(i);
// Back substitute all solved parts in
for (int j = i + 1; j < numColumns; ++j)
{
v -= R.get(i, j) * result.values[j];
}
// Solve with the diagonal element
result.values[i] = (v / R.get(i, i));
}
return result;
}
@Override
final public Matrix solve(
final Matrix B)
{
checkSolveDimensions(B);
if (!isSquare())
{
throw new IllegalStateException("Solve only works on square "
+ "matrices (this is " + getNumRows() + " x " + getNumColumns());
}
QR qr = qrDecompose();
// I'll only use it as the transpose
qr.Q = qr.Q.transpose();
final int numColumns = B.getNumColumns();
DenseMatrix X = new DenseMatrix(getNumColumns(), numColumns);
for (int i = 0; i < numColumns; ++i)
{
X.setColumn(i,
upperTriangularSolve(qr.R, qr.Q.times(B.getColumn(i))));
}
return X;
}
@Override
final public Vector solve(
final Vector b)
{
checkSolveDimensions(b);
if (!isSquare())
{
throw new IllegalStateException("Solve only works on square "
+ "matrices (this is " + getNumRows() + " x " + getNumColumns());
}
QR qr = qrDecompose();
return upperTriangularSolve(qr.R, qr.Q.transpose().times(b));
}
@Override
final public void identity()
{
// NOTE: This is a bad idea as you're storing a diagonal matrix in a
// dense data structure
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
if (i == j)
{
this.rows[i].values[j] = 1;
}
else
{
this.rows[i].values[j] = 0;
}
}
}
}
@Override
final public Vector getColumn(
final int columnIndex)
{
if (columnIndex < 0 || columnIndex >= getNumColumns())
{
throw new ArrayIndexOutOfBoundsException("Input column index ("
+ columnIndex + ") is not within this " + getNumRows() + "x"
+ getNumColumns() + " matrix");
}
DenseVector result = new DenseVector(getNumRows());
final int numRows = this.getNumRows();
for (int i = 0; i < numRows; ++i)
{
result.values[i] = rows[i].get(columnIndex);
}
return result;
}
@Override
final public Vector getRow(
final int rowIndex)
{
if (rowIndex < 0 || rowIndex >= getNumRows())
{
throw new ArrayIndexOutOfBoundsException("Input row index ("
+ rowIndex + ") is not within this " + getNumRows() + "x"
+ getNumColumns() + " matrix");
}
return new DenseVector(rows[rowIndex]);
}
@Override
final public void convertFromVector(
final Vector v)
{
v.assertDimensionalityEquals(this.getNumRows() * this.getNumColumns());
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
this.rows[i].values[j] = v.get(i + j
* getNumRows());
}
}
}
@Override
final public Vector convertToVector()
{
DenseVector result = new DenseVector(getNumRows() * getNumColumns());
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
result.values[i + j * getNumRows()] = this.rows[i].values[j];
}
}
return result;
}
@Override
public final Vector preTimes(
final SparseVector vector)
{
vector.assertDimensionalityEquals(this.getNumRows());
DenseVector result = new DenseVector(getNumColumns());
vector.compress();
int[] locs = vector.getIndices();
double[] vals = vector.getValues();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numColumns; ++i)
{
double entry = 0;
for (int j = 0; j < locs.length; ++j)
{
entry += vals[j] * rows[locs[j]].get(i);
}
result.setElement(i, entry);
}
return result;
}
@Override
public final Vector preTimes(
final DenseVector vector)
{
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
vector.assertDimensionalityEquals(numRows);
DenseVector result = new DenseVector(numColumns);
for (int i = 0; i < numColumns; ++i)
{
double entry = 0;
for (int j = 0; j < numRows; ++j)
{
entry += vector.get(j) * rows[j].get(i);
}
result.setElement(i, entry);
}
return result;
}
/**
* Package-private helper that returns the dense vector that stores row i.
* This is intended for speeding up various computations by SparseMatrix and
* DiagonalMatrix.
*
* @param i The row index -- only checked by Java's array indexing.
* @return The DenseVector stored at i
*/
final DenseVector row(
final int i)
{
return rows[i];
}
/**
* Package-private helper that sets the input vector to the specified vector
* without any checks. This is to speed up matrix operations in DenseMatrix
* and SparseMatrix.
*
* @param i The row index -- only checked by Java's array indexing.
* @param v The DenseVector to store at i -- not checked at all
*/
final void setRow(
final int i,
final DenseVector v)
{
rows[i] = v;
}
/**
* Simple method that computes the number of non-zero elements stored in
* this.
*
* @return the number of elements in this that are non-zero.
*/
final public int getNonZeroCount()
{
int nnz = 0;
final int numRows = this.getNumRows();
final int numColumns = this.getNumColumns();
for (int i = 0; i < numRows; ++i)
{
for (int j = 0; j < numColumns; ++j)
{
nnz += (rows[i].get(j) == 0) ? 0 : 1;
}
}
return nnz;
}
/**
* Returns the percentage of this that is non-zero elements
*
* @return the percentage of this that is non-zero elements
*/
final public double getNonZeroPercent()
{
return ((double) getNonZeroCount()) / ((double) (getNumRows()
* getNumColumns()));
}
@Override
public MatrixFactory> getMatrixFactory()
{
return CustomDenseMatrixFactory.INSTANCE;
}
}