def solve_acon(out_c,out_d,out_v,w,wb,par): num = 3 # i. allocate c = np.zeros((par.Nc_acon,par.Nb_acon)) d = np.zeros((par.Nc_acon,par.Nb_acon)) a = np.zeros((par.Nc_acon,par.Nb_acon)) b = np.zeros((par.Nc_acon,par.Nb_acon)) w_acon = np.zeros((par.Nc_acon,par.Nb_acon)) for i_b in range(par.Nb_acon): # ii. setup wb_acon = linear_interp.interp_2d(par.grid_b_pd,par.grid_a_pd,wb,par.b_acon[i_b],0) c_min = utility.inv_marg_func((par.chi+1)*wb_acon,par) c_max = utility.inv_marg_func(wb_acon,par) c[:,i_b] = misc.nonlinspace_jit(c_min,c_max,par.Nc_acon,par.phi_m) # iii. choices d[:,i_b] = par.chi/(utility.marg_func(c[:,i_b],par)/(wb_acon)-1)-1 # iv. post-decision states and value function b[:,i_b] = par.b_acon[i_b] w_acon[:,i_b] = linear_interp.interp_2d(par.grid_b_pd,par.grid_a_pd,w,par.b_acon[i_b],0) # v. states and value m,n,v = inv_mn_and_v(c,d,a,b,w_acon,par) # vi. upperenvelope and interp to common upperenvelope.compute(out_c,out_d,out_v,m,n,c,d,v,num,w,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 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 solve_adj(inv_v_keep,c_keep,d_ubar,sigma,n_max,Np,Nx,grid_n,grid_m,grid_x,P_d_p): # P_d_p, """solve bellman equation for adjusters using nvfi""" # unpack output adj_shape = (Np,Nx) inv_v_adj = np.zeros(adj_shape) d_adj = np.zeros(adj_shape) c_adj = np.zeros(adj_shape) # loop over outer states for i_p in prange(Np): # loop over x state for i_x in range(Nx): # a. cash-on-hand x = grid_x[i_x] if i_x == 0: d_adj[i_p,i_x] = 0 c_adj[i_p,i_x] = 0 inv_v_adj[i_p,i_x] = 0 continue # b. optimal choice d_low = np.fmin(x/2,1e-8) d_high = np.fmin(x/P_d_p,n_max) # cash on hand grid now x = m + (1-tau)*n + B.C. (eg 0.5, or collateral eg. coll_ratio*n) d_adj[i_p,i_x] = golden_section_search.optimizer(obj_adj,d_low,d_high,args=(x,inv_v_keep[i_p],grid_n,grid_m,P_d_p),tol=1e-8) # c. optimal value m = x - P_d_p*d_adj[i_p,i_x] c_adj[i_p,i_x] = linear_interp.interp_2d(grid_n,grid_m,c_keep[i_p],d_adj[i_p,i_x],m) inv_v_adj[i_p,i_x] = -obj_adj(d_adj[i_p,i_x],x,inv_v_keep[i_p],grid_n,grid_m,P_d_p) return inv_v_adj, d_adj, c_adj
def compute_wq_simple(t, sol, par, compute_w=False, compute_q=False): """ compute the post-decision functions w and/or q """ # this is a variant of Algorithm 3 in Druedahl (2019): A Guide to Solve Non-Convex Consumption-Saving Problems # note: same result as compute_wq, simpler code, but much slower # unpack (helps numba optimize) w = sol.w q = sol.q # loop over outermost post-decision state for ip in prange(par.Np): # in parallel for ia in range(par.Na): # initialize at zero if compute_w: w[ip, ia] = 0 if compute_q: q[ip, ia] = 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 = par.grid_p[ip] * psi y_plus = p_plus * xi m_plus = par.R * par.grid_a[ia] + y_plus # iii. weights weight = psi_w * xi_w # iv. interpolate and accumulate if compute_w: w[ip, ia] += weight * par.beta * linear_interp.interp_2d( par.grid_p, par.grid_m, sol.v[t + 1], p_plus, m_plus) if compute_q: c_plus_temp = linear_interp.interp_2d( par.grid_p, par.grid_m, sol.c[t + 1], p_plus, m_plus) q[ip, ia] += weight * par.R * par.beta * utility.marg_func( c_plus_temp, par)
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 optimal_choice(t, p, n, m, discrete, d, c, a, sol, par): x = trans.x_plus_func(m, n, par) # a. discrete choice inv_v_keep = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_m, sol.inv_v_keep[t], p, n, m) inv_v_adj = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.inv_v_adj[t], p, x) adjust = inv_v_adj > inv_v_keep # b. continuous choices if adjust: discrete[0] = 1 d[0] = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.d_adj[t], p, x) c[0] = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.c_adj[t], p, x) tot = d[0] + c[0] if tot > x: d[0] *= x / tot c[0] *= x / tot a[0] = 0.0 else: a[0] = x - tot else: discrete[0] = 0 d[0] = n c[0] = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_m, sol.c_keep[t], p, n, m) if c[0] > m: c[0] = m a[0] = 0.0 else: a[0] = m - c[0]
def obj_outer(d,n,m,t,sol,par): """ evaluate bellman equation """ # a. cash-on-hand m_pure_c = m-d # b. durables n_pure_c = n + d + pens.func(d,par) # c. value-of-choice return -linear_interp.interp_2d(par.grid_b_pd,par.grid_l,sol.inv_v_pure_c[t],n_pure_c,m_pure_c) # we are minimizing
def obj_adj(d,x,inv_v_keep,grid_n,grid_m): """ evaluate bellman equation """ # a. cash-on-hand m = x-d # b. durables n = d # c. value-of-choice return -linear_interp.interp_2d(grid_n,grid_m,inv_v_keep,n,m) # we are minimizing
def solve_adj(t, sol, par): """solve bellman equation for adjusters using nvfi""" # unpack output inv_v = sol.inv_v_adj[t] inv_marg_u = sol.inv_marg_u_adj[t] d = sol.d_adj[t] c = sol.c_adj[t] # unpack input inv_v_keep = sol.inv_v_keep[t] c_keep = sol.c_keep[t] grid_n = par.grid_n grid_m = par.grid_m d_ubar = par.d_ubar alpha = par.alpha rho = par.rho # loop over outer states for i_p in prange(par.Np): # loop over x state for i_x in range(par.Nx): # a. cash-on-hand x = par.grid_x[i_x] if i_x == 0: d[i_p, i_x] = 0 c[i_p, i_x] = 0 inv_v[i_p, i_x] = 0 if par.do_marg_u: inv_marg_u[i_p, i_x] = 0 continue # b. optimal choice d_low = np.fmin(x / 2, 1e-8) d_high = np.fmin(x, par.n_max) d[i_p, i_x] = golden_section_search.optimizer(obj_adj, d_low, d_high, args=(x, inv_v_keep[i_p], grid_n, grid_m), tol=par.tol) # c. optimal value m = x - d[i_p, i_x] c[i_p, i_x] = linear_interp.interp_2d(par.grid_n, par.grid_m, c_keep[i_p], d[i_p, i_x], m) inv_v[i_p, i_x] = -obj_adj(d[i_p, i_x], x, inv_v_keep[i_p], grid_n, grid_m) if par.do_marg_u: inv_marg_u[i_p, i_x] = 1 / utility.marg_func_nopar( c[i_p, i_x], d[i_p, i_x], d_ubar, alpha, rho)
def run(simT, simN, b_policy, d_policy, c_policy, e_grid, b_grid, d_grid, ergodic, e_cum, Ne, sim_p, sim_b, sim_d, sim_c, unif): # i. initial guess on productivity distribution for i in range(Ne): if i == 0: sim_p[0, 0:int(ergodic[i])] = i else: sim_p[ 0, int(np.sum(ergodic[:i])):int(np.sum(ergodic[:i]) + ergodic[i])] = i # ii. simulate for t in range(1, simT): for n in prange( simN): # parallelize inner loop as time needs to be consistent # i. Markov Chain shock sim_p[t, n] = choice(e_cum[int(sim_p[t - 1, n])], unif[t, n]) # ii. last period d,b b_minus = sim_b[t - 1, n] d_minus = sim_d[t - 1, n] # iii. find current d,b by interpolation of policy functions sim_d[t, n] = linear_interp.interp_2d( b_grid, d_grid, d_policy[int(sim_p[t, n]), :, :], b_minus, d_minus) sim_b[t, n] = linear_interp.interp_2d( b_grid, d_grid, b_policy[int(sim_p[t, n]), :, :], b_minus, d_minus) sim_c[t, n] = linear_interp.interp_2d( b_grid, d_grid, c_policy[int(sim_p[t, n]), :, :], b_minus, d_minus) return sim_b, sim_d, sim_c
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 lifecycle(sim, sol, par): """ simulate full life-cycle """ # unpack (to help numba optimize) p = sim.p m = sim.m c = sim.c a = sim.a for t in range(par.simT): for i in prange(par.simN): # in parallel # a. beginning of period states if t == 0: p[t, i] = 1 m[t, i] = 1 else: p[t, i] = sim.psi[t, i] * p[t - 1, i] m[t, i] = par.R * a[t - 1, i] + sim.xi[t, i] * p[t, i] # b. choices c[t, i] = linear_interp.interp_2d(par.grid_p, par.grid_m, sol.c[t], p[t, i], m[t, i]) a[t, i] = m[t, i] - c[t, i]
def euler(sim, sol, par): euler = sim.euler # a. grids min_m = 0.50 min_n = 0.01 m_max = 5.00 n_max = 5.00 n_grid = np.linspace(min_n, n_max, par.eulerK) m_grid = np.linspace(min_m, m_max, par.eulerK) # b. loop over time for t in range(par.T - 1): for i_n in range(par.eulerK): for i_m in range(par.eulerK): # i. states n = n_grid[i_n] m = m_grid[i_m] m_retire = m_grid[i_m] + n_grid[i_n] # ii. discrete choice inv_v_retire = linear_interp.interp_1d(sol.m_ret[t], sol.inv_v_ret[t], m_retire) inv_v = linear_interp.interp_2d(par.grid_n, par.grid_m, sol.inv_v[t], n, m) if inv_v_retire > inv_v: continue # iii. continuous choice c = np.fmin( linear_interp.interp_2d(par.grid_n, par.grid_m, sol.c[t], n, m), m) d = np.fmax( linear_interp.interp_2d(par.grid_n, par.grid_m, sol.d[t], n, m), 0) a = m - c - d b = n + d + pens.func(d, par) if a < 0.001: continue # iv. shocks RHS = 0 for i_eta in range(par.Neta): # o. state variables n_plus = par.Rb * b m_plus = par.Ra * a + par.eta[i_eta] m_retire_plus = m_plus + n_plus # oo. discrete choice inv_v_retire = linear_interp.interp_1d( sol.m_ret[t + 1], sol.inv_v_ret[t + 1], m_retire_plus) inv_v = linear_interp.interp_2d(par.grid_n, par.grid_m, sol.inv_v[t + 1], n_plus, m_plus) # ooo. continous choice if inv_v_retire > inv_v: c_plus = np.fmin( linear_interp.interp_1d(sol.m_ret[t + 1], sol.c_ret[t + 1], m_retire_plus), m_retire_plus) else: c_plus = np.fmin( linear_interp.interp_2d(par.grid_n, par.grid_m, sol.c[t + 1], n_plus, m_plus), m_plus) # oooo. accumulate RHS += par.w_eta[ i_eta] * par.beta * par.Ra * utility.marg_func( c_plus, par) # v. euler error euler_raw = c - utility.inv_marg_func(RHS, par) euler[t, i_m, i_n] = np.log10(np.abs(euler_raw / c) + 1e-16)
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
def optimal_choice_2d(t, p, n1, n2, m, discrete, d1, d2, c, a, sol, par): # a. discrete choice inv_v = 0 inv_v_keep = linear_interp.interp_4d(par.grid_p, par.grid_n, par.grid_n, par.grid_m, sol.inv_v_keep_2d[t], p, n1, n2, m) x_full = m + (1 - par.tau1) * n1 + (1 - par.tau2) * n2 inv_v_adj_full = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.inv_v_adj_full_2d[t], p, x_full) x_d1 = m + (1 - par.tau1) * n1 inv_v_adj_d1 = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_x, sol.inv_v_adj_d1_2d[t], p, n2, x_d1) x_d2 = m + (1 - par.tau2) * n2 inv_v_adj_d2 = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_x, sol.inv_v_adj_d2_2d[t], p, n1, x_d2) keep = False adj_full = False adj_d1 = False adj_d2 = False if inv_v_keep > inv_v: inv_v = inv_v_keep keep = True if inv_v_adj_full > inv_v: inv_v = inv_v_adj_full keep = False adj_full = True if inv_v_adj_d1 > inv_v: inv_v = inv_v_adj_d1 keep = False adj_full = False adj_d1 = True if inv_v_adj_d2 > inv_v: inv_v = inv_v_adj_d2 keep = False adj_full = False adj_d1 = False adj_d2 = True # b. continuous choices if keep: discrete[0] = 0 d1[0] = n1 d2[0] = n2 c[0] = linear_interp.interp_4d(par.grid_p, par.grid_n, par.grid_n, par.grid_m, sol.c_keep_2d[t], p, n1, n2, m) if c[0] > m: c[0] = m a[0] = 0.0 else: a[0] = m - c[0] elif adj_full: discrete[0] = 1 d1[0] = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.d1_adj_full_2d[t], p, x_full) d2[0] = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.d2_adj_full_2d[t], p, x_full) c[0] = linear_interp.interp_2d(par.grid_p, par.grid_x, sol.c_adj_full_2d[t], p, x_full) tot = d1[0] + d2[0] + c[0] if tot > x_full: d1[0] *= x_full / tot d2[0] *= x_full / tot c[0] *= x_full / tot a[0] = 0.0 else: a[0] = x_full - tot elif adj_d1: discrete[0] = 2 d1[0] = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_x, sol.d1_adj_d1_2d[t], p, n2, x_d1) d2[0] = n2 c[0] = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_x, sol.c_adj_d1_2d[t], p, n2, x_d1) tot = d1[0] + c[0] if tot > x_d1: d1[0] *= x_d1 / tot c[0] *= x_d1 / tot a[0] = 0.0 else: a[0] = x_d1 - tot elif adj_d2: discrete[0] = 3 d1[0] = n1 d2[0] = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_x, sol.d2_adj_d2_2d[t], p, n1, x_d2) c[0] = linear_interp.interp_3d(par.grid_p, par.grid_n, par.grid_x, sol.c_adj_d2_2d[t], p, n1, x_d2) tot = d2[0] + c[0] if tot > x_d2: d2[0] *= x_d2 / tot c[0] *= x_d2 / tot a[0] = 0.0 else: a[0] = x_d2 - tot