/* Copyright (C) 1997-2007  The Chemistry Development Kit (CDK) project
 *
 *  Contact: cdk-devel@lists.sourceforge.net
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2.1
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */
package org.openscience.cdk.graph.invariant;

import org.openscience.cdk.graph.invariant.exception.BadMatrixFormatException;
import org.openscience.cdk.graph.invariant.exception.IndexOutOfBoundsException;
import org.openscience.cdk.graph.invariant.exception.MatrixNotInvertibleException;

/**
 * This class is intended to provide the user an efficient way of implementing matrix of double number and
 * using normal operations (linear operations, addition, subtraction, multiplication, inversion, concatenation)
 * on them. The internal representation of a matrix is an array of array of double objects. For the moment,
 * double class is the best way I have developed to perform exact operation on numbers; however, for
 * irdoubles, normal operations on float and doubles have to be performed, with the well-known risks of error
 * this implies. This class also provides a way of representing matrix as arrays of String for output use.
 *
 * <P>Please note that although in most books matrix elements' indexes take values between [1..n] I chose not
 * to disturb Java language way of calling indexes; so the indexes used here take values between [0..n-1] instead.
 *
 * @author Jean-Sebastien Senecal
 * @cdk.githash
 * @version 1.0
 * @cdk.created 1999-05-20
 */
public class GIMatrix {

    private double[][] array; // the matrix itself as an array of doubles
    private int        m, n; // matrix's params (m=no of line, n=no of columns)

    /**
     * Class constructor. Uses an array of integers to create a new Matrix object. Note that integers
     * will be converted to double objects so mathematical operations may be properly performed and
     * provide exact solutions. The given array should be properly instantiated as a matrix i.e. it
     * must contain a fixed number of lines and columns, otherwise an exception will be thrown.
     * Array must be at leat 1x1.
     * @param array an array of integer (first index is the line, second is the column)
     */
    public GIMatrix(int[][] array) {
        double[][] temp = new double[array.length][];
        for (int i = 0; i < array.length; i++) {
            temp[i] = new double[array[i].length]; // line by line ...
            for (int j = 0; j < array[i].length; j++)
                temp[i][j] = array[i][j]; // converts ints to doubles
        }
        //	verifyMatrixFormat(temp);
        this.array = temp;
        m = temp.length;
        n = temp[0].length;
    } // constructor Matrix(int[][])

    /**
     * Class constructor. Uses an array of doubles to create a new Matrix object. The given array should
     * be properly instantiated as a matrix i.e. it must contain a fixed number of lines and columns,
     * otherwise an exception will be thrown. Array must be at leat 1x1.
     *
     * @param array an array of double objects (first index is the line, second is the column)
     * @exception BadMatrixFormatException in case the given array is unproper to construct a matrix
     */
    public GIMatrix(double[][] array) throws BadMatrixFormatException {
        verifyMatrixFormat(array);
        double[][] temp = new double[array.length][];
        for (int i = 0; i < array.length; i++) {
            temp[i] = new double[array[i].length]; // line by line ...
            for (int j = 0; j < array[i].length; j++)
                temp[i][j] = array[i][j];
        }
        this.array = temp;
        m = array.length;
        n = array[0].length;
    } // constructor Matrix(double[][])

    /**
     * Class constructor. Creates a new Matrix object with fixed dimensions. The matrix is
     * initialised to the "zero" matrix.
     *
     * @param line number of lines
     * @param col number of columns
     */
    public GIMatrix(int line, int col) {
        array = new double[line][col];
        for (int i = 0; i < line; i++)
            for (int j = 0; j < col; j++)
                array[i][j] = 0.0;
        m = line;
        n = col;
    } // constructor Matrix(int,int)

    /**
     * Class constructor. Copies an already existing Matrix object in a new Matrix object.
     * @param matrix a Matrix object
     */
    public GIMatrix(GIMatrix matrix) {
        double[][] temp = new double[matrix.height()][];
        for (int i = 0; i < matrix.height(); i++) {
            temp[i] = new double[matrix.width()]; // line by line ...
            for (int j = 0; j < matrix.width(); j++) {
                try {
                    temp[i][j] = matrix.getValueAt(i, j);
                } catch (IndexOutOfBoundsException e) {
                } // never happens
            }
        }
        this.array = temp;
        m = array.length;
        n = array[0].length;
    } // constructor Matrix(Matrix)

