simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 });