UNPKG

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