/*
* Copyright (c) 1997 - 2016
* Actelion Pharmaceuticals Ltd.
* Gewerbestrasse 16
* CH-4123 Allschwil, Switzerland
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
*    list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
* 3. Neither the name of the the copyright holder nor the
*    names of its contributors may be used to endorse or promote products
*    derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

package com.actelion.research.calc;

import com.actelion.research.util.DoubleVec;
import com.actelion.research.util.convert.String2DoubleArray;
import com.actelion.research.util.datamodel.IntegerDouble;
import com.actelion.research.util.datamodel.ScorePoint;

import java.awt.*;
import java.io.*;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;
import java.util.stream.Stream;

public class Matrix {

    public static final double TINY04 = 0.0001;
    public static final double TINY08 = 0.00000001;
    public static final double TINY16 = Math.pow(10, -16);
    public static String OUT_SEPARATOR_COL = "\t";
    
    public static String OUT_SEPARATOR_ROW = "\n";
    
    private final static String FORMAT = "0.############";

    public static final double TINY = 10e-12;

    private double [][] data;

    private int identifier;

    public Matrix() {
        data = new double[1][1];
    }

    public Matrix(int rows, int cols) {
        data = new double[rows][cols];
    }

    public Matrix(Matrix ma) {
        this(ma.getArray());
    }

    /**
     *
     * @param row if true the matrix has one row. If false the matrix has one column.
     * @param arr
     */
    public Matrix(boolean row, double [] arr) {
        if (row) {
            data = new double[1][];
            data[0]=arr;
        }
        else {
            data = new double[arr.length][1];
            for (int i = 0; i < getRowDim(); i++) {
                data[i][0] = arr[i];
            }
        }
    }
    
    public Matrix(boolean row, byte [] arr) {
        if (row) {
            data = new double[1][arr.length];
            for (int i = 0; i < arr.length; i++) {
                data[0][i]=arr[i];
            }
        }
        else {
            data = new double[arr.length][1];
            for (int i = 0; i < getRowDim(); i++) {
                data[i][0] = arr[i];
            }
        }
    }

    public Matrix(boolean row, int [] dArray) {
        if (row) {
            data = new double[1][dArray.length];
            for (int jj = 0; jj < getColDim(); jj++) {
                data[0][jj] = dArray[jj];
            }
        }
        else {
            data = new double[dArray.length][1];
            for (int ii = 0; ii < getRowDim(); ii++) {
                data[ii][0] = dArray[ii];
            }
        }
    }
    /**
     * Deep copy
     * @param arrArr
     */
    public Matrix(double [][]arrArr) {
    	
        data = new double[arrArr.length][];
        
        int rows = arrArr.length;
        
        int cols = arrArr[0].length;
        
        for (int i = 0; i < rows; i++) {
        	double [] arr = new double [cols];
        	
        	System.arraycopy(arrArr[i], 0, arr, 0, cols);
            
            data[i] = arr;
        }
    }

    public Matrix(double [][]arrArr, boolean flat) {

        if(!flat){
            throw new RuntimeException("Only flat constructor!");
        }

        data = arrArr;

    }

    public Matrix(float [][]arrArr) {
    	
        data = new double[arrArr.length][arrArr[0].length];
        
        int rows = arrArr.length;
        
        int cols = arrArr[0].length;
        
        for (int i = 0; i < rows; i++) {
        	double [] arr = new double [cols];
        	
        	for (int j = 0; j < arr.length; j++) {
        		data[i][j] = arrArr[i][j];
			}
        }
    }

    public Matrix(int [][]arrArr) {
        
        data = new double[arrArr.length][];
        
        int rows = arrArr.length;
        
        int cols = arrArr[0].length;
        
        // 29.06.2012 System.arrayCopy gave ArrayStoreException.
        for (int i = 0; i < rows; i++) {
        	double [] arr = new double [cols];
        	
        	for (int j = 0; j < cols; j++) {
        		arr[j]=arrArr[i][j];
			}
        	
            
            data[i] = arr;
        }
    }
    
    public Matrix(byte [][]arrArr) {
    	
        data = new double[arrArr.length][];
        
        int rows = arrArr.length;
        
        int cols = arrArr[0].length;
        
        for (int i = 0; i < rows; i++) {
        	double [] arr = new double [cols];
        	
        	System.arraycopy(arrArr[i], 0, arr, 0, cols);
            
            data[i] = arr;
        }
    }

    /**
     *
     * @param vecDoubleVec Vector with DoubleVec
     */
    public Matrix(List<DoubleVec> vecDoubleVec) {
        DoubleVec dv = (DoubleVec) vecDoubleVec.get(0);
        data = new double[vecDoubleVec.size()][dv.size()];
        for (int ii = 0; ii < getRowDim(); ii++) {
            dv = (DoubleVec) vecDoubleVec.get(ii);
            for (int jj = 0; jj < getColDim(); jj++) {
                data[ii][jj] = dv.get(jj);
            }
        }
    }

    public Matrix(boolean bRow, List<Double> liDoubles) {
        if (bRow) {
            data = new double[1][liDoubles.size()];
            for (int i = 0; i < getColDim(); i++) {
                data[0][i] = liDoubles.get(i);
            }
        }
        else {
            data = new double[liDoubles.size()][1];
            for (int i = 0; i < getRowDim(); i++) {
                data[i][0] = liDoubles.get(i);
            }
        }
    }


    public Matrix add(double v) {

        int r = rows();
        int c = cols();

        Matrix A = new Matrix(r,c);

        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                double s = get(i,j) + v;
                A.set(i,j,s);
            }
        }
        return A;
    }

    /**
     *
     * @param arr adds a row at the end of the matrix.
     */
    public void addRow(double [] arr) {
        if(getColDim() != arr.length) {
            throw new RuntimeException("Matrices have wrong dimensions.");
        }

        resize(getRowDim() + 1, getColDim());

        for (int ii = 0; ii < arr.length; ii++)
          data[getRowDim() - 1][ii] = arr[ii];
    }

    public void addRow(int [] arr) {
        if(getColDim() != arr.length) {
            throw new RuntimeException("Matrices have wrong dimensions.");
        }

        resize(getRowDim() + 1, getColDim());

        for (int ii = 0; ii < arr.length; ii++)
          data[getRowDim() - 1][ii] = arr[ii];
    }
    /**
     * @param row where the data are added on
     * @param ma2 matrix from where the data are taken
     * @param row2 data row
     */
    public void add2Row(int row, Matrix ma2, int row2) {
        for (int ii = 0; ii < getColDim(); ii++)
          data[row][ii] += ma2.data[row2][ii];
    }

    public void addToElement(int i, int j, double value) {
        data[i][j]+=value;

    }

    /**
     * The value in the col from the input matrix is
     * added to all values in the corresponding col in the matrix.
     * @param maRow matrix with one row.
     */
    public Matrix add2CompleteCol(Matrix maRow) {
        Matrix m = new Matrix(this);
        for (int ii = 0; ii < getRowDim(); ii++)
            for (int jj = 0; jj < getColDim(); jj++)
                m.data[ii][jj] += maRow.data[0][jj];

        return m;
    }

    public void assignCol(int iCol, Matrix ma) {
        if(getRowDim() != ma.getRowDim()) {
            throw new RuntimeException("Matrices have wrong dimensions.");
        }
        for (int ii = 0; ii < data.length; ii++) {
          data[ii][iCol] = ma.data[ii][0];
        }
    }

    public void assignCol(int iCol, Matrix ma, int iColMa) {
        if(getRowDim() != ma.getRowDim()) {
            throw new RuntimeException("Matrices have wrong dimensions.");
        }
        for (int ii = 0; ii < data.length; ii++) {
          data[ii][iCol] = ma.data[ii][iColMa];
        }
    }

    public void assignRow(int iRow, Vector<Double> vecRow) {
        if(getColDim() != vecRow.size()) {
            throw new RuntimeException("Matrix and Vector have wrong dimensions.");
        }
        for (int jj = 0; jj < data[0].length; jj++) {
          data[iRow][jj] = vecRow.get(jj);
        }
    }

    public void assignRow(int iRow, double [] arr) {
        if(getColDim() != arr.length) {
            throw new RuntimeException("Matrix and Vector have wrong dimensions.");
        }
        for (int jj = 0; jj < arr.length; jj++) {
          data[iRow][jj] = arr[jj];
        }
    }

    public void assignRow(int iRow, int [] arr) {
        if(getColDim() != arr.length) {
            throw new RuntimeException("Matrix and Vector have wrong dimensions.");
        }
        for (int jj = 0; jj < arr.length; jj++) {
          data[iRow][jj] = arr[jj];
        }
    }

    public void assignRow(int iRow, DoubleVec dv) {
        if(getColDim() != dv.size()) {
            throw new RuntimeException("Matrix and Vector have wrong dimensions.");
        }
        for (int jj = 0; jj < dv.size(); jj++) {
          data[iRow][jj] = dv.get(jj);
        }
    }

    public boolean containsRow(DoubleVec dv) {
        boolean bOK = false;

      int iRows = getRowDim();
      int iCols = getColDim();

      for(int ii=0; ii < iRows; ii++) {
          bOK = true;
          for (int jj = 0; jj < iCols; jj++)
              if (data[ii][jj] != dv.get(jj)) {
                  bOK = false;
                  break;
              }
          if(bOK)
              break;
      }
        return bOK;
    }

    /**
     * Copies a matrix ma into this, the pointer to ma1 is not changed.
     * @param maSource
     */
    public void copy(Matrix maSource) {
		int rows = maSource.rows();
		int cols = maSource.cols();

		if (rows() == rows && cols() == cols) {
			for (int i = 0; i < rows; i++) {
				System.arraycopy(maSource.data[i], 0, data[i], 0, cols);
			}
		} else {
			data = new double[rows][];
			for (int i = 0; i < rows; i++) {

				double[] a = new double[cols];

				System.arraycopy(maSource.data[i], 0, a, 0, cols);

				data[i] = a;
			}
		}
    }

    /**
     * This (target matrix) must have appropriate dimensions.
     * @param offsetRows
     * @param maSource
     */
    public void copy(int offsetRows, Matrix maSource) {

		if(cols() != maSource.cols()){
		    throw new RuntimeException("Matrix col dimension error. Number of cols differ!");
        }

		int cols = cols();
		int r = maSource.rows();
        for (int i = 0; i < r; i++) {
            System.arraycopy(maSource.data[i], 0, data[offsetRows+i], 0, cols);
        }
    }

    public void copyColumn(Matrix maSource, int colSource, int colDestination) {
		int rows = maSource.rows();
        for (int i = 0; i < rows; i++) {
            data[i][colDestination] = maSource.get(i, colSource);
        }
    }

    final public double get(final int row, final int col) {
        return data[row][col];
    }

    public Matrix getAbs() {
        Matrix ma = new Matrix(getRowDim(), getColDim());
        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                ma.data[ii][jj] = Math.abs(data[ii][jj]);
            }
        }
        return ma;
    }

    final public double [][] getArray() {
        return data;
    }

    public int [][] getArrayAsInt() throws NumberFormatException {
        int [][] arr = new int[data.length][data[0].length];
        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                if(data[ii][jj] > Integer.MAX_VALUE ||
                   data[ii][jj] < Integer.MIN_VALUE) {
                    String err = "Double value instead of int " + data[ii][jj] + ".";
                    throw new NumberFormatException(err);
                }
                arr[ii][jj] = (int)data[ii][jj];
            }
        }
        return arr;
    }

    public double [][] getArrayCopy() {
        double [][] arr = new double[data.length][data[0].length];
        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                arr[ii][jj] = data[ii][jj];
            }
        }
        return arr;
    }

    /**
     *
     * @return column wise centered matrix.
     */
    public Matrix getCenteredMatrix() {
    	
    	final int cols = cols();
    	
    	final int rows = rows();

    	double [][] arr = new double[rows][];
    	
        Matrix maMean = getMeanCols();
        
        double [] arrMean = maMean.getRow(0);
        
        for (int i = 0; i < rows; i++) {
        	
        	double [] arrRowSrc = getRow(i);
        	
        	double [] arrRow = new double [cols];
            
            System.arraycopy(arrRowSrc, 0, arrRow, 0, arrRowSrc.length);
        	
            for (int j = 0; j < cols; j++) {
            	arrRow[j] = arrRow[j] - arrMean[j];
            }
            
            arr[i]=arrRow;
            
        }
        
        Matrix ma = new Matrix();
        
        ma.setFlat(arr);
        
        return ma;
    }

    public Matrix getCenteredMatrix(Matrix maMean) {
        Matrix ma = new Matrix(getRowDim(), getColDim());
        for (int i = 0; i < rows(); i++) {
            for (int j = 0; j < cols(); j++) {
                ma.data[i][j] = data[i][j] - maMean.data[0][j];
            }
        }
        return ma;
    }

    public Matrix getCol(int col) {
        Matrix ma = new Matrix(rows(), 1);
        int iRows = getRowDim();
        for(int i=0; i < iRows; i++){
            ma.data[i][0] = data[i][col];
        }
        return ma;
    }

    public Matrix removeCol(int col2Remove) {

        int r = rows();
        int c = cols();

        Matrix ma = new Matrix(r,c-1);

        for(int i=0; i < r; i++){

            int ccColNew=0;

            for (int j = 0; j < c; j++) {
                if(j!=col2Remove) {
                    ma.data[i][ccColNew++] = data[i][j];
                }
            }
        }

        return ma;
    }

    public double [] getColAsDouble(int col) {
    	double [] arr = new double [rows()];
        for(int i=0; i < rows(); i++){
            arr[i] = data[i][col];
        }
        return arr;
    }
    
    public List<Double> getColAsList(int col) {
    	List<Double> li = new ArrayList<Double>(rows());
        for(int i=0; i < rows(); i++){
        	li.add(data[i][col]);
        }
        return li;
    }

    public float [] getColAsFloat(int iCol) {
        float [] arr = new float [rows()];
        for(int i=0; i < rows(); i++){
            arr[i] = (float)data[i][iCol];
        }
        return arr;
    }
    
    public int [] getColAsInt(int iCol) {
        int [] arr = new int [rows()];
        for(int i=0; i < rows(); i++){
            arr[i] = (int)(data[i][iCol]+0.5);
        }
        return arr;
    }

    /**
     * 
     * @param vecIndices
     * @return
     * @deprecated use getColumns(List<Integer> vecIndices) instead
     */
    public Matrix getColumns(Vector<Integer> vecIndices) {
        Matrix maReduced = new Matrix(getRowDim(), vecIndices.size());
        for (int i = 0; i < vecIndices.size(); i++) {
            int col= vecIndices.get(i);
            maReduced.assignCol(i, this, col);
        }

        return maReduced;
    }

    public Matrix getColumns(List<Integer> liIndex) {
        Matrix maReduced = new Matrix(getRowDim(), liIndex.size());
        for (int i = 0; i < liIndex.size(); i++) {
            int col = liIndex.get(i);
            maReduced.assignCol(i, this, col);
        }
        return maReduced;
    }

    public Matrix getColumns(int [] arrIndex) {
        Matrix maReduced = new Matrix(getRowDim(), arrIndex.length);
        for (int i = 0; i < arrIndex.length; i++) {
            int col = arrIndex[i];
            maReduced.assignCol(i, this, col);
        }
        return maReduced;
    }

    public int getColDim() {
        return data[0].length;
    }
    public int cols() {
        return data[0].length;
    }
    /**
     * Deletes columns with zero variance.
     * @param vecIndices the integer vector is filled with the indices from the input
     * matrix which are taken for the new matrix.
     * @return input matrix reduced by all cols with zero variance.
     */
    public Matrix getDeleteColsZeroVar(List<Integer> vecIndices) {
        vecIndices.clear();
        Matrix maReduced = new Matrix(0,0);

        Matrix maVar = getVarianceCols();
        vecIndices.clear();
        for(int ii = 0; ii < maVar.getColDim(); ii++) {
          if(maVar.get(0,ii) > 0) {
            vecIndices.add(new Integer(ii));
          }
        }
        maReduced = getColumns(vecIndices);

        return maReduced;

    }

    /** Generate a covariance matrix, each column contains values of a pulling.
     @return     An n-by-n matrix.
     */
    /*T
     <instructions>Matrix A = Matrix.random(4,4);Matrix B = A.covariance();</instructions>
     <result>A</result>
     <result>B</result>
     */

    public Matrix covariance() {

        int n = getColDim();
        int m = getRowDim();
        Matrix X = new Matrix(n, n);
        int degrees = (m - 1);
        double c;
        double s1;
        double s2;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                c = 0;
                s1 = 0;
                s2 = 0;
                for (int k = 0; k < m; k++) {
                    s1 += this.get(k, i);
                    s2 += this.get(k, j);
                }
                s1 = s1 / m;
                s2 = s2 / m;
                for (int k = 0; k < m; k++) {
                    c += (this.get(k, i) - s1) * (this.get(k, j) - s2);
                }
                X.set(i, j, c / degrees);
            }
        }
        return X;
    }

    /** Generate a correlation matrix, each column contains values of a pulling.
     @return     An n-by-n matrix.
     */
    /*T
     <instructions>Matrix A = Matrix.random(4,4);Matrix B = A.correlation();</instructions>
     <result>A</result>
     <result>B</result>
     */

    public Matrix correlation() {
        int n = getColDim();
        int m = getRowDim();
        Matrix X = new Matrix(n, n);
        int degrees = (m - 1);
        Matrix V = new Matrix(n, n);
        double c;
        double s1;
        double s2;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                c = 0;
                s1 = 0;
                s2 = 0;
                for (int k = 0; k < m; k++) {
                    s1 += this.get(k, i);
                    s2 += this.get(k, j);
                }
                s1 = s1 / m;
                s2 = s2 / m;
                for (int k = 0; k < m; k++) {
                    c += (this.get(k, i) - s1) * (this.get(k, j) - s2);
                }
                V.set(i, j, c / degrees);
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                X.set(i, j, V.get(i, j) / Math.sqrt(V.get(i, i) * V.get(j, j)));
            }
        }
        return X;
    }

    /**
     * Matrix diagonal extraction.
     * @return An d*1 Matrix of diagonal elements, d = min(m,n).
     */
    public Matrix getDiagonal() {
        int n = Math.min(getRowDim(), getColDim());
        Matrix ma = new Matrix(n,1);

        for (int i = 0; i < n; i++) {
          ma.set(i,0, data[i][i]);
        }


        return ma;
    }

    public Matrix getLinedCol() {
        Matrix ma = new Matrix(getNumElements(), 1);

        int ind = 0;
        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                ma.data[ind][0] = data[i][j];
                ind++;
            }
        }

        return ma;
    }
    
    public List<Double> getList() {
    	
    	List<Double> li = new ArrayList<Double>(getRowDim()*getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
            	li.add(data[i][j]);
               
            }
        }

        return li;
    }

    public Matrix getMatrix (int i0, int i1, int j0, int j1) {
        Matrix X = new Matrix(i1-i0+1,j1-j0+1);
        double[][] B = X.getArray();
        try {
            for (int i = i0; i <= i1; i++) {
                for (int j = j0; j <= j1; j++) {
                    B[i-i0][j-j0] = data[i][j];
                }
            }
        } catch(ArrayIndexOutOfBoundsException e) {
            throw new ArrayIndexOutOfBoundsException("Submatrix indices");
        }
        return X;
    }

    /** Get a submatrix.
     @param r    Array of row indices.
     @param c    Array of column indices.
     @return     A(r(:),c(:))
     @exception  ArrayIndexOutOfBoundsException Submatrix indices
     */

    public Matrix getMatrix (int[] r, int[] c) {
        Matrix X = new Matrix(r.length,c.length);
        double[][] B = X.getArray();
        try {
            for (int i = 0; i < r.length; i++) {
                for (int j = 0; j < c.length; j++) {
                    B[i][j] = data[r[i]][c[j]];
                }
            }
        } catch(ArrayIndexOutOfBoundsException e) {
            throw new ArrayIndexOutOfBoundsException("Submatrix indices");
        }
        return X;
    }

    /** Get a submatrix.
     @param i0   Initial row index
     @param i1   Final row index
     @param c    Array of column indices.
     @return     A(i0:i1,c(:))
     @exception  ArrayIndexOutOfBoundsException Submatrix indices
     */

    public Matrix getMatrix (int i0, int i1, int[] c) {
        Matrix X = new Matrix(i1-i0+1,c.length);
        double[][] B = X.getArray();
        try {
            for (int i = i0; i <= i1; i++) {
                for (int j = 0; j < c.length; j++) {
                    B[i-i0][j] = data[i][c[j]];
                }
            }
        } catch(ArrayIndexOutOfBoundsException e) {
            throw new ArrayIndexOutOfBoundsException("Submatrix indices");
        }
        return X;
    }

    /** Get a submatrix.
     @param r    Array of row indices.
     @param j0   Initial column index
     @param j1   Final column index
     @return     A(r(:),j0:j1)
     @exception  ArrayIndexOutOfBoundsException Submatrix indices
     */

    public Matrix getMatrix (int[] r, int j0, int j1) {
        Matrix X = new Matrix(r.length,j1-j0+1);
        double[][] B = X.getArray();
        try {
            for (int i = 0; i < r.length; i++) {
                for (int j = j0; j <= j1; j++) {
                    B[i][j-j0] = data[r[i]][j];
                }
            }
        } catch(ArrayIndexOutOfBoundsException e) {
            throw new ArrayIndexOutOfBoundsException("Submatrix indices");
        }
        return X;
    }


    public int getNumElements() {
        return cols() * rows();
    }
    
    public int getNumElementsLarger(double val2Compare) {
    	
        int ind = 0;
        for (int i = 0; i < rows(); i++) {
            for (int j = 0; j < cols(); j++) {
                if(data[i][j]>val2Compare)
                	ind++;
            }
        }
    	
        return ind;
    }
    
    public int getNumElementsEqual(double val2Compare) {
    	
        int ind = 0;
        for (int i = 0; i < rows(); i++) {
            for (int j = 0; j < cols(); j++) {
                if(data[i][j]==val2Compare)
                	ind++;
            }
        }
    	
        return ind;
    }

    /**
     * Log function from the class Math .
     * @return matrix with the natural logarithm (base e) of a double value.
     */
    public Matrix log() {
        Matrix maResult = new Matrix(getRowDim(), getColDim());
        for (int i = 0; i < rows(); i++) {
            for (int j = 0; j < cols(); j++) {
                if(data[i][j] > 0) {
                    maResult.data[i][j] = Math.log(data[i][j]);
                } else {
                    maResult.data[i][j] = 0.0;

                }
            }
        }
        return maResult;
    }

    public Matrix diagonalize() {
        Matrix maResult = new Matrix();

        if((getRowDim() > 1) && (cols() == 1)) {
            maResult.resize(rows(), rows());
            for (int i = 0; i < maResult.rows(); i++) {
                maResult.data[i][i] = data[i][0];
            }
        } else if((cols() > 1) && (rows() == 1)) {
            maResult.resize(cols() , cols());
            for (int i = 0; i < maResult.cols(); i++) {
                maResult.data[i][i] = data[0][i];
            }
        }

        return maResult;
    }

    public Matrix devide(double dDivisor) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                maResult.data[i][j] = data[i][j] / dDivisor;
            }
        }
        return maResult;
    }

    public Matrix devide(Matrix maDivisor) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                maResult.data[i][j] = data[i][j] / maDivisor.data[i][j];
            }
        }
        return maResult;
    }

    public Matrix devideDivisorBigger(Matrix maDivisor) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                if(data[i][j] < maDivisor.data[i][j]) {
                    maResult.data[i][j] = data[i][j] / maDivisor.data[i][j];
                } else {
                    maResult.data[i][j] = maDivisor.data[i][j] / data[i][j] ;
                }
            }
        }
        return maResult;
    }

    public Matrix devideCols(Matrix maDivisor) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                maResult.data[i][j] = data[i][j] / maDivisor.get(0,j);
            }
        }
        return maResult;
    }

    public void devideRow(int row, double denominator) {
        for (int j = 0; j < getColDim(); j++) {
            data[row][j] /= denominator;
        }
    }