    /**
     * Class constructor. Creates a new Matrix object using a table of matrices (an array of Matrix objects).
     * The given array should be properly instantiated i.e. it must contain a fixed number of lines and columns,
     * otherwise an exception will be thrown.
     * @param table an array of matrices
     * @exception BadMatrixFormatException if the table is not properly instantiated
     */
    public GIMatrix(GIMatrix[][] table) throws BadMatrixFormatException {
        verifyTableFormat(table);
        m = n = 0;
        for (int i = 0; i < table.length; i++)
            m += table[i][0].height();
        for (int j = 0; j < table[0].length; j++)
            n += table[0][j].width();
        double[][] temp = new double[m][n];
        int k = 0; // counters for matrices
        for (int i = 0; i < m; i++) {
            temp[i] = new double[n]; // line by line ...
            if (i == table[k][0].height()) k++; // last line of matrix reached
            int h = 0;
            for (int j = 0; j < n; j++) {
                if (j == table[k][h].width()) h++; // last column of matrix reached
                try {
                    GIMatrix tempMatrix = table[k][h];
                    temp[i][j] = tempMatrix.getValueAt(i - k * tempMatrix.height(), j - h * tempMatrix.width());
                } catch (IndexOutOfBoundsException e) {
                } // never happens
            }
        }
        this.array = temp;
    } // constructor Matrix(Matrix)

    /**
     * Returns the number of lines of the matrix.
     * @return the height of the matrix
     */
    public int height() {
        return m;
    } // method heigth()

    /**
     * Returns the number of columns of the matrix.
     * @return the width of the matrix
     */
    public int width() {
        return n;
    } // method width()

    /**
     * Returns the internal representation of the matrix, that is an array of double objects.
     * @return an array of double equivalent to the matrix
     */
    public double[][] getArrayValue() {
        return array;
    } // method getArrayValue()

    /**
     * Resets the value of the matrix to the given array of double numbers.
     * @param array an array of double objects (first index is the line, second is the column)
     * @exception BadMatrixFormatException in case the given array is unproper to construct a matrix
     */
    public void setArrayValue(double[][] array) throws BadMatrixFormatException {
        verifyMatrixFormat(array);
        this.array = array;
    } // method setArrayValue(double[][])

    /**
     * Returns the value of the given element.
     * @param i the line number
     * @param j the column number
     * @return the double at the given index in the Matrix
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     */
    public double getValueAt(int i, int j) throws IndexOutOfBoundsException {
        if ((i < 0) || (i >= m) || (j < 0) || (j >= n)) throw new IndexOutOfBoundsException();
        return array[i][j];
    } // method getValueAt(int,int)

    /**
     * Sets the value of the element at the given index.
     * @param i the line number
     * @param j the column number
     * @param element the double to place at the given index in the Matrix
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     */
    public void setValueAt(int i, int j, double element) throws IndexOutOfBoundsException {
        if ((i < 0) || (i >= m) || (j < 0) || (j >= n)) throw new IndexOutOfBoundsException();
        array[i][j] = element;
    } // method setValueAt(int,int,double)

