1 | import { factory } from '../../../utils/factory'
|
2 | import { createCsSpsolve } from './csSpsolve'
|
3 |
|
4 | const name = 'csLu'
|
5 | const dependencies = [
|
6 | 'abs',
|
7 | 'divideScalar',
|
8 | 'multiply',
|
9 | 'subtract',
|
10 | 'larger',
|
11 | 'largerEq',
|
12 | 'SparseMatrix'
|
13 | ]
|
14 |
|
15 | export const createCsLu = /* #__PURE__ */ factory(name, dependencies, ({ abs, divideScalar, multiply, subtract, larger, largerEq, SparseMatrix }) => {
|
16 | const csSpsolve = createCsSpsolve({ divideScalar, multiply, subtract })
|
17 |
|
18 | /**
|
19 | * Computes the numeric LU factorization of the sparse matrix A. Implements a Left-looking LU factorization
|
20 | * algorithm that computes L and U one column at a tume. At the kth step, it access columns 1 to k-1 of L
|
21 | * and column k of A. Given the fill-reducing column ordering q (see parameter s) computes L, U and pinv so
|
22 | * L * U = A(p, q), where p is the inverse of pinv.
|
23 | *
|
24 | * @param {Matrix} m The A Matrix to factorize
|
25 | * @param {Object} s The symbolic analysis from csSqr(). Provides the fill-reducing
|
26 | * column ordering q
|
27 | * @param {Number} tol Partial pivoting threshold (1 for partial pivoting)
|
28 | *
|
29 | * @return {Number} The numeric LU factorization of A or null
|
30 | *
|
31 | * Reference: http://faculty.cse.tamu.edu/davis/publications.html
|
32 | */
|
33 | return function csLu (m, s, tol) {
|
34 | // validate input
|
35 | if (!m) { return null }
|
36 | // m arrays
|
37 | const size = m._size
|
38 | // columns
|
39 | const n = size[1]
|
40 | // symbolic analysis result
|
41 | let q
|
42 | let lnz = 100
|
43 | let unz = 100
|
44 | // update symbolic analysis parameters
|
45 | if (s) {
|
46 | q = s.q
|
47 | lnz = s.lnz || lnz
|
48 | unz = s.unz || unz
|
49 | }
|
50 | // L arrays
|
51 | const lvalues = [] // (lnz)
|
52 | const lindex = [] // (lnz)
|
53 | const lptr = [] // (n + 1)
|
54 | // L
|
55 | const L = new SparseMatrix({
|
56 | values: lvalues,
|
57 | index: lindex,
|
58 | ptr: lptr,
|
59 | size: [n, n]
|
60 | })
|
61 | // U arrays
|
62 | const uvalues = [] // (unz)
|
63 | const uindex = [] // (unz)
|
64 | const uptr = [] // (n + 1)
|
65 | // U
|
66 | const U = new SparseMatrix({
|
67 | values: uvalues,
|
68 | index: uindex,
|
69 | ptr: uptr,
|
70 | size: [n, n]
|
71 | })
|
72 | // inverse of permutation vector
|
73 | const pinv = [] // (n)
|
74 | // vars
|
75 | let i, p
|
76 | // allocate arrays
|
77 | const x = [] // (n)
|
78 | const xi = [] // (2 * n)
|
79 | // initialize variables
|
80 | for (i = 0; i < n; i++) {
|
81 | // clear workspace
|
82 | x[i] = 0
|
83 | // no rows pivotal yet
|
84 | pinv[i] = -1
|
85 | // no cols of L yet
|
86 | lptr[i + 1] = 0
|
87 | }
|
88 | // reset number of nonzero elements in L and U
|
89 | lnz = 0
|
90 | unz = 0
|
91 | // compute L(:,k) and U(:,k)
|
92 | for (let k = 0; k < n; k++) {
|
93 | // update ptr
|
94 | lptr[k] = lnz
|
95 | uptr[k] = unz
|
96 | // apply column permutations if needed
|
97 | const col = q ? q[k] : k
|
98 | // solve triangular system, x = L\A(:,col)
|
99 | const top = csSpsolve(L, m, col, xi, x, pinv, 1)
|
100 | // find pivot
|
101 | let ipiv = -1
|
102 | let a = -1
|
103 | // loop xi[] from top -> n
|
104 | for (p = top; p < n; p++) {
|
105 | // x[i] is nonzero
|
106 | i = xi[p]
|
107 | // check row i is not yet pivotal
|
108 | if (pinv[i] < 0) {
|
109 | // absolute value of x[i]
|
110 | const xabs = abs(x[i])
|
111 | // check absoulte value is greater than pivot value
|
112 | if (larger(xabs, a)) {
|
113 | // largest pivot candidate so far
|
114 | a = xabs
|
115 | ipiv = i
|
116 | }
|
117 | } else {
|
118 | // x(i) is the entry U(pinv[i],k)
|
119 | uindex[unz] = pinv[i]
|
120 | uvalues[unz++] = x[i]
|
121 | }
|
122 | }
|
123 | // validate we found a valid pivot
|
124 | if (ipiv === -1 || a <= 0) { return null }
|
125 | // update actual pivot column, give preference to diagonal value
|
126 | if (pinv[col] < 0 && largerEq(abs(x[col]), multiply(a, tol))) { ipiv = col }
|
127 | // the chosen pivot
|
128 | const pivot = x[ipiv]
|
129 | // last entry in U(:,k) is U(k,k)
|
130 | uindex[unz] = k
|
131 | uvalues[unz++] = pivot
|
132 | // ipiv is the kth pivot row
|
133 | pinv[ipiv] = k
|
134 | // first entry in L(:,k) is L(k,k) = 1
|
135 | lindex[lnz] = ipiv
|
136 | lvalues[lnz++] = 1
|
137 | // L(k+1:n,k) = x / pivot
|
138 | for (p = top; p < n; p++) {
|
139 | // row
|
140 | i = xi[p]
|
141 | // check x(i) is an entry in L(:,k)
|
142 | if (pinv[i] < 0) {
|
143 | // save unpermuted row in L
|
144 | lindex[lnz] = i
|
145 | // scale pivot column
|
146 | lvalues[lnz++] = divideScalar(x[i], pivot)
|
147 | }
|
148 | // x[0..n-1] = 0 for next k
|
149 | x[i] = 0
|
150 | }
|
151 | }
|
152 | // update ptr
|
153 | lptr[n] = lnz
|
154 | uptr[n] = unz
|
155 | // fix row indices of L for final pinv
|
156 | for (p = 0; p < lnz; p++) { lindex[p] = pinv[lindex[p]] }
|
157 | // trim arrays
|
158 | lvalues.splice(lnz, lvalues.length - lnz)
|
159 | lindex.splice(lnz, lindex.length - lnz)
|
160 | uvalues.splice(unz, uvalues.length - unz)
|
161 | uindex.splice(unz, uindex.length - unz)
|
162 | // return LU factor
|
163 | return {
|
164 | L: L,
|
165 | U: U,
|
166 | pinv: pinv
|
167 | }
|
168 | }
|
169 | })
|