# Source: Matrix.js

    /**
     * @license
     * Copyright 2020 Roberto Luiz Souza Monteiro,
     *                Renata Souza Barreto,
     *                Hernane Borges de Barros Pereira.
     *
     * Licensed under the Apache License, Version 2.0 (the 'License');
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *   http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an 'AS IS' BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /**
     * MaiaScript matrix library.
     * @class
     */
    function Matrix() {
        init();

        /**
         * Creates the attributes of the class.
         */
        function init() {
            // Class attributes goes here.
        }

        /**
         * Returns the mean and standard deviation of the values contained in a matrix.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  Mean and standard deviation of the values contained in a matrix.
         */
        this.avg = function(mtx, r1, c1, r2, c2)
        {
            var res = {
                'avg': 0,
                'dev': 0
            };
            var sx;
            var sx2;
            var n;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    sx = 0;
                    sx2 = 0;
                    n = 0;
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                sx += mtx[i][j];
                                sx2 += mtx[i][j] * mtx[i][j];
                                n++;
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function avg. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    if (dimMatrix.length == 1) {
                        if (typeof c1 == 'undefined') {
                            c1 = 0;
                        }
                        if (typeof c2 == 'undefined') {
                            c2 = dimMatrix[0] - 1;
                        }
                        sx = 0;
                        sx2 = 0;
                        n = 0;
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[j]) == 'number') {
                                sx += mtx[j];
                                sx2 += mtx[j] * mtx[j];
                                n++;
                            } else {
                                throw new Error('Invalid element ' + mtx[j] + ' in matrix for function avg. All elements must be numeric.');
                            }
                        }
                    } else {
                        throw new Error('Invalid argument for function avg. The matrix must be one or two-dimensional.');
                    }
                }
            } else {
                throw new Error('Invalid argument for function avg. Argument must be a matrix.');
            }
            res.avg = sx / n;
            res.dev = Math.sqrt((sx2 - (sx * sx) / n) / (n - 1));
            return res;
        }

        /**
         * Returns the number of non-zero elements in the matrix.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  The number of non-zero elements in the matrix.
         */
        this.count = function(mtx, r1, c1, r2, c2)
        {
            var res;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    res = 0;
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                if (mtx[i][j] != 0) {
                                    res++;
                                }
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function count. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    if (dimMatrix.length == 1) {
                        res = 0;
                        if (typeof c1 == 'undefined') {
                            c1 = 0;
                        }
                        if (typeof c2 == 'undefined') {
                            c2 = dimMatrix[0] - 1;
                        }
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[j]) == 'number') {
                                if (mtx[j] != 0) {
                                    res++;
                                }
                            } else {
                                throw new Error('Invalid element ' + mtx[j] + ' in matrix for function count. All elements must be numeric.');
                            }
                        }
                    } else {
                        throw new Error('Invalid argument for function count. The matrix must be one or two-dimensional.');
                    }
                }
            } else {
                throw new Error('Invalid argument for function count. Argument must be a matrix.');
            }
            return res;
        }

        /**
         * Calculates the cross product of two vectors A and B.
         * @param {object}  a - The matrix A.
         * @param {object}  b - The matrix B.
         * @return {array}  A (rows x columns) matrix.
         */
        this.cross = function(a, b)
        {
            var mtx;
            if ((core.type(a) == 'matrix') && (core.type(b) == 'matrix')) {
                var dimA = core.dim(a);
                var dimB = core.dim(b);
                if ((dimA[0] == dimB[0]) && (dimA[1] == dimB[1])) {
                    var m = dimA[0];
                    var mtx = core.matrix(0, 1, m);
                    if (m == 2) {
                        mtx[0] = a[0] * b[1];
                        mtx[1] = a[1] * b[0];
                    } else if (m == 3) {
                        mtx[0] = a[1] * b[2] - b[1] * a[2];
                        mtx[1] = a[2] * b[0] - b[2] * a[0];
                        mtx[2] = a[0] * b[1] - b[0] * a[1];
                    }
                } else {
                    throw new Error('The matrices must have equal dimensions for function cross(), in the expression cross(' + core.toString(a) + "," + core.toString(b) + ').');
                }
            } else {
                throw new Error('The arguments for function cross() must be matrices, in the expression cross(' + core.toString(a) + "," + core.toString(b) + ').');
            }
            return mtx;
        }

        /**
         * Calculates the dot product of two vectors A and B.
         * @param {object}  a - The matrix A.
         * @param {object}  b - The matrix B.
         * @return {array}  A (rows x columns) matrix.
         */
        this.dot = function(a, b)
        {
            var res;
            if ((core.type(a) == 'matrix') && (core.type(b) == 'matrix')) {
                var dimA = core.dim(a);
                var dimB = core.dim(b);
                if ((dimA[0] == dimB[0]) && (dimA[1] == dimB[1])) {
                    var m = dimA[0];
                    if (m == 2) {
                        res = a[0] * b[0] + a[1] * b[1];
                    } else if (m == 3) {
                        res = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
                    }
                } else {
                    throw new Error('The matrices must have equal dimensions for function cross(), in the expression cross(' + core.toString(a) + "," + core.toString(b) + ').');
                }
            } else {
                throw new Error('The arguments for function dot() must be matrices, in the expression dot(' + core.toString(a) + "," + core.toString(b) + ').');
            }
            return res;
        }

        /**
         * Returns the largest value in an array.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  The smallest value in an array.
         */
        this.max = function(mtx, r1, c1, r2, c2)
        {
            var res;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    res = 0;
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                if (typeof res == 'undefined') {
                                    res = mtx[i][j];
                                } else {
                                    if (mtx[i][j] > res) {
                                        res = mtx[i][j];
                                    }
                                }
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function max. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    if (dimMatrix.length == 1) {
                        res = 0;
                        if (typeof c1 == 'undefined') {
                            c1 = 0;
                        }
                        if (typeof c2 == 'undefined') {
                            c2 = dimMatrix[0] - 1;
                        }
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[j]) == 'number') {
                                if (typeof res == 'undefined') {
                                    res = mtx[j];
                                } else {
                                    if (mtx[j] > res) {
                                        res = mtx[j];
                                    }
                                }
                            } else {
                                throw new Error('Invalid element ' + mtx[j] + ' in matrix for function max. All elements must be numeric.');
                            }
                        }
                    } else {
                        throw new Error('Invalid argument for function max. The matrix must be one or two-dimensional.');
                    }
                }
            } else {
                throw new Error('Invalid argument for function max. Argument must be a matrix.');
            }
            return res;
        }

        /**
         * Returns the smallest value in an array.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  The smallest value in an array.
         */
        this.min = function(mtx, r1, c1, r2, c2)
        {
            var res;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                if (typeof res == 'undefined') {
                                    res = mtx[i][j];
                                } else {
                                    if (mtx[i][j] < res) {
                                        res = mtx[i][j];
                                    }
                                }
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function min. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    if (dimMatrix.length == 1) {
                        res = 0;
                        if (typeof c1 == 'undefined') {
                            c1 = 0;
                        }
                        if (typeof c2 == 'undefined') {
                            c2 = dimMatrix[0] - 1;
                        }
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[j]) == 'number') {
                                if (typeof res == 'undefined') {
                                    res = mtx[j];
                                } else {
                                    if (mtx[j] < res) {
                                        res = mtx[j];
                                    }
                                }
                            } else {
                                throw new Error('Invalid element ' + mtx[j] + ' in matrix for function min. All elements must be numeric.');
                            }
                        }
                    } else {
                        throw new Error('Invalid argument for function min. The matrix must be one or two-dimensional.');
                    }
                }
            } else {
                throw new Error('Invalid argument for function min. Argument must be a matrix.');
            }
            return res;
        }

        /**
         * Calculates the sum of all cells in the matrix.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  Sum of the values contained in a matrix.
         */
        this.sum = function(mtx, r1, c1, r2, c2)
        {
            var res;
            var sx;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    sx = 0;
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                sx += mtx[i][j];
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function sum. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    if (dimMatrix.length == 1) {
                        if (typeof c1 == 'undefined') {
                            c1 = 0;
                        }
                        if (typeof c2 == 'undefined') {
                            c2 = dimMatrix[0] - 1;
                        }
                        sx = 0;
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[j]) == 'number') {
                                sx += mtx[j];
                            } else {
                                throw new Error('Invalid element ' + mtx[j] + ' in matrix for function sum. All elements must be numeric.');
                            }
                        }
                    } else {
                        throw new Error('Invalid argument for function sum. The matrix must be one or two-dimensional.');
                    }
                }
            } else {
                throw new Error('Invalid argument for function sum. Argument must be a matrix.');
            }
            res = sx;
            return res;
        }

        /**
         * Calculates the squared sum of all cells in the matrix.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  Sum of the values contained in a matrix.
         */
        this.sum2 = function(mtx, r1, c1, r2, c2)
        {
            var res;
            var sx;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    sx = 0;
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                sx += mtx[i][j] * mtx[i][j];
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function sum. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    if (dimMatrix.length == 1) {
                        if (typeof c1 == 'undefined') {
                            c1 = 0;
                        }
                        if (typeof c2 == 'undefined') {
                            c2 = dimMatrix[0] - 1;
                        }
                        sx = 0;
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[j]) == 'number') {
                                sx += mtx[j] * mtx[j];
                            } else {
                                throw new Error('Invalid element ' + mtx[j] + ' in matrix for function sum. All elements must be numeric.');
                            }
                        }
                    } else {
                        throw new Error('Invalid argument for function sum. The matrix must be one or two-dimensional.');
                    }
                }
            } else {
                throw new Error('Invalid argument for function sum. Argument must be a matrix.');
            }
            res = sx;
            return res;
        }

        /**
         * Returns the transpose of an array.
         * @param {object}   mtx - The matrix.
         * @param {number}   r1 - First row.
         * @param {number}   c1 - First column.
         * @param {number}   r2 - Last row.
         * @param {number}   c2 - Last column.
         * @return {number}  The transpose of an array.
         */
        this.trans = function(mtx, r1, c1, r2, c2)
        {
            var res;
            if (core.type(mtx) == 'matrix') {
                dimMatrix = core.dim(mtx);
                if (dimMatrix.length == 2) {
                    res = 0;
                    if (typeof r1 == 'undefined') {
                        r1 = 0;
                    }
                    if (typeof c1 == 'undefined') {
                        c1 = 0;
                    }
                    if (typeof r2 == 'undefined') {
                        r2 = dimMatrix[0] - 1;
                    }
                    if (typeof c2 == 'undefined') {
                        c2 = dimMatrix[1] - 1;
                    }
                    res = core.matrix(0, dimMatrix[0], dimMatrix[1]);
                    for (var i = r1; i <= r2; i++) {
                        for (var j = c1; j <= c2; j++) {
                            if (core.type(mtx[i][j]) == 'number') {
                                res[j][i] = mtx[i][j];
                            } else {
                                throw new Error('Invalid element ' + mtx[i][j] + ' in matrix for function trans. All elements must be numeric.');
                            }
                        }
                    }
                } else {
                    throw new Error('Invalid argument for function trans. The matrix must be two-dimensional.');
                }
            } else {
                throw new Error('Invalid argument for function trans. Argument must be a matrix.');
            }
            return res;
        }

        /**
         * Calculates the triangular equivalent matrix.
         * @param {object}  mtx - The matrix to calculate the triangular equivalent matrix.
         * @return {array}  A (rows x columns) matrix.
         */
        this.triang = function(mtx)
        {
            if (core.type(mtx) == 'matrix') {
                var dim = core.dim(mtx);
                var m = dim[0];
                var n = dim[1];
                // Convert to the triangular equivalent matrix.
                var cpy = mtx.slice();
                for (k = 0; k < m - 1; k++) {
                    for (i = k + 1; i < m; i++) {
                        var scale = -cpy[i][k] / cpy[k][k]
                        for (j = 0; j < n; j++) {
                            cpy[i][j] = cpy[i][j] + scale * cpy[k][j];
                        }
                    }
                }
                // Calculates the determinant of the matrix.
                var det = 1;
                for (i = 0; i < m; i++) {
                    det = det * cpy[i][i];
                }
                if (det == 0) {
                    throw new Error('The matrix is singular, in the expression triang(' + core.toString(mtx) + ').');
                }
            } else {
                throw new Error('The argument for function triang() must be a matrix, in the expression triang(' + core.toString(mtx) + ').');
            }
            return cpy;
        }
    }

    matrix = new Matrix();