    /**
     * Returns the line-matrix at the given line index.
     * @param i the line number
     * @return the specified line as a Matrix object
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     */
    public GIMatrix getLine(int i) throws IndexOutOfBoundsException {
        if ((i < 0) || (i >= m)) throw new IndexOutOfBoundsException();
        double[][] line = new double[1][n];
        for (int k = 0; k < n; k++)
            line[0][k] = array[i][k];
        try {
            return new GIMatrix(line);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method getLine(int)

    /**
     * Returns the column-matrix at the given line index.
     * @param j the column number
     * @return the specified column as a Matrix object
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     */
    public GIMatrix getColumn(int j) throws IndexOutOfBoundsException {
        if ((j < 0) || (j >= n)) throw new IndexOutOfBoundsException();
        double[][] column = new double[m][1];
        for (int k = 0; k < m; k++)
            column[k][0] = array[k][j];
        try {
            return new GIMatrix(column);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method getColumn(int)

    /**
     * Sets the line of the matrix at the specified index to a new value.
     * @param i the line number
     * @param line the line to be placed at the specified index
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     * @exception BadMatrixFormatException in case the given Matrix is unproper to replace a line of this Matrix
     */
    public void setLine(int i, GIMatrix line) throws IndexOutOfBoundsException, BadMatrixFormatException {
        if ((i < 0) || (i >= m)) throw new IndexOutOfBoundsException();
        if ((line.height() != 1) || (line.width() != n)) throw new BadMatrixFormatException();
        for (int k = 0; k < n; k++)
            array[i][k] = line.getValueAt(0, k);
    } // method setLine(int,Matrix)

    /**
     * Sets the column of the matrix at the specified index to a new value.
     * @param j the column number
     * @param column the column to be placed at the specified index
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     * @exception BadMatrixFormatException in case the given Matrix is unproper to replace a column of this Matrix
     */
    public void setColumn(int j, GIMatrix column) throws IndexOutOfBoundsException, BadMatrixFormatException {
        if ((j < 0) || (j >= n)) throw new IndexOutOfBoundsException();
        if ((column.height() != m) || (column.width() != 1)) throw new BadMatrixFormatException();
        for (int k = 0; k < m; k++)
            array[k][j] = column.getValueAt(k, 0);
    } // method setColumn(int,Matrix)

    /**
     * Returns the identity matrix.
     * @param n the matrix's dimension (identity matrix is a square matrix)
     * @return the identity matrix of format nxn
     */
    public static GIMatrix identity(int n) {
        double[][] identity = new double[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++)
                identity[i][j] = 0.0;
            identity[i][i] = 1.0;
            for (int j = i + 1; j < n; j++)
                identity[i][j] = 0.0;
        }
        try {
            return new GIMatrix(identity);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method identity(int)

    /**
     * Returns a null matrix (with zeros everywhere) of given dimensions.
     * @param m number of lines
     * @param n number of columns
     * @return the zero (null) matrix of format mxn
     */
    public static GIMatrix zero(int m, int n) {
        double[][] zero = new double[m][n];
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                zero[i][j] = 0.0;
        try {
            return new GIMatrix(zero);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method zero(int,int)

    /**
     * Verifies if two given matrix are equal or not. The matrix must be of the same size and dimensions,
     * otherwise an exception will be thrown.
     * @param matrix the Matrix object to be compared to
     * @return true if both matrix are equal element to element
     * @exception BadMatrixFormatException if the given matrix doesn't have the same dimensions as this one
     */
    public boolean equals(GIMatrix matrix) throws BadMatrixFormatException {
        if ((height() != matrix.height()) || (width() != matrix.width())) throw new BadMatrixFormatException();
        double[][] temp = matrix.getArrayValue();
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                if (!(array[i][j] == temp[i][j])) return false;
        return true;
    } // method equals(Matrix)

    /**
     * Verifies if the matrix is square, that is if it has an equal number of lines and columns.
     * @return true if this matrix is square
     */
    public boolean isSquare() {
        return (m == n);
    } // method isSquare()

    /**
     * Verifies if the matrix is symmetric, that is if the matrix is equal to it's transpose.
     * @return true if the matrix is symmetric
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public boolean isSymmetric() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        // the loop looks in the lower half of the matrix to find non-symetric elements
        for (int i = 1; i < m; i++)
            // starts at index 1 because index (0,0) always symmetric
            for (int j = 0; j < i; j++)
                if (!(array[i][j] == array[j][i])) return false;
        return true; // the matrix has passed the test
    } //method isSymmetric()

    // NOT OVER, LOOK MORE CAREFULLY FOR DEFINITION
    /**
     * Verifies if the matrix is antisymmetric, that is if the matrix is equal to the opposite of
     * it's transpose.
     * @return true if the matrix is antisymmetric
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public boolean isAntisymmetric() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        // the loop looks in the lower half of the matrix to find non-antisymetric elements
        for (int i = 0; i < m; i++)
            // not as isSymmetric() loop
            for (int j = 0; j <= i; j++)
                if (!(array[i][j] == -array[j][i])) return false;
        return true; // the matrix has passed the test
    } // method isAntisymmetric()

    /**
     * Verifies if the matrix is triangular superior or not. A triangular superior matrix has
     * zero (0) values everywhere under it's diagonal.
     * @return true if the matrix is triangular superior
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public boolean isTriangularSuperior() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        // the loop looks in the lower half of the matrix to find non-null elements
        for (int i = 1; i < m; i++)
            // starts at index 1 because index (0,0) is on the diagonal
            for (int j = 0; j < i; j++)
                if (!(array[i][j] == 0.0)) return false;
        return true; // the matrix has passed the test
    } // method isTriangularSuperior

    /**
     * Verifies if the matrix is triangular inferior or not. A triangular inferior matrix has
     * zero (0) values everywhere upper it's diagonal.
     * @return true if the matrix is triangular inferior
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public boolean isTriangularInferior() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        // the loop looks in the upper half of the matrix to find non-null elements
        for (int i = 1; i < m; i++)
            // starts at index 1 because index (0,0) is on the diagonal
            for (int j = i; j < n; j++)
                if (!(array[i][j] == 0.0)) return false;
        return true; // the matrix has passed the test
    } // method isTriangularInferior()

    /**
     * Verifies whether or not the matrix is diagonal. A diagonal matrix only has elements on its diagonal
     * and zeros (0) at every other index. The matrix must be square.
     * @return true if the matrix is diagonal
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public boolean isDiagonal() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        // the loop looks both halves of the matrix to find non-null elements
        for (int i = 1; i < m; i++)
            // starts at index 1 because index (0,0) must not be checked
            for (int j = 0; j < i; j++)
                if ((!(array[i][j] == 0.0)) || (!(array[j][i] == 0.0))) return false; // not null
        return true;
    } // method isDiagonal

    /**
     * Verifies if the matrix is invertible or not by asking for its determinant.
     * @return true if the matrix is invertible
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public boolean isInvertible() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        return (!(determinant() == 0)); // det != 0
    } // method isInvertible()

    /**
     * Returns the transpose of this matrix. The transpose of a matrix A = {a(i,j)} is the matrix B = {b(i,j)}
     * such that b(i,j) = a(j,i) for every i,j i.e. it is the symmetrical reflection of the matrix along its
     * diagonal. The matrix must be square to use this method, otherwise an exception will be thrown.
     * @return the matrix's transpose as a Matrix object
     * @exception MatrixNotInvertibleException if the matrix is not square
     */
    public GIMatrix inverse() throws MatrixNotInvertibleException {
        try {
            if (!isInvertible()) throw new MatrixNotInvertibleException();
        } catch (BadMatrixFormatException e) {
            throw new MatrixNotInvertibleException();
        }
        GIMatrix I = identity(n); // Creates an identity matrix of same dimensions
        GIMatrix table;
        try {
            GIMatrix[][] temp = {{this, I}};
            table = new GIMatrix(temp);
        } catch (BadMatrixFormatException e) {
            return null;
        } // never happens
        table = table.GaussJordan(); // linear reduction method applied
        double[][] inv = new double[m][n];
        for (int i = 0; i < m; i++)
            // extracts inverse matrix
            for (int j = n; j < 2 * n; j++) {
                try {
                    inv[i][j - n] = table.getValueAt(i, j);
                } catch (IndexOutOfBoundsException e) {
                    return null;
                } // never happens
            }
        try {
            return new GIMatrix(inv);
        } catch (BadMatrixFormatException e) {
            return null;
        } // never happens...
    } // method inverse()

    /**
     * Gauss-Jordan algorithm. Returns the reduced-echeloned matrix of this matrix. The
     * algorithm has not yet been optimised but since it is quite simple, it should not be
     * a serious problem.
     * @return the reduced matrix
     */
    public GIMatrix GaussJordan() {
        GIMatrix tempMatrix = new GIMatrix(this);
        try {
            int i = 0;
            int j = 0;
            int k = 0;
            boolean end = false;
            while ((i < m) && (!end)) {
                boolean allZero = true; // true if all elements under line i are null (zero)
                while (j < n) { // determination of the pivot
                    for (k = i; k < m; k++) {
                        if (!(tempMatrix.getValueAt(k, j) == 0.0)) { // if an element != 0
                            allZero = false;
                            break;
                        }
                    }
                    if (allZero)
                        j++;
                    else
                        break;
                }
                if (j == n)
                    end = true;
                else {
                    if (k != i) tempMatrix = tempMatrix.invertLine(i, k);
                    if (!(tempMatrix.getValueAt(i, j) == 1.0)) // if element != 1
                        tempMatrix = // A = L(i)(1/a(i,j))(A)
                        tempMatrix.multiplyLine(i, 1 / tempMatrix.getValueAt(i, j));
                    for (int q = 0; q < m; q++)
                        if (q != i) // A = L(q,i)(-a(q,j))(A)
                            tempMatrix = tempMatrix.addLine(q, i, -tempMatrix.getValueAt(q, j));
                }
                i++;
            }
            // normally here, r = i-1
            return tempMatrix;
        } catch (IndexOutOfBoundsException e) {
            return null;
        } // never happens... well I hope ;)
        /*
         * From: LEROUX, P. Algebre lineaire: une approche matricielle. Modulo
         * Editeur, 1983. p. 75. (In French)
         */
    } // method GaussJordan()

    /**
     * Returns the transpose of this matrix. The transpose of a matrix A = {a(i,j)} is the matrix B = {b(i,j)}
     * such that b(i,j) = a(j,i) for every i,j i.e. it is the symmetrical reflection of the matrix along its
     * diagonal. The matrix must be square to use this method, otherwise an exception will be thrown.
     * @return the matrix's transpose as a Matrix object
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public GIMatrix transpose() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        double[][] transpose = new double[array.length][array[0].length];
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                transpose[i][j] = array[j][i];
        return new GIMatrix(transpose);
    } // method transpose()

    /**
     * Returns a matrix containing all of the diagonal elements of this matrix and zero (0) everywhere
     * else. This matrix is called the diagonal of the matrix.
     * @return the diagonal of the matrix
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public GIMatrix diagonal() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        double[][] diagonal = new double[array.length][array[0].length];;
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++) {
                if (i == j)
                    diagonal[i][j] = array[i][j];
                else
                    diagonal[i][j] = 0;
            }
        return new GIMatrix(diagonal);
    } // method diagonal()

    /**
     * Returns the resulting matrix of an elementary linear operation that consists of multiplying a
     * single line of the matrix by a constant.
     * @param i the line number
     * @param c the double constant that multiplies the line
     * @return the resulting Matrix object of the linear operation
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     */
    public GIMatrix multiplyLine(int i, double c) throws IndexOutOfBoundsException {
        if ((i < 0) || (i >= m)) throw new IndexOutOfBoundsException();
        double[][] temp = array;
        for (int k = 0; k < n; k++)
            temp[i][k] = c * temp[i][k]; // mutliply every member of the line by c
        try {
            return new GIMatrix(temp);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method multiplyLine(int,int)

    /**
     * Returns the resulting matrix of an elementary linear operation that consists of inverting two lines.
     * @param i the first line number
     * @param j the second line number
     * @return the resulting Matrix object of the linear operation
     * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
     */
    public GIMatrix invertLine(int i, int j) throws IndexOutOfBoundsException {
        if ((i < 0) || (i >= m) || (j < 0) || (j >= m)) throw new IndexOutOfBoundsException();
        double[][] temp = array;
        double[] tempLine = temp[j]; // temporary line
        temp[j] = temp[i];
        temp[i] = tempLine;
        try {
            return new GIMatrix(temp);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method invertLine(int,int)

    /**
      * Returns the resulting matrix of an elementary linear operation that consists of adding one line,
      * multiplied by some constant factor, to another line.
      * @param i the first line number
      * @param j the second line number (to be added to the first)
      * @param c the double constant that multiplies the first line
      * @return the resulting Matrix object of the linear operation
      * @exception IndexOutOfBoundsException if the given index is out of the matrix's range
      */
    public GIMatrix addLine(int i, int j, double c) throws IndexOutOfBoundsException {
        if ((i < 0) || (i >= m) || (j < 0) || (j >= m)) throw new IndexOutOfBoundsException();
        double[][] temp = array;
        for (int k = 0; k < n; k++)
            temp[i][k] = temp[i][k] + c * temp[j][k]; // add multiplied element of i to element of j
        try {
            return new GIMatrix(temp);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method addLine(int,int,double)

    /**
     *  Addition from two matrices.
     */
    public GIMatrix add(GIMatrix b) {
        if ((b == null) || (m != b.m) || (n != b.n)) return null;

        int i, j;
        GIMatrix result = new GIMatrix(m, n);
        for (i = 0; i < m; i++)
            for (j = 0; j < n; j++)
                result.array[i][j] = array[i][j] + b.array[i][j];
        return result;
    }

    /**
     * Returns the result of the scalar multiplication of the matrix, that is the multiplication of every
     * of its elements by a given number.
     * @param c the constant by which the matrix is multiplied
     * @return the resulting matrix of the scalar multiplication
     */
    public GIMatrix multiply(double c) {
        double[][] temp = array;
        for (int i = 0; i < m; i++)
            for (int j = 0; j < n; j++)
                temp[i][j] = c * temp[i][j];
        try {
            return new GIMatrix(temp);
        } // format is always OK anyway ...
        catch (BadMatrixFormatException e) {
            return null;
        }
    } // method multiply(double)

    /**
     * Returns the result of the matrix multiplication of this matrix by another one. The matrix passed
     * as parameter <i>follows</i> this matrix in the multiplication, so for an example if the dimension of
     * the actual matrix is mxn, the dimension of the second one should be nxp in order for the multiplication
     * to be performed (otherwise an exception will be thrown) and the resulting matrix will have dimension mxp.
     * @param matrix the matrix following this one in the matrix multiplication
     * @return the resulting matrix of the matrix multiplication
     * @exception BadMatrixFormatException if the matrix passed in arguments has wrong dimensions
     */
    public GIMatrix multiply(GIMatrix matrix) throws BadMatrixFormatException {
        if (n != matrix.height()) throw new BadMatrixFormatException(); // unsuitable dimensions
        int p = matrix.width();
        double[][] temp = new double[m][p];
        double[][] multiplied = matrix.getArrayValue();
        for (int i = 0; i < m; i++)
            // line index of the first matrix
            for (int k = 0; k < p; k++) { // column index of the second matrix
                temp[i][k] = array[i][0] * multiplied[0][k]; // first multiplication
                for (int j = 1; j < n; j++)
                    // sum of multiplications
                    temp[i][k] = temp[i][k] + array[i][j] * multiplied[j][k];
            }
        return new GIMatrix(temp);
    } // method multiply(Matrix)

    /**
     * Returns the determinant of this matrix. The matrix must be
     * square in order to use this method, otherwise an exception will be thrown.
     * <i>Warning: this algorithm is very inefficient and takes too much time to compute
     * with large matrices.</i>
     * @return the determinant of the matrix
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public double determinant() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        return det(array); // use of recursive method
    } // method determinant()

    // Method used for recursive determinant algorithm. Supposes the given array is square.
    private double det(double[][] mat) {
        if (mat.length == 1) return mat[0][0];
        double temp = mat[0][0] * det(M(mat, 0, 0)); // (-1)^(0+0)*m[0][0]*det(M(i,j)) ... first assignation
        for (int k = 1; k < mat.length; k++)
            temp = temp + (det(M(mat, 0, k)) * ((k % 2 == 0) ? mat[0][k] : -mat[0][k]));
        // Note: ((0+k)%2 == 0)?1:-1 is equivalent to (-1)^(0+k)
        return temp;
    } // method det(double[][])

    // Returns the minor of the array (supposed square) i.e. the array least its i-th line
    // and j-th column
    private double[][] M(double[][] mat, int i, int j) {
        double[][] temp = new double[mat.length - 1][mat[0].length - 1]; // "void minor"
        for (int k = 0; k < i; k++) {
            for (int h = 0; h < j; h++)
                temp[k][h] = mat[k][h];
            for (int h = j + 1; h < mat[0].length; h++)
                temp[k][h - 1] = mat[k][h];
        }
        for (int k = i + 1; k < mat.length; k++) {
            for (int h = 0; h < j; h++)
                temp[k - 1][h] = mat[k][h];
            for (int h = j + 1; h < mat[0].length; h++)
                temp[k - 1][h - 1] = mat[k][h];
        }
        return temp;
    } // method M(double[][],int,int)

    /**
     * Returns the trace of this matrix, that is the sum of the elements of its diagonal. The matrix must be
     * square in order to use this method, otherwise an exception will be thrown.
     * @return the trace of the matrix
     * @exception BadMatrixFormatException if the matrix is not square
     */
    public double trace() throws BadMatrixFormatException {
        if (m != n) throw new BadMatrixFormatException();
        double trace = array[0][0];
        for (int i = 1; i < m; i++)
            trace = trace + array[i][i];
        return trace;
    } // method trace()

    //    /**
    //     * Returns the matrix as a String.
    //     * The double numbers are printed using the form "p/q" or "a b/c" depending on the value of the boolean,
    //     * and use a minimal number of spaces as specified. It is the user's responsibility to grant a number
    //     * of spaces wide enough to get proper alignment.
    //     * @see tatien.toolbox.double#toString
    //     * @param simple must be true to get simple expression, false otherwise as specified
    //     * @param spaces number of spaces
    //     */
    //    public String print(boolean simple, int spaces) {
    //	String print = "";
    //	for (int i = 0; i < m; i++) {
    //	    print = print + array[i][0].toString(simple,spaces);
    //	    for (int j = 1; j < n; j++) {
    //	       print = print + array[i][j].toString(simple,spaces);
    //	    }
    //	    print = print + "\n";
    //	}
    //	return print;
    //    } // method print()

    // Verifies if the matrix is of good format when calling a constructor or setArrayValue
    private void verifyMatrixFormat(double[][] testedMatrix) throws BadMatrixFormatException {
        if ((testedMatrix.length == 0) || (testedMatrix[0].length == 0)) throw new BadMatrixFormatException();
        int noOfColumns = testedMatrix[0].length;
        for (int i = 1; i < testedMatrix.length; i++)
            if (testedMatrix[i].length != noOfColumns) throw new BadMatrixFormatException();
    } // method verifyMatrixFormat(double[][])

    // In the case of the implementation of a table i.e. an array of matrices, verifies if the table is proper.
    private void verifyTableFormat(GIMatrix[][] testedTable) throws BadMatrixFormatException {
        if ((testedTable.length == 0) || (testedTable[0].length == 0)) throw new BadMatrixFormatException();
        int noOfColumns = testedTable[0].length;
        int currentHeigth, currentWidth;
        for (int i = 0; i < testedTable.length; i++) { // verifies correspondence of m's (heigth)
            if (testedTable[i].length != noOfColumns) throw new BadMatrixFormatException();
            currentHeigth = testedTable[i][0].height();
            for (int j = 1; j < testedTable[0].length; j++)
                if (testedTable[i][j].height() != currentHeigth) throw new BadMatrixFormatException();
        }
        for (int j = 0; j < testedTable[0].length; j++) { // verifies correspondence of n's (width)
            currentWidth = testedTable[0][j].width();
            for (int i = 1; i < testedTable.length; i++)
                if (testedTable[i][j].width() != currentWidth) throw new BadMatrixFormatException();
        }
    } // method verifyTableFormat(Matrix[][])

} // class Matrix

