/*
* Copyright (c) 2009-2013, Peter Abeles. All Rights Reserved.
*
* This file is part of Efficient Java Matrix Library (EJML).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mikera.matrixx.decompose.impl.svd;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import mikera.matrixx.Matrix;
import mikera.matrixx.algo.Multiplications;
/**
* @author Peter Abeles
*/
public abstract class StandardSvdChecks {
private static double EPS = Math.pow(2,-52);
public abstract SvdImplicitQr createSvd();
boolean omitVerySmallValues = false;
public void allTests() {
testDecompositionOfTrivial();
testWide();
testTall();
checkGetU_Transpose();
if( !omitVerySmallValues )
testVerySmallValue();
testZero();
testLargeToSmall();
testIdentity();
testLots();
}
public void testDecompositionOfTrivial()
{
// Test 1
Matrix A = Matrix.create(new double[][] {{5,2,3},
{1.5, -2, 8},
{-3, 4.7, -0.5}});
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
assertEquals(3, rank(alg, EPS));
assertEquals(0, nullity(alg, EPS));
double []w = alg.getSingularValues().toDoubleArray();
checkNumFound(1,1e-5,9.59186,w);
checkNumFound(1,1e-5,5.18005,w);
checkNumFound(1,1e-5,4.55558,w);
checkComponents(alg,A);
// Test 2
Matrix B = Matrix.create(new double[][] {{1, 2, 3},
{4, 5, 6},
{7, 8, 9}});
alg = createSvd();
assertNotNull(alg._decompose(B));
assertEquals(2, rank(alg, 10*EPS));
assertEquals(0, nullity(alg, EPS));
w = alg.getSingularValues().toDoubleArray();
checkNumFound(1,1e-5,16.848103,w);
checkNumFound(1,1e-5,1.068370,w);
checkNumFound(1,1e-5,0,w);
checkComponents(alg,B);
}
public void testWide() {
Matrix A = Matrix.createRandom(1,1);
A.sub(0.5);
A.scale(2);
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
checkComponents(alg,A);
}
public void testTall() {
Matrix A = Matrix.createRandom(21,5);
A.sub(0.5);
A.scale(2);
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
checkComponents(alg,A);
}
public void testZero() {
for( int i = 1; i <= 11; i += 5 ) {
for( int j = 1; j <= 11; j += 5 ) {
Matrix A = Matrix.create(i,j);
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
int min = Math.min(i,j);
assertEquals(min,checkOccurrence(0,alg.getSingularValues().toDoubleArray(),min),1e-5);
checkComponents(alg,A);
}
}
}
public void testIdentity() {
Matrix A = Matrix.createIdentity(6,6);
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
assertEquals(6,checkOccurrence(1,alg.getSingularValues().toDoubleArray(),6),1e-5);
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() {
Matrix A = Matrix.createRandom(5,5);
A.sub(0.5);
A.scale(2);
A.scale(1e-200);
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
checkComponents(alg,A);
}
public void testLots() {
SvdImplicitQr alg = createSvd();
for( int i = 1; i < 8; i+=2 ) {
for( int j = 1; j < 8; j+=2 ) {
Matrix A = Matrix.createRandom(i,j);
A.sub(0.5);
A.scale(2);
assertNotNull(alg._decompose(A));
checkComponents(alg,A);
}
}
}
/**
* Makes sure transposed flag is correctly handled.
*/
public void checkGetU_Transpose() {
Matrix A = Matrix.createRandom(5, 7);
A.sub(0.5);
A.scale(2);
SvdImplicitQr alg = createSvd();
assertNotNull(alg._decompose(A));
Matrix U = alg.getU().toMatrix();
Matrix Ut = alg.getU().getTranspose().toMatrix();
Matrix found = U.getTransposeCopy().toMatrix();
assertArrayEquals(Ut.getElements(), found.getElements(), 1e-6);
}
/**
* 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() {
SvdImplicitQr alg = createSvd();
// first the larger one
Matrix A = Matrix.createRandom(10,10);
A.sub(0.5);
A.scale(2);
assertNotNull(alg._decompose(A));
checkComponents(alg,A);
// then the smaller one
A = Matrix.createRandom(5,5);
A.sub(0.5);
A.scale(2);
assertNotNull(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( SvdImplicitQr svd , Matrix expected )
{
Matrix U = svd.getU().toMatrix();
Matrix Vt = svd.getV().getTranspose().toMatrix();
Matrix W = svd.getS().toMatrix();
assertTrue( !U.hasUncountable() );
assertTrue( !Vt.hasUncountable() );
assertTrue( !W.hasUncountable() );
if( svd.isCompact() ) {
assertTrue(W.columnCount()==W.rowCount());
assertTrue(U.columnCount()==W.rowCount());
assertTrue(Vt.rowCount()==W.columnCount());
} else {
assertTrue(U.columnCount()==W.rowCount());
assertTrue(W.columnCount()==Vt.rowCount());
assertTrue(U.columnCount()==U.rowCount());
assertTrue(Vt.columnCount()==Vt.rowCount());
}
Matrix found = Multiplications.multiply(U, Multiplications.multiply(W, Vt));
// found.print();
// expected.print();
// assertTrue(expected.equals(found));
assertArrayEquals(expected.toDoubleArray(), found.toDoubleArray(), 1e-6);
}
/**
* Extracts the rank of a matrix using a preexisting decomposition.
*
* @param svd A precomputed decomposition. Not modified.
* @param threshold Tolerance used to determine of a singular value is singular.
* @return The rank of the decomposed matrix.
*/
// taken from SingularOps
private static int rank( SvdImplicitQr svd , double threshold ) {
int numRank=0;
double w[]= svd.getSingularValues().toDoubleArray();
int N = svd.numberOfSingularValues();
for( int j = 0; j < N; j++ ) {
if( w[j] > threshold)
numRank++;
}
return numRank;
}
/**
* Extracts the nullity of a matrix using a preexisting decomposition.
*
* @param svd A precomputed decomposition. Not modified.
* @param threshold Tolerance used to determine of a singular value is singular.
* @return The nullity of the decomposed matrix.
*/
// taken from SingularOps
public static int nullity( SvdImplicitQr svd , double threshold ) {
int ret = 0;
double w[]= svd.getSingularValues().toDoubleArray();
int N = svd.numberOfSingularValues();
int numCol = svd.numCols();
for( int j = 0; j < N; j++ ) {
if( w[j] <= threshold) ret++;
}
return ret + numCol-N;
}
// taken from SingularOps
private static void checkNumFound( int expected , double tol , double value , double data[] )
{
int numFound = 0;
for( int i = 0; i < data.length; i++ ) {
if( Math.abs(data[i]-value) <= tol )
numFound++;
}
assertEquals(expected,numFound);
}
}