| /* |
| * 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.Arrays; |
| import java.io.Serializable; |
| import java.awt.geom.AffineTransform; // For javadoc |
| import org.opengis.referencing.operation.Matrix; |
| import org.apache.sis.internal.referencing.ExtendedPrecisionMatrix; |
| import org.apache.sis.internal.util.DoubleDouble; |
| import org.apache.sis.internal.util.Numerics; |
| import org.apache.sis.util.ArgumentChecks; |
| import org.apache.sis.util.ComparisonMode; |
| import org.apache.sis.util.LenientComparable; |
| import org.apache.sis.util.resources.Errors; |
| |
| |
| /** |
| * A {@link Matrix} able to perform some operations of interest to Spatial Information Systems (SIS). |
| * This class completes the GeoAPI {@link Matrix} interface with some operations used by {@code sis-referencing}. |
| * It is not a {@code MatrixSIS} goal to provide all possible Matrix operations, as there is too many of them. |
| * This class focuses on: |
| * |
| * <ul> |
| * <li>Only the basic matrix operations needed for <cite>referencing by coordinates</cite>: |
| * <ul> |
| * <li>{@link #isIdentity()}</li> |
| * <li>{@link #multiply(Matrix)}</li> |
| * <li>{@link #inverse()}</li> |
| * <li>{@link #transpose()}</li> |
| * </ul> |
| * </li><li>Other operations which are not general-purpose matrix operations, |
| * but are needed in the context of referencing by coordinates: |
| * <ul> |
| * <li>{@link #isAffine()}</li> |
| * <li>{@link #normalizeColumns()}</li> |
| * <li>{@link #convertBefore(int, Number, Number)}</li> |
| * <li>{@link #convertAfter(int, Number, Number)}</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * |
| * @author Martin Desruisseaux (IRD, Geomatys) |
| * @version 1.0 |
| * |
| * @see Matrices |
| * |
| * @since 0.4 |
| * @module |
| */ |
| public abstract class MatrixSIS implements Matrix, LenientComparable, Cloneable, Serializable { |
| /** |
| * For cross-version compatibility. |
| */ |
| private static final long serialVersionUID = 3075280376118406219L; |
| |
| /** |
| * For sub-class constructors. |
| */ |
| protected MatrixSIS() { |
| } |
| |
| /** |
| * Ensures that the given array is non-null and has the expected length. |
| * This is a convenience method for subclasses constructors. |
| * |
| * @throws IllegalArgumentException if the given array does not have the expected length. |
| */ |
| static void ensureLengthMatch(final int expected, final double[] elements) throws IllegalArgumentException { |
| ArgumentChecks.ensureNonNull("elements", elements); |
| if (elements.length != expected) { |
| throw new IllegalArgumentException(Errors.format( |
| Errors.Keys.UnexpectedArrayLength_2, expected, elements.length)); |
| } |
| } |
| |
| /** |
| * Ensures that the given matrix has the given dimension. |
| * This is a convenience method for subclasses. |
| */ |
| static void ensureSizeMatch(final int numRow, final int numCol, final Matrix matrix) |
| throws MismatchedMatrixSizeException |
| { |
| final int othRow = matrix.getNumRow(); |
| final int othCol = matrix.getNumCol(); |
| if (numRow != othRow || numCol != othCol) { |
| throw new MismatchedMatrixSizeException(Errors.format( |
| Errors.Keys.MismatchedMatrixSize_4, numRow, numCol, othRow, othCol)); |
| } |
| } |
| |
| /** |
| * Ensures that the number of rows of a given matrix matches the given value. |
| * This is a convenience method for {@link #multiply(Matrix)} implementations. |
| * |
| * @param expected the expected number of rows. |
| * @param actual the actual number of rows in the matrix to verify. |
| * @param numCol the number of columns to report in case of errors. This is an arbitrary |
| * value and have no incidence on the verification performed by this method. |
| */ |
| static void ensureNumRowMatch(final int expected, final int actual, final int numCol) { |
| if (actual != expected) { |
| throw new MismatchedMatrixSizeException(Errors.format( |
| Errors.Keys.MismatchedMatrixSize_4, expected, "⒩", actual, numCol)); |
| } |
| } |
| |
| /** |
| * Returns an exception for the given indices. |
| */ |
| static IndexOutOfBoundsException indexOutOfBounds(final int row, final int column) { |
| return new IndexOutOfBoundsException(Errors.format(Errors.Keys.IndicesOutOfBounds_2, row, column)); |
| } |
| |
| /** |
| * Casts or copies the given matrix to a SIS implementation. If {@code matrix} is already |
| * an instance of {@code MatrixSIS}, then it is returned unchanged. Otherwise all elements |
| * are copied in a new {@code MatrixSIS} object. |
| * |
| * @param matrix the matrix to cast or copy, or {@code null}. |
| * @return the matrix argument if it can be safely casted (including {@code null} argument), |
| * or a copy of the given matrix otherwise. |
| * |
| * @see Matrices#copy(Matrix) |
| */ |
| public static MatrixSIS castOrCopy(final Matrix matrix) { |
| if (matrix == null || matrix instanceof MatrixSIS) { |
| return (MatrixSIS) matrix; |
| } |
| return Matrices.copy(matrix); |
| } |
| |
| /** |
| * Stores the value at the specified row and column in the given {@code dd} object. |
| * This method does not need to verify argument validity. |
| */ |
| void get(final int row, final int column, final DoubleDouble dd) { |
| dd.value = getElement(row, column); |
| dd.error = DoubleDouble.errorForWellKnownValue(dd.value); |
| } |
| |
| /** |
| * Stores the value of the given {@code dd} object at the specified row and column. |
| * This method does not need to verify argument validity. |
| * |
| * @throws UnsupportedOperationException if this matrix is unmodifiable. |
| */ |
| void set(final int row, final int column, final DoubleDouble dd) { |
| setElement(row, column, dd.doubleValue()); |
| } |
| |
| /** |
| * Retrieves the value at the specified row and column of this matrix, wrapped in a {@code Number}. |
| * The {@code Number} type depends on the matrix accuracy. |
| * |
| * @param row the row index, from 0 inclusive to {@link #getNumRow()} exclusive. |
| * @param column the column index, from 0 inclusive to {@link #getNumCol()} exclusive. |
| * @return the current value at the given row and column. |
| */ |
| public Number getNumber(int row, int column) { |
| return getElement(row, column); |
| } |
| |
| /** |
| * Modifies the value at the specified row and column of this matrix. |
| * This method is the converses of {@link #getNumber(int, int)}. |
| * |
| * @param row the row index, from 0 inclusive to {@link #getNumRow()} exclusive. |
| * @param column the column index, from 0 inclusive to {@link #getNumCol()} exclusive. |
| * @param value the new matrix element value. |
| * |
| * @see #setElement(int, int, double) |
| * |
| * @since 0.8 |
| */ |
| public void setNumber(int row, int column, final Number value) { |
| if (value instanceof DoubleDouble) { |
| set(row, column, (DoubleDouble) value); |
| } else if (DoubleDouble.shouldConvert(value)) { |
| set(row, column, new DoubleDouble(value)); |
| } else { |
| setElement(row, column, value.doubleValue()); |
| } |
| } |
| |
| /** |
| * Retrieves the value at the specified row and column of this matrix. |
| * |
| * @param row the row index, from 0 inclusive to {@link #getNumRow()} exclusive. |
| * @param column the column index, from 0 inclusive to {@link #getNumCol()} exclusive. |
| * @return the current value at the given row and column. |
| */ |
| @Override |
| public abstract double getElement(int row, int column); |
| |
| /** |
| * Returns a copy of all matrix elements in a flat, row-major (column indices vary fastest) array. |
| * The array length is <code>{@linkplain #getNumRow()} * {@linkplain #getNumCol()}</code>. |
| * |
| * @return a copy of all current matrix elements in a row-major array. |
| */ |
| public double[] getElements() { |
| final int numRow = getNumRow(); |
| final int numCol = getNumCol(); |
| final double[] elements = new double[numRow * numCol]; |
| for (int k=0,j=0; j<numRow; j++) { |
| for (int i=0; i<numCol; i++) { |
| elements[k++] = getElement(j, i); |
| } |
| } |
| return elements; |
| } |
| |
| /** |
| * Stores all matrix elements in the given flat array. This method does not verify the array length. |
| * All subclasses in this {@code org.apache.sis.referencing.operation.matrix} package override this |
| * method with a more efficient implementation. |
| * |
| * @param dest the destination array. May be longer than necessary (this happen when the caller needs to |
| * append {@link org.apache.sis.internal.util.DoubleDouble#error} values after the elements). |
| */ |
| void getElements(final double[] dest) { |
| final double[] elements = getElements(); |
| System.arraycopy(elements, 0, dest, 0, elements.length); |
| } |
| |
| /** |
| * Copies the elements of the given matrix in the given array. |
| * This method ignores the error terms, if any. |
| * |
| * @param matrix the matrix to copy. |
| * @param numRow {@code matrix.getNumRow()}. |
| * @param numCol {@code matrix.getNumCol()}. |
| * @param elements where to copy the elements. |
| */ |
| static void getElements(final Matrix matrix, final int numRow, final int numCol, final double[] elements) { |
| if (matrix instanceof MatrixSIS) { |
| ((MatrixSIS) matrix).getElements(elements); |
| } else { |
| for (int k=0,j=0; j<numRow; j++) { |
| for (int i=0; i<numCol; i++) { |
| elements[k++] = matrix.getElement(j, i); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets all matrix elements from a flat, row-major (column indices vary fastest) array. |
| * The array length shall be <code>{@linkplain #getNumRow()} * {@linkplain #getNumCol()}</code>. |
| * |
| * @param elements The new matrix elements in a row-major array. |
| * @throws IllegalArgumentException if the given array does not have the expected length. |
| * @throws UnsupportedOperationException if this matrix is unmodifiable. |
| * |
| * @see Matrices#create(int, int, double[]) |
| */ |
| public abstract void setElements(final double[] elements); |
| |
| /** |
| * Sets elements in a sub-region of this matrix, optionally including error terms. |
| * |
| * @param source row-major values as given by {@link ExtendedPrecisionMatrix#getExtendedElements()}. |
| * @param length number of elements ({@code numRow} × {@code numCol}) in the source matrix, not including error terms. |
| * @param stride number of columns in the source matrix, used for computing indices in {@code source} array. |
| * @param srcRow index of the first row from the {@code source} to copy in {@code this}. |
| * @param srcCol index of the first column from the {@code source} to copy in {@code this}. |
| * @param dstRow index of the first row in {@code this} where to copy the {@code source} values. |
| * @param dstCol index of the first column in {@code this} where to copy the {@code source} values. |
| * @param numRow number of rows to copy. |
| * @param numCol number of columns to copy. |
| * @param transfer if both {@code source} and {@code this} use extended precision, |
| * the temporary object to use for transferring values. Otherwise {@code null}. |
| */ |
| final void setElements(final double[] source, final int length, final int stride, final DoubleDouble transfer, |
| int srcRow, final int srcCol, |
| int dstRow, final int dstCol, |
| int numRow, final int numCol) |
| { |
| while (--numRow >= 0) { |
| final int valueOffset = srcRow*stride + srcCol; |
| for (int i=0; i<numCol; i++) { |
| if (transfer != null) { |
| transfer.setFrom(source, valueOffset + i, length); |
| set(dstRow, dstCol + i, transfer); |
| } else { |
| setElement(dstRow, dstCol + i, source[valueOffset + i]); |
| } |
| } |
| srcRow++; |
| dstRow++; |
| } |
| } |
| |
| /** |
| * Sets this matrix to the values of another matrix. |
| * The given matrix must have the same size. |
| * |
| * @param matrix the matrix to copy. |
| * @throws MismatchedMatrixSizeException if the given matrix has a different size than this matrix. |
| * |
| * @since 0.7 |
| */ |
| public void setMatrix(final Matrix matrix) throws MismatchedMatrixSizeException { |
| ArgumentChecks.ensureNonNull("matrix", matrix); |
| final int numRow = getNumRow(); |
| final int numCol = getNumCol(); |
| ensureSizeMatch(numRow, numCol, matrix); |
| final int count = numRow * numCol; |
| final double[] elements; |
| /* |
| * If both matrices use extended precision, the elements array will have twice the expected length |
| * with the matrix values in the first half and the error terms in the second half. If we want to |
| * preserve the extended precision, we have to transfer the values between the two matrices with a |
| * DoubleDouble object. |
| */ |
| if (isExtendedPrecision() && matrix instanceof ExtendedPrecisionMatrix) { |
| elements = ((ExtendedPrecisionMatrix) matrix).getExtendedElements(); |
| if (elements.length > count) { |
| final DoubleDouble t = new DoubleDouble(); |
| for (int i=0; i<count; i++) { |
| t.value = elements[i]; |
| t.error = elements[i + count]; |
| set(i / numCol, i % numCol, t); |
| } |
| return; |
| } |
| } else { |
| // Fallback for matrices that do not use extended precision. |
| elements = new double[count]; |
| getElements(matrix, numRow, numCol, elements); |
| } |
| setElements(elements); |
| } |
| |
| /** |
| * Returns {@code true} if this matrix uses extended precision. |
| */ |
| boolean isExtendedPrecision() { |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if this matrix represents an affine transform. |
| * A transform is affine if the matrix is square and its last row contains |
| * only zeros, except in the last column which contains 1. |
| * |
| * @return {@code true} if this matrix represents an affine transform. |
| * |
| * @see Matrices#isAffine(Matrix) |
| * @see org.apache.sis.referencing.operation.transform.LinearTransform#isAffine() |
| */ |
| public boolean isAffine() { |
| return isAffine(this); |
| } |
| |
| /** |
| * Fallback for matrix of unknown implementation. |
| */ |
| static boolean isAffine(final Matrix matrix) { |
| int j = matrix.getNumRow(); |
| int i = matrix.getNumCol(); |
| if (i != j--) { |
| return false; // Matrix is not square. |
| } |
| double e = 1; |
| while (--i >= 0) { |
| if (matrix.getElement(j, i) != e) { |
| return false; |
| } |
| e = 0; |
| } |
| return true; |
| } |
| |
| /** |
| * Returns {@code true} if this matrix is an identity matrix. |
| * This method is equivalent to the following code, except that it is potentially more efficient: |
| * |
| * {@preformat java |
| * return Matrices.isIdentity(this, 0.0); |
| * } |
| * |
| * @return {@code true} if this matrix is an identity matrix. |
| * |
| * @see Matrices#isIdentity(Matrix, double) |
| * @see AffineTransform#isIdentity() |
| */ |
| @Override |
| public abstract boolean isIdentity(); |
| |
| /** |
| * Sets the value of this matrix to its transpose. |
| * |
| * @throws UnsupportedOperationException if this matrix is unmodifiable. |
| */ |
| public abstract void transpose(); |
| |
| /** |
| * Normalizes all columns in-place. Each columns in this matrix is considered as a vector. |
| * For each column (vector), this method computes the magnitude (vector length) as the square |
| * root of the sum of all square values. Then, all values in the column are divided by that |
| * magnitude. |
| * |
| * <p>This method is useful when the matrix is a |
| * {@linkplain org.opengis.referencing.operation.MathTransform#derivative transform derivative}. |
| * In such matrix, each column is a vector representing the displacement in target space when an |
| * coordinate in the source space is increased by one. Invoking this method turns those vectors |
| * into unitary vectors, which is useful for forming the basis of a new coordinate system.</p> |
| * |
| * @throws UnsupportedOperationException if this matrix is unmodifiable. |
| */ |
| public void normalizeColumns() { |
| final int numRow = getNumRow(); |
| final int numCol = getNumCol(); |
| final DoubleDouble sum = new DoubleDouble(); |
| final DoubleDouble dot = new DoubleDouble(); |
| final DoubleDouble tmp = new DoubleDouble(); |
| for (int i=0; i<numCol; i++) { |
| sum.clear(); |
| for (int j=0; j<numRow; j++) { |
| get(j, i, dot); |
| dot.square(); |
| sum.add(dot); |
| } |
| sum.sqrt(); |
| for (int j=0; j<numRow; j++) { |
| get(j, i, tmp); |
| dot.setFrom(sum); |
| dot.inverseDivide(tmp); |
| set(j, i, dot); |
| } |
| } |
| } |
| |
| /** |
| * Assuming that this matrix represents an affine transform, concatenates a scale and a translation on the |
| * given dimension. Converting a point with the resulting matrix is equivalent to first convert the point |
| * with {@code coordinates[srcDim] = coordinates[srcDim] * scale + offset}, then apply the original matrix. |
| * |
| * <div class="section">Equivalence between this method and Java2D {@code AffineTransform} methods</div> |
| * If this matrix was an instance of Java2D {@link AffineTransform}, then invoking this method would |
| * be equivalent to invoke the following {@code AffineTransform} methods in the order shown below: |
| * |
| * <table class="sis"> |
| * <caption>Equivalence between this method and AffineTransform methods</caption> |
| * <tr> |
| * <th>{@code MatrixSIS} method</th> |
| * <th class="sep">{@code AffineTransform} methods</th> |
| * </tr><tr> |
| * <td>{@code concatenate(0, scale, offset)}</td> |
| * <td class="sep"><code>at.{@linkplain AffineTransform#translate(double, double) translate}(offset, 0); |
| * at.{@linkplain AffineTransform#scale(double, double) scale}(scale, 1);</code></td> |
| * </tr><tr> |
| * <td class="hsep">{@code concatenate(1, scale, offset)}</td> |
| * <td class="hsep sep"><code>at.{@linkplain AffineTransform#translate(double, double) translate}(0, offset); |
| * at.{@linkplain AffineTransform#scale(double, double) scale}(1, scale);</code></td> |
| * </tr> |
| * </table> |
| * |
| * @param srcDim the dimension of the coordinate to rescale in the source coordinates. |
| * @param scale the amount by which to multiply the source coordinate value before to apply the transform, or {@code null} if none. |
| * @param offset the amount by which to translate the source coordinate value before to apply the transform, or {@code null} if none. |
| * @throws UnsupportedOperationException if this matrix is unmodifiable. |
| * |
| * @see AffineTransform#concatenate(AffineTransform) |
| * |
| * @since 0.6 |
| */ |
| public void convertBefore(final int srcDim, final Number scale, final Number offset) { |
| final int lastCol = getNumCol() - 1; |
| ArgumentChecks.ensureValidIndex(lastCol, srcDim); |
| final DoubleDouble s = new DoubleDouble(); |
| final DoubleDouble t = new DoubleDouble(); |
| for (int j = getNumRow(); --j >= 0;) { |
| if (offset != null) { |
| get(j, srcDim, s); // Scale factor |
| get(j, lastCol, t); // Translation factor |
| s.multiplyGuessError(offset); |
| t.add(s); |
| set(j, lastCol, t); |
| } |
| if (scale != null) { |
| get(j, srcDim, s); // Scale factor |
| s.multiplyGuessError(scale); |
| set(j, srcDim, s); |
| } |
| } |
| } |
| |
| /** |
| * Assuming that this matrix represents an affine transform, pre-concatenates a scale and a translation on the |
| * given dimension. Converting a point with the resulting matrix is equivalent to first convert the point with |
| * the original matrix, then convert the result with {@code coordinates[tgtDim] = coordinates[tgtDim] * scale + offset}. |
| * |
| * @param tgtDim the dimension of the coordinate to rescale in the target coordinates. |
| * @param scale the amount by which to multiply the target coordinate value after this transform, or {@code null} if none. |
| * @param offset the amount by which to translate the target coordinate value after this transform, or {@code null} if none. |
| * @throws UnsupportedOperationException if this matrix is unmodifiable. |
| * |
| * @see AffineTransform#preConcatenate(AffineTransform) |
| * |
| * @since 0.6 |
| */ |
| public void convertAfter(final int tgtDim, final Number scale, final Number offset) { |
| final int lastRow = getNumRow() - 1; |
| final int lastCol = getNumCol() - 1; |
| ArgumentChecks.ensureValidIndex(lastRow, tgtDim); |
| final DoubleDouble s = new DoubleDouble(); |
| if (scale != null) { |
| for (int i=lastCol; i>=0; i--) { |
| get(tgtDim, i, s); |
| s.multiplyGuessError(scale); |
| set(tgtDim, i, s); |
| } |
| } |
| if (offset != null) { |
| get(tgtDim, lastCol, s); |
| s.addGuessError(offset); |
| set(tgtDim, lastCol, s); |
| } |
| } |
| |
| /** |
| * Returns a new matrix which is the result of multiplying this matrix with the specified one. |
| * In other words, returns {@code this} × {@code matrix}. |
| * |
| * <div class="section">Relationship with coordinate operations</div> |
| * In the context of coordinate operations, {@code Matrix.multiply(other)} is equivalent to |
| * <code>{@linkplain AffineTransform#concatenate AffineTransform.concatenate}(other)</code>: |
| * first transforms by the supplied transform and then transform the result by the original transform. |
| * |
| * @param matrix the matrix to multiply to this matrix. |
| * @return the result of {@code this} × {@code matrix}. |
| * @throws MismatchedMatrixSizeException if the number of rows in the given matrix is not equals to the |
| * number of columns in this matrix. |
| */ |
| public MatrixSIS multiply(final Matrix matrix) throws MismatchedMatrixSizeException { |
| final int nc = matrix.getNumCol(); |
| ensureNumRowMatch(getNumCol(), matrix.getNumRow(), nc); |
| final GeneralMatrix result = GeneralMatrix.createExtendedPrecision(getNumRow(), nc, false); |
| result.setToProduct(this, matrix); |
| return result; |
| } |
| |
| /** |
| * Returns a new vector which is the result of multiplying this matrix with the specified vector. |
| * In other words, returns {@code this} × {@code vector}. The length of the given vector must be |
| * equal to the number of columns in this matrix, and the length of the returned vector will be |
| * equal to the number of rows in this matrix. |
| * |
| * <div class="section">Relationship with coordinate operations</div> |
| * In the context of coordinate operations, {@code Matrix.multiply(vector)} is related to |
| * <code>{@linkplain AffineTransform#transform(double[], int, double[], int, int) AffineTransform.transform}(…)</code> |
| * except that the last {@code vector} number is implicitly 1 in {@code AffineTransform} operations. |
| * While this {@code multiply(double[])} method could be used for coordinate transformation, it is not its purpose. |
| * This method is designed for occasional uses when accuracy is more important than performance. |
| * |
| * @param vector the vector to multiply to this matrix. |
| * @return the result of {@code this} × {@code vector}. |
| * @throws MismatchedMatrixSizeException if the length of the given vector is not equals to the |
| * number of columns in this matrix. |
| * |
| * @since 0.8 |
| */ |
| public double[] multiply(final double[] vector) { |
| final int numCol = getNumCol(); |
| if (vector.length != numCol) { |
| throw new MismatchedMatrixSizeException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, numCol, vector.length)); |
| } |
| final double[] target = new double[getNumRow()]; |
| final DoubleDouble ele = new DoubleDouble(); |
| final DoubleDouble sum = new DoubleDouble(); |
| for (int j=0; j<target.length; j++) { |
| for (int i=0; i<numCol; i++) { |
| get(j, i, ele); |
| ele.multiplyGuessError(vector[i]); |
| sum.add(ele); |
| } |
| target[j] = sum.doubleValue(); |
| sum.clear(); |
| } |
| return target; |
| } |
| |
| /** |
| * Multiplies this matrix by a translation matrix. Invoking this method is equivalent to invoking |
| * <code>{@linkplain #multiply(Matrix) multiply}(T)</code> where <var>T</var> is a matrix like |
| * below (size varies): |
| * |
| * {@preformat math |
| * ┌ ┐ |
| * │ 1 0 0 vector[0] │ |
| * T = │ 0 1 0 vector[1] │ |
| * │ 0 0 1 vector[2] │ |
| * │ 0 0 0 vector[3] │ |
| * └ ┘ |
| * } |
| * |
| * The length of the given vector must be equals to the number of columns in this matrix. |
| * The last vector element is 1 for an affine transform, but other values are allowed. |
| * This matrix will be modified in-place. |
| * |
| * <p>If this matrix is used for coordinate conversions, then converting a position with |
| * the resulting matrix is equivalent to first translating the point by the given vector, |
| * then applying the conversion represented by the original matrix.</p> |
| * |
| * @param vector a vector representing a translation to be applied before this matrix. |
| * |
| * @since 1.0 |
| * |
| * @see AffineTransform#translate(double, double) |
| */ |
| public void translate(final double[] vector) { |
| final int numCol = getNumCol(); |
| if (vector.length != numCol) { |
| throw new MismatchedMatrixSizeException(Errors.format(Errors.Keys.UnexpectedArrayLength_2, numCol, vector.length)); |
| } |
| final int numRow = getNumRow(); |
| final DoubleDouble s = new DoubleDouble(); |
| final DoubleDouble t = new DoubleDouble(); |
| for (int j=0; j<numRow; j++) { |
| s.clear(); |
| for (int i=0; i<numCol; i++) { |
| get(j, i, t); |
| t.multiplyGuessError(vector[i]); |
| s.add(t); |
| } |
| set(j, numCol-1, s); |
| } |
| } |
| |
| /** |
| * Returns the value of <var>U</var> which solves {@code this} × <var>U</var> = {@code matrix}. |
| * This is equivalent to first computing the inverse of {@code this}, then multiplying the result |
| * by the given matrix. |
| * |
| * @param matrix the matrix to solve. |
| * @return the <var>U</var> matrix that satisfies {@code this} × <var>U</var> = {@code matrix}. |
| * @throws MismatchedMatrixSizeException if the number of rows in the given matrix is not equals |
| * to the number of columns in this matrix. |
| * @throws NoninvertibleMatrixException if this matrix is not invertible. |
| */ |
| public MatrixSIS solve(final Matrix matrix) throws MismatchedMatrixSizeException, NoninvertibleMatrixException { |
| return Solver.solve(this, matrix); |
| } |
| |
| /** |
| * Returns the inverse of this matrix. |
| * |
| * @return the inverse of this matrix. |
| * @throws NoninvertibleMatrixException if this matrix is not invertible. |
| * |
| * @see AffineTransform#createInverse() |
| */ |
| public MatrixSIS inverse() throws NoninvertibleMatrixException { |
| return Solver.inverse(this, true); |
| } |
| |
| /** |
| * Returns a new matrix with the same elements than this matrix except for the specified rows. |
| * This method is useful for removing a range of <em>target</em> dimensions in an affine transform. |
| * |
| * @param lower index of the first row to remove (inclusive). |
| * @param upper index after the last row to remove (exclusive). |
| * @return a copy of this matrix with the specified rows removed. |
| * |
| * @since 0.7 |
| */ |
| public MatrixSIS removeRows(final int lower, final int upper) { |
| final int numRow = getNumRow(); |
| final int numCol = getNumCol(); |
| ArgumentChecks.ensureValidIndexRange(numRow, lower, upper); |
| final DoubleDouble dd = isExtendedPrecision() ? new DoubleDouble() : null; |
| final MatrixSIS reduced = Matrices.createZero(numRow - (upper - lower), numCol, dd != null); |
| int dest = 0; |
| for (int j=0; j<numRow; j++) { |
| if (j == lower) { |
| j = upper; |
| if (j == numRow) break; |
| } |
| for (int i=0; i<numCol; i++) { |
| if (dd != null) { |
| get(j, i, dd); |
| reduced.set(dest, i, dd); |
| } else { |
| reduced.setElement(dest, i, getElement(j, i)); |
| } |
| } |
| dest++; |
| } |
| return reduced; |
| } |
| |
| /** |
| * Returns a new matrix with the same elements than this matrix except for the specified columns. |
| * This method is useful for removing a range of <em>source</em> dimensions in an affine transform. |
| * Coordinates will be converted as if the values in the removed dimensions were zeros. |
| * |
| * @param lower index of the first column to remove (inclusive). |
| * @param upper index after the last column to remove (exclusive). |
| * @return a copy of this matrix with the specified columns removed. |
| * |
| * @since 0.7 |
| */ |
| public MatrixSIS removeColumns(final int lower, final int upper) { |
| final int numRow = getNumRow(); |
| final int numCol = getNumCol(); |
| ArgumentChecks.ensureValidIndexRange(numCol, lower, upper); |
| final DoubleDouble dd = isExtendedPrecision() ? new DoubleDouble() : null; |
| final MatrixSIS reduced = Matrices.createZero(numRow, numCol - (upper - lower), dd != null); |
| int dest = 0; |
| for (int i=0; i<numCol; i++) { |
| if (i == lower) { |
| i = upper; |
| if (i == numCol) break; |
| } |
| for (int j=0; j<numRow; j++) { |
| if (dd != null) { |
| get(j, i, dd); |
| reduced.set(j, dest, dd); |
| } else { |
| reduced.setElement(j, dest, getElement(j, i)); |
| } |
| } |
| dest++; |
| } |
| return reduced; |
| } |
| |
| /** |
| * Returns a hash code value based on the data values in this matrix. |
| * |
| * @return a hash code value for this matrix. |
| */ |
| @Override |
| public int hashCode() { |
| return Arrays.hashCode(getElements()) ^ (int) serialVersionUID; |
| } |
| |
| /** |
| * Returns {@code true} if the specified object is of the same class than this matrix and |
| * all of the data members are equal to the corresponding data members in this matrix. |
| * |
| * @param object the object to compare with this matrix for equality. |
| * @return {@code true} if the given object is equal to this matrix. |
| */ |
| @Override |
| public boolean equals(final Object object) { |
| if (object != null && object.getClass() == getClass()) { |
| final int numRow = getNumRow(); |
| final int numCol = getNumCol(); |
| final MatrixSIS that = (MatrixSIS) object; |
| if (that.getNumRow() == numRow && that.getNumCol() == numCol) { |
| for (int j=numRow; --j >= 0;) { |
| for (int i=numCol; --i >= 0;) { |
| if (!Numerics.equals(that.getElement(j, i), getElement(j, i))) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Compares the given matrices for equality, using the given absolute tolerance threshold. |
| * The given matrix does not need to be the same implementation class than this matrix. |
| * |
| * <p>The matrix elements are compared as below:</p> |
| * <ul> |
| * <li>{@link Double#NaN} values are considered equals to all other NaN values.</li> |
| * <li>Infinite values are considered equal to other infinite values of the same sign.</li> |
| * <li>All other values are considered equal if the absolute value of their difference is |
| * smaller than or equals to the given threshold.</li> |
| * </ul> |
| * |
| * @param matrix the matrix to compare. |
| * @param tolerance the tolerance value. |
| * @return {@code true} if this matrix is close enough to the given matrix given the tolerance value. |
| * |
| * @see Matrices#equals(Matrix, Matrix, double, boolean) |
| */ |
| public boolean equals(final Matrix matrix, final double tolerance) { |
| return Matrices.equals(this, matrix, tolerance, false); |
| } |
| |
| /** |
| * Compares this matrix with the given object for equality. To be considered equal, the two |
| * objects must met the following conditions, which depend on the {@code mode} argument: |
| * |
| * <ul> |
| * <li>{@link ComparisonMode#STRICT STRICT}: |
| * the two matrices must be of the same class, have the same size and the same element values.</li> |
| * <li>{@link ComparisonMode#BY_CONTRACT BY_CONTRACT}: |
| * the two matrices must have the same size and the same element values, |
| * but are not required to be the same implementation class (any {@link Matrix} is okay).</li> |
| * <li>{@link ComparisonMode#IGNORE_METADATA IGNORE_METADATA}: same as {@code BY_CONTRACT}. |
| * <li>{@link ComparisonMode#APPROXIMATE APPROXIMATE}: |
| * the two matrices must have the same size, but the element values can differ up to some threshold. |
| * The threshold value is determined empirically and may change in any future SIS versions.</li> |
| * </ul> |
| * |
| * @param object the object to compare to {@code this}. |
| * @param mode the strictness level of the comparison. |
| * @return {@code true} if both objects are equal. |
| * |
| * @see Matrices#equals(Matrix, Matrix, ComparisonMode) |
| */ |
| @Override |
| public boolean equals(final Object object, final ComparisonMode mode) { |
| return (object instanceof Matrix) && Matrices.equals(this, (Matrix) object, mode); |
| } |
| |
| /** |
| * Returns a clone of this matrix. |
| * |
| * @return a new matrix of the same class and with the same values than this matrix. |
| * |
| * @see Matrices#copy(Matrix) |
| */ |
| @Override |
| public MatrixSIS clone() { |
| try { |
| return (MatrixSIS) super.clone(); |
| } catch (CloneNotSupportedException e) { |
| throw new AssertionError(e); // Should never happen since we are cloneable. |
| } |
| } |
| |
| /** |
| * Returns a unlocalized string representation of this matrix. |
| * For each column, the numbers are aligned on the decimal separator. |
| * |
| * @see Matrices#toString(Matrix) |
| */ |
| @Override |
| public String toString() { |
| return Matrices.toString(this); |
| } |
| } |