expm.js (4546B)
1 import { isSparseMatrix } from '../../utils/is.js'; 2 import { format } from '../../utils/string.js'; 3 import { factory } from '../../utils/factory.js'; 4 var name = 'expm'; 5 var dependencies = ['typed', 'abs', 'add', 'identity', 'inv', 'multiply']; 6 export var createExpm = /* #__PURE__ */factory(name, dependencies, _ref => { 7 var { 8 typed, 9 abs, 10 add, 11 identity, 12 inv, 13 multiply 14 } = _ref; 15 16 /** 17 * Compute the matrix exponential, expm(A) = e^A. The matrix must be square. 18 * Not to be confused with exp(a), which performs element-wise 19 * exponentiation. 20 * 21 * The exponential is calculated using the Padé approximant with scaling and 22 * squaring; see "Nineteen Dubious Ways to Compute the Exponential of a 23 * Matrix," by Moler and Van Loan. 24 * 25 * Syntax: 26 * 27 * math.expm(x) 28 * 29 * Examples: 30 * 31 * const A = [[0,2],[0,0]] 32 * math.expm(A) // returns [[1,2],[0,1]] 33 * 34 * See also: 35 * 36 * exp 37 * 38 * @param {Matrix} x A square Matrix 39 * @return {Matrix} The exponential of x 40 */ 41 return typed(name, { 42 Matrix: function Matrix(A) { 43 // Check matrix size 44 var size = A.size(); 45 46 if (size.length !== 2 || size[0] !== size[1]) { 47 throw new RangeError('Matrix must be square ' + '(size: ' + format(size) + ')'); 48 } 49 50 var n = size[0]; // Desired accuracy of the approximant (The actual accuracy 51 // will be affected by round-off error) 52 53 var eps = 1e-15; // The Padé approximant is not so accurate when the values of A 54 // are "large", so scale A by powers of two. Then compute the 55 // exponential, and square the result repeatedly according to 56 // the identity e^A = (e^(A/m))^m 57 // Compute infinity-norm of A, ||A||, to see how "big" it is 58 59 var infNorm = infinityNorm(A); // Find the optimal scaling factor and number of terms in the 60 // Padé approximant to reach the desired accuracy 61 62 var params = findParams(infNorm, eps); 63 var q = params.q; 64 var j = params.j; // The Pade approximation to e^A is: 65 // Rqq(A) = Dqq(A) ^ -1 * Nqq(A) 66 // where 67 // Nqq(A) = sum(i=0, q, (2q-i)!p! / [ (2q)!i!(q-i)! ] A^i 68 // Dqq(A) = sum(i=0, q, (2q-i)!q! / [ (2q)!i!(q-i)! ] (-A)^i 69 // Scale A by 1 / 2^j 70 71 var Apos = multiply(A, Math.pow(2, -j)); // The i=0 term is just the identity matrix 72 73 var N = identity(n); 74 var D = identity(n); // Initialization (i=0) 75 76 var factor = 1; // Initialization (i=1) 77 78 var AposToI = Apos; // Cloning not necessary 79 80 var alternate = -1; 81 82 for (var i = 1; i <= q; i++) { 83 if (i > 1) { 84 AposToI = multiply(AposToI, Apos); 85 alternate = -alternate; 86 } 87 88 factor = factor * (q - i + 1) / ((2 * q - i + 1) * i); 89 N = add(N, multiply(factor, AposToI)); 90 D = add(D, multiply(factor * alternate, AposToI)); 91 } 92 93 var R = multiply(inv(D), N); // Square j times 94 95 for (var _i = 0; _i < j; _i++) { 96 R = multiply(R, R); 97 } 98 99 return isSparseMatrix(A) ? A.createSparseMatrix(R) : R; 100 } 101 }); 102 103 function infinityNorm(A) { 104 var n = A.size()[0]; 105 var infNorm = 0; 106 107 for (var i = 0; i < n; i++) { 108 var rowSum = 0; 109 110 for (var j = 0; j < n; j++) { 111 rowSum += abs(A.get([i, j])); 112 } 113 114 infNorm = Math.max(rowSum, infNorm); 115 } 116 117 return infNorm; 118 } 119 /** 120 * Find the best parameters for the Pade approximant given 121 * the matrix norm and desired accuracy. Returns the first acceptable 122 * combination in order of increasing computational load. 123 */ 124 125 126 function findParams(infNorm, eps) { 127 var maxSearchSize = 30; 128 129 for (var k = 0; k < maxSearchSize; k++) { 130 for (var q = 0; q <= k; q++) { 131 var j = k - q; 132 133 if (errorEstimate(infNorm, q, j) < eps) { 134 return { 135 q: q, 136 j: j 137 }; 138 } 139 } 140 } 141 142 throw new Error('Could not find acceptable parameters to compute the matrix exponential (try increasing maxSearchSize in expm.js)'); 143 } 144 /** 145 * Returns the estimated error of the Pade approximant for the given 146 * parameters. 147 */ 148 149 150 function errorEstimate(infNorm, q, j) { 151 var qfac = 1; 152 153 for (var i = 2; i <= q; i++) { 154 qfac *= i; 155 } 156 157 var twoqfac = qfac; 158 159 for (var _i2 = q + 1; _i2 <= 2 * q; _i2++) { 160 twoqfac *= _i2; 161 } 162 163 var twoqp1fac = twoqfac * (2 * q + 1); 164 return 8.0 * Math.pow(infNorm / Math.pow(2, j), 2 * q) * qfac * qfac / (twoqfac * twoqp1fac); 165 } 166 });