def timeshift(expr, variables, date): from sympy import Symbol from dolang import stringify d = { Symbol(stringify((v, 0))): Symbol(stringify((v, date))) for v in variables } return expr.subs(d)
def eval_formula(expr, dataframe=None, context=None): ''' expr: string Symbolic expression to evaluate. Example: `k(1)-delta*k-i` table: (optional) pandas dataframe Each column is a time series, which can be indexed with dolo notations. context: dict or CalibrationDict ''' if context is None: dd = {} # context dictionary elif isinstance(context, CalibrationDict): dd = context.flat.copy() else: dd = context.copy() # compat since normalize form for parameters doesn't match calib dict. for k in [*dd.keys()]: dd[stringify(k)] = dd[k] from numpy import log, exp dd['log'] = log dd['exp'] = exp if dataframe is not None: import pandas as pd tvariables = dataframe.columns for k in tvariables: if k in dd: dd[k + '_ss'] = dd[k] # steady-state value dd[stringify((k, 0))] = dataframe[k] for h in range(1, 3): # maximum number of lags dd[stringify((k, -h))] = dataframe[k].shift(h) dd[stringify((k, h))] = dataframe[k].shift(-h) dd['t'] = pd.Series(dataframe.index, index=dataframe.index) import ast expr_ast = ast.parse(expr).body[0].value # nexpr = StandardizeDatesSimple(tvariables).visit(expr_ast) print(tvariables) nexpr = normalize(expr_ast, variables=tvariables) expr = to_source(nexpr) res = eval(expr, dd) return res
def __init__(self, values, model=None): from dolang.symbolic import sanitize, stringify exogenous = model.symbols['exogenous'] states = model.symbols['states'] controls = model.symbols['controls'] parameters = model.symbols['parameters'] preamble = dict([(s, values[s]) for s in values.keys() if s not in controls]) equations = [values[s] for s in controls] variables = exogenous + states + controls + [*preamble.keys()] preamble_str = dict() for k in [*preamble.keys()]: v = preamble[k] if '(' not in k: vv = f'{k}(0)' else: vv = k preamble_str[stringify(vv)] = stringify(sanitize(v, variables)) # let's reorder the preamble from dolang.triangular_solver import get_incidence, triangular_solver incidence = get_incidence(preamble_str) sol = triangular_solver(incidence) kk = [*preamble_str.keys()] preamble_str = dict([(kk[k], preamble_str[kk[k]]) for k in sol]) equations = [ dolang.symbolic.sanitize(eq, variables) for eq in equations ] equations_strings = [ dolang.stringify(eq, variables) for eq in equations ] args = dict([('m', [(e, 0) for e in exogenous]), ('s', [(e, 0) for e in states]), ('p', [e for e in parameters])]) args = dict([(k, [stringify_symbol(e) for e in v]) for k, v in args.items()]) targets = [stringify_symbol((e, 0)) for e in controls] eqs = dict([(targets[i], eq) for i, eq in enumerate(equations_strings)]) fff = FlatFunctionFactory(preamble_str, eqs, args, 'custom_dr') fun, gufun = make_method_from_factory(fff) self.p = model.calibration['parameters'] self.exo_grid = model.exogenous.discretize() # this is never used self.endo_grid = model.get_grid() self.gufun = gufun
def projection(self): #, m: 'n_e', y: "n_y", p: "n_p"): if self.__projection__ is None: arguments_ = { # 'e': [(e,0) for e in self.model.symbols['exogenous']], # 's': [(e,0) for e in self.model.symbols['states']], # 'x': [(e,0) for e in self.model.symbols['controls']], 'm': [(e, 0) for e in self.symbols['exogenous']], 'y': [(e, 0) for e in self.symbols['aggregate']], 'p': self.symbols['parameters'] } vars = sum([[e[0] for e in h] for h in [*arguments_.values()][:-1]], []) arguments = { k: [dolang.symbolic.stringify_symbol(e) for e in v] for k, v in arguments_.items() } preamble = {} # for now projdefs = self.data.get('projection', {}) pkeys = [*projdefs.keys()] n_p = len(pkeys) equations = [ projdefs[v] for v in self.model.symbols['exogenous'][:n_p] ] equations = [ dolang.stringify(eq, variables=vars) for eq in equations ] content = {f'{pkeys[i]}_0': eq for i, eq in enumerate(equations)} fff = FlatFunctionFactory(preamble, content, arguments, 'equilibrium') fun = dolang.function_compiler.make_method_from_factory( fff, debug=self.debug) from dolang.vectorize import standard_function self.__projection__ = standard_function(fun[1], len(equations)) return self.__projection__
def ℰ(self): if self.__equilibrium__ is None: arguments_ = { 'e': [(e, 0) for e in self.model.symbols['exogenous']], 's': [(e, 0) for e in self.model.symbols['states']], 'x': [(e, 0) for e in self.model.symbols['controls']], 'm': [(e, 0) for e in self.symbols['exogenous']], 'y': [(e, 0) for e in self.symbols['aggregate']], 'p': self.symbols['parameters'] } vars = sum([[e[0] for e in h] for h in [*arguments_.values()][:-1]], []) arguments = { k: [dolang.symbolic.stringify_symbol(e) for e in v] for k, v in arguments_.items() } preamble = {} # for now equations = [ ("{}-({})".format(*(str(eq).split('='))) if '=' in eq else eq) for eq in self.data['equilibrium'] ] equations = [ dolang.stringify(eq, variables=vars) for eq in equations ] content = {f'eq_{i}': eq for i, eq in enumerate(equations)} fff = FlatFunctionFactory(preamble, content, arguments, 'equilibrium') fun = dolang.function_compiler.make_method_from_factory( fff, debug=self.debug) from dolang.vectorize import standard_function self.__equilibrium__ = standard_function(fun[1], len(equations)) return self.__equilibrium__
def model_to_fg(model, order=2): # compile f, g function at higher order all_variables = sum( [model.symbols[e] for e in model.symbols if e != 'parameters'], []) all_dvariables = ([(d, 0) for d in all_variables] + [(d, 1) for d in all_variables] + [(d, -1) for d in all_variables]) psyms = [(e, 0) for e in model.symbols['parameters']] if hasattr(model.symbolic, 'definitions'): definitions = model.symbolic.definitions else: definitions = {} d = dict() for k in definitions: v = parse_equation(definitions[k], all_variables, to_sympy=True) kk = stringify((k, 0)) kk_m1 = stringify((k, -1)) kk_1 = stringify((k, 1)) d[sympy.Symbol(kk)] = v d[sympy.Symbol(kk_m1)] = timeshift(v, all_variables, -1) d[sympy.Symbol(kk_1)] = timeshift(v, all_variables, 1) f_eqs = model.symbolic.equations['arbitrage'] f_eqs = [parse_equation(eq, all_variables, to_sympy=True) for eq in f_eqs] f_eqs = [eq.subs(d) for eq in f_eqs] g_eqs = model.symbolic.equations['transition'] g_eqs = [ parse_equation(eq, all_variables, to_sympy=True, substract_lhs=False) for eq in g_eqs ] #solve_recursively from collections import OrderedDict dd = OrderedDict() for eq in g_eqs: dd[eq[0]] = eq[1].subs(dd).subs(d) g_eqs = dd.values() f_syms = [(e,0) for e in model.symbols['states']] + \ [(e,0) for e in model.symbols['controls']] + \ [(e,1) for e in model.symbols['states']] + \ [(e,1) for e in model.symbols['controls']] g_syms = [(e,-1) for e in model.symbols['states']] + \ [(e,-1) for e in model.symbols['controls']] + \ [(e,0) for e in model.symbols['shocks']] params = model.symbols['parameters'] f = compile_higher_order_function(f_eqs, f_syms, params, order=order, funname='f', return_code=False, compile=False) g = compile_higher_order_function(g_eqs, g_syms, params, order=order, funname='g', return_code=False, compile=False) # cache result model.__higher_order_functions__ = dict(f=f, g=g) model.__highest_order__ = order return [f, g]
def compile_higher_order_function(eqs, syms, params, order=2, funname='anonymous', return_code=False, compile=False): '''From a list of equations and variables, define a multivariate functions with higher order derivatives.''' from dolang import normalize, stringify vars = [s[0] for s in syms] # TEMP: compatibility fix when eqs is an Odict: eqs = [eq for eq in eqs] if isinstance(eqs[0], str): # elif not isinstance(eqs[0], sympy.Basic): # assume we have ASTs eqs = list([ast.parse(eq).body[0] for eq in eqs]) eqs_std = list([normalize(eq, variables=vars) for eq in eqs]) eqs_sym = list([ast_to_sympy(eq) for eq in eqs_std]) else: eqs_sym = eqs symsd = list([stringify((a, b)) for a, b in syms]) paramsd = list([stringify(a) for a in params]) D = higher_order_diff(eqs_sym, symsd, order=order) txt = """def {funname}(x, p, order=1): import numpy from numpy import log, exp, tan, sqrt from numpy import pi as pi_ from numpy import inf as inf_ from scipy.special import erfc """.format(funname=funname) for i in range(len(syms)): txt += " {} = x[{}]\n".format(symsd[i], i) txt += "\n" for i in range(len(params)): txt += " {} = p[{}]\n".format(paramsd[i], i) txt += "\n out = numpy.zeros({})".format(len(eqs)) for i in range(len(eqs)): txt += "\n out[{}] = {}".format(i, D[0][i]) txt += """ if order == 0: return out """ if order >= 1: # Jacobian txt += " out_1 = numpy.zeros(({},{}))\n".format(len(eqs), len(syms)) for i in range(len(eqs)): for j in range(len(syms)): val = D[1][i, j] if val != 0: txt += " out_1[{},{}] = {}\n".format(i, j, D[1][i, j]) txt += """ if order == 1: return [out, out_1] """ if order >= 2: # Hessian txt += " out_2 = numpy.zeros(({},{},{}))\n".format( len(eqs), len(syms), len(syms)) for n in range(len(eqs)): for i in range(len(syms)): for j in range(len(syms)): val = D[2][n, i, j] if val is not None: if val != 0: txt += " out_2[{},{},{}] = {}\n".format( n, i, j, D[2][n, i, j]) else: i1, j1 = sorted((i, j)) if D[2][n, i1, j1] != 0: txt += " out_2[{},{},{}] = out_2[{},{},{}]\n".format( n, i, j, n, i1, j1) txt += """ if order == 2: return [out, out_1, out_2] """ if order >= 3: # Hessian txt += " out_3 = numpy.zeros(({},{},{},{}))\n".format( len(eqs), len(syms), len(syms), len(syms)) for n in range(len(eqs)): for i in range(len(syms)): for j in range(len(syms)): for k in range(len(syms)): val = D[3][n, i, j, k] if val is not None: if val != 0: txt += " out_3[{},{},{},{}] = {}\n".format( n, i, j, k, D[3][n, i, j, k]) else: i1, j1, k1 = sorted((i, j, k)) if D[3][n, i1, j1, k1] != 0: txt += " out_3[{},{},{},{}] = out_3[{},{},{},{}]\n".format( n, i, j, k, n, i1, j1, k1) txt += """ if order == 3: return [out, out_1, out_2, out_3] """ if return_code: return txt else: d = {} d['division'] = division exec(txt, d) fun = d[funname] if compile: raise Exception("Not implemented.") return fun
regex = re.compile("\{(.*)\}", re.MULTILINE) m = regex.search(out.decode('utf8').replace("\n", "")) txt = m.group(0) data = json.loads(txt) endo_vars = [e['name'] for e in data['modfile']['endogenous']] exo_vars = [e['name'] for e in data['modfile']['exogenous']] parameters = [e['name'] for e in data['modfile']['parameters']] variables = endo_vars + exo_vars # all variables with time-index equations = [f"{e['rhs']} - ({e['lhs']})" for e in data['modfile']['model']] equations = [dolang.symbolic.sanitize(eq, variables) for eq in equations] equations_strings = [dolang.stringify(eq, variables) for eq in equations] args = dict([('y_f', [(e, 1) for e in endo_vars]), ('y', [(e, 0) for e in endo_vars]), ('y_p', [(e, -1) for e in endo_vars]), ('e', [(e, 0) for e in exo_vars]), ('p', [e for e in parameters])]) args = dict([(k, [stringify_symbol(e) for e in v]) for k, v in args.items()]) from dolang.symbolic import stringify_symbol from dolang.factory import FlatFunctionFactory eqs = dict([(f"equation_{i+1}", eq) for i, eq in enumerate(equations_strings)]) fff = FlatFunctionFactory(dict(), eqs, args, 'f_dynamic')
def compile_higher_order_function(eqs, syms, params, order=2, funname='anonymous', return_code=False, compile=False): '''From a list of equations and variables, define a multivariate functions with higher order derivatives.''' from dolang import normalize, stringify vars = [s[0] for s in syms] # TEMP: compatibility fix when eqs is an Odict: eqs = [eq for eq in eqs] if isinstance(eqs[0], str): # elif not isinstance(eqs[0], sympy.Basic): # assume we have ASTs eqs = list([ast.parse(eq).body[0] for eq in eqs]) eqs_std = list( [normalize(eq, variables=vars) for eq in eqs] ) eqs_sym = list( [ast_to_sympy(eq) for eq in eqs_std] ) else: eqs_sym = eqs symsd = list( [stringify((a,b)) for a,b in syms] ) paramsd = list( [stringify(a) for a in params] ) D = higher_order_diff(eqs_sym, symsd, order=order) txt = """def {funname}(x, p, order=1): import numpy from numpy import log, exp, tan, sqrt from numpy import pi as pi_ from numpy import inf as inf_ from scipy.special import erfc """.format(funname=funname) for i in range(len(syms)): txt += " {} = x[{}]\n".format(symsd[i], i) txt += "\n" for i in range(len(params)): txt += " {} = p[{}]\n".format(paramsd[i], i) txt += "\n out = numpy.zeros({})".format(len(eqs)) for i in range(len(eqs)): txt += "\n out[{}] = {}".format(i, D[0][i]) txt += """ if order == 0: return out """ if order >= 1: # Jacobian txt += " out_1 = numpy.zeros(({},{}))\n".format(len(eqs), len(syms)) for i in range(len(eqs)): for j in range(len(syms)): val = D[1][i,j] if val != 0: txt += " out_1[{},{}] = {}\n".format(i,j,D[1][i,j]) txt += """ if order == 1: return [out, out_1] """ if order >= 2: # Hessian txt += " out_2 = numpy.zeros(({},{},{}))\n".format(len(eqs), len(syms), len(syms)) for n in range(len(eqs)): for i in range(len(syms)): for j in range(len(syms)): val = D[2][n,i,j] if val is not None: if val != 0: txt += " out_2[{},{},{}] = {}\n".format(n,i,j,D[2][n,i,j]) else: i1, j1 = sorted( (i,j) ) if D[2][n,i1,j1] != 0: txt += " out_2[{},{},{}] = out_2[{},{},{}]\n".format(n,i,j,n,i1,j1) txt += """ if order == 2: return [out, out_1, out_2] """ if order >= 3: # Hessian txt += " out_3 = numpy.zeros(({},{},{},{}))\n".format(len(eqs), len(syms), len(syms), len(syms)) for n in range(len(eqs)): for i in range(len(syms)): for j in range(len(syms)): for k in range(len(syms)): val = D[3][n,i,j,k] if val is not None: if val != 0: txt += " out_3[{},{},{},{}] = {}\n".format(n,i,j,k,D[3][n,i,j,k]) else: i1, j1, k1 = sorted( (i,j,k) ) if D[3][n,i1,j1,k1] != 0: txt += " out_3[{},{},{},{}] = out_3[{},{},{},{}]\n".format(n,i,j,k,n,i1,j1,k1) txt += """ if order == 3: return [out, out_1, out_2, out_3] """ print(txt) if return_code: return txt else: d = {} d['division'] = division exec(txt, d) fun = d[funname] if compile: raise Exception("Not implemented.") return fun