def qz_solver(B, A): # A - \lambda B = Q(S - \lambda T)Z* S, T, Q, Z = la.qz(A=A, B=B) eigenvalues = np.array(S).diagonal() / np.array(T).diagonal() # TODO: Calculate eigenvectors return eigenvalues, Q
def qzordered(A,B,m_states): TOL = 1e-10 from numpy import real_if_close,where from scipy.linalg import qz # [S,T,Q,Z,nev] = qz_scipy(A,B,sort='ouc') # wait until sorted output is activated in scipy [S,T,Q,Z] = qz(A,B) S,T,Q,Z = [real_if_close(mm) for mm in (S,T,Q,Z)] u = np.diag(S) v = np.diag(T) eigval = v / where( u >TOL, u, TOL) sortindex = abs(eigval).argsort() # (Xi_sortabs doesn't really seem to be needed) sortval = eigval[sortindex] select = slice(0, m_states) stake = (abs(sortval[select])).max() + TOL S,T,Q,Z = qzdiv(stake,S,T,Q,Z) return [S,T,Q,Z,eigval]
def qz_BB2roots(bb): n = len(bb)-1 bbt = [bb[k].T for k in range(n+1)] # transposition ! dim x DIM dim, DIM = bbt[0].shape q, r, p = la.qr(bbt[0], pivoting=True) qr_BB = [] for k in range(n+1): qb = q.T.dot(bbt[k]) qbp = qb[:, p] qr_BB.append(qbp[:, 0:dim]) chow_mat = np.zeros((dim, dim)) for k in range(n): chow_mat += np.random.randn()*qr_BB[k+1] b0 = qr_BB[0] chowchow, b0b0, Q, Z = la.qz(chow_mat, b0, output='complex') qzBB = [] for k in range(n+1): qzBB.append( Q.T.conjugate().dot(qr_BB[k]).dot(Z) ) roots = np.zeros((dim, n), dtype=complex) for j in range(n): roots[:, j] = np.diag(qzBB[j+1])/np.diag(qzBB[0]) return roots
def qzordered(A, B): """ QZORDERED QZ decomposition ordered by the absolute value of the generalized eigenvalues See QZ Based on code by Pedro Oviedo & Chris Sims """ n = A.shape[0] S, T, Q, Z = qz(A, B) Q = Q.T i = 0 while i < n - 1: if abs(T[i, i] * S[i+1, i+1]) > abs(S[i, i] * T[i+1, i+1]): qzswitch(i,S, T, Q, Z) if i > 0: i -=1 else: i += 1 else: i += 1 return S, T, Q, Z
# DNT ==== First-order derivatives of the functions g and h =================== stake=1.0 #Create system matrices A,B AA = c_[-nfxp,-nfyp] BB = c_[nfx,nfy] NK = nfx.shape[1] #Complex Schur Decomposition ss,tt,qq,zz = linalg.qz(AA,BB); #Pick non-explosive (stable) eigenvalues slt = (abs(diag(tt))<stake*abs(diag(ss))); noslt=logical_not(slt) nk=sum(slt); # Prep for QZ decomposition- qzswitch() def qzswitch(i,ss,tt,qq,zz): ssout = ss.copy(); ttout = tt.copy(); qqout = qq.copy(); zzout = zz.copy() ix = i-1 # from 1-based to 0-based indexing... # use all 1x1-matrices for convenient conjugate-transpose even if real:
def _solve_discrete_generalized_lyapunov(A, E, Y, tol=1e-12): ''' Solves A.T X A - E.T X E + Y = 0 for symmetric Y ''' mat33 = np.zeros((3, 3), dtype=float) mat44 = np.zeros((4, 4), dtype=float) def mini_sylvester(S, V, Yt, R=None, U=None): ''' A helper function to solve the 1x1 or 2x2 Sylvester equations arising in the solution of the generalized continuous-time Lyapunov equations Note that, this doesn't have any protection against LinAlgError hence the caller needs to `try` to see whether it is properly executed. ''' if R is None: if S.size == 1: return -Yt / (S ** 2 - V ** 2) else: a, b, c, d = S.ravel().tolist() e, f, g, h = V.ravel().tolist() mat33[0, :] = [a*a - e*e, 2 * (a*c - e*g), c*c - g*g] mat33[1, :] = [a*b - e*f, a*d - e*h + c*b - g*f, c*d - g*h] mat33[2, :] = [b*b - f*f, 2 * (b*d - f*h), d*d - h*h] a, b, c = solve(mat33, -Yt.reshape(-1, 1)[[0, 1, 3], :] ).ravel().tolist() return np.array([[a, b], [b, c]], dtype=float) elif S.size == 4: if R.size == 4: a, b, c, d = R.ravel().tolist() e, f, g, h = S.ravel().tolist() k, l, m, n = U.ravel().tolist() p, q, r, s = V.ravel().tolist() mat44[0, :] = [a*e - k*p, a*g - k*r, c*e - m*p, c*g - m*r] mat44[1, :] = [a*f - k*q, a*h - k*s, c*f - m*q, c*h - m*s] mat44[2, :] = [b*e - l*p, b*g - l*r, d*e - n*p, d*g - n*r] mat44[3, :] = [b*f - l*q, b*h - l*s, d*f - n*q, d*h - n*s] return solve(mat44, -Yt.reshape(-1, 1)).reshape(2, 2) else: return solve(R[0, 0]*S.T - U[0, 0]*V.T, -Yt.T).T elif R.size == 4: return solve(S[0, 0]*R.T - V[0, 0]*U.T, -Yt) else: return -Yt / (R * S - U * V) # ============================= # Prepare the data # ============================= # if the problem is small then solve directly if A.shape[0] < 3: return mini_sylvester(A, E, Y) As, Es, Q, Z = qz(A, E, overwrite_a=True, overwrite_b=True) Ys = Z.T @ Y @ Z n = As.shape[0] # If there are nontrivial entries on the subdiagonal, we have a 2x2 block. # Based on that we have the block sizes `bz` and starting positions `bs`. subdiag_entries = np.abs(As[range(1, n), range(0, n-1)]) > tol subdiag_indices = [ind for ind, x in enumerate(subdiag_entries) if x] bz = np.ones(n) for x in subdiag_indices: bz[x] = 2 bz[x+1] = np.nan bz = bz[~np.isnan(bz)].astype(int) bs = [0] + np.cumsum(bz[:-1]).tolist() + [None] total_blk = bz.size Xs = np.empty_like(Y) # ============================= # Main Loop # ============================= # Now we know how the matrices should be partitioned. We then start # from the uppper left corner and alternate between updating the # Y term and solving the next entry of X. We walk over X row-wise for row in range(total_blk): thisr = bs[row] nextr = bs[row+1] # This block is executed at the second and further spins of the # for loop. Humans should start reading from (**) if row is not 0: Ys[thisr:nextr, thisr:nextr] += \ As[thisr:nextr, thisr:nextr].T @ \ Xs[thisr:nextr, :thisr] @ \ As[:thisr, thisr:nextr] - \ Es[thisr:nextr, thisr:nextr].T @ \ Xs[thisr:nextr, :thisr] @ \ Es[:thisr, thisr:nextr] # (**) Solve for the diagonal via Akk , Ekk , Ykk and place it in Xkk tempx = mini_sylvester(As[thisr:nextr, thisr:nextr], Es[thisr:nextr, thisr:nextr], Ys[thisr:nextr, thisr:nextr]) # Place it in the data Xs[thisr:nextr, thisr:nextr] = tempx # Form the common products of X * E and X * A tempx = Xs[thisr:nextr, :nextr] XE_of_row = tempx @ Es[:nextr, thisr:] XA_of_row = tempx @ As[:nextr, thisr:] # Update Y terms right of the diagonal Ys[thisr:nextr, thisr:] += \ As[thisr:nextr, thisr:nextr].T @ XA_of_row - \ Es[thisr:nextr, thisr:nextr].T @ XE_of_row # Walk over upper triangular terms for col in range(row + 1, total_blk): thisc = bs[col] nextc = bs[col+1] # The corresponding Y term has already been updated, solve for X tempx = mini_sylvester(As[thisc:nextc, thisc:nextc], Es[thisc:nextc, thisc:nextc], Ys[thisr:nextr, thisc:nextc], As[thisr:nextr, thisr:nextr], Es[thisr:nextr, thisr:nextr]) # Place it in the data Xs[thisr:nextr, thisc:nextc] = tempx Xs[thisc:nextc, thisr:nextr] = tempx.T # Post column solution Y update # XA and XE terms tempe = tempx @ Es[thisc:nextc, thisc:] tempa = tempx @ As[thisc:nextc, thisc:] # Update Y towards left Ys[thisr:nextr, thisc:] += \ As[thisr:nextr, thisr:nextr].T @ tempa - \ Es[thisr:nextr, thisr:nextr].T @ tempe # Update Y downwards XE_of_row[:, (thisc - thisr):] += tempe XA_of_row[:, (thisc - thisr):] += tempa ugly_sl = slice(thisc - thisr, nextc - thisr if nextc is not None else None) Ys[nextr:nextc, thisc:nextc] += \ As[thisr:nextr, nextr:nextc].T @ XA_of_row[:, ugly_sl] - \ Es[thisr:nextr, nextr:nextc].T @ XE_of_row[:, ugly_sl] return Q @ Xs @ Q.T
for j in range(len(V)): First_Derivatives.append([nFx[i][j] for i in range(len(F))]) nfxp = First_Derivatives[2] nfyp = First_Derivatives[3] nfx = First_Derivatives[0] nfy = First_Derivatives[1] nfxp = np.asarray(nfxp) A = np.concatenate((nfxp,nfyp),axis=1) A = (-1)*A B = np.concatenate((nfx,nfy),axis=1) NK = np.shape(nfx)[1] s, t, q, z = linalg.qz(-A, B) stake = 1 slt = abs(np.diag(t)) < stake*abs(np.diag(s)) nk = sum(slt) def qzdiv(stake, A, B, Q, Z): # translation of qzdiv by Chris Sims # Takes U.T. matrices A, B, orthonormal matrices Q,Z, rearranges them # so that all cases of abs(B(i,i)/A(i,i))>stake are in lower right # corner, while preserving U.T. and orthonormal properties and Q'AZ' and # Q'BZ'. [n, jnk] = np.shape(A) root = np.asarray([abs(np.diag(A)), abs(np.diag(B))]) root[0] = root[0] - 1 * (root[0] < 1.e-13) * (root[0] + root[1])
def solve_sylv_schur(A, Ar, E=None, Er=None, B=None, Br=None, C=None, Cr=None): r"""Solve Sylvester equation by Schur decomposition. Solves Sylvester equation .. math:: A V E_r^T + E V A_r^T + B B_r^T = 0 or .. math:: A^T W E_r + E^T W A_r + C^T C_r = 0 or both using (generalized) Schur decomposition (Algorithms 3 and 4 in [BKS11]_), if the necessary parameters are given. Parameters ---------- A Real |Operator|. Ar Real |Operator|. It is converted into a |NumPy array| using :func:`~pymor.algorithms.to_matrix.to_matrix`. E Real |Operator| or `None` (then assumed to be the identity). Er Real |Operator| or `None` (then assumed to be the identity). It is converted into a |NumPy array| using :func:`~pymor.algorithms.to_matrix.to_matrix`. B Real |Operator| or `None`. Br Real |Operator| or `None`. It is assumed that `Br.range.from_numpy` is implemented. C Real |Operator| or `None`. Cr Real |Operator| or `None`. It is assumed that `Cr.source.from_numpy` is implemented. Returns ------- V Returned if `B` and `Br` are given, |VectorArray| from `A.source`. W Returned if `C` and `Cr` are given, |VectorArray| from `A.source`. Raises ------ ValueError If `V` and `W` cannot be returned. """ # check types assert isinstance(A, OperatorInterface) and A.linear and A.source == A.range assert isinstance(Ar, OperatorInterface) and Ar.linear and Ar.source == Ar.range assert E is None or isinstance(E, OperatorInterface) and E.linear and E.source == E.range == A.source if E is None: E = IdentityOperator(A.source) assert Er is None or isinstance(Er, OperatorInterface) and Er.linear and Er.source == Er.range == Ar.source compute_V = B is not None and Br is not None compute_W = C is not None and Cr is not None if not compute_V and not compute_W: raise ValueError('Not enough parameters are given to solve a Sylvester equation.') if compute_V: assert isinstance(B, OperatorInterface) and B.linear and B.range == A.source assert isinstance(Br, OperatorInterface) and Br.linear and Br.range == Ar.source assert B.source == Br.source if compute_W: assert isinstance(C, OperatorInterface) and C.linear and C.source == A.source assert isinstance(Cr, OperatorInterface) and Cr.linear and Cr.source == Ar.source assert C.range == Cr.range # convert reduced operators Ar = to_matrix(Ar, format='dense') r = Ar.shape[0] if Er is not None: Er = to_matrix(Er, format='dense') # (Generalized) Schur decomposition if Er is None: TAr, Z = spla.schur(Ar, output='complex') Q = Z else: TAr, TEr, Q, Z = spla.qz(Ar, Er, output='complex') # solve for V, from the last column to the first if compute_V: V = A.source.empty(reserve=r) BrTQ = Br.apply_adjoint(Br.range.from_numpy(Q.T)) BBrTQ = B.apply(BrTQ) for i in range(-1, -r - 1, -1): rhs = -BBrTQ[i].copy() if i < -1: if Er is not None: rhs -= A.apply(V.lincomb(TEr[i, :i:-1].conjugate())) rhs -= E.apply(V.lincomb(TAr[i, :i:-1].conjugate())) TErii = 1 if Er is None else TEr[i, i] eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E V.append(eAaE.apply_inverse(rhs)) V = V.lincomb(Z.conjugate()[:, ::-1]) V = V.real # solve for W, from the first column to the last if compute_W: W = A.source.empty(reserve=r) CrZ = Cr.apply(Cr.source.from_numpy(Z.T)) CTCrZ = C.apply_adjoint(CrZ) for i in range(r): rhs = -CTCrZ[i].copy() if i > 0: if Er is not None: rhs -= A.apply_adjoint(W.lincomb(TEr[:i, i])) rhs -= E.apply_adjoint(W.lincomb(TAr[:i, i])) TErii = 1 if Er is None else TEr[i, i] eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E W.append(eAaE.apply_inverse_adjoint(rhs)) W = W.lincomb(Q.conjugate()) W = W.real if compute_V and compute_W: return V, W elif compute_V: return V else: return W
def second_order_solver(FF,GG,HH): from scipy.linalg import qz from dolo.numeric.extern.qz import qzdiv from numpy import array,mat,c_,r_,eye,zeros,real_if_close,diag,allclose,where,diagflat from numpy.linalg import solve Psi_mat = array(FF) Gamma_mat = array(-GG) Theta_mat = array(-HH) m_states = FF.shape[0] Xi_mat = r_[c_[Gamma_mat, Theta_mat], c_[eye(m_states), zeros((m_states, m_states))]] Delta_mat = r_[c_[Psi_mat, zeros((m_states, m_states))], c_[zeros((m_states, m_states)), eye(m_states)]] AAA,BBB,Q,Z = qz(Delta_mat, Xi_mat) Delta_up,Xi_up,UUU,VVV = [real_if_close(mm) for mm in (AAA,BBB,Q,Z)] Xi_eigval = diag(Xi_up)/where(diag(Delta_up)>TOL, diag(Delta_up), TOL) Xi_sortindex = abs(Xi_eigval).argsort() # (Xi_sortabs doesn't really seem to be needed) Xi_sortval = Xi_eigval[Xi_sortindex] Xi_select = slice(0, m_states) stake = (abs(Xi_sortval[Xi_select])).max() + TOL Delta_up,Xi_up,UUU,VVV = qzdiv(stake,Delta_up,Xi_up,UUU,VVV) try: # check that all unused roots are unstable assert abs(Xi_sortval[m_states]) > (1-TOL) # check that all used roots are stable assert abs(Xi_sortval[Xi_select]).max() < 1+TOL except: raise BKError('generic') # check for unit roots anywhere # assert (abs((abs(Xi_sortval) - 1)) > TOL).all() Lambda_mat = diagflat(Xi_sortval[Xi_select]) VVVH = VVV.T VVV_2_1 = VVVH[m_states:2*m_states, :m_states] VVV_2_2 = VVVH[m_states:2*m_states, m_states:2*m_states] UUU_2_1 = UUU[m_states:2*m_states, :m_states] PP = - solve(VVV_2_1, VVV_2_2) # slightly different check than in the original toolkit: assert allclose(real_if_close(PP), PP.real) PP = PP.real ## end of solve_qz! return [Xi_sortval[Xi_select],PP]
def LinApp_Solve(AA, BB, CC, DD, FF, GG, HH, JJ, KK, LL, MM, WWW, TT, NN, Z0, Sylv): """ This code takes Uhlig's original code and puts it in the form of a function. This version outputs the policy function coefficients: PP, QQ and UU for X, and RR, SS and VV for Y. Inputs overview: The matrices of derivatives: AA - TT. The autoregression coefficient matrix NN from the law of motion for Z. Z0 is the Z-point about which the linearization is taken. For linearizing about the steady state this is Zbar and normally Zbar = 0. Sylv is an indicator variable telling the program to use the built-in function sylvester() to solve for QQ and SS, if possible. Default is to use Sylv=1. Parameters ---------- AA : array_like, dtype=float, shape=(ny, nx) The matrix represented above by :math:`A`. It is the matrix of derivatives of the Y equations with repsect to :math:`X_t` BB : array_like, dtype=float, shape=(ny, nx) The matrix represented above by :math:`B`. It is the matrix of derivatives of the Y equations with repsect to :math:`X_{t-1}`. CC : array_like, dtype=float, shape=(ny, ny) The matrix represented above by :math:`C`. It is the matrix of derivatives of the Y equations with repsect to :math:`Y_t` DD : array_like, dtype=float, shape=(ny, nz) The matrix represented above by :math:`C`. It is the matrix of derivatives of the Y equations with repsect to :math:`Z_t` FF : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`F`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_{t+1}` GG : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`G`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_t` HH : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`H`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_{t-1}` JJ : array_like, dtype=float, shape=(nx, ny) The matrix represetned above by :math:`J`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Y_{t+1}` KK : array_like, dtype=float, shape=(nx, ny) The matrix represetned above by :math:`K`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Y_t` LL : array_like, dtype=float, shape=(nx, nz) The matrix represetned above by :math:`L`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Z_{t+1}` MM : array_like, dtype=float, shape=(nx, nz) The matrix represetned above by :math:`M`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Z_t` WWW : array, dtype=float, shape=(ny,) The vector of the numberial errors of first ny characterizing equations TT : array, dtype=float, shape=(nx,) The vector of the numberial errors of the next nx characterizing equations following the first ny equations NN : array_like, dtype=float, shape=(nz, nz) The autocorrelation matrix for the exogenous state vector z. Z0 : array, dtype=float, shape=(nz,) the Z-point about which the linearization is taken. For linearizing about the steady state this is Zbar and normally Zbar = 0. QQ if true. Sylv: binary, dtype=int an indicator variable telling the program to use the built-in function sylvester() to solve for QQ and SS, if possible. Default is to use Sylv=1. Returns ------- P : 2D-array, dtype=float, shape=(nx, nx) The matrix :math:`P` in the law of motion for endogenous state variables described above. Q : 2D-array, dtype=float, shape=(nx, nz) The matrix :math:`Q` in the law of motion for exogenous state variables described above. U : array, dtype=float, shape=(nx,) ?????????? R : 2D-array, dtype=float, shape=(ny, nx) The matrix :math:`R` in the law of motion for endogenous state variables described above. S : 2D-array, dtype=float, shape=(ny, nz) The matrix :math:`S` in the law of motion for exogenous state variables described above. V : array, dtype=float, shape=(ny,) ??????????? References ---------- .. [1] Uhlig, H. (1999): "A toolkit for analyzing nonlinear dynamic stochastic models easily," in Computational Methods for the Study of Dynamic Economies, ed. by R. Marimon, pp. 30-61. Oxford University Press. """ #The original coding we did used the np.matrix form for our matrices so we #make sure to set our inputs to numpy matrices. AA = np.matrix(AA) BB = np.matrix(BB) CC = np.matrix(CC) DD = np.matrix(DD) FF = np.matrix(FF) GG = np.matrix(GG) HH = np.matrix(HH) JJ = np.matrix(JJ) KK = np.matrix(KK) LL = np.matrix(LL) MM = np.matrix(MM) NN = np.matrix(NN) WWW = np.array(WWW) TT = np.array(TT) Z0 = np.array(Z0) #Tolerance level to use TOL = .000001 # Here we use matrices to get pertinent dimensions. nx = FF.shape[1] l_equ = CC.shape[0] ny = CC.shape[1] nz = min(NN.shape) # The following if and else blocks form the # Psi, Gamma, Theta Xi, Delta mats if l_equ == 0: if CC.any(): # This blcok makes sure you don't throw an error with an empty CC. CC_plus = la.pinv(CC) CC_0 = _nullSpaceBasis(CC.T) else: CC_plus = np.mat([]) CC_0 = np.mat([]) Psi_mat = FF Gamma_mat = -GG Theta_mat = -HH Xi_mat = np.mat( vstack((hstack( (Gamma_mat, Theta_mat)), hstack((eye(nx), zeros((nx, nx))))))) Delta_mat = np.mat( vstack((hstack((Psi_mat, zeros( (nx, nx)))), hstack((zeros((nx, nx)), eye(nx)))))) else: CC_plus = la.pinv(CC) CC_0 = _nullSpaceBasis(CC.T) if l_equ != ny: Psi_mat = vstack((zeros((l_equ - ny, nx)), FF \ - dot(dot(JJ, CC_plus), AA))) Gamma_mat = vstack((dot(CC_0, AA), dot(dot(JJ, CC_plus), BB) \ - GG + dot(dot(KK, CC_plus), AA))) Theta_mat = vstack((dot(CC_0, BB), dot(dot(KK, CC_plus), BB) - HH)) else: CC_inv = la.inv(CC) Psi_mat = FF - dot(JJ.dot(CC_inv), AA) Gamma_mat = dot(JJ.dot(CC_inv), BB) - GG + dot(dot(KK, CC_inv), AA) Theta_mat = dot(KK.dot(CC_inv), BB) - HH Xi_mat = vstack((hstack((Gamma_mat, Theta_mat)), \ hstack((eye(nx), zeros((nx, nx)))))) Delta_mat = vstack((hstack((Psi_mat, np.mat(zeros((nx, nx))))),\ hstack((zeros((nx, nx)), eye(nx))))) # Now we need the generalized eigenvalues/vectors for Xi with respect to # Delta. That is eVals and eVecs below. eVals, eVecs = la.eig(Xi_mat, Delta_mat) if npla.matrix_rank(eVecs) < nx: print("Error: Xi is not diagonalizable, stopping...") # From here to line 158 we Diagonalize Xi, form Lambda/Omega and find P. else: Xi_sortabs = np.sort(abs(eVals)) Xi_sortindex = np.argsort(abs(eVals)) Xi_sortedVec = np.array([eVecs[:, i] for i in Xi_sortindex]).T Xi_sortval = eVals[Xi_sortindex] Xi_select = np.arange(0, nx) if np.imag(Xi_sortval[nx - 1]).any(): if (abs(Xi_sortval[nx - 1] - sp.conj(Xi_sortval[nx])) < TOL): drop_index = 1 cond_1 = (abs(np.imag(Xi_sortval[drop_index - 1])) > TOL) cond_2 = drop_index < nx while cond_1 and cond_2: drop_index += 1 if drop_index >= nx: print("There is an error. Too many complex eigenvalues." + " Quitting...") else: print("Droping the lowest real eigenvalue. Beware of" + " sunspots!") Xi_select = np.array([np.arange(0, drop_index - 1),\ np.arange(drop_index, nx + 1)]) # Here Uhlig computes stuff if user chose "Manual roots" I skip it. if max(abs(Xi_sortval[Xi_select])) > 1 + TOL: print( "It looks like we have unstable roots. This might not work...") if abs(max(abs(Xi_sortval[Xi_select])) - 1) < TOL: print("Check the model to make sure you have a unique steady" + " state we are having problems with convergence.") Lambda_mat = np.diag(Xi_sortval[Xi_select]) Omega_mat = Xi_sortedVec[nx:2 * nx, Xi_select] if npla.matrix_rank(Omega_mat) < nx: print("Omega matrix is not invertible, Can't solve for P; we" + " proceed with QZ-method instead.") #~~~~~~~~~ QZ-method codes from SOLVE_QZ ~~~~~~~~# Delta_up, Xi_up, UUU, VVV = la.qz(Delta_mat, Xi_mat, output='complex') UUU = UUU.T Xi_eigval = np.diag( np.diag(Xi_up) / np.maximum(np.diag(Delta_up), TOL)) Xi_sortabs = np.sort(abs(np.diag(Xi_eigval))) Xi_sortindex = np.argsort(abs(np.diag(Xi_eigval))) Xi_sortval = Xi_eigval[Xi_sortindex, Xi_sortindex] Xi_select = np.arange(0, nx) stake = max(abs(Xi_sortval[Xi_select])) + TOL Delta_up, Xi_up, UUU, VVV = qzdiv(stake, Delta_up, Xi_up, UUU, VVV) #Check conditions from line 49-109 if np.imag(Xi_sortval[nx - 1]).any(): if (abs(Xi_sortval[nx - 1] - sp.conj(Xi_sortval[nx])) < TOL): print( "Problem: You have complex eigenvalues! And this means" + " PP matrix will contain complex numbers by this method." ) drop_index = 1 cond_1 = (abs(np.imag(Xi_sortval[drop_index - 1])) > TOL) cond_2 = drop_index < nx while cond_1 and cond_2: drop_index += 1 if drop_index >= nx: print("There is an error. Too many complex eigenvalues." + " Quitting...") else: print("Dropping the lowest real eigenvalue. Beware of" + " sunspots!") for i in xrange(drop_index, nx + 1): Delta_up, Xi_up, UUU, VVV = qzswitch( i, Delta_up, Xi_up, UUU, VVV) Xi_select1 = np.arange(0, drop_index - 1) Xi_select = np.append(Xi_select1, np.arange(drop_index, nx + 1)) if Xi_sortval[max(Xi_select)] < 1 - TOL: print('There are stable roots NOT used. Proceeding with the' + ' smallest root.') if max(abs(Xi_sortval[Xi_select])) > 1 + TOL: print( "It looks like we have unstable roots. This might not work..." ) if abs(max(abs(Xi_sortval[Xi_select])) - 1) < TOL: print("Check the model to make sure you have a unique steady" + " state we are having problems with convergence.") #End of checking conditions #Lambda_mat = np.diag(Xi_sortval[Xi_select]) # to help sol_out.m VVV = VVV.conj().T VVV_2_1 = VVV[nx:2 * nx, 0:nx] VVV_2_2 = VVV[nx:2 * nx, nx:2 * nx] UUU_2_1 = UUU[nx:2 * nx, 0:nx] VVV = VVV.conj().T if abs(la.det(UUU_2_1)) < TOL: print( "One necessary condition for computing P is NOT satisfied," + " but we proceed anyways...") if abs(la.det(VVV_2_1)) < TOL: print( "VVV_2_1 matrix, used to compute for P, is not invertible; we" + " are in trouble but we proceed anyways...") PP = np.matrix(la.solve(-VVV_2_1, VVV_2_2)) PP_imag = np.imag(PP) PP = np.real(PP) if (sum(sum(abs(PP_imag))) / sum(sum(abs(PP))) > .000001).any(): print( "A lot of P is complex. We will continue with the" + " real part and hope we don't lose too much information.") #~~~~~~~~~ End of QZ-method ~~~~~~~~~# #This follows the original uhlig.py file else: PP = dot(dot(Omega_mat, Lambda_mat), la.inv(Omega_mat)) PP_imag = np.imag(PP) PP = np.real(PP) if (sum(sum(abs(PP_imag))) / sum(sum(abs(PP))) > .000001).any(): print( "A lot of P is complex. We will continue with the" + " real part and hope we don't lose too much information.") # The code from here to the end was from he Uhlig file calc_qrs.m. # I think for python it fits better here than in a separate file. # The if and else below make RR and VV depending on our model's setup. if l_equ == 0: RR = zeros((0, nx)) VV = hstack((kron(NN.T, FF) + kron(eye(nz), \ (dot(FF, PP) + GG)), kron(NN.T, JJ) + kron(eye(nz), KK))) else: RR = -dot(CC_plus, (dot(AA, PP) + BB)) VV = sp.vstack((hstack((kron(eye(nz), AA), \ kron(eye(nz), CC))), hstack((kron(NN.T, FF) +\ kron(eye(nz), dot(FF, PP) + dot(JJ, RR) + GG),\ kron(NN.T, JJ) + kron(eye(nz), KK))))) # Now we use LL, NN, RR, VV to get the QQ, RR, SS, VV matrices. # first try using Sylvester equation solver if ny > 0: PM = (FF - la.solve(JJ.dot(CC), AA)) if npla.matrix_rank(PM) < nx + ny: Sylv = 0 print("Sylvester equation solver condition is not satisfied;"\ +" proceed with the original method...") else: if npla.matrix_rank(FF) < nx: Sylv = 0 print("Sylvester equation solver condition is not satisfied;"\ +" proceed with the original method...") if Sylv: print("Using Sylvester equation solver...") if ny > 0: Anew = la.solve(PM, (FF.dot(PP)+GG+JJ.dot(RR)-\ la.solve(KK.dot(CC), AA)) ) Bnew = NN Cnew1 = la.solve(JJ.dot(CC),DD.dot(NN))+la.solve(KK.dot(CC), DD)-\ LL.dot(NN)-MM Cnew = la.solve(PM, Cnew1) QQ = la.solve_sylvester(Anew, Bnew, Cnew) SS = la.solve(-CC, (AA.dot(QQ) + DD)) else: Anew = la.solve(FF, (FF.dot(PP) + GG)) Bnew = NN Cnew = la.solve(FF, (-LL.dot(NN) - MM)) QQ = la.solve_sylvester(Anew, Bnew, Cnew) SS = np.zeros((0, nz)) #empty matrix # then the Uhlig's way else: if (npla.matrix_rank(VV) < nz * (nx + ny)): print("Sorry but V is not invertible. Can't solve for Q and S;" + " but we proceed anyways...") LL = sp.mat(LL) NN = sp.mat(NN) LLNN_plus_MM = dot(LL, NN) + MM if DD.any(): impvec = vstack( [DD.T, np.reshape(LLNN_plus_MM, (nx * nz, 1), 'F')]) else: impvec = np.reshape(LLNN_plus_MM, (nx * nz, 1), 'F') QQSS_vec = np.matrix(la.solve(-VV, impvec)) if (max(abs(QQSS_vec)) == sp.inf).any(): print("We have issues with Q and S. Entries are undefined." + " Probably because V is no inverible.") #Build QQ SS QQ = np.reshape(np.matrix(QQSS_vec[0:nx * nz, 0]), (nx, nz), 'F') SS = np.reshape(QQSS_vec[(nx * nz):((nx + ny) * nz), 0],\ (ny, nz), 'F') #Build WW - WW has the property [x(t)',y(t)',z(t)']=WW [x(t)',z(t)']. WW = sp.vstack( (hstack((eye(nx), zeros((nx, nz)))), hstack((dot(RR, la.pinv(PP)), (SS - dot(dot(RR, la.pinv(PP)), QQ)))), hstack((zeros((nz, nx)), eye(nz))))) # find constant terms # redefine matrix to be 2D-array for generating vectors UU and VVV AA = np.array(AA) CC = np.array(CC) FF = np.array(FF) GG = np.array(GG) JJ = np.array(JJ) KK = np.array(KK) LL = np.array(LL) NN = np.array(NN) RR = np.array(RR) QQ = np.array(QQ) SS = np.array(SS) if ny > 0: UU1 = -(FF.dot(PP) + GG + JJ.dot(RR) + FF - (JJ + KK).dot(la.solve(CC, AA))) UU2 = (TT+(FF.dot(QQ)+JJ.dot(SS)+LL).dot(NN.dot(Z0)-Z0)- \ (JJ+KK).dot(la.solve(CC,WWW))) UU = la.solve(UU1, UU2) VVV = la.solve(-CC, (WWW + AA.dot(UU))) else: UU = la.solve(-(FF.dot(PP) + FF + GG), (TT + (FF.dot(QQ) + LL).dot(NN.dot(Z0) - Z0))) VVV = np.array([]) return np.array(PP), np.array(QQ), np.array(UU), np.array(RR), np.array(SS),\ np.array(VVV)
def SGU_solver(Xss, Yss, Gamma_state, Gamma_control, InvGamma, Copula, par, mpar, grid, targets, P_H, aggrshock, oc): # State = np.zeros((mpar['numstates'], 1)) State_m = State.copy() Contr = np.zeros((mpar['numcontrols'], 1)) Contr_m = Contr.copy() # F = lambda S, S_m, C, C_m : Fsys(S, S_m, C, C_m, # Xss,Yss,Gamma_state,Gamma_control,InvGamma, # self.Copula,self.par,self.mpar,self.grid,self.targets,self.P_H,aggrshock,oc) F = lambda S, S_m, C, C_m: Fsys(S, S_m, C, C_m, Xss, Yss, Gamma_state, Gamma_control, InvGamma, Copula, par, mpar, grid, targets, P_H, aggrshock, oc) start_time = time.clock() result_F = F(State, State_m, Contr, Contr_m) end_time = time.clock() print('Elapsed time is ', (end_time - start_time), ' seconds.') Fb = result_F['Difference'] pool = cpu_count() / 2 - 1 F1 = np.zeros((mpar['numstates'] + mpar['numcontrols'], mpar['numstates'])) F2 = np.zeros( (mpar['numstates'] + mpar['numcontrols'], mpar['numcontrols'])) F3 = np.zeros((mpar['numstates'] + mpar['numcontrols'], mpar['numstates'])) F4 = np.asmatrix( np.vstack((np.zeros((mpar['numstates'], mpar['numcontrols'])), np.eye(mpar['numcontrols'], mpar['numcontrols'])))) print('Use Schmitt Grohe Uribe Algorithm') print(' A *E[xprime uprime] =B*[x u]') print(' A = (dF/dxprimek dF/duprime), B =-(dF/dx dF/du)') numscale = 1 pnum = pool packagesize = int(ceil(mpar['numstates'] / float(3 * pnum))) blocks = int(ceil(mpar['numstates'] / float(packagesize))) par['scaleval1'] = 1e-9 par['scaleval2'] = 1e-4 start_time = time.clock() print('Computing Jacobian F1=DF/DXprime F3 =DF/DX') print('Total number of parallel blocks: ', str(blocks), '.') FF1 = [] FF3 = [] for bl in range(0, blocks): range_ = range(bl * packagesize, min(packagesize * (bl + 1), mpar['numstates'])) DF1 = np.asmatrix(np.zeros((len(Fb), len(range_)))) DF3 = np.asmatrix(np.zeros((len(Fb), len(range_)))) cc = np.zeros((mpar['numcontrols'], 1)) ss = np.zeros((mpar['numstates'], 1)) for Xct in range_: X = np.zeros((mpar['numstates'], 1)) h = par['scaleval1'] X[Xct] = h Fx = F(ss, X, cc, cc) DF3[:, Xct - bl * packagesize] = (Fx['Difference'] - Fb) / h Fx = F(X, ss, cc, cc) DF1[:, Xct - bl * packagesize] = (Fx['Difference'] - Fb) / h if sum(range_ == mpar['numstates'] - 2) == 1: Xct = mpar['numstates'] - 2 X = np.zeros((mpar['numstates'], 1)) h = par['scaleval2'] X[Xct] = h Fx = F(ss, X, cc, cc) DF3[:, Xct - bl * packagesize] = (Fx['Difference'] - Fb) / h Fx = F(X, ss, cc, cc) DF1[:, Xct - bl * packagesize] = (Fx['Difference'] - Fb) / h if sum(range_ == mpar['numstates'] - 1) == 1: Xct = mpar['numstates'] - 1 X = np.zeros((mpar['numstates'], 1)) h = par['scaleval2'] X[Xct] = h Fx = F(ss, X, cc, cc) DF3[:, Xct - bl * packagesize] = (Fx['Difference'] - Fb) / h Fx = F(X, ss, cc, cc) DF1[:, Xct - bl * packagesize] = (Fx['Difference'] - Fb) / h FF1.append(DF1.copy()) FF3.append(DF3.copy()) print('Block number: ', str(bl), ' done.') for i in range(0, int(ceil(mpar['numstates'] / float(packagesize)))): range_ = range(i * packagesize, min(packagesize * (i + 1), mpar['numstates'])) F1[:, range_] = FF1[i] F3[:, range_] = FF3[i] end_time = time.clock() print('Elapsed time is ', (end_time - start_time), ' seconds.') # jacobian wrt Y' packagesize = int(ceil(mpar['numcontrols'] / (3.0 * pnum))) blocks = int(ceil(mpar['numcontrols'] / float(packagesize))) print('Computing Jacobian F2 - DF/DYprime') print('Total number of parallel blocks: ', str(blocks), '.') FF = [] start_time = time.clock() for bl in range(0, blocks): range_ = range(bl * packagesize, min(packagesize * (bl + 1), mpar['numcontrols'])) DF2 = np.asmatrix(np.zeros((len(Fb), len(range_)))) cc = np.zeros((mpar['numcontrols'], 1)) ss = np.zeros((mpar['numstates'], 1)) for Yct in range_: Y = np.zeros((mpar['numcontrols'], 1)) h = par['scaleval2'] Y[Yct] = h Fx = F(ss, ss, Y, cc) DF2[:, Yct - bl * packagesize] = (Fx['Difference'] - Fb) / h FF.append(DF2.copy()) print('Block number: ', str(bl), ' done.') for i in range(0, int(ceil(mpar['numcontrols'] / float(packagesize)))): range_ = range(i * packagesize, min(packagesize * (i + 1), mpar['numcontrols'])) F2[:, range_] = FF[i] end_time = time.clock() print('Elapsed time is ', (end_time - start_time), ' seconds.') FF = [] FF1 = [] FF3 = [] cc = np.zeros((mpar['numcontrols'], 1)) ss = np.zeros((mpar['numstates'], 1)) for Yct in range(0, oc): Y = np.zeros((mpar['numcontrols'], 1)) h = par['scaleval2'] Y[-1 - Yct] = h Fx = F(ss, ss, cc, Y) F4[:, -1 - Yct] = (Fx['Difference'] - Fb) / h s, t, Q, Z = linalg.qz(np.hstack((F1, F2)), -np.hstack((F3, F4)), output='complex') relev = np.divide(abs(np.diag(s)), abs(np.diag(t))) ll = sorted(relev) slt = relev >= 1 nk = sum(slt) slt = 1 * slt mpar['overrideEigen'] = 1 if nk > mpar['numstates']: if mpar['overrideEigen']: print( 'Warning: The Equilibrium is Locally Indeterminate, critical eigenvalue shifted to: ', str(ll[-1 - mpar['numstates']])) slt = relev > ll[-1 - mpar['numstates']] nk = sum(slt) else: print('No Local Equilibrium Exists, last eigenvalue: ', str(ll[-1 - mpar['numstates']])) elif nk < mpar['numstates']: if mpar.overrideEigen: print( 'Warning: No Local Equilibrium Exists, critical eigenvalue shifted to: ', str(ll[-1 - mpar['numstates']])) slt = relev > ll[-1 - mpar['numstates']] nk = sum(slt) else: print('No Local Equilibrium Exists, last eigenvalue: ', str(ll[-1 - mpar['numstates']])) s_ord, t_ord, __, __, __, Z_ord = linalg.ordqz(np.hstack((F1, F2)), -np.hstack((F3, F4)), sort='ouc', output='complex') z21 = Z_ord[nk:, 0:nk] z11 = Z_ord[0:nk, 0:nk] s11 = s_ord[0:nk, 0:nk] t11 = t_ord[0:nk, 0:nk] if matrix_rank(z11) < nk: print('Warning: invertibility condition violated') # z11i=linalg.solve(z11,np.eye(nk)) # A\B, Ax=B # gx_= np.dot(z21,z11i) # gx=gx_.real # hx_=np.dot(z11,np.dot(linalg.solve(s11,t11),z11i)) # hx=hx_.real z11i = np.dot(np.linalg.inv(z11), np.eye(nk)) # compute the solution gx = np.real(np.dot(z21, z11i)) hx = np.real(np.dot(z11, np.dot(np.dot(np.linalg.inv(s11), t11), z11i))) return { 'hx': hx, 'gx': gx, 'F1': F1, 'F2': F2, 'F3': F3, 'F4': F4, 'par': par }
def LinApp_Solve(AA, BB, CC, DD, FF, GG, HH, JJ, KK, LL, MM, NN, Z0, Sylv): """ This code takes Uhlig's original code and puts it in the form of a function. This version outputs the policy function coefficients: PP, QQ and UU for X, and RR, SS and VV for Y. Inputs overview: The matrices of derivatives: AA - MM. The autoregression coefficient matrix NN from the law of motion for Z. Z0 is the Z-point about which the linearization is taken. For linearizing about the steady state this is Zbar and normally Zbar = 0. Sylv is an indicator variable telling the program to use the built-in function sylvester() to solve for QQ and SS, if possible. Default is to use Sylv=1. This option is now disabled and we always set Sylv=1. Parameters ---------- AA : array_like, dtype=float, shape=(ny, nx) The matrix represented above by :math:`A`. It is the matrix of derivatives of the Y equations with repsect to :math:`X_t` BB : array_like, dtype=float, shape=(ny, nx) The matrix represented above by :math:`B`. It is the matrix of derivatives of the Y equations with repsect to :math:`X_{t-1}`. CC : array_like, dtype=float, shape=(ny, ny) The matrix represented above by :math:`C`. It is the matrix of derivatives of the Y equations with repsect to :math:`Y_t` DD : array_like, dtype=float, shape=(ny, nz) The matrix represented above by :math:`C`. It is the matrix of derivatives of the Y equations with repsect to :math:`Z_t` FF : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`F`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_{t+1}` GG : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`G`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_t` HH : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`H`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_{t-1}` JJ : array_like, dtype=float, shape=(nx, ny) The matrix represetned above by :math:`J`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Y_{t+1}` KK : array_like, dtype=float, shape=(nx, ny) The matrix represetned above by :math:`K`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Y_t` LL : array_like, dtype=float, shape=(nx, nz) The matrix represetned above by :math:`L`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Z_{t+1}` MM : array_like, dtype=float, shape=(nx, nz) The matrix represetned above by :math:`M`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Z_t` NN : array_like, dtype=float, shape=(nz, nz) The autocorrelation matrix for the exogenous state vector z. Z0 : array, dtype=float, shape=(nz,) the Z-point about which the linearization is taken. For linearizing about the steady state this is Zbar and normally Zbar = 0. QQ if true. Sylv: binary, dtype=int an indicator variable telling the program to use the built-in function sylvester() to solve for QQ and SS, if possible. Default is to use Sylv=1. Returns ------- P : 2D-array, dtype=float, shape=(nx, nx) The matrix :math:`P` in the law of motion for endogenous state variables described above. Q : 2D-array, dtype=float, shape=(nx, nz) The matrix :math:`Q` in the law of motion for exogenous state variables described above. R : 2D-array, dtype=float, shape=(ny, nx) The matrix :math:`R` in the law of motion for endogenous state variables described above. S : 2D-array, dtype=float, shape=(ny, nz) The matrix :math:`S` in the law of motion for exogenous state variables described above. References ---------- .. [1] Uhlig, H. (1999): "A toolkit for analyzing nonlinear dynamic stochastic models easily," in Computational Methods for the Study of Dynamic Economies, ed. by R. Marimon, pp. 30-61. Oxford University Press. """ # The coding for Uhlig's solution for QQ and SS gives incorrect results # So we will use numpy's Sylvester equation solver regardless of the value # chosen for Sylv #The original coding we did used the np.matrix form for our matrices so we #make sure to set our inputs to numpy matrices. AA = np.matrix(AA) BB = np.matrix(BB) CC = np.matrix(CC) DD = np.matrix(DD) FF = np.matrix(FF) GG = np.matrix(GG) HH = np.matrix(HH) JJ = np.matrix(JJ) KK = np.matrix(KK) LL = np.matrix(LL) MM = np.matrix(MM) NN = np.matrix(NN) Z0 = np.array(Z0) #Tolerance level to use TOL = .000001 # Here we use matrices to get pertinent dimensions. nx = FF.shape[1] l_equ = CC.shape[0] ny = CC.shape[1] nz = min(NN.shape) # The following if and else blocks form the # Psi, Gamma, Theta Xi, Delta mats if l_equ == 0: if CC.any(): # This blcok makes sure you don't throw an error with an empty CC. CC_plus = la.pinv(CC) CC_0 = _nullSpaceBasis(CC.T) else: CC_plus = np.mat([]) CC_0 = np.mat([]) Psi_mat = FF Gamma_mat = -GG Theta_mat = -HH Xi_mat = np.mat( vstack((hstack( (Gamma_mat, Theta_mat)), hstack((eye(nx), zeros((nx, nx))))))) Delta_mat = np.mat( vstack((hstack((Psi_mat, zeros( (nx, nx)))), hstack((zeros((nx, nx)), eye(nx)))))) else: CC_plus = la.pinv(CC) CC_0 = _nullSpaceBasis(CC.T) if l_equ != ny: Psi_mat = vstack((zeros((l_equ - ny, nx)), FF \ - dot(dot(JJ, CC_plus), AA))) Gamma_mat = vstack((dot(CC_0, AA), dot(dot(JJ, CC_plus), BB) \ - GG + dot(dot(KK, CC_plus), AA))) Theta_mat = vstack((dot(CC_0, BB), dot(dot(KK, CC_plus), BB) - HH)) else: CC_inv = la.inv(CC) Psi_mat = FF - dot(JJ.dot(CC_inv), AA) Gamma_mat = dot(JJ.dot(CC_inv), BB) - GG + dot(dot(KK, CC_inv), AA) Theta_mat = dot(KK.dot(CC_inv), BB) - HH Xi_mat = vstack((hstack((Gamma_mat, Theta_mat)), \ hstack((eye(nx), zeros((nx, nx)))))) Delta_mat = vstack((hstack((Psi_mat, np.mat(zeros((nx, nx))))),\ hstack((zeros((nx, nx)), eye(nx))))) # Now we need the generalized eigenvalues/vectors for Xi with respect to # Delta. That is eVals and eVecs below. eVals, eVecs = la.eig(Xi_mat, Delta_mat) if npla.matrix_rank(eVecs) < nx: print("Error: Xi is not diagonalizable, stopping...") # From here to line 158 we Diagonalize Xi, form Lambda/Omega and find P. else: Xi_sortindex = np.argsort(abs(eVals)) Xi_sortedVec = np.array([eVecs[:, i] for i in Xi_sortindex]).T Xi_sortval = eVals[Xi_sortindex] Xi_select = np.arange(0, nx) if np.imag(Xi_sortval[nx - 1]).any(): if (abs(Xi_sortval[nx - 1] - sp.conj(Xi_sortval[nx])) < TOL): drop_index = 1 cond_1 = (abs(np.imag(Xi_sortval[drop_index - 1])) > TOL) cond_2 = drop_index < nx while cond_1 and cond_2: drop_index += 1 if drop_index >= nx: print("There is an error. Too many complex eigenvalues." + " Quitting...") else: print("Droping the lowest real eigenvalue. Beware of" + " sunspots!") Xi_select = np.array([np.arange(0, drop_index - 1),\ np.arange(drop_index, nx + 1)]) # Here Uhlig computes stuff if user chose "Manual roots" I skip it. if max(abs(Xi_sortval[Xi_select])) > 1 + TOL: print( "It looks like we have unstable roots. This might not work...") if abs(max(abs(Xi_sortval[Xi_select])) - 1) < TOL: print("Check the model to make sure you have a unique steady" + " state we are having problems with convergence.") Lambda_mat = np.diag(Xi_sortval[Xi_select]) Omega_mat = Xi_sortedVec[nx:2 * nx, Xi_select] if npla.matrix_rank(Omega_mat) < nx: print("Omega matrix is not invertible, Can't solve for P; we" + " proceed with QZ-method instead.") #~~~~~~~~~ QZ-method codes from SOLVE_QZ ~~~~~~~~# Delta_up, Xi_up, UUU, VVV = la.qz(Delta_mat, Xi_mat, output='complex') UUU = UUU.T Xi_eigval = np.diag( np.diag(Xi_up) / np.maximum(np.diag(Delta_up), TOL)) Xi_sortindex = np.argsort(abs(np.diag(Xi_eigval))) Xi_sortval = Xi_eigval[Xi_sortindex, Xi_sortindex] Xi_select = np.arange(0, nx) stake = max(abs(Xi_sortval[Xi_select])) + TOL Delta_up, Xi_up, UUU, VVV = qzdiv(stake, Delta_up, Xi_up, UUU, VVV) #Check conditions from line 49-109 if np.imag(Xi_sortval[nx - 1]).any(): if (abs(Xi_sortval[nx - 1] - sp.conj(Xi_sortval[nx])) < TOL): print( "Problem: You have complex eigenvalues! And this means" + " PP matrix will contain complex numbers by this method." ) drop_index = 1 cond_1 = (abs(np.imag(Xi_sortval[drop_index - 1])) > TOL) cond_2 = drop_index < nx while cond_1 and cond_2: drop_index += 1 if drop_index >= nx: print("There is an error. Too many complex eigenvalues." + " Quitting...") else: print("Dropping the lowest real eigenvalue. Beware of" + " sunspots!") for i in range(drop_index, nx + 1): Delta_up, Xi_up, UUU, VVV = qzswitch( i, Delta_up, Xi_up, UUU, VVV) Xi_select1 = np.arange(0, drop_index - 1) Xi_select = np.append(Xi_select1, np.arange(drop_index, nx + 1)) if Xi_sortval[max(Xi_select)] < 1 - TOL: print('There are stable roots NOT used. Proceeding with the' + ' smallest root.') if max(abs(Xi_sortval[Xi_select])) > 1 + TOL: print( "It looks like we have unstable roots. This might not work..." ) if abs(max(abs(Xi_sortval[Xi_select])) - 1) < TOL: print("Check the model to make sure you have a unique steady" + " state we are having problems with convergence.") #End of checking conditions #Lambda_mat = np.diag(Xi_sortval[Xi_select]) # to help sol_out.m VVV = VVV.conj().T VVV_2_1 = VVV[nx:2 * nx, 0:nx] VVV_2_2 = VVV[nx:2 * nx, nx:2 * nx] UUU_2_1 = UUU[nx:2 * nx, 0:nx] VVV = VVV.conj().T if abs(la.det(UUU_2_1)) < TOL: print( "One necessary condition for computing P is NOT satisfied," + " but we proceed anyways...") if abs(la.det(VVV_2_1)) < TOL: print( "VVV_2_1 matrix, used to compute for P, is not invertible; we" + " are in trouble but we proceed anyways...") PP = np.matrix(la.solve(-VVV_2_1, VVV_2_2)) PP_imag = np.imag(PP) PP = np.real(PP) if (sum(sum(abs(PP_imag))) / sum(sum(abs(PP))) > .000001).any(): print( "A lot of P is complex. We will continue with the" + " real part and hope we don't lose too much information.") #~~~~~~~~~ End of QZ-method ~~~~~~~~~# #This follows the original uhlig.py file else: PP = dot(dot(Omega_mat, Lambda_mat), la.inv(Omega_mat)) PP_imag = np.imag(PP) PP = np.real(PP) if (sum(sum(abs(PP_imag))) / sum(sum(abs(PP))) > .000001).any(): print( "A lot of P is complex. We will continue with the" + " real part and hope we don't lose too much information.") # The if and else below make RR depending on our model's setup. if l_equ == 0: RR = zeros((0, nx)) #empty matrix else: RR = -dot(CC_plus, (dot(AA, PP) + BB)) # Now we use Sylvester equation solver to find QQ and SS matrices. ''' This code written by Kerk Phillips 2020 ''' CCinv = npla.inv(CC) if ny > 0: PM = npla.inv(FF - np.matmul(np.matmul(JJ, CCinv), AA)) if npla.matrix_rank(PM) < nx + ny: print("Sylvester equation solver condition is not satisfied") else: PM = npla.inv(FF) if npla.matrix_rank(FF) < nx: print("Sylvester equation solver condition is not satisfied") if ny > 0: JCAP = np.matmul(np.matmul(JJ, CCinv), np.matmul(AA, PP)) JCB = np.matmul(np.matmul(JJ, CCinv), BB) KCA = np.matmul(np.matmul(KK, CCinv), AA) KCD = np.matmul(np.matmul(KK, CCinv), DD) JCDN = np.matmul(np.matmul(JJ, CCinv), np.matmul(DD, NN)) Anew = PM.dot(FF.dot(PP) + GG - JCAP - JCB - KCA) Bnew = NN Cnew = PM.dot(KCD - LL.dot(NN) + JCDN - MM) QQ = la.solve_sylvester(Anew, Bnew, Cnew) SS = la.solve(-CC, (AA.dot(QQ) + DD)) else: Anew = PM.dot(FF.dot(PP) + GG) Bnew = NN Cnew = PM.dot(-LL.dot(NN) - MM) QQ = la.solve_sylvester(Anew, Bnew, Cnew) SS = np.zeros((0, nz)) #empty matrix return np.array(PP), np.array(QQ), np.array(RR), np.array(SS)
def LinApp_Solve(AA,BB,CC,DD,FF,GG,HH,JJ,KK,LL,MM,WWW,TT,NN,Z0,Sylv): """ This code takes Uhlig's original code and puts it in the form of a function. This version outputs the policy function coefficients: PP, QQ and UU for X, and RR, SS and VV for Y. Inputs overview: The matrices of derivatives: AA - TT. The autoregression coefficient matrix NN from the law of motion for Z. Z0 is the Z-point about which the linearization is taken. For linearizing about the steady state this is Zbar and normally Zbar = 0. Sylv is an indicator variable telling the program to use the built-in function sylvester() to solve for QQ and SS, if possible. Default is to use Sylv=1. Parameters ---------- AA : array_like, dtype=float, shape=(ny, nx) The matrix represented above by :math:`A`. It is the matrix of derivatives of the Y equations with repsect to :math:`X_t` BB : array_like, dtype=float, shape=(ny, nx) The matrix represented above by :math:`B`. It is the matrix of derivatives of the Y equations with repsect to :math:`X_{t-1}`. CC : array_like, dtype=float, shape=(ny, ny) The matrix represented above by :math:`C`. It is the matrix of derivatives of the Y equations with repsect to :math:`Y_t` DD : array_like, dtype=float, shape=(ny, nz) The matrix represented above by :math:`C`. It is the matrix of derivatives of the Y equations with repsect to :math:`Z_t` FF : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`F`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_{t+1}` GG : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`G`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_t` HH : array_like, dtype=float, shape=(nx, nx) The matrix represetned above by :math:`H`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`X_{t-1}` JJ : array_like, dtype=float, shape=(nx, ny) The matrix represetned above by :math:`J`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Y_{t+1}` KK : array_like, dtype=float, shape=(nx, ny) The matrix represetned above by :math:`K`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Y_t` LL : array_like, dtype=float, shape=(nx, nz) The matrix represetned above by :math:`L`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Z_{t+1}` MM : array_like, dtype=float, shape=(nx, nz) The matrix represetned above by :math:`M`. It is the matrix of derivatives of the model's characterizing equations with respect to :math:`Z_t` WWW : array, dtype=float, shape=(ny,) The vector of the numerical errors of first ny characterizing equations TT : array, dtype=float, shape=(nx,) The vector of the numerical errors of the next nx characterizing equations following the first ny equations NN : array_like, dtype=float, shape=(nz, nz) The autocorrelation matrix for the exogenous state vector z. Z0 : array, dtype=float, shape=(nz,) The Z-point about which the linearization is taken. For linearizing about the steady state this is Zbar and normally Zbar = 0. QQ if true. Sylv: binary, dtype=int An indicator variable telling the program to use the built-in function sylvester() to solve for QQ and SS, if possible. Default is to use Sylv=1. Returns ------- P : 2D-array, dtype=float, shape=(nx, nx) The matrix :math:`P` in the law of motion for endogenous state variables described above. Q : 2D-array, dtype=float, shape=(nx, nz) The matrix :math:`Q` in the law of motion for exogenous state variables described above. U : array, dtype=float, shape=(nx,) The vector of the constant term of the policy function for X, the endogenous state variables R : 2D-array, dtype=float, shape=(ny, nx) The matrix :math:`R` in the law of motion for endogenous state variables described above. S : 2D-array, dtype=float, shape=(ny, nz) The matrix :math:`S` in the law of motion for exogenous state variables described above. V : array, dtype=float, shape=(ny,) The vector of the constant term of the policy function for Y, the endogenous non-state variables References ---------- .. [1] Uhlig, H. (1999): "A toolkit for analyzing nonlinear dynamic stochastic models easily," in Computational Methods for the Study of Dynamic Economies, ed. by R. Marimon, pp. 30-61. Oxford University Press. """ #The original coding we did used the np.matrix form for our matrices so we #make sure to set our inputs to numpy matrices. AA = np.matrix(AA) BB = np.matrix(BB) CC = np.matrix(CC) DD = np.matrix(DD) FF = np.matrix(FF) GG = np.matrix(GG) HH = np.matrix(HH) JJ = np.matrix(JJ) KK = np.matrix(KK) LL = np.matrix(LL) MM = np.matrix(MM) NN = np.matrix(NN) WWW = np.array(WWW) TT = np.array(TT) Z0 = np.array(Z0) #Tolerance level to use TOL = .000001 # Here we use matrices to get pertinent dimensions. nx = FF.shape[1] l_equ = CC.shape[0] ny = CC.shape[1] nz = min(NN.shape) # The following if and else blocks form the # Psi, Gamma, Theta Xi, Delta mats if l_equ == 0: if CC.any(): # This blcok makes sure you don't throw an error with an empty CC. CC_plus = la.pinv(CC) CC_0 = _nullSpaceBasis(CC.T) else: CC_plus = np.mat([]) CC_0 = np.mat([]) Psi_mat = FF Gamma_mat = -GG Theta_mat = -HH Xi_mat = np.mat(vstack((hstack((Gamma_mat, Theta_mat)), hstack((eye(nx), zeros((nx, nx))))))) Delta_mat = np.mat(vstack((hstack((Psi_mat, zeros((nx, nx)))), hstack((zeros((nx, nx)), eye(nx)))))) else: CC_plus = la.pinv(CC) CC_0 = _nullSpaceBasis(CC.T) if l_equ != ny: Psi_mat = vstack((zeros((l_equ - ny, nx)), FF \ - dot(dot(JJ, CC_plus), AA))) Gamma_mat = vstack((dot(CC_0, AA), dot(dot(JJ, CC_plus), BB) \ - GG + dot(dot(KK, CC_plus), AA))) Theta_mat = vstack((dot(CC_0, BB), dot(dot(KK, CC_plus), BB) - HH)) else: CC_inv = la.inv(CC) Psi_mat = FF - dot(JJ.dot(CC_inv), AA) Gamma_mat = dot(JJ.dot(CC_inv), BB) - GG + dot(dot(KK, CC_inv), AA) Theta_mat = dot(KK.dot(CC_inv), BB) - HH Xi_mat = vstack((hstack((Gamma_mat, Theta_mat)), \ hstack((eye(nx), zeros((nx, nx)))))) Delta_mat = vstack((hstack((Psi_mat, np.mat(zeros((nx, nx))))),\ hstack((zeros((nx, nx)), eye(nx))))) # Now we need the generalized eigenvalues/vectors for Xi with respect to # Delta. That is eVals and eVecs below. eVals, eVecs = la.eig(Xi_mat, Delta_mat) if npla.matrix_rank(eVecs) < nx: print("Error: Xi is not diagonalizable, stopping...") # From here to line 158 we Diagonalize Xi, form Lambda/Omega and find P. else: Xi_sortabs = np.sort(abs(eVals)) Xi_sortindex = np.argsort(abs(eVals)) Xi_sortedVec = np.array([eVecs[:, i] for i in Xi_sortindex]).T Xi_sortval = eVals[Xi_sortindex] Xi_select = np.arange(0, nx) if np.imag(Xi_sortval[nx - 1]).any(): if (abs(Xi_sortval[nx - 1] - sp.conj(Xi_sortval[nx])) < TOL): drop_index = 1 cond_1 = (abs(np.imag(Xi_sortval[drop_index-1])) > TOL) cond_2 = drop_index < nx while cond_1 and cond_2: drop_index += 1 if drop_index >= nx: print("There is an error. Too many complex eigenvalues." +" Quitting...") else: print("Droping the lowest real eigenvalue. Beware of" + " sunspots!") Xi_select = np.array([np.arange(0, drop_index - 1),\ np.arange(drop_index, nx + 1)]) # Here Uhlig computes stuff if user chose "Manual roots" I skip it. if max(abs(Xi_sortval[Xi_select])) > 1 + TOL: print("It looks like we have unstable roots. This might not work...") if abs(max(abs(Xi_sortval[Xi_select])) - 1) < TOL: print("Check the model to make sure you have a unique steady" + " state we are having problems with convergence.") Lambda_mat = np.diag(Xi_sortval[Xi_select]) Omega_mat = Xi_sortedVec[nx:2 * nx, Xi_select] if npla.matrix_rank(Omega_mat) < nx: print("Omega matrix is not invertible, Can't solve for P; we" + " proceed with the alternative, QZ-method, to get P...") #~~~~~~~~~ QZ-method codes from SOLVE_QZ ~~~~~~~~# Delta_up,Xi_up,UUU,VVV=la.qz(Delta_mat,Xi_mat, output='complex') UUU=UUU.T Xi_eigval = np.diag( np.diag(Xi_up)/np.maximum(np.diag(Delta_up),TOL)) Xi_sortabs= np.sort(abs(np.diag(Xi_eigval))) Xi_sortindex= np.argsort(abs(np.diag(Xi_eigval))) Xi_sortval = Xi_eigval[Xi_sortindex, Xi_sortindex] Xi_select = np.arange(0, nx) stake = max(abs(Xi_sortval[Xi_select])) + TOL Delta_up, Xi_up, UUU, VVV = qzdiv(stake,Delta_up,Xi_up,UUU,VVV) #Check conditions from line 49-109 if np.imag(Xi_sortval[nx - 1]).any(): if (abs(Xi_sortval[nx - 1] - sp.conj(Xi_sortval[nx])) < TOL): print("Problem: You have complex eigenvalues! And this means"+ " PP matrix will contain complex numbers by this method." ) drop_index = 1 cond_1 = (abs(np.imag(Xi_sortval[drop_index-1])) > TOL) cond_2 = drop_index < nx while cond_1 and cond_2: drop_index += 1 if drop_index >= nx: print("There is an error. Too many complex eigenvalues." +" Quitting...") else: print("Dropping the lowest real eigenvalue. Beware of" + " sunspots!") for i in xrange(drop_index,nx+1): Delta_up,Xi_up,UUU,VVV = qzswitch(i,Delta_up,Xi_up,UUU,VVV) Xi_select1 = np.arange(0,drop_index-1) Xi_select = np.append(Xi_select1, np.arange(drop_index,nx+1)) if Xi_sortval[max(Xi_select)] < 1 - TOL: print('There are stable roots NOT used. Proceeding with the' + ' smallest root.') if max(abs(Xi_sortval[Xi_select])) > 1 + TOL: print("It looks like we have unstable roots. This might not work...") if abs(max(abs(Xi_sortval[Xi_select])) - 1) < TOL: print("Check the model to make sure you have a unique steady" + " state we are having problems with convergence.") #End of checking conditions #Lambda_mat = np.diag(Xi_sortval[Xi_select]) # to help sol_out.m VVV=VVV.conj().T VVV_2_1 = VVV[nx : 2*nx, 0 : nx] VVV_2_2 = VVV[nx : 2*nx, nx :2*nx] UUU_2_1 = UUU[nx : 2*nx, 0 : nx] VVV = VVV.conj().T if abs(la.det(UUU_2_1))< TOL: print("One necessary condition for computing P is NOT satisfied,"+ " but we proceed anyways...") if abs(la.det(VVV_2_1))< TOL: print("VVV_2_1 matrix, used to compute for P, is not invertible; we"+ " are in trouble but we proceed anyways...") PP = np.matrix( la.solve(- VVV_2_1, VVV_2_2) ) PP_imag = np.imag(PP) PP = np.real(PP) if (sum(sum(abs(PP_imag))) / sum(sum(abs(PP))) > .000001).any(): print("A lot of P is complex. We will continue with the" + " real part and hope we don't lose too much information.") #~~~~~~~~~ End of QZ-method ~~~~~~~~~# #This follows the original uhlig.py file else: PP = dot(dot(Omega_mat, Lambda_mat), la.inv(Omega_mat)) PP_imag = np.imag(PP) PP = np.real(PP) if (sum(sum(abs(PP_imag))) / sum(sum(abs(PP))) > .000001).any(): print("A lot of P is complex. We will continue with the" + " real part and hope we don't lose too much information.") # The code from here to the end was from the Uhlig file calc_qrs.m. # I think for python it fits better here than in a separate file. # The if and else below make RR and VV depending on our model's setup. if l_equ == 0: RR = zeros((0, nx)) VV = hstack((kron(NN.T, FF) + kron(eye(nz), \ (dot(FF, PP) + GG)), kron(NN.T, JJ) + kron(eye(nz), KK))) else: RR = - dot(CC_plus, (dot(AA, PP) + BB)) VV = sp.vstack((hstack((kron(eye(nz), AA), \ kron(eye(nz), CC))), hstack((kron(NN.T, FF) +\ kron(eye(nz), dot(FF, PP) + dot(JJ, RR) + GG),\ kron(NN.T, JJ) + kron(eye(nz), KK))))) # Now we use LL, NN, RR, VV to get the QQ, RR, SS, VV matrices. # first try using Sylvester equation solver if ny>0: PM = (FF-la.solve(JJ.dot(CC),AA)) if npla.matrix_rank(PM)< nx+ny: Sylv=0 print("Sylvester equation solver condition is not satisfied;"\ +" proceed with the original method...") else: if npla.matrix_rank(FF)< nx: Sylv=0 print("Sylvester equation solver condition is not satisfied;"\ +" proceed with the original method...") if Sylv: print("Using Sylvester equation solver...") if ny>0: Anew = la.solve(PM, (FF.dot(PP)+GG+JJ.dot(RR)-\ la.solve(KK.dot(CC), AA)) ) Bnew = NN Cnew1 = la.solve(JJ.dot(CC),DD.dot(NN))+la.solve(KK.dot(CC), DD)-\ LL.dot(NN)-MM Cnew = la.solve(PM, Cnew1) QQ = la.solve_sylvester(Anew,Bnew,Cnew) SS = la.solve(-CC, (AA.dot(QQ)+DD)) else: Anew = la.solve(FF, (FF.dot(PP)+GG)) Bnew = NN Cnew = la.solve(FF, (-LL.dot(NN)-MM)) QQ = la.solve_sylvester(Anew,Bnew,Cnew) SS = np.zeros((0,nz)) #empty matrix # then the Uhlig's way else: if (npla.matrix_rank(VV) < nz * (nx + ny)): print("Sorry but V is not invertible. Can't solve for Q and S;"+ " but we proceed anyways...") LL = sp.mat(LL) NN = sp.mat(NN) LLNN_plus_MM = dot(LL, NN) + MM if DD.any(): impvec = vstack([DD.T, np.reshape(LLNN_plus_MM, (nx * nz, 1), 'F')]) else: impvec = np.reshape(LLNN_plus_MM, (nx * nz, 1), 'F') QQSS_vec = np.matrix(la.solve(-VV, impvec)) if (max(abs(QQSS_vec)) == sp.inf).any(): print("We have issues with Q and S. Entries are undefined." + " Probably because V is no inverible.") #Build QQ SS QQ = np.reshape(np.matrix(QQSS_vec[0:nx * nz, 0]), (nx, nz), 'F') SS = np.reshape(QQSS_vec[(nx * nz):((nx + ny) * nz), 0],\ (ny, nz), 'F') #Build WW - WW has the property [x(t)',y(t)',z(t)']=WW [x(t)',z(t)']. WW = sp.vstack(( hstack((eye(nx), zeros((nx, nz)))), hstack((dot(RR, la.pinv(PP)), (SS - dot(dot(RR, la.pinv(PP)), QQ)))), hstack((zeros((nz, nx)), eye(nz))))) # find constant terms # redefine matrices to be 2D-arrays for generating vector UU and VVV AA = np.array(AA) CC = np.array(CC) FF = np.array(FF) GG = np.array(GG) JJ = np.array(JJ) KK = np.array(KK) LL = np.array(LL) NN = np.array(NN) RR = np.array(RR) QQ = np.array(QQ) SS = np.array(SS) if ny>0: UU1 = -(FF.dot(PP)+GG+JJ.dot(RR)+FF-(JJ+KK).dot(la.solve(CC,AA))) UU2 = (TT+(FF.dot(QQ)+JJ.dot(SS)+LL).dot(NN.dot(Z0)-Z0)- \ (JJ+KK).dot(la.solve(CC,WWW))) UU = la.solve(UU1, UU2) VVV = la.solve(- CC, (WWW+AA.dot(UU)) ) else: UU = la.solve( -(FF.dot(PP)+FF+GG), (TT+(FF.dot(QQ)+LL).dot(NN.dot(Z0)-Z0)) ) VVV = np.array([]) return np.array(PP), np.array(QQ), np.array(UU), np.array(RR), np.array(SS),\ np.array(VVV)
def second_order_solver(FF, GG, HH): from scipy.linalg import qz from dolo.numeric.extern.qz import qzdiv from numpy import array, mat, c_, r_, eye, zeros, real_if_close, diag, allclose, where, diagflat from numpy.linalg import solve Psi_mat = array(FF) Gamma_mat = array(-GG) Theta_mat = array(-HH) m_states = FF.shape[0] Xi_mat = r_[c_[Gamma_mat, Theta_mat], c_[eye(m_states), zeros((m_states, m_states))]] Delta_mat = r_[c_[Psi_mat, zeros((m_states, m_states))], c_[zeros( (m_states, m_states)), eye(m_states)]] AAA, BBB, Q, Z = qz(Delta_mat, Xi_mat) Delta_up, Xi_up, UUU, VVV = [real_if_close(mm) for mm in (AAA, BBB, Q, Z)] Xi_eigval = diag(Xi_up) / where(diag(Delta_up) > TOL, diag(Delta_up), TOL) Xi_sortindex = abs(Xi_eigval).argsort() # (Xi_sortabs doesn't really seem to be needed) Xi_sortval = Xi_eigval[Xi_sortindex] Xi_select = slice(0, m_states) stake = (abs(Xi_sortval[Xi_select])).max() + TOL Delta_up, Xi_up, UUU, VVV = qzdiv(stake, Delta_up, Xi_up, UUU, VVV) try: # check that all unused roots are unstable assert abs(Xi_sortval[m_states]) > (1 - TOL) # check that all used roots are stable assert abs(Xi_sortval[Xi_select]).max() < 1 + TOL except: raise BKError('generic') # check for unit roots anywhere # assert (abs((abs(Xi_sortval) - 1)) > TOL).all() Lambda_mat = diagflat(Xi_sortval[Xi_select]) VVVH = VVV.T VVV_2_1 = VVVH[m_states:2 * m_states, :m_states] VVV_2_2 = VVVH[m_states:2 * m_states, m_states:2 * m_states] UUU_2_1 = UUU[m_states:2 * m_states, :m_states] PP = -solve(VVV_2_1, VVV_2_2) # slightly different check than in the original toolkit: assert allclose(real_if_close(PP), PP.real) PP = PP.real ## end of solve_qz! return [Xi_sortval[Xi_select], PP]
def solve_sylv_schur(A, Ar, E=None, Er=None, B=None, Br=None, C=None, Cr=None): r"""Solve Sylvester equation by Schur decomposition. Solves Sylvester equation .. math:: A V E_r^T + E V A_r^T + B B_r^T = 0 or .. math:: A^T W E_r + E^T W A_r + C^T C_r = 0 or both using (generalized) Schur decomposition (Algorithms 3 and 4 in [BKS11]_), if the necessary parameters are given. Parameters ---------- A Real |Operator|. Ar Real |Operator|. It is converted into a |NumPy array| using :func:`~pymor.algorithms.to_matrix.to_matrix`. E Real |Operator| or `None` (then assumed to be the identity). Er Real |Operator| or `None` (then assumed to be the identity). It is converted into a |NumPy array| using :func:`~pymor.algorithms.to_matrix.to_matrix`. B Real |Operator| or `None`. Br Real |Operator| or `None`. It is converted into a |VectorArray| using `Br.as_source_array()`. C Real |Operator| or `None`. Cr Real |Operator| or `None`. It is converted into a |VectorArray| using `Cr.as_range_array()`. Returns ------- V Returned if `B` and `Br` are given, |VectorArray| from `A.source`. W Returned if `C` and `Cr` are given, |VectorArray| from `A.source`. Raises ------ ValueError If `V` and `W` cannot be returned. """ # check types assert isinstance(A, OperatorInterface) and A.linear and A.source == A.range assert isinstance( Ar, OperatorInterface) and Ar.linear and Ar.source == Ar.range assert E is None or isinstance( E, OperatorInterface) and E.linear and E.source == E.range == A.source if E is None: E = IdentityOperator(A.source) assert Er is None or isinstance( Er, OperatorInterface) and Er.linear and Er.source == Er.range == Ar.source compute_V = B is not None and Br is not None compute_W = C is not None and Cr is not None if not compute_V and not compute_W: raise ValueError( 'Not enough parameters are given to solve a Sylvester equation.') if compute_V: assert isinstance( B, OperatorInterface) and B.linear and B.range == A.source assert isinstance( Br, OperatorInterface) and Br.linear and Br.range == Ar.source assert B.source == Br.source if compute_W: assert isinstance( C, OperatorInterface) and C.linear and C.source == A.source assert isinstance( Cr, OperatorInterface) and Cr.linear and Cr.source == Ar.source assert C.range == Cr.range # convert reduced operators Ar = to_matrix(Ar, format='dense') r = Ar.shape[0] if Er is not None: Er = to_matrix(Er, format='dense') if Br is not None: Br = Br.as_source_array() if Cr is not None: Cr = Cr.as_range_array() # (Generalized) Schur decomposition if Er is None: TAr, Z = spla.schur(Ar, output='complex') Q = Z else: TAr, TEr, Q, Z = spla.qz(Ar, Er, output='complex') # solve for V, from the last column to the first if compute_V: V = A.source.empty(reserve=r) Br2 = Br.lincomb(Q.T) BBr2 = B.apply(Br2) for i in range(-1, -r - 1, -1): rhs = -BBr2[i].copy() if i < -1: if Er is not None: rhs -= A.apply(V.lincomb(TEr[i, :i:-1].conjugate())) rhs -= E.apply(V.lincomb(TAr[i, :i:-1].conjugate())) TErii = 1 if Er is None else TEr[i, i] eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E V.append(eAaE.apply_inverse(rhs)) V = V.lincomb(Z.conjugate()[:, ::-1]) V = V.real # solve for W, from the first column to the last if compute_W: W = A.source.empty(reserve=r) Cr2 = Cr.lincomb(Z.T) CTCr2 = C.apply_adjoint(Cr2) for i in range(r): rhs = -CTCr2[i].copy() if i > 0: if Er is not None: rhs -= A.apply_adjoint(W.lincomb(TEr[:i, i])) rhs -= E.apply_adjoint(W.lincomb(TAr[:i, i])) TErii = 1 if Er is None else TEr[i, i] eAaE = TErii.conjugate() * A + TAr[i, i].conjugate() * E W.append(eAaE.apply_inverse_adjoint(rhs)) W = W.lincomb(Q.conjugate()) W = W.real if compute_V and compute_W: return V, W elif compute_V: return V else: return W
for ii in range(n)])(*SSval)).reshape(n, nx, nxp) nfxx = array( lambdify(args, [fx[ii, :].jacobian(x) for ii in range(n)], 'numpy')(*SSval)).reshape(n, nx, nx) # DNT ==== First-order derivatives of the functions g and h =================== stake = 1.0 #Create system matrices A,B AA = c_[-nfxp, -nfyp] BB = c_[nfx, nfy] NK = nfx.shape[1] #Complex Schur Decomposition ss, tt, qq, zz = linalg.qz(AA, BB) #Pick non-explosive (stable) eigenvalues slt = (abs(diag(tt)) < stake * abs(diag(ss))) noslt = logical_not(slt) nk = sum(slt) # Prep for QZ decomposition- qzswitch() def qzswitch(i, ss, tt, qq, zz): ssout = ss.copy() ttout = tt.copy() qqout = qq.copy() zzout = zz.copy() ix = i - 1 # from 1-based to 0-based indexing...