/*
* Copyright (c) 2009-2012, Peter Abeles. All Rights Reserved.
*
* This file is part of Efficient Java Matrix Library (EJML).
*
* EJML 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.
*
* EJML 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 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with EJML. If not, see <http://www.gnu.org/licenses/>.
*/
package org.ejml.alg.dense.decomposition.svd;
import org.ejml.UtilEjml;
import org.ejml.data.DenseMatrix64F;
import org.ejml.data.UtilTestMatrix;
import org.ejml.factory.SingularValueDecomposition;
import org.ejml.ops.CommonOps;
import org.ejml.ops.MatrixFeatures;
import org.ejml.ops.RandomMatrices;
import org.ejml.ops.SingularOps;
import org.ejml.simple.SimpleMatrix;
import java.util.Random;
import static org.junit.Assert.*;
/**
* @author Peter Abeles
*/
public abstract class StandardSvdChecks {
Random rand = new Random(73675);
public abstract SingularValueDecomposition<DenseMatrix64F> createSvd();
boolean omitVerySmallValues = false;
public void allTests() {
testDecompositionOfTrivial();
testWide();
testTall();
checkGetU_Transpose();
checkGetU_Storage();
checkGetV_Transpose();
checkGetV_Storage();
if( !omitVerySmallValues )
testVerySmallValue();
testZero();
testLargeToSmall();
testIdentity();
testLarger();
testLots();
}
public void testDecompositionOfTrivial()
{
DenseMatrix64F A = new DenseMatrix64F(3,3, true, 5, 2, 3, 1.5, -2, 8, -3, 4.7, -0.5);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
assertEquals(3, SingularOps.rank(alg, UtilEjml.EPS));
assertEquals(0, SingularOps.nullity(alg, UtilEjml.EPS));
double []w = alg.getSingularValues();
UtilTestMatrix.checkNumFound(1,1e-5,9.59186,w);
UtilTestMatrix.checkNumFound(1,1e-5,5.18005,w);
UtilTestMatrix.checkNumFound(1,1e-5,4.55558,w);
checkComponents(alg,A);
}
public void testWide() {
DenseMatrix64F A = RandomMatrices.createRandom(5,20,-1,1,rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
checkComponents(alg,A);
}
public void testTall() {
DenseMatrix64F A = RandomMatrices.createRandom(21,5,-1,1,rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
checkComponents(alg,A);
}
public void testZero() {
for( int i = 1; i <= 16; i += 5 ) {
for( int j = 1; j <= 16; j += 5 ) {
DenseMatrix64F A = new DenseMatrix64F(i,j);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
int min = Math.min(i,j);
assertEquals(min,checkOccurrence(0,alg.getSingularValues(),min),1e-5);
checkComponents(alg,A);
}
}
}
public void testIdentity() {
DenseMatrix64F A = CommonOps.identity(6,6);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
assertEquals(6,checkOccurrence(1,alg.getSingularValues(),6),1e-5);
checkComponents(alg,A);
}
public void testLarger() {
DenseMatrix64F A = RandomMatrices.createRandom(200,200,-1,1,rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
checkComponents(alg,A);
}
/**
* See if it can handle very small values and not blow up. This can some times
* cause a zero to appear unexpectedly and thus a divided by zero.
*/
public void testVerySmallValue() {
DenseMatrix64F A = RandomMatrices.createRandom(5,5,-1,1,rand);
CommonOps.scale(1e-200,A);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
checkComponents(alg,A);
}
public void testLots() {
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
for( int i = 1; i < 10; i++ ) {
for( int j = 1; j < 10; j++ ) {
DenseMatrix64F A = RandomMatrices.createRandom(i,j,-1,1,rand);
assertTrue(alg.decompose(A));
checkComponents(alg,A);
}
}
}
/**
* Makes sure transposed flag is correctly handled.
*/
public void checkGetU_Transpose() {
DenseMatrix64F A = RandomMatrices.createRandom(5, 7, -1, 1, rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
DenseMatrix64F U = alg.getU(null,false);
DenseMatrix64F Ut = alg.getU(null,true);
DenseMatrix64F found = new DenseMatrix64F(U.numCols,U.numRows);
CommonOps.transpose(U,found);
assertTrue( MatrixFeatures.isEquals(Ut,found));
}
/**
* Makes sure the optional storage parameter is handled correctly
*/
public void checkGetU_Storage() {
DenseMatrix64F A = RandomMatrices.createRandom(5,7,-1,1,rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
// test positive cases
DenseMatrix64F U = alg.getU(null,false);
DenseMatrix64F storage = new DenseMatrix64F(U.numRows,U.numCols);
alg.getU(storage,false);
assertTrue( MatrixFeatures.isEquals(U,storage));
U = alg.getU(null,true);
storage = new DenseMatrix64F(U.numRows,U.numCols);
alg.getU(storage,true);
assertTrue( MatrixFeatures.isEquals(U,storage));
// give it an incorrect sign
try {
alg.getU(new DenseMatrix64F(10,20),true);
fail("Exception should have been thrown");
} catch( RuntimeException e ){}
try {
alg.getU(new DenseMatrix64F(10,20),false);
fail("Exception should have been thrown");
} catch( RuntimeException e ){}
}
/**
* Makes sure transposed flag is correctly handled.
*/
public void checkGetV_Transpose() {
DenseMatrix64F A = RandomMatrices.createRandom(5,7,-1,1,rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
DenseMatrix64F V = alg.getV(null,false);
DenseMatrix64F Vt = alg.getV(null,true);
DenseMatrix64F found = new DenseMatrix64F(V.numCols,V.numRows);
CommonOps.transpose(V,found);
assertTrue( MatrixFeatures.isEquals(Vt,found));
}
/**
* Makes sure the optional storage parameter is handled correctly
*/
public void checkGetV_Storage() {
DenseMatrix64F A = RandomMatrices.createRandom(5,7,-1,1,rand);
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
assertTrue(alg.decompose(A));
// test positive cases
DenseMatrix64F V = alg.getV(null, false);
DenseMatrix64F storage = new DenseMatrix64F(V.numRows,V.numCols);
alg.getV(storage, false);
assertTrue(MatrixFeatures.isEquals(V, storage));
V = alg.getV(null, true);
storage = new DenseMatrix64F(V.numRows,V.numCols);
alg.getV(storage, true);
assertTrue( MatrixFeatures.isEquals(V,storage));
// give it an incorrect sign
try {
alg.getV(new DenseMatrix64F(10, 20), true);
fail("Exception should have been thrown");
} catch( RuntimeException e ){}
try {
alg.getV(new DenseMatrix64F(10, 20), false);
fail("Exception should have been thrown");
} catch( RuntimeException e ){}
}
/**
* Makes sure arrays are correctly set when it first computers a larger matrix
* then a smaller one. When going from small to large its often forces to declare
* new memory, this way it actually uses memory.
*/
public void testLargeToSmall() {
SingularValueDecomposition<DenseMatrix64F> alg = createSvd();
// first the larger one
DenseMatrix64F A = RandomMatrices.createRandom(10,10,-1,1,rand);
assertTrue(alg.decompose(A));
checkComponents(alg,A);
// then the smaller one
A = RandomMatrices.createRandom(5,5,-1,1,rand);
assertTrue(alg.decompose(A));
checkComponents(alg,A);
}
private int checkOccurrence( double check , double[]values , int numSingular ) {
int num = 0;
for( int i = 0; i < numSingular; i++ ) {
if( Math.abs(values[i]-check)<1e-8)
num++;
}
return num;
}
private void checkComponents( SingularValueDecomposition<DenseMatrix64F> svd , DenseMatrix64F expected )
{
SimpleMatrix U = SimpleMatrix.wrap(svd.getU(null,false));
SimpleMatrix Vt = SimpleMatrix.wrap(svd.getV(null,true));
SimpleMatrix W = SimpleMatrix.wrap(svd.getW(null));
assertTrue( !U.hasUncountable() );
assertTrue( !Vt.hasUncountable() );
assertTrue( !W.hasUncountable() );
if( svd.isCompact() ) {
assertEquals(W.numCols(),W.numRows());
assertEquals(U.numCols(),W.numRows());
assertEquals(Vt.numRows(),W.numCols());
} else {
assertEquals(U.numCols(),W.numRows());
assertEquals(W.numCols(),Vt.numRows());
assertEquals(U.numCols(),U.numRows());
assertEquals(Vt.numCols(),Vt.numRows());
}
DenseMatrix64F found = U.mult(W).mult(Vt).getMatrix();
// found.print();
// expected.print();
assertTrue(MatrixFeatures.isIdentical(expected,found,1e-8));
}
}