def second_order_solver(FF,GG,HH, eigmax=1.0+1e-6): # from scipy.linalg import qz from dolo.numeric.extern.qz import qzordered 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)]] [Delta_up,Xi_up,UUU,VVV,eigval] = qzordered(Delta_mat, Xi_mat,) 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 return [eigval,PP]
def classical_perturbation(g_s, g_x, f_s, f_x, f_S, f_X): n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_v = n_s + n_x A = row_stack([ column_stack([eye(n_s), zeros((n_s, n_x))]), column_stack([-f_S, -f_X]) ]) B = row_stack([column_stack([g_s, g_x]), column_stack([f_s, f_x])]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0 - 1e-8) Q = Q.real # is it really necessary ? Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] # S11 = S[:n_s, :n_s] # T11 = T[:n_s, :n_s] # first order solution # P = (solve(S11.T, Z11.T).T @ solve(Z11.T, T11.T).T) C = solve(Z11.T, Z21.T).T return C, eigval
def classical_perturbation(g_s, g_x, f_s, f_x, f_S, f_X): n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_v = n_s + n_x A = row_stack([ column_stack([eye(n_s), zeros((n_s, n_x))]), column_stack([-f_S , -f_X ]) ]) B = row_stack([ column_stack([g_s, g_x]), column_stack([f_s, f_x]) ]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0-1e-8) Q = Q.real # is it really necessary ? Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] # S11 = S[:n_s, :n_s] # T11 = T[:n_s, :n_s] # first order solution # P = (solve(S11.T, Z11.T).T @ solve(Z11.T, T11.T).T) C = solve(Z11.T, Z21.T).T return C, eigval
def approximate_controls(model, verbose=False, steady_state=None, eigmax=1.0, solve_steady_state=False, order=1): """Compute first order approximation of optimal controls Parameters: ----------- model: NumericModel Model to be solved verbose: boolean If True: displays number of contracting eigenvalues steady_state: ndarray Use supplied steady-state value to compute the approximation. The routine doesn't check whether it is really a solution or not. solve_steady_state: boolean Use nonlinear solver to find the steady-state orders: {1} Approximation order. (Currently, only first order is supported). Returns: -------- TaylorExpansion: Decision Rule for the optimal controls around the steady-state. """ if order>1: raise Exception("Not implemented.") # get steady_state import numpy # if model.model_type == 'fga': # model = GModel_fg_from_fga(model) # g = model.functions['transition'] # f = model.functions['arbitrage'] from dolo.algos.convert import get_fg_functions [f,g] = get_fg_functions(model) if steady_state is not None: calib = steady_state else: calib = model.calibration if solve_steady_state: from dolo.algos.steady_state import find_deterministic_equilibrium calib = find_deterministic_equilibrium(model) p = calib['parameters'] s = calib['states'] x = calib['controls'] e = calib['shocks'] if model.covariances is not None: sigma = model.covariances else: sigma = numpy.zeros((len(e), len(e))) from numpy.linalg import solve l = g(s,x,e,p, diff=True) [junk, g_s, g_x, g_e] = l[:4] # [el[0,...] for el in l[:4]] l = f(s,x,e,s,x,p, diff=True) [res, f_s, f_x, f_e, f_S, f_X] = l #[el[0,...] for el in l[:6]] n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_e = g_e.shape[1] n_v = n_s + n_x A = row_stack([ column_stack( [ eye(n_s), zeros((n_s,n_x)) ] ), column_stack( [ -f_S , -f_X ] ) ]) B = row_stack([ column_stack( [ g_s, g_x ] ), column_stack( [ f_s, f_x ] ) ]) from dolo.numeric.extern.qz import qzordered [S,T,Q,Z,eigval] = qzordered(A,B,n_s) Q = Q.real # is it really necessary ? Z = Z.real diag_S = numpy.diag(S) diag_T = numpy.diag(T) # Check Blanchard=Kahn conditions n_big_one = sum(eigval>eigmax) n_expected = n_x if verbose: print( "There are {} eigenvalues greater than {}. Expected: {}.".format( n_big_one, eigmax, n_x ) ) if n_expected != n_big_one: raise BlanchardKahnError(n_big_one, n_expected) tol_geneigvals = 1e-10 try: assert( sum( (abs( diag_S ) < tol_geneigvals) * (abs(diag_T) < tol_geneigvals) ) == 0) except Exception as e: print e print(numpy.column_stack([diag_S, diag_T])) # raise GeneralizedEigenvaluesError(diag_S, diag_T) Z11 = Z[:n_s,:n_s] Z12 = Z[:n_s,n_s:] Z21 = Z[n_s:,:n_s] Z22 = Z[n_s:,n_s:] S11 = S[:n_s,:n_s] T11 = T[:n_s,:n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T , solve(Z11.T, T11.T).T ) Q = g_e s = s.ravel() x = x.ravel() A = g_s + dot( g_x, C ) B = g_e dr = CDR([s, x, C]) dr.A = A dr.B = B dr.sigma = sigma return dr
def approximate_1st_order(g_s, g_x, g_e, f_s, f_x, f_S, f_X): n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_e = g_e.shape[1] n_v = n_s + n_x A = row_stack([ column_stack([eye(n_s), zeros((n_s, n_x))]), column_stack([-f_S , -f_X ]) ]) B = row_stack([ column_stack([g_s, g_x]), column_stack([f_s, f_x]) ]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0-1e-8) Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 try: ok = sum((abs(diag_S) < tol_geneigvals) * (abs(diag_T) < tol_geneigvals)) == 0 assert(ok) except Exception as e: raise GeneralizedEigenvaluesError(diag_S=diag_S, diag_T=diag_T) eigval_s = sorted(eigval, reverse=False) if max(eigval[:n_s]) >= 1 and min(eigval[n_s:]) < 1: # BK conditions are met pass else: ev_a = eigval_s[n_s-1] ev_b = eigval_s[n_s] cutoff = (ev_a - ev_b)/2 if not ev_a > ev_b: raise GeneralizedEigenvaluesSelectionError( A=A, B=B, eigval=eigval, cutoff=cutoff, diag_S=diag_S, diag_T=diag_T, n_states=n_s ) import warnings if cutoff > 1: warnings.warn("Solution is not convergent.") else: warnings.warn("There are multiple convergent solutions. The one with the smaller eigenvalues was selected.") [S, T, Q, Z, eigval] = qzordered(A, B, cutoff) Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] # S11 = S[:n_s, :n_s] # T11 = T[:n_s, :n_s] # first order solution # P = (solve(S11.T, Z11.T).T @ solve(Z11.T, T11.T).T) C = solve(Z11.T, Z21.T).T A = g_s + g_x @ C B = g_e return C, eigval_s
def state_perturb(problem: PerturbationProblem, verbose=True): """Computes a Taylor approximation of decision rules, given the supplied derivatives. The original system is assumed to be in the the form: .. math:: E_t f(s_t,x_t,s_{t+1},x_{t+1}) s_t = g(s_{t-1},x_{t-1}, \\lambda \\epsilon_t) where :math:`\\lambda` is a scalar scaling down the risk. the solution is a function :math:`\\varphi` such that: .. math:: x_t = \\varphi ( s_t, \\sigma ) The user supplies, a list of derivatives of f and g. :param f_fun: list of derivatives of f [order0, order1, order2, ...] :param g_fun: list of derivatives of g [order0, order1, order2, ...] :param sigma: covariance matrix of :math:`\\epsilon_t` Assuming :math:`s_t` , :math:`x_t` and :math:`\\epsilon_t` are vectors of size :math:`n_s`, :math:`n_x` and :math:`n_x` respectively. In general the derivative of order :math:`i` of :math:`f` is a multimensional array of size :math:`n_x \\times (N, ..., N)` with :math:`N=2(n_s+n_x)` repeated :math:`i` times (possibly 0). Similarly the derivative of order :math:`i` of :math:`g` is a multidimensional array of size :math:`n_s \\times (M, ..., M)` with :math:`M=n_s+n_x+n_2` repeated :math:`i` times (possibly 0). """ import numpy as np from numpy.linalg import solve approx_order = problem.order # order of approximation [f0, f1] = problem.f[:2] [g0, g1] = problem.g[:2] sigma = problem.sigma n_x = f1.shape[0] # number of controls n_s = f1.shape[1] // 2 - n_x # number of states n_e = g1.shape[1] - n_x - n_s n_v = n_s + n_x f_s = f1[:, :n_s] f_x = f1[:, n_s:n_s + n_x] f_snext = f1[:, n_v:n_v + n_s] f_xnext = f1[:, n_v + n_s:] g_s = g1[:, :n_s] g_x = g1[:, n_s:n_s + n_x] g_e = g1[:, n_v:] A = np.row_stack([ np.column_stack([np.eye(n_s), np.zeros((n_s, n_x))]), np.column_stack([-f_snext, -f_xnext]) ]) B = np.row_stack( [np.column_stack([g_s, g_x]), np.column_stack([f_s, f_x])]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0 - 1e-8) Q = Q.real # is it really necessary ? Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 try: ok = sum((abs(diag_S) < tol_geneigvals) * (abs(diag_T) < tol_geneigvals)) == 0 assert (ok) except Exception as e: raise GeneralizedEigenvaluesError(diag_S=diag_S, diag_T=diag_T) if max(eigval[:n_s]) >= 1 and min(eigval[n_s:]) < 1: # BK conditions are met pass else: eigval_s = sorted(eigval, reverse=True) ev_a = eigval_s[n_s - 1] ev_b = eigval_s[n_s] cutoff = (ev_a - ev_b) / 2 if not ev_a > ev_b: raise GeneralizedEigenvaluesSelectionError(A=A, B=B, eigval=eigval, cutoff=cutoff, diag_S=diag_S, diag_T=diag_T, n_states=n_s) import warnings if cutoff > 1: warnings.warn("Solution is not convergent.") else: warnings.warn( "There are multiple convergent solutions. The one with the smaller eigenvalues was selected." ) [S, T, Q, Z, eigval] = qzordered(A, B, cutoff) Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] S11 = S[:n_s, :n_s] T11 = T[:n_s, :n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T, solve(Z11.T, T11.T).T) Q = g_e # if False: # from numpy import dot # test = f_s + f_x @ C + f_snext @ (g_s + g_x @ C) + f_xnext @ C @ (g_s + g_x @ C) # print('Error: ' + str(abs(test).max())) if approx_order == 1: return [C] # second order solution from dolo.numeric.tensor import sdot, mdot from numpy import dot from dolo.numeric.matrix_equations import solve_sylvester f2 = problem.f[2] g2 = problem.g[2] g_ss = g2[:, :n_s, :n_s] g_sx = g2[:, :n_s, n_s:n_v] g_xx = g2[:, n_s:n_v, n_s:n_v] X_s = C V1_3 = g_s + dot(g_x, X_s) V1 = np.row_stack([np.eye(n_s), X_s, V1_3, X_s @ V1_3]) K2 = g_ss + 2 * sdot(g_sx, X_s) + mdot(g_xx, X_s, X_s) A = f_x + dot(f_snext + dot(f_xnext, X_s), g_x) B = f_xnext C = V1_3 D = mdot(f2, V1, V1) + sdot(f_snext + dot(f_xnext, X_s), K2) X_ss = solve_sylvester(A, B, C, D) # test = sdot( A, X_ss ) + sdot( B, mdot(X_ss,V1_3,V1_3) ) + D g_ee = g2[:, n_v:, n_v:] v = np.row_stack([g_e, dot(X_s, g_e)]) K_tt = mdot(f2[:, n_v:, n_v:], v, v) K_tt += sdot(f_snext + dot(f_xnext, X_s), g_ee) K_tt += mdot(sdot(f_xnext, X_ss), g_e, g_e) K_tt = np.tensordot(K_tt, sigma, axes=((1, 2), (0, 1))) L_tt = f_x + dot(f_snext, g_x) + dot(f_xnext, dot(X_s, g_x) + np.eye(n_x)) X_tt = solve(L_tt, -K_tt) if approx_order == 2: return [[X_s, X_ss], [X_tt]] # third order solution f3 = problem.f[3] g3 = problem.g[3] g_sss = g3[:, :n_s, :n_s, :n_s] g_ssx = g3[:, :n_s, :n_s, n_s:n_v] g_sxx = g3[:, :n_s, n_s:n_v, n_s:n_v] g_xxx = g3[:, n_s:n_v, n_s:n_v, n_s:n_v] V2_3 = K2 + sdot(g_x, X_ss) V2 = np.row_stack([ np.zeros((n_s, n_s, n_s)), X_ss, V2_3, dot(X_s, V2_3) + mdot(X_ss, V1_3, V1_3) ]) K3 = g_sss + 3 * sdot(g_ssx, X_s) + 3 * mdot(g_sxx, X_s, X_s) + 2 * sdot( g_sx, X_ss) K3 += 3 * mdot(g_xx, X_ss, X_s) + mdot(g_xxx, X_s, X_s, X_s) L3 = 3 * mdot(X_ss, V1_3, V2_3) # A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before # B = f_xnext # same # C = V1_3 # same D = mdot(f3, V1, V1, V1) + 3 * mdot(f2, V2, V1) + sdot( f_snext + dot(f_xnext, X_s), K3) D += sdot(f_xnext, L3) X_sss = solve_sylvester(A, B, C, D) # now doing sigma correction with sigma replaced by l in the subscripts g_se = g2[:, :n_s, n_v:] g_xe = g2[:, n_s:n_v, n_v:] g_see = g3[:, :n_s, n_v:, n_v:] g_xee = g3[:, n_s:n_v, n_v:, n_v:] W_l = np.row_stack([g_e, dot(X_s, g_e)]) I_e = np.eye(n_e) V_sl = g_se + mdot(g_xe, X_s, np.eye(n_e)) W_sl = np.row_stack([V_sl, mdot(X_ss, V1_3, g_e) + sdot(X_s, V_sl)]) K_ee = mdot(f3[:, :, n_v:, n_v:], V1, W_l, W_l) K_ee += 2 * mdot(f2[:, n_v:, n_v:], W_sl, W_l) # stochastic part of W_ll SW_ll = np.row_stack([g_ee, mdot(X_ss, g_e, g_e) + sdot(X_s, g_ee)]) DW_ll = np.concatenate( [X_tt, dot(g_x, X_tt), dot(X_s, sdot(g_x, X_tt)) + X_tt]) K_ee += mdot(f2[:, :, n_v:], V1, SW_ll) K_ = np.tensordot(K_ee, sigma, axes=((2, 3), (0, 1))) K_ += mdot(f2[:, :, n_s:], V1, DW_ll) def E(vec): n = len(vec.shape) return np.tensordot(vec, sigma, axes=((n - 2, n - 1), (0, 1))) L = sdot(g_sx, X_tt) + mdot(g_xx, X_s, X_tt) L += E(g_see + mdot(g_xee, X_s, I_e, I_e)) M = E(mdot(X_sss, V1_3, g_e, g_e) + 2 * mdot(X_ss, V_sl, g_e)) M += mdot(X_ss, V1_3, E(g_ee) + sdot(g_x, X_tt)) A = f_x + dot(f_snext + dot(f_xnext, X_s), g_x) # same as before B = f_xnext # same C = V1_3 # same D = K_ + dot(f_snext + dot(f_xnext, X_s), L) + dot(f_xnext, M) X_stt = solve_sylvester(A, B, C, D) if approx_order == 3: # if sigma is None: # return [X_s,X_ss,X_sss] # else: # return [[X_s,X_ss,X_sss],[X_tt, X_stt]] return [[X_s, X_ss, X_sss], [X_tt, X_stt]]
def state_perturb(f_fun, g_fun, sigma, sigma2_correction=None, verbose=True, eigmax=1.00000): """Computes a Taylor approximation of decision rules, given the supplied derivatives. The original system is assumed to be in the the form: .. math:: E_t f(s_t,x_t,s_{t+1},x_{t+1}) s_t = g(s_{t-1},x_{t-1}, \\lambda \\epsilon_t) where :math:`\\lambda` is a scalar scaling down the risk. the solution is a function :math:`\\varphi` such that: .. math:: x_t = \\varphi ( s_t, \\sigma ) The user supplies, a list of derivatives of f and g. :param f_fun: list of derivatives of f [order0, order1, order2, ...] :param g_fun: list of derivatives of g [order0, order1, order2, ...] :param sigma: covariance matrix of :math:`\\epsilon_t` :param sigma2_correction: (optional) first and second derivatives of g w.r.t. sigma if :math:`g` explicitely depends :math:`sigma` Assuming :math:`s_t` , :math:`x_t` and :math:`\\epsilon_t` are vectors of size :math:`n_s`, :math:`n_x` and :math:`n_x` respectively. In general the derivative of order :math:`i` of :math:`f` is a multimensional array of size :math:`n_x \\times (N, ..., N)` with :math:`N=2(n_s+n_x)` repeated :math:`i` times (possibly 0). Similarly the derivative of order :math:`i` of :math:`g` is a multidimensional array of size :math:`n_s \\times (M, ..., M)` with :math:`M=n_s+n_x+n_2` repeated :math:`i` times (possibly 0). """ import numpy as np from numpy.linalg import solve approx_order = len(f_fun) - 1 # order of approximation [f0,f1] = f_fun[:2] [g0,g1] = g_fun[:2] n_x = f1.shape[0] # number of controls n_s = f1.shape[1]/2 - n_x # number of states n_e = g1.shape[1] - n_x - n_s n_v = n_s + n_x f_s = f1[:,:n_s] f_x = f1[:,n_s:n_s+n_x] f_snext = f1[:,n_v:n_v+n_s] f_xnext = f1[:,n_v+n_s:] g_s = g1[:,:n_s] g_x = g1[:,n_s:n_s+n_x] g_e = g1[:,n_v:] A = np.row_stack([ np.column_stack( [ np.eye(n_s), np.zeros((n_s,n_x)) ] ), np.column_stack( [ -f_snext , -f_xnext ] ) ]) B = np.row_stack([ np.column_stack( [ g_s, g_x ] ), np.column_stack( [ f_s, f_x ] ) ]) from dolo.numeric.extern.qz import qzordered [S,T,Q,Z,eigval] = qzordered(A,B,n_s) # Check Blanchard=Kahn conditions n_big_one = sum(eigval>eigmax) n_expected = n_x if verbose: print( "There are {} eigenvalues greater than 1. Expected: {}.".format( n_big_one, n_x ) ) if n_big_one != n_expected: raise Exception("There should be exactly {} eigenvalues greater than one. Not {}.".format(n_x, n_big_one)) Q = Q.real # is it really necessary ? Z = Z.real Z11 = Z[:n_s,:n_s] Z12 = Z[:n_s,n_s:] Z21 = Z[n_s:,:n_s] Z22 = Z[n_s:,n_s:] S11 = S[:n_s,:n_s] T11 = T[:n_s,:n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T , solve(Z11.T, T11.T).T ) Q = g_e if False: from numpy import dot test = f_s + dot(f_x,C) + dot( f_snext, g_s + dot(g_x,C) ) + dot(f_xnext, dot( C, g_s + dot(g_x,C) ) ) print('Error: ' + str(abs(test).max())) if approx_order == 1: return [C] # second order solution from dolo.numeric.tensor import sdot, mdot from numpy import dot from dolo.numeric.matrix_equations import solve_sylvester f2 = f_fun[2] g2 = g_fun[2] g_ss = g2[:,:n_s,:n_s] g_sx = g2[:,:n_s,n_s:n_v] g_xx = g2[:,n_s:n_v,n_s:n_v] X_s = C V1_3 = g_s + dot(g_x,X_s) V1 = np.row_stack([ np.eye(n_s), X_s, V1_3, dot( X_s, V1_3 ) ]) K2 = g_ss + 2 * sdot(g_sx,X_s) + mdot(g_xx,[X_s,X_s]) #L2 = A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) B = f_xnext C = V1_3 D = mdot(f2,[V1,V1]) + sdot(f_snext + dot(f_xnext,X_s),K2) X_ss = solve_sylvester(A,B,C,D) # test = sdot( A, X_ss ) + sdot( B, mdot(X_ss,[V1_3,V1_3]) ) + D # if sigma is not None: if True: g_ee = g2[:,n_v:,n_v:] v = np.row_stack([ g_e, dot(X_s,g_e) ]) K_tt = mdot( f2[:,n_v:,n_v:], [v,v] ) K_tt += sdot( f_snext + dot(f_xnext,X_s) , g_ee ) K_tt += mdot( sdot( f_xnext, X_ss), [g_e, g_e] ) K_tt = np.tensordot( K_tt, sigma, axes=((1,2),(0,1))) if sigma2_correction is not None: K_tt += sdot( f_snext + dot(f_xnext,X_s) , sigma2_correction[0] ) L_tt = f_x + dot(f_snext, g_x) + dot(f_xnext, dot(X_s, g_x) + np.eye(n_x) ) X_tt = solve( L_tt, - K_tt) if approx_order == 2: return [[X_s,X_ss],[X_tt]] # third order solution f3 = f_fun[3] g3 = g_fun[3] g_sss = g3[:,:n_s,:n_s,:n_s] g_ssx = g3[:,:n_s,:n_s,n_s:n_v] g_sxx = g3[:,:n_s,n_s:n_v,n_s:n_v] g_xxx = g3[:,n_s:n_v,n_s:n_v,n_s:n_v] V2_3 = K2 + sdot(g_x,X_ss) V2 = np.row_stack([ np.zeros( (n_s,n_s,n_s) ), X_ss, V2_3, dot( X_s, V2_3 ) + mdot(X_ss,[V1_3,V1_3]) ]) K3 = g_sss + 3*sdot(g_ssx,X_s) + 3*mdot(g_sxx,[X_s,X_s]) + 2*sdot(g_sx,X_ss) K3 += 3*mdot( g_xx,[X_ss,X_s] ) + mdot(g_xxx,[X_s,X_s,X_s]) L3 = 3*mdot(X_ss,[V1_3,V2_3]) # A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before # B = f_xnext # same # C = V1_3 # same D = mdot(f3,[V1,V1,V1]) + 3*mdot(f2,[ V2,V1 ]) + sdot(f_snext + dot(f_xnext,X_s),K3) D += sdot( f_xnext, L3 ) X_sss = solve_sylvester(A,B,C,D) # now doing sigma correction with sigma replaced by l in the subscripts # if not sigma is None: if True: g_se= g2[:,:n_s,n_v:] g_xe= g2[:,n_s:n_v,n_v:] g_see= g3[:,:n_s,n_v:,n_v:] g_xee= g3[:,n_s:n_v,n_v:,n_v:] W_l = np.row_stack([ g_e, dot(X_s,g_e) ]) I_e = np.eye(n_e) V_sl = g_se + mdot( g_xe, [X_s, np.eye(n_e)]) W_sl = np.row_stack([ V_sl, mdot( X_ss, [ V1_3, g_e ] ) + sdot( X_s, V_sl) ]) K_ee = mdot(f3[:,:,n_v:,n_v:], [V1, W_l, W_l ]) K_ee += 2 * mdot( f2[:,n_v:,n_v:], [W_sl, W_l]) # stochastic part of W_ll SW_ll = np.row_stack([ g_ee, mdot(X_ss, [g_e, g_e]) + sdot(X_s, g_ee) ]) DW_ll = np.concatenate([ X_tt, dot(g_x, X_tt), dot(X_s, sdot(g_x,X_tt )) + X_tt ]) K_ee += mdot( f2[:,:,n_v:], [V1, SW_ll]) K_ = np.tensordot(K_ee, sigma, axes=((2,3),(0,1))) K_ += mdot(f2[:,:,n_s:], [V1, DW_ll]) def E(vec): n = len(vec.shape) return np.tensordot(vec,sigma,axes=((n-2,n-1),(0,1))) L = sdot(g_sx,X_tt) + mdot(g_xx,[X_s,X_tt]) L += E(g_see + mdot(g_xee,[X_s,I_e,I_e]) ) M = E( mdot(X_sss,[V1_3, g_e, g_e]) + 2*mdot(X_ss, [V_sl,g_e]) ) M += mdot( X_ss, [V1_3, E( g_ee ) + sdot(g_x, X_tt)] ) A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before B = f_xnext # same C = V1_3 # same D = K_ + dot( f_snext + dot(f_xnext,X_s), L) + dot( f_xnext, M ) if sigma2_correction is not None: g_sl = sigma2_correction[1][:,:n_s] g_xl = sigma2_correction[1][:,n_s:(n_s+n_x)] D += dot( f_snext + dot(f_xnext,X_s), g_sl + dot(g_xl,X_s) ) # constant X_stt = solve_sylvester(A,B,C,D) if approx_order == 3: # if sigma is None: # return [X_s,X_ss,X_sss] # else: # return [[X_s,X_ss,X_sss],[X_tt, X_stt]] return [[X_s,X_ss,X_sss],[X_tt, X_stt]]
def state_perturb(f_fun, g_fun, sigma, sigma2_correction=None): """ Compute the perturbation of a system in the form: $E_t f(s_t,x_t,s_{t+1},x_{t+1})$ $s_t = g(s_{t-1},x_{t-1},\\epsilon_t$ :param f_fun: list of derivatives of f [order0, order1, order2, ...] :param g_fun: list of derivatives of g [order0, order1, order2, ...] """ import numpy as np from dolo.numeric.extern.qz import qzordered from numpy.linalg import solve approx_order = len(f_fun) - 1 # order of approximation [f0,f1] = f_fun[:2] [g0,g1] = g_fun[:2] n_x = f1.shape[0] # number of controls n_s = f1.shape[1]/2 - n_x # number of states n_e = g1.shape[1] - n_x - n_s n_v = n_s + n_x f_s = f1[:,:n_s] f_x = f1[:,n_s:n_s+n_x] f_snext = f1[:,n_v:n_v+n_s] f_xnext = f1[:,n_v+n_s:] g_s = g1[:,:n_s] g_x = g1[:,n_s:n_s+n_x] g_e = g1[:,n_v:] A = np.row_stack([ np.column_stack( [ np.eye(n_s), np.zeros((n_s,n_x)) ] ), np.column_stack( [ -f_snext , -f_xnext ] ) ]) B = np.row_stack([ np.column_stack( [ g_s, g_x ] ), np.column_stack( [ f_s, f_x ] ) ]) [S,T,Q,Z,eigval] = qzordered(A,B,n_s) Z11 = Z[:n_s,:n_s] Z12 = Z[:n_s,n_s:] Z21 = Z[n_s:,:n_s] Z22 = Z[n_s:,n_s:] S11 = S[:n_s,:n_s] T11 = T[:n_s,:n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T , solve(Z11.T, T11.T).T ) Q = g_e if False: from numpy import dot test = f_s + dot(f_x,C) + dot( f_snext, g_s + dot(g_x,C) ) + dot(f_xnext, dot( C, g_s + dot(g_x,C) ) ) print('Error: ' + str(abs(test).max())) if approx_order == 1: return [C] # second order solution from dolo.numeric.tensor import sdot, mdot from numpy import dot from dolo.numeric.matrix_equations import solve_sylvester f2 = f_fun[2] g2 = g_fun[2] g_ss = g2[:,:n_s,:n_s] g_sx = g2[:,:n_s,n_s:n_v] g_xx = g2[:,n_s:n_v,n_s:n_v] X_s = C V1_3 = g_s + dot(g_x,X_s) V1 = np.row_stack([ np.eye(n_s), X_s, V1_3, dot( X_s, V1_3 ) ]) K2 = g_ss + 2 * sdot(g_sx,X_s) + mdot(g_xx,[X_s,X_s]) #L2 = A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) B = f_xnext C = V1_3 D = mdot(f2,[V1,V1]) + sdot(f_snext + dot(f_xnext,X_s),K2) X_ss = solve_sylvester(A,B,C,D) # test = sdot( A, X_ss ) + sdot( B, mdot(X_ss,[V1_3,V1_3]) ) + D if not sigma == None: g_ee = g2[:,n_v:,n_v:] v = np.row_stack([ g_e, dot(X_s,g_e) ]) K_tt = mdot( f2[:,n_v:,n_v:], [v,v] ) K_tt += sdot( f_snext + dot(f_xnext,X_s) , g_ee ) K_tt += mdot( sdot( f_xnext, X_ss), [g_e, g_e] ) K_tt = np.tensordot( K_tt, sigma, axes=((1,2),(0,1))) if sigma2_correction is not None: K_tt += sdot( f_snext + dot(f_xnext,X_s) , sigma2_correction[0] ) L_tt = f_x + dot(f_snext, g_x) + dot(f_xnext, dot(X_s, g_x) + np.eye(n_x) ) from numpy.linalg import det X_tt = solve( L_tt, - K_tt) if approx_order == 2: if sigma == None: return [X_s,X_ss] # here, we don't approximate the law of motion of the states else: return [[X_s,X_ss],[X_tt]] # here, we don't approximate the law of motion of the states # third order solution f3 = f_fun[3] g3 = g_fun[3] g_sss = g3[:,:n_s,:n_s,:n_s] g_ssx = g3[:,:n_s,:n_s,n_s:n_v] g_sxx = g3[:,:n_s,n_s:n_v,n_s:n_v] g_xxx = g3[:,n_s:n_v,n_s:n_v,n_s:n_v] V2_3 = K2 + sdot(g_x,X_ss) V2 = np.row_stack([ np.zeros( (n_s,n_s,n_s) ), X_ss, V2_3, dot( X_s, V2_3 ) + mdot(X_ss,[V1_3,V1_3]) ]) K3 = g_sss + 3*sdot(g_ssx,X_s) + 3*mdot(g_sxx,[X_s,X_s]) + 2*sdot(g_sx,X_ss) K3 += 3*mdot( g_xx,[X_ss,X_s] ) + mdot(g_xxx,[X_s,X_s,X_s]) L3 = 3*mdot(X_ss,[V1_3,V2_3]) # A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before # B = f_xnext # same # C = V1_3 # same D = mdot(f3,[V1,V1,V1]) + 3*mdot(f2,[ V2,V1 ]) + sdot(f_snext + dot(f_xnext,X_s),K3) D += sdot( f_xnext, L3 ) X_sss = solve_sylvester(A,B,C,D) # now doing sigma correction with sigma replaced by l in the subscripts if not sigma is None: g_se= g2[:,:n_s,n_v:] g_xe= g2[:,n_s:n_v,n_v:] g_see= g3[:,:n_s,n_v:,n_v:] g_xee= g3[:,n_s:n_v,n_v:,n_v:] W_l = np.row_stack([ g_e, dot(X_s,g_e) ]) I_e = np.eye(n_e) V_sl = g_se + mdot( g_xe, [X_s, np.eye(n_e)]) W_sl = np.row_stack([ V_sl, mdot( X_ss, [ V1_3, g_e ] ) + sdot( X_s, V_sl) ]) K_ee = mdot(f3[:,:,n_v:,n_v:], [V1, W_l, W_l ]) K_ee += 2 * mdot( f2[:,n_v:,n_v:], [W_sl, W_l]) # stochastic part of W_ll SW_ll = np.row_stack([ g_ee, mdot(X_ss, [g_e, g_e]) + sdot(X_s, g_ee) ]) DW_ll = np.concatenate([ X_tt, dot(g_x, X_tt), dot(X_s, sdot(g_x,X_tt )) + X_tt ]) K_ee += mdot( f2[:,:,n_v:], [V1, SW_ll]) K_ = np.tensordot(K_ee, sigma, axes=((2,3),(0,1))) K_ += mdot(f2[:,:,n_s:], [V1, DW_ll]) def E(vec): n = len(vec.shape) return np.tensordot(vec,sigma,axes=((n-2,n-1),(0,1))) L = sdot(g_sx,X_tt) + mdot(g_xx,[X_s,X_tt]) L += E(g_see + mdot(g_xee,[X_s,I_e,I_e]) ) M = E( mdot(X_sss,[V1_3, g_e, g_e]) + 2*mdot(X_ss, [V_sl,g_e]) ) M += mdot( X_ss, [V1_3, E( g_ee ) + sdot(g_x, X_tt)] ) A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before B = f_xnext # same C = V1_3 # same D = K_ + dot( f_snext + dot(f_xnext,X_s), L) + dot( f_xnext, M ) if sigma2_correction is not None: g_sl = sigma2_correction[1][:,:n_s] g_xl = sigma2_correction[1][:,n_s:(n_s+n_x)] D += dot( f_snext + dot(f_xnext,X_s), g_sl + dot(g_xl,X_s) ) # constant X_stt = solve_sylvester(A,B,C,D) if approx_order == 3: if sigma is None: return [X_s,X_ss,X_sss] else: return [[X_s,X_ss,X_sss],[X_tt, X_stt]]
def state_perturb(f_fun, g_fun, sigma, sigma2_correction=None, verbose=True): """Computes a Taylor approximation of decision rules, given the supplied derivatives. The original system is assumed to be in the the form: .. math:: E_t f(s_t,x_t,s_{t+1},x_{t+1}) s_t = g(s_{t-1},x_{t-1}, \\lambda \\epsilon_t) where :math:`\\lambda` is a scalar scaling down the risk. the solution is a function :math:`\\varphi` such that: .. math:: x_t = \\varphi ( s_t, \\sigma ) The user supplies, a list of derivatives of f and g. :param f_fun: list of derivatives of f [order0, order1, order2, ...] :param g_fun: list of derivatives of g [order0, order1, order2, ...] :param sigma: covariance matrix of :math:`\\epsilon_t` :param sigma2_correction: (optional) first and second derivatives of g w.r.t. sigma if :math:`g` explicitely depends :math:`sigma` Assuming :math:`s_t` , :math:`x_t` and :math:`\\epsilon_t` are vectors of size :math:`n_s`, :math:`n_x` and :math:`n_x` respectively. In general the derivative of order :math:`i` of :math:`f` is a multimensional array of size :math:`n_x \\times (N, ..., N)` with :math:`N=2(n_s+n_x)` repeated :math:`i` times (possibly 0). Similarly the derivative of order :math:`i` of :math:`g` is a multidimensional array of size :math:`n_s \\times (M, ..., M)` with :math:`M=n_s+n_x+n_2` repeated :math:`i` times (possibly 0). """ import numpy as np from numpy.linalg import solve approx_order = len(f_fun) - 1 # order of approximation [f0, f1] = f_fun[:2] [g0, g1] = g_fun[:2] n_x = f1.shape[0] # number of controls n_s = f1.shape[1] / 2 - n_x # number of states n_e = g1.shape[1] - n_x - n_s n_v = n_s + n_x f_s = f1[:, :n_s] f_x = f1[:, n_s:n_s + n_x] f_snext = f1[:, n_v:n_v + n_s] f_xnext = f1[:, n_v + n_s:] g_s = g1[:, :n_s] g_x = g1[:, n_s:n_s + n_x] g_e = g1[:, n_v:] A = np.row_stack([ np.column_stack([np.eye(n_s), np.zeros((n_s, n_x))]), np.column_stack([-f_snext, -f_xnext]) ]) B = np.row_stack( [np.column_stack([g_s, g_x]), np.column_stack([f_s, f_x])]) from dolo.numeric.extern.qz import qzordered [S, T, Q, Z, eigval] = qzordered(A, B, n_s) # Check Blanchard=Kahn conditions n_big_one = sum(eigval > 1.0) n_expected = n_x if verbose: print("There are {} eigenvalues greater than 1. Expected: {}.".format( n_big_one, n_x)) if n_big_one != n_expected: raise Exception( "There should be exactly {} eigenvalues greater than one. Not {}.". format(n_x, n_big_one)) Q = Q.real # is it really necessary ? Z = Z.real Z11 = Z[:n_s, :n_s] Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] Z22 = Z[n_s:, n_s:] S11 = S[:n_s, :n_s] T11 = T[:n_s, :n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T, solve(Z11.T, T11.T).T) Q = g_e if False: from numpy import dot test = f_s + dot(f_x, C) + dot(f_snext, g_s + dot(g_x, C)) + dot( f_xnext, dot(C, g_s + dot(g_x, C))) print('Error: ' + str(abs(test).max())) if approx_order == 1: return [C] # second order solution from dolo.numeric.tensor import sdot, mdot from numpy import dot from dolo.numeric.matrix_equations import solve_sylvester f2 = f_fun[2] g2 = g_fun[2] g_ss = g2[:, :n_s, :n_s] g_sx = g2[:, :n_s, n_s:n_v] g_xx = g2[:, n_s:n_v, n_s:n_v] X_s = C V1_3 = g_s + dot(g_x, X_s) V1 = np.row_stack([np.eye(n_s), X_s, V1_3, dot(X_s, V1_3)]) K2 = g_ss + 2 * sdot(g_sx, X_s) + mdot(g_xx, [X_s, X_s]) #L2 = A = f_x + dot(f_snext + dot(f_xnext, X_s), g_x) B = f_xnext C = V1_3 D = mdot(f2, [V1, V1]) + sdot(f_snext + dot(f_xnext, X_s), K2) X_ss = solve_sylvester(A, B, C, D) # test = sdot( A, X_ss ) + sdot( B, mdot(X_ss,[V1_3,V1_3]) ) + D if not sigma == None: g_ee = g2[:, n_v:, n_v:] v = np.row_stack([g_e, dot(X_s, g_e)]) K_tt = mdot(f2[:, n_v:, n_v:], [v, v]) K_tt += sdot(f_snext + dot(f_xnext, X_s), g_ee) K_tt += mdot(sdot(f_xnext, X_ss), [g_e, g_e]) K_tt = np.tensordot(K_tt, sigma, axes=((1, 2), (0, 1))) if sigma2_correction is not None: K_tt += sdot(f_snext + dot(f_xnext, X_s), sigma2_correction[0]) L_tt = f_x + dot(f_snext, g_x) + dot(f_xnext, dot(X_s, g_x) + np.eye(n_x)) X_tt = solve(L_tt, -K_tt) if approx_order == 2: if sigma == None: return [ X_s, X_ss ] # here, we don't approximate the law of motion of the states else: return [[X_s, X_ss], [ X_tt ]] # here, we don't approximate the law of motion of the states # third order solution f3 = f_fun[3] g3 = g_fun[3] g_sss = g3[:, :n_s, :n_s, :n_s] g_ssx = g3[:, :n_s, :n_s, n_s:n_v] g_sxx = g3[:, :n_s, n_s:n_v, n_s:n_v] g_xxx = g3[:, n_s:n_v, n_s:n_v, n_s:n_v] V2_3 = K2 + sdot(g_x, X_ss) V2 = np.row_stack([ np.zeros((n_s, n_s, n_s)), X_ss, V2_3, dot(X_s, V2_3) + mdot(X_ss, [V1_3, V1_3]) ]) K3 = g_sss + 3 * sdot(g_ssx, X_s) + 3 * mdot(g_sxx, [X_s, X_s]) + 2 * sdot( g_sx, X_ss) K3 += 3 * mdot(g_xx, [X_ss, X_s]) + mdot(g_xxx, [X_s, X_s, X_s]) L3 = 3 * mdot(X_ss, [V1_3, V2_3]) # A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before # B = f_xnext # same # C = V1_3 # same D = mdot(f3, [V1, V1, V1]) + 3 * mdot(f2, [V2, V1]) + sdot( f_snext + dot(f_xnext, X_s), K3) D += sdot(f_xnext, L3) X_sss = solve_sylvester(A, B, C, D) # now doing sigma correction with sigma replaced by l in the subscripts if not sigma is None: g_se = g2[:, :n_s, n_v:] g_xe = g2[:, n_s:n_v, n_v:] g_see = g3[:, :n_s, n_v:, n_v:] g_xee = g3[:, n_s:n_v, n_v:, n_v:] W_l = np.row_stack([g_e, dot(X_s, g_e)]) I_e = np.eye(n_e) V_sl = g_se + mdot(g_xe, [X_s, np.eye(n_e)]) W_sl = np.row_stack([V_sl, mdot(X_ss, [V1_3, g_e]) + sdot(X_s, V_sl)]) K_ee = mdot(f3[:, :, n_v:, n_v:], [V1, W_l, W_l]) K_ee += 2 * mdot(f2[:, n_v:, n_v:], [W_sl, W_l]) # stochastic part of W_ll SW_ll = np.row_stack([g_ee, mdot(X_ss, [g_e, g_e]) + sdot(X_s, g_ee)]) DW_ll = np.concatenate( [X_tt, dot(g_x, X_tt), dot(X_s, sdot(g_x, X_tt)) + X_tt]) K_ee += mdot(f2[:, :, n_v:], [V1, SW_ll]) K_ = np.tensordot(K_ee, sigma, axes=((2, 3), (0, 1))) K_ += mdot(f2[:, :, n_s:], [V1, DW_ll]) def E(vec): n = len(vec.shape) return np.tensordot(vec, sigma, axes=((n - 2, n - 1), (0, 1))) L = sdot(g_sx, X_tt) + mdot(g_xx, [X_s, X_tt]) L += E(g_see + mdot(g_xee, [X_s, I_e, I_e])) M = E(mdot(X_sss, [V1_3, g_e, g_e]) + 2 * mdot(X_ss, [V_sl, g_e])) M += mdot(X_ss, [V1_3, E(g_ee) + sdot(g_x, X_tt)]) A = f_x + dot(f_snext + dot(f_xnext, X_s), g_x) # same as before B = f_xnext # same C = V1_3 # same D = K_ + dot(f_snext + dot(f_xnext, X_s), L) + dot(f_xnext, M) if sigma2_correction is not None: g_sl = sigma2_correction[1][:, :n_s] g_xl = sigma2_correction[1][:, n_s:(n_s + n_x)] D += dot(f_snext + dot(f_xnext, X_s), g_sl + dot(g_xl, X_s)) # constant X_stt = solve_sylvester(A, B, C, D) if approx_order == 3: if sigma is None: return [X_s, X_ss, X_sss] else: return [[X_s, X_ss, X_sss], [X_tt, X_stt]]
def approximate_controls(model, verbose=False, steady_state=None, eigmax=1.0-1e-6, solve_steady_state=False, order=1): """Compute first order approximation of optimal controls Parameters: ----------- model: NumericModel Model to be solved verbose: boolean If True: displays number of contracting eigenvalues steady_state: ndarray Use supplied steady-state value to compute the approximation. The routine doesn't check whether it is really a solution or not. solve_steady_state: boolean Use nonlinear solver to find the steady-state orders: {1} Approximation order. (Currently, only first order is supported). Returns: -------- TaylorExpansion: Decision Rule for the optimal controls around the steady-state. """ if order > 1: raise Exception("Not implemented.") f = model.functions['arbitrage'] g = model.functions['transition'] if steady_state is not None: calib = steady_state else: calib = model.calibration if solve_steady_state: calib = find_deterministic_equilibrium(model) p = calib['parameters'] s = calib['states'] x = calib['controls'] e = calib['shocks'] distrib = model.get_distribution() sigma = distrib.sigma l = g(s, x, e, p, diff=True) [junk, g_s, g_x, g_e] = l[:4] # [el[0,...] for el in l[:4]] l = f(s, x, e, s, x, p, diff=True) [res, f_s, f_x, f_e, f_S, f_X] = l # [el[0,...] for el in l[:6]] n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_e = g_e.shape[1] n_v = n_s + n_x A = row_stack([ column_stack([eye(n_s), zeros((n_s, n_x))]), column_stack([-f_S , -f_X ]) ]) B = row_stack([ column_stack([g_s, g_x]), column_stack([f_s, f_x]) ]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0-1e-8) Q = Q.real # is it really necessary ? Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 try: ok = sum((abs(diag_S) < tol_geneigvals) * (abs(diag_T) < tol_geneigvals)) == 0 assert(ok) except Exception as e: raise GeneralizedEigenvaluesError(diag_S=diag_S, diag_T=diag_T) if max(eigval[:n_s]) >= 1 and min(eigval[n_s:]) < 1: # BK conditions are met pass else: eigval_s = sorted(eigval, reverse=True) ev_a = eigval_s[n_s-1] ev_b = eigval_s[n_s] cutoff = (ev_a - ev_b)/2 if not ev_a > ev_b: raise GeneralizedEigenvaluesSelectionError( A=A, B=B, eigval=eigval, cutoff=cutoff, diag_S=diag_S, diag_T=diag_T, n_states=n_s ) import warnings if cutoff > 1: warnings.warn("Solution is not convergent.") else: warnings.warn("There are multiple convergent solutions. The one with the smaller eigenvalues was selected.") [S, T, Q, Z, eigval] = qzordered(A, B, cutoff) Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] # S11 = S[:n_s, :n_s] # T11 = T[:n_s, :n_s] # first order solution # P = (solve(S11.T, Z11.T).T @ solve(Z11.T, T11.T).T) C = solve(Z11.T, Z21.T).T Q = g_e s = s.ravel() x = x.ravel() A = g_s + g_x @ C B = g_e dr = CDR([s, x, C]) dr.A = A dr.B = B dr.sigma = sigma return dr
def approximate_controls(model, verbose=False, steady_state=None, eigmax=1.0 - 1e-6, solve_steady_state=False, order=1): """Compute first order approximation of optimal controls Parameters: ----------- model: NumericModel Model to be solved verbose: boolean If True: displays number of contracting eigenvalues steady_state: ndarray Use supplied steady-state value to compute the approximation. The routine doesn't check whether it is really a solution or not. solve_steady_state: boolean Use nonlinear solver to find the steady-state orders: {1} Approximation order. (Currently, only first order is supported). Returns: -------- TaylorExpansion: Decision Rule for the optimal controls around the steady-state. """ if order > 1: raise Exception("Not implemented.") f = model.functions['arbitrage'] g = model.functions['transition'] if steady_state is not None: calib = steady_state else: calib = model.calibration if solve_steady_state: calib = find_deterministic_equilibrium(model) p = calib['parameters'] s = calib['states'] x = calib['controls'] e = calib['shocks'] distrib = model.get_distribution() sigma = distrib.sigma l = g(s, x, e, p, diff=True) [junk, g_s, g_x, g_e] = l[:4] # [el[0,...] for el in l[:4]] l = f(s, x, e, s, x, p, diff=True) [res, f_s, f_x, f_e, f_S, f_X] = l # [el[0,...] for el in l[:6]] n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_e = g_e.shape[1] n_v = n_s + n_x A = row_stack([ column_stack([eye(n_s), zeros((n_s, n_x))]), column_stack([-f_S, -f_X]) ]) B = row_stack([column_stack([g_s, g_x]), column_stack([f_s, f_x])]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0 - 1e-8) Q = Q.real # is it really necessary ? Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 try: ok = sum((abs(diag_S) < tol_geneigvals) * (abs(diag_T) < tol_geneigvals)) == 0 assert (ok) except Exception as e: raise GeneralizedEigenvaluesError(diag_S=diag_S, diag_T=diag_T) if max(eigval[:n_s]) >= 1 and min(eigval[n_s:]) < 1: # BK conditions are met pass else: eigval_s = sorted(eigval, reverse=True) ev_a = eigval_s[n_s - 1] ev_b = eigval_s[n_s] cutoff = (ev_a - ev_b) / 2 if not ev_a > ev_b: raise GeneralizedEigenvaluesSelectionError(A=A, B=B, eigval=eigval, cutoff=cutoff, diag_S=diag_S, diag_T=diag_T, n_states=n_s) import warnings if cutoff > 1: warnings.warn("Solution is not convergent.") else: warnings.warn( "There are multiple convergent solutions. The one with the smaller eigenvalues was selected." ) [S, T, Q, Z, eigval] = qzordered(A, B, cutoff) Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] # S11 = S[:n_s, :n_s] # T11 = T[:n_s, :n_s] # first order solution # P = (solve(S11.T, Z11.T).T @ solve(Z11.T, T11.T).T) C = solve(Z11.T, Z21.T).T Q = g_e s = s.ravel() x = x.ravel() A = g_s + g_x @ C B = g_e dr = CDR([s, x, C]) dr.A = A dr.B = B dr.sigma = sigma return dr
def state_perturb(problem: PerturbationProblem, verbose=True): """Computes a Taylor approximation of decision rules, given the supplied derivatives. The original system is assumed to be in the the form: .. math:: E_t f(s_t,x_t,s_{t+1},x_{t+1}) s_t = g(s_{t-1},x_{t-1}, \\lambda \\epsilon_t) where :math:`\\lambda` is a scalar scaling down the risk. the solution is a function :math:`\\varphi` such that: .. math:: x_t = \\varphi ( s_t, \\sigma ) The user supplies, a list of derivatives of f and g. :param f_fun: list of derivatives of f [order0, order1, order2, ...] :param g_fun: list of derivatives of g [order0, order1, order2, ...] :param sigma: covariance matrix of :math:`\\epsilon_t` Assuming :math:`s_t` , :math:`x_t` and :math:`\\epsilon_t` are vectors of size :math:`n_s`, :math:`n_x` and :math:`n_x` respectively. In general the derivative of order :math:`i` of :math:`f` is a multimensional array of size :math:`n_x \\times (N, ..., N)` with :math:`N=2(n_s+n_x)` repeated :math:`i` times (possibly 0). Similarly the derivative of order :math:`i` of :math:`g` is a multidimensional array of size :math:`n_s \\times (M, ..., M)` with :math:`M=n_s+n_x+n_2` repeated :math:`i` times (possibly 0). """ import numpy as np from numpy.linalg import solve approx_order = problem.order # order of approximation [f0, f1] = problem.f[:2] [g0, g1] = problem.g[:2] sigma = problem.sigma n_x = f1.shape[0] # number of controls n_s = f1.shape[1]//2 - n_x # number of states n_e = g1.shape[1] - n_x - n_s n_v = n_s + n_x f_s = f1[:, :n_s] f_x = f1[:, n_s:n_s+n_x] f_snext = f1[:, n_v:n_v+n_s] f_xnext = f1[:, n_v+n_s:] g_s = g1[:, :n_s] g_x = g1[:, n_s:n_s+n_x] g_e = g1[:, n_v:] A = np.row_stack([ np.column_stack([np.eye(n_s), np.zeros((n_s, n_x))]), np.column_stack([-f_snext , -f_xnext ]) ]) B = np.row_stack([ np.column_stack([g_s, g_x]), np.column_stack([f_s, f_x]) ]) [S, T, Q, Z, eigval] = qzordered(A, B, 1.0-1e-8) Q = Q.real # is it really necessary ? Z = Z.real diag_S = np.diag(S) diag_T = np.diag(T) tol_geneigvals = 1e-10 try: ok = sum((abs(diag_S) < tol_geneigvals) * (abs(diag_T) < tol_geneigvals)) == 0 assert(ok) except Exception as e: raise GeneralizedEigenvaluesError(diag_S=diag_S, diag_T=diag_T) if max(eigval[:n_s]) >= 1 and min(eigval[n_s:]) < 1: # BK conditions are met pass else: eigval_s = sorted(eigval, reverse=True) ev_a = eigval_s[n_s-1] ev_b = eigval_s[n_s] cutoff = (ev_a - ev_b)/2 if not ev_a > ev_b: raise GeneralizedEigenvaluesSelectionError( A=A, B=B, eigval=eigval, cutoff=cutoff, diag_S=diag_S, diag_T=diag_T, n_states=n_s ) import warnings if cutoff > 1: warnings.warn("Solution is not convergent.") else: warnings.warn("There are multiple convergent solutions. The one with the smaller eigenvalues was selected.") [S, T, Q, Z, eigval] = qzordered(A, B, cutoff) Z11 = Z[:n_s, :n_s] # Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] # Z22 = Z[n_s:, n_s:] S11 = S[:n_s, :n_s] T11 = T[:n_s, :n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T, solve(Z11.T, T11.T).T) Q = g_e # if False: # from numpy import dot # test = f_s + f_x @ C + f_snext @ (g_s + g_x @ C) + f_xnext @ C @ (g_s + g_x @ C) # print('Error: ' + str(abs(test).max())) if approx_order == 1: return [C] # second order solution from dolo.numeric.tensor import sdot, mdot from numpy import dot from dolo.numeric.matrix_equations import solve_sylvester f2 = problem.f[2] g2 = problem.g[2] g_ss = g2[:, :n_s, :n_s] g_sx = g2[:, :n_s, n_s:n_v] g_xx = g2[:, n_s:n_v, n_s:n_v] X_s = C V1_3 = g_s + dot(g_x, X_s) V1 = np.row_stack([ np.eye(n_s), X_s, V1_3, X_s @ V1_3 ]) K2 = g_ss + 2 * sdot(g_sx, X_s) + mdot(g_xx, X_s, X_s) A = f_x + dot(f_snext + dot(f_xnext, X_s), g_x) B = f_xnext C = V1_3 D = mdot(f2, V1, V1) + sdot(f_snext + dot(f_xnext, X_s), K2) X_ss = solve_sylvester(A, B, C, D) # test = sdot( A, X_ss ) + sdot( B, mdot(X_ss,V1_3,V1_3) ) + D g_ee = g2[:, n_v:, n_v:] v = np.row_stack([ g_e, dot(X_s, g_e) ]) K_tt = mdot(f2[:, n_v:, n_v:], v, v) K_tt += sdot(f_snext + dot(f_xnext, X_s), g_ee) K_tt += mdot(sdot(f_xnext, X_ss), g_e, g_e) K_tt = np.tensordot(K_tt, sigma, axes=((1, 2), (0, 1))) L_tt = f_x + dot(f_snext, g_x) + dot(f_xnext, dot(X_s, g_x) + np.eye(n_x)) X_tt = solve(L_tt, - K_tt) if approx_order == 2: return [[X_s, X_ss], [X_tt]] # third order solution f3 = problem.f[3] g3 = problem.g[3] g_sss = g3[:, :n_s, :n_s, :n_s] g_ssx = g3[:, :n_s, :n_s, n_s:n_v] g_sxx = g3[:, :n_s, n_s:n_v, n_s:n_v] g_xxx = g3[:, n_s:n_v, n_s:n_v, n_s:n_v] V2_3 = K2 + sdot(g_x, X_ss) V2 = np.row_stack([ np.zeros((n_s, n_s, n_s)), X_ss, V2_3, dot(X_s, V2_3) + mdot(X_ss, V1_3, V1_3) ]) K3 = g_sss + 3*sdot(g_ssx, X_s) + 3*mdot(g_sxx, X_s, X_s) + 2*sdot(g_sx, X_ss) K3 += 3*mdot(g_xx, X_ss, X_s) + mdot(g_xxx, X_s, X_s, X_s) L3 = 3*mdot(X_ss, V1_3, V2_3) # A = f_x + dot( f_snext + dot(f_xnext,X_s), g_x) # same as before # B = f_xnext # same # C = V1_3 # same D = mdot(f3, V1, V1, V1) + 3*mdot(f2, V2, V1) + sdot(f_snext + dot(f_xnext, X_s), K3) D += sdot(f_xnext, L3) X_sss = solve_sylvester(A, B, C, D) # now doing sigma correction with sigma replaced by l in the subscripts g_se = g2[:, :n_s, n_v:] g_xe = g2[:, n_s:n_v, n_v:] g_see = g3[:, :n_s, n_v:, n_v:] g_xee = g3[:, n_s:n_v, n_v:, n_v:] W_l = np.row_stack([ g_e, dot(X_s, g_e) ]) I_e = np.eye(n_e) V_sl = g_se + mdot(g_xe, X_s, np.eye(n_e)) W_sl = np.row_stack([ V_sl, mdot(X_ss, V1_3, g_e) + sdot(X_s, V_sl) ]) K_ee = mdot(f3[:, :, n_v:, n_v:], V1, W_l, W_l) K_ee += 2 * mdot(f2[:, n_v:, n_v:], W_sl, W_l) # stochastic part of W_ll SW_ll = np.row_stack([ g_ee, mdot(X_ss, g_e, g_e) + sdot(X_s, g_ee) ]) DW_ll = np.concatenate([ X_tt, dot(g_x, X_tt), dot(X_s, sdot(g_x, X_tt)) + X_tt ]) K_ee += mdot(f2[:, :, n_v:], V1, SW_ll) K_ = np.tensordot(K_ee, sigma, axes=((2,3), (0,1))) K_ += mdot(f2[:, :, n_s:], V1, DW_ll) def E(vec): n = len(vec.shape) return np.tensordot(vec, sigma, axes=((n-2, n-1), (0, 1))) L = sdot(g_sx, X_tt) + mdot(g_xx, X_s, X_tt) L += E(g_see + mdot(g_xee, X_s, I_e, I_e)) M = E(mdot(X_sss, V1_3, g_e, g_e) + 2*mdot(X_ss, V_sl, g_e)) M += mdot(X_ss, V1_3, E(g_ee) + sdot(g_x, X_tt)) A = f_x + dot(f_snext + dot(f_xnext, X_s), g_x) # same as before B = f_xnext # same C = V1_3 # same D = K_ + dot(f_snext + dot(f_xnext, X_s), L) + dot(f_xnext, M) X_stt = solve_sylvester(A, B, C, D) if approx_order == 3: # if sigma is None: # return [X_s,X_ss,X_sss] # else: # return [[X_s,X_ss,X_sss],[X_tt, X_stt]] return [[X_s, X_ss, X_sss], [X_tt, X_stt]]
def approximate_controls(model, return_dr=True): # get steady_state import numpy p = model.calibration['parameters'] sigma = model.calibration['covariances'] s = model.calibration['states'][:, None] x = model.calibration['controls'][:, None] e = model.calibration['shocks'][:, None] from numpy.linalg import solve g = model.functions['transition'] f = model.functions['arbitrage'] l = g(s, x, e, p, derivs=True) [junk, g_s, g_x, g_e] = [el[..., 0] for el in l] if model.model_type == "fg2": l = f(s, x, e, s, x, p, derivs=True) [res, f_s, f_x, f_e, f_S, f_X] = [el[..., 0] for el in l] else: l = f(s, x, s, x, p, derivs=True) [res, f_s, f_x, f_S, f_X] = [el[..., 0] for el in l] n_s = g_s.shape[0] # number of controls n_x = g_x.shape[1] # number of states n_e = g_e.shape[1] n_v = n_s + n_x A = row_stack([ column_stack([eye(n_s), zeros((n_s, n_x))]), column_stack([-f_S, -f_X]) ]) B = row_stack([column_stack([g_s, g_x]), column_stack([f_s, f_x])]) from dolo.numeric.extern.qz import qzordered [S, T, Q, Z, eigval] = qzordered(A, B, n_s) Q = Q.real # is it really necessary ? Z = Z.real Z11 = Z[:n_s, :n_s] Z12 = Z[:n_s, n_s:] Z21 = Z[n_s:, :n_s] Z22 = Z[n_s:, n_s:] S11 = S[:n_s, :n_s] T11 = T[:n_s, :n_s] # first order solution C = solve(Z11.T, Z21.T).T P = np.dot(solve(S11.T, Z11.T).T, solve(Z11.T, T11.T).T) Q = g_e s = s.ravel() x = x.ravel() A = g_s + dot(g_x, C) B = g_e dr = CDR([s, x, C]) dr.A = A dr.B = B dr.sigma = sigma return dr