simple-squiggle

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

expm.js (4788B)


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