import { curry, map, fold, max, min, reduce, equals, not, flatMap, identity } from 'fun.js'
import concat from './util/concat'
import empty from './util/empty'
import dot from './util/dot'
import matrixIdentity from './util/identity'
import transpose from './util/transpose'
import generate from './util/generate'
import solve from './util/solve'
/**
* @class Matrix
* @classdesc Matrix applicative providing standard matrix operations
* @summary The Matrix class should not be instantiated with the new keyword. Instead use the Matrix.of syntax to create a new Matrix. Unfortunatly jsdocs does not allow for the constructor to be hidden.
* @hidecontructor
* @see Matrix.of
* @example
*
* const m = Matrix.of([[1,2],[2,3],[4,5]])
*
*/
let Matrix = function (val) {
this.__value = val
}
/**
* @memberOf Matrix
* @static
* @function Matrix.of
* @desc Creates a Matrix object and flattens the Matrix
* @param {Array|Function} val - An array of arrays
* @returns {Matrix}
* @example
*
* const m = Matrix.of([[1,2],[2,3],[4,5]])
*
*/
Matrix.of = function (val) {
if (val instanceof Matrix) return val
if (this instanceof Matrix) {
this.__value = val
return this
}
return new Matrix(val)
}
/**
* @memberOf Matrix
* @property {String} type - Returns the string 'Matrix' for all Matrix objects
* @type {String}
* @example
*
* const m = Matrix.of([[1,2],[2,3],[4,5]])
* m.type === 'Matrix'
*/
Matrix.prototype.type = 'Matrix'
/**
* @memberOf Matrix
* @property {Number} precision - Floating point precision is set to 4 by default
* @type {Number}
* @example
*
* const m = Matrix.of([[1,2],[2,3],[4,5]])
* m.precision === 4
*/
Matrix.prototype.precision = 4
/**
* @memberOf Matrix
* @function Matrix#setPrecision
* @desc The precision is used in floating point calculations for the dot product
* @param {Number} [precision=4] - Set the number of decimals for rounding
* @example
*
* const m = Matrix.of([[1,2],[2,3],[4,5]])
* m.setPrecision(10)
* m.precision === 10
*/
Matrix.prototype.setPrecision = function (precision) {
this.precision = precision
}
/**
* @memberOf Matrix
* @function Matrix#isSymmetric
* @desc Boolean indicating whether the Matrix is symmetric by testing for equality of the transposed Matrix.
* @returns {Boolean}
* @example
*
* const A = Matrix.of([[1, 1], [1, 1]])
* true === A.isSymmetric()
*
*/
Matrix.prototype.isSymmetric = function () {
const a = this.__value
const b = Matrix.transpose(this).__value
return equals(a, b)
}
/**
* @memberOf Matrix
* @function Matrix#isSquare
* @desc Boolean indicating whether the Matrix object is square.
* @returns {Boolean}
* @example
*
* const A = Matrix.of([[1, 1], [1, 1]])
* true === A.isSquare()
*
*/
Matrix.prototype.isSquare = function () {
return equals(this.getCols(), this.getRows())
}
/**
* @memberOf Matrix
* @function Matrix#isOrthogonal
* @desc Boolean indicating whether the Matrix is orthogonal by testing for equality between Identity Matrix and the dot product of the Matrix and its transpose.
* @returns {Boolean}
* @example
*
* const result = [[-0.3092, -0.9510], [-0.9510, 0.3092]]
* const A = Matrix.fromArray(result)
* true === A.isOrthogonal()
*/
Matrix.prototype.isOrthogonal = function () {
const AxAt = this.dot(this.transpose())
const I = this.identity()
return equals(AxAt, I)
}
/**
* @memberOf Matrix
* @function Matrix#getCols
* @desc Number indicating the number of columns in the Matrix
* @returns {Number}
* @example
*
* const A = Matrix.of([[1, 1], [1, 1]])
* A.getCols() === 2
*
*/
Matrix.prototype.getCols = function () {
return this.__value[0].length
}
/**
* @memberOf Matrix
* @function Matrix#equals
* @desc Function returning a boolean testing for equality of the values of a Matrix or Array
* @param {Matrix|Array} M - Matrix or Array to compare for equality
* @returns {Boolean}
* @example
*
* var a = [[1, 1], [1, 1]]
* var A = Matrix.of(a)
* var B = Matrix.of(a)
* true === A.equals(B)
*/
Matrix.prototype.equals = function (M) {
return equals(this.__value, M.__value || M)
}
/**
* @memberOf Matrix
* @function Matrix#getRows
* @desc Number indicating the number of rows in the Matrix
* @returns {Number}
* @example
*
* const A = Matrix.of([[1, 1], [1, 1]])
* A.getRows() // 2
*/
Matrix.prototype.getRows = function () {
return this.__value.length
}
/**
* @memberOf Matrix
* @function Matrix#getShape
* @returns {Array}
* @example
*
* const A = Matrix.of([[1, 1], [1, 1]])
* A.getShape() // [2, 2]
*/
Matrix.prototype.getShape = function () {
return [this.getRows(), this.getCols()]
}
/**
* @memberOf Matrix
* @function Matrix#map
* @description Maps over the rows of the matrix using a map function
* @param {Function} f - An iterator function
* @returns {Matrix}
* @example
*
* const m = Matrix.of([[1, 1], [1, 1]])
* m.map(x => x.map(y => y+ 1))
* // [[2, 2], [2, 2]]
*
*/
Matrix.prototype.map = function (f) {
return Matrix.of(map(f)(this.__value))
}
/**
* @memberOf Matrix
* @static
* @function Matrix.map
* @description Curried function that maps over the rows of the matrix using a map function
* @param {Function} f - An iterator function
* @param {Matrix|Array} M - Matrix or array to map
* @returns {Matrix}
* @example
*
* const m = Matrix.map(x= > x.map(y => y+ 1), [[1, 1], [1, 1]])
* // [[2, 2], [2, 2]]
*
*/
Matrix.map = curry(function (f, M) {
return Matrix.of(M).map(f)
})
/**
* @memberOf Matrix
* @function Matrix#flatMap
* @description Runs flatMap on the value of hte Matrix
* @param {Function} fn Flatten function
* @returns {*}
* @example
*
* const a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
* const A = Matrix.of(a)
* const flattenedArray = A.flatMap(x => x) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
*
*/
Matrix.prototype.flatMap = function (fn) {
return flatMap(fn)(this.__value)
}
/**
* @memberOf Matrix
* @function Matrix.flatMap
* @description Runs flatMap on the value of hte Matrix
* @param {Function} fn Flatten function
* @param {Matrix | Array} M
* @returns {*}
* @example
*
* const a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
* const flattenedArray = Matrix.flatMap(x => x, a) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
*
*/
Matrix.flatMap = curry(function (fn, M) {
return Matrix.of(M).flatMap(fn)
})
/**
* @memberOf Matrix
* @function Matrix#flatten
* @description Flattens the value of a Matrix into an one dimensional array
* @returns {Array}
* @example
*
* const a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
* const A = Matrix.of(a)
* const flattenedArray = A.flatten() // [1, 2, 3, 4, 5, 6, 7, 8, 9]
*
*/
Matrix.prototype.flatten = function () {
return this.flatMap(identity)
}
/**
* @memberOf Matrix
* @function Matrix.flatten
* @description Flattens the value of a Matrix into an one dimensional array
* @returns {Array}
* @returns {*}
* @example
*
* const a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
* const flattenedArray = Matrix.flatten(a) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
*
*/
Matrix.flatten = function (M) {
return Matrix.of(M).flatMap(identity)
}
/**
* @memberOf Matrix
* @function Matrix#fold
* @description Reduce the matrix rows using a reduce function
* @param {Function} f - A reduce/fold function
* @returns {Matrix}
* @example
*
* // Flatten Matrix
* Matrix.of([[1, 1], [1, 1]]).fold((prev, next) => prev.concat(next))
* // [1, 1, 1, 1]
*/
Matrix.prototype.fold = function (f) {
return Matrix.of(fold(f, [])(this.__value))
}
/**
* @memberOf Matrix
* @static
* @function Matrix.fold
* @description Static function to reduce the matrix rows using a reduce function
* @param {Function} f - A reduce/fold function
* @param {Matrix|Array} M - The Matrix to reduce
* @returns {Matrix}
* @example
* // Sum of all matrix values
* const reducer = (prev, next) => Number(prev) + next.reduce((acc, x) => acc + x, 0)
* const A = Matrix.of([[1, 1], [1, 1]]
* Matrix.fold(reducer, A)
* // 4
*/
Matrix.fold = curry(function (f, M) {
return Matrix.of(M).fold(f)
})
/**
* @memberOf Matrix
* @function Matrix#ap
* @description Function that applies a function to a Matrix
* @param {Matrix|Array} M - Matrix or Array to apply a function
* @returns {Matrix}
* @example
*
* const f = x => x.reduce((prev, next) => prev + next)
* const A = Matrix.of([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
* Matrix.of(f).ap(M)
* // [[6], [15], [24]
*/
Matrix.prototype.ap = function (M) {
return Matrix.of(M).map(this.__value)
}
/**
* @memberOf Matrix
* @static
* @function Matrix.ap
* @description Curried function that applies a function to a Matrix
* @param {Function} f - Function that accepts a Matrix as input
* @param {Matrix|Array} M - Matrix or Array to apply a function
* @returns {Matrix}
* @example
*
* const f = x => x.reduce((prev, next) => prev + next)
* Matrix.ap(f, [[1, 2, 3], [4, 5, 6], [7, 8, 9]])
* // [[6], [15], [24]
*/
Matrix.ap = curry(function (f, M) {
return Matrix.of(f).ap(M)
})
/**
* @memberOf Matrix
* @function Matrix#concat
* @description Concatenates 2 Matrices using a function as operator
* @param {Matrix} M - The right side of the concatenation/product
* @param {Function} [f=concat] - A curried function accepting 2 matrices as input
* @returns {Matrix}
* @example
*
* const a = [[0, 1, 1], [2, 3, 4]]
* const b = [[2, 2, 2], [3, 3, 3]]
* const A = Matrix.of(a)
* const B = Matrix.of(b)
* const M = A.concat(B)
* // [[0, 1, 1, 2, 2, 2], [2, 3, 4, 3, 3, 3]]
*
*/
Matrix.prototype.concat = function (M, f = concat) {
return this.map(f(M))
}
/**
* @memberOf Matrix
* @static
* @function Matrix.concat
* @description A curried function that concatenates 2 Matrices using a function as operator
* @param {Matrix} A - Left side Matrix of the concatenation
* @param {Matrix} B - Right side Matrix of the concatenation
* @param {Function} [f=concat] - A curried function accepting 2 matrices as input
* @returns {Matrix}
* @example
*
* const a = [[0, 1, 1], [2, 3, 4]]
* const b = [[2, 2, 2], [3, 3, 3]]
* const A = Matrix.of(a)
* const B = Matrix.of(b)
* const M = Matrix.concat(A, B)
* // [[0, 1, 1, 2, 2, 2], [2, 3, 4, 3, 3, 3]]
*
*/
Matrix.concat = curry(function (A, B, f = concat) {
return Matrix.of(A).map(f(B))
})
/**
* @memberOf Matrix
* @function Matrix#empty
* @description Returns an empty Matrix from an existing Matrix
* @returns {Matrix}
*/
Matrix.prototype.empty = function () {
return this.map(empty)
}
/**
* @memberOf Matrix
* @static
* @function Matrix.empty
* @description Returns an empty Matrix from an existing Matrix
* @param {Number} [rows=0] - Rows to generate
* @param {Number} [cols=0] - Cols to generate
* @returns {Matrix}
*/
Matrix.empty = curry(function (rows = 0, cols = 0) {
const m = generate(rows, cols) // Array.apply(null, Array(rows)).map(x => Array.apply(null, Array(cols)))
return Matrix.of(m).map(empty)
})
// #### #### #### //
/**
* @memberOf Matrix
* @function Matrix#matrixIdentity
* @desc Returns an matrixIdentity matrix
* @returns {Matrix}
* @example
*
* const a = [[1, 2, 3], [4, 5, 6]]
* const A = Matrix.of(a)
* const Aidentity = A.matrixIdentity()
* // [[1, 0, 0], [0, 1, 0]]
*
*/
Matrix.prototype.identity = function () {
return Matrix.of(matrixIdentity).ap(this)
}
/**
* @memberOf Matrix
* @static
* @function Matrix.matrixIdentity
* @desc curried fucntion that returns an matrixIdentity matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.matrixIdentity(3, 2)
* // [[1, 0, 0], [0, 1, 0]]
*
*/
Matrix.identity = curry(function (rows, cols) {
const m = generate(rows, cols) // Array.apply(null, Array(rows)).map(x => Array.apply(null, Array(cols)))
return Matrix.of(matrixIdentity).ap(m)
})
/**
* @memberOf Matrix
* @function Matrix#combine
* @desc Concatenates 2 Matrices together.
* @see Matrix.concat
* @param {Matrix } M - Right side Matrix of the combine operation
* @returns {Matrix}
*/
Matrix.prototype.combine = function (M) {
return this.concat(Matrix.of(M), concat)
}
/**
* @memberOf Matrix
* @static
* @function Matrix.combine
* @desc Curried fucntion that combines 2 Matrices
* @see Matrix.concat
* @param {Matrix} A - Left side of the combine operator
* @param {Matrix} A - Right side of the combine operator
* @returns {Matrix}
*/
Matrix.combine = curry(function (A, B) {
return Matrix.of(A).concat(Matrix.of(B), concat)
})
/**
* @memberOf Matrix
* @function Matrix#dot
* @description Returns the dot product between 2 matrices
* @param {Matrix|Array} M - Right side of the dot product
* @returns {Matrix}
* @example
*
* // Create matrix
* const m = Matrix.of([[1, 2], [3, 4]])
*
* // Generate matrixIdentity matrix
* const I = m.matrixIdentity() // [[1, 0], [0, 1]]
*
* if(m.dot(I).equals(m)) {
* console.log('Dot product with matrixIdentity matrix returns the same matrix')
* }
*
*/
Matrix.prototype.dot = function (M) {
return this.concat(Matrix.of(M), dot(this.precision))
}
/**
* @memberOf Matrix
* @static
* @function Matrix.dot
* @description Curried fucntion that returns the dot product of 2 matrices
* @param {Matrix|Array} A - Left side of the dot product
* @param {Matrix|Array} B - Right side of the dot product
* @returns {Matrix}
* @example
* const a = [[1, 2, 3], [4, 5, 6]]
* const b = [[7, 8], [9, 10], [11, 12]]
*
* const A = Matrix.of(a)
* const B = Matrix.of(b)
*
* Matrix.dot(A, B) // [[58, 64], [139, 154]]
*
*/
Matrix.dot = curry(function (A, B) {
return Matrix.of(A).dot(Matrix.of(B))
})
/**
* @memberOf Matrix
* @function Matrix#fill
* @desc Fill up an empty matrix with the provided map function
* @param {Function} f - Function that generates a value
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[1,2,3], [3,2,1], [4,5,6]]).fill(x => 42)
* // [[42,42,42], [42,42,42], [42,42,42]]
*
*/
Matrix.prototype.fill = function (f) {
return this.map(map(x => f(x)))
}
/**
* @memberOf Matrix
* @function Matrix#zeros
* @desc Fill up an empty matrix with zeros
* @returns {Matrix}
*
* @example
*
* const A = Matrix.of([[1,2,3], [3,2,1], [4,5,6]]).zeros()
* // [[0,0,0], [0,0,0], [0,0,0]]
*
*/
Matrix.prototype.zeros = function () {
return this.fill(x => 0)
}
/**
* @memberOf Matrix
* @function Matrix.zeros
* @desc Fill up an empty matrix with zeros
* @param {Number} rows - Defines the rows of the matrix
* @param {Number} cols - Defines the columns of the matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.zeros(3, 3)
* // [[0,0,0], [0,0,0], [0,0,0]]
*
*/
Matrix.zeros = function (rows, cols) {
const m = generate(rows, cols)
return Matrix.of(m).fill(x => 0)
}
/**
* @memberOf Matrix
* @function Matrix#ones
* @desc Fill up an empty matrix with ones
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[1,2,3], [3,2,1], [4,5,6]]).ones()
* // [[1,1,1], [1,1,1], [1,1,1]]
*
*/
Matrix.prototype.ones = function () {
return this.fill(x => 1)
}
/**
* @memberOf Matrix
* @function Matrix.ones
* @desc Fill up an empty matrix with ones
* @param {Number} rows - Defines the rows of the matrix
* @param {Number} cols - Defines the columns of the matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.ones(1, 1)
* // [[1,1,1], [1,1,1], [1,1,1]]
*
*/
Matrix.ones = function (rows, cols) {
const m = generate(rows, cols)
return Matrix.of(m).fill(x => 1)
}
/**
* @memberOf Matrix
* @function Matrix#random
* @desc Fill up an empty matrix with random values
* @param {Function} [f = e => Math.random() * 2 - 1] - Function producing random values, can be any type of value
* @returns {Matrix}
*/
Matrix.prototype.random = function (f = e => Math.random() * 2 - 1) {
return this.fill(f)
}
/**
* @memberOf Matrix
* @function Matrix.random
* @desc Fill up an empty matrix with random numbers
* @param {Function} f - Function which returns random values. Default random values are between -1 and 1
* @param {Number} rows - Defines the rows of the matrix
* @param {Number} cols - Defines the columns of the matrix
* @returns {Matrix}
*/
Matrix.random = function (f = e => (Math.random() * 2 - 1), rows, cols) {
const m = generate(rows, cols)
return Matrix.of(m).fill(f)
}
/**
* @memberOf Matrix
* @function Matrix#toArray
* @desc Returns the array from the matrix
* @returns {Array}
*/
Matrix.prototype.toArray = function () {
return this.__value.map(row => row.map(col => col))
}
/**
* @memberOf Matrix
* @function Matrix#clone
* @desc Returns a clone of the matrix
* @returns {Matrix}
*/
Matrix.prototype.clone = function () {
const M = Matrix.fromArray(this.__value)
M.setPrecision(this.precision)
return M
}
/**
* @memberOf Matrix
* @function Matrix#fromArray
* @desc Returns a Matrix from an array
* @returns {Array}
*/
Matrix.fromArray = function (arr) {
return Matrix.of(map(row => map(col => col)(row))(arr))
}
/**
* @memberOf Matrix
* @function Matrix#transpose
* @desc Returns a transposed Matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.of([-1, 2], [3, 4], [-8, 2])
* const b = A.transpose().toArray()
* // returns [[-1, 3,-8], [2, 4, 2]]
*/
Matrix.prototype.transpose = function () {
return Matrix.of(fold(transpose, [], this.__value))
}
/**
* @memberOf Matrix
* @function Matrix.transpose
* @desc Returns a transposed Matrix
* @param {Matrix|Array} M - A Matrix or a matrix array
* @returns {Matrix}
* @example
*
* const a = [-1, 2], [3, 4], [-8, 2]
* const b = Matrix.transpose(a).toArray()
* // returns [[-1, 3,-8], [2, 4, 2]]
*/
Matrix.transpose = function (M) {
return Matrix.of(M).transpose()
}
/**
* @memberOf Matrix
* @function Matrix#add
* @desc Adds a number or a Matrix to this
* @param {Matrix|Number} M - Add a Matrix or a number
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[5, 4]])
* A.add(1) // [[6, 5]]
* const B = Matrix.of([[5, 5]])
* B.add(B) // [[10, 10]]
*
*/
Matrix.prototype.add = function (M) {
if (M instanceof Matrix) {
if (this.getCols() !== M.getCols() || this.getRows() !== M.getRows()) {
throw new Error('Matrices do not match, cannot add')
}
return this.map((row, idx) => map((val, jdx) => val + M.__value[idx][jdx])(row))
} else {
return this.map(map(x => x + M))
}
}
/**
* @memberOf Matrix
* @function Matrix#subtract
* @desc Subtracts a number or a Matrix from this
* @param {Matrix|Number} M - Subtract a Matrix or a number
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[5, 4]])
* A.subtract(1) // [[4, 2]]
* const B = Matrix.of([[5, 5]])
* B.subtract(B) // [[0, 0]]
*
*/
Matrix.prototype.subtract = function (M) {
if (M instanceof Matrix) {
if (this.getCols() !== M.getCols() || this.getRows() !== M.getRows()) {
throw new Error('Matrices do not match, cannot subtract')
}
return this.map((row, idx) => map((val, jdx) => val - M.__value[idx][jdx])(row))
} else {
return this.map(map(x => x - M))
}
}
/**
* @memberOf Matrix
* @function Matrix#multiply
* @desc Mutliply a scalar or a matrix with a matrix. Throws an error if the multiplication is not possible.
* @param {Matrix|Number} M - A Matrix M or a Number to multiply a Matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[5, 4]])
* A.multiply(2) // [[10, 8]]
* const B = Matrix.of([[5, 5]])
* B.multiply(B) // [[25, 25]]
*
*/
Matrix.prototype.multiply = function (M) {
if (M instanceof Matrix) {
if (this.getCols() !== M.getCols() || this.getRows() !== M.getRows()) {
console.log('Use static method \'dot\' to do matrix multiplication')
throw new Error('Matrices do not match, cannot create hadamard product')
}
return this.map((row, idx) => map((col, jdx) => col * M.__value[idx][jdx])(row))
} else {
return this.map(map(x => x * M))
}
}
/**
* @memberOf Matrix
* @function Matrix#additiveinverse
* @desc Function that returns the matrix obtained by changing the sign of every matrix element. The additive inverse of matrix A is written –A.
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[5,-5], [-4, 4]])
* const minusA = A.additiveinverse()
* // [[-5, 5], [4, -4]]
*/
Matrix.prototype.additiveinverse = function () {
return this.multiply(-1)
}
/**
* @memberOf Matrix
* @function Matrix#hadamard
* @desc Hadamar is an alias of the multiply function
* @see Matrix.multiply
* @param {Matrix|Number} M - A Matrix M or a Number to multiply a Matrix
* @returns {Matrix}
* @see Matrix.hadamard
* @example
*
* const A = Matrix.of([[5, 4]])
* A.hadamard(2) // [[10, 8]]
* const B = Matrix.of([[5, 5]])
* B.hadamard(B) // [[25, 25]]
*/
Matrix.prototype.hadamard = function (M) {
return this.multiply(M)
}
/**
* @memberOf Matrix
* @function Matrix#lu
* @desc Calculates LU decomposition of the Matrix
* @returns {Matrix[]}
* @example
*
* const result = [[3, -7, -2, 2], [-3, 5, 1, 0], [6, -4, 0, -5], [-9, 5, -5, 12]]
* const A = Matrix.fromArray(result)
* const lu = A.lu()
* // L.__value = [ [ 1, 0, 0, 0 ], [ -1, 1, 0, 0 ], [ 2, -5, 1, 0 ], [ -3, 8, 3, 1 ] ]
* // U.__value = [ [ 3, -7, -2, 2 ], [ 0, -2, -1, 2 ], [ 0, 0, -1, 1 ], [ 0, 0, 0, -1 ] ]
* Matrix.dot(lu[0], lu[1]) // returns clone of A
*
*/
Matrix.prototype.lu = function () {
const n = this.getRows()
const tol = 1e-6
const A = this.clone()
const L = this.zeros()
const U = this.zeros()
for (let k = 0; k < n; ++k) {
if (Math.abs(A.__value[k][k]) < tol) throw Error('Cannot proceed without a row exchange')
L.__value[k][k] = 1
for (let i = k + 1; i < n; ++i) {
L.__value[i][k] = A.__value[i][k] / A.__value[k][k]
for (let j = k + 1; j < n; ++j) {
A.__value[i][j] = A.__value[i][j] - L.__value[i][k] * A.__value[k][j]
}
}
for (let l = k; l < n; ++l) {
U.__value[k][l] = A.__value[k][l]
}
}
return [L, U]
}
/**
* @memberOf Matrix
* @function Matrix#rref
* @desc Returns a Matrix containing the row reduced echelon form
* @returns {Matrix}
* @example
*
* var A = Matrix.of([[-1, 1], [-1, 0], [0, -1], [-1, -2]])
* A.rref() // [ [ 1, 0 ], [ -0, 1 ], [ 0, 0 ], [ 0, 0 ] ]
*/
Matrix.prototype.rref = function () {
let lead = 0
const resultMatrix = this.clone()
for (let r = 0; r < this.getRows(); ++r) {
if (this.getCols() <= lead) {
return resultMatrix
}
let i = r
while (resultMatrix.__value[i][lead] === 0) {
++i
if (this.getRows() === i) {
i = r
++lead
if (this.getCols() === lead) {
return resultMatrix
}
}
}
let tmp = resultMatrix.__value[i]
resultMatrix.__value[i] = resultMatrix.__value[r]
resultMatrix.__value[r] = tmp
let val = resultMatrix.__value[r][lead]
for (let j = 0; j < this.getCols(); ++j) {
resultMatrix.__value[r][j] /= val
}
for (let i = 0; i < this.getRows(); ++i) {
if (i === r) continue
val = resultMatrix.__value[i][lead]
for (let j = 0; j < this.getCols(); ++j) {
resultMatrix.__value[i][j] -= val * resultMatrix.__value[r][j]
}
}
lead++
}
return resultMatrix
}
/**
* @memberOf Matrix
* @function Matrix#solve
* @desc Returns the solution for a system of linear equations
* @param {Array} b - The numbers for which to solve the system of linear equations
* @returns {Array}
* @example
*
* // Solve xA = b
* // 5x + y = 7
* // 3x - 4y = 18
* // Solution for x and y:
* // x = 2
* // y = -3
*
* const A = Matrix.of([[5, 1], [3, -4]])
* const solveA = A.solve([7, 18]) // [2, -3]
*
*/
Matrix.prototype.solve = function (b) {
const LU = this.lu()
const L = LU[0]
const U = LU[1]
const n = this.getRows()
return solve(n, L, U, b)
}
/**
* @memberOf Matrix
* @function Matrix#inverse
* @desc Returns the inverse of a Matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[1, 1], [2, 4]]).inverse()
* // [ [ 2, -0.5 ], [ -1, 0.5 ] ]
*
*/
Matrix.prototype.inverse = function () {
const A = this.clone()
const I = A.identity()
const Inv = A.concat(I).rref()
const result = Inv.__value.reduce((result, x, idx) => {
const half = x.length / 2
result.push(x.slice(half, x.length))
return result
}, [])
return Matrix.of(result)
}
/**
* @memberOf Matrix
* @function Matrix#rank
* @desc Number indicating the maximum number of linearly independent columns.
* @returns {Number}
*/
Matrix.prototype.rank = function () {
const rref = this.rref()
let result = 0
for (let i = 0; i < rref.getCols(); ++i) {
result += rref.__value[i][i]
}
return result
}
/**
* @memberOf Matrix
* @function Matrix#dimension
* @desc Number indicating the maximum number of linearly independent columns.
* @see Matrix.rank
* @returns {Number}
*/
Matrix.prototype.dimension = function () {
return this.rank()
}
/**
* @memberOf Matrix
* @function Matrix#diag
* @desc Returns an array containing the values on the diagonal
* @returns {Array}
* @example
*
* const diag1 = Matrix.ones(3, 3).diag()
* // [1, 1, 1]
*
* const diag0 = Matrix.zeros(5, 5).diag()
* // [0, 0, 0, 0, 0]
*
*/
Matrix.prototype.diag = function () {
return fold((acc, x, idx) => {
return acc.concat(x[idx])
})([])(this.__value)
}
/**
* @memberOf Matrix
* @function Matrix.diag
* @desc Returns an array containing the values on the diagonal
* @param {Matrix|Array} M - Matrix from which to return the diagonal
* @returns {Array}
* @example
*
* const diag1 = Matrix.diag([[2, 1], [1, 5]])
* // [2, 5]
*
*/
Matrix.diag = function (M) {
return Matrix.of(M).diag()
}
/**
* @memberOf Matrix
* @function Matrix#diagproduct
* @desc Returns the product of the values on the diagonal
* @returns {Number}
* @example
*
* const diag1 = Matrix.ones(3, 3).diagproduct()
* // 1
*
* const diag0 = Matrix.zeros(5, 5).diagproduct()
* // 0
*
*/
Matrix.prototype.diagproduct = function () {
return fold((acc, x, idx) => {
acc *= x[idx]
return acc
})(1)(this.__value)
}
/**
* @memberOf Matrix
* @function Matrix.diagproduct
* @desc Returns the product of the values on the diagonal
* @param {Matrix|Array} M - Matrix from which to return the diagonal
* @returns {Number}
* @example
*
* const diag1 = Matrix.diagproduct([[2, 1], [1, 5]])
* // 10
*
*/
Matrix.diagproduct = function (M) {
return Matrix.of(M).diagproduct()
}
/**
* @memberOf Matrix
* @function Matrix#sum
* @desc Returns the sum of the values in the Matrix
* @returns {Number}
* @example
*
* const diag1 = Matrix.ones(3, 3).sum()
* // 9
*
* const diag0 = Matrix.zeros(5, 5).sum()
* // 0
*
*/
Matrix.prototype.sum = function () {
return fold((acc, x) => {
acc += fold((prev, next) => prev + next)(0)(x)
return acc
})(0)(this.__value)
}
/**
* @memberOf Matrix
* @function Matrix.sum
* @desc Returns the sum of the values in the Matrix
* @param {Matrix|Array} M - Matrix from which to return the diagonal
* @returns {Number}
* @example
*
* const diag1 = Matrix.sum([[2, 1], [1, 5]])
* // 9
*
*/
Matrix.sum = function (M) {
return Matrix.of(M).sum()
}
/**
* @memberOf Matrix
* @function Matrix#kronecker
* @desc The Kronecker product is an operation on two matrices of arbitrary size resulting in a block matrix.
* @param {Matrix} M - The right side Matrix of the product (this ⊗ M)
* @returns {Matrix}
*/
Matrix.prototype.kronecker = function (M) {
const m = this.getRows()
const n = this.getCols()
const p = M.getRows()
const q = M.getCols()
const left = this.__value
const right = M.__value
const frame = generate(m * p, n * q)
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
for (let k = 0; k < p; k++) {
for (let l = 0; l < q; l++) {
frame[p * i + k][q * j + l] = left[i][j] * right[k][l]
}
}
}
}
return Matrix.of(frame)
}
/**
* @memberOf Matrix
* @function Matrix.kronecker
* @desc The Kronecker product is an operation on two matrices of arbitrary size resulting in a block matrix.
* @param {Matrix} A - The left side Matrix of the product (A ⊗ B)
* @param {Matrix} B - The right side Matrix of the product (A ⊗ B)
* @returns {Matrix}
*/
Matrix.kronecker = function (A, B) {
return Matrix.of(A).kronecker(B)
}
/**
* @memberOf Matrix
* @function Matrix#determinant
* @desc Calculates the determinant of a square Matrix using Sarrus' rule or LU decomposition
* @returns {Number}
*/
Matrix.prototype.determinant = function () {
if (this.isSquare()) {
if (this.getCols() === 2) {
const a = this.__value[0][0]
const b = this.__value[0][1]
const c = this.__value[1][0]
const d = this.__value[1][1]
return a * d - b * c
}
const lu = this.lu()
const detA = Number(lu[0].diagproduct())
const detB = Number(lu[1].diagproduct())
return detA * detB
} else {
throw Error('The Matrix needs to be a square Matrix to calculate the determinant')
}
}
/**
* @memberOf Matrix
* @function Matrix.determinant
* @desc Calculates the determinant of a square Matrix using Sarrus' rule or LU decomposition
* @param {Matrix|Array} A - Matrix as input to calculate the determinant
* @returns {Number}
*/
Matrix.determinant = function (A) {
return Matrix.of(A).determinant()
}
/**
* @memberOf Matrix
* @function Matrix#max
* @desc Returns the largest number in the Matrix
* @returns {*}
*/
Matrix.prototype.max = function () {
return reduce(max, [].concat.apply([], this.__value))
}
/**
* @memberOf Matrix
* @function Matrix#min
* @desc Returns the smallest number in the Matrix
* @returns {*}
*/
Matrix.prototype.min = function () {
return reduce(min, [].concat.apply([], this.__value))
}
/**
* @memberOf Matrix
* @function Matrix#divide
* @desc Divide a scalar or a matrix by a matrix. Throws an error if the division is not possible.
* @param {Matrix|Number} M - A Matrix M or a Number to divide a Matrix
* @returns {Matrix}
* @example
*
* const A = Matrix.of([[5, 4]])
* A.divide(2) // [[10, 8]]
* const B = Matrix.of([[1, 1], [2, 4]])
* B.divide(B) // [[1, 0], [0, 1]]
*
*/
Matrix.prototype.divide = function (M) {
if (M instanceof Matrix) {
if (this.getCols() !== M.getCols() || this.getRows() !== M.getRows()) {
throw new Error('Matrices do not match, cannot create division')
}
if (not(M.isSquare())) {
throw new Error('Matrix is not square, cannot create inverse')
}
const mInv = M.inverse()
return this.dot(mInv)
} else {
return this.multiply(1 / M)
}
}
/**
* @memberOf Matrix
* @function Matrix#getColumn
* @desc Returns the values of a Matrix column
* @param {Number} index Index of the column
* @returns {Array}
*/
Matrix.prototype.getColumn = function (index) {
return this.flatMap(x => x[index])
}
/**
* @memberOf Matrix
* @function Matrix.getColumn
* @desc Returns the values of a Matrix column
* @param {Number} index Index of the column
* @param {Matrix | Array} M
* @returns {Array}
*/
Matrix.getColumn = curry(function (index, M) {
return Matrix.of(M).getColumn(index)
})
/**
* @memberOf Matrix
* @function Matrix#getRow
* @desc Returns the values of a Matrix row
* @param {Number} index Index of the row
* @returns {Array}
*/
Matrix.prototype.getRow = function (index) {
return this.__value[index]
}
/**
* @memberOf Matrix
* @function Matrix.getRow
* @desc Returns the values of a Matrix row
* @param {Number} index Index of the row
* @param {Matrix | Array} M
* @returns {Array}
*/
Matrix.getRow = curry(function (index, M) {
return Matrix.of(M).getRow(index)
})
export default Matrix