package gov.sandia.cognition.learning.algorithm.minimization.matrix;
import gov.sandia.cognition.algorithm.IterativeAlgorithm;
import gov.sandia.cognition.algorithm.IterativeAlgorithmListener;
import gov.sandia.cognition.learning.data.InputOutputPair;
import gov.sandia.cognition.math.matrix.Vector;
import gov.sandia.cognition.math.matrix.custom.DenseVector;
import gov.sandia.cognition.math.matrix.custom.DiagonalMatrix;
import gov.sandia.cognition.math.matrix.custom.SparseMatrix;
import gov.sandia.cognition.math.matrix.custom.SparseVector;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
*
* @author Jeremy D. Wendt
*/
public class IterativeMatrixSolversTest
{
@Test
public void basicCorrectnessTest()
{
// First, an easy diagonal matrix w/ all diagonals equal
DiagonalMatrix mat = new DiagonalMatrix(5);
mat.setElement(0, 0, 2);
mat.setElement(1, 1, 2);
mat.setElement(2, 2, 2);
mat.setElement(3, 3, 2);
mat.setElement(4, 4, 2);
double[] vals =
{
1, 2, 3, 4, 5
};
DenseVector b = new DenseVector(vals);
MatrixVectorMultiplier scale = new MatrixVectorMultiplier(mat);
SteepestDescentMatrixSolver steepestDescent =
new SteepestDescentMatrixSolver(
new DenseVector(5), b);
Vector x = steepestDescent.learn(scale).getOutput();
assertTrue(x.scale(2).equals(b, 1e-6));
// NOTE: As all eigenvalues are equal, it only takes one step
assertEquals(1, steepestDescent.getIteration());
ConjugateGradientMatrixSolver conjugateGradient =
new ConjugateGradientMatrixSolver(new DenseVector(5), b);
x = conjugateGradient.learn(scale).getOutput();
assertTrue(x.scale(2).equals(b, 1e-6));
// NOTE: As all eigenvalues are equal, it only takes one step
assertEquals(1, conjugateGradient.getIteration());
ConjugateGradientWithPreconditionerMatrixSolver precondCg =
new ConjugateGradientWithPreconditionerMatrixSolver(
new SparseVector(5), b);
MatrixVectorMultiplierDiagonalPreconditioner precondMult =
new MatrixVectorMultiplierDiagonalPreconditioner(mat);
x = precondCg.learn(precondMult).getOutput();
assertTrue(x.scale(2).equals(b, 1e-6));
assertEquals(1, precondCg.getIteration());
OverconstrainedConjugateGradientMatrixMinimizer cgMin =
new OverconstrainedConjugateGradientMatrixMinimizer(
new SparseVector(5), b);
OverconstrainedMatrixVectorMultiplier overMult =
new OverconstrainedMatrixVectorMultiplier(mat);
x = cgMin.learn(overMult).getOutput();
assertTrue(x.scale(2).equals(b, 1e-6));
assertEquals(1, cgMin.getIteration());
// Now it's still diagonal, but with 3 distinct values (out of 5)
mat.setElement(0, 0, 1);
mat.setElement(4, 4, 3);
MatrixVectorMultiplier diag = new MatrixVectorMultiplier(mat);
steepestDescent.setInitialGuess(new DenseVector(5));
x = steepestDescent.learn(diag).getOutput();
assertTrue(diag.evaluate(x).equals(b, 1e-4));
conjugateGradient.setInitialGuess(new DenseVector(5));
x = conjugateGradient.learn(diag).getOutput();
assertTrue(diag.evaluate(x).equals(b, 1e-4));
// NOTE: CG should only take the same number of iterations as there are
// unique eigenvalues (barring numerical errors, which in this case
// shouldn't happen).
assertEquals(3, conjugateGradient.getIteration());
assertTrue(conjugateGradient.getIteration()
<= steepestDescent.getIteration());
precondMult = new MatrixVectorMultiplierDiagonalPreconditioner(mat);
precondCg.setInitialGuess(new SparseVector(5));
x = precondCg.learn(precondMult).getOutput();
assertTrue(diag.evaluate(x).equals(b, 1e-4));
assertEquals(1, precondCg.getIteration());
assertTrue(precondCg.getIteration() <= conjugateGradient.getIteration());
overMult = new OverconstrainedMatrixVectorMultiplier(mat);
x = cgMin.learn(overMult).getOutput();
assertTrue(diag.evaluate(x).equals(b, 1e-4));
// I don't care about the number of iterations for this one as much
// as it should be stiffer than regular CG for square matrices
// Now for something closer to "real" -- a small, 1D finite difference
// matrix
SparseMatrix finiteDiff = new SparseMatrix(5, 5);
finiteDiff.setElement(0, 0, 2);
finiteDiff.setElement(0, 1, -1);
finiteDiff.setElement(1, 0, -1);
finiteDiff.setElement(1, 1, 2);
finiteDiff.setElement(1, 2, -1);
finiteDiff.setElement(2, 1, -1);
finiteDiff.setElement(2, 2, 2);
finiteDiff.setElement(2, 3, -1);
finiteDiff.setElement(3, 2, -1);
finiteDiff.setElement(3, 3, 2);
finiteDiff.setElement(3, 4, -1);
finiteDiff.setElement(4, 3, -1);
finiteDiff.setElement(4, 4, 2);
MatrixVectorMultiplier fd = new MatrixVectorMultiplier(finiteDiff);
SteepestDescentMatrixSolver fdSdSolve = new SteepestDescentMatrixSolver(
new DenseVector(finiteDiff.getNumRows()), b, 1e-16, (int) 1e6);
x = fdSdSolve.learn(fd).getOutput();
assertTrue(finiteDiff.times(x).equals(b, 1e-6));
System.err.println(fdSdSolve.getIteration());
ConjugateGradientMatrixSolver fdCgSolve =
new ConjugateGradientMatrixSolver(new DenseVector(
finiteDiff.getNumRows()), b, 1e-16, (int) 1e6);
x = fdCgSolve.learn(fd).getOutput();
assertTrue(finiteDiff.times(x).equals(b, 1e-6));
System.err.println(fdCgSolve.getIteration());
assertTrue(fdCgSolve.getIteration() <= fdSdSolve.getIteration());
// NOTE: CG should only take the same number of iterations as there are
// unique eigenvalues (barring numerical errors, which in this case
// shouldn't happen).
assertEquals(5, fdCgSolve.getIteration());
precondMult = new MatrixVectorMultiplierDiagonalPreconditioner(
finiteDiff);
precondCg.setInitialGuess(new SparseVector(5));
x = precondCg.learn(precondMult).getOutput();
assertTrue(finiteDiff.times(x).equals(b, 1e-4));
assertTrue(precondCg.getIteration() <= fdCgSolve.getIteration());
overMult = new OverconstrainedMatrixVectorMultiplier(finiteDiff);
x = cgMin.learn(overMult).getOutput();
assertTrue(finiteDiff.times(x).equals(b, 1e-4));
// I don't care about the number of iterations for this one as much
// as it should be stiffer than regular CG for square matrices
// Now, I'll show that cgMin can solve for non-square matrices
SparseMatrix m = new SparseMatrix(6, 5);
m.setElement(0, 0, 1);
m.setElement(1, 1, 1);
m.setElement(2, 2, 1);
m.setElement(3, 3, 1);
m.setElement(4, 4, 1);
m.setElement(5, 0, 1);
m.setElement(5, 4, 1);
double[] vals6 =
{
1, 2, 3, 4, 5, 6
};
DenseVector rhs = new DenseVector(vals6);
cgMin = new OverconstrainedConjugateGradientMatrixMinimizer(
new DenseVector(5), rhs);
overMult = new OverconstrainedMatrixVectorMultiplier(m);
x = cgMin.learn(overMult).getOutput();
assertTrue(m.times(x).equals(rhs, 1e-4));
// Now make it have no exact solution, just a minimum
m.setElement(5, 1, .5);
overMult = new OverconstrainedMatrixVectorMultiplier(m);
x = cgMin.learn(overMult).getOutput();
// Make sure that the two columns that are uneffected by the change are
// still the same
assertEquals(3, x.getElement(2), 1e-6);
assertEquals(4, x.getElement(3), 1e-6);
// The rest should be close, but only really w/in .5-ish
assertEquals(1, x.getElement(0), .5);
assertEquals(2, x.getElement(1), .5);
assertEquals(5, x.getElement(4), .5);
}
@Test
public void testGettersAndSetters()
{
// First, an easy diagonal matrix w/ all diagonals equal
DiagonalMatrix mat = new DiagonalMatrix(5);
mat.setElement(0, 0, 2);
mat.setElement(1, 1, 2);
mat.setElement(2, 2, 2);
mat.setElement(3, 3, 3);
mat.setElement(4, 4, 4);
double[] vals =
{
1, 2, 3, 4, 5
};
MatrixVectorMultiplier mvm = new MatrixVectorMultiplier(mat);
DenseVector b = new DenseVector(vals);
SteepestDescentMatrixSolver steepDesc = new SteepestDescentMatrixSolver(
new DenseVector(5), b, 1e-10);
ConjugateGradientMatrixSolver conjGrad =
new ConjugateGradientMatrixSolver(new DenseVector(5), b, 1e-10);
// steepDesc.clone();
// steepDesc.equals(b);
// Make sure it clones to the same values, but not the same reference
assertTrue(steepDesc.clone().equals(steepDesc));
assertFalse(steepDesc.clone() == steepDesc);
assertTrue(conjGrad.clone().equals(conjGrad));
assertFalse(conjGrad.clone() == conjGrad);
// steepDesc.getInitialGuess();
// steepDesc.setInitialGuess(b);
assertTrue(steepDesc.getInitialGuess().isZero());
assertTrue(conjGrad.getInitialGuess().isZero());
Vector x0;
x0 = new SparseVector(5);
x0.setElement(0, 4);
x0.setElement(4, 3);
steepDesc.setInitialGuess(x0);
conjGrad.setInitialGuess(x0);
assertEquals(x0, steepDesc.getInitialGuess());
assertEquals(x0, conjGrad.getInitialGuess());
x0.zero();
steepDesc.setInitialGuess(x0);
conjGrad.setInitialGuess(x0);
assertTrue(steepDesc.getInitialGuess().isZero());
assertTrue(conjGrad.getInitialGuess().isZero());
// steepDesc.getTolerance();
// steepDesc.setTolerance(tolerance);
assertEquals(1e-10, steepDesc.getTolerance(), 1e-16);
assertEquals(1e-10, conjGrad.getTolerance(), 1e-16);
steepDesc.setTolerance(1e-14);
conjGrad.setTolerance(1e-14);
assertEquals(1e-14, steepDesc.getTolerance(), 1e-16);
assertEquals(1e-14, conjGrad.getTolerance(), 1e-16);
// steepDesc.getMaxIterations();
// steepDesc.setMaxIterations(maxIterations);
steepDesc.setMaxIterations((int) 1e6);
conjGrad.setMaxIterations((int) 1e6);
assertEquals((int) 1e6, steepDesc.getMaxIterations());
assertEquals((int) 1e6, conjGrad.getMaxIterations());
steepDesc.setMaxIterations(2);
conjGrad.setMaxIterations(2);
assertEquals(2, steepDesc.getMaxIterations());
assertEquals(2, conjGrad.getMaxIterations());
// steepDesc.getIteration();
// steepDesc.getResult();
// steepDesc.isResultValid();
// steepDesc.learn(null);
InputOutputPair<Vector, Vector> sd1 = steepDesc.learn(mvm);
InputOutputPair<Vector, Vector> sd2 = steepDesc.getResult();
InputOutputPair<Vector, Vector> cg1 = conjGrad.learn(mvm);
InputOutputPair<Vector, Vector> cg2 = conjGrad.getResult();
assertFalse(steepDesc.isResultValid());
assertFalse(conjGrad.isResultValid());
assertTrue(sd1.equals(sd2));
assertTrue(cg1.equals(cg2));
assertEquals(2, steepDesc.getIteration());
assertEquals(2, conjGrad.getIteration());
mat.setElement(3, 3, 2);
mat.setElement(4, 4, 2);
mvm = new MatrixVectorMultiplier(mat);
sd1 = steepDesc.learn(mvm);
sd2 = steepDesc.getResult();
cg1 = conjGrad.learn(mvm);
cg2 = conjGrad.getResult();
assertTrue(steepDesc.isResultValid());
assertTrue(conjGrad.isResultValid());
assertTrue(sd1.equals(sd2));
assertTrue(cg1.equals(cg2));
assertEquals(1, steepDesc.getIteration());
assertEquals(1, conjGrad.getIteration());
// steeDesc.hashCode() -- just call it to make sure it doesn't fail
steepDesc.hashCode();
}
@Test
public void exceptionsThrown()
{
// First, an easy diagonal matrix w/ all diagonals equal
DiagonalMatrix mat = new DiagonalMatrix(5);
mat.setElement(0, 0, 2);
mat.setElement(1, 1, 2);
mat.setElement(2, 2, 2);
mat.setElement(3, 3, 3);
mat.setElement(4, 4, 4);
double[] vals =
{
1, 2, 3, 4, 5
};
MatrixVectorMultiplier mvm = new MatrixVectorMultiplier(mat);
DenseVector b = new DenseVector(vals);
SteepestDescentMatrixSolver steepDesc = new SteepestDescentMatrixSolver(
new DenseVector(3), b, 1e-10);
ConjugateGradientMatrixSolver conjGrad =
new ConjugateGradientMatrixSolver(new DenseVector(5), b.subVector(
0, 3), 1e-10);
// steepDesc.clone();
// No exceptions possible
// steepDesc.equals(b);
// No exceptions possible
// steepDesc.getInitialGuess();
// No exceptions possible
// steepDesc.getTolerance();
// No exceptions possible
// steepDesc.getMaxIterations();
// No exceptions possible
// steepDesc.getIteration();
// No exceptions possible
// steepDesc.getResult();
// No exceptions possible
// steepDesc.isResultValid();
// No exceptions possible
// steepDesc.setInitialGuess(b);
try
{
steepDesc.setInitialGuess(null);
assertFalse(true);
}
catch (NullPointerException e)
{
// correct path
}
try
{
conjGrad.setInitialGuess(null);
assertFalse(true);
}
catch (NullPointerException e)
{
// correct path
}
// steepDesc.setTolerance(tolerance);
try
{
steepDesc.setTolerance(-1e12);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// correct path
}
try
{
conjGrad.setTolerance(-1e12);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// correct path
}
// This is fine
steepDesc.setTolerance(0);
conjGrad.setTolerance(0);
// steepDesc.setMaxIterations(maxIterations);
try
{
steepDesc.setMaxIterations(-1);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// correct path
}
try
{
conjGrad.setMaxIterations(-1);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// correct path
}
try
{
steepDesc.setMaxIterations(0);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// correct path
}
try
{
conjGrad.setMaxIterations(0);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// correct path
}
// steepDesc.learn(null);
try
{
steepDesc.learn(mvm);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// Correct path
}
try
{
conjGrad.learn(mvm);
assertFalse(true);
}
catch (IllegalArgumentException e)
{
// Correct path
}
try
{
steepDesc.learn(null);
assertFalse(true);
}
catch (NullPointerException e)
{
// Correct path
}
try
{
conjGrad.learn(null);
assertFalse(true);
}
catch (NullPointerException e)
{
// Correct path
}
}
@Test
public void listenersTest()
{
DiagonalMatrix mat = new DiagonalMatrix(5);
mat.setElement(0, 0, 2);
mat.setElement(1, 1, 3);
mat.setElement(2, 2, 4);
mat.setElement(3, 3, 5);
mat.setElement(4, 4, 6);
double[] vals =
{
1, 2, 3, 4, 5
};
DenseVector b = new DenseVector(vals);
MatrixVectorMultiplier scale = new MatrixVectorMultiplier(mat);
TestAlgListener l1 = new TestAlgListener();
assertFalse(l1.started);
assertFalse(l1.ended);
assertEquals(0, l1.numStepStarted);
assertEquals(0, l1.numStepEnded);
assertEquals(-1, l1.stopAtStep);
ConjugateGradientMatrixSolver cg = new ConjugateGradientMatrixSolver(
new SparseVector(5), b);
cg.addIterativeAlgorithmListener(l1);
cg.learn(scale);
assertTrue(l1.started);
assertTrue(l1.ended);
assertEquals(5, l1.numStepStarted);
assertEquals(5, l1.numStepEnded);
assertEquals(-1, l1.stopAtStep);
l1.started = false;
l1.ended = false;
l1.numStepStarted = 0;
l1.numStepEnded = 0;
l1.stopAtStep = 3;
cg.learn(scale);
assertTrue(l1.started);
assertTrue(l1.ended);
assertEquals(3, l1.numStepStarted);
assertEquals(3, l1.numStepEnded);
assertEquals(3, l1.stopAtStep);
l1.started = false;
l1.ended = false;
l1.numStepStarted = 0;
l1.numStepEnded = 0;
l1.stopAtStep = 3;
cg.removeIterativeAlgorithmListener(l1);
cg.learn(scale);
assertFalse(l1.started);
assertFalse(l1.ended);
assertEquals(0, l1.numStepStarted);
assertEquals(0, l1.numStepEnded);
assertEquals(3, l1.stopAtStep);
}
private static class TestAlgListener
implements IterativeAlgorithmListener
{
boolean started = false;
boolean ended = false;
int numStepStarted = 0;
int numStepEnded = 0;
int stopAtStep = -1;
@Override
public void algorithmStarted(IterativeAlgorithm algorithm)
{
started = true;
}
@Override
public void algorithmEnded(IterativeAlgorithm algorithm)
{
ended = true;
}
@Override
public void stepStarted(IterativeAlgorithm algorithm)
{
++numStepStarted;
if ((stopAtStep != -1) && (stopAtStep == algorithm.getIteration()))
{
((IterativeMatrixSolver) algorithm).stop();
}
}
@Override
public void stepEnded(IterativeAlgorithm algorithm)
{
++numStepEnded;
}
};
}