def get_sys(self, par=None, reduce_sys=None, l_max=None, k_max=None, linear=False, tol=1e-8, ignore_tests=False, verbose=False): """Creates the transition function given a set of parameters. If no parameters are given this will default to the calibration in the `yaml` file. Parameters ---------- par : array or list, optional The parameters to parse into the transition function. (defaults to calibration in `yaml`) reduce_sys : bool, optional If true, the state space is reduced. This speeds up computation. l_max : int, optional The expected number of periods *until* the constraint binds (defaults to 3). k_max : int, optional The expected number of periods for which the constraint binds (defaults to 17). """ st = time.time() reduce_sys = reduce_sys if reduce_sys is not None else self.fdict.get( 'reduce_sys') ignore_tests = ignore_tests if ignore_tests is not None else self.fdict.get( 'ignore_tests') if l_max is not None: if l_max < 2: print('[get_sys:]'.ljust(15, ' ') + ' `l_max` must be at least 2 (is %s). Correcting...' % l_max) l_max = 2 # effective l_max is one lower because algorithm exists on l_max l_max += 1 elif hasattr(self, 'lks'): l_max = self.lks[0] else: l_max = 1 if linear else 3 if k_max is not None: pass elif hasattr(self, 'lks'): k_max = self.lks[1] else: k_max = 0 if linear else 17 self.lks = [l_max, k_max] self.fdict['reduce_sys'] = reduce_sys self.fdict['ignore_tests'] = ignore_tests par = self.p0() if par is None else list(par) try: ppar = self.pcompile(par) # parsed par except AttributeError: ppar = self.compile(par) # parsed par self.par = par self.ppar = ppar if not self.const_var: raise NotImplementedError('Package is only meant to work with OBCs') vv_v = np.array([v.name for v in self.variables]) vv_x = np.array(self.variables) dim_v = len(vv_v) # obtain matrices AA = self.AA(ppar) # forward BB = self.BB(ppar) # contemp CC = self.CC(ppar) # backward bb = self.bb(ppar).flatten().astype(float) # constraint # define transition shocks -> state D = self.PSI(ppar) # mask those vars that are either forward looking or part of the constraint in_x = ~fast0(AA, 0) | ~fast0(bb[:dim_v]) # reduce x vector vv_x2 = vv_x[in_x] A1 = AA[:, in_x] b1 = np.hstack((bb[:dim_v][in_x], bb[dim_v:])) dim_x = len(vv_x2) # define actual matrices N = np.block([[np.zeros(A1.shape), CC], [np.eye(dim_x), np.zeros((dim_x, dim_v))]]) P = np.block([[-A1, -BB], [np.zeros((dim_x, dim_x)), np.eye(dim_v)[in_x]]]) c_arg = list(vv_x2).index(self.const_var) # c contains information on how the constraint var affects the system c1 = N[:, c_arg] c_P = P[:, c_arg] # get rid of constrained var b2 = np.delete(b1, c_arg) N1 = np.delete(N, c_arg, 1) P1 = np.delete(P, c_arg, 1) vv_x3 = np.delete(vv_x2, c_arg) dim_x = len(vv_x3) M1 = N1 + np.outer(c1, b2) # solve using Klein's method OME = re_bk(M1, P1, d_endo=dim_x) J = np.hstack((np.eye(dim_x), -OME)) # desingularization of P U, s, V = nl.svd(P1) s0 = s < tol P2 = U.T @ P1 N2 = U.T @ N1 c2 = U.T @ c1 # actual desingularization by iterating equations in M forward P2[s0] = N2[s0] # I could possible create auxiallary variables to make this work. Or I get the stuff directly from the boehlgo if not fast0(c2[s0], 2) or not fast0(U.T[s0] @ c_P, 2): raise NotImplementedError( 'The system depends directly or indirectly on whether the constraint holds in the future or not.\n' ) if verbose > 1: print('[get_sys:]'.ljust(15, ' ') + ' determinant of `P` is %1.2e.' % nl.det(P2)) if 'x_bar' in [p.name for p in self.parameters]: x_bar = par[[p.name for p in self.parameters].index('x_bar')] elif 'x_bar' in self.parafunc[0]: pf = self.parafunc x_bar = pf[1](par)[pf[0].index('x_bar')] else: print( "Parameter `x_bar` (maximum value of the constraint) not specified. Assuming x_bar = -1 for now." ) x_bar = -1 try: cx = nl.inv(P2) @ c2 * x_bar except ParafuncError: raise SyntaxError( "At least one parameter is a function of other parameters, and should be declared in `parafunc`." ) # create the stuff that the algorithm needs N = nl.inv(P2) @ N2 A = nl.inv(P2) @ (N2 + np.outer(c2, b2)) out_msk = fast0(N, 0) & fast0(A, 0) & fast0(b2) & fast0(cx) out_msk[-len(vv_v):] = out_msk[-len(vv_v):] & fast0(self.ZZ(ppar), 0) # store those that are/could be reduced self.out_msk = out_msk[-len(vv_v):].copy() if not reduce_sys: out_msk[-len(vv_v):] = False s_out_msk = out_msk[-len(vv_v):] if hasattr(self, 'P'): if self.P.shape[0] < sum(~s_out_msk): P_new = np.zeros((len(self.out_msk), len(self.out_msk))) if P_new[~self.out_msk][:, ~self.out_msk].shape != self.P.shape: print( '[get_sys:]'.ljust(15, ' ') + ' Shape missmatch of P-matrix, number of states seems to differ!' ) P_new[~self.out_msk][:, ~self.out_msk] = self.P self.P = P_new elif self.P.shape[0] > sum(~s_out_msk): self.P = self.P[~s_out_msk][:, ~s_out_msk] # add everything to the DSGE object self.vv = vv_v[~s_out_msk] self.vx = np.array([v.name for v in vv_x3]) self.dim_x = dim_x self.dim_v = len(self.vv) self.hx = self.ZZ(ppar)[:, ~s_out_msk], self.DD(ppar).squeeze() self.obs_arg = np.where(self.hx[0])[1] N2 = N[~out_msk][:, ~out_msk] A2 = A[~out_msk][:, ~out_msk] J2 = J[:, ~out_msk] self.SIG = (BB.T @ D)[~s_out_msk] self.sys = N2, A2, J2, cx[~out_msk], b2[~out_msk], x_bar if verbose: print('[get_sys:]'.ljust(15, ' ') + ' Creation of system matrices finished in %ss.' % np.round(time.time() - st, 3)) preprocess(self, self.lks[0], self.lks[1], verbose) if not ignore_tests: test_obj = self.precalc_mat[0][1, 0, 1] test_con = eig(test_obj[-test_obj.shape[1]:]) > 1 if test_con.any(): raise ValueError('Explosive dynamics detected: %s EV(s) > 1' % sum(test_con)) return
def get_sys(self, par=None, reduce_sys=None, verbose=False): st = time.time() if reduce_sys is None: try: reduce_sys = self.fdict['reduce_sys'] except KeyError: reduce_sys = False self.fdict['reduce_sys'] = reduce_sys if par is None: par = self.p0() if not self.const_var: raise NotImplementedError('Pakage is only meant to work with OBCs') vv_v = np.array([v.name for v in self.variables]) vv_x = np.array(self.variables) dim_v = len(vv_v) # obtain matrices from pydsge # this could be further accelerated by getting them directly from the equations in pydsge AA = self.AA(par) # forward BB = self.BB(par) # contemp CC = self.CC(par) # backward bb = self.bb(par).flatten() # constraint # the special case in which the constraint is just a cut-off of another variable requires b = bb.astype(float) # define transition shocks -> state D = self.PSI(par) # mask those vars that are either forward looking or part of the constraint in_x = ~fast0(AA, 0) | ~fast0(b[:dim_v]) # reduce x vector vv_x2 = vv_x[in_x] A1 = AA[:, in_x] b1 = np.hstack((b[:dim_v][in_x], b[dim_v:])) dim_x = len(vv_x2) # define actual matrices M = np.block([[np.zeros(A1.shape), CC], [np.eye(dim_x), np.zeros((dim_x, dim_v))]]) P = np.block([[A1, -BB], [np.zeros((dim_x, dim_x)), np.eye(dim_v)[in_x]]]) c_arg = list(vv_x2).index(self.const_var) # c contains information on how the constraint var affects the system c_M = M[:, c_arg] c_P = P[:, c_arg] # get rid of constrained var b2 = np.delete(b1, c_arg) M1 = np.delete(M, c_arg, 1) P1 = np.delete(P, c_arg, 1) vv_x3 = np.delete(vv_x2, c_arg) # decompose P in singular & nonsingular rows U, s, V = nl.svd(P1) s0 = fast0(s) P2 = np.diag(s) @ V M2 = U.T @ M1 c1 = U.T @ c_M if not fast0(c1[s0], 2) or not fast0(U.T[s0] @ c_P, 2): NotImplementedError( 'The system depends directly or indirectly on whether the constraint holds in the future or not.\n' ) # actual desingularization by iterating equations in M forward P2[s0] = M2[s0] if 'x_bar' in [p.name for p in self.parameters]: x_bar = par[[p.name for p in self.parameters].index('x_bar')] elif 'x_bar' in self.parafunc[0]: pf = self.parafunc x_bar = pf[1](par)[pf[0].index('x_bar')] else: print( "Parameter `x_bar` (maximum value of the constraint) not specified. Assuming x_bar = -1 for now." ) x_bar = -1 # create the stuff that the algorithm needs N = nl.inv(P2) @ M2 A = nl.inv(P2) @ (M2 + np.outer(c1, b2)) if sum(eig(A).round(3) >= 1) - len(vv_x3): raise ValueError('BC *not* satisfied.') dim_x = len(vv_x3) OME = re_bc(A, dim_x) J = np.hstack((np.eye(dim_x), -OME)) try: cx = nl.inv(P2) @ c1 * x_bar except ParafuncError: raise SyntaxError( "At least one parameter should rather be a function of parameters ('parafunc')..." ) # check condition: n1 = N[:dim_x, :dim_x] n3 = N[dim_x:, :dim_x] cc1 = cx[:dim_x] cc2 = cx[dim_x:] bb1 = b2[:dim_x] out_msk = fast0(N, 0) & fast0(A, 0) & fast0(b2) & fast0(cx) out_msk[-len(vv_v):] = out_msk[-len(vv_v):] & fast0(self.ZZ(par), 0) # store those that are/could be reduced self.out_msk = out_msk[-len(vv_v):].copy() if not reduce_sys: out_msk[-len(vv_v):] = False s_out_msk = out_msk[-len(vv_v):] if hasattr(self, 'P'): if self.P.shape[0] < sum(~s_out_msk): P_new = np.zeros((len(self.out_msk), len(self.out_msk))) if P_new[~self.out_msk][:, ~self.out_msk].shape != self.P.shape: print( '[get_sys:]'.ljust(15, ' ') + ' Shape missmatch of P-matrix, number of states seems to differ!' ) P_new[~self.out_msk][:, ~self.out_msk] = self.P self.P = P_new elif self.P.shape[0] > sum(~s_out_msk): self.P = self.P[~s_out_msk][:, ~s_out_msk] # add everything to the DSGE object self.vv = vv_v[~s_out_msk] self.vx = np.array([v.name for v in vv_x3]) self.dim_x = dim_x self.dim_v = len(self.vv) self.par = par self.hx = self.ZZ(par)[:, ~s_out_msk], self.DD(par).squeeze() self.obs_arg = np.where(self.hx[0])[1] N2 = N[~out_msk][:, ~out_msk] A2 = A[~out_msk][:, ~out_msk] J2 = J[:, ~out_msk] self.SIG = (BB.T @ D)[~s_out_msk] self.sys = N2, A2, J2, cx[~out_msk], b2[~out_msk], x_bar if verbose: print('[get_sys:]'.ljust(15, ' ') + 'Creation of system matrices finished in %ss.' % np.round(time.time() - st, 3)) return
def get_sys(self, par=None, reduce_sys=None, l_max=None, k_max=None, ignore_tests=False, verbose=False): """Creates the transition function given a set of parameters. If no parameters are given this will default to the calibration in the `yaml` file. Parameters ---------- par : array or list, optional The parameters to parse into the transition function. (defaults to calibration in `yaml`) reduce_sys : bool, optional If true, the state space is reduced. This speeds up computation. l_max : int, optional The expected number of periods *until* the constraint binds (defaults to 3). k_max : int, optional The expected number of periods for which the constraint binds (defaults to 17). """ st = time.time() reduce_sys = reduce_sys if reduce_sys is not None else self.fdict.get( 'reduce_sys') ignore_tests = ignore_tests if ignore_tests is not None else self.fdict.get( 'ignore_tests') l_max = 3 if l_max is None else l_max k_max = 17 if k_max is None else k_max self.fdict['reduce_sys'] = reduce_sys self.fdict['ignore_tests'] = ignore_tests par = self.p0() if par is None else list(par) ppar = self.compile(par) # parsed par if not self.const_var: raise NotImplementedError('Pakage is only meant to work with OBCs') vv_v = np.array([v.name for v in self.variables]) vv_x = np.array(self.variables) dim_v = len(vv_v) # obtain matrices from pydsge # this could be further accelerated by getting them directly from the equations in pydsge AA = self.AA(ppar) # forward BB = self.BB(ppar) # contemp CC = self.CC(ppar) # backward bb = self.bb(ppar).flatten() # constraint # the special case in which the constraint is just a cut-off of another variable requires b = bb.astype(float) # define transition shocks -> state D = self.PSI(ppar) # mask those vars that are either forward looking or part of the constraint in_x = ~fast0(AA, 0) | ~fast0(b[:dim_v]) # reduce x vector vv_x2 = vv_x[in_x] A1 = AA[:, in_x] b1 = np.hstack((b[:dim_v][in_x], b[dim_v:])) dim_x = len(vv_x2) # define actual matrices M = np.block([[np.zeros(A1.shape), CC], [np.eye(dim_x), np.zeros((dim_x, dim_v))]]) P = np.block([[A1, -BB], [np.zeros((dim_x, dim_x)), np.eye(dim_v)[in_x]]]) c_arg = list(vv_x2).index(self.const_var) # c contains information on how the constraint var affects the system c_M = M[:, c_arg] c_P = P[:, c_arg] # get rid of constrained var b2 = np.delete(b1, c_arg) M1 = np.delete(M, c_arg, 1) P1 = np.delete(P, c_arg, 1) vv_x3 = np.delete(vv_x2, c_arg) # decompose P in singular & nonsingular rows U, s, V = nl.svd(P1) s0 = fast0(s) P2 = np.diag(s) @ V M2 = U.T @ M1 c1 = U.T @ c_M if not fast0(c1[s0], 2) or not fast0(U.T[s0] @ c_P, 2): raise NotImplementedError( 'The system depends directly or indirectly on whether the constraint holds in the future or not.\n' ) # actual desingularization by iterating equations in M forward P2[s0] = M2[s0] if 'x_bar' in [p.name for p in self.parameters]: x_bar = par[[p.name for p in self.parameters].index('x_bar')] elif 'x_bar' in self.parafunc[0]: pf = self.parafunc x_bar = pf[1](par)[pf[0].index('x_bar')] else: print( "Parameter `x_bar` (maximum value of the constraint) not specified. Assuming x_bar = -1 for now." ) x_bar = -1 # create the stuff that the algorithm needs N = nl.inv(P2) @ M2 A = nl.inv(P2) @ (M2 + np.outer(c1, b2)) # rounding here to allow for small numeric errors during SVD & inversion if not ignore_tests: if sum(eig(A).round(3) < 1) > len(vv_v): raise ValueError( 'B-K condition *not* satisfied (too many EV < 1).') if sum(eig(A).round(3) >= 1) > len(vv_x3): raise ValueError( 'B-K condition *not* satisfied (too many EV > 1).') dim_x = len(vv_x3) OME = re_bc(A, dim_x) J = np.hstack((np.eye(dim_x), -OME)) try: cx = nl.inv(P2) @ c1 * x_bar except ParafuncError: raise SyntaxError( "At least one parameter is a function of other parameters, and should be declared in `parafunc`." ) # check condition: n1 = N[:dim_x, :dim_x] n3 = N[dim_x:, :dim_x] cc1 = cx[:dim_x] cc2 = cx[dim_x:] bb1 = b2[:dim_x] out_msk = fast0(N, 0) & fast0(A, 0) & fast0(b2) & fast0(cx) out_msk[-len(vv_v):] = out_msk[-len(vv_v):] & fast0(self.ZZ(ppar), 0) # store those that are/could be reduced self.out_msk = out_msk[-len(vv_v):].copy() if not reduce_sys: out_msk[-len(vv_v):] = False s_out_msk = out_msk[-len(vv_v):] if hasattr(self, 'P'): if self.P.shape[0] < sum(~s_out_msk): P_new = np.zeros((len(self.out_msk), len(self.out_msk))) if P_new[~self.out_msk][:, ~self.out_msk].shape != self.P.shape: print( '[get_sys:]'.ljust(15, ' ') + ' Shape missmatch of P-matrix, number of states seems to differ!' ) P_new[~self.out_msk][:, ~self.out_msk] = self.P self.P = P_new elif self.P.shape[0] > sum(~s_out_msk): self.P = self.P[~s_out_msk][:, ~s_out_msk] # add everything to the DSGE object self.vv = vv_v[~s_out_msk] self.vx = np.array([v.name for v in vv_x3]) self.dim_x = dim_x self.dim_v = len(self.vv) self.par = par self.ppar = ppar self.hx = self.ZZ(ppar)[:, ~s_out_msk], self.DD(ppar).squeeze() self.obs_arg = np.where(self.hx[0])[1] N2 = N[~out_msk][:, ~out_msk] A2 = A[~out_msk][:, ~out_msk] J2 = J[:, ~out_msk] self.SIG = (BB.T @ D)[~s_out_msk] self.sys = N2, A2, J2, cx[~out_msk], b2[~out_msk], x_bar if verbose: print('[get_sys:]'.ljust(15, ' ') + ' Creation of system matrices finished in %ss.' % np.round(time.time() - st, 3)) preprocess(self, l_max, k_max, verbose) test = self.precalc_mat[0][1, 0, 1] if not ignore_tests and (eig(test[-test.shape[1]:]) > 1).any(): raise ValueError('Explosive dynamics detected.') return