/*
    public static void getEigenvectorBig(Matrix a, int n, Matrix d, Matrix e)
    {
        mt.DenseMatrix A = new mt.DenseMatrix(a.mData);
        // mt.Matrix B = new mt.DenseMatrix(ma.mData);
        // mt.Matrix C = new mt.DenseMatrix(getRowDim(), ma.getColDim());
        // maResult = new Matrix(mt.util.Matrices.getArray(C));

        mt.fact.EigenvalueComputer evc = new mt.fact.EigenvalueComputer(a.getRowDim());
        try {
            EigenvalueDecomposition evd = evc.factor(A);
            double [] arrEV = evd.getRealEigenvalues();
            d.resize(arrEV.length,1);
            for (int ii = 0; ii < arrEV.length; ii++) {
              d.set(ii, 0, arrEV[ii]);
            }
            a.set(mt.util.Matrices.getArray(evd.getLeftEigenvectors()));
            e.set(mt.util.Matrices.getArray(evd.getRightEigenvectors()));
        }
        catch (NotConvergedException ex) {
            ex.printStackTrace();
        }
   }
*/
    /**
     * Householder reduction according num rec 11.2.
     * Followed from an Eigen value decomposition with the calculation of the
     * Eigen vectors according num rec 11.3.
     * @param A intercept input matrix
     * @param n number of considered eigen values
     * @param D
     * @param E
     */

    final public static void getEigenvector(Matrix A, int n, Matrix D, Matrix E) {
        int l,k,j, i;
        double scale,hh,h,g,f;

       double [] a = A.toArray();
       double [] d = new double [n]; 
       double [] e = new double [n];
       
       int cols = n;
       
       // Householder reduction of a real, symmetric matrix
       for (i = n-1; i >= 1; i--) {
           l = i;
           h = scale = 0.0;
           if (l > 0) {
               for (k = 0; k < l; k++)
                   scale += Math.abs(a[i * cols + k]);
               
               if (scale == 0.0)
                   e[i] = a[i * cols + l-1];
               
               else {
            	   
                   for (k = 0; k < l; k++) {
                       a[i * cols + k] /= scale;
                       h += a[i * cols + k] * a[i * cols + k];
                   }
                   
                   f = a[i * cols + l-1];
                   g = (f >= 0.0 ? - Math.sqrt(h) : Math.sqrt(h));
                   e[i] = scale * g;
                   h -= f * g;
                   a[i * cols + l-1] = f - g;
                   f = 0.0;
                   
                   for (j = 0; j < l; j++) {
                       a[j * cols + i] = a[i * cols + j] / h;
                       g = 0.0;
                       
                       for (k = 0; k < j+1; k++)
                           g += a[j * cols + k] * a[i * cols + k];
                       
                       for (k = j+1; k < l; k++)
                           g += a[k * cols + j] * a[i * cols + k];
                       
                       e[j] = g / h;
                       f += e[j] * a[i * cols + j];
                   }
                   
                   hh = f / (h + h);
                   for (j = 0; j < l; j++) {
                       f=a[i * cols + j];
                       e[j]= g = e[j] - hh * f;
                       
                       for (k = 0; k < j+1; k++)
                           a[j * cols + k] -= (f * e[k] + g * a[i * cols + k]);
                   }
               }
           } else
               e[i] = a[i * cols + l-1];
           
           d[i] = h;
       }
       
       d[0] = 0.0;
       e[0] = 0.0;
       
       // Contents of this loop can be omitted if Eigen vectors not
       // wanted except for statement d[i]=a[i * cols + i]
       for (i = 0; i < n; i++) {
           l = i;
           if (d[i] != 0) {
               for (j = 0; j < l; j++) {
                   g = 0.0;
                   for (k = 0; k < l; k++)
                       g += a[i * cols + k] * a[k * cols + j];
                   for (k = 0; k < l; k++)
                       a[k * cols + j] -= g * a[k * cols + i];
               }
           }
           
           d[i] = a[i * cols + i];
           
           a[i * cols + i] = 1.0;
           
           for (j = 0; j < l; j++)
               a[j * cols + i] = a[i * cols + j] = 0.0;
       }

       // Eigen values and Eigen vectors of a trigiagonal matrix
       // void tqli(float d[], float e[], int n, float **z)

        int m,iter;
        double s,r,p,dd,c,b;

        for (i = 1; i < n; i++)
            e[i - 1] = e[i];
        
        e[n-1] = 0.0;
        
        for (l = 0; l < n; l++) {
        	
            iter = 0;
            
            do {
                for (m = l; m <= n-2; m++) {
                    dd = Math.abs(d[m]) + Math.abs(d[m + 1]);
                    
                    if ((double)(Math.abs(e[m]) + dd) == dd)
                        break;
                }
                
                if (m != l) {
                	
                    if (iter++ == 30)
                        System.err.println("Too many iterations in tqli");
                    
                    g=(d[l+1] - d[l]) / (2.0 * e[l]);
                    
                    r = getPythag(g,1.0);
                    
                    g=d[m] - d[l] + e[l] / (g + Sign(r,g)); // ??? Sign correct
                    
                    s=c=1.0;
                    
                    p=0.0;
                    
                    for (i = m-1; i >= l; i--) {
                    	
                        f = s * e[i];
                        
                        b = c * e[i];
                        
                        e[i+1]= (r = getPythag(f,g));
                        
                        if (r == 0.0) {
                            d[i+1] -= p;
                            e[m] = 0.0;
                            break;
                        }
                        s = f / r;
                        c = g / r;
                        
                        g = d[i+1] - p;
                        
                        r = (d[i] - g) * s + 2.0 * c * b;
                        d[i+1] = g + (p = s * r);
                        g = c * r - b;
                        for (k = 0; k < n; k++) {
                            f = a[k * cols + i + 1];
                            a[k * cols + i + 1] = s * a[k * cols + i] + c * f;
                            a[k * cols + i] = c * a[k * cols + i] - s * f;
                        }
                    }
                    if (r == 0.0 && i >= l)
                        continue;
                    
                    d[l] -= p;
                    e[l] = g;
                    e[m] = 0.0;
                }
            } while (m != l);
        }

        for (int o = 0; o < A.rows(); o++) {
			for (int q = 0; q < A.cols(); q++) {
				A.set(o,q, a[o*n+q]);
			}
		}
        
        D.resize(n, 1);
        
        D.setCol(0, d);
        
        E.resize(n, 1);

        E.setCol(0, e);
        
        return;
    }
    
