You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2013/10/04 21:08:19 UTC

svn commit: r1529262 [2/3] - in /sis/trunk: ./ application/sis-console/src/main/java/org/apache/sis/console/ core/sis-metadata/src/main/java/org/apache/sis/internal/jaxb/gml/ core/sis-metadata/src/test/java/org/apache/sis/xml/ core/sis-referencing/src/...

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/Solver.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -17,6 +17,8 @@
 package org.apache.sis.referencing.operation.matrix;
 
 import org.opengis.referencing.operation.Matrix;
+import org.apache.sis.internal.util.DoubleDouble;
+import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.ArraysExt;
 
 
@@ -96,68 +98,121 @@ final class Solver implements Matrix {
     }
 
     /**
-     * Computes the inverse of the given matrix. This method shall be invoked only for square matrices
-     * (this is <strong>not</strong> verified by this method).
+     * Computes the inverse of the given matrix. This method shall be invoked only for square matrices.
+     *
+     * @throws NoninvertibleMatrixException If the {@code X} matrix is not square or singular.
      */
     static MatrixSIS inverse(final MatrixSIS X) throws NoninvertibleMatrixException {
-        return solve(X, IDENTITY, X.getNumRow());
+        final int size = X.getNumRow();
+        final int numCol = X.getNumCol();
+        if (numCol != size) {
+            throw new NoninvertibleMatrixException(Errors.format(Errors.Keys.NonInvertibleMatrix_2, size, numCol));
+        }
+        return solve(X, IDENTITY, null, size, size);
     }
 
     /**
      * Solves {@code X} × <var>U</var> = {@code Y}.
      * This method is an adaptation of the {@code LUDecomposition} class of the JAMA matrix package.
      *
+     * @param  X The matrix to invert.
+     * @param  Y The desired result of {@code X} × <var>U</var>.
+     * @throws NoninvertibleMatrixException If the {@code X} matrix is not square or singular.
+     */
+    static MatrixSIS solve(final MatrixSIS X, final Matrix Y) throws NoninvertibleMatrixException {
+        final int size = X.getNumRow();
+        final int numCol = X.getNumCol();
+        if (numCol != size) {
+            throw new NoninvertibleMatrixException(Errors.format(Errors.Keys.NonInvertibleMatrix_2, size, numCol));
+        }
+        final int innerSize = Y.getNumCol();
+        GeneralMatrix.ensureNumRowMatch(size, Y, innerSize);
+        double[] eltY = null;
+        if (Y instanceof GeneralMatrix) {
+            eltY = ((GeneralMatrix) Y).elements;
+            if (eltY.length == size * innerSize) {
+                eltY = null; // Matrix does not contains error terms.
+            }
+        }
+        return solve(X, Y, eltY, size, innerSize);
+    }
+
+    /**
+     * Implementation of {@code solve} and {@code inverse} methods.
+     * Use a "left-looking", dot-product, Crout/Doolittle algorithm.
+     *
      * <p>This method does <strong>not</strong> checks the matrix size.
      * Check for matrix size shall be performed by the caller like below:</p>
      *
      * {@preformat java
      *     final int size = X.getNumRow();
      *     if (X.getNumCol() != size) {
-     *         throw new NoninvertibleTransformException("Matrix must be square.");
+     *         throw new NoninvertibleMatrixException("Matrix must be square.");
      *     }
      *     if (Y.getNumRow() != size) {
      *         throw new MismatchedMatrixSizeException("Matrix row dimensions must agree.");
      *     }
      * }
      *
-     * @param  X The matrix to invert.
+     * @param  X         The matrix to invert.
+     * @param  Y         The desired result of {@code X} × <var>U</var>.
+     * @param  eltY      Elements and error terms of the {@code Y} matrix, or {@code null} if not available.
+     * @param  size      The value of {@code X.getNumRow()}, {@code X.getNumCol()} and {@code Y.getNumRow()}.
      * @param  innerSize The value of {@code Y.getNumCol()}.
-     * @throws NoninvertibleMatrixException If the {@code X} matrix is singular.
+     * @throws NoninvertibleMatrixException If the {@code X} matrix is not square or singular.
      */
