/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2015, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.referencing.operation.matrix;
import java.awt.geom.AffineTransform;
import java.text.ParseException;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.util.Locale;
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Serializable;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.operation.Matrix;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.ejml.UtilEjml;
import org.ejml.data.DenseMatrix64F;
import org.ejml.ops.CommonOps;
import org.geotools.io.LineFormat;
import org.geotools.io.ContentFormatException;
import org.geotools.util.Utilities;
import org.geotools.resources.XArray;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
/**
* A two dimensional array of numbers. Row and column numbering begins with zero.
*
* @since 2.2
* @version 14.0
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
* @author Simone Giannecchini
*
* @see java.awt.geom.AffineTransform
*/
public class GeneralMatrix implements XMatrix, Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 8447482612423035361L;
DenseMatrix64F mat;
/**
* Constructs a square identity matrix of size {@code size} × {@code size}.
*
* @param size The number of rows and columns.
*/
public GeneralMatrix(final int size) {
mat = new DenseMatrix64F(size, size);
setIdentity();
}
/**
* Creates a matrix of size {@code numRow} × {@code numCol}.
* Elements on the diagonal <var>j==i</var> are set to 1.
*
* @param numRow Number of rows.
* @param numCol Number of columns.
*/
public GeneralMatrix(final int numRow, final int numCol) {
mat = new DenseMatrix64F(numRow, numCol);
setIdentity();
}
/**
* Constructs a {@code numRow} × {@code numCol} matrix
* initialized to the values in the {@code matrix} array. The array values
* are copied in one row at a time in row major fashion. The array should be
* exactly <code>numRow*numCol</code> in length. Note that because row and column
* numbering begins with zero, {@code numRow} and {@code numCol} will be
* one larger than the maximum possible matrix index values.
*
* @param numRow Number of rows.
* @param numCol Number of columns.
* @param matrix Initial values in row order
*/
public GeneralMatrix(final int numRow, final int numCol, final double ... matrix) {
mat = new DenseMatrix64F(numRow, numCol, true, matrix);
if (numRow * numCol != matrix.length) {
throw new IllegalArgumentException(String.valueOf(matrix.length));
}
}
/**
* Constructs a {@code numRow} × {@code numCol} matrix
* initialized to the values in the {@code matrix} array. The array values
* are copied in one row at a time in row major fashion. The array should be
* exactly <code>numRow*numCol</code> in length. Note that because row and column
* numbering begins with zero, {@code numRow} and {@code numCol} will be
* one larger than the maximum possible matrix index values.
*
* @param numRow Number of rows.
* @param numCol Number of columns.
* @param matrix Initial values in row order
*/
public GeneralMatrix(final int numRow, final int numCol, final Matrix matrix) {
mat = new DenseMatrix64F(numRow, numCol);
if (matrix.getNumRow()!=numRow || matrix.getNumCol()!=numCol) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_MATRIX_SIZE));
}
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
setElement(j,i, matrix.getElement(j,i));
}
}
}
/**
* Constructs a new matrix from a two-dimensional array of doubles.
*
* @param matrix Array of rows. Each row must have the same length.
* @throws IllegalArgumentException if the specified matrix is not regular
* (i.e. if all rows doesn't have the same length).
*/
public GeneralMatrix(final double[][] matrix) throws IllegalArgumentException {
mat = new DenseMatrix64F(matrix);
final int numRow = getNumRow();
final int numCol = getNumCol();
for (int j = 0; j < numRow; j++) {
if (matrix[j].length != numCol) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.MATRIX_NOT_REGULAR));
}
for (int i = 0; i < numCol; i++) {
mat.set(j, i, matrix[j][i]);
}
}
}
/**
* Constructs a new matrix and copies the initial values from the parameter matrix.
*
* @param matrix The matrix to copy.
*/
public GeneralMatrix(final Matrix matrix) {
if (matrix instanceof GeneralMatrix) {
mat = new DenseMatrix64F(((GeneralMatrix) matrix).mat);
} else {
mat = new DenseMatrix64F(matrix.getNumRow(), matrix.getNumCol());
final int height = getNumRow();
final int width = getNumCol();
for (int j = 0; j < height; j++) {
for (int i = 0; i < width; i++) {
mat.set(j, i, matrix.getElement(j, i));
}
}
}
}
/**
* Constructs a new matrix and copies the initial values from the parameter matrix.
*
* @param matrix The matrix to copy.
*/
public GeneralMatrix(final GeneralMatrix matrix) {
mat = new DenseMatrix64F(matrix.mat);
}
/**
* Constructs a 3×3 matrix from the specified affine transform.
*
* @param transform The matrix to copy.
*/
public GeneralMatrix(final AffineTransform transform) {
mat = new DenseMatrix64F(
3,3, true, new double[] {
transform.getScaleX(), transform.getShearX(), transform.getTranslateX(),
transform.getShearY(), transform.getScaleY(), transform.getTranslateY(),
0, 0, 1
});
assert isAffine() : this;
}
/**
* Constructs a transform that maps a source region to a destination region.
* Axis order and direction are left unchanged.
*
* <P>If the source dimension is equals to the destination dimension,
* then the transform is affine. However, the following special cases
* are also handled:</P>
*
* <UL>
* <LI>If the target dimension is smaller than the source dimension,
* then extra dimensions are dropped.</LI>
* <LI>If the target dimension is greater than the source dimension,
* then the coordinates in the new dimensions are set to 0.</LI>
* </UL>
*
* @param srcRegion The source region.
* @param dstRegion The destination region.
*/
public GeneralMatrix(final Envelope srcRegion, final Envelope dstRegion) {
mat = new DenseMatrix64F(dstRegion.getDimension()+1, srcRegion.getDimension()+1);
// Next lines should be first if only Sun could fix RFE #4093999 (sigh...)
final int srcDim = srcRegion.getDimension();
final int dstDim = dstRegion.getDimension();
for (int i=Math.min(srcDim, dstDim); --i>=0;) {
double scale = dstRegion.getSpan (i) / srcRegion.getSpan (i);
double translate = dstRegion.getMinimum(i) - srcRegion.getMinimum(i)*scale;
setElement(i, i, scale);
setElement(i, srcDim, translate);
}
setElement(dstDim, srcDim, 1);
assert (srcDim != dstDim) || isAffine() : this;
}
/**
* Constructs a transform changing axis order and/or direction.
* For example, the transform may converts (NORTH,WEST) coordinates
* into (EAST,NORTH). Axis direction can be inversed only. For example,
* it is illegal to transform (NORTH,WEST) coordinates into (NORTH,DOWN).
*
* <P>If the source dimension is equals to the destination dimension,
* then the transform is affine. However, the following special cases
* are also handled:</P>
* <BR>
* <UL>
* <LI>If the target dimension is smaller than the source dimension,
* extra axis are dropped. An exception is thrown if the target
* contains some axis not found in the source.</LI>
* </UL>
*
* @param srcAxis The set of axis direction for source coordinate system.
* @param dstAxis The set of axis direction for destination coordinate system.
* @throws IllegalArgumentException If {@code dstAxis} contains some axis
* not found in {@code srcAxis}, or if some colinear axis were found.
*/
public GeneralMatrix(final AxisDirection[] srcAxis,
final AxisDirection[] dstAxis)
{
this(null, srcAxis, null, dstAxis, false);
}
/**
* Constructs a transform mapping a source region to a destination region.
* Axis order and/or direction can be changed during the process.
* For example, the transform may convert (NORTH,WEST) coordinates
* into (EAST,NORTH). Axis direction can be inversed only. For example,
* it is illegal to transform (NORTH,WEST) coordinates into (NORTH,DOWN).
*
* <P>If the source dimension is equals to the destination dimension,
* then the transform is affine. However, the following special cases
* are also handled:</P>
* <BR>
* <UL>
* <LI>If the target dimension is smaller than the source dimension,
* extra axis are dropped. An exception is thrown if the target
* contains some axis not found in the source.</LI>
* </UL>
*
* @param srcRegion The source region.
* @param srcAxis Axis direction for each dimension of the source region.
* @param dstRegion The destination region.
* @param dstAxis Axis direction for each dimension of the destination region.
* @throws MismatchedDimensionException if the envelope dimension doesn't
* matches the axis direction array length.
* @throws IllegalArgumentException If {@code dstAxis} contains some axis
* not found in {@code srcAxis}, or if some colinear axis were found.
*/
public GeneralMatrix(final Envelope srcRegion, final AxisDirection[] srcAxis,
final Envelope dstRegion, final AxisDirection[] dstAxis)
{
this(srcRegion, srcAxis, dstRegion, dstAxis, true);
}
/**
* Implementation of constructors expecting envelope and/or axis directions.
*
* @param validRegions {@code true} if source and destination regions must
* be taken in account. If {@code false}, then source and destination
* regions will be ignored and may be null.
*/
private GeneralMatrix(final Envelope srcRegion, final AxisDirection[] srcAxis,
final Envelope dstRegion, final AxisDirection[] dstAxis,
final boolean validRegions)
{
this(dstAxis.length+1, srcAxis.length+1);
if (validRegions) {
ensureDimensionMatch("srcRegion", srcRegion, srcAxis.length);
ensureDimensionMatch("dstRegion", dstRegion, dstAxis.length);
}
/*
* Map source axis to destination axis. If no axis is moved (for example if the user
* want to transform (NORTH,EAST) to (SOUTH,EAST)), then source and destination index
* will be equal. If some axis are moved (for example if the user want to transform
* (NORTH,EAST) to (EAST,NORTH)), then ordinates at index {@code srcIndex} will
* have to be moved at index {@code dstIndex}.
*/
setZero();
for (int dstIndex=0; dstIndex<dstAxis.length; dstIndex++) {
boolean hasFound = false;
final AxisDirection dstAxe = dstAxis[dstIndex];
final AxisDirection search = dstAxe.absolute();
for (int srcIndex=0; srcIndex<srcAxis.length; srcIndex++) {
final AxisDirection srcAxe = srcAxis[srcIndex];
if (search.equals(srcAxe.absolute())) {
if (hasFound) {
// TODO: Use the localized version of 'getName' in GeoAPI 2.1
throw new IllegalArgumentException(Errors.format(ErrorKeys.COLINEAR_AXIS_$2,
srcAxe.name(), dstAxe.name()));
}
hasFound = true;
/*
* Set the matrix elements. Some matrix elements will never
* be set. They will be left to zero, which is their wanted
* value.
*/
final boolean normal = srcAxe.equals(dstAxe);
double scale = (normal) ? +1 : -1;
double translate = 0;
if (validRegions) {
translate = (normal) ? dstRegion.getMinimum(dstIndex)
: dstRegion.getMaximum(dstIndex);
scale *= dstRegion.getSpan(dstIndex) /
srcRegion.getSpan(srcIndex);
translate -= srcRegion.getMinimum(srcIndex) * scale;
}
setElement(dstIndex, srcIndex, scale);
setElement(dstIndex, srcAxis.length, translate);
}
}
if (!hasFound) {
// TODO: Use the localized version of 'getName' in GeoAPI 2.1
throw new IllegalArgumentException(Errors.format(
ErrorKeys.NO_SOURCE_AXIS_$1, dstAxis[dstIndex].name()));
}
}
setElement(dstAxis.length, srcAxis.length, 1);
assert (srcAxis.length != dstAxis.length) || isAffine() : this;
}
//
// In-place operations
//
/**
* Cast (or convert) Matrix to internal DenseMatrix64F representation required for CommonOps.
* @param matrix
* @return
*/
private DenseMatrix64F internal( Matrix matrix ){
if( matrix instanceof GeneralMatrix ){
return ((GeneralMatrix)matrix).mat;
}
else {
DenseMatrix64F a = new DenseMatrix64F(matrix.getNumRow(), matrix.getNumCol());
for (int j = 0; j < a.numRows; j++) {
for (int i = 0; i < a.numCols; i++) {
a.set(j, i, matrix.getElement(j, i));
}
}
return a;
}
}
/**
* Convenience method for checking object dimension validity.
* This method is usually invoked for argument checking.
*
* @param name The name of the argument to check.
* @param envelope The envelope to check.
* @param dimension The expected dimension for the object.
* @throws MismatchedDimensionException if the envelope doesn't have the expected dimension.
*/
private static void ensureDimensionMatch(final String name,
final Envelope envelope,
final int dimension)
throws MismatchedDimensionException
{
final int dim = envelope.getDimension();
if (dimension != dim) {
throw new MismatchedDimensionException(Errors.format(
ErrorKeys.MISMATCHED_DIMENSION_$3, name, dim, dimension));
}
}
/**
* Retrieves the specifiable values in the transformation matrix into a
* 2-dimensional array of double precision values. The values are stored
* into the 2-dimensional array using the row index as the first subscript
* and the column index as the second. Values are copied; changes to the
* returned array will not change this matrix.
*
* @param matrix The matrix to extract elements from.
* @return The matrix elements.
*/
public static double[][] getElements(final Matrix matrix) {
if (matrix instanceof GeneralMatrix) {
return ((GeneralMatrix) matrix).getElements();
}
final int numCol = matrix.getNumCol();
final double[][] rows = new double[matrix.getNumRow()][];
for (int j=0; j<rows.length; j++) {
final double[] row;
rows[j] = row = new double[numCol];
for (int i=0; i<row.length; i++) {
row[i] = matrix.getElement(j, i);
}
}
return rows;
}
/**
* Retrieves the specifiable values in the transformation matrix into a
* 2-dimensional array of double precision values. The values are stored
* into the 2-dimensional array using the row index as the first subscript
* and the column index as the second. Values are copied; changes to the
* returned array will not change this matrix.
*
* @return The matrix elements.
*/
public final double[][] getElements() {
final int numCol = getNumCol();
final double[][] rows = new double[getNumRow()][];
for (int j=0; j<rows.length; j++) {
getRow(j, rows[j]=new double[numCol]);
}
return rows;
}
/**
* {@inheritDoc}
*/
public final boolean isAffine() {
int dimension = getNumRow();
if (dimension != getNumCol()) {
return false;
}
dimension--;
for (int i=0; i<=dimension; i++) {
if (getElement(dimension, i) != (i==dimension ? 1 : 0)) {
return false;
}
}
return true;
}
/**
* Changes the sign of each element in the matrix.
*/
@Override
public void negate() {
// JNH: This seems the most aggressive approach.
CommonOps.changeSign(mat);
}
@Override
public void negate(Matrix matrix) {
DenseMatrix64F a = internal(matrix);
CommonOps.changeSign(a);
this.mat = a;
}
/**
* Transposes the matrix.
*/
@Override
public void transpose() {
CommonOps.transpose(mat);
}
@Override
public void transpose(Matrix matrix) {
DenseMatrix64F a = internal(matrix);
CommonOps.transpose(a, mat);
}
@Override
public void invert() {
boolean success = CommonOps.invert(mat);
if(!success){
throw new SingularMatrixException("Could not invert, possible singular matrix?");
}
}
@Override
public void invert(Matrix matrix) throws SingularMatrixException {
DenseMatrix64F a;
if( matrix instanceof GeneralMatrix ){
a = new DenseMatrix64F( ((GeneralMatrix)matrix).mat );
}
else {
a = new DenseMatrix64F(matrix.getNumRow(), matrix.getNumCol());
for (int j = 0; j < mat.numRows; j++) {
for (int i = 0; i < mat.numCols; i++) {
mat.set(j, i, matrix.getElement(j, i));
}
}
}
boolean success = CommonOps.invert(a);
if(!success){
throw new SingularMatrixException("Could not invert, possible singular matrix?");
}
this.mat = a;
}
/**
* Gets the number of rows in the matrix.
* @return The number of rows in the matrix.
*/
@Override
public int getNumRow() {
return mat.getNumRows();
}
/**
* Gets the number of columns in the matrix.
* @return The number of columns in the matrix.
*/
@Override
public int getNumCol() {
return mat.getNumCols();
}
/**
* Returns the value at the row, column position in the matrix.
* @param row
* @param column
* @return Matrix value at the given row and column.
*/
@Override
public double getElement(int row, int column) {
return mat.get(row, column);
}
public void setColumn( int column, double ... values ){
if ( values.length != mat.getNumCols() ) {
throw new IllegalArgumentException("Call setRow received an array of length " + values.length + ". " +
"The dimensions of the matrix is " + mat.getNumRows() + " by " + mat.getNumCols() + ".");
}
for( int i = 0; i < values.length; i++) {
mat.set(i, column, values[i]);
}
}
public void setRow(int row, double ... values) {
if ( values.length != mat.getNumCols() ) {
throw new IllegalArgumentException("Call setRow received an array of length " + values.length + ". " +
"The dimensions of the matrix is " + mat.getNumRows() + " by " + mat.getNumCols() + ".");
}
for( int i = 0; i < values.length; i++) {
mat.set(row, i, values[i]);
}
}
/**
* Sets the value of the row, column position in the matrix.
* @param row
* @param column
* @param value
*/
@Override
public void setElement(int row, int column, double value) {
mat.set(row, column, value);
}
/**
* Sets each value of the matrix to 0.0.
*/
@Override
public void setZero() {
mat.zero();
}
/**
* Sets the main diagonal of this matrix to be 1.0.
*/
@Override
public void setIdentity() {
CommonOps.setIdentity(mat);
}
/**
* Returns {@code true} if this matrix is an identity matrix.
*/
public final boolean isIdentity() {
final int numRow = getNumRow();
final int numCol = getNumCol();
if (numRow != numCol) {
return false;
}
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
if (getElement(j,i) != (i==j ? 1.0 : 0.0)) {
return false;
}
}
}
assert isAffine() : this;
assert isIdentity(0) : this;
return true;
}
/**
* {@inheritDoc}
*
* @since 2.3.1
*/
public final boolean isIdentity(double tolerance) {
return isIdentity(this, tolerance);
}
/**
* Returns {@code true} if the matrix is an identity matrix using the provided tolerance.
*/
static boolean isIdentity(final Matrix matrix, double tolerance) {
tolerance = Math.abs(tolerance);
final int numRow = matrix.getNumRow();
final int numCol = matrix.getNumCol();
if (numRow != numCol) {
return false;
}
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
double e = matrix.getElement(j,i);
if (i == j) {
e--;
}
if (!(Math.abs(e) <= tolerance)) { // Uses '!' in order to catch NaN values.
return false;
}
}
}
// Note: we can't assert matrix.isAffine().
return true;
}
/**
* {@inheritDoc}
*/
public final void multiply(final Matrix matrix) {
mul(matrix);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
if (mat == null) {
return prime * result;
}
result = prime * result + mat.numRows;
result = prime * result + mat.numCols;
for (double d : mat.data) {
long bits = Double.doubleToRawLongBits(d);
result = prime * result + ((int)(bits ^ (bits >>> 32)));
}
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
GeneralMatrix other = (GeneralMatrix) obj;
return equals(other,0);
}
public boolean equals(final Matrix matrix, final double tolerance) {
return epsilonEquals(this, matrix, tolerance);
}
/**
* Compares the element values.
*/
static boolean epsilonEquals(final Matrix m1, final Matrix m2, final double tolerance) {
final int numRow = m1.getNumRow();
if (numRow != m2.getNumRow()) {
return false;
}
final int numCol = m1.getNumCol();
if (numCol != m2.getNumCol()) {
return false;
}
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
final double v1 = m1.getElement(j, i);
final double v2 = m2.getElement(j, i);
if (!(Math.abs(v1 - v2) <= tolerance)) {
if (Double.doubleToLongBits(v1) == Double.doubleToLongBits(v2)) {
// Special case for NaN and infinite values.
continue;
}
return false;
}
}
}
return true;
}
/**
* Returns an affine transform for this matrix.
* This is a convenience method for interoperability with Java2D.
*
* @return The affine transform for this matrix.
* @throws IllegalStateException if this matrix is not 3×3,
* or if the last row is not {@code [0 0 1]}.
*/
public final AffineTransform toAffineTransform2D() throws IllegalStateException {
int check;
if ((check=getNumRow())!=3 || (check=getNumCol())!=3) {
throw new IllegalStateException(Errors.format(
ErrorKeys.NOT_TWO_DIMENSIONAL_$1, check-1));
}
if (isAffine()) {
return new AffineTransform(getElement(0,0), getElement(1,0),
getElement(0,1), getElement(1,1),
getElement(0,2), getElement(1,2));
}
throw new IllegalStateException(Errors.format(ErrorKeys.NOT_AN_AFFINE_TRANSFORM));
}
/**
* Loads data from the specified file until the first blank line or end of file.
*
* @param file The file to read.
* @return The matrix parsed from the file.
* @throws IOException if an error occured while reading the file.
*
* @since 2.2
*/
public static GeneralMatrix load(final File file) throws IOException {
final BufferedReader in = new BufferedReader(new FileReader(file));
try {
return load(in, Locale.US);
} finally {
in.close();
}
}
/**
* Loads data from the specified streal until the first blank line or end of stream.
*
* @param in The stream to read.
* @param locale The locale for the numbers to be parsed.
* @return The matrix parsed from the stream.
* @throws IOException if an error occured while reading the stream.
*
* @since 2.2
*/
public static GeneralMatrix load(final BufferedReader in, final Locale locale)
throws IOException
{
final LineFormat parser = new LineFormat(locale);
double[] data = null;
double[] row = null;
int numRow = 0;
int numData = 0;
String line;
while ((line=in.readLine()) != null) {
if ((line=line.trim()).length() == 0) {
if (numRow == 0) {
continue;
} else {
break;
}
}
try {
parser.setLine(line);
row = parser.getValues(row);
} catch (ParseException exception) {
throw new ContentFormatException(exception.getLocalizedMessage(), exception);
}
final int upper = numData + row.length;
if (data == null) {
// Assumes a square matrix.
data = new double[numData * numData];
}
if (upper > data.length) {
data = XArray.resize(data, upper*2);
}
System.arraycopy(row, 0, data, numData, row.length);
numData = upper;
numRow++;
assert numData % numRow == 0 : numData;
}
data = (data!=null) ? XArray.resize(data, numData) : new double[0];
return new GeneralMatrix(numRow, numData/numRow, data);
}
/**
* Returns a string representation of this matrix. The returned string is implementation
* dependent. It is usually provided for debugging purposes only.
*/
@Override
public String toString() {
return toString(this);
}
/**
* Returns a string representation of the specified matrix. The returned string is
* implementation dependent. It is usually provided for debugging purposes only.
*/
static String toString(final Matrix matrix) {
final int numRow = matrix.getNumRow();
final int numCol = matrix.getNumCol();
StringBuffer buffer = new StringBuffer();
final int columnWidth = 12;
final String lineSeparator = System.getProperty("line.separator", "\n");
final FieldPosition dummy = new FieldPosition(0);
final NumberFormat format = NumberFormat.getNumberInstance();
format.setGroupingUsed(false);
format.setMinimumFractionDigits(6);
format.setMaximumFractionDigits(6);
for (int j=0; j<numRow; j++) {
for (int i=0; i<numCol; i++) {
final int position = buffer.length();
buffer = format.format(matrix.getElement(j,i), buffer, dummy);
final int spaces = Math.max(columnWidth - (buffer.length() - position), 1);
buffer.insert(position, Utilities.spaces(spaces));
}
buffer.append(lineSeparator);
}
return buffer.toString();
}
// Method Compatibility
/**
* Returns a clone of this matrix.
*/
@Override
public GeneralMatrix clone() {
return new GeneralMatrix(this);
}
/** Extract a subMatrix to the provided target */
public void copySubMatrix(int rowSource, int colSource,
int numRows, int numCol,
int rowDest, int colDest, GeneralMatrix target) {
int rowLimit = rowSource + numRows;
int colLimit = colSource + numCol;
CommonOps.extract(mat, rowSource, rowLimit, colSource, colLimit, target.mat, rowDest, colDest);
}
/**
* Extract col to provided array.
*
* @param col
* @param array
*/
public void getColumn(int col, double[] array) {
for (int j = 0; j < array.length; j++) {
array[j] = mat.get(j, col);
}
}
@Override
public void mul(double scalar) {
CommonOps.scale(scalar, this.mat);
}
@Override
public void mul(double scalar, Matrix matrix) {
DenseMatrix64F a = new DenseMatrix64F( matrix.getNumRow(), matrix.getNumCol() );
CommonOps.scale(scalar, internal( matrix ), a );
mat = a;
}
/**
* Extract row to provided array
* @param row
* @param array
*/
public void getRow(int row, double[] array) {
for (int i = 0; i < array.length; i++) {
array[i] = mat.get(row, i);
}
}
/**
* In-place multiply with provided matrix.
* @param matrix
*
*/
public final void mul(Matrix matrix){
DenseMatrix64F b = internal(matrix);
DenseMatrix64F ret = new DenseMatrix64F(mat.numRows,b.numCols);
CommonOps.mult(mat,b,ret);
mat = ret;
}
/**
* In-place update from matrix1 * matrix2.
* @param matrix1
* @param matrix2
*/
public void mul(Matrix matrix1, Matrix matrix2) {
DenseMatrix64F a = internal(matrix1);
DenseMatrix64F b = internal(matrix2);
if( a == mat || b == mat ){
mat = new DenseMatrix64F(a.numRows, b.numCols );
}
else {
mat.reshape(a.numRows, b.numCols, false);
}
CommonOps.mult(a, b, mat);
}
@Override
public void sub(double scalar) {
CommonOps.subtract(mat, scalar, mat);
}
@Override
public void sub(double scalar, Matrix matrix) {
DenseMatrix64F a = internal(matrix);
mat.reshape(a.numRows, a.numCols, false);
CommonOps.subtract(scalar, a, mat);
}
public void sub(Matrix matrix) {
CommonOps.subtract(mat, internal(matrix), mat);
}
public void sub(Matrix matrix1, Matrix matrix2) {
DenseMatrix64F a = internal(matrix1);
DenseMatrix64F b = internal(matrix2);
mat.reshape(a.numRows, a.numCols, false);
CommonOps.subtract(a, b, mat);
}
/**
* Update in place to the provided matrix (row-order).
* @param matrix
*/
public void set(double[] matrix) {
mat.setData(matrix);
}
/**
* Resize the matrix to the specified number of rows and columns (preserving remaining elements).
*
* @param numRows The new number of rows in the matrix.
* @param numCols The new number of columns in the matrix.
*/
public void setSize(int numRows, int numCols) {
if( numRows == mat.numCols && numCols == mat.numCols ){
// do nothing
}
else {
// grow or shrink
DenseMatrix64F ret = new DenseMatrix64F(numRows,numCols);
CommonOps.extract(mat,0,numRows,0,numCols,ret,0,0);
mat = ret;
}
}
@Override
public void add(double scalar) {
CommonOps.add(mat, scalar, mat);
}
@Override
public void add(double scalar, XMatrix matrix) {
DenseMatrix64F a = internal(matrix);
mat.reshape(a.numRows, a.numCols, false);
CommonOps.add(a, scalar, mat);
}
@Override
public void add(XMatrix matrix) {
CommonOps.add(mat, internal(matrix), mat);
}
@Override
public void add(XMatrix matrix1, XMatrix matrix2) {
DenseMatrix64F a = internal(matrix1);
DenseMatrix64F b = internal(matrix2);
mat.reshape(a.numRows, a.numCols, false);
CommonOps.add(a, b, mat);
}
@Override
public double determinate() {
double det = CommonOps.det(mat);
// if the decomposition silently failed then the matrix is most likely singular
if(UtilEjml.isUncountable(det))
return 0;
return det;
}
}