//    public static void getEigenvector(MatrixK a, int n, MatrixK d, MatrixK e) {
//        int l,k,j, i;
//        double scale,hh,h,g,f;
//
//       a.resize((n + 1), (n + 1));
//       d.resize((n + 1), 1);
//       e.resize((n + 1), 1);
//
//       // Shift since routine works from indices 1..n and not from 0..n-1
//       for (i = n; i > 0; i--)
//           for (j = n; j > 0; j--)
//              a.data[i][j] = a.data[i - 1][j - 1];
//
//       // Householder reduction of a real, symmetric matrix
//        for (i = n; i >= 2; i--) {
//            l = i - 1;
//            h = scale = 0.0;
//            if (l > 1) {
//                for (k = 1; k <= l; k++)
//                    scale += Math.abs(a.data[i][k]);
//                if (scale == 0.0)
//                    e.data[i][0] = a.data[i][l];
//                else {
//                    for (k = 1; k <= l; k++) {
//                        a.data[i][k] /= scale;
//                        h += a.data[i][k] * a.data[i][k];
//                    }
//                    f = a.data[i][l];
//                    g = (f >= 0.0 ? - Math.sqrt(h) : Math.sqrt(h));
//                    e.data[i][0] = scale * g;
//                    h -= f * g;
//                    a.data[i][l] = f - g;
//                    f = 0.0;
//                    for (j = 1; j <= l; j++) {
//                        a.data[j][i] = a.data[i][j] / h;
//                        g = 0.0;
//                        for (k = 1; k <= j; k++)
//                            g += a.data[j][k] * a.data[i][k];
//                        for (k = j + 1; k <= l; k++)
//                            g += a.data[k][j] * a.data[i][k];
//                        e.data[j][0] = g / h;
//                        f += e.data[j][0] * a.data[i][j];
//                    }
//                    hh = f / (h + h);
//                    for (j = 1; j <= l; j++) {
//                        f=a.data[i][j];
//                        e.data[j][0]= g = e.data[j][0] - hh * f;
//                        for (k = 1; k <= j; k++)
//                            a.data[j][k] -= (f * e.data[k][0] + g * a.data[i][k]);
//                    }
//                }
//            } else
//                e.data[i][0] = a.data[i][l];
//            d.data[i][0] = h;
//        }
//        d.data[1][0] = 0.0;
//        e.data[1][0] = 0.0;
//        // Contents of this loop can be omitted if eigenvectors not
//        // wanted except for statement d[i]=a[i][i]
//        for (i = 1; i <= n; i++) {
//            l = i - 1;
//            if (d.data[i][0] != 0) {
//                for (j = 1; j <= l; j++) {
//                    g = 0.0;
//                    for (k = 1; k <= l; k++)
//                        g += a.data[i][k] * a.data[k][j];
//                    for (k = 1; k<= l; k++)
//                        a.data[k][j] -= g * a.data[k][i];
//                }
//            }
//            d.data[i][0] = a.data[i][i];
//            a.data[i][i] = 1.0;
//            for (j = 1; j <= l; j++)
//                a.data[j][i] = a.data[i][j] = 0.0;
//        }
//
//       // Eigenvalues and Eigenvectors of a trigiagonal matrix
//       // void tqli(float d[], float e[], int n, float **z)
//
//        int m,iter;
//        double s,r,p,dd,c,b;
//
//        for (i = 2; i <= n; i++)
//            e.data[i - 1][0] = e.data[i][0];
//        e.data[n][0] = 0.0;
//        
//        for (l = 1; l <= n; l++) {
//            iter = 0;
//            do {
//                for (m = l; m <= n-1; m++) {
//                    dd = Math.abs(d.data[m][0]) + Math.abs(d.data[m + 1][0]);
//                    
//                    if ((double)(Math.abs(e.data[m][0]) + dd) == dd)
//                        break;
//                }
//                if (m != l) {
//                    if (iter++ == 30)
//                        System.err.println("Too many iterations in tqli");
//                    g=(d.data[l+1][0] - d.data[l][0]) / (2.0 * e.data[l][0]);
//                    r = getPythag(g,1.0);
//                    g=d.data[m][0] - d.data[l][0] + e.data[l][0] / (g + Sign(r,g)); // ??? Sign correct
//                    
//                    s=c=1.0;
//                    p=0.0;
//                    for (i=m-1;i>=l;i--) {
//                        f = s * e.data[i][0];
//                        b = c * e.data[i][0];
//                        e.data[i+1][0] = (r = getPythag(f,g));
//                        if (r == 0.0) {
//                            d.data[i+1][0] -= p;
//                            e.data[m][0] = 0.0;
//                            break;
//                        }
//                        s = f / r;
//                        c = g / r;
//                        g = d.data[i+1][0] - p;
//                        r = (d.data[i][0] - g) * s + 2.0 * c * b;
//                        d.data[i+1][0] = g + (p = s * r);
//                        g = c * r - b;
//                        for (k = 1; k <= n; k++) {
//                            f = a.data[k][i + 1];
//                            a.data[k][i + 1] = s * a.data[k][i] + c * f;
//                            a.data[k][i] = c * a.data[k][i] - s * f;
//                        }
//                    }
//                    if (r == 0.0 && i >= l)
//                        continue;
//                    d.data[l][0] -= p;
//                    e.data[l][0] = g;
//                    e.data[m][0] = 0.0;
//                }
//            } while (m != l);
//        }
//
//       // Shift back;
//       for(i = 0; i < n; i++)
//           for (j = 0; j < n; j++)
//              a.data[i][j] = a.data[i + 1][j + 1];
//
//       for(j = 0; j < n; j++)
//            d.data[j][0] = d.data[j + 1][0];
//
//       for(j = 0; j < n; j++)
//           e.data[j][0] = e.data[j + 1][0];
//
//       // Resize arrays properly
//       a.resize(n, n);
//       d.resize(n, 1);
//       e.resize(n, 1);
//       
//       return;
//    }
    /**
     *
     * @param row row
     * @return smallest value in specified row.
     */
    public double getMinRow(int row) {
        double val = Double.MAX_VALUE;
        for (int i = 0; i < getColDim(); i++) {
          if(data[row][i] < val) {
              val = data[row][i];
          }
        }
        return val;
    }

    /**
     *
     * @param row row
     * @return the column index of the smallest value in the row.
     */
    public int getMinRowIndex(int row) {
        int index = 0;
        double min = Double.MAX_VALUE;
        for (int i = 0; i < getColDim(); i++) {
          if(data[row][i] < min) {
              min = data[row][i];
              index = i;
          }
        }
        return index;
    }

    /**
     * 03.10.04 MvK
     * Shuffles the indices before searching the minimum. This randomizes the
     * search order.
     * @param row row
     * @return the column index of the smallest value in the row.
     */
    public int getMinRowIndexRND(int row) {
        int indexMin = 0;
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < getColDim(); i++) {
          list.add(new Integer(i));
        }
        Collections.shuffle(list);

        int [] arrIndex = new int [getColDim()];
        for (int i = 0; i < arrIndex.length; i++) {
          arrIndex[i] = ((Integer) list.get(i)).intValue();
        }

        double min = Double.MAX_VALUE;
        for (int i = 0; i < getColDim(); i++) {
            int ind = arrIndex[i];
            if (data[row][ind] < min) {
                min = data[row][ind];
                indexMin = ind;
            }
        }
        return indexMin;
    }

    public int getID() {
        return identifier;
    }

    /**
     * Skipping the sqrt.
     * @param iRow
     * @param A
     * @param iRowA
     * @return
     */
    public double getEuclideanDistanceFastRows(int iRow, Matrix A, int iRowA) {
        double distance = 0;

        int cols = cols();

        for (int i = 0; i < cols; i++) {
          distance += (data[iRow][i] - A.data[iRowA][i]) * (data[iRow][i] - A.data[iRowA][i]);
        }
        return distance;
    }

    /**
     * Skipping the sqrt.
     * @param row1
     * @param row2
     * @return
     */
    public double getEuclideanDistanceFastRows(int row1, int row2) {
        double distance = 0;

        int cols = cols();

        for (int i = 0; i < cols; i++) {
          distance += (data[row1][i] - data[row2][i]) * (data[row1][i] - data[row2][i]);
        }

        return distance;
    }

    public double getMaximumValue() {
        double max = Double.MIN_VALUE;

        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                if(max < data[i][j])
                    max = data[i][j];
            }
        }
        return max;
    }

    public double getMean() {
        int cols = getColDim();
        int rows = getRowDim();
        double mean = getSum() / (cols * rows);
        return mean;
    }

    public Matrix getMeanCols() {
        
    	final int cols = cols();
    	
    	final int rows = rows();
    	
    	final double [] a = new double [cols];

    	for (int i = 0; i < rows; i++) {
    		final double [] arrRow = data[i];
    		
    		for (int j = 0; j < arrRow.length; j++) {
    			a[j] += arrRow[j];
			}
		}

    	for (int i = 0; i < cols; i++) {
    		a[i] /= rows;
    	}

        return new Matrix(true, a);
    }

    public Matrix getMeanCols(int [] rowIndex) {
        Matrix ma = new Matrix(1, getColDim());
        int cols = getColDim();
        for (int j = 0; j < cols; j++) {
            ma.data[0][j] = getMeanCol(j, rowIndex);
        }
        return ma;
    }

    public double getMeanCol(int col) {

        int rows = rows();
        
        double sum = 0;
        
        for (int i = 0; i < rows; i++) {
            sum += data[i][col];
        }

        return sum / rows;
    }

    public double getMeanRow(int row) {

        int iCols = getColDim();
        double dMean = 0;
        for (int i = 0; i < iCols; i++) {
            dMean += data[row][i];
        }

        return dMean / iCols;
    }
    
    public Matrix getMeanRows() {
        Matrix ma = new Matrix(1, getRowDim());
        int iRows = getRowDim();
        for (int i = 0; i < iRows; i++) {
        	ma.set(0,i,getMeanRow(i));
        }
        return ma;
    }

    public double getMeanCol(int iColIndex, int [] rowIndex) {

        int iRows = getRowDim();
        double dMean = 0;
        for (int ii = 0; ii < rowIndex.length; ii++) {
            dMean += data[rowIndex[ii]][iColIndex];
        }

        return dMean / iRows;
    }
    
    public double getMedian(int row) {
    	
    	double [] arr = getRowCopy(row);
    	
    	Arrays.sort(arr);
    	
		double median = 0;
		int len = arr.length;
		if(len % 2 != 0) {
			median = arr[len / 2];
		} else {
			int ind = (int)(((double)len / 2.0)+0.5);
			median = (arr[ind] + arr[ind-1]) / 2.0;
		}
		
		return median;
    }

    public double getMedian() {
    	
    	int nRows = rows();
    	int nCols = cols();
    	
    	double [] arr = new double [nRows*nCols]; 
    	
    	int k = 0;
    	for (int i = 0; i < nRows; i++) {
			for (int j = 0; j < nCols; j++) {
				arr[k++]=get(i, j);
			}
		}
    	
    	Arrays.sort(arr);
    	
		double median = 0;
		int len = arr.length;
		if(len % 2 != 0) {
			median = arr[len / 2];
		} else {
			int ind = (int)(((double)len / 2.0)+0.5);
			median = (arr[ind] + arr[ind-1]) / 2.0;
		}
		
		return median;
    }

    public Matrix getMedianCols() {

        Matrix maMedian = new Matrix(1, cols());

    	int rows = rows();
    	int cols = cols();

    	double [] arr = new double [rows];

        for (int i = 0; i < cols; i++) {

            for (int j = 0; j < rows; j++) {
                arr[j]=get(j,i);
            }

            Arrays.sort(arr);

            double median = 0;
            int len = arr.length;
            if(len % 2 != 0) {
                median = arr[len / 2];
            } else {
                int ind = (int)(((double)len / 2.0)+0.5);
                median = (arr[ind] + arr[ind-1]) / 2.0;
            }

            maMedian.set(0, i, median);

        }

		return maMedian;
    }

    public Matrix getMergeRows(Matrix ma) {

        Matrix maMerge = new Matrix(getRowDim() + ma.getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                maMerge.data[i][j] = data[i][j];
            }
        }

        int iRow = getRowDim();
        for (int ii = 0; ii < ma.getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                maMerge.data[iRow + ii][jj] = ma.data[ii][jj];
            }
        }

        return maMerge;
    }

    /**
     * max and min vals for all cols  
     * @return matrix with two rows.
     */
    public Matrix getMaxMin() {

        Matrix maMaxMin = new Matrix(2, getColDim());

        int cols = getColDim();
        
        for (int i = 0; i < cols; i++) {
            maMaxMin.set(1,i, getMin(i));
            maMaxMin.set(0,i, getMax(i));
        }
        return maMaxMin;
    }

    /**
     *
     * @return
     */
    public double getMax() {

        int rows = getRowDim();
        int cols = getColDim();
        double dMax = -Double.MAX_VALUE;
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++)
                if(data[i][j] > dMax)
                    dMax = data[i][j];

        return dMax;
    }

    /**
     * get max value for that col.
     * @param col
     * @return
     */
    public double getMax(int col) {

        int iRows = getRowDim();
        
        double max = -Double.MAX_VALUE;
        
        for (int i = 0; i < iRows; i++) {
            if(data[i][col] > max)
                max = data[i][col];
        }
        
        return max;
    }
    
    /**
     * 
     * @return Point(row,col) for the largest value.
     */
    public Point getMaxIndex() {

    	Point p = new Point(0,0);
    	
        int rows = rows();
        
        int cols = cols();
        
        double max = data[0][0];
        
        for (int i = 0; i < rows; i++) {
        	for (int j = 0; j < cols; j++) {
                if(data[i][j] > max) {
                    max = data[i][j];
                    p.y = i;
                    p.x = j;
                }
			}
        }
        
        return p;
    }

    public double getMaxRow(int row) {
        double max = -Double.MAX_VALUE;
        for (int i = 0; i < getColDim(); i++) {
            if(data[row][i] > max)
                max = data[row][i];
        }
        return max;
    }

    /**
     * Shuffles the indices before searching the minimum.
     * @param row row
     * @return the column index of the largest value in the row.
     */
    public int getMaxRowIndexRND(int row) {
        int indexMax = 0;
        
        List<Integer> list = new ArrayList<Integer>();
        
        for (int i = 0; i < cols(); i++) {
          list.add(i);
        }
        Collections.shuffle(list);
        int [] arrIndex = new int [getColDim()];
        for (int i = 0; i < arrIndex.length; i++) {
          arrIndex[i] = ((Integer) list.get(i)).intValue();
        }
        double max = -Double.MAX_VALUE;
        for (int i = 0; i < getColDim(); i++) {
            int ind = arrIndex[i];
            if (data[row][ind] > max) {
                max = data[row][ind];
                indexMax = ind;
            }
        }
        return indexMax;
    }
    
    /**
     * @param col
     * @return the row index of the largest value in the col.
     */
    public int getMaxRowIndex(int col) {
        int row = -1;
        
        double max = -Double.MAX_VALUE;
        
        for (int i = 0; i < rows(); i++) {
            
            if (data[i][col] > max) {
                max = data[i][col];
                row = i;
            }
        }
        
        return row;
    }

    public double getMin() {

        int rows = getRowDim();
        int cols = getColDim();
        double min = Double.MAX_VALUE;
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++)
                if(data[i][j] < min)
                    min = data[i][j];

        return min;
    }