-    static MatrixSIS solve(final MatrixSIS X, final Matrix Y, final int innerSize)
-            throws NoninvertibleMatrixException
+    private static MatrixSIS solve(final MatrixSIS X, final Matrix Y, final double[] eltY,
+            final int size, final int innerSize) throws NoninvertibleMatrixException
     {
-        final int size = X.getNumRow();
-        /*
-         * Use a "left-looking", dot-product, Crout/Doolittle algorithm.
-         */
-        final double[] LU = X.getElements();
+        assert (X.getNumRow() == size && X.getNumCol() == size) : size;
+        assert (Y.getNumRow() == size && Y.getNumCol() == innerSize) || (Y instanceof Solver);
+
+        final int errorLU = size * size;
+        final double[] LU = GeneralMatrix.getExtendedElements(X, size, size, true);
+        assert errorLU == GeneralMatrix.indexOfErrors(size, size, LU);
         final int[] pivot = new int[size];
         for (int j=0; j<size; j++) {
            pivot[j] = j;
         }
-        final double[] column = new double[size];
+        final double[]  column = new double[size * 2]; // [0 … size-1] : column values; [size … 2*size-1] : error terms.
+        final DoubleDouble acc = new DoubleDouble();   // Temporary variable for sum ("accumulator") and subtraction.
+        final DoubleDouble rat = new DoubleDouble();   // Temporary variable for products and ratios.
         for (int i=0; i<size; i++) {
             /*
              * Make a copy of the i-th column.
              */
             for (int j=0; j<size; j++) {
-                column[j] = LU[j*size + i];
+                final int k = j*size + i;
+                column[j]        = LU[k];            // Value
+                column[j + size] = LU[k + errorLU];  // Error
             }
             /*
-             * Apply previous transformations.
+             * Apply previous transformations. This part is equivalent to the following code,
+             * but using double-double arithmetic instead than the primitive 'double' type:
+             *
+             *     double sum = 0;
+             *     for (int k=0; k<kmax; k++) {
+             *         sum += LU[rowOffset + k] * column[k];
+             *     }
+             *     LU[rowOffset + i] = (column[j] -= sum);
              */
             for (int j=0; j<size; j++) {
                 final int rowOffset = j*size;
                 final int kmax = Math.min(j,i);
-                double s = 0.0;
+                acc.clear();
                 for (int k=0; k<kmax; k++) {
-                   s += LU[rowOffset + k] * column[k];
+                    rat.setFrom(LU, rowOffset + k, errorLU);
+                    rat.multiply(column, k, size);
+                    acc.add(rat);
                 }
-                LU[rowOffset + i] = (column[j] -= s);
+                acc.subtract(column, j, size);
+                acc.negate();
+                acc.storeTo(column, j, size);
+                acc.storeTo(LU, rowOffset + i, errorLU);
             }
             /*
-             * Find pivot and exchange if necessary.
+             * Find pivot and exchange if necessary. There is no floating-point arithmetic here
+             * (ignoring the comparison for magnitude order), only work on index values.
              */
             int p = i;
             for (int j=i; ++j < size;) {
@@ -169,17 +224,28 @@ final class Solver implements Matrix {
                 final int pRow = p*size;
                 final int iRow = i*size;
                 for (int k=0; k<size; k++) { // Swap two full rows.
-                    ArraysExt.swap(LU, pRow + k, iRow + k);
+                    DoubleDouble.swap(LU, pRow + k, iRow + k, errorLU);
                 }
                 ArraysExt.swap(pivot, p, i);
             }
             /*
-             * Compute multipliers.
+             * Compute multipliers. This part is equivalent to the following code, but
+             * using double-double arithmetic instead than the primitive 'double' type:
+             *
+             *     final double sum = LU[i*size + i];
+             *     if (sum != 0.0) {
+             *         for (int j=i; ++j < size;) {
+             *             LU[j*size + i] /= sum;
+             *         }
+             *     }
              */
-            final double d = LU[i*size + i];
-            if (d != 0.0) {
+            acc.setFrom(LU, i*size + i, errorLU);
+            if (!acc.isZero()) {
                 for (int j=i; ++j < size;) {
-                    LU[j*size + i] /= d;
+                    final int t = j*size + i;
+                    rat.setFrom(acc);
+                    rat.inverseDivide(LU, t, errorLU);
+                    rat.storeTo      (LU, t, errorLU);
                 }
             }
         }
@@ -188,51 +254,87 @@ final class Solver implements Matrix {
          * Ensure that the matrix is not singular.
          */
         for (int j=0; j<size; j++) {
-            if (LU[j*size + j] == 0) {
-                throw new NoninvertibleMatrixException();
+            rat.setFrom(LU, j*size + j, errorLU);
+            if (rat.isZero()) {
+                final Integer n = size;
+                throw new NoninvertibleMatrixException(Errors.format(Errors.Keys.NonInvertibleMatrix_2, n, n));
             }
         }
         /*
-         * Copy right hand side with pivoting.
-         * We will write the result of this method directly in the elements array.
+         * Copy right hand side with pivoting. Write the result directly in the elements array
+         * of the result matrix. This block does not perform floating-point arithmetic operations.
          */
-        final double[] elements = new double[size * innerSize];
+        final GeneralMatrix result = GeneralMatrix.createExtendedPrecision(size, innerSize);
+        final double[] elements = result.elements;
+        final int errorOffset = size * innerSize;
         for (int k=0,j=0; j<size; j++) {
             final int p = pivot[j];
             for (int i=0; i<innerSize; i++) {
-                elements[k++] = Y.getElement(p, i);
+                if (eltY != null) {
+                    final int t = p*innerSize + i;
+                    elements[k]               = eltY[t];
+                    elements[k + errorOffset] = eltY[t + errorOffset];
+                } else {
+                    elements[k] = Y.getElement(p, i);
+                }
+                k++;
             }
         }
         /*
-         * Solve L*Y = B(pivot, :)
+         * Solve L*Y = B(pivot, :). The inner block is equivalent to the following line,
+         * but using double-double arithmetic instead of 'double' primitive type:
+         *
+         *     elements[loRowOffset + i] -= (elements[rowOffset + i] * LU[luRowOffset + k]);
          */
         for (int k=0; k<size; k++) {
             final int rowOffset = k*innerSize;          // Offset of row computed by current iteration.
             for (int j=k; ++j < size;) {
-                final int loRowOffset = j*innerSize;    // Offset of a row after (locate lower) the current row.
-                final int luRowOffset = j*size;  // Offset of the corresponding row in the LU matrix.
+                final int loRowOffset = j*innerSize;    // Offset of some row after the current row.
+                final int luRowOffset = j*size;         // Offset of the corresponding row in the LU matrix.
                 for (int i=0; i<innerSize; i++) {
-                    elements[loRowOffset + i] -= (elements[rowOffset + i] * LU[luRowOffset + k]);
+                    acc.setFrom (elements, loRowOffset + i, errorOffset);
+                    rat.setFrom (elements, rowOffset   + i, errorOffset);
+                    rat.multiply(LU,       luRowOffset + k, errorLU);
+                    acc.subtract(rat);
+                    acc.storeTo (elements, loRowOffset + i, errorOffset);
                 }
             }
         }
         /*
-         * Solve U*X = Y
+         * Solve U*X = Y. The content of the loop is equivalent to the following line,
+         * but using double-double arithmetic instead of 'double' primitive type:
+         *
+         *     double sum = LU[k*size + k];
+         *     for (int i=0; i<innerSize; i++) {
+         *         elements[rowOffset + i] /= sum;
+         *     }
+         *     for (int j=0; j<k; j++) {
+         *         sum = LU[j*size + k];
+         *         for (int i=0; i<innerSize; i++) {
+         *             elements[upRowOffset + i] -= (elements[rowOffset + i] * sum);
+         *         }
+         *     }
          */
         for (int k=size; --k >= 0;) {
             final int rowOffset = k*innerSize;          // Offset of row computed by current iteration.
-            final double d = LU[k*size + k];     // A diagonal element on the current row.
+            acc.setFrom(LU, k*size + k, errorLU);       // A diagonal element on the current row.
             for (int i=0; i<innerSize; i++) {           // Apply to all columns in the current row.
-                elements[rowOffset + i] /= d;
+                rat.setFrom(acc);
+                rat.inverseDivide(elements, rowOffset + i, errorOffset);
+                rat.storeTo      (elements, rowOffset + i, errorOffset);
             }
             for (int j=0; j<k; j++) {
                 final int upRowOffset = j*innerSize;    // Offset of a row before (locate upper) the current row.
-                final double c = LU[j*size + k]; // Same column than the diagonal element, but in the upper row.
+                acc.setFrom(LU, j*size + k, errorLU);   // Same column than the diagonal element, but in the upper row.
                 for (int i=0; i<innerSize; i++) {       // Apply to all columns in the upper row.
-                    elements[upRowOffset + i] -= (elements[rowOffset + i] * c);
+                    rat.setFrom(elements, rowOffset + i, errorOffset);
+                    rat.multiply(acc);
+                    rat.subtract(elements, upRowOffset + i, errorOffset);
+                    rat.negate();
+                    rat.storeTo(elements, upRowOffset + i, errorOffset);
                 }
             }
         }
-        return Matrices.create(size, innerSize, elements);
+        return result;
     }
 }

Modified: sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java (original)
+++ sis/trunk/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/matrix/package-info.java Fri Oct  4 19:08:18 2013
@@ -43,6 +43,20 @@
  *
  * <p><center><img src="doc-files/AffineTransform.png"></center></p>
  *
+ * {@section Extended floating point precision}
+ * This package uses extended floating point precision for most arithmetic operations like matrix multiplications and
+ * inversions. SIS needs extended precision because <cite>affine transforms</cite> concatenations like conversion from
+ * degrees to radians, followed by some operations, followed by conversion back from radians to degrees, are very frequent.
+ * Without extended precision, we often obtain values like 0.99999… where we would expect an identity transform.
+ * The usual workaround - namely comparing the floating point values with a small <var>epsilon</var> tolerance value -
+ * is dangerous in this particular case because <cite>datum shifts</cite>, when expressed as a matrix from their
+ * {@linkplain org.apache.sis.referencing.datum.BursaWolfParameters Bursa-Wolf parameters}, are very close to the
+ * identity transform.
+ *
+ * <p>The current implementation uses
+ * <a href="http://en.wikipedia.org/wiki/Double-double_%28arithmetic%29#Double-double_arithmetic">double-double
+ * arithmetic</a>. However this may change in any future SIS version.</p>
+ *
  * {@section Related projects}
  * This package is <strong>not</strong> designed for large matrices, and is rooted in
  * {@code org.apache.sis.referencing} for making clearer that this is not a general-purpose library.

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/GeneralMatrixTest.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -16,7 +16,8 @@
  */
 package org.apache.sis.referencing.operation.matrix;
 
-import org.apache.sis.test.DependsOn;
+import java.util.Random;
+import org.junit.Test;
 
 import static org.junit.Assert.*;
 
@@ -30,17 +31,19 @@ import static org.junit.Assert.*;
  * @version 0.4
  * @module
  */
-@DependsOn(SolverTest.class)
 public final strictfp class GeneralMatrixTest extends MatrixTestCase {
     /**
      * Number of rows and columns.
      */
-    private final int size;
+    private int size;
 
     /**
-     * Creates a test with a random size for the square matrix.
+     * Computes a random size for the next matrix to create.
+     *
+     * @param random The random number generator to use.
      */
-    public GeneralMatrixTest() {
+    @Override
+    void prepareNewMatrixSize(final Random random) {
         size = 5 + random.nextInt(8); // Matrix sizes from 5 to 12 inclusive.
     }
 
@@ -55,4 +58,39 @@ public final strictfp class GeneralMatri
         super.validate(matrix);
         assertEquals(GeneralMatrix.class, matrix.getClass());
     }
+
+    /**
+     * Tests {@link GeneralMatrix#getExtendedElements(Matrix, int, int, boolean)}.
+     * This test verifies that {@code getExtendedElements} can infer default error
+     * terms for some well known values.
+     *
+     * @see Matrix2Test#testGetExtendedElements()
+     */
+    @Test
+    public void testGetExtendedElements() {
+        testGetExtendedElements(new GeneralMatrix(2, 2, new double[] {
+                StrictMath.PI / 180, // Degrees to radians
+                180 / StrictMath.PI, // Radians to degrees
+                0.9,                 // Gradians to degrees
+                0.1234567}));        // Random value with no special meaning.
+    }
+
+    /**
+     * Implementation of {@link #testGetExtendedElements()} shared by {@link Matrix2Test}.
+     */
+    static void testGetExtendedElements(final MatrixSIS matrix) {
+        final double[] elements = GeneralMatrix.getExtendedElements(matrix, Matrix2.SIZE, Matrix2.SIZE, false);
+        assertArrayEquals(new double[] {
+                // Same values than in the above matrix.
+                StrictMath.PI / 180,
+                180 / StrictMath.PI,
+                0.9,
+                0.1234567,
+
+                // Values below this point are error terms copied from DoubleDouble.ERRORS.
+                 2.9486522708701687E-19,
+                -1.9878495670576283E-15,
+                -2.2204460492503132E-17,
+                 0}, elements, STRICT);
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatricesTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatricesTest.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatricesTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatricesTest.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -331,18 +331,28 @@ public final strictfp class MatricesTest
      */
     @Test
     public void testToString() {
-        final MatrixSIS matrix = Matrices.create(4, 4, new double[] {
-            39.5193682106975150,  -68.5200,     80.0,  98,
-           -66.0358637477182200,   Double.NaN,  43.9,  Double.NEGATIVE_INFINITY,
-             2.0741018968776337,   83.7260,     37.0,  -3,
-            91.8796187759200600,  -18.2674,     24.0,  36
+        assertMultilinesEquals(
+                "┌            ┐\n" +
+                "│ 1  0  0  0 │\n" +
+                "│ 0  1  0  0 │\n" +
+                "│ 0  0  1  0 │\n" +
+                "│ 0  0  0  1 │\n" +
+                "└            ┘\n", new Matrix4().toString());
+        /*
+         * Mix of values with different precision, ±0, ±1, NaN and infinities.
+         */
+        final MatrixSIS matrix = Matrices.create(4, 5, new double[] {
+            39.5193682106975150,  -68.5200,     -1.0,  1,  98,
+           -66.0358637477182200,   Double.NaN,  43.0,  0,  Double.NEGATIVE_INFINITY,
+             2.0741018968776337,   83.7260,     -0.0,  1,  -3,
+            91.8796187759200600,  -18.2674,     24.5,  0,  36.5
         });
         assertMultilinesEquals(
-                "┌                                            ┐\n" +
-                "│  39.5193682106975150  -68.5200  80.0  98.0 │\n" +
-                "│ -66.0358637477182200       NaN  43.9    -∞ │\n" +
-                "│   2.0741018968776337   83.7260  37.0  -3.0 │\n" +
-                "│  91.8796187759200600  -18.2674  24.0  36.0 │\n" +
-                "└                                            ┘\n", matrix.toString());
+                "┌                                               ┐\n" +
+                "│  39.5193682106975150  -68.5200  -1    1  98.0 │\n" +
+                "│ -66.0358637477182200       NaN  43.0  0    -∞ │\n" +
+                "│   2.0741018968776337   83.7260  -0    1  -3.0 │\n" +
+                "│  91.8796187759200600  -18.2674  24.5  0  36.5 │\n" +
+                "└                                               ┘\n", matrix.toString());
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix1Test.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix1Test.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix1Test.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix1Test.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -55,6 +55,7 @@ public final strictfp class Matrix1Test 
      */
     @Test
     public void testConstructor() {
+        initialize(415870088589607716L);
         final double[] elements = createRandomPositiveValues(SIZE * SIZE);
         final Matrix1 matrix = new Matrix1(
                 elements[0]);

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix2Test.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix2Test.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix2Test.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix2Test.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -55,6 +55,7 @@ public final strictfp class Matrix2Test 
      */
     @Test
     public void testConstructor() {
+        initialize(-8453835559080304420L);
         final double[] elements = createRandomPositiveValues(SIZE * SIZE);
         final Matrix2 matrix = new Matrix2(
                 elements[0],
@@ -64,4 +65,18 @@ public final strictfp class Matrix2Test 
         validate(matrix);
         assertArrayEquals(elements, matrix.getElements(), STRICT);
     }
+
+    /**
+     * Tests {@link GeneralMatrix#getExtendedElements(Matrix, int, int, boolean)}.
+     * This test is a copy of {@link GeneralMatrixTest#testGetExtendedElements()},
+     * but on a {@link Matrix2} instance instead of {@link GeneralMatrix}.
+     */
+    @Test
+    public void testGetExtendedElements() {
+        GeneralMatrixTest.testGetExtendedElements(new Matrix2(
+                StrictMath.PI / 180, // Degrees to radians
+                180 / StrictMath.PI, // Radians to degrees
+                0.9,                 // Gradians to degrees
+                0.1234567));         // Random value with no special meaning.
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix3Test.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix3Test.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix3Test.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix3Test.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -56,6 +56,7 @@ public final strictfp class Matrix3Test 
      */
     @Test
     public void testConstructor() {
+        initialize(-2078758443421995879L);
         final double[] elements = createRandomPositiveValues(SIZE * SIZE);
         final Matrix3 matrix = new Matrix3(
                 elements[0],

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/Matrix4Test.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -56,6 +56,7 @@ public final strictfp class Matrix4Test 
      */
     @Test
     public void testConstructor() {
+        initialize(-7053945420932915425L);
         final double[] elements = createRandomPositiveValues(SIZE * SIZE);
         final Matrix4 matrix = new Matrix4(
                 elements[ 0],
@@ -77,4 +78,44 @@ public final strictfp class Matrix4Test 
         validate(matrix);
         assertArrayEquals(elements, matrix.getElements(), STRICT);
     }
+
+    /**
+     * Tests the accuracy of a chain of matrix operations.
+     *
+     * @throws NoninvertibleMatrixException Should never happen.
+     */
+    @Test
+    public void testAccuracy() throws NoninvertibleMatrixException {
+        final double parisMeridian = 2 + (20 + 13.82/60)/60;  // Paris meridian: 2°20'13.82"
+        final double toRadians = StrictMath.PI / 180;
+        /*
+         * Gradians to degrees with a Prime Meridian shift
+         * and a random conversion factor for z values.
+         */
+        final Matrix4 step1 = new Matrix4(
+                0.9,    0,   0, parisMeridian,
+                0, 0.9, 0,   0,
+                0, 0,   0.8, 0, // Random conversion factor for z values.
+                0, 0,   0,   1);
+        /*
+         * Degrees to radians with swapping of (longitude, latitude) axes
+         * and a conversion factor of z values from feet to metres.
+         */
+        final Matrix4 step2 = new Matrix4(
+                0, toRadians, 0, 0,
+                toRadians, 0, 0, 0,
+                0, 0, 0.3048, 0,
+                0, 0, 0, 1);
+        /*
+         * Converse of the above operations.
+         */
+        final MatrixSIS step3 = step2.multiply(step1).inverse();
+        /*
+         * Concatenate everything, which should go back to the identity transform.
+         * Note that the 'isIdentity()' test fail if the double-double arithmetic is
+         * disabled, because some scale factors will be 0.9999999999999999 instead of 1.
+         */
+        final MatrixSIS result = step3.multiply(step2).multiply(step1);
+        assertTrue("isIdentity()", result.isIdentity());
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/MatrixTestCase.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -18,6 +18,8 @@ package org.apache.sis.referencing.opera
 
 import java.util.Random;
 import Jama.Matrix;
+import org.apache.sis.math.Statistics;
+import org.apache.sis.internal.util.DoubleDouble;
 import org.apache.sis.test.TestCase;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOnMethod;
@@ -48,22 +50,47 @@ import static org.apache.sis.test.Assert
  */
 public abstract strictfp class MatrixTestCase extends TestCase {
     /**
+     * {@code true} for reusing the same sequences of random numbers in every execution of test cases, or
+     * {@code false} for "truly" random sequences of random numbers. This flag can be set to {@code false}
+     * for testing purpose, but should be set to {@code true} otherwise for avoiding random test failure.
+     * This is needed because we want to set {@link #TOLERANCE} to a small value, but it is very difficult
+     * to guaranteed that a random sequence of numbers will not cause a larger discrepancy.
+     *
+     * <p>Note that this flag is set to {@code false} if double-double arithmetic is disabled because in such
+     * case, the results should be identical to the JAMA results (i.e. equal using a {@link #TOLERANCE} of zero)
+     * for any sequence of numbers.</p>
+     */
+    protected static final boolean DETERMINIST = !DoubleDouble.DISABLED;
+
+    /**
      * A constant for any test in this class or a subclass which expect
      * a floating point value to be strictly equals to an other value.
      */
     static final double STRICT = 0;
 
     /**
-     * Tolerance factor for comparisons of floating point numbers.
-     * The matrix elements used in this class varies between 0 and 100,
+     * Tolerance factor for comparisons of floating point numbers between SIS and JAMA implementation,
+     * which is {@value}. Note that the matrix element values used in this class vary between 0 and 100,
      * and the {@code Math.ulp(100.0)} value is approximatively 1.4E-14.
+     *
+     * {@section How this value is determined}
+     * Experience (by looking at {@link #statistics}) shows that the differences are usually smaller than 1E-12.
+     * However when using non-determinist sequence of random values ({@link #DETERMINIST} sets to {@code false}),
+     * we do have from time-to-time a difference around 1E-9.
+     *
+     * Those differences exist because SIS uses double-double arithmetic, while JAMA uses ordinary double.
+     * To remove that ambiguity, one can temporarily set {@link DoubleDouble#DISABLED} to {@code true},
+     * in which case the SIS results should be strictly identical to the JAMA ones.
+     *
+     * @see SolverTest#TOLERANCE
+     * @see NonSquareMatrixTest#printStatistics()
      */
-    static final double TOLERANCE = 1E-10;
+    protected static final double TOLERANCE = DoubleDouble.DISABLED ? STRICT : 1E-12;
 
     /**
      * Number of random matrices to try in arithmetic operation tests.
      */
-    static final int NUMBER_OF_REPETITIONS = 10;
+    static final int NUMBER_OF_REPETITIONS = 100;
 
     /**
      * The threshold in matrix determinant for attempting to compute the inverse.
@@ -72,15 +99,45 @@ public abstract strictfp class MatrixTes
     private static final double DETERMINANT_THRESHOLD = 0.001;
 
     /**
-     * Random number generator, created by {@link #initialize(String, boolean)} when first needed.
+     * Statistics about the different between the JAMA and SIS matrix elements, or {@code null}
+     * if those statistics do not need to be collected. This is used during the test development
+     * phase for tuning the tolerance threshold.
+     *
+     * @see NonSquareMatrixTest#printStatistics()
+     */
+    static final Statistics statistics = verbose ? new Statistics("|SIS - JAMA|") : null;
+
+    /**
+     * Random number generator, created by {@link #initialize(long)} as the first operation of
+     * any test method which will use random numbers. This random number generator will use a
+     * fixed seed if {@link #DETERMINIST} is {@code true}, which is the normal case.
      */
-    final Random random;
+    private Random random;
 
     /**
      * For subclasses only.
      */
     MatrixTestCase() {
-        random = TestUtilities.createRandomNumberGenerator();
+    }
+
+    /**
+     * Initializes the random number generator to the given seed. If {@link #DETERMINIST} is {@code false}
+     * (which happen only when performing some more extensive tests), then the given seed will be replaced
+     * by a random one.
+     *
+     * @param seed The initial seed.
+     */
+    final void initialize(final long seed) {
+        random = DETERMINIST ? new Random(seed) : TestUtilities.createRandomNumberGenerator();
+    }
+
+    /**
+     * Computes a random size for the next matrix to create. This method is overridden
+     * only by subclasses that test matrix implementations supporting arbitrary sizes.
+     *
+     * @param random The random number generator to use for computing a random matrix size.
+     */
+    void prepareNewMatrixSize(final Random random) {
     }
 
     /** Returns the number of rows of the matrix being tested.    */ abstract int getNumRow();
@@ -98,15 +155,27 @@ public abstract strictfp class MatrixTes
 
     /**
      * Verifies that the SIS matrix is equals to the JAMA one, up to the given tolerance value.
+     *
+     * @param expected  The JAMA matrix used as a reference implementation.
+     * @param actual    The SIS matrix to compare to JAMA.
+     * @param tolerance The tolerance threshold, usually either {@link #STRICT} or {@link #TOLERANCE}.
      */
     static void assertMatrixEquals(final Matrix expected, final MatrixSIS actual, final double tolerance) {
         final int numRow = actual.getNumRow();
         final int numCol = actual.getNumCol();
         assertEquals("numRow", expected.getRowDimension(),    numRow);
         assertEquals("numCol", expected.getColumnDimension(), numCol);
+        final String name = actual.getClass().getSimpleName();
         for (int j=0; j<numRow; j++) {
             for (int i=0; i<numCol; i++) {
-                assertEquals(expected.get(j,i), actual.getElement(j,i), tolerance);
+                final double e = expected.get(j,i);
+                final double a = actual.getElement(j,i);
+                assertEquals(name, e, a, tolerance);
+                if (tolerance != STRICT && statistics != null) {
+                    synchronized (statistics) {
+                        statistics.accept(StrictMath.abs(e - a));
+                    }
+                }
             }
         }
     }
@@ -134,6 +203,8 @@ public abstract strictfp class MatrixTes
      */
     @Test
     public void testGetElements() {
+        initialize(3812872376135347328L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final double[] elements = createRandomPositiveValues(numRow * numCol);
@@ -155,6 +226,8 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testGetElements")
     public void testSetElement() {
+        initialize(-8079924100564483073L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final MatrixSIS matrix = Matrices.createZero(numRow, numCol);
@@ -184,6 +257,8 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testSetElement")
     public void testIsIdentity() {
+        initialize(6173145457052452823L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final MatrixSIS matrix = Matrices.createDiagonal(numRow, numCol);
@@ -213,6 +288,8 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testSetElement")
     public void testCloneEquals() {
+        initialize(-4572234104840706847L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final double[] elements = createRandomPositiveValues(numRow * numCol);
@@ -241,6 +318,8 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testGetElements")
     public void testTranspose() {
+        initialize(585037875560696050L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final double[] elements = createRandomPositiveValues(numRow * numCol);
@@ -260,6 +339,8 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testGetElements")
     public void testNormalizeColumns() {
+        initialize(1549772118153010333L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final double[] elements = createRandomPositiveValues(numRow * numCol);
@@ -273,7 +354,7 @@ public abstract strictfp class MatrixTes
                 m += e*e;
             }
             m = StrictMath.sqrt(m);
-            assertEquals(1, m, TOLERANCE);
+            assertEquals(1, m, 1E-12);
         }
     }
 
@@ -283,9 +364,11 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testGetElements")
     public void testMultiply() {
-        final int numRow = getNumRow();
-        final int numCol = getNumCol();
+        initialize(2478887638739725150L);
         for (int n=0; n<NUMBER_OF_REPETITIONS; n++) {
+            prepareNewMatrixSize(random);
+            final int numRow = getNumRow();
+            final int numCol = getNumCol();
             double[] elements = createRandomPositiveValues(numRow * numCol);
             final MatrixSIS matrix = Matrices.create(numRow, numCol, elements);
             final Matrix reference = new Matrix(elements, numCol).transpose();
@@ -318,12 +401,11 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testMultiply")
     public void testSolve() throws NoninvertibleMatrixException {
-        final int numRow = getNumRow();
-        final int numCol = getNumCol();
-
-        if (numRow != 1 || numCol != 1) return; // Temporary limitation.
-
+        initialize(2108474073121762243L);
         for (int n=0; n<NUMBER_OF_REPETITIONS; n++) {
+            prepareNewMatrixSize(random);
+            final int numRow = getNumRow();
+            final int numCol = getNumCol();
             double[] elements = createRandomPositiveValues(numRow * numCol);
             final Matrix reference = new Matrix(elements, numCol).transpose();
             if (!(reference.det() >= DETERMINANT_THRESHOLD)) {
@@ -347,7 +429,7 @@ public abstract strictfp class MatrixTes
              */
             final Matrix referenceResult = reference.solve(referenceArg);
             final MatrixSIS matrixResult = matrix.solve(matrixArg);
-            assertMatrixEquals(referenceResult, matrixResult, TOLERANCE);
+            assertMatrixEquals(referenceResult, matrixResult, SolverTest.TOLERANCE);
         }
     }
 
@@ -360,9 +442,11 @@ public abstract strictfp class MatrixTes
     @Test
     @DependsOnMethod("testSolve")
     public void testInverse() throws NoninvertibleMatrixException {
-        final int numRow = getNumRow();
-        final int numCol = getNumCol();
+        initialize(-9063921123024549789L);
         for (int n=0; n<NUMBER_OF_REPETITIONS; n++) {
+            prepareNewMatrixSize(random);
+            final int numRow = getNumRow();
+            final int numCol = getNumCol();
             final double[] elements = createRandomPositiveValues(numRow * numCol);
             final Matrix reference = new Matrix(elements, numCol).transpose();
             if (!(reference.det() >= DETERMINANT_THRESHOLD)) {
@@ -378,6 +462,8 @@ public abstract strictfp class MatrixTes
      */
     @Test
     public void testSerialization() {
+        initialize(-3232759118744327281L);
+        prepareNewMatrixSize(random);
         final int numRow = getNumRow();
         final int numCol = getNumCol();
         final MatrixSIS matrix = Matrices.create(numRow, numCol, createRandomPositiveValues(numRow * numCol));

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/NonSquareMatrixTest.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -16,7 +16,10 @@
  */
 package org.apache.sis.referencing.operation.matrix;
 
+import java.util.Random;
 import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestUtilities;
+import org.junit.AfterClass;
 
 import static org.junit.Assert.*;
 
@@ -25,22 +28,31 @@ import static org.junit.Assert.*;
  * Tests the {@link NonSquareMatrix} implementation.
  * This class inherits all tests defined in {@link MatrixTestCase}.
  *
+ * <p>This class is expected to be the last {@code MatrixTestCase} subclass to be executed,
+ * because it sends the {@link #statistics} to {@link #out}. This condition is ensured if
+ * the tests are executed by {@link org.apache.sis.test.suite.ReferencingTestSuite}.
+ * However it is not a big deal if this condition is broken, as the only consequence
+ * is that reported statistics will be incomplete.</p>
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.4
  * @version 0.4
  * @module
  */
-@DependsOn(GeneralMatrixTest.class)
+@DependsOn(SolverTest.class)
 public final strictfp class NonSquareMatrixTest extends MatrixTestCase {
     /**
      * Number of rows and columns, initialized by {@link #initialize(String, boolean)}.
      */
-    private final int numRow, numCol;
+    private int numRow, numCol;
 
     /**
-     * Creates a test with a random size for the matrix and ensure that the matrix is not square.
+     * Computes a random size for the next matrix to create.
+     *
+     * @param random The random number generator to use.
      */
-    public NonSquareMatrixTest() {
+    @Override
+    void prepareNewMatrixSize(final Random random) {
         numRow = 5 + random.nextInt(8); // Matrix sizes from 5 to 12 inclusive.
         int n;
         do n = 5 + random.nextInt(8);
@@ -67,4 +79,27 @@ public final strictfp class NonSquareMat
     @org.junit.Ignore
     public void testInverse() throws NoninvertibleMatrixException {
     }
+
+    /**
+     * TODO: inverse transform not yet implemented for non-square matrix.
+     */
+    @Override
+    @org.junit.Ignore
+    public void testSolve() throws NoninvertibleMatrixException {
+    }
+
+    /**
+     * Prints the statistics about the differences between JAMA and SIS matrix elements.
+     * Those statistics will be visible only if {@link #verbose} is {@code true}.
+     */
+    @AfterClass
+    public static void printStatistics() {
+        if (statistics != null) {
+            TestUtilities.printSeparator("Overall statistics on agreement of matrix arithmetic");
+            synchronized (statistics) {
+                out.println(statistics);
+            }
+            TestUtilities.forceFlushOutput();
+        }
+    }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/SolverTest.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/SolverTest.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/SolverTest.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/matrix/SolverTest.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -18,6 +18,7 @@ package org.apache.sis.referencing.opera
 
 import java.util.Random;
 import Jama.Matrix;
+import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.TestCase;
 import org.junit.Test;
@@ -27,13 +28,36 @@ import org.junit.Test;
  * Tests the {@link Solver} class using <a href="http://math.nist.gov/javanumerics/jama">JAMA</a>
  * as the reference implementation.
  *
+ * {@section Cyclic dependency}
+ * 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)
  * @since   0.4
  * @version 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;
@@ -48,7 +72,7 @@ public final strictfp class SolverTest e
      * 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);
+        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++) {
@@ -66,7 +90,12 @@ public final strictfp class SolverTest e
      */
     @Test
     public void testSolve() throws NoninvertibleMatrixException {
-        final Random random = TestUtilities.createRandomNumberGenerator();
+        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);
@@ -80,8 +109,8 @@ public final strictfp class SolverTest e
                 out.println(e); // "Matrix is singular."
                 continue;
             }
-            final MatrixSIS U = Solver.solve(matrix, matrixArg, matrixArg.getNumCol());
-            MatrixTestCase.assertMatrixEquals(jama, U, MatrixTestCase.TOLERANCE);
+            final MatrixSIS U = Solver.solve(matrix, matrixArg);
+            MatrixTestCase.assertMatrixEquals(jama, U, TOLERANCE);
         }
     }
 }

Modified: sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java [UTF-8] (original)
+++ sis/trunk/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -31,13 +31,13 @@ import org.junit.BeforeClass;
  */
 @Suite.SuiteClasses({
     // Test matrix first because they may be used in about every SIS corners.
+    org.apache.sis.referencing.operation.matrix.GeneralMatrixTest.class,
     org.apache.sis.referencing.operation.matrix.SolverTest.class,
     org.apache.sis.referencing.operation.matrix.Matrix1Test.class,
     org.apache.sis.referencing.operation.matrix.Matrix2Test.class,
     org.apache.sis.referencing.operation.matrix.Matrix3Test.class,
     org.apache.sis.referencing.operation.matrix.Matrix4Test.class,
-    org.apache.sis.referencing.operation.matrix.GeneralMatrixTest.class,
-    org.apache.sis.referencing.operation.matrix.NonSquareMatrixTest.class,
+    org.apache.sis.referencing.operation.matrix.NonSquareMatrixTest.class, // Expected to be last MatrixTestCase - see javadoc.
     org.apache.sis.referencing.operation.matrix.MatricesTest.class,
     org.apache.sis.referencing.operation.matrix.AffineTransforms2DTest.class,
 

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/Context.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/Context.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/Context.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/Context.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -25,6 +25,9 @@ import org.apache.sis.util.Version;
 import org.apache.sis.util.Exceptions;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.logging.WarningListener;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Messages;
+import org.apache.sis.util.resources.IndexedResourceBundle;
 import org.apache.sis.xml.MarshalContext;
 import org.apache.sis.xml.ValueConverter;
 import org.apache.sis.xml.ReferenceResolver;
@@ -401,12 +404,42 @@ public final class Context extends Marsh
     }
 
     /**
+     * Convenience method for sending a warning for the given message from the {@link Errors} or {@link Messages}
+     * resources. The message will be logged at {@link Level#WARNING}.
+     *
+     * @param context   The current context, or {@code null} if none.
+     * @param source    The object that emitted a warning. Can not be null.
+     * @param classe    The class to declare as the warning source.
+     * @param method    The name of the method to declare as the warning source.
+     * @param resources Either {@code Errors.class} or {@code Messages.class}.
+     * @param key       The resource keys as one of the constants defined in the {@code Keys} inner class.
+     * @param arguments The arguments to be given to {@code MessageFormat} for formatting the log message.
+     */
+    public static void warningOccured(final Context context, final Object source, final Class<?> classe, final String method,
+            final Class<? extends IndexedResourceBundle> resources, final int key, final Object... arguments)
+    {
+        final Locale locale = context != null ? context.getLocale() : null;
+        final IndexedResourceBundle bundle;
+        if (resources == Errors.class) {
+            bundle = Errors.getResources(locale);
+        } else if (resources == Messages.class) {
+            bundle = Messages.getResources(locale);
+        } else {
+            throw new IllegalArgumentException(String.valueOf(resources));
+        }
+        final LogRecord warning = bundle.getLogRecord(Level.WARNING, key, arguments);
+        warning.setSourceClassName(classe.getCanonicalName());
+        warning.setSourceMethodName(method);
+        warningOccured(context, source, warning);
+    }
+
+    /**
      * Convenience method for sending a warning for the given exception.
      * The logger will be {@code "org.apache.sis.xml"}.
      *
      * @param context The current context, or {@code null} if none.
      * @param source  The object that emitted a warning. Can not be null.
-     * @param classe  The name of the class to declare as the warning source.
+     * @param classe  The class to declare as the warning source.
      * @param method  The name of the method to declare as the warning source.
      * @param cause   The exception which occurred.
      * @param warning {@code true} for {@link Level#WARNING}, or {@code false} for {@link Level#FILE}.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/GO_CharacterString.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -16,8 +16,6 @@
  */
 package org.apache.sis.internal.jaxb.gco;
 
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import javax.xml.bind.annotation.XmlType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlSeeAlso;
@@ -137,13 +135,8 @@ public class GO_CharacterString {
                 property  = type;
                 noset     = true;
             }
-            final Context context = Context.current();
-            final LogRecord record = Messages.getResources(context != null ? context.getLocale() : null)
-                    .getLogRecord(Level.WARNING, Messages.Keys.DiscardedExclusiveProperty_2,
-                                  NAMES[discarded], NAMES[property]);
-            record.setSourceClassName(getClass().getCanonicalName());
-            record.setSourceMethodName("setText");
-            Context.warningOccured(context, value, record);
+            Context.warningOccured(Context.current(), value, getClass(), "setText",
+                    Messages.class, Messages.Keys.DiscardedExclusiveProperty_2, NAMES[discarded], NAMES[property]);
             if (noset) {
                 return;
             }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/Measure.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/Measure.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/Measure.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/Measure.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -19,8 +19,6 @@ package org.apache.sis.internal.jaxb.gco
 import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import javax.measure.unit.Unit;
 import javax.measure.unit.NonSI;
 import javax.xml.bind.annotation.XmlValue;
@@ -165,11 +163,8 @@ public final class Measure {
      */
     public void setUnit(final Unit<?> newUnit) {
         if (unit != null && !unit.equals(newUnit)) {
-            final LogRecord record = Errors.getResources(null)
-                    .getLogRecord(Level.WARNING, Errors.Keys.IncompatiblePropertyValue_1, unit);
-            record.setSourceClassName(getClass().getName());
-            record.setSourceMethodName("setUnit");
-            Context.warningOccured(Context.current(), this, record);
+            Context.warningOccured(Context.current(), this, getClass(), "setUnit",
+                    Errors.class, Errors.Keys.IncompatiblePropertyValue_1, unit);
         }
         unit = newUnit;
     }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/ObjectReference.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/ObjectReference.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/ObjectReference.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/jaxb/gco/ObjectReference.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -17,8 +17,6 @@
 package org.apache.sis.internal.jaxb.gco;
 
 import java.util.UUID;
-import java.util.logging.Level;
-import java.util.logging.LogRecord;
 import org.apache.sis.xml.XLink;
 import org.apache.sis.xml.IdentifierMap;
 import org.apache.sis.xml.IdentifierSpace;
@@ -150,11 +148,8 @@ final class ObjectReference {
         if (value != null) {
             final T previous = map.putSpecialized(authority, value);
             if (previous != null && !previous.equals(value)) {
-                final LogRecord record = Errors.getResources((context != null) ? context.getLocale() : null)
-                        .getLogRecord(Level.WARNING, Errors.Keys.InconsistentAttribute_2, authority.getName(), value);
-                record.setSourceClassName(IdentifierMap.class.getName());
-                record.setSourceMethodName("putSpecialized");
-                Context.warningOccured(context, map, record);
+                Context.warningOccured(context, map, IdentifierMap.class, "putSpecialized",
+                        Errors.class, Errors.Keys.InconsistentAttribute_2, authority.getName(), value);
                 map.putSpecialized(authority, previous);
             }
         }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/system/Supervisor.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -21,7 +21,8 @@ import java.util.List;
 import java.util.Locale;
 import java.util.TimeZone;
 import java.util.ResourceBundle;
-import java.util.logging.Logger;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
 import javax.management.ObjectName;
 import javax.management.StandardMBean;
 import javax.management.MBeanServer;
@@ -32,12 +33,14 @@ import javax.management.MBeanParameterIn
 import javax.management.MBeanConstructorInfo;
 import javax.management.JMException;
 import javax.management.NotCompliantMBeanException;
+import javax.management.InstanceAlreadyExistsException;
 import java.lang.management.ManagementFactory;
 
 import org.apache.sis.setup.About;
 import org.apache.sis.util.Localized;
 import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.resources.Messages;
 import org.apache.sis.util.collection.TreeTable;
 
 
@@ -87,8 +90,13 @@ public final class Supervisor extends St
                 final ObjectName n = new ObjectName(NAME);
                 server.registerMBean(new Supervisor(null, null), n);
                 name = n; // Store only on success.
+            } catch (InstanceAlreadyExistsException e) {
+                final LogRecord record = Messages.getResources(null)
+                        .getLogRecord(Level.CONFIG, Messages.Keys.AlreadyRegistered_2, "MBean", NAME);
+                record.setLoggerName("org.apache.sis");
+                Logging.log(Supervisor.class, "register", record);
             } catch (Exception e) { // (SecurityException | JMException) on the JDK7 branch.
-                Logging.unexpectedException(Logger.getLogger("org.apache.sis"), Supervisor.class, "register", e);
+                Logging.unexpectedException(Logging.getLogger("org.apache.sis"), Supervisor.class, "register", e);
             }
         }
     }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedArrayList.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -17,9 +17,12 @@
 package org.apache.sis.internal.util;
 
 import java.util.List;
+import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import org.apache.sis.internal.jaxb.Context;
+import org.apache.sis.util.ArraysExt;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.CheckedContainer;
 
@@ -42,7 +45,7 @@ import static org.apache.sis.util.Argume
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-2.1)
- * @version 0.3
+ * @version 0.4
  * @module
  *
  * @see Collections#checkedList(List, Class)
@@ -90,30 +93,83 @@ public final class CheckedArrayList<E> e
     }
 
     /**
+     * Returns {@code true} if a unmarshalling process is under way.
+     * In the later case, logs a warning for non-null element of the wrong type.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-139">SIS-139</a>
+     */
+    static boolean warning(final Collection<?> source, final Object element, final Class<?> type) {
+        final Context context = Context.current();
+        if (context == null) {
+            return false;
+        }
+        if (element != null) {
+            Context.warningOccured(context, source, source.getClass(), "add",
+                    Errors.class, Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass());
+        }
+        return true;
+    }
+
+    /**
      * Ensures that the given element is non-null and assignable to the type
      * specified at construction time.
      *
      * @param  element the object to check, or {@code null}.
+     * @return {@code true} if the instance is valid, {@code false} if it shall be ignored.
      * @throws IllegalArgumentException if the specified element can not be added to this list.
      */
-    private void ensureValid(final E element) throws IllegalArgumentException {
-        if (!type.isInstance(element)) {
-            ensureNonNull("element", element);
-            throw new IllegalArgumentException(Errors.format(
-                    Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass()));
+    private boolean ensureValid(final E element) throws IllegalArgumentException {
+        if (type.isInstance(element)) {
+            return true;
         }
+        if (warning(this, element, type)) {
+            /*
+             * If a unmarshalling process is under way, silently discard null element.
+             * This case happen when a XML element for a collection contains no child.
+             * See https://issues.apache.org/jira/browse/SIS-139
+             */
+            return false;
+        }
+        ensureNonNull("element", element);
+        throw new IllegalArgumentException(Errors.format(
+                Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass()));
     }
 
     /**
      * Ensures that all elements of the given collection can be added to this list.
      *
      * @param  collection the collection to check, or {@code null}.
+     * @return The potentially filtered collection of elements to add.
      * @throws IllegalArgumentException if at least one element can not be added to this list.
      */
-    private void ensureValidCollection(final Collection<? extends E> collection) throws IllegalArgumentException {
-        for (final E element : collection) {
-            ensureValid(element);
+    @SuppressWarnings("unchecked")
+    private List<E> ensureValidCollection(final Collection<? extends E> collection) throws IllegalArgumentException {
+        int count = 0;
+        final Object[] array = collection.toArray();
+        for (int i=0; i<array.length; i++) {
+            final Object element = array[i];
+            if (ensureValid((E) element)) {
+                array[count++] = element;
+            }
         }
+        // Not-so-unsafe cast: we verified in the above loop that all elements are instance of E.
+        // The array itself may not be an instance of E[], but this is not important for Mediator.
+        return new Mediator<E>(ArraysExt.resize((E[]) array, count));
+    }
+
+    /**
+     * A wrapper around the given array for use by {@link #addAll(Collection)} only.  This wrapper violates
+     * some {@link List} method contracts, so it must really be used only as a temporary object for passing
+     * the array to {@code AbstractList.addAll(…)} implementation. In particular {@link #toArray()} returns
+     * directly the internal array, because this is the method to be invoked by {@code addAll(…)}  (this is
+     * actually the only important method for this wrapper).
+     */
+    private static final class Mediator<E> extends AbstractList<E> {
+        private final E[] array;
+        Mediator(final E[] array)           {this.array = array;}
+        @Override public int size()         {return array.length;}
+        @Override public E   get(int index) {return array[index];}
+        @Override public E[] toArray()      {return array;} // See class javadoc.
     }
 
     /**
@@ -127,8 +183,10 @@ public final class CheckedArrayList<E> e
      */
     @Override
     public E set(final int index, final E element) throws IllegalArgumentException {
-        ensureValid(element);
-        return super.set(index, element);
+        if (ensureValid(element)) {
+            return super.set(index, element);
+        }
+        return get(index);
     }
 
     /**
@@ -140,8 +198,10 @@ public final class CheckedArrayList<E> e
      */
     @Override
     public boolean add(final E element) throws IllegalArgumentException {
-        ensureValid(element);
-        return super.add(element);
+        if (ensureValid(element)) {
+            return super.add(element);
+        }
+        return false;
     }
 
     /**
@@ -154,8 +214,9 @@ public final class CheckedArrayList<E> e
      */
     @Override
     public void add(final int index, final E element) throws IllegalArgumentException {
-        ensureValid(element);
-        super.add(index, element);
+        if (ensureValid(element)) {
+            super.add(index, element);
+        }
     }
 
     /**
@@ -168,8 +229,7 @@ public final class CheckedArrayList<E> e
      */
     @Override
     public boolean addAll(final Collection<? extends E> collection) throws IllegalArgumentException {
-        ensureValidCollection(collection);
-        return super.addAll(collection);
+        return super.addAll(ensureValidCollection(collection));
     }
 
     /**
@@ -183,7 +243,6 @@ public final class CheckedArrayList<E> e
      */
     @Override
     public boolean addAll(final int index, final Collection<? extends E> collection) throws IllegalArgumentException {
-        ensureValidCollection(collection);
-        return super.addAll(index, collection);
+        return super.addAll(index, ensureValidCollection(collection));
     }
 }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/CheckedHashSet.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -41,7 +41,7 @@ import static org.apache.sis.util.Argume
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-2.1)
- * @version 0.3
+ * @version 0.4
  * @module
  *
  * @see Collections#checkedSet(Set, Class)
@@ -98,6 +98,14 @@ public final class CheckedHashSet<E> ext
     @Override
     public boolean add(final E element) throws IllegalArgumentException {
         if (!type.isInstance(element)) {
+            if (CheckedArrayList.warning(this, element, type)) {
+                /*
+                 * If a unmarshalling process is under way, silently discard null element.
+                 * This case happen when a XML element for a collection contains no child.
+                 * See https://issues.apache.org/jira/browse/SIS-139
+                 */
+                return false;
+            }
             ensureNonNull("element", element);
             throw new IllegalArgumentException(Errors.format(
                     Errors.Keys.IllegalArgumentClass_3, "element", type, element.getClass()));

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/UnmodifiableArrayList.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/UnmodifiableArrayList.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/UnmodifiableArrayList.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/internal/util/UnmodifiableArrayList.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -358,6 +358,15 @@ public class UnmodifiableArrayList<E> ex
     }
 
     /**
+     * Returns a copy of the backing array. Note that the array type is {@code E[]} rather than {@code Object[]}.
+     * This is not what {@code ArrayList} does, but is not forbidden by {@link List#toArray()} javadoc neither.
+     */
+    @Override
+    public E[] toArray() {
+        return array.clone();
+    }
+
+    /**
      * Compares this list with the given object for equality.
      *
      * @param  object The object to compare with this list.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -27,6 +27,7 @@ import static java.lang.Float.intBitsToF
 import static java.lang.Float.floatToRawIntBits;
 import static java.lang.Double.longBitsToDouble;
 import static java.lang.Double.doubleToRawLongBits;
+import org.apache.sis.internal.util.DoubleDouble;
 import static org.apache.sis.internal.util.Numerics.SIGN_BIT_MASK;
 
 
@@ -173,27 +174,32 @@ public final class MathFunctions extends
         int i = vector.length;
 
         // If every elements in the array are zero, returns zero.
-        double sum;
+        double v1;
         do if (i == 0) return 0;
-        while ((sum = vector[--i]) == 0);
+        while ((v1 = vector[--i]) == 0);
 
         // We have found a non-zero element. If it is the only one, returns it directly.
-        double v;
-        do if (i == 0) return Math.abs(sum);
-        while ((v = vector[--i]) == 0);
-
-        // If there is exactly 2 elements, use Math.hypot which is more robust than our algorithm.
         double v2;
-        do if (i == 0) return Math.hypot(sum, v);
+        do if (i == 0) return Math.abs(v1);
         while ((v2 = vector[--i]) == 0);
 
-        // Usual magnitude computation.
-        sum = sum*sum + v*v + v2*v2;
+        // If there is exactly 2 elements, use Math.hypot which is more robust than our algorithm.
+        double v3;
+        do if (i == 0) return Math.hypot(v1, v2);
+        while ((v3 = vector[--i]) == 0);
+
+        // Usual magnitude computation, but using double-double arithmetic.
+        final DoubleDouble sum = new DoubleDouble();
+        final DoubleDouble dot = new DoubleDouble();
+        sum.setToProduct(v1, v1);
+        dot.setToProduct(v2, v2); sum.add(dot);
+        dot.setToProduct(v3, v3); sum.add(dot);
         while (i != 0) {
-            v = vector[--i];
-            sum += v*v;
+            v1 = vector[--i];
+            dot.setToProduct(v1, v1);
+            sum.add(dot);
         }
-        return Math.sqrt(sum);
+        return Math.sqrt(sum.value);
     }
 
     /**

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/ArraysExt.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -1715,6 +1715,24 @@ public final class ArraysExt extends Sta
     }
 
     /**
+     * Swaps the elements at the given indices in the given array of {@code Object} values.
+     *
+     * {@note While trivial, this method is provided because its need occurs relatively often
+     *        and the availability of a <code>swap</code> method makes the code easier to read.}
+     *
+     * @param data The array in which to swap elements.
+     * @param i0   Index of one element to be swapped.
+     * @param i1   Index of the other element to be swapped.
+     *
+     * @since 0.4
+     */
+    public static void swap(final Object[] data, final int i0, final int i1) {
+        final Object t = data[i0];
+        data[i0] = data[i1];
+        data[i1] = t;
+    }
+
+    /**
      * Swaps the elements at the given indices in the given array of {@code double} values.
      *
      * {@note While trivial, this method is provided because its need occurs relatively often

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/logging/Logging.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -47,7 +47,7 @@ import org.apache.sis.util.Classes;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-2.4)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final class Logging extends Static {
@@ -178,13 +178,14 @@ public final class Logging extends Stati
      * This convenience method performs the following steps:
      *
      * <ul>
-     *   <li>Get the logger using {@link #getLogger(Class)};</li>
+     *   <li>Unconditionally {@linkplain LogRecord#setSourceClassName(String) set the source class name}
+     *       to the {@linkplain Class#getCanonicalName() canonical name} of the given class;</li>
+     *   <li>Unconditionally {@linkplain LogRecord#setSourceMethodName(String) set the source method name}
+     *       to the given value;</li>
+     *   <li>Get the logger for the {@linkplain LogRecord#getLoggerName() logger name} if specified,
+     *       or using {@link #getLogger(Class)} otherwise;</li>
      *   <li>{@linkplain LogRecord#setLoggerName(String) Set the logger name} of the given record,
      *       if not already set;</li>
-     *   <li>Unconditionally {@linkplain LogRecord#setSourceClassName(String) set the source class
-     *       name} to the {@linkplain Class#getCanonicalName() canonical name} of the given class;</li>
-     *   <li>Unconditionally {@linkplain LogRecord#setSourceMethodName(String) set the source method
-     *       name} to the given value;</li>
      *   <li>{@linkplain Logger#log(LogRecord) Log} the modified record.</li>
      * </ul>
      *
@@ -195,9 +196,13 @@ public final class Logging extends Stati
     public static void log(final Class<?> classe, final String method, final LogRecord record) {
         record.setSourceClassName(classe.getCanonicalName());
         record.setSourceMethodName(method);
-        final Logger logger = getLogger(classe);
-        if (record.getLoggerName() == null) {
+        final String loggerName = record.getLoggerName();
+        final Logger logger;
+        if (loggerName == null) {
+            logger = getLogger(classe);
             record.setLoggerName(logger.getName());
+        } else {
+            logger = getLogger(loggerName);
         }
         logger.log(record);
     }

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -456,6 +456,11 @@ public final class Errors extends Indexe
         public static final int NonInvertibleConversion = 82;
 
         /**
+         * Non invertible {0}×{1} matrix.
+         */
+        public static final int NonInvertibleMatrix_2 = 124;
+
+        /**
          * Transform is not invertible.
          */
         public static final int NonInvertibleTransform = 83;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties [ISO-8859-1] Fri Oct  4 19:08:18 2013
@@ -98,6 +98,7 @@ NodeIsLeaf_1                    = Node \
 NodeNotFound_1                  = No \u201c{0}\u201d node found.
 NonEquilibratedParenthesis_2    = Missing a \u2018{1}\u2019 parenthesis in \u201c{0}\u201d.
 NonInvertibleConversion         = Conversion is not invertible.
+NonInvertibleMatrix_2           = Non invertible {0}\u00d7{1} matrix.
 NonInvertibleTransform          = Transform is not invertible.
 NonAngularUnit_1                = \u201c{0}\u201d is not an angular unit.
 NonLinearUnit_1                 = \u201c{0}\u201d is not a linear unit.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties [ISO-8859-1] Fri Oct  4 19:08:18 2013
@@ -88,6 +88,7 @@ NodeIsLeaf_1                    = Le n\u
 NodeNotFound_1                  = Aucun n\u0153ud \u201c{0}\u201d n\u2019a \u00e9t\u00e9 trouv\u00e9.
 NonEquilibratedParenthesis_2    = Il manque une parenth\u00e8se \u2018{1}\u2019 dans \u201c{0}\u201d.
 NonInvertibleConversion         = La conversion n\u2019est pas inversible.
+NonInvertibleMatrix_2           = Matrice {0}\u00d7{1} non inversible.
 NonInvertibleTransform          = La transformation n\u2019est pas inversible.
 NonAngularUnit_1                = \u201c{0}\u201d n\u2019est pas une unit\u00e9 d\u2019angles.
 NonLinearUnit_1                 = \u201c{0}\u201d n\u2019est pas une unit\u00e9 de longueurs.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -55,6 +55,11 @@ public final class Messages extends Inde
         }
 
         /**
+         * {0} “{1}” is already registered. The second instance will be ignored.
+         */
+        public static final int AlreadyRegistered_2 = 5;
+
+        /**
          * Changed the container capacity from {0} to {1} elements.
          */
         public static final int ChangedContainerCapacity_2 = 0;

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages.properties [ISO-8859-1] Fri Oct  4 19:08:18 2013
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+AlreadyRegistered_2             = {0} \u201c{1}\u201d is already registered. The second instance will be ignored.
 ChangedContainerCapacity_2      = Changed the container capacity from {0} to {1} elements.
 DiscardedExclusiveProperty_2    = Property \u201c{0}\u201d has been discarded in favor of \u201c{1}\u201d, because those two properties are mutually exclusive.
 PropertyHiddenBy_2              = Property \u201c{0}\u201d is hidden by \u201c{1}\u201d.

Modified: sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties [ISO-8859-1] (original)
+++ sis/trunk/core/sis-utility/src/main/java/org/apache/sis/util/resources/Messages_fr.properties [ISO-8859-1] Fri Oct  4 19:08:18 2013
@@ -14,6 +14,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+AlreadyRegistered_2             = Le {0} \u201c{1}\u201d est d\u00e9j\u00e0 inscrit dans le registre. La seconde instance sera ignor\u00e9e.
 ChangedContainerCapacity_2      = Changement de la capacit\u00e9 du conteneur de {0} vers {1} \u00e9l\u00e9ments.
 DiscardedExclusiveProperty_2    = La propri\u00e9t\u00e9 \u201c{0}\u201d a \u00e9t\u00e9 \u00e9cart\u00e9e en faveur de \u201c{1}\u201d, parce que ces deux propri\u00e9t\u00e9s sont mutuellement exclusives.
 PropertyHiddenBy_2              = La propri\u00e9t\u00e9 \u201c{0}\u201d est masqu\u00e9e par \u201c{1}\u201d.

Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/TestUtilities.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -44,7 +44,7 @@ import static org.junit.Assert.*;
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.16)
- * @version 0.3
+ * @version 0.4
  * @module
  */
 public final strictfp class TestUtilities extends Static {
@@ -90,6 +90,17 @@ public final strictfp class TestUtilitie
     }
 
     /**
+     * Prints and clear the current content of {@link TestCase#out}, regardless of whether
+     * {@link TestCase#verbose} is {@code true} or {@code false}. This method should rarely
+     * be needed.
+     *
+     * @since 0.4
+     */
+    public static void forceFlushOutput() {
+        TestCase.flushOutput();
+    }
+
+    /**
      * If verbose output are enabled, prints the given title to {@link TestCase#out} in a box.
      * This method is invoked for writing a clear visual separator between the verbose output
      * of different test cases. This method does nothing if verbose output is not enabled,

Modified: sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1529262&r1=1529261&r2=1529262&view=diff
==============================================================================
--- sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java [UTF-8] (original)
+++ sis/trunk/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java [UTF-8] Fri Oct  4 19:08:18 2013
@@ -58,9 +58,11 @@ import org.junit.BeforeClass;
     org.apache.sis.math.StatisticsFormatTest.class,
     org.apache.sis.internal.util.UtilitiesTest.class,
     org.apache.sis.internal.util.NumericsTest.class,
+    org.apache.sis.internal.util.DoubleDoubleTest.class,
     org.apache.sis.internal.jdk8.JDK8Test.class,
 
     // Collections.
+    org.apache.sis.internal.util.CheckedArrayListTest.class,
     org.apache.sis.internal.system.ReferenceQueueConsumerTest.class,
     org.apache.sis.util.collection.WeakHashSetTest.class,
     org.apache.sis.util.collection.WeakValueHashMapTest.class,