def inv_mn_and_v(c, d, a, b, w, par): v = utility.func(c, par) + w m = a + c + d n = b - d - pens.func(d, par) return m, n, v
def deviate_d_con(valt, n, c, a, w, par): for i_b in range(par.Nb_pd): for i_a in range(par.Na_pd): # a. choices d_x = par.delta_con * c[i_b, i_a] c_x = (1.0 - par.delta_con) * c[i_b, i_a] # b. post-decision states b_x = n[i_b, i_a] + d_x + pens.func(d_x, par) if not np.imag(b_x) == 0: valt[i_b, i_a] = np.nan continue # c. value w_x = linear_interp.interp_2d(par.grid_b_pd, par.grid_a_pd, w, b_x, a[i_b, i_a]) v_x = utility.func(c_x, par) + w_x if not np.imag(v_x) == 0: valt[i_b, i_a] = np.nan else: valt[i_b, i_a] = v_x
def obj_last_period(d, x, par): """ objective function in last period """ # implied consumption (rest) c = x - d return -utility.func(c, d, par)
def obj_bellman(c, p, m, v_plus, par): """ evaluate bellman equation """ # a. end-of-period assets a = m - c # b. continuation value w = 0 for ishock in range(par.Nshocks): # i. shocks psi = par.psi[ishock] psi_w = par.psi_w[ishock] xi = par.xi[ishock] xi_w = par.xi_w[ishock] # ii. next-period states p_plus = p * psi y_plus = p_plus * xi m_plus = par.R * a + y_plus # iii. weight weight = psi_w * xi_w # iv. interpolate w += weight * par.beta * linear_interp.interp_2d( par.grid_p, par.grid_m, v_plus, p_plus, m_plus) # c. total value value_of_choice = utility.func(c, par) + w return -value_of_choice # we are minimizing
def solve(sol, par, G2EGM=True): # unpack t = par.T - 1 c = sol.c[t] d = sol.d[t] inv_v = sol.inv_v[t] inv_vm = sol.inv_vm[t] if G2EGM: inv_vn = sol.inv_vn[t] for i_n in range(par.Nn): # i. states m = par.grid_m n = par.grid_n[i_n] # ii. consume everything d[i_n, :] = 0 c[i_n, :] = m + n # iii. value function v = utility.func(c[i_n, :], par) inv_v[i_n, :] = -1.0 / v # iv. value function derivatives vm = utility.marg_func(c[i_n, :], par) inv_vm[i_n, :] = 1.0 / vm if G2EGM: inv_vn[i_n, :] = inv_vm[i_n, :]
def calc_utility(sim, sol, par): """ calculate utility for each individual """ # unpack u = sim.utility for t in range(par.simT): for i in prange(par.simN): u[i] += par.beta**t * utility.func(sim.c[t, i], sim.d[t, i], par)
def obj_bellman(c, m, interp_w, par): """ evaluate bellman equation """ # a. end-of-period assets a = m - c # b. continuation value w = linear_interp.interp_1d(par.grid_a, interp_w, a) # c. total value value_of_choice = utility.func(c, par) + w return -value_of_choice # we are minimizing
def _egm(model, t, i_p, i_n): # a. unpack par = model.par sol = model.sol # b. figure fig = plt.figure(figsize=(12, 6)) ax_c = fig.add_subplot(1, 2, 1) ax_v = fig.add_subplot(1, 2, 2) # c. plot before c_vec = sol.q_c[t, i_p, i_n] m_vec = sol.q_m[t, i_p, i_n] ax_c.plot(m_vec, c_vec, 'o', MarkerSize=0.5, label='before') ax_c.set_title( f'$c$ ($t = {t}$, $p = {par.grid_p[i_p]:.2f}$, $n = {par.grid_n[i_n]:.2f}$)', pad=10) inv_v_vec = np.zeros(par.Na) for i_a in range(par.Na): inv_v_vec[i_a] = utility.func(c_vec[i_a], par.grid_n[i_n], par) + (-1 / sol.inv_w[t, i_p, i_n, i_a]) inv_v_vec = -1.0 / inv_v_vec ax_v.plot(m_vec, inv_v_vec, 'o', MarkerSize=0.5, label='before') ax_v.set_title( f'neg. inverse $v$ ($t = {t}$, $p = {par.grid_p[i_p]:.2f}$, $n = {par.grid_n[i_n]:.2f}$)', pad=10) # d. plot after c_vec = sol.c_keep[t, i_p, i_n, :] ax_c.plot(par.grid_m, c_vec, 'o', MarkerSize=0.5, label='after') inv_v_vec = sol.inv_v_keep[t, i_p, i_n, :] ax_v.plot(par.grid_m, inv_v_vec, 'o', MarkerSize=0.5, label='after') # e. details ax_c.legend() ax_c.set_ylabel('$c_t$') ax_c.set_ylim([c_vec[0], c_vec[-1]]) ax_v.set_ylim([inv_v_vec[0], inv_v_vec[-1]]) for ax in [ax_c, ax_v]: ax.grid(True) ax.set_xlabel('$m_t$') ax.set_xlim([par.grid_m[0], par.grid_m[-1]]) plt.show()
def value_of_choice(t, c, d, p, x, inv_v_keep, inv_v_adj, par): # a. end-of-period-assets a = x - c - d # b. continuation value w = 0 for ishock in range(par.Nshocks): # i. shocks psi = par.psi[ishock] psi_w = par.psi_w[ishock] xi = par.xi[ishock] xi_w = par.xi_w[ishock] # ii. next-period states p_plus = trans.p_plus_func(p, psi, par) n_plus = trans.n_plus_func(d, par) m_plus = trans.m_plus_func(a, p_plus, xi, par) x_plus = trans.x_plus_func(m_plus, n_plus, par) # iii. weight weight = psi_w * xi_w # iv. update inv_v_plus_keep_now = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_m, inv_v_keep[t + 1], p_plus, n_plus, m_plus) inv_v_plus_adj_now = linear_interp.interp_2d(par.grid_p, par.grid_x, inv_v_adj[t + 1], p_plus, x_plus) v_plus_now = -np.inf # huge negative value if inv_v_plus_keep_now > inv_v_plus_adj_now and inv_v_plus_keep_now > 0: v_plus_now = -1 / inv_v_plus_keep_now elif inv_v_plus_adj_now > 0: v_plus_now = -1 / inv_v_plus_adj_now w += weight * par.beta * v_plus_now # v. total value v = utility.func(c, d, par) + w return v # we are minimizing
def solve_outer(t,sol,par): # unpack output inv_v = sol.inv_v[t] inv_vm = sol.inv_vm[t] c = sol.c[t] d = sol.d[t] # loop over outer states for i_n in range(par.Nn): n = par.grid_n[i_n] # loop over m state for i_m in range(par.Nm): m = par.grid_m[i_m] # a. optimal choice d_low = 1e-8 d_high = m-1e-8 d[i_n,i_m] = golden_section_search.optimizer(obj_outer,d_low,d_high,args=(n,m,t,sol,par),tol=1e-8) # b. optimal value n_pure_c = n + d[i_n,i_m] + pens.func(d[i_n,i_m],par) m_pure_c = m - d[i_n,i_m] c[i_n,i_m] = np.fmin(linear_interp.interp_2d(par.grid_b_pd,par.grid_l,sol.c_pure_c[t],n_pure_c,m_pure_c),m_pure_c) inv_v[i_n,i_m] = -obj_outer(d[i_n,i_m],n,m,t,sol,par) # c. dcon obj_dcon = -obj_outer(0,n,m,t,sol,par) if obj_dcon > inv_v[i_n,i_m]: c[i_n,i_m] = linear_interp.interp_2d(par.grid_b_pd,par.grid_l,sol.c_pure_c[t],n,m) d[i_n,i_m] = 0 inv_v[i_n,i_m] = obj_dcon # d. con w = linear_interp.interp_2d(par.grid_b_pd,par.grid_a_pd,sol.w[t],n,0) obj_con = -1.0/(utility.func(m,par) + w) if obj_con > inv_v[i_n,i_m]: c[i_n,i_m] = m d[i_n,i_m] = 0 inv_v[i_n,i_m] = obj_con # e. derivative inv_vm[i_n,i_m] = 1.0/utility.marg_func(c[i_n,i_m],par)
def solve(t,ma,st,ra,d,sol_c,sol_m,sol_v,par): """ solve the model in the last period for singles """ # unpack (helps numba optimize) c = sol_c[t,ra,d,:] m = sol_m[:] v = sol_v[t,ra,d,:] # initialize c[:] = m[:] # optimal choice cons = (par.beta*par.gamma)**(-1/par.rho) for i in range(len(m)): if m[i] > cons: c[i] = cons else: c[i] = m[i] v[i] = utility.func(c[i],d,ma,st,par) + par.beta*par.gamma*(m[i]-c[i])
def solve(t, sol, par): """ solve the problem in the last period """ # unpack (helps numba optimize) v = sol.v[t] c = sol.c[t] # loop over states for ip in prange(par.Np): # in parallel for im in range(par.Nm): # a. states _p = par.grid_p[ip] m = par.grid_m[im] # b. optimal choice (consume everything) c[ip, im] = m # c. optimal value v[ip, im] = utility.func(c[ip, im], par)
def solve_con(out_c,out_d,out_v,w,par): # i. choices c = par.grid_m_nd d = np.zeros(c.shape) # ii. post-decision states a = np.zeros(c.shape) b = par.grid_n_nd # iii. post decision value w_con = np.zeros(c.shape) linear_interp.interp_2d_vec(par.grid_b_pd,par.grid_a_pd,w,b.ravel(),a.ravel(),w_con.ravel()) # iv. value v = utility.func(c,par) + w_con out_c[:] = c out_d[:] = d out_v[:] = v
def solve(t, sol, par): """ solve the problem in the last period """ # unpack inv_v_keep = sol.inv_v_keep[t] inv_marg_u_keep = sol.inv_marg_u_keep[t] c_keep = sol.c_keep[t] inv_v_adj = sol.inv_v_adj[t] inv_marg_u_adj = sol.inv_marg_u_adj[t] d_adj = sol.d_adj[t] c_adj = sol.c_adj[t] # a. keep for i_p in prange(par.Np): for i_n in range(par.Nn): for i_m in range(par.Nm): # i. states n = par.grid_n[i_n] m = par.grid_m[i_m] if m == 0: # forced c = 0 c_keep[i_p, i_n, i_m] = 0 inv_v_keep[i_p, i_n, i_m] = 0 inv_marg_u_keep[i_p, i_n, i_m] = 0 continue # ii. optimal choice c_keep[i_p, i_n, i_m] = m # iii. optimal value v_keep = utility.func(c_keep[i_p, i_n, i_m], n, par) inv_v_keep[i_p, i_n, i_m] = -1.0 / v_keep inv_marg_u_keep[i_p, i_n, i_m] = 1.0 / utility.marg_func( c_keep[i_p, i_n, i_m], n, par) # b. adj for i_p in prange(par.Np): for i_x in range(par.Nx): # i. states x = par.grid_x[i_x] if x == 0: # forced c = d = 0 d_adj[i_p, i_x] = 0 c_adj[i_p, i_x] = 0 inv_v_adj[i_p, i_x] = 0 inv_marg_u_adj[i_p, i_x] = 0 continue # ii. optimal choices d_low = np.fmin(x / 2, 1e-8) d_high = np.fmin(x, par.n_max) d_adj[i_p, i_x] = golden_section_search.optimizer( d_low, d_high, par.tol, obj_last_period, x, par) c_adj[i_p, i_x] = x - d_adj[i_p, i_x] # iii. optimal value v_adj = -obj_last_period(d_adj[i_p, i_x], x, par) inv_v_adj[i_p, i_x] = -1.0 / v_adj inv_marg_u_adj[i_p, i_x] = 1.0 / utility.marg_func( c_adj[i_p, i_x], d_adj[i_p, i_x], par)
def upperenvelope(out_c, out_d, out_v, holes, i_a, i_b, tri, m, n, c, d, v, Na, Nb, valid, num, w, par): # a. simplex in (a,b)-space (or similar with constrained choices) i_b_1 = i_b i_a_1 = i_a if i_b == Nb - 1: return i_b_2 = i_b + 1 i_a_2 = i_a i_b_3 = -1 # to be overwritten i_a_3 = -1 # to be overwritten if tri == 0: if i_a == 0 or i_b == Nb - 1: return i_b_3 = i_b + 1 i_a_3 = i_a - 1 else: if i_a == Na - 1: return i_b_3 = i_b i_a_3 = i_a + 1 if ~valid[i_b_1, i_a_1] or ~valid[i_b_2, i_a_2] or ~valid[i_b_3, i_a_3]: return # b. simplex in (m,n)-space m1 = m[i_b_1, i_a_1] m2 = m[i_b_2, i_a_2] m3 = m[i_b_3, i_a_3] n1 = n[i_b_1, i_a_1] n2 = n[i_b_2, i_a_2] n3 = n[i_b_3, i_a_3] # c. boundary box values and indices in common grid m_max = np.fmax(m1, np.fmax(m2, m3)) m_min = np.fmin(m1, np.fmin(m2, m3)) n_max = np.fmax(n1, np.fmax(n2, n3)) n_min = np.fmin(n1, np.fmin(n2, n3)) im_low = 0 if m_min >= 0: im_low = linear_interp.binary_search(0, par.Nm, par.grid_m, m_min) im_high = linear_interp.binary_search(0, par.Nm, par.grid_m, m_max) + 1 in_low = 0 if n_min >= 0: in_low = linear_interp.binary_search(0, par.Nn, par.grid_n, n_min) in_high = linear_interp.binary_search(0, par.Nn, par.grid_n, n_max) + 1 # correction to allow for more extrapolation im_low = np.fmax(im_low - par.egm_extrap_add, 0) im_high = np.fmin(im_high + par.egm_extrap_add, par.Nm) in_low = np.fmax(in_low - par.egm_extrap_add, 0) in_high = np.fmin(in_high + par.egm_extrap_add, par.Nn) # d. prepare barycentric interpolation denom = (n2 - n3) * (m1 - m3) + (m3 - m2) * (n1 - n3) # e. loop through common grid nodes in interior of bounding box for i_n in range(in_low, in_high): for i_m in range(im_low, im_high): # i. common grid values m_now = par.grid_m[i_m] n_now = par.grid_n[i_n] # ii. barycentric coordinates w1 = ((n2 - n3) * (m_now - m3) + (m3 - m2) * (n_now - n3)) / denom w2 = ((n3 - n1) * (m_now - m3) + (m1 - m3) * (n_now - n3)) / denom w3 = 1 - w1 - w2 # iii. exit if too much outside simplex if w1 < par.egm_extrap_w or w2 < par.egm_extrap_w or w3 < par.egm_extrap_w: continue # iv. interpolate choices if num == 1: # ucon, interpolate c and d c_interp = w1 * c[i_b_1, i_a_1] + w2 * c[ i_b_2, i_a_2] + w3 * c[i_b_3, i_a_3] d_interp = w1 * d[i_b_1, i_a_1] + w2 * d[ i_b_2, i_a_2] + w3 * d[i_b_3, i_a_3] a_interp = m_now - c_interp - d_interp b_interp = n_now + d_interp + pens.func(d_interp, par) elif num == 2: # dcon, interpolate c c_interp = w1 * c[i_b_1, i_a_1] + w2 * c[ i_b_2, i_a_2] + w3 * c[i_b_3, i_a_3] d_interp = 0.0 a_interp = m_now - c_interp - d_interp b_interp = n_now # d_interp = 0 elif num == 3: # acon, interpolate d a_interp = 0.0 d_interp = w1 * d[i_b_1, i_a_1] + w2 * d[ i_b_2, i_a_2] + w3 * d[i_b_3, i_a_3] c_interp = m_now - a_interp - d_interp b_interp = n_now + d_interp + pens.func(d_interp, par) if c_interp <= 0.0 or d_interp < 0.0 or a_interp < 0 or b_interp < 0: continue # v. value-of-choice w_interp = linear_interp.interp_2d(par.grid_b_pd, par.grid_a_pd, w, b_interp, a_interp) v_interp = utility.func(c_interp, par) + w_interp # vi. update if max if v_interp > out_v[i_n, i_m]: out_v[i_n, i_m] = v_interp out_c[i_n, i_m] = c_interp out_d[i_n, i_m] = d_interp holes[i_n, i_m] = 0
def fill_holes(out_c, out_d, out_v, holes, w, num, par): # a. locate global bounding box with content i_n_min = 0 i_n_max = par.Nn - 1 min_n = np.inf max_n = -np.inf i_m_min = 0 i_m_max = par.Nm - 1 min_m = np.inf max_m = -np.inf for i_n in range(par.Nn): for i_m in range(par.Nn): m_now = par.grid_m[i_m] n_now = par.grid_n[i_n] if holes[i_n, i_m] == 1: continue if m_now < min_m: min_m = m_now i_m_min = i_m if m_now > max_m: max_m = m_now i_m_max = i_m if n_now < min_n: min_n = n_now i_n_min = i_n if n_now > max_n: max_n = n_now i_n_max = i_n # b. loop through m, n, k nodes to detect holes i_n_max = np.fmin(i_n_max + 1, par.Nn) i_m_max = np.fmin(i_m_max + 1, par.Nm) for i_n in range(i_n_min, i_n_max): for i_m in range(i_m_min, i_m_max): if holes[i_n, i_m] == 0: # if not hole continue m_now = par.grid_m[i_m] n_now = par.grid_n[i_n] m_add = 2 n_add = 2 # loop over points close by i_n_close_min = np.fmax(0, i_n - n_add) i_n_close_max = np.fmin(i_n + n_add + 1, par.Nn) i_m_close_min = np.fmax(0, i_m - m_add) i_m_close_max = np.fmin(i_m + m_add + 1, par.Nm) for i_n_close in range(i_n_close_min, i_n_close_max): for i_m_close in range(i_m_close_min, i_m_close_max): if holes[i_n_close, i_m_close] == 1: # if itself a hole continue if num == 1: # ucon, interpolate c and d c_interp = out_c[i_n_close, i_m_close] d_interp = out_d[i_n_close, i_m_close] a_interp = m_now - c_interp - d_interp b_interp = n_now + d_interp + par.chi * np.log( 1.0 + d_interp) elif num == 2: # dcon, interpolate c c_interp = out_c[i_n_close, i_m_close] d_interp = 0.0 a_interp = m_now - c_interp - d_interp b_interp = n_now # d_interp = 0 elif num == 3: # acon, interpolate d a_interp = 0.0 d_interp = out_d[i_n_close, i_m_close] c_interp = m_now - a_interp - d_interp b_interp = n_now + d_interp + par.chi * np.log( 1.0 + d_interp) if c_interp <= 0.0 or d_interp < 0.0 or a_interp < 0 or b_interp < 0: continue # value-of-choice w_interp = linear_interp.interp_2d(par.grid_b_pd, par.grid_a_pd, w, b_interp, a_interp) v_interp = utility.func(c_interp, par) + w_interp # update if better if v_interp > out_v[i_n, i_m]: out_v[i_n, i_m] = v_interp out_c[i_n, i_m] = c_interp out_d[i_n, i_m] = d_interp