/**
 * 
 * @return ValPos
 */
    public ScorePoint getMinPos() {

        int rows = getRowDim();
        int cols = getColDim();
        
        ScorePoint td = new ScorePoint();
        
        td.setScore(Double.MAX_VALUE);
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++)
                if(data[i][j] < td.getScore()) {
                	td.y = i;
                	td.x = j;
                	td.setScore(data[i][j]);
                }

        return td;
    }
/**
 * 
 * @return matrix with minimum value from each row.
 */
    public Matrix getMinRows() {
        int rows = getRowDim();
        int cols = getColDim();
        Matrix ma = new Matrix(rows, 1);
        for (int i = 0; i < rows; i++) {
        	double dMin = Double.MAX_VALUE;
            for (int j = 0; j < cols; j++)
                if(data[i][j] < dMin)
                    dMin = data[i][j];
            ma.set(i, 0, dMin);
        }

        return ma;
    }
/**
 * 
 * @return matrix dimension n,2. First row contains column position of min val,
 * second col the min value.
 */
    public Matrix getMinRowsPosCol() {
        int rows = getRowDim();
        int cols = getColDim();
        Matrix ma = new Matrix(rows, 2);
        for (int i = 0; i < rows; i++) {
        	double dMin = Double.MAX_VALUE;
            for (int j = 0; j < cols; j++)
                if(data[i][j] < dMin) {
                	ma.set(i, 0, j);
                	ma.set(i, 1, data[i][j]);
                }
        }

        return ma;
    }

    public double getMin(int col) {

        int rows = getRowDim();
        double min = Double.MAX_VALUE;
        for (int i = 0; i < rows; i++) {
            if(data[i][col] < min)
                min = data[i][col];
        }
        return min;
    }
	/**
	 * 
	 * @param row
	 * @return column index for the field with the lowest value for the given row.
	 */
    public int getMinColIndex(int row) {

        int cols = getColDim();
        double min = Double.MAX_VALUE;
        int ind = -1;
        for (int i = 0; i < cols; i++) {
            if(data[row][i] < min) {
                min = data[row][i];
                ind=i;
            }
        }
        return ind;
    }
    
	/**
	 * 
	 * @param row
	 * @return column index for the field with the largest value for the given row.
	 */
    public int getColIndexContainingMaxVal(int row) {

        int cols = getColDim();
        double max = -Double.MAX_VALUE;
        int col = -1;
        for (int i = 0; i < cols; i++) {
            if(data[row][i] > max) {
                max = data[row][i];
                col=i;
            }
        }
        return col;
    }
    
    /**
     * 
     * @param rowEnd the values in the matrix will be considered until this row (exclusively).
     * @param colEnd the values in the matrix will be considered until this col (exclusively).
     * @return
     */
    public ScorePoint getColIndexContainingMaxVal(int rowEnd, int colEnd) {
    	
    	int cols = colEnd;
    	
    	int rows = rowEnd;
    	        
        double max = -Double.MAX_VALUE;
        
        int rowMax = -1;
        int colMax = -1;
        
        for (int i = 0; i < cols; i++) {
        	
        	double maxInCol = -Double.MAX_VALUE;
        	
        	int rowMaxInCol=-1;
        	for (int j = 0; j < rows; j++) {
        		if(data[j][i] > maxInCol) {
        			maxInCol = data[j][i];
        			rowMaxInCol=j;
                }
			}
        	
        	if(maxInCol>max){
        		max = maxInCol;
        		rowMax = rowMaxInCol;
        		colMax = i;
        	}
        }
        
        ScorePoint sc = new ScorePoint(rowMax, colMax);
        
        sc.setScore(max);
        
        return sc;
    }

    public Matrix getNormalizedMatrix() {

        int iRows = getRowDim();
        int iCols = getColDim();

        Matrix maNorm = new Matrix(iRows, iCols);
        Matrix maStandardDeviation = getStandardDeviationCols();
        for (int ii = 0; ii < iRows; ii++)
            for (int jj = 0; jj < iCols; jj++) {
                double dStandardDeviation = maStandardDeviation.get(0, jj);
                if (dStandardDeviation == 0) {
                    dStandardDeviation = Float.MIN_VALUE;
                }
                double dVal = get(ii, jj) / dStandardDeviation;
                maNorm.set(ii, jj, dVal);
            }
        return maNorm;
    }

    public Matrix getNormalizedMatrix(Matrix maStandardDeviation) {

        int iRows = getRowDim();
        int iCols = getColDim();

        Matrix maNorm = new Matrix(iRows, iCols);

        for (int ii = 0; ii < iRows; ii++)
            for (int jj = 0; jj < iCols; jj++) {
                double dStandardDeviation = maStandardDeviation.get(0, jj);
                if (dStandardDeviation == 0) {
                    dStandardDeviation = Float.MIN_VALUE;
                }
                double dVal = get(ii, jj) / dStandardDeviation;
                maNorm.set(ii, jj, dVal);
            }
        return maNorm;
    }

    /**
     * For a quadratic matrix only.
     * @return
     */
    public double [] getUpperTriangle(){

        int r = rows();
        int c = cols();

        if(r != c){
            throw new RuntimeException("Not a quadratic matrix.");
        }

        int n = ((r * r) - r)/2;

        double [] a = new double[n];

        int cc = 0;
        for (int i = 0; i < r; i++) {

            for (int j = i+1; j < r; j++) {
                a[cc++] = get(i,j);
            }
        }

        return a;
    }

    /**
     *pythag computes sqrt(a^2 + b^2) without destructive underflow or overflow.
     * @param a length a
     * @param b length b
     * @return double
     */
    public static double getPythag(double a, double b)
    {
      double absa = Math.abs(a);
      double absb = Math.abs(b);
      if (absa > absb) {
          double dScale = (absb / absa) * (absb / absa);
          return absa * Math.sqrt(1.0 + dScale);
      }
      else if(absb == 0.0)
          return 0.0;
      else {
          double dScale = (absa / absb) * (absa / absb);
          return (absb * Math.sqrt(1.0 + dScale));
      }
    }

    public static double getPythag2(double a, double b)
    {
        return Math.sqrt((a * a) + (b * b));
    }


    public boolean areRowsEqual(int row1, int row2) {

        boolean equal = true;

        int cols = cols();

        for (int i = 0; i < cols; i++) {

            double diff = Math.abs(data[row1][i]-data[row2][i]);

            if(diff>TINY){
                equal=false;
                break;
            }
        }

        return equal;
    }

    public boolean equal(Matrix ma) {
        boolean bEQ = true;

        if(equalDimension(ma) == true) {
            for (int i = 0; i < getRowDim(); i++) {
                for (int j = 0; j < getColDim(); j++) {
                    if(data[i][j] != ma.data[i][j]) {
                        bEQ = false;
                        break;
                    }
                }
            }
        } else {
            bEQ = false;
        }

        return bEQ;
    }

    public boolean equal(Matrix ma, double dLimit) {
        boolean bEQ = true;

        if(equalDimension(ma) == true) {
            for (int i = 0; i < getRowDim(); i++) {
                for (int j = 0; j < getColDim(); j++) {
                    double dDiff = Math.abs(data[i][j] - ma.data[i][j]);
                    if(dDiff > dLimit) {
                        bEQ = false;
                        break;
                    }
                }
            }
        } else {
            bEQ = false;
        }

        return bEQ;
    }

    /**
     * Checks two matrices for equal dimensions.
     * @param ma matrix to compare with.
     * @return true if the number of getColDim and the number of getRowDim are
     * corresponding.
     */
    public boolean equalDimension(Matrix ma) {
        boolean bEqual = false;

        if((getColDim() == ma.getColDim()) && (getRowDim() == ma.getRowDim()))
            bEqual = true;

        return bEqual;
    }

    public boolean hasOnlyFinite() {

        boolean finite = true;

        int r = rows();

        int c = cols();

        ma:
        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if(!Double.isFinite(data[i][j])){
                    finite = false;
                    break ma;
                }
            }
        }

        return finite;

    }
    /**
     * Value by value multiplication
     * Matrices must have the same dimensions.
     * @param ma
     * @return
     */
    public Matrix multiplyValByVal(Matrix ma) {
    	
    	Matrix maProd = new Matrix(rows(), cols());
    	
    	for (int i = 0; i < rows(); i++) {
			for (int j = 0; j < cols(); j++) {
				maProd.set(i, j, get(i,j) * ma.get(i, j));
			}
		}
    	
    	return maProd;
    	
    }

    
    public Matrix multCols(Matrix maMuiltiplicant) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                maResult.data[i][j] = data[i][j] * maMuiltiplicant.get(0,j);
            }
        }
        return maResult;
    }

    public Matrix multiply(double dScalar) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                maResult.data[i][j] = data[i][j] * dScalar;
            }
        }

        return maResult;
    }

    public Matrix multiply(Matrix ma) {
        return multiply(false,false,ma);
    }

    public double multiply(int row1, Matrix ma2, int row2) {
        double sum = 0;
        for (int ii = 0; ii < getColDim(); ii++) {
            sum += data[row1][ii] * ma2.data[row2][ii];
        }
        return sum;
    }


    /**
     * Multiplication of two matrices. The algorithm checks for the correct
     * dimensions of the matrices. If the dimensions are not corresponding a
     * runtime exception is thrown. The left matrix is the instantiated object.
     * @param transA if true the left matrix is transposed before the
     * multiplication
     * @param transB if true the right matrix is transposed before the
     * multiplication
     * @param ma right matrix
     * @return matrix with dimension getRowDim(), ma.getColDim().
     */
