def test_denhaan_errors(): from dolo import yaml_import from dolo.algos.dtcscc.time_iteration import time_iteration as time_iteration model = yaml_import('examples/models/rbc.yaml') from dolo.algos.dtcscc.perturbations import approximate_controls dr = approximate_controls(model) dr_global = time_iteration(model, verbose=False) s0 = model.calibration['states'] sigma = model.get_distribution().sigma from dolo.algos.dtcscc.accuracy import denhaanerrors denerr_1 = denhaanerrors(model, dr) denerr_2 = denhaanerrors(model, dr_global) print(denerr_1) print(denerr_2) print(denerr_2['max_errors'][0]) assert (max(denerr_2['max_errors']) < 10 - 7 ) # errors with solyak colocations at order 4 are very small
def test_denhaan_errors(): from dolo import yaml_import from dolo.algos.dtcscc.time_iteration import time_iteration as time_iteration model = yaml_import('examples/models/rbc.yaml') from dolo.algos.dtcscc.perturbations import approximate_controls dr = approximate_controls(model) dr_global = time_iteration(model, verbose=False) s0 = model.calibration['states'] sigma = model.get_distribution().sigma from dolo.algos.dtcscc.accuracy import denhaanerrors denerr_1 = denhaanerrors(model, dr) denerr_2 = denhaanerrors(model, dr_global) print(denerr_1) print(denerr_2) print(denerr_2['max_errors'][0]) assert( max(denerr_2['max_errors']) < 10-7) # errors with solyak colocations at order 4 are very small
def test_omega_errors(): from dolo import yaml_import from dolo.algos.dtcscc.time_iteration import time_iteration as time_iteration model = yaml_import('examples/models/rbc.yaml') from dolo.algos.dtcscc.perturbations import approximate_controls dr = approximate_controls(model) dr_global = time_iteration(model, smolyak_order=3, verbose=False, pert_order=1) sigma = model.covariances model.sigma = sigma s_0 = dr.S_bar from dolo.algos.dtcscc.accuracy import omega res_1 = omega( model, dr, orders=[10,10], time_discount=0.96) res_2 = omega( model, dr_global) print(res_1) print(res_2)
def test_omega_errors(): from dolo import yaml_import from dolo.algos.dtcscc.time_iteration import time_iteration as time_iteration model = yaml_import('examples/models/rbc.yaml') from dolo.algos.dtcscc.perturbations import approximate_controls dr = approximate_controls(model) dr_global = time_iteration(model, smolyak_order=3, verbose=False, pert_order=1) sigma = model.covariances model.sigma = sigma s_0 = dr.S_bar from dolo.algos.dtcscc.accuracy import omega res_1 = omega(model, dr, orders=[10, 10], time_discount=0.96) res_2 = omega(model, dr_global) print(res_1) print(res_2)
def approximate_controls(model, *args, **kwargs): if model.model_type == 'dtcscc': order = kwargs.get('order') if order is None or order==1: from dolo.algos.dtcscc.perturbations import approximate_controls else: from dolo.algos.dtcscc.perturbations_higher_order import approximate_controls else: raise Exception("Model type {} not supported.".format(model.model_type)) return approximate_controls(model, *args, **kwargs)
def approximate_controls(model, *args, **kwargs): if model.model_type == 'dtcscc': order = kwargs.get('order') if order is None or order == 1: from dolo.algos.dtcscc.perturbations import approximate_controls else: from dolo.algos.dtcscc.perturbations_higher_order import approximate_controls else: raise Exception("Model type {} not supported.".format( model.model_type)) return approximate_controls(model, *args, **kwargs)
def test_omega_errors(): from dolo import yaml_import from dolo.algos.dtcscc.time_iteration import time_iteration as time_iteration model = yaml_import('examples/models/compat/rbc.yaml') from dolo.algos.dtcscc.perturbations import approximate_controls dr = approximate_controls(model) dr_global = time_iteration(model, verbose=False, pert_order=1) sigma = model.get_distribution().sigma s_0 = dr.S_bar from dolo.algos.dtcscc.accuracy import omega res_1 = omega(model, dr, grid=dict(orders=[10, 10]), time_discount=0.96) res_2 = omega(model, dr_global) print(res_1) print(res_2)
def time_iteration(model, bounds=None, verbose=False, initial_dr=None, pert_order=1, with_complementarities=True, interp_type='smolyak', smolyak_order=3, interp_orders=None, maxit=500, tol=1e-8, inner_maxit=10, integration='gauss-hermite', integration_orders=None, T=200, n_s=3, hook=None): ''' Finds a global solution for ``model`` using backward time-iteration. This algorithm iterates on the residuals of the arbitrage equations Parameters ---------- model : NumericModel "fg" or "fga" model to be solved bounds : ndarray boundaries for approximations. First row contains minimum values. Second row contains maximum values. verbose : boolean if True, display iterations initial_dr : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess with_complementarities : boolean (True) if False, complementarity conditions are ignored interp_type : {`smolyak`, `spline`} type of interpolation to use for future controls smolyak_orders : int parameter ``l`` for Smolyak interpolation interp_orders : 1d array-like list of integers specifying the number of nodes in each dimension if ``interp_type="spline" `` Returns ------- decision rule : approximated solution ''' def vprint(t): if verbose: print(t) parms = model.calibration['parameters'] sigma = model.covariances if initial_dr is None: if pert_order == 1: initial_dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") if interp_type == 'perturbations': return initial_dr if bounds is not None: pass elif model.options and 'approximation_space' in model.options: vprint('Using bounds specified by model') approx = model.options['approximation_space'] a = approx['a'] b = approx['b'] bounds = np.row_stack([a, b]) bounds = np.array(bounds, dtype=float) if interp_orders is None: interp_orders = approx.get('orders', [5] * bounds.shape[1]) else: vprint('Using asymptotic bounds given by first order solution.') from dolo.numeric.timeseries import asymptotic_variance # this will work only if initial_dr is a Taylor expansion Q = asymptotic_variance(initial_dr.A.real, initial_dr.B.real, initial_dr.sigma, T=T) devs = np.sqrt(np.diag(Q)) bounds = np.row_stack([ initial_dr.S_bar - devs * n_s, initial_dr.S_bar + devs * n_s, ]) if interp_orders is None: interp_orders = [5] * bounds.shape[1] if interp_type == 'smolyak': from dolo.numeric.interpolation.smolyak import SmolyakGrid dr = SmolyakGrid(bounds[0, :], bounds[1, :], smolyak_order) elif interp_type == 'spline': from dolo.numeric.interpolation.splines import MultivariateSplines dr = MultivariateSplines(bounds[0, :], bounds[1, :], interp_orders) if integration == 'optimal_quantization': from dolo.numeric.discretization import quantization_nodes # number of shocks [epsilons, weights] = quantization_nodes(N_e, sigma) elif integration == 'gauss-hermite': from dolo.numeric.discretization import gauss_hermite_nodes if not integration_orders: integration_orders = [3] * sigma.shape[0] [epsilons, weights] = gauss_hermite_nodes(integration_orders, sigma) vprint('Starting time iteration') # TODO: transpose grid = dr.grid xinit = initial_dr(grid) xinit = xinit.real # just in case... f = model.functions['arbitrage'] g = model.functions['transition'] # define objective function (residuals of arbitrage equations) def fun(x): return step_residual(grid, x, dr, f, g, parms, epsilons, weights) ## t1 = time.time() err = 1 x0 = xinit it = 0 verbit = True if verbose == 'full' else False if with_complementarities: lbfun = model.functions['controls_lb'] ubfun = model.functions['controls_ub'] lb = lbfun(grid, parms) ub = ubfun(grid, parms) else: lb = None ub = None if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} | {4:3} |' headline = headline.format('N', ' Error', 'Gain', 'Time', 'nit') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} | {4:3} |' err_0 = 1 while err > tol and it < maxit: # update counters t_start = time.time() it += 1 # update interpolation coefficients (NOTE: filters through `fun`) dr.set_values(x0) # Derivative of objective function sdfun = SerialDifferentiableFunction(fun) # Apply solver with current decision rule for controls if with_complementarities: [x, nit] = ncpsolve(sdfun, lb, ub, x0, verbose=verbit, maxit=inner_maxit) else: [x, nit] = serial_newton(sdfun, x0, verbose=verbit) # update error and print if `verbose` err = abs(x-x0).max() err_SA = err/err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed, nit)) # Update control vector x0[:] = x # x0 = x0 + (x-x0) # call user supplied hook, if any if hook: hook(dr, it, err) # warn and bail if we get inf if False in np.isfinite(x0): print('iteration {} failed : non finite value') return [x0, x] if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr
def pea(model, maxit=100, tol=1e-8, initial_dr=None, verbose=False): t1 = time.time() g = model.functions['transition'] d = model.functions['direct_response'] h = model.functions['expectation'] p = model.calibration['parameters'] if initial_dr is None: drp = approximate_controls(model) else: drp = approximate_controls(model) nodes, weights = gauss_hermite_nodes([5], model.covariances) ap = model.options['approximation_space'] a = ap['a'] b = ap['b'] orders = ap['orders'] grid = mlinspace(a,b,orders) dr = MultivariateCubicSplines(a,b,orders) N = grid.shape[0] z = np.zeros((N,len(model.symbols['expectations']))) x_0 = drp(grid) it = 0 err = 10 err_0 = 10 if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err>tol and it<=maxit: t_start = time.time() dr.set_values(x_0) z[...] = 0 for i in range(weights.shape[0]): e = nodes[i,:] S = g(grid, x_0, e, p) # evaluate future controls X = dr(S) z += weights[i]*h(S,X,p) # TODO: check that control is admissible new_x = d(grid, z, p) # check whether they differ from the preceding guess err = (abs(new_x - x_0).max()) x_0 = new_x if verbose: # update error and print if `verbose` err_SA = err/err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr
def parameterized_expectations_direct(model, verbose=False, dr0=None, pert_order=1, grid={}, distribution={}, maxit=100, tol=1e-8): ''' Finds a global solution for ``model`` using parameterized expectations function. Requires the model to be written with controls as a direct function of the model objects. The algorithm iterates on the expectations function in the arbitrage equation. It follows the discussion in section 9.9 of Miranda and Fackler (2002). Parameters ---------- model : NumericModel "dtcscc" model to be solved verbose : boolean if True, display iterations dr0 : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess grid: grid options distribution: distribution options maxit: maximum number of iterations tol: tolerance criterium for successive approximations Returns ------- decision rule : approximated solution ''' t1 = time.time() g = model.functions['transition'] d = model.functions['direct_response'] h = model.functions['expectation'] parms = model.calibration['parameters'] if dr0 is None: if pert_order == 1: dr0 = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") approx = model.get_endo_grid(**grid) grid = approx.grid interp_type = approx.interpolation dr = create_interpolator(approx, interp_type) expect = create_interpolator(approx, interp_type) distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() N = grid.shape[0] z = np.zeros((N, len(model.symbols['expectations']))) x_0 = dr0(grid) x_0 = x_0.real # just in case ... h_0 = h(grid, x_0, parms) it = 0 err = 10 err_0 = 10 if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-' * len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: it += 1 t_start = time.time() # dr.set_values(x_0) expect.set_values(h_0) z[...] = 0 for i in range(weights.shape[0]): e = nodes[i, :] S = g(grid, x_0, e, parms) # evaluate expectation over the future state z += weights[i] * expect(S) # TODO: check that control is admissible new_x = d(grid, z, parms) new_h = h(grid, new_x, parms) # update error err = (abs(new_h - h_0).max()) # Update guess for decision rule and expectations function x_0 = new_x h_0 = new_h # print error information if `verbose` err_SA = err / err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) # Interpolation for the decision rule dr.set_values(x_0) return dr
def parameterized_expectations(model, verbose=False, initial_dr=None, pert_order=1, with_complementarities=True, grid={}, distribution={}, maxit=100, tol=1e-8, inner_maxit=10, direct=False): ''' Find global solution for ``model`` via parameterized expectations. Controls must be expressed as a direct function of equilibrium objects. Algorithm iterates over the expectations function in the arbitrage equation. Parameters: ---------- model : NumericModel ``dtcscc`` model to be solved verbose : boolean if True, display iterations initial_dr : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess grid : grid options distribution : distribution options maxit : maximum number of iterations tol : tolerance criterium for successive approximations inner_maxit : maximum number of iteration for inner solver direct : if True, solve with direct method. If false, solve indirectly Returns ------- decision rule : approximated solution ''' t1 = time.time() g = model.functions['transition'] h = model.functions['expectation'] d = model.functions['direct_response'] f = model.functions['arbitrage_exp'] # f(s, x, z, p, out) parms = model.calibration['parameters'] if initial_dr is None: if pert_order == 1: initial_dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") approx = model.get_grid(**grid) grid = approx.grid interp_type = approx.interpolation dr = create_interpolator(approx, interp_type) expect = create_interpolator(approx, interp_type) distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() N = grid.shape[0] z = np.zeros((N, len(model.symbols['expectations']))) x_0 = initial_dr(grid) x_0 = x_0.real # just in case ... h_0 = h(grid, x_0, parms) it = 0 err = 10 err_0 = 10 verbit = True if verbose == 'full' else False if with_complementarities is True: lbfun = model.functions['controls_lb'] ubfun = model.functions['controls_ub'] lb = lbfun(grid, parms) ub = ubfun(grid, parms) else: lb = None ub = None if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: it += 1 t_start = time.time() # dr.set_values(x_0) expect.set_values(h_0) # evaluate expectation over the future state z[...] = 0 for i in range(weights.shape[0]): e = nodes[i, :] S = g(grid, x_0, e, parms) z += weights[i]*expect(S) if direct is True: # Use control as direct function of arbitrage equation new_x = d(grid, z, parms) if with_complementarities is True: new_x = np.minimum(new_x, ub) new_x = np.maximum(new_x, lb) else: # Find control by solving arbitrage equation def fun(x): return f(grid, x, z, parms) sdfun = SerialDifferentiableFunction(fun) if with_complementarities is True: [new_x, nit] = ncpsolve(sdfun, lb, ub, x_0, verbose=verbit, maxit=inner_maxit) else: [new_x, nit] = serial_newton(sdfun, x_0, verbose=verbit) new_h = h(grid, new_x, parms) # update error err = (abs(new_h - h_0).max()) # Update guess for decision rule and expectations function x_0 = new_x h_0 = new_h # print error infomation if `verbose` err_SA = err/err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) # Interpolation for the decision rule dr.set_values(x_0) return dr
def solve_policy(model, tol=1e-6, grid={}, distribution={}, integration_orders=None, maxit=500, maxit_howard=500, verbose=False, hook=None, initial_dr=None, pert_order=1): ''' Solve for the value function and associated decision rule by iterating over the value function. Parameters ----------- model : ``dtcscc`` model. Must contain a ``felicity`` function. Returns -------- dr : Markov decision rule The solved decision rule/policy function drv : decision rule The solved value function ''' assert (model.model_type == 'dtcscc') def vprint(t): if verbose: print(t) # transition(s, x, e, p, out), felicity(s, x, p, out) transition = model.functions['transition'] felicity = model.functions['felicity'] controls_lb = model.functions['controls_lb'] controls_ub = model.functions['controls_ub'] parms = model.calibration['parameters'] discount = model.calibration['beta'] x0 = model.calibration['controls'] s0 = model.calibration['states'] r0 = felicity(s0, x0, parms) approx = model.get_grid(**grid) # a = approx.a # b = approx.b # orders = approx.orders distrib = model.get_distribution(**distribution) sigma = distrib.sigma # Possibly use the approximation orders? if integration_orders is None: integration_orders = [3] * sigma.shape[0] [nodes, weights] = gauss_hermite_nodes(integration_orders, sigma) interp_type = approx.interpolation drv = create_interpolator(approx, interp_type) if initial_dr is None: if pert_order == 1: dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") else: dr = initial_dr grid = drv.grid N = grid.shape[0] # Number of states n_x = grid.shape[1] # Number of controls controls = dr(grid) controls_0 = np.zeros((N, n_x)) controls_0[:, :] = model.calibration['controls'][None, :] values_0 = np.zeros((N, 1)) values_0[:, :] = r0 / (1 - discount) if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |'.format( 'N', ' Error', 'Gain', 'Time') stars = '-' * len(headline) print(stars) print(headline) print(stars) t1 = time.time() # FIRST: value function iterations, 10 iterations to start it = 0 err_v = 100 err_v_0 = 0.0 gain_v = 0.0 err_x = 100 err_x_0 = 100 if verbose: print(stars) print('Starting value function iteration') print(stars) while err_v > tol and it < 10: t_start = time.time() it += 1 # update interpolation object with current values drv.set_values(values_0) values = values_0.copy() controls = controls_0.copy() for n in range(N): s = grid[n, :] x = controls[n, :] lb = controls_lb(s, parms) ub = controls_ub(s, parms) bnds = [e for e in zip(lb, ub)] def valfun(xx): return -choice_value(transition, felicity, s, xx, drv, nodes, weights, parms, discount)[0] res = minimize(valfun, x, bounds=bnds, tol=1e-4) controls[n, :] = res.x values[n, 0] = -valfun(res.x) # compute error, update value and dr err_x = abs(controls - controls_0).max() err_v = abs(values - values_0).max() t_end = time.time() elapsed = t_end - t_start values_0 = values controls_0 = controls gain_x = err_x / err_x_0 gain_v = err_v / err_v_0 err_x_0 = err_x err_v_0 = err_v # print update to user, if verbose if verbose: print('|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |'.format( it, err_v, gain_v, elapsed)) # SECOND: Howard improvement step, 10-20 iterations it = 0 err_v = 100 err_v_0 = 0.0 gain_v = 0.0 if verbose: print(stars) print('Starting Howard improvement step') print(stars) while err_v > tol and it < maxit_howard: t_start = time.time() it += 1 # update interpolation object with current values drv.set_values(values_0) values = values_0.copy() controls = controls_0.copy() # controls = controls_0.copy() # No need to keep updating for n in range(N): s = grid[n, :] x = controls[n, :] values[n, 0] = choice_value(transition, felicity, s, x, drv, nodes, weights, parms, discount)[0] # compute error, update value function err_v = abs(values - values_0).max() values_0 = values t_end = time.time() elapsed = t_end - t_start gain_v = err_v / err_v_0 err_v_0 = err_v # print update to user, if verbose if verbose: print('|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |'.format( it, err_v, gain_v, elapsed)) # THIRD: Back to value function iteration until convergence. it = 0 err_v = 100 err_v_0 = 0.0 gain_v = 0.0 err_x = 100 err_x_0 = 100 if verbose: print(stars) print('Starting value function iteration') print(stars) while err_v > tol and it < maxit: t_start = time.time() it += 1 # update interpolation object with current values drv.set_values(values_0) values = values_0.copy() controls = controls_0.copy() for n in range(N): s = grid[n, :] x = controls[n, :] lb = controls_lb(s, parms) ub = controls_ub(s, parms) bnds = [e for e in zip(lb, ub)] def valfun(xx): return -choice_value(transition, felicity, s, xx, drv, nodes, weights, parms, discount)[0] res = minimize(valfun, x, bounds=bnds, tol=1e-4) controls[n, :] = res.x values[n, 0] = -valfun(res.x) # compute error, update value and dr err_x = abs(controls - controls_0).max() err_v = abs(values - values_0).max() t_end = time.time() elapsed = t_end - t_start values_0 = values controls_0 = controls gain_x = err_x / err_x_0 gain_v = err_v / err_v_0 err_x_0 = err_x err_v_0 = err_v # print update to user, if verbose if verbose: print('|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |'.format( it, err_v, gain_v, elapsed)) if it == maxit: warnings.warn(UserWarning("Maximum number of iterations reached")) t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) # final value function and decision rule drv.set_values(values_0) dr = create_interpolator(approx, interp_type) dr.set_values(controls_0) return dr, drv
def time_iteration(model, verbose=False, initial_dr=None, pert_order=1, with_complementarities=True, grid={}, distribution={}, maxit=500, tol=1e-8, inner_maxit=10, hook=None): ''' Finds a global solution for ``model`` using backward time-iteration. This algorithm iterates on the residuals of the arbitrage equations Parameters ---------- model : NumericModel "dtcscc" model to be solved verbose : boolean if True, display iterations initial_dr : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess with_complementarities : boolean (True) if False, complementarity conditions are ignored grid: grid options distribution: distribution options maxit: maximum number of iterations inner_maxit: maximum number of iteration for inner solver tol: tolerance criterium for successive approximations Returns ------- decision rule : approximated solution ''' def vprint(t): if verbose: print(t) f = model.functions['arbitrage'] g = model.functions['transition'] parms = model.calibration['parameters'] approx = model.get_grid(**grid) interp_type = approx.interpolation dr = create_interpolator(approx, interp_type) distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() if initial_dr is None: if pert_order == 1: initial_dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") vprint('Starting time iteration') # TODO: transpose grid = dr.grid x_0 = initial_dr(grid) x_0 = x_0.real # just in case... # define objective function (residuals of arbitrage equations) def fun(x): return step_residual(grid, x, dr, f, g, parms, nodes, weights) ## t1 = time.time() err = 1 err_0 = 1 it = 0 verbit = True if verbose == 'full' else False if with_complementarities: lbfun = model.functions['controls_lb'] ubfun = model.functions['controls_ub'] lb = lbfun(grid, parms) ub = ubfun(grid, parms) else: lb = None ub = None if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} | {4:3} |' headline = headline.format('N', ' Error', 'Gain', 'Time', 'nit') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} | {4:3} |' while err > tol and it < maxit: # update counters t_start = time.time() it += 1 # update interpolation coefficients (NOTE: filters through `fun`) dr.set_values(x_0) # Derivative of objective function sdfun = SerialDifferentiableFunction(fun) # Apply solver with current decision rule for controls if with_complementarities: [x, nit] = ncpsolve(sdfun, lb, ub, x_0, verbose=verbit, maxit=inner_maxit) else: [x, nit] = serial_newton(sdfun, x_0, verbose=verbit) # update error and print if `verbose` err = abs(x-x_0).max() err_SA = err/err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed, nit)) # Update control vector x_0[:] = x # x0 = x0 + (x-x0) # call user supplied hook, if any if hook: hook(dr, it, err) # warn and bail if we get inf if False in np.isfinite(x_0): print('iteration {} failed : non finite value') return [x_0, x] if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final time and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr
def time_iteration(model, bounds=None, verbose=False, initial_dr=None, pert_order=1, with_complementarities=True, interp_type='smolyak', smolyak_order=3, interp_orders=None, maxit=500, tol=1e-8, inner_maxit=10, integration='gauss-hermite', integration_orders=None, T=200, n_s=3, hook=None): ''' Finds a global solution for ``model`` using backward time-iteration. This algorithm iterates on the residuals of the arbitrage equations Parameters ---------- model : NumericModel "fg" or "fga" model to be solved bounds : ndarray boundaries for approximations. First row contains minimum values. Second row contains maximum values. verbose : boolean if True, display iterations initial_dr : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess with_complementarities : boolean (True) if False, complementarity conditions are ignored interp_type : {`smolyak`, `spline`} type of interpolation to use for future controls smolyak_orders : int parameter ``l`` for Smolyak interpolation interp_orders : 1d array-like list of integers specifying the number of nodes in each dimension if ``interp_type="spline" `` Returns ------- decision rule : approximated solution ''' def vprint(t): if verbose: print(t) parms = model.calibration['parameters'] sigma = model.covariances if initial_dr is None: if pert_order == 1: initial_dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") if interp_type == 'perturbations': return initial_dr if bounds is not None: pass elif model.options and 'approximation_space' in model.options: vprint('Using bounds specified by model') approx = model.options['approximation_space'] a = approx['a'] b = approx['b'] bounds = np.row_stack([a, b]) bounds = np.array(bounds, dtype=float) if interp_orders is None: interp_orders = approx.get('orders', [5] * bounds.shape[1]) else: vprint('Using asymptotic bounds given by first order solution.') from dolo.numeric.timeseries import asymptotic_variance # this will work only if initial_dr is a Taylor expansion Q = asymptotic_variance(initial_dr.A.real, initial_dr.B.real, initial_dr.sigma, T=T) devs = np.sqrt(np.diag(Q)) bounds = np.row_stack([ initial_dr.S_bar - devs * n_s, initial_dr.S_bar + devs * n_s, ]) if interp_orders is None: interp_orders = [5] * bounds.shape[1] if interp_type == 'smolyak': from dolo.numeric.interpolation.smolyak import SmolyakGrid dr = SmolyakGrid(bounds[0, :], bounds[1, :], smolyak_order) elif interp_type == 'spline': from dolo.numeric.interpolation.splines import MultivariateSplines dr = MultivariateSplines(bounds[0, :], bounds[1, :], interp_orders) if integration == 'optimal_quantization': from dolo.numeric.discretization import quantization_nodes # number of shocks [epsilons, weights] = quantization_nodes(N_e, sigma) elif integration == 'gauss-hermite': from dolo.numeric.discretization import gauss_hermite_nodes if not integration_orders: integration_orders = [3] * sigma.shape[0] [epsilons, weights] = gauss_hermite_nodes(integration_orders, sigma) vprint('Starting time iteration') # TODO: transpose grid = dr.grid xinit = initial_dr(grid) xinit = xinit.real # just in case... f = model.functions['arbitrage'] g = model.functions['transition'] # define objective function (residuals of arbitrage equations) def fun(x): return step_residual(grid, x, dr, f, g, parms, epsilons, weights) ## t1 = time.time() err = 1 x0 = xinit it = 0 verbit = True if verbose == 'full' else False if with_complementarities: lbfun = model.functions['controls_lb'] ubfun = model.functions['controls_ub'] lb = lbfun(grid, parms) ub = ubfun(grid, parms) else: lb = None ub = None if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} | {4:3} |' headline = headline.format('N', ' Error', 'Gain', 'Time', 'nit') stars = '-' * len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} | {4:3} |' err_0 = 1 while err > tol and it < maxit: # update counters t_start = time.time() it += 1 # update interpolation coefficients (NOTE: filters through `fun`) dr.set_values(x0) # Derivative of objective function sdfun = SerialDifferentiableFunction(fun) # Apply solver with current decision rule for controls if with_complementarities: [x, nit] = ncpsolve(sdfun, lb, ub, x0, verbose=verbit, maxit=inner_maxit) else: [x, nit] = serial_newton(sdfun, x0, verbose=verbit) # update error and print if `verbose` err = abs(x - x0).max() err_SA = err / err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed, nit)) # Update control vector x0[:] = x # x0 = x0 + (x-x0) # call user supplied hook, if any if hook: hook(dr, it, err) # warn and bail if we get inf if False in np.isfinite(x0): print('iteration {} failed : non finite value') return [x0, x] if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr
def parameterized_expectations_direct(model, verbose=False, initial_dr=None, pert_order=1, grid={}, distribution={}, maxit=100, tol=1e-8): ''' Finds a global solution for ``model`` using parameterized expectations function. Requires the model to be written with controls as a direct function of the model objects. The algorithm iterates on the expectations function in the arbitrage equation. It follows the discussion in section 9.9 of Miranda and Fackler (2002). Parameters ---------- model : NumericModel "dtcscc" model to be solved verbose : boolean if True, display iterations initial_dr : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess grid: grid options distribution: distribution options maxit: maximum number of iterations tol: tolerance criterium for successive approximations Returns ------- decision rule : approximated solution ''' t1 = time.time() g = model.functions['transition'] d = model.functions['direct_response'] h = model.functions['expectation'] parms = model.calibration['parameters'] if initial_dr is None: if pert_order == 1: initial_dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") approx = model.get_grid(**grid) grid = approx.grid interp_type = approx.interpolation dr = create_interpolator(approx, interp_type) expect = create_interpolator(approx, interp_type) distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() N = grid.shape[0] z = np.zeros((N, len(model.symbols['expectations']))) x_0 = initial_dr(grid) x_0 = x_0.real # just in case ... h_0 = h(grid, x_0, parms) it = 0 err = 10 err_0 = 10 if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: it += 1 t_start = time.time() # dr.set_values(x_0) expect.set_values(h_0) z[...] = 0 for i in range(weights.shape[0]): e = nodes[i, :] S = g(grid, x_0, e, parms) # evaluate expectation over the future state z += weights[i]*expect(S) # TODO: check that control is admissible new_x = d(grid, z, parms) new_h = h(grid, new_x, parms) # update error err = (abs(new_h - h_0).max()) # Update guess for decision rule and expectations function x_0 = new_x h_0 = new_h # print error information if `verbose` err_SA = err/err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) # Interpolation for the decision rule dr.set_values(x_0) return dr
def time_iteration_direct(model, verbose=False, initial_dr=None, pert_order=1, with_complementarities=True, grid={}, distribution={}, maxit=500, tol=1e-8): ''' Finds a global solution for ``model`` using backward time-iteration. Requires the model to be written with controls as a direct function of the model objects. This algorithm iterates on the (directly expressed) decision rule, which is a re-expression of the arbitrage equation. Parameters ---------- model : NumericModel "dtcscc" model to be solved verbose : boolean if True, display iterations initial_dr : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess grid: grid options distribution: distribution options maxit: maximum number of iterations tol: tolerance criterium for successive approximations Returns ------- decision rule : approximated solution ''' def vprint(t): if verbose: print(t) g = model.functions['transition'] d = model.functions['direct_response'] h = model.functions['expectation'] parms = model.calibration['parameters'] approx = model.get_grid(**grid) interp_type = approx.interpolation dr = create_interpolator(approx, interp_type) distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() if initial_dr is None: if pert_order == 1: initial_dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") vprint('Starting direct response time iteration') grid = approx.grid N = grid.shape[0] z = np.zeros((N, len(model.symbols['expectations']))) x_0 = initial_dr(grid) x_0 = x_0.real # just in case... if with_complementarities: lbfun = model.functions['controls_lb'] ubfun = model.functions['controls_ub'] lb = lbfun(grid, parms) ub = ubfun(grid, parms) else: lb = None ub = None ## t1 = time.time() err = 10 err_0 = 10 it = 0 if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: # update counters t_start = time.time() it += 1 # update interpolation coefficients dr.set_values(x_0) # Compute expectations function z[...] = 0 for i in range(weights.shape[0]): e = nodes[i, :] S = g(grid, x_0, e, parms) # evaluate future controls X = dr(S) z += weights[i]*h(S, X, parms) # Update control if with_complementarities: new_x = d(grid, z, parms) new_x = np.minimum(new_x, ub) new_x = np.maximum(new_x, lb) else: new_x = d(grid, z, parms) # update error err = (abs(new_x - x_0).max()) # Update control vector x_0[:] = new_x # print error information if `verbose` err_SA = err/err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final time and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr
def nonlinear_system(model, initial_dr=None, maxit=10, tol=1e-8, grid={}, distribution={}, verbose=True): ''' Finds a global solution for ``model`` by solving one large system of equations using a simple newton algorithm. Parameters ---------- model: NumericModel "dtcscc" model to be solved verbose: boolean if True, display iterations initial_dr: decision rule initial guess for the decision rule maxit: int maximum number of iterationsd tol: tolerance criterium for successive approximations grid: grid options distribution: distribution options Returns ------- decision rule : approximated solution ''' if verbose: headline = '|{0:^4} | {1:10} | {2:8} |' headline = headline.format('N', ' Error', 'Time') stars = '-'*len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} |' f = model.functions['arbitrage'] g = model.functions['transition'] p = model.calibration['parameters'] distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() approx = model.get_grid(**grid) ms = create_interpolator(approx, approx.interpolation) grid = ms.grid if initial_dr is None: dr = approximate_controls(model) else: dr = initial_dr ms.set_values(dr(grid)) x = dr(grid) x0 = x.copy() it = 0 err = 10 a0 = x0.copy().reshape((x0.shape[0]*x0.shape[1],)) a = a0.copy() while err > tol and it < maxit: it += 1 t1 = time.time() r, da = residuals(f, g, grid, a.reshape(x0.shape), ms, nodes, weights, p, diff=True)[:2] r = r.flatten() err = abs(r).max() t2 = time.time() if verbose: print(fmt_str.format(it, err, t2-t1)) if err > tol: a -= scipy.sparse.linalg.spsolve(da, r) if verbose: print(stars) return ms
def parameterized_expectations(model, verbose=False, dr0=None, pert_order=1, with_complementarities=True, grid={}, distribution={}, maxit=100, tol=1e-8, inner_maxit=10, direct=False): ''' Find global solution for ``model`` via parameterized expectations. Controls must be expressed as a direct function of equilibrium objects. Algorithm iterates over the expectations function in the arbitrage equation. Parameters: ---------- model : NumericModel ``dtcscc`` model to be solved verbose : boolean if True, display iterations dr0 : decision rule initial guess for the decision rule pert_order : {1} if no initial guess is supplied, the perturbation solution at order ``pert_order`` is used as initial guess grid : grid options distribution : distribution options maxit : maximum number of iterations tol : tolerance criterium for successive approximations inner_maxit : maximum number of iteration for inner solver direct : if True, solve with direct method. If false, solve indirectly Returns ------- decision rule : approximated solution ''' t1 = time.time() g = model.functions['transition'] h = model.functions['expectation'] d = model.functions['direct_response'] f = model.functions['arbitrage_exp'] # f(s, x, z, p, out) parms = model.calibration['parameters'] if dr0 is None: if pert_order == 1: dr0 = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") approx = model.get_endo_grid(**grid) grid = approx.grid interp_type = approx.interpolation dr = create_interpolator(approx, interp_type) expect = create_interpolator(approx, interp_type) distrib = model.get_distribution(**distribution) nodes, weights = distrib.discretize() N = grid.shape[0] z = np.zeros((N, len(model.symbols['expectations']))) x_0 = dr0(grid) x_0 = x_0.real # just in case ... h_0 = h(grid, x_0, parms) it = 0 err = 10 err_0 = 10 verbit = True if verbose == 'full' else False if with_complementarities is True: lbfun = model.functions['controls_lb'] ubfun = model.functions['controls_ub'] lb = lbfun(grid, parms) ub = ubfun(grid, parms) else: lb = None ub = None if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-' * len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: it += 1 t_start = time.time() # dr.set_values(x_0) expect.set_values(h_0) # evaluate expectation over the future state z[...] = 0 for i in range(weights.shape[0]): e = nodes[i, :] S = g(grid, x_0, e, parms) z += weights[i] * expect(S) if direct is True: # Use control as direct function of arbitrage equation new_x = d(grid, z, parms) if with_complementarities is True: new_x = np.minimum(new_x, ub) new_x = np.maximum(new_x, lb) else: # Find control by solving arbitrage equation def fun(x): return f(grid, x, z, parms) sdfun = SerialDifferentiableFunction(fun) if with_complementarities is True: [new_x, nit] = ncpsolve(sdfun, lb, ub, x_0, verbose=verbit, maxit=inner_maxit) else: [new_x, nit] = serial_newton(sdfun, x_0, verbose=verbit) new_h = h(grid, new_x, parms) # update error err = (abs(new_h - h_0).max()) # Update guess for decision rule and expectations function x_0 = new_x h_0 = new_h # print error infomation if `verbose` err_SA = err / err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) # Interpolation for the decision rule dr.set_values(x_0) return dr
err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr if __name__ == '__main__': from dolo import * model = yaml_import("examples/models/rbc_full.yaml") drp = approximate_controls(model) sol = pea(model, initial_dr=drp, verbose=True) sol = pea(model, initial_dr=drp, verbose=True) # computes expectations # based on xinit
t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr if __name__ == '__main__': from dolo import * model = yaml_import("examples/models/rbc_full.yaml") drp = approximate_controls(model) sol = pea(model, initial_dr=drp, verbose=True) sol = pea(model, initial_dr=drp, verbose=True) # computes expectations # based on xinit
def pea(model, maxit=100, tol=1e-8, initial_dr=None, verbose=False): t1 = time.time() g = model.functions['transition'] d = model.functions['direct_response'] h = model.functions['expectation'] p = model.calibration['parameters'] if initial_dr is None: drp = approximate_controls(model) else: drp = approximate_controls(model) nodes, weights = gauss_hermite_nodes([5], model.covariances) ap = model.options['approximation_space'] a = ap['a'] b = ap['b'] orders = ap['orders'] grid = mlinspace(a, b, orders) dr = MultivariateCubicSplines(a, b, orders) N = grid.shape[0] z = np.zeros((N, len(model.symbols['expectations']))) x_0 = drp(grid) it = 0 err = 10 err_0 = 10 if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-' * len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: t_start = time.time() dr.set_values(x_0) z[...] = 0 for i in range(weights.shape[0]): e = nodes[i, :] S = g(grid, x_0, e, p) # evaluate future controls X = dr(S) z += weights[i] * h(S, X, p) # TODO: check that control is admissible new_x = d(grid, z, p) # check whether they differ from the preceding guess err = (abs(new_x - x_0).max()) x_0 = new_x if verbose: # update error and print if `verbose` err_SA = err / err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) if it == maxit: import warnings warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) return dr
def solve_policy(model, tol=1e-6, grid={}, distribution={}, integration_orders=None, maxit=500, maxit_howard=500, verbose=False, hook=None, initial_dr=None, pert_order=1): ''' Solve for the value function and associated decision rule by iterating over the value function. Parameters ----------- model : ``dtcscc`` model. Must contain a ``felicity`` function. Returns -------- dr : Markov decision rule The solved decision rule/policy function drv : decision rule The solved value function ''' assert (model.model_type == 'dtcscc') def vprint(t): if verbose: print(t) # transition(s, x, e, p, out), felicity(s, x, p, out) transition = model.functions['transition'] felicity = model.functions['felicity'] controls_lb = model.functions['controls_lb'] controls_ub = model.functions['controls_ub'] parms = model.calibration['parameters'] discount = model.calibration['beta'] x0 = model.calibration['controls'] s0 = model.calibration['states'] r0 = felicity(s0, x0, parms) approx = model.get_grid(**grid) # a = approx.a # b = approx.b # orders = approx.orders distrib = model.get_distribution(**distribution) sigma = distrib.sigma # Possibly use the approximation orders? if integration_orders is None: integration_orders = [3] * sigma.shape[0] [nodes, weights] = gauss_hermite_nodes(integration_orders, sigma) interp_type = approx.interpolation drv = create_interpolator(approx, interp_type) if initial_dr is None: if pert_order == 1: dr = approximate_controls(model) if pert_order > 1: raise Exception("Perturbation order > 1 not supported (yet).") else: dr = initial_dr grid = drv.grid N = grid.shape[0] # Number of states n_x = grid.shape[1] # Number of controls controls = dr(grid) controls_0 = np.zeros((N, n_x)) controls_0[:, :] = model.calibration['controls'][None, :] values_0 = np.zeros((N, 1)) values_0[:, :] = r0/(1-discount) if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |'.format('N', ' Error','Gain', 'Time') stars = '-' * len(headline) print(stars) print(headline) print(stars) t1 = time.time() # FIRST: value function iterations, 10 iterations to start it = 0 err_v = 100 err_v_0 = 0.0 gain_v = 0.0 err_x = 100 err_x_0 = 100 if verbose: print(stars) print('Starting value function iteration') print(stars) while err_v > tol and it < 10: t_start = time.time() it += 1 # update interpolation object with current values drv.set_values(values_0) values = values_0.copy() controls = controls_0.copy() for n in range(N): s = grid[n, :] x = controls[n, :] lb = controls_lb(s, parms) ub = controls_ub(s, parms) bnds = [e for e in zip(lb, ub)] def valfun(xx): return -choice_value(transition, felicity, s, xx, drv, nodes, weights, parms, discount)[0] res = minimize(valfun, x, bounds=bnds, tol=1e-4) controls[n, :] = res.x values[n, 0] = -valfun(res.x) # compute error, update value and dr err_x = abs(controls - controls_0).max() err_v = abs(values - values_0).max() t_end = time.time() elapsed = t_end - t_start values_0 = values controls_0 = controls gain_x = err_x / err_x_0 gain_v = err_v / err_v_0 err_x_0 = err_x err_v_0 = err_v # print update to user, if verbose if verbose: print('|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |'.format( it, err_v, gain_v, elapsed)) # SECOND: Howard improvement step, 10-20 iterations it = 0 err_v = 100 err_v_0 = 0.0 gain_v = 0.0 if verbose: print(stars) print('Starting Howard improvement step') print(stars) while err_v > tol and it < maxit_howard: t_start = time.time() it += 1 # update interpolation object with current values drv.set_values(values_0) values = values_0.copy() controls = controls_0.copy() # controls = controls_0.copy() # No need to keep updating for n in range(N): s = grid[n, :] x = controls[n, :] values[n, 0] = choice_value(transition, felicity, s, x, drv, nodes, weights, parms, discount)[0] # compute error, update value function err_v = abs(values - values_0).max() values_0 = values t_end = time.time() elapsed = t_end - t_start gain_v = err_v / err_v_0 err_v_0 = err_v # print update to user, if verbose if verbose: print('|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |'.format( it, err_v, gain_v, elapsed)) # THIRD: Back to value function iteration until convergence. it = 0 err_v = 100 err_v_0 = 0.0 gain_v = 0.0 err_x = 100 err_x_0 = 100 if verbose: print(stars) print('Starting value function iteration') print(stars) while err_v > tol and it < maxit: t_start = time.time() it += 1 # update interpolation object with current values drv.set_values(values_0) values = values_0.copy() controls = controls_0.copy() for n in range(N): s = grid[n, :] x = controls[n, :] lb = controls_lb(s, parms) ub = controls_ub(s, parms) bnds = [e for e in zip(lb, ub)] def valfun(xx): return -choice_value(transition, felicity, s, xx, drv, nodes, weights, parms, discount)[0] res = minimize(valfun, x, bounds=bnds, tol=1e-4) controls[n, :] = res.x values[n, 0] = -valfun(res.x) # compute error, update value and dr err_x = abs(controls - controls_0).max() err_v = abs(values - values_0).max() t_end = time.time() elapsed = t_end - t_start values_0 = values controls_0 = controls gain_x = err_x / err_x_0 gain_v = err_v / err_v_0 err_x_0 = err_x err_v_0 = err_v # print update to user, if verbose if verbose: print('|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |'.format( it, err_v, gain_v, elapsed)) if it == maxit: warnings.warn(UserWarning("Maximum number of iterations reached")) t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) # final value function and decision rule drv.set_values(values_0) dr = create_interpolator(approx, interp_type) dr.set_values(controls_0) return dr, drv
def gssa(model, maxit=100, tol=1e-8, initial_dr=None, verbose=False, n_sim=10000, deg=3, damp=0.1, seed=42): """ Sketch of algorithm: 0. Choose levels for the initial states and the simulation length (n_sim) 1. Obtain an initial decision rule -- here using first order perturbation 2. Draw a sequence of innovations epsilon 3. Iterate on the following steps: - Use the epsilons, initial states, and proposed decision rule to simulate model forward. Will leave us with time series of states and controls - Evaluate expectations using quadrature - Use direct response to get alternative proposal for controls - Regress updated controls on the simulated states to get proposal coefficients. New coefficients are convex combination of previous coefficients and proposal coefficients. Weights controlled by damp, where damp is the weight on the old coefficients. This should be fairly low to increase chances of convergence. - Check difference between the simulated series of controls and the direct response version of controls """ # verify input arguments if deg < 0 or deg > 5: raise ValueError("deg must be in [1, 5]") if damp < 0 or damp > 1: raise ValueError("damp must be in [0, 1]") t1 = time.time() # extract model functions and parameters g = model.__original_functions__['transition'] g_gu = model.__original_gufunctions__['transition'] h_gu = model.__original_gufunctions__['expectation'] d_gu = model.__original_gufunctions__['direct_response'] p = model.calibration['parameters'] n_s = len(model.symbols["states"]) n_x = len(model.symbols["controls"]) n_z = len(model.symbols["expectations"]) n_eps = len(model.symbols["shocks"]) s0 = model.calibration["states"] x0 = model.calibration["controls"] # construct initial decision rule if not supplied if initial_dr is None: drp = approximate_controls(model) else: drp = initial_dr # set up quadrature weights and nodes distrib = model.get_distribution() nodes, weights = distrib.discretize() # draw sequence of innovations np.random.seed(seed) distrib = model.get_distribution() sigma = distrib.sigma epsilon = np.random.multivariate_normal(np.zeros(n_eps), sigma, n_sim) # simulate initial decision rule and do initial regression for coefs init_sim = simulate(model, drp, horizon=n_sim, return_array=True, forcing_shocks=epsilon) s_sim = init_sim[:, 0, 0:n_s] x_sim = init_sim[:, 0, n_s:n_s + n_x] Phi_sim = complete_polynomial(s_sim.T, deg).T coefs = np.ascontiguousarray(lstsq(Phi_sim, x_sim)[0]) # NOTE: the ascontiguousarray above was needed for numba to compile the # `np.dot` in the simulation function in no python mode. Appearantly # the array returned from lstsq is not C-contiguous # allocate for simulated series of expectations and next period states z_sim = np.empty((n_sim, n_z)) S = np.empty_like(s_sim) X = np.empty_like(x_sim) H = np.empty_like(z_sim) new_x = np.empty_like(x_sim) # set initial states and controls s_sim[0, :] = s0 x_sim[0, :] = x0 Phi_t = np.empty(n_complete(n_s, deg)) # buffer array for simulation # create jitted function that will simulate states and controls, using # the epsilon shocks from above (define here as closure over all data # above). @jit(nopython=True) def simulate_states_controls(s, x, Phi_t, coefs): for t in range(1, n_sim): g(s[t - 1, :], x[t - 1, :], epsilon[t, :], p, s[t, :]) # fill Phi_t with new complete poly version of s[t, :] _complete_poly_impl_vec(s[t, :], deg, Phi_t) # do inner product to get new controls x[t, :] = Phi_t @coefs it = 0 err = 10.0 err_0 = 10 if verbose: headline = '|{0:^4} | {1:10} | {2:8} | {3:8} |' headline = headline.format('N', ' Error', 'Gain', 'Time') stars = '-' * len(headline) print(stars) print(headline) print(stars) # format string for within loop fmt_str = '|{0:4} | {1:10.3e} | {2:8.3f} | {3:8.3f} |' while err > tol and it <= maxit: t_start = time.time() # simulate with new coefficients simulate_states_controls(s_sim, x_sim, Phi_t, coefs) # update expectations of z # update_expectations(s_sim, x_sim, z_sim, Phi_sim) z_sim[:, :] = 0.0 for i in range(weights.shape[0]): e = nodes[i, :] # extract nodes # evaluate future states at each node (stores in S) g_gu(s_sim, x_sim, e, p, S) # evaluate future controls at each future state _complete_poly_impl(S.T, deg, Phi_sim.T) np.dot(Phi_sim, coefs, out=X) # compute expectation (stores in H) h_gu(S, X, p, H) z_sim += weights[i] * H # get controls on the simulated points from direct_resposne # (stores in new_x) d_gu(s_sim, z_sim, p, new_x) # update basis matrix and do regression of new_x on s_sim to get # updated coefficients _complete_poly_impl(s_sim.T, deg, Phi_sim.T) new_coefs = np.ascontiguousarray(lstsq(Phi_sim, new_x)[0]) # check whether they differ from the preceding guess err = (abs(new_x - x_sim).max()) # update the series of controls and coefficients x_sim[:, :] = new_x coefs = (1 - damp) * new_coefs + damp * coefs if verbose: # update error and print if `verbose` err_SA = err / err_0 err_0 = err t_finish = time.time() elapsed = t_finish - t_start if verbose: print(fmt_str.format(it, err, err_SA, elapsed)) it += 1 if it == maxit: warnings.warn(UserWarning("Maximum number of iterations reached")) # compute final fime and do final printout if `verbose` t2 = time.time() if verbose: print(stars) print('Elapsed: {} seconds.'.format(t2 - t1)) print(stars) cp = CompletePolynomial(deg, len(s0)) cp.fit_values(s_sim, x_sim) return cp