def expm(A, use_exact_onenorm="auto", verbose=False): from scipy.linalg import expm as expm_scipy return expm_scipy(A) # Core of expm, separated to allow testing exact and approximate # algorithms. # Hardcode a matrix order threshold for exact vs. estimated one-norms. use_exact_onenorm = A.shape[0] < 200 h = _ExpmPadeHelper(A, use_exact_onenorm=use_exact_onenorm) # Use Pade order 13. eta_3 = max(h.d6_tight, h.d8_loose) eta_4 = max(h.d8_loose, h.d10_loose) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 # Choose smallest s>=0 such that 2**(-s) eta_5 <= theta_13 if eta_5 == 0: # Nilpotent special case s = 0 else: s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + _ell(2**-s * h.A, 13) U, V = h.pade13_scaled(s) X = _solve_P_Q(U, V) # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) return X
def _expm_SS(A, ssA, order): # , use_exact_onenorm='auto'): # Track functions of A to help compute the matrix exponential. h = _ExpmPadeHelper_SS(A, ssA, order) structure = None # Try Pade order 3. eta_1 = max(h.d4_loose, h.d6_loose) if eta_1 < 1.495585217958292e-002 and mf._ell(h.A, 3) == 0: U, V = h.pade3() return mf._solve_P_Q(U, V, structure=structure) # Try Pade order 5. eta_2 = max(h.d4_tight, h.d6_loose) if eta_2 < 2.539398330063230e-001 and mf._ell(h.A, 5) == 0: U, V = h.pade5() return mf._solve_P_Q(U, V, structure=structure) # Try Pade orders 7 and 9. eta_3 = max(h.d6_tight, h.d8_loose) if eta_3 < 9.504178996162932e-001 and mf._ell(h.A, 7) == 0: U, V = h.pade7() return mf._solve_P_Q(U, V, structure=structure) if eta_3 < 2.097847961257068e000 and mf._ell(h.A, 9) == 0: U, V = h.pade9() return mf._solve_P_Q(U, V, structure=structure) # Use Pade order 13. eta_4 = max(h.d8_loose, h.d10_loose) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + mf._ell(2 ** -s * h.A, 13) U, V = h.pade13_scaled(s) X = mf._solve_P_Q(U, V, structure=structure) # X = r_13(A)^(2^s) by repeated squaring. for _ in range(s): X = X.dot(X) return X
def Return(U, V, P, Q, geti2, pade): E = mf._solve_P_Q(U, V, structure=structure) I = _solve_P_Q_2(P, Q, structure=structure) if geti2: return E, I, _geti2(H, E, I, h, pade) return E, I
def expmint(A, h, geti2=False): """ Compute the matrix exponential and its integral(s) using Pade approximation. Parameters ---------- A : (M, M) array_like or sparse matrix 2D Array or Matrix (sparse or dense) to be exponentiated h : scalar Time step geti2 : bool If True, also return the `I2` integral below. Useful for first order holds. Returns ------- E : (M, M) ndarray Matrix exponential of `A*h`: exp(A*h) I : (M, M) ndarray Integral of exp(A*t) dt from 0 to h I2 : (M, M) ndarray; optional Integral of exp(A*t)*t dt from 0 to h. Only returned if `geti2` is True. Notes ----- This routine is modeled after and augments :func:`scipy.linalg.expm`. The power series expansions for these matrices are (I = identity): .. code-block:: none E = I + A*h + (A*h)**2/2! + (A*h)**3/3! + ... I1 = h*(I + A*h/2 + (A*h)**2/3! + (A*h)**3/4! + ...) I2 = h*h*(I/2 + A*h/3 + (A*h)**2/(4*2!) + (A*h)**3/(5*3!) + ...) If `A` is non-singular, the exact solutions for `I1` and `I2` are:: E = exp(A*h) I1 = inv(A)*(E-I) I2 = inv(A)*(E*h-I1) The Pade approximants for those power series are used for `E` and `I1`. If necessary, the 'squaring and scaling' method will be used such that a 13th order Pade approximation will be accurate. See references [#exp1]_, [#exp2]_, and [#exp3]_ for more information. For `I2`, a Pade approximation is used if a 3rd, 5th, 7th or 9th order is accurate. If not, `A` is checked for singularity. If non-singular, the exact solution show above is used. If A is singular, a the power series is used directly until it converges (and a warning message is printed about using a finer time step.) References ---------- .. [#exp1] Awad H. Al-Mohy and Nicholas J. Higham (2009) "A New Scaling and Squaring Algorithm for the Matrix Exponential." SIAM Journal on Matrix Analysis and Applications. 31 (3). pp. 970-989. ISSN 1095-7162 .. [#exp2] Nicholas J. Higham (2005) "The Scaling and Squaring Method for the Matrix Exponential Revisited." SIAM Journal on Matrix Analysis and Applications. Vol 26, No. 4, pp 1179-1193. .. [#exp3] David Westreich (1990) "A Practical Method for Computing the Exponential of a Matrix and its Integral." Communications in Applied Numerical Methods, Vol 6, 375-380. Examples -------- >>> from pyyeti import expmint >>> import numpy as np >>> import scipy.linalg as la >>> np.set_printoptions(4) >>> a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) >>> e, i, i2 = expmint.expmint(a, .05, True) >>> e array([[ 1.0996, 0.1599, 0.2202], [ 0.3099, 1.3849, 0.46 ], [ 0.5202, 0.61 , 1.6998]]) >>> i array([[ 0.052 , 0.0034, 0.0048], [ 0.0067, 0.0583, 0.01 ], [ 0.0114, 0.0133, 0.0651]]) >>> i2 array([[ 0.0013, 0.0001, 0.0002], [ 0.0002, 0.0015, 0.0003], [ 0.0004, 0.0005, 0.0018]]) """ # Avoid indiscriminate asarray() to allow sparse or other strange # arrays. if isinstance(A, (list, tuple)): A = np.asarray(A) if len(A.shape) != 2 or A.shape[0] != A.shape[1]: raise ValueError("expected a square matrix") # Detect upper triangularity. if mf._is_upper_triangular(A): structure = mf.UPPER_TRIANGULAR else: structure = None # Hardcode a matrix order threshold for exact vs. estimated # one-norms. use_exact_onenorm = A.shape[0] < 200 # Track functions of A to help compute the matrix exponential. H = _ExpmIntPadeHelper( A * h, structure=structure, use_exact_onenorm=use_exact_onenorm ) def Return(U, V, P, Q, geti2, pade): E = mf._solve_P_Q(U, V, structure=structure) I = _solve_P_Q_2(P, Q, structure=structure) if geti2: return E, I, _geti2(H, E, I, h, pade) return E, I # Try Pade order 3. eta_1 = max(H.d4_loose, H.d6_loose) if eta_1 < 1.495585217958292e-002 and mf._ell(H.A, 3) == 0: U, V, P, Q = H.pade3_i(h) return Return(U, V, P, Q, geti2, 3) # Try Pade order 5. eta_2 = max(H.d4_tight, H.d6_loose) if eta_2 < 2.539398330063230e-001 and mf._ell(H.A, 5) == 0: U, V, P, Q = H.pade5_i(h) return Return(U, V, P, Q, geti2, 5) # Try Pade orders 7 and 9. eta_3 = max(H.d6_tight, H.d8_loose) if eta_3 < 9.504178996162932e-001 and mf._ell(H.A, 7) == 0: U, V, P, Q = H.pade7_i(h) return Return(U, V, P, Q, geti2, 7) if eta_3 < 2.097847961257068e000 and mf._ell(H.A, 9) == 0: U, V, P, Q = H.pade9_i(h) return Return(U, V, P, Q, geti2, 9) # Use Pade order 13. eta_4 = max(H.d8_loose, H.d10_loose) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + mf._ell(2 ** -s * H.A, 13) U, V, P, Q = H.pade13_scaled_i(s, h) E = mf._solve_P_Q(U, V, structure=structure) I = _solve_P_Q_2(P, Q, structure=structure) # E = r_13(A)^(2^s) by repeated squaring. for _ in range(s): I += I.dot(E) E = E.dot(E) if geti2: return E, I, _geti2(H, E, I, h, 13) return E, I
def expm(self, A, max_mat_mult=100, balance=True): ''' max_mat_mult can be used to reduce the computational complexity of the exponential 6: at most a Pade order 13 can be used but with no scaling 5: at most a Pade order 9 is used 4: at most a Pade order 7 is used 3: at most a Pade order 5 is used 2: at most a Pade order 3 is used ''' if (np.any(np.isnan(A))): print("Matrix A contains nan") # Compute expm(A)*v if balance: A_bal, D = matrix_balance(A, permute=False) Dinv = np.copy(D) for i in range(D.shape[0]): Dinv[i, i] = 1.0 / D[i, i] # assert(np.max(np.abs(A_bal-(Dinv@A@D)))==0.0) else: A_bal = A # Hardcode a matrix order threshold for exact vs. estimated one-norms. use_exact_onenorm = A.shape[0] < 200 h = _ExpmPadeHelper(A_bal, use_exact_onenorm=use_exact_onenorm) structure = None # Compute the number of mat-mat multiplications needed in theory self.mat_mult_in_theory = self.compute_mat_mult(A_bal) self.mat_mult = min(self.mat_mult_in_theory, max_mat_mult) self.mat_norm = np.linalg.norm(A_bal, 1) if self.mat_mult <= 0: U, V = self.pade1(A_bal) X = _solve_P_Q(U, V, structure=structure) if self.mat_mult == 1: U, V = self.pade2(A_bal) X = _solve_P_Q(U, V, structure=structure) if self.mat_mult == 2: U, V = h.pade3() # U_, V_ = self.pade3(A_bal) # assert(np.max(np.abs(U-U_))==0.0) # assert(np.max(np.abs(V-V_))==0.0) X = _solve_P_Q(U, V, structure=structure) # Try Pade order 5. if self.mat_mult == 3: U, V = h.pade5() X = _solve_P_Q(U, V, structure=structure) # Try Pade orders 7 and 9. if self.mat_mult == 4: U, V = h.pade7() X = _solve_P_Q(U, V, structure=structure) if self.mat_mult == 5: U, V = h.pade9() X = _solve_P_Q(U, V, structure=structure) if self.mat_mult > 5: s = self.mat_mult - 6 U, V = h.pade13_scaled(s) X = _solve_P_Q(U, V) # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) if (balance): X = D @ X @ Dinv # assert(np.max(np.abs(expm(A) - X)) == 0.0) return X
def expm(A, A2=A2): """ Compute the matrix exponential using Pade approximation. .. versionadded:: 0.12.0 Parameters ---------- A : (M,M) array or sparse matrix 2D Array or Matrix (sparse or dense) to be exponentiated Returns ------- expA : (M,M) ndarray Matrix exponential of `A` Notes ----- This is algorithm (6.1) which is a simplification of algorithm (5.1). References ---------- .. [1] Awad H. Al-Mohy and Nicholas J. Higham (2009) "A New Scaling and Squaring Algorithm for the Matrix Exponential." SIAM Journal on Matrix Analysis and Applications. 31 (3). pp. 970-989. ISSN 1095-7162 """ # Use Pade order 3, no matter what. multiply(A, A, out=A2) U, V = _pade3(A, ident, A2) return _solve_P_Q(U, V, structure=None) # Detect upper triangularity. # structure = UPPER_TRIANGULAR if _is_upper_triangular(A) else None structure = None # Define the identity matrix depending on sparsity. # ident = _ident_like(A) # Try Pade order 3. A2 = A.dot(A) d6 = _onenormest_matrix_power(A2, 3)**(1 / 6.) eta_1 = max(_onenormest_matrix_power(A2, 2)**(1 / 4.), d6) if eta_1 < 1.495585217958292e-002 and _ell(A, 3) == 0: U, V = _pade3(A, ident, A2) return _solve_P_Q(U, V, structure=structure) # Try Pade order 5. A4 = A2.dot(A2) d4 = _exact_1_norm(A4)**(1 / 4.) eta_2 = max(d4, d6) if eta_2 < 2.539398330063230e-001 and _ell(A, 5) == 0: U, V = _pade5(A, ident, A2, A4) return _solve_P_Q(U, V, structure=structure) # Try Pade orders 7 and 9. A6 = A2.dot(A4) d6 = _exact_1_norm(A6)**(1 / 6.) d8 = _onenormest_matrix_power(A4, 2)**(1 / 8.) eta_3 = max(d6, d8) if eta_3 < 9.504178996162932e-001 and _ell(A, 7) == 0: U, V = _pade7(A, ident, A2, A4, A6) return _solve_P_Q(U, V, structure=structure) if eta_3 < 2.097847961257068e+000 and _ell(A, 9) == 0: U, V = _pade9(A, ident, A2, A4, A6) return _solve_P_Q(U, V, structure=structure) # Use Pade order 13. d10 = _onenormest_product((A4, A6))**(1 / 10.) eta_4 = max(d8, d10) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + _ell(2**-s * A, 13) B = A * 2**-s B2 = A2 * 2**(-2 * s) B4 = A4 * 2**(-4 * s) B6 = A6 * 2**(-6 * s) U, V = _pade13(B, ident, B2, B4, B6) X = _solve_P_Q(U, V, structure=structure) if structure == UPPER_TRIANGULAR: # Invoke Code Fragment 2.1. X = _fragment_2_1(X, A, s) else: # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) return X
def expm(A,A2 = A2): """ Compute the matrix exponential using Pade approximation. .. versionadded:: 0.12.0 Parameters ---------- A : (M,M) array or sparse matrix 2D Array or Matrix (sparse or dense) to be exponentiated Returns ------- expA : (M,M) ndarray Matrix exponential of `A` Notes ----- This is algorithm (6.1) which is a simplification of algorithm (5.1). References ---------- .. [1] Awad H. Al-Mohy and Nicholas J. Higham (2009) "A New Scaling and Squaring Algorithm for the Matrix Exponential." SIAM Journal on Matrix Analysis and Applications. 31 (3). pp. 970-989. ISSN 1095-7162 """ # Use Pade order 3, no matter what. multiply(A,A,out=A2) U, V = _pade3(A, ident, A2) return _solve_P_Q(U, V, structure=None) # Detect upper triangularity. # structure = UPPER_TRIANGULAR if _is_upper_triangular(A) else None structure = None # Define the identity matrix depending on sparsity. # ident = _ident_like(A) # Try Pade order 3. A2 = A.dot(A) d6 = _onenormest_matrix_power(A2, 3)**(1/6.) eta_1 = max(_onenormest_matrix_power(A2, 2)**(1/4.), d6) if eta_1 < 1.495585217958292e-002 and _ell(A, 3) == 0: U, V = _pade3(A, ident, A2) return _solve_P_Q(U, V, structure=structure) # Try Pade order 5. A4 = A2.dot(A2) d4 = _exact_1_norm(A4)**(1/4.) eta_2 = max(d4, d6) if eta_2 < 2.539398330063230e-001 and _ell(A, 5) == 0: U, V = _pade5(A, ident, A2, A4) return _solve_P_Q(U, V, structure=structure) # Try Pade orders 7 and 9. A6 = A2.dot(A4) d6 = _exact_1_norm(A6)**(1/6.) d8 = _onenormest_matrix_power(A4, 2)**(1/8.) eta_3 = max(d6, d8) if eta_3 < 9.504178996162932e-001 and _ell(A, 7) == 0: U, V = _pade7(A, ident, A2, A4, A6) return _solve_P_Q(U, V, structure=structure) if eta_3 < 2.097847961257068e+000 and _ell(A, 9) == 0: U, V = _pade9(A, ident, A2, A4, A6) return _solve_P_Q(U, V, structure=structure) # Use Pade order 13. d10 = _onenormest_product((A4, A6))**(1/10.) eta_4 = max(d8, d10) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + _ell(2**-s * A, 13) B = A * 2**-s B2 = A2 * 2**(-2*s) B4 = A4 * 2**(-4*s) B6 = A6 * 2**(-6*s) U, V = _pade13(B, ident, B2, B4, B6) X = _solve_P_Q(U, V, structure=structure) if structure == UPPER_TRIANGULAR: # Invoke Code Fragment 2.1. X = _fragment_2_1(X, A, s) else: # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) return X
def expm_times_v(A, v, scaling_reduction=0, verbose=False): ''' scaling_reduction can be used to reduce the computational complexity of the exponential 1: no scaling can be performed 2: at most a Pade order 9 is used 3: at most a Pade order 7 is used 4: at most a Pade order 5 is used 5: at most a Pade order 3 is used ''' # Compute expm(A)*v # Hardcode a matrix order threshold for exact vs. estimated one-norms. use_exact_onenorm = A.shape[0] < 200 h = _ExpmPadeHelper(A, use_exact_onenorm=use_exact_onenorm) structure = None # Try Pade order 3. eta_1 = max(h.d4_loose, h.d6_loose) if scaling_reduction > 4 or (eta_1 < 1.495585217958292e-002 and _ell(h.A, 3) == 0): U, V = h.pade3() X = _solve_P_Q(U, V, structure=structure) return X.dot(v) # Try Pade order 5. eta_2 = max(h.d4_tight, h.d6_loose) if scaling_reduction > 3 or (eta_2 < 2.539398330063230e-001 and _ell(h.A, 5) == 0): U, V = h.pade5() X = _solve_P_Q(U, V, structure=structure) return X.dot(v) # Try Pade orders 7 and 9. eta_3 = max(h.d6_tight, h.d8_loose) if scaling_reduction > 2 or (eta_3 < 9.504178996162932e-001 and _ell(h.A, 7) == 0): U, V = h.pade7() X = _solve_P_Q(U, V, structure=structure) return X.dot(v) if scaling_reduction > 1 or (eta_3 < 2.097847961257068e+000 and _ell(h.A, 9) == 0): U, V = h.pade9() X = _solve_P_Q(U, V, structure=structure) return X.dot(v) # Use Pade order 13. eta_3 = max(h.d6_tight, h.d8_loose) eta_4 = max(h.d8_loose, h.d10_loose) eta_5 = min(eta_3, eta_4) theta_13 = 4.25 # Choose smallest s>=0 such that 2**(-s) eta_5 <= theta_13 if eta_5 == 0: # Nilpotent special case s = 0 else: s = max(int(np.ceil(np.log2(eta_5 / theta_13))), 0) s = s + _ell(2**-s * h.A, 13) if (scaling_reduction > 0): s = 0 U, V = h.pade13_scaled(s) X = _solve_P_Q(U, V) # X = r_13(A)^(2^s) by repeated squaring. for i in range(s): X = X.dot(X) res = X.dot(v) return res