blob: 08dce2bacb82f8a01925fcb53d9f3d8ed0a55d54 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.sis.referencing.operation.matrix;
import java.util.Random;
import Jama.Matrix;
import org.apache.sis.test.DependsOn;
import org.apache.sis.test.DependsOnMethod;
import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.TestCase;
import org.junit.Test;
import static java.lang.Double.NaN;
import static org.apache.sis.referencing.operation.matrix.MatrixTestCase.assertEqualsJAMA;
import static org.apache.sis.referencing.operation.matrix.MatrixTestCase.assertEqualsElements;
/**
* Tests the {@link Solver} class using <a href="http://math.nist.gov/javanumerics/jama">JAMA</a>
* as the reference implementation.
*
* <div class="section">Cyclic dependency</div>
* There is a cyclic test dependency since {@link GeneralMatrix} needs {@link Solver} for some operations,
* and conversely. To be more specific the dependency order is:
*
* <ol>
* <li>Simple {@link GeneralMatrix} methods (construction, get/set elements)</li>
* <li>{@link Solver}</li>
* <li>More complex {@code GeneralMatrix} methods (matrix inversion, solve)</li>
* </ol>
*
* We test {@code GeneralMatrix} before {@code Solver} since nothing could be done without
* the above-cited simple operations anyway.
*
* @author Martin Desruisseaux (Geomatys)
* @version 0.4
* @since 0.4
* @module
*/
@DependsOn(GeneralMatrixTest.class) // See class javadoc
public final strictfp class SolverTest extends TestCase {
/**
* The tolerance threshold for this test case, which is {@value}. This value needs to be higher then the
* {@link MatrixTestCase#TOLERANCE} one because of the increased complexity of {@link Solver} operations.
*
* @see MatrixTestCase#TOLERANCE
* @see NonSquareMatrixTest#printStatistics()
*/
protected static final double TOLERANCE = 100 * MatrixTestCase.TOLERANCE;
/**
* The matrix to test.
*/
private MatrixSIS matrix;
/**
* A matrix to use as the reference implementation.
* Contains the same value than {@link #matrix}.
*/
private Matrix reference;
/**
* Initializes the {@link #matrix} and {@link #reference} matrices to random values.
*/
private void createMatrices(final int numRow, final int numCol, final Random random) {
matrix = new GeneralMatrix(numRow, numCol, false, 1);
reference = new Matrix(numRow, numCol);
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
final double e = random.nextDouble() * 1000;
matrix.setElement(j, i, e);
reference.set(j, i, e);
}
}
}
/**
* Tests the {@code Solver.solve(MatrixSIS, Matrix, int)} method.
*
* @throws NoninvertibleMatrixException if an unexpected error occurred while inverting the matrix.
*/
@Test
public void testSolve() throws NoninvertibleMatrixException {
final Random random;
if (MatrixTestCase.DETERMINIST) {
random = new Random(7671901444622173417L);
} else {
random = TestUtilities.createRandomNumberGenerator();
}
for (int k=0; k<MatrixTestCase.NUMBER_OF_REPETITIONS; k++) {
final int size = random.nextInt(16) + 1;
createMatrices(size, random.nextInt(16) + 1, random);
final Matrix referenceArg = this.reference;
final MatrixSIS matrixArg = this.matrix;
createMatrices(size, size, random);
final Matrix jama;
try {
jama = reference.solve(referenceArg);
} catch (RuntimeException e) {
out.println(e); // "Matrix is singular."
continue;
}
final MatrixSIS U = Solver.solve(matrix, matrixArg);
assertEqualsJAMA(jama, U, TOLERANCE);
}
}
/**
* Tests {@link Solver#inverse(org.opengis.referencing.operation.Matrix, boolean)}
* with a square matrix that contains a {@link Double#NaN} value.
*
* @throws NoninvertibleMatrixException if an unexpected error occurred while inverting the matrix.
*/
@Test
@DependsOnMethod("testSolve")
public void testInverseWithNaN() throws NoninvertibleMatrixException {
/*
* Just for making sure that our matrix is correct.
*/
matrix = Matrices.create(5, 5, new double[] {
20, 0, 0, 0, -3000,
0, -20, 0, 0, 4000,
0, 0, 0, 2, 20,
0, 0, 400, 0, 2000,
0, 0, 0, 0, 1
});
double[] expected = {
0.05, 0, 0, 0, 150,
0, -0.05, 0, 0, 200,
0, 0, 0, 0.0025, -5,
0, 0, 0.5, 0, -10,
0, 0, 0, 0, 1
};
MatrixSIS inverse = Solver.inverse(matrix, false);
assertEqualsElements(expected, 5, 5, inverse, TOLERANCE);
/*
* Set a scale factor to NaN. The translation term for the corresponding
* dimension become unknown, so it most become NaN in the inverse matrix.
*/
matrix = Matrices.create(5, 5, new double[] {
20, 0, 0, 0, -3000,
0, -20, 0, 0, 4000,
0, 0, 0, NaN, 20, // Translation is 20: can not be converted.
0, 0, 400, 0, 2000,
0, 0, 0, 0, 1
});
expected = new double[] {
0.05, 0, 0, 0, 150,
0, -0.05, 0, 0, 200,
0, 0, 0, 0.0025, -5,
0, 0, NaN, 0, NaN,
0, 0, 0, 0, 1
};
inverse = Solver.inverse(matrix, false);
assertEqualsElements(expected, 5, 5, inverse, TOLERANCE);
/*
* Set a scale factor to NaN with translation equals to 0.
* The zero value should be preserved, since 0 × any == 0
* (ignoring infinities).
*/
matrix = Matrices.create(5, 5, new double[] {
20, 0, 0, 0, -3000,
0, -20, 0, 0, 4000,
0, 0, 0, NaN, 0, // Translation is 0: should be preserved.
0, 0, 400, 0, 2000,
0, 0, 0, 0, 1
});
expected = new double[] {
0.05, 0, 0, 0, 150,
0, -0.05, 0, 0, 200,
0, 0, 0, 0.0025, -5,
0, 0, NaN, 0, 0,
0, 0, 0, 0, 1
};
inverse = Solver.inverse(matrix, false);
assertEqualsElements(expected, 5, 5, inverse, TOLERANCE);
/*
* Set a translation term to NaN. The translation should be NaN in
* the inverse matrix too, but the scale factor can still be compute.
*/
matrix = Matrices.create(5, 5, new double[] {
20, 0, 0, 0, -3000,
0, -20, 0, 0, 4000,
0, 0, 0, 2, NaN,
0, 0, 400, 0, 2000,
0, 0, 0, 0, 1
});
expected = new double[] {
0.05, 0, 0, 0, 150,
0, -0.05, 0, 0, 200,
0, 0, 0, 0.0025, -5,
0, 0, 0.5, 0, NaN,
0, 0, 0, 0, 1
};
inverse = Solver.inverse(matrix, false);
assertEqualsElements(expected, 5, 5, inverse, TOLERANCE);
}
}