//    public MatrixK multiply(boolean transA, boolean transB, MatrixK ma) {
//        MatrixK maResult = null;
//
//
//        // Tasks:
//        // Computes
//        // C = A * B
//        // C = A' * B
//        // C = A * B'
//        // C = A' * B'
//        //
//        
//        // C = A * B
//        if ( (transA == false) && (transB == false)) {
//        	MatrixK B = ma.getTranspose();
//            
//        	maResult = multATransB(B);
//
//        }
//
//        //
//        // C = A' * B
//        //
//        // Matrix A is transposed
//        if ( (transA == true) && (transB == false)) {
//
//        	MatrixK A = getTranspose();
//        	MatrixK B = ma.getTranspose();
//            
//        	maResult = A.multATransB(B);
//            
//        } 
//
//        //
//        // C = A * B'
//        //
//        // Matrix B is transposed
//        if ( (transA == false) && (transB == true)) {
//        	maResult = multATransB(ma);
//        } 
//
//        //
//        // C = A' * B'
//        //
//        // Matrix A + B are transposed
//        if ( (transA == true) && (transB == true)) {
//        	MatrixK A = getTranspose();
//        	maResult = A.multATransB(ma);
//        }
//
//        return maResult;
//    }
    
    
    
//    private MatrixK multATransB(MatrixK ma){
//        //
//        // C = A * B'
//        //
//        int n = cols();
//        
//        int m = rows();
//        
//        int maRows = ma.rows();
//        
//        if (n != ma.cols()) {
//            throw new RuntimeException("Error in Routine SMatrix::Mult(). Attempt to calculate the product of two incompatible matrices. Do nothing and return.");
//        }
//
//        MatrixK maResult = new MatrixK(rows(), maRows);
//        
//        double[][] C = maResult.getArray();
//        
//        double[] Bcolj = new double[n];
//        
//        for (int j = 0; j < maRows; j++) {
//           for (int k = 0; k < n; k++) {
//              Bcolj[k] = ma.data[j][k];
//           }
//           for (int i = 0; i < m; i++) {
//              double[] Arowi = data[i];
//              double s = 0;
//              for (int k = 0; k < n; k++) {
//                 s += Arowi[k]*Bcolj[k];
//              }
//              C[i][j] = s;
//           }
//        }
//        return maResult;
//    }
    
    
    /**
     * Multiplication of two matrices. The algorithm checks for the correct
     * dimensions of the matrices. If the dimensions are not corresponding a
     * runtime exception is thrown. The left matrix is the instantiated object.
     * Algorithm taken from Sedgewick.
     * @param transA if true the left matrix is transposed before the
     * multiplication
     * @param transB if true the right matrix is transposed before the
     * multiplication
     * @param ma right matrix
     * @return matrix with dimension getRowDim(), ma.getColDim().
     */
    
    final public Matrix multiply(boolean transA, boolean transB, Matrix ma) {
        Matrix maResult = null;


        // Tasks:
        // Computes
        // C = A * B
        // C = A' * B
        // C = A * B'
        // C = A' * B'
        //
        
        // C = A * B
        if ( (transA == false) && (transB == false)) {
        	
            int n = cols();
            
            int m = rows();
            
            int maCols = ma.cols();
            
            if (n != ma.rows()) {
                throw new RuntimeException("Error in Routine Matrix.multiply(...). Attempt to calculate the product of two incompatible matrices. Do nothing and return.");
            }

            maResult = new Matrix(rows(), ma.cols());
            
            double[][] C = maResult.getArray();
            
            Matrix Btrans = ma.getTranspose(); 
            
            double[][] bTrans = Btrans.getArray(); 
            
            double [] Bcolj = null;
            
            double [] Arowi = null;
            
            int c4 = n/4 * 4;
            
            for (int j = 0; j < maCols; j++) {
               
               Bcolj = bTrans[j];
               
               for (int i = 0; i < m; i++) {
                  Arowi = data[i];
                  
                  double s = 0;
                  
                  for (int k = 0; k < c4; k+=4) {
                     
                	  final double s1 = Arowi[k]*Bcolj[k];
                	  final double s2 = Arowi[k+1]*Bcolj[k+1];
                	  final double s3 = Arowi[k+2]*Bcolj[k+2];
                	  final double s4 = Arowi[k+3]*Bcolj[k+3];
                	  
                	  s += s1+s2+s3+s4;
                  }
                  
                  for (int k = c4; k < n; k++) {
                      s += Arowi[k]*Bcolj[k];
                  }
                                    
                  C[i][j] = s;
               }
            }

        }

        //
        // C = A' * B
        //
        // Matrix A is transposed
        if ( (transA == true) && (transB == false)) {

            // Reverse n
            int n = rows();
            
            int m = cols();
            
            int maCols = ma.cols();
            
            if (n != ma.rows()) {
                throw new RuntimeException("Error in Routine SMatrix::Mult(). Attempt to calculate the product of two incompatible matrices. Do nothing and return.");
            }
            
            // Perform C = A' * B
            maResult = new Matrix(cols(), ma.cols());
            
            double[][] C = maResult.getArray();
            
            double [] Bcolj = null;
            
            int c4 = n/4 * 4;
            
            Matrix Atrans = getTranspose(); 
            
            double [][] aTrans = Atrans.getArray();
            
            Matrix Btrans = ma.getTranspose(); 
            
            double[][] bTrans = Btrans.getArray();
            
            double [] Arowi = null;
            
            for (int j = 0; j < maCols; j++) {
               
               Bcolj = bTrans[j];
               
               for (int i = 0; i < m; i++) {
            	   
            	  Arowi = aTrans[i];
                  double s = 0;
                  
                  for (int k = 0; k < c4; k+=4) {
                      
                	  final double s1 = Arowi[k]*Bcolj[k];
                	  final double s2 = Arowi[k+1]*Bcolj[k+1];
                	  final double s3 = Arowi[k+2]*Bcolj[k+2];
                	  final double s4 = Arowi[k+3]*Bcolj[k+3];
                 	  
                 	  s += s1+s2+s3+s4;
                   }
                   
                   for (int k = c4; k < n; k++) {
                       s += Arowi[k]*Bcolj[k];
                   }
                   
                  C[i][j] = s;
               }
            }
            
        } 

        //
        // C = A * B'
        //
        // Matrix B is transposed
        if ( (transA == false) && (transB == true)) {
            int n = cols();
            
            int m = rows();
            
            int maRows = ma.rows();
            
            if (n != ma.cols()) {
                throw new RuntimeException("Error in Routine SMatrix::Mult(). Attempt to calculate the product of two incompatible matrices. Do nothing and return.");
            }

            maResult = new Matrix(rows(), maRows);
            
            double[][] C = maResult.getArray();
            
            double [] Bcolj = null;
            
            int c4 = n/4 * 4;
            
            double [] Arowi = null;
            
            for (int j = 0; j < maRows; j++) {
               
               Bcolj = ma.data[j];
               
               for (int i = 0; i < m; i++) {
            	   
                  Arowi = data[i];
                  
                  double s = 0;
                  
                  for (int k = 0; k < c4; k+=4) {
                      
                	  final double s1 = Arowi[k]*Bcolj[k];
                	  final double s2 = Arowi[k+1]*Bcolj[k+1];
                	  final double s3 = Arowi[k+2]*Bcolj[k+2];
                	  final double s4 = Arowi[k+3]*Bcolj[k+3];
                 	  
                 	  s += s1+s2+s3+s4;
                   }
                   
                   for (int k = c4; k < n; k++) {
                       s += Arowi[k]*Bcolj[k];
                   }
                  
                  C[i][j] = s;
               }
            }
        } 

        //
        // C = A' * B'
        //
        // Matrix A + B are transposed
        if ( (transA == true) && (transB == true)) {
        	
            // Reverse n
            int n = rows();
            
            int m = cols();
            
            int maRows = ma.rows();
            
            if (n != ma.cols()) {
                throw new RuntimeException("Error in Routine SMatrix::Mult(). Attempt to calculate the product of two incompatible matrices. Do nothing and return.");
            }
            
            // Perform C = A' * B'
            maResult = new Matrix(cols(), maRows);
            
            double[][] C = maResult.getArray();
            
            double [] Bcolj = null;
            
            int c4 = n/4 * 4;
            
            Matrix Atrans = getTranspose(); 
            
            double [][] aTrans = Atrans.getArray();
            
            double [] Arowi = null;
            
            for (int j = 0; j < maRows; j++) {
               
               Bcolj = ma.data[j];
               
               for (int i = 0; i < m; i++) {
            	   
             	  Arowi = aTrans[i];
             	  
                  double s = 0;
                  
                  for (int k = 0; k < c4; k+=4) {
                      
                	  final double s1 = Arowi[k]*Bcolj[k];
                	  final double s2 = Arowi[k+1]*Bcolj[k+1];
                	  final double s3 = Arowi[k+2]*Bcolj[k+2];
                	  final double s4 = Arowi[k+3]*Bcolj[k+3];
                 	  
                 	  s += s1+s2+s3+s4;
                   }
                   
                   for (int k = c4; k < n; k++) {
                       s += Arowi[k]*Bcolj[k];
                   }
                   
                  C[i][j] = s;
               }
            }
        }

        return maResult;
    }
    
