UNPKG

mathjs

Version:

Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif

395 lines (386 loc) 10.4 kB
import { clone } from '../../../utils/object' import { factory } from '../../../utils/factory' const name = 'lup' const dependencies = [ 'typed', 'matrix', 'abs', 'addScalar', 'divideScalar', 'multiplyScalar', 'subtract', 'larger', 'equalScalar', 'unaryMinus', 'DenseMatrix', 'SparseMatrix', 'Spa' ] export const createLup = /* #__PURE__ */ factory(name, dependencies, ( { typed, matrix, abs, addScalar, divideScalar, multiplyScalar, subtract, larger, equalScalar, unaryMinus, DenseMatrix, SparseMatrix, Spa } ) => { /** * Calculate the Matrix LU decomposition with partial pivoting. Matrix `A` is decomposed in two matrices (`L`, `U`) and a * row permutation vector `p` where `A[p,:] = L * U` * * Syntax: * * math.lup(A) * * Example: * * const m = [[2, 1], [1, 4]] * const r = math.lup(m) * // r = { * // L: [[1, 0], [0.5, 1]], * // U: [[2, 1], [0, 3.5]], * // P: [0, 1] * // } * * See also: * * slu, lsolve, lusolve, usolve * * @param {Matrix | Array} A A two dimensional matrix or array for which to get the LUP decomposition. * * @return {{L: Array | Matrix, U: Array | Matrix, P: Array.<number>}} The lower triangular matrix, the upper triangular matrix and the permutation matrix. */ return typed(name, { DenseMatrix: function (m) { return _denseLUP(m) }, SparseMatrix: function (m) { return _sparseLUP(m) }, Array: function (a) { // create dense matrix from array const m = matrix(a) // lup, use matrix implementation const r = _denseLUP(m) // result return { L: r.L.valueOf(), U: r.U.valueOf(), p: r.p } } }) function _denseLUP (m) { // rows & columns const rows = m._size[0] const columns = m._size[1] // minimum rows and columns let n = Math.min(rows, columns) // matrix array, clone original data const data = clone(m._data) // l matrix arrays const ldata = [] const lsize = [rows, n] // u matrix arrays const udata = [] const usize = [n, columns] // vars let i, j, k // permutation vector const p = [] for (i = 0; i < rows; i++) { p[i] = i } // loop columns for (j = 0; j < columns; j++) { // skip first column in upper triangular matrix if (j > 0) { // loop rows for (i = 0; i < rows; i++) { // min i,j const min = Math.min(i, j) // v[i, j] let s = 0 // loop up to min for (k = 0; k < min; k++) { // s = l[i, k] - data[k, j] s = addScalar(s, multiplyScalar(data[i][k], data[k][j])) } data[i][j] = subtract(data[i][j], s) } } // row with larger value in cvector, row >= j let pi = j let pabsv = 0 let vjj = 0 // loop rows for (i = j; i < rows; i++) { // data @ i, j const v = data[i][j] // absolute value const absv = abs(v) // value is greater than pivote value if (larger(absv, pabsv)) { // store row pi = i // update max value pabsv = absv // value @ [j, j] vjj = v } } // swap rows (j <-> pi) if (j !== pi) { // swap values j <-> pi in p p[j] = [p[pi], p[pi] = p[j]][0] // swap j <-> pi in data DenseMatrix._swapRows(j, pi, data) } // check column is in lower triangular matrix if (j < rows) { // loop rows (lower triangular matrix) for (i = j + 1; i < rows; i++) { // value @ i, j const vij = data[i][j] if (!equalScalar(vij, 0)) { // update data data[i][j] = divideScalar(data[i][j], vjj) } } } } // loop columns for (j = 0; j < columns; j++) { // loop rows for (i = 0; i < rows; i++) { // initialize row in arrays if (j === 0) { // check row exists in upper triangular matrix if (i < columns) { // U udata[i] = [] } // L ldata[i] = [] } // check we are in the upper triangular matrix if (i < j) { // check row exists in upper triangular matrix if (i < columns) { // U udata[i][j] = data[i][j] } // check column exists in lower triangular matrix if (j < rows) { // L ldata[i][j] = 0 } continue } // diagonal value if (i === j) { // check row exists in upper triangular matrix if (i < columns) { // U udata[i][j] = data[i][j] } // check column exists in lower triangular matrix if (j < rows) { // L ldata[i][j] = 1 } continue } // check row exists in upper triangular matrix if (i < columns) { // U udata[i][j] = 0 } // check column exists in lower triangular matrix if (j < rows) { // L ldata[i][j] = data[i][j] } } } // l matrix const l = new DenseMatrix({ data: ldata, size: lsize }) // u matrix const u = new DenseMatrix({ data: udata, size: usize }) // p vector const pv = [] for (i = 0, n = p.length; i < n; i++) { pv[p[i]] = i } // return matrices return { L: l, U: u, p: pv, toString: function () { return 'L: ' + this.L.toString() + '\nU: ' + this.U.toString() + '\nP: ' + this.p } } } function _sparseLUP (m) { // rows & columns const rows = m._size[0] const columns = m._size[1] // minimum rows and columns const n = Math.min(rows, columns) // matrix arrays (will not be modified, thanks to permutation vector) const values = m._values const index = m._index const ptr = m._ptr // l matrix arrays const lvalues = [] const lindex = [] const lptr = [] const lsize = [rows, n] // u matrix arrays const uvalues = [] const uindex = [] const uptr = [] const usize = [n, columns] // vars let i, j, k // permutation vectors, (current index -> original index) and (original index -> current index) const pvCo = [] const pvOc = [] for (i = 0; i < rows; i++) { pvCo[i] = i pvOc[i] = i } // swap indices in permutation vectors (condition x < y)! const swapIndeces = function (x, y) { // find pv indeces getting data from x and y const kx = pvOc[x] const ky = pvOc[y] // update permutation vector current -> original pvCo[kx] = y pvCo[ky] = x // update permutation vector original -> current pvOc[x] = ky pvOc[y] = kx } // loop columns for (j = 0; j < columns; j++) { // sparse accumulator const spa = new Spa() // check lower triangular matrix has a value @ column j if (j < rows) { // update ptr lptr.push(lvalues.length) // first value in j column for lower triangular matrix lvalues.push(1) lindex.push(j) } // update ptr uptr.push(uvalues.length) // k0 <= k < k1 where k0 = _ptr[j] && k1 = _ptr[j+1] const k0 = ptr[j] const k1 = ptr[j + 1] // copy column j into sparse accumulator for (k = k0; k < k1; k++) { // row i = index[k] // copy column values into sparse accumulator (use permutation vector) spa.set(pvCo[i], values[k]) } // skip first column in upper triangular matrix if (j > 0) { // loop rows in column j (above diagonal) spa.forEach(0, j - 1, function (k, vkj) { // loop rows in column k (L) SparseMatrix._forEachRow(k, lvalues, lindex, lptr, function (i, vik) { // check row is below k if (i > k) { // update spa value spa.accumulate(i, unaryMinus(multiplyScalar(vik, vkj))) } }) }) } // row with larger value in spa, row >= j let pi = j let vjj = spa.get(j) let pabsv = abs(vjj) // loop values in spa (order by row, below diagonal) spa.forEach(j + 1, rows - 1, function (x, v) { // absolute value const absv = abs(v) // value is greater than pivote value if (larger(absv, pabsv)) { // store row pi = x // update max value pabsv = absv // value @ [j, j] vjj = v } }) // swap rows (j <-> pi) if (j !== pi) { // swap values j <-> pi in L SparseMatrix._swapRows(j, pi, lsize[1], lvalues, lindex, lptr) // swap values j <-> pi in U SparseMatrix._swapRows(j, pi, usize[1], uvalues, uindex, uptr) // swap values in spa spa.swap(j, pi) // update permutation vector (swap values @ j, pi) swapIndeces(j, pi) } // loop values in spa (order by row) spa.forEach(0, rows - 1, function (x, v) { // check we are above diagonal if (x <= j) { // update upper triangular matrix uvalues.push(v) uindex.push(x) } else { // update value v = divideScalar(v, vjj) // check value is non zero if (!equalScalar(v, 0)) { // update lower triangular matrix lvalues.push(v) lindex.push(x) } } }) } // update ptrs uptr.push(uvalues.length) lptr.push(lvalues.length) // return matrices return { L: new SparseMatrix({ values: lvalues, index: lindex, ptr: lptr, size: lsize }), U: new SparseMatrix({ values: uvalues, index: uindex, ptr: uptr, size: usize }), p: pvCo, toString: function () { return 'L: ' + this.L.toString() + '\nU: ' + this.U.toString() + '\nP: ' + this.p } } } })