/*
    public Matrix multiplyBig(boolean bTransA, boolean bTransB, Matrix ma) {
        Matrix maResult = new Matrix(getRowDim(), ma.getColDim());


        // Tasks:
        // Computes
        // C = A * B
        // C = A' * B
        // C = A * B'
        // C = A' * B'
        //
        // ==>> where *this.Val == C
        //
        // Input:
        // A() n x m matrix
        // B() s x r matrix, where m == s
        //
        // Output:
        // C() n x r matrix (*this.Val is changed)
        //
        // Requirements:
        // m = s
        //
        // Resulting dimensions of matrix product:
        // Matrix C => n x r (C == *this.Val)
        //
        // Remark:
        // For Var/Covar-Matrix the routine requires a centered Matrix
        // => more options could be added here

        int i, j, k, n, m, s, r;
        //
        // C = A * B
        //
        // No matrix should be transposed
        if ( (bTransA == false) && (bTransB == false)) {
            mt.Matrix A = new mt.DenseMatrix(mData);
            mt.Matrix B = new mt.DenseMatrix(ma.mData);
            mt.Matrix C = new mt.DenseMatrix(getRowDim(), ma.getColDim());
            C = A.mult(B,C);
            maResult = new Matrix(mt.util.Matrices.getArray(C));

        } // End if

        //
        // C = A' * B
        //
        // Matrix A should be transposed
        if ( (bTransA == true) && (bTransB == false)) {

            mt.Matrix A = new mt.DenseMatrix(mData);
            mt.Matrix B = new mt.DenseMatrix(ma.mData);
            mt.Matrix C = new mt.DenseMatrix(getColDim(), ma.getColDim());
            C = A.transAmult(B,C);
            maResult = new Matrix(mt.util.Matrices.getArray(C));

        } // End if

        //
        // C = A * B'
        //
        // Matrix B should be transposed
        if ( (bTransA == false) && (bTransB == true)) {
            mt.Matrix A = new mt.DenseMatrix(mData);
            mt.Matrix B = new mt.DenseMatrix(ma.mData);
            mt.Matrix C = new mt.DenseMatrix(getRowDim(), ma.getRowDim());
            C = A.transBmult(B,C);
            maResult = new Matrix(mt.util.Matrices.getArray(C));
        } // End if

        //
        // C = A' * B'
        //
        // Matrix A + B should be transposed
        if ( (bTransA == true) && (bTransB == true)) {
            mt.Matrix A = new mt.DenseMatrix(mData);
            mt.Matrix B = new mt.DenseMatrix(ma.mData);
            mt.Matrix C = new mt.DenseMatrix(getColDim(), ma.getRowDim());
            C = A.transABmult(B,C);
            maResult = new Matrix(mt.util.Matrices.getArray(C));
        }

        return maResult;
    }
*/

/**
 * Parses an vector with Strings and converts it to a Matrix.
 * @param vecStringMatrix vector with strings.
 * @return Matrix
 */
    public static Matrix getParsed(Vector<String> vecStringMatrix) throws Exception {

        Matrix ma = new Matrix();
        String sRow = vecStringMatrix.get(0);

        int iLen = 0;

        double[] dArr = String2DoubleArray.convert(sRow);
        iLen = dArr.length;

        ma.resize(vecStringMatrix.size(), dArr.length);

        for (int ii = 0; ii < dArr.length; ii++) {
            ma.set(0, ii, dArr[ii]);
        }

        for (int ii = 1; ii < vecStringMatrix.size(); ii++) {
            sRow = (String) vecStringMatrix.get(ii);
            dArr = String2DoubleArray.convert(sRow);

            if (iLen != dArr.length) {
                System.err.println(
                    "Vectors for matrix generation differ in length");
                (new RuntimeException()).printStackTrace();
                break;
            }

            for (int jj = 0; jj < dArr.length; jj++) {
                ma.set(ii, jj, dArr[jj]);
            }
        }
        return ma;
    }


    public Matrix plus(Matrix ma) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());
        if(!equalDimension(ma)) {
            throw new RuntimeException("Matrices have wrong dimensions.");
        }

        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                maResult.data[ii][jj] = data[ii][jj] + ma.data[ii][jj];
            }
        }
        return maResult;
    }

    public Matrix pow(double exp) {
        Matrix ma = new Matrix(getRowDim(), getColDim());
        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                ma.data[i][j] = Math.pow(data[i][j], exp);
            }
        }
        return ma;
    }

    /** Matrix determinant
     @return     determinant
     */

    public double det () {
        return new LUDecomposition(this).det();
    }

    /** Matrix rank
     @return     effective numerical rank, obtained from SVD.
     */



    /** Matrix condition (2 norm)
     @return     ratio of largest to smallest singular value.
     */



    /** Matrix trace.
     @return     sum of the diagonal elements.
     */


    /**
     * Resizes the matrix, the old data are written to the new matrix, if
     * possible.
     * @param rowsNew new number of getRowDim.
     * @param colsNew new number of getColDim.
     */
    public void resize(int rowsNew, int colsNew) {
        double [][] arrTmp = new double [rowsNew][];

        int rowsMin = Math.min(rows(), rowsNew);
        
        int colsMin = Math.min(cols(), colsNew);

        for (int i = 0; i < rowsMin; i++) {
			double [] a = new double [colsNew];
			
			System.arraycopy(data[i], 0, a, 0, colsMin);
			
			arrTmp[i]=a;
			
		}
        
        for (int i = rowsMin; i < rowsNew; i++) {
			double [] a = new double [colsNew];
			
			arrTmp[i]=a;
			
		}
        
        data = arrTmp;
    }
    
    
    
    
//    public void resize(int iNumberRowsNew, int iNumberColsNew) {
//        double [][] dTmp = new double [iNumberRowsNew][iNumberColsNew];
//
//        int iRows = iNumberRowsNew;
//        int iCols = iNumberColsNew;
//
//        if(iNumberRowsNew > getRowDim())
//            iRows = getRowDim();
//        if(iNumberColsNew > getColDim())
//            iCols = getColDim();
//        for (int ii = 0; ii < iRows; ii++) {
//            for (int jj = 0; jj < iCols; jj++) {
//                dTmp[ii][jj] = data[ii][jj];
//            }
//        }
//        data = dTmp;
//    }

    /**
     * Flat copy.
     * @param row
     * @return
     */
    public double [] getRow(int row) {
        return data[row];
    }

    public double [] getRowCopy(int row) {
    	double [] arr = new double[data[0].length];
    	for (int i = 0; i < arr.length; i++) {
			arr[i] = data[row][i];
		}
        return arr;
    }

    public List<Double> getRowAsList(int row) {
        List<Double> list = new ArrayList<Double>();
        for (int ii = 0; ii < data[0].length; ii++) {
          list.add(new Double(data[row][ii]));
        }
        return list;
    }
    
    public float [] getRowAsFloat(int row) {
    	float [] arr = new float [data[0].length];
        for (int i = 0; i < data[0].length; i++) {
        	arr[i]=(float)data[row][i];
        }
        return arr;
    }

    public int getRowDim() {
        return data.length;
    }
    public int rows() {
        return data.length;
    }
    /**
     * Sorts the row of the matrix according the compareTo(...) function in
     * DoubleVec
     * @return sorted matrix
     */
    public Matrix getSorted() {
    	
        Matrix ma = new Matrix(getRowDim(), getColDim());
        
        List<DoubleVec> list = new ArrayList<DoubleVec>();
        
        for (int i = 0; i < data.length; i++) {
          list.add(new DoubleVec(data[i]));
        }
        Collections.sort(list);
        for (int i = 0; i < data.length; i++) {
          ma.assignRow(i, (DoubleVec) list.get(i));
        }
        return ma;
    }

    public Matrix getSQRT() {
        Matrix ma = new Matrix(getRowDim(), getColDim());
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                ma.data[i][j] = Math.sqrt(data[i][j]);
            }
        }
        return ma;
    }

    public double getSquaredSum() {
        double s = 0;
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                s += data[i][j] * data[i][j];
            }
        }
        return s;
    }

    public Matrix getStandardDeviationCols() {
        
        Matrix maMean = getMeanCols();

        final double [] arrMeanCols = maMean.getRow(0);
    	
    	final int cols = cols();
    	
    	final int rows = rows();
    	
    	final double [] arrStdvCols = new double [cols];
        
        for (int i = 0; i < rows; i++) {
            
            final double [] arrRow = data[i];
            for (int j = 0; j < cols; j++) {
            	arrStdvCols[j] += (arrRow[j] - arrMeanCols[j]) * (arrRow[j] - arrMeanCols[j]);
            }
        }
        
        for (int i = 0; i < cols; i++) {
        	double sdv = Math.sqrt(arrStdvCols[i] / (rows - 1));
        	
        	arrStdvCols[i] = sdv;
        }
        
        return new Matrix(true, arrStdvCols);
    }
    
    public double getStandardDeviation() {
        
    	double sdv=0;
    	
    	double n = rows()*cols();
    	
    	double mean = getMean();
    	
    	double sum=0;
    	for (int i = 0; i < data.length; i++) {
    		for (int j = 0; j < data[0].length; j++) {
    			sum += (data[i][j]-mean)*(data[i][j]-mean);
    		}
		}
    	
    	sdv = Math.sqrt(sum / (n - 1));
        
        return sdv;
    }

    public double getVariance() {
        double var = 0;

        double mean = getMean();
        double sum = 0;
        for (int j = 0; j < data[0].length; j++) {
            for (int i = 0; i < data.length; i++) {
                sum += (data[i][j] - mean) * (data[i][j] - mean);
            }
        }

        var = sum / (getNumElements() - 1);

        return var;
    }
    /**
     * Coefficient of variation (CV) is a normalized measure of dispersion of a probability distribution. 
     * It is defined as the ratio of the standard deviation sigma to the mean mu.
     * @return
     */
    public double getCoefficientVariation() {

        double mean = getMean();
        double sum = 0;
        for (int j = 0; j < data[0].length; j++) {
            for (int i = 0; i < data.length; i++) {
                sum += (data[i][j] - mean) * (data[i][j] - mean);
            }
        }

        double var = sum / (getNumElements() - 1);

        double sdv = Math.sqrt(var);
        
        double varCoeff=sdv/mean;
        
        return varCoeff;
    }

    public double getVarianceCol(int col) {
        double var = 0;

        double mean = getMeanCol(col);
        double dSum = 0;
        for (int i = 0; i < data.length; i++) {
        	dSum += (data[i][col] - mean) * (data[i][col] - mean);
        }

        var = dSum / (data.length - 1.0);

        return var;
    }

    public double getVarianceCentered() {
        double var = 0;

        double sum = 0;
        for (int j = 0; j < data[0].length; j++) {
            for (int i = 0; i < data.length; i++) {
                sum += (data[i][j]) * (data[i][j]);
            }
        }

        var = sum / (getNumElements() - 1);

        return var;
    }

    /**
     * 
     * @return row with variance
     */
    public Matrix getVarianceCols() {
        Matrix ma = new Matrix(1, getColDim());
        Matrix maMean = getMeanCols();

        for (int j = 0; j < data[0].length; j++) {
        	
            double sum = 0;
            
            for (int i = 0; i < data.length; i++) {
                sum += (data[i][j] - maMean.data[0][j]) * (data[i][j] - maMean.data[0][j]);
            }
            
            double dVariance = sum / (getRowDim() - 1);
            
            ma.data[0][j] = dVariance;
        }
        return ma;
    }

    public void increase(int row, int col, double v){
    	data[row][col]+=v;
    }
    
    final public void set(final int row, final int col, final double v) {
        data[row][col] = v;
    }

    public void set(Matrix ma) {
        resize(ma.getRowDim(), ma.getColDim());
        for (int ii = 0; ii < data.length; ii++) {
            for (int jj = 0; jj < data[0].length; jj++) {
                data[ii][jj] = ma.data[ii][jj];
            }
        }
    }

    public void set(double [][] arr) {
        resize(arr.length, arr[0].length);
        for (int ii = 0; ii < data.length; ii++) {
            for (int jj = 0; jj < data[0].length; jj++) {
                data[ii][jj] = arr[ii][jj];
            }
        }
    }
    
    public void setFlat(double [][] arr) {
    	data = arr;
    }

    public void set(double v) {
        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                data[i][j] = v;
            }
        }
    }

    public void setID(int id) {
        identifier = id;
    }

    public void setCol(int col, double v) {
        for (int i = 0; i < data.length; i++)
            data[i][col] = v;
    }
    
    public void setCol(int col, double [] arr) {
        for (int i = 0; i < data.length; i++)
            data[i][col] = arr[i];
    }
    
    public void setRow(int iRow, double v) {
        for (int i = 0; i < data[0].length; i++)
            data[iRow][i] = v;
    }

    /**
     * Deep copy
     * @param iRow
     * @param arr
     */
    public void setRow(int iRow, double [] arr) {
        for (int i = 0; i < data[0].length; i++)
            data[iRow][i] = arr[i];
    }
    
    public void setRow(int iRow, int [] arr) {
        for (int i = 0; i < data[0].length; i++)
            data[iRow][i] = arr[i];
    }

    public static void setSeparatorCol(String s) {
        OUT_SEPARATOR_COL = s;
    }

    public static void setSeparatorRow(String s) {
        OUT_SEPARATOR_ROW = s;
    }


    public void shuffleRows() {
        Random rnd = new Random();
        int r = rows();
        for(int i = r; i > 1; --i) {
            swapRows(i, rnd.nextInt(i));
        }
    }

    public void swapRows(int a, int b) {
        double [] t = data[a];
        data[a]=data[b];
        data[b]=t;
    }

    public void sortRows(int col){
        int r= rows();
        List<IntegerDouble> li = new ArrayList<>(r);
        for (int i = 0; i < r; i++) {
            li.add(new IntegerDouble(i, get(i, col)));
        }
        Collections.sort(li, IntegerDouble.getComparatorDouble());
        double [][] dataSorted = new double[r][];
        for (int i = 0; i < r; i++) {
            dataSorted[i]=data[li.get(i).getInt()];
        }
        data = dataSorted;
    }

    /**
     * Get the standard scores, also known as z-scores.
     * @return matrux with standardized values.
     */
    public Matrix getStandardized() {

        Matrix ma = new Matrix(getRowDim(), getColDim());
        Matrix maStandardDeviation = getStandardDeviationCols();
        Matrix Xc = getCenteredMatrix();

        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
                //double d = Xc.mData[ii][jj] / maStandardDeviation.mData[0][jj];
                ma.data[i][j] = Xc.data[i][j] / maStandardDeviation.data[0][j];
            }
        }
        return ma;
    }

    /**
     *
     * @param indexRowStart start row included
     * @param indexRowEnd end row included
     * @param indexColStart start col included
     * @param indexColEnd end col included
     * @return matrix
     */

    public Matrix getSubMatrix(int indexRowStart, int indexRowEnd, int indexColStart, int indexColEnd) {

        int rows = indexRowEnd - indexRowStart + 1;
        int cols = indexColEnd - indexColStart + 1;

        Matrix ma = new Matrix();

        double [][] arr = new double [rows][cols];
        
        for (int i = 0; i < rows; i++) {
        	System.arraycopy(data[indexRowStart+i], indexColStart, arr[i], 0, cols);
        }
        
        ma.data = arr;
        
        return ma;
    }
    
    public Matrix getSubMatrix(List<Integer> liIndexRow) {

        int cols = cols();

        Matrix ma = new Matrix();

        double [][] arr = new double [liIndexRow.size()][cols];
        
        int row=0;
        for (int i = 0; i < liIndexRow.size(); i++) {
        	System.arraycopy(data[liIndexRow.get(i)], 0, arr[row], 0, cols);
        	row++;
        }
        
        ma.data = arr;
        
        return ma;
    }

    public double getSum() {
        double sum = 0;
        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                sum += data[i][j];
            }
        }
        return sum;
    }

    /**
     * Matrix has to be quadratic.
     * @return
     */
    public double getSumUpperTriangle() {
    	
    	if(rows()!=cols()){
    		throw new RuntimeException("Not a quadratic matrix.");
    	}
    	
        int rows = cols();

		double sum=0;
		for (int i = 0; i < rows; i++) {
			for (int j = i+1; j < rows; j++) {
				if(!Double.isNaN(get(i, j))) {
					sum += get(i, j);
				}
			}
		}

		return sum;
    }
    
    
    public Matrix getSumCols() {
        Matrix ma = new Matrix(1,getColDim());
        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                ma.data[0][j] += data[i][j];
            }
        }
        return ma;
    }
    
    public Matrix getSumRows() {
        Matrix ma = new Matrix(1,getRowDim());
        for (int i = 0; i < getRowDim(); i++) {
           ma.set(0, i, getSumRow(i));
        }
        return ma;
    }

    public double getSumCol(int col) {
        double sum = 0;
        for (int i = 0; i < getRowDim(); i++) {
                sum += data[i][col];
            
        }
        return sum;
    }

    public double getSumRow(int row) {
        double sum = 0;
        for (int i = 0; i < getColDim(); i++) {
                sum += data[row][i];
            
        }
        return sum;
    }
    
    public double getSumSquared() {
        double sum = 0;
        for (int i = 0; i < getRowDim(); i++) {
            for (int j = 0; j < getColDim(); j++) {
                sum += (data[i][j] * data[i][j]);
            }
        }
        return sum;
    }

    public double [] getNext4NeighboursTorus(int row, int col) {
        double [] arr = new double [4];

        // value above
        arr[0] = getTorus(row - 1, col);
        // left
        arr[1] = getTorus(row, col - 1);
        // right
        arr[2] = getTorus(row, col + 1);
        // down
        arr[3] = getTorus(row + 1, col);

        return arr;
    }

    public double [] getNext8NeighboursTorus(int row, int col) {
        double [] arr = new double [8];

        int cc = 0;
        for (int i = -1; i < 2; i++) {
            for (int j = -1; j < 2; j++) {
                if(i != 0 || j != 0) {
                    arr[cc] = getTorus(row + i, col + j);
                    cc++;
                }
            }
        }
        return arr;
    }

    public Matrix row2Matrix(int row, int len) {
    	Matrix m = new Matrix(getColDim()/len, len);
    	
		int r = 0;
		int c = 0;
    	for (int i = 0; i < getColDim(); i++) {
    		if((i % len == 0) && (i > 0)) {
    			r++;
    			c = 0;
    		}
    		m.set(r,c, get(0,i));
			c++;
		}    	
    	
    	return m;
    }
    
    public double getTorus(int row, int col) {

        int torRow = row;
        int torCol = col;

        while(torRow < 0) {
            torRow += getRowDim();
        }

        while(torRow >= getRowDim()) {
            torRow -= getRowDim();
        }

        while(torCol < 0) {
            torCol += getColDim();
        }

        while(torCol >= getColDim()) {
            torCol -= getColDim();
        }

        return get(torRow, torCol);
    }

    /** Matrix trace.
       @return     sum of the diagonal elements.
     */
    public double getTrace() {
        double t = 0;

        int m = getRowDim();
        int n = getColDim();

        for (int ii = 0; ii < Math.min(m, n); ii++) {
            t = t + get(ii, ii);
        }
        return t;
    }

    public Matrix getTranspose() {
        int rows = getRowDim();
        int cols = getColDim();
        Matrix ma = new Matrix(cols, rows);
        for (int ii = 0; ii < rows; ii++) {
            for (int jj = 0; jj < cols; jj++) {
                ma.data[jj][ii] = data[ii][jj];
            }
        }
        return ma;
    }
    /**
     * Opposite direction to transpose.
     */ 
    public Matrix getFlipped() {
        int rows = getRowDim();
        int cols = getColDim();
        Matrix ma = new Matrix(cols, rows);
        for (int ii = 0; ii < rows; ii++) {
            for (int jj = 0; jj < cols; jj++) {
                ma.data[cols-jj-1][ii] = data[ii][jj];
            }
        }
        return ma;
    }

    public Matrix toRow() {
    	Matrix m = new Matrix(1, getRowDim() * getColDim());
    	for (int i = 0; i < getRowDim(); i++) {
        	for (int j = 0; j < getColDim(); j++) {
    			m.set(0,i * getColDim() + j, get(i,j));
    		}
		}
    	return m;
    }
    
    /**
     * 
     * @return deep copy.
     */
    public double [] toArray() {
    	
    	final int rows = rows();
    	final int cols = cols();
    	
    	double [] a = new double [rows*cols];
    	
    	for (int i = 0; i < rows; i++) {
        	for (int j = 0; j < cols; j++) {
    			a[i * cols + j] = get(i,j);
    		}
		}
    	
    	return a;
    }
    
    public String toString() {

        int iRequireDigits = 20;
        int len = getRowDim() * getColDim() * iRequireDigits;
        StringBuilder sb = new StringBuilder(len);

        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length - 1; j++) {
                sb.append(data[i][j] + OUT_SEPARATOR_COL);
            }
            sb.append(data[i][data[0].length - 1]);
            // No line break after the last line
            if(i < data.length - 1)
                sb.append(OUT_SEPARATOR_ROW);
        }


        return sb.toString();
    }
    
    public String toStringBinary() {

        int len = getRowDim() * getColDim();
        StringBuilder sb = new StringBuilder(len);

        for (int i = 0; i < data.length; i++) {
            for (int j = 0; j < data[0].length; j++) {
            	if(data[i][j]==0)
            		sb.append(0);
            	else
            		sb.append(1);
            }
            // No line break after the last line
            if(i < data.length - 1)
                sb.append(OUT_SEPARATOR_ROW);
        }


        return sb.toString();
    }
    public String toString(int digits) {
        return toString(rows(), cols(), digits);
    }

    /**
     *
     * @param rowEnd exclusive
     * @param colEnd exclusive
     * @param digits
     * @return
     */
    public String toString(int rowEnd, int colEnd, int digits) {

        int iRequireDigits = 20;

        String sFormat = "";

        sFormat += "0";
        int iCounter = 0;
        if(digits > 0)
            sFormat += ".";

        while(iCounter < digits) {
          sFormat += "0";
          iCounter++;
        }

        DecimalFormat nf = new DecimalFormat(sFormat);

        int len = getRowDim() * getColDim() * iRequireDigits;
        StringBuilder sb = new StringBuilder(len);

        
        for (int i = 0; i < rowEnd; i++) {
            for (int j = 0; j < colEnd; j++) {
            	
            	String sVal = nf.format(data[i][j]);
            	if(data[i][j]==Double.MAX_VALUE)
            		sVal = "Max";
            	
                sb.append(sVal);
                
                if(j<data[0].length-1)
                	sb.append(OUT_SEPARATOR_COL);
            }
            
            if(i < data.length - 1)
                sb.append(OUT_SEPARATOR_ROW);
        }

        return sb.toString();
    }

    public String toStringRow(int row, int iDigits) {
        int iRequireDigits = 20;

        String sFormat = "";

        sFormat += "0";
        int iCounter = 0;
        if(iDigits > 0)
            sFormat += ".";

        while(iCounter < iDigits) {
          sFormat += "0";
          iCounter++;
        }

        DecimalFormat nf = new DecimalFormat(sFormat);

        int len = getRowDim() * getColDim() * iRequireDigits;
        StringBuilder sb = new StringBuilder(len);


        for (int j = 0; j < data[0].length; j++) {

            String sVal = nf.format(data[row][j]);
            if(data[row][j]==Double.MAX_VALUE)
                sVal = "Max";

            sb.append(sVal);

            if(j<data[0].length-1)
                sb.append(OUT_SEPARATOR_COL);
        }


        return sb.toString();
    }

    public String toStringRowNumber(int iDigits, String sSeparatorCol) {
        int iRequireDigits = 20;

        String sFormat = "##,";

        int iCounter = 0;
        while(iCounter < iDigits) {
          sFormat += "#";
          iCounter++;
        }
        sFormat += "0.";
        iCounter = 0;
        while(iCounter < iDigits) {
          sFormat += "0";
          iCounter++;
        }

        DecimalFormat nf = new DecimalFormat(sFormat);

        int len = getRowDim() * getColDim() * iRequireDigits;
        StringBuilder sb = new StringBuilder(len);

        for (int i = 0; i < data.length; i++) {
            sb.append(i + sSeparatorCol);
            for (int j = 0; j < data[0].length - 1; j++) {
                sb.append(nf.format(data[i][j]) + sSeparatorCol);
            }
            sb.append(nf.format(data[i][data[0].length - 1]));
            if(i < data.length - 1)
                sb.append(OUT_SEPARATOR_ROW);
        }

        return sb.toString();
    }

    public Matrix subtract(Matrix ma) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());
        if(!equalDimension(ma)) {
            throw new RuntimeException("Matrices have wrong dimensions.");
        }

        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                maResult.data[ii][jj] = data[ii][jj] - ma.data[ii][jj];
            }
        }
        return maResult;
    }
    
    public Matrix subtract(double val) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                maResult.data[ii][jj] = data[ii][jj] - val;
            }
        }
        return maResult;
    }

    public Matrix subtractFromCols(Matrix maDivisor) {
        Matrix maResult = new Matrix(getRowDim(), getColDim());

        for (int ii = 0; ii < getRowDim(); ii++) {
            for (int jj = 0; jj < getColDim(); jj++) {
                maResult.data[ii][jj] = data[ii][jj] - maDivisor.get(0,jj);
            }
        }
        return maResult;
    }

    public static double Sign(double a, double b) {
        // ( (b) >= 0.0 ? Math.abs(a) : -Math.abs(a))

        if(b >= 0.0)
            return Math.abs(a);
        else
            return -Math.abs(a);

    }
    
	public static Matrix getRND(int rows, int cols){
		
		Matrix ma = new Matrix(rows, cols);
		
		for (int i = 0; i < rows; i++)
            for (int j = 0; j < cols; j++)
                ma.set(i,j,Math.random());
		
		return ma;
	}

    public void write(String sFile, boolean apppend) {
    	
    	DecimalFormat df = new DecimalFormat(FORMAT);
    	
    	write(new File(sFile), df, apppend);
    }
    
    public void write(String sFile) {
    	DecimalFormat df = new DecimalFormat(FORMAT);
    	
    	write(new File(sFile), df, false);
    }
    
    public void write(File fiMa) {
    	
    	DecimalFormat df = new DecimalFormat(FORMAT);
    	
    	write(fiMa, df, false);
    }
    
    public void write(File fiMa, boolean apppend, int digits) throws IOException{
        String sFormat = "##,";
        int iCounter = 0;
        while(iCounter < digits) {
          sFormat += "#";
          iCounter++;
        }
        sFormat += "0.";
        iCounter = 0;
        while(iCounter < digits) {
          sFormat += "0";
          iCounter++;
        }

        DecimalFormat nf = new DecimalFormat(sFormat);

        write(fiMa,  nf, apppend);
    }
    
    public void write(String sFiMa, boolean apppend, int digits) throws IOException{
    	write(new File (sFiMa), apppend, digits);
    }

    public void write(File fiMa, DecimalFormat nf, boolean apppend) {

        try {
            FileOutputStream os = new FileOutputStream(fiMa, apppend);
            write(os, nf);
            os.close();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
    }

    public void write(OutputStream os) {
        NumberFormat nf = new DecimalFormat("#.###############");
        write(os, nf);
    }

    public String writeAsLineBase64Encoded() {

        NumberFormat nf = new DecimalFormat("#.###############");

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        write(baos, nf);

        Base64.Encoder encoder = Base64.getEncoder();

        byte [] arr64 = encoder.encode(baos.toByteArray());

        return new String(arr64);
    }


    public void write(OutputStream os, NumberFormat nf) {

        try {
			for (int i = 0; i < data.length; i++) {

			    StringBuilder sb =  new StringBuilder();

			    for (int j = 0; j < data[0].length; j++) {

			    	sb.append(nf.format(data[i][j]));

			    	if(j<data[0].length-1){
			    		sb.append(OUT_SEPARATOR_COL);
			    	}
			    }

			    os.write(sb.toString().getBytes());
			    // No line break after the last line
			    if(i < data.length - 1) {
                    os.write(OUT_SEPARATOR_ROW.getBytes());
			    }
			}

			os.flush();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
    }


    public String toStringWithColTags(List<String> liColTags, DecimalFormat nf, String separator) {
    	    	
    	if(cols()!=liColTags.size()){
    		throw new RuntimeException("Number of cols and col tags differ.");
    	}
    	
    	int [] arrWidth = new int [cols()];
    	
    	for (int i = 0; i < arrWidth.length; i++) {
			
    		arrWidth[i] = liColTags.get(i).length();
    		
		}
    	
    	for (int i = 0; i < arrWidth.length; i++) {
    		
    		for (int j = 0; j < rows(); j++) {
    			arrWidth[i] = Math.max(arrWidth[i], nf.format(get(j,i)).length());
			}
    	}
    	
    	StringBuilder sbAll = new StringBuilder();
    	
    	for (int i = 0; i < arrWidth.length; i++) {
    		
    		int w = arrWidth[i];
    		
    		StringBuilder sb = new StringBuilder(liColTags.get(i));
    		
    		int l = w-sb.length();
    		
    		for (int j = 0; j < l; j++) {
    			sb.append(" ");
			}
    		
    		sbAll.append(sb.toString());
    		sbAll.append(" ");
    		
    	}
    	
    	sbAll.append("\n");
    	
    	
    	for (int i = 0; i < rows(); i++) {
    		
    		for (int j = 0; j < arrWidth.length; j++) {
        		
        		int w = arrWidth[j];
        		
        		StringBuilder sb = new StringBuilder(nf.format(get(i,j)));
        		
        		int l = w-sb.length();
        		
        		for (int k = 0; k < l; k++) {
        			sb.append(" ");
    			}
        		
        		sbAll.append(sb.toString());
        		sbAll.append(" ");
        		
        	}
    		
    		sbAll.append("\n");
		}
    	
    	return sbAll.toString();
    }
    
    public String toStringWithRowTags(List<String> liRowTags, DecimalFormat nf, String separator) {
    	
    	if(rows()!=liRowTags.size()){
    		throw new RuntimeException("Number of rows and row tags differ.");
    	}
    	
    	int [] arrWidth = new int [cols()+1];
    	
    	for (int i = 0; i < liRowTags.size(); i++) {
			
    		arrWidth[0] = Math.max(arrWidth[0], liRowTags.get(i).length());
    		
		}
    	
    	for (int i = 0; i < cols(); i++) {
    		
    		for (int j = 0; j < rows(); j++) {
    			arrWidth[i+1] = Math.max(arrWidth[i+1], nf.format(get(j,i)).length());
			}
    	}
    	
    	StringBuilder sbAll = new StringBuilder();
    	
    	for (int i = 0; i < arrWidth.length; i++) {
    		
    		int w = arrWidth[i];
    		
    		StringBuilder sb = new StringBuilder(liRowTags.get(i));
    		
    		int l = w-sb.length();
    		
    		for (int j = 0; j < l; j++) {
    			sb.append(" ");
			}
    		
    		sbAll.append(sb.toString());
    		sbAll.append(" ");
    		
    	}
    	
    	sbAll.append("\n");
    	
    	
    	for (int i = 0; i < rows(); i++) {
    		
    		for (int j = 0; j < arrWidth.length; j++) {
        		
        		int w = arrWidth[j];
        		
        		StringBuilder sb = new StringBuilder(nf.format(get(i,j)));
        		
        		int l = w-sb.length();
        		
        		for (int k = 0; k < l; k++) {
        			sb.append(" ");
    			}
        		
        		sbAll.append(sb.toString());
        		sbAll.append(" ");
        		
        	}
    		
    		sbAll.append("\n");
		}
    	
    	return sbAll.toString();
    }

    
    
    
	public void writeSerialized(File fiOut) throws IOException {
		FileOutputStream fos = new FileOutputStream(fiOut);
		ObjectOutputStream oos = new ObjectOutputStream(fos);

		
		oos.writeObject(data);
		
		
		oos.close();
	}
	
	public static Matrix readSerialized(File fiIn) throws FileNotFoundException, IOException, ClassNotFoundException {
		
		Matrix ma = null;
		
		FileInputStream fos = new FileInputStream(fiIn);
		
		ObjectInputStream ois = new ObjectInputStream(fos);
		
		double [][] data = (double [][]) ois.readObject();
		
		/**
		 * We are using a deep copy because of memory handling problems with using the de-serialized object directly.
		 */
		ma = new Matrix(data);
		
		ois.close();
		
		return ma;
	}

    public static DecimalFormat format(int digits) {
        String sFormat = "##,###";
        
        
        if(digits > 0) {
        	sFormat += ".";
	        int iCounter = 0;
	        while (iCounter < digits) {
	            sFormat += "0";
	            iCounter++;
	        }
        }
        return  new DecimalFormat(sFormat);
    }

    public void write(String sFile, boolean bApppend, int digits, int totalWidth) {

        try {
            DecimalFormat nf = format(digits);
            BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
                sFile), bApppend));

            StringBuffer sVal = new StringBuffer();
            for (int ii = 0; ii < data.length; ii++) {
                sVal = new StringBuffer();
                for (int jj = 0; jj < data[0].length; jj++) {
                    sVal.append(format(data[ii][jj], nf, totalWidth) +
                                OUT_SEPARATOR_COL);
                }
                writer.write(sVal.toString());
                // No line break after the last line
                if (ii < data.length - 1) {
                    writer.write(OUT_SEPARATOR_ROW);
                }
            }
            writer.flush();
            writer.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static String format(double val, DecimalFormat nf, int totalWidth) {
        String str = "";
        str = nf.format(val);
        while(str.length() < totalWidth) {
            str = " " + str;
        }
        return str;
    }

}