def euler_errors(sim, sol, par): # unpack euler_error = sim.euler_error euler_error_c = sim.euler_error_c for i in prange(par.simN): discrete_plus = np.zeros(1) d_plus = np.zeros(1) c_plus = np.zeros(1) a_plus = np.zeros(1) for t in range(par.simT - 1): constrained = sim.a[t, i] < par.euler_cutoff if constrained: euler_error[t, i] = np.nan euler_error_c[t, i] = np.nan continue else: LHS = utility.marg_func(sim.c[t, i], sim.d[t, i], par) RHS = 0.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(sim.p[t, i], psi, par) n_plus = trans.n_plus_func(sim.d[t, i], par) m_plus = trans.m_plus_func(sim.a[t, i], p_plus, xi, par) x_plus = trans.x_plus_func(m_plus, n_plus, par) # iii. weight weight = psi_w * xi_w # iv. next-period choices optimal_choice(t + 1, p_plus, n_plus, m_plus, x_plus, discrete_plus, d_plus, c_plus, a_plus, sol, par) # v. next-period marginal utility RHS += weight * par.beta * par.R * utility.marg_func( c_plus[0], d_plus[0], par) euler_error[t, i] = LHS - RHS euler_error_c[t, i] = sim.c[t, i]
def solve(sol,par,G2EGM=True): # a. last_period t = par.T-1 sol.m_ret[t,:] = par.grid_m_ret sol.c_ret[t,:] = sol.m_ret[t,:] v = utility.func_ret(sol.c_ret[t,:],par) sol.inv_v_ret[t,:] = -1.0/v vm = utility.marg_func(sol.c_ret[t,:],par) sol.inv_vm_ret[t,:] = 1.0/vm if G2EGM: sol.inv_vn_ret[t,:] = sol.inv_vm_ret[t,:] # b. backwards inducation for j in range(2,par.T+1): t = par.T-j # i. optimal c choice m_plus = par.Ra*par.grid_a_ret + par.yret c_plus = np.zeros(m_plus.shape) linear_interp.interp_1d_vec(sol.m_ret[t+1,:],sol.c_ret[t+1,:],m_plus,c_plus) vm_plus = utility.marg_func(c_plus,par) q = par.beta*par.Ra*vm_plus sol.c_ret[t,par.Nmcon_ret:] = utility.inv_marg_func(q,par) # ii. constraint sol.c_ret[t,:par.Nmcon_ret] = nonlinspace_jit(1e-6,sol.c_ret[t,par.Nmcon_ret]*0.999,par.Nmcon_ret,par.phi_m) # iii. end-of-period assets and value-of-choice sol.a_ret[t,par.Nmcon_ret:] = par.grid_a_ret inv_v_plus = np.zeros(m_plus.shape) linear_interp.interp_1d_vec(sol.m_ret[t+1,:],sol.inv_v_ret[t+1,:],m_plus,inv_v_plus) v_plus = -1.0/inv_v_plus v1 = utility.func_ret(sol.c_ret[t,:par.Nmcon_ret],par) + par.beta*v_plus[0] v2 = utility.func_ret(sol.c_ret[t,par.Nmcon_ret:],par) + par.beta*v_plus sol.inv_v_ret[t,:par.Nmcon_ret] = -1.0/v1 sol.inv_v_ret[t,par.Nmcon_ret:] = -1.0/v2 # iv. endogenous grid sol.m_ret[t,:] = sol.a_ret[t,:] + sol.c_ret[t,:] # v. marginal v vm = utility.marg_func(sol.c_ret[t,:],par) sol.inv_vm_ret[t,:] = 1.0/vm if G2EGM: sol.inv_vn_ret[t,:] = sol.inv_vm_ret[t,:]
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 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 solve(t,sol,par): w = sol.w[t] wa = sol.wa[t] wb = sol.wb[t] # a. solve each segment solve_ucon(sol.ucon_c[t,:,:],sol.ucon_d[t,:,:],sol.ucon_v[t,:,:],w,wa,wb,par) solve_dcon(sol.dcon_c[t,:,:],sol.dcon_d[t,:,:],sol.dcon_v[t,:,:],w,wa,par) solve_acon(sol.acon_c[t,:,:],sol.acon_d[t,:,:],sol.acon_v[t,:,:],w,wb,par) solve_con(sol.con_c[t,:,:],sol.con_d[t,:,:],sol.con_v[t,:,:],w,par) # b. upper envelope seg_max = np.zeros(4) for i_n in range(par.Nn): for i_m in range(par.Nm): # i. find max seg_max[0] = sol.ucon_v[t,i_n,i_m] seg_max[1] = sol.dcon_v[t,i_n,i_m] seg_max[2] = sol.acon_v[t,i_n,i_m] seg_max[3] = sol.con_v[t,i_n,i_m] i = np.argmax(seg_max) # ii. over-arching optimal choices sol.inv_v[t,i_n,i_m] = -1.0/seg_max[i] if i == 0: sol.c[t,i_n,i_m] = sol.ucon_c[t,i_n,i_m] sol.d[t,i_n,i_m] = sol.ucon_d[t,i_n,i_m] elif i == 1: sol.c[t,i_n,i_m] = sol.dcon_c[t,i_n,i_m] sol.d[t,i_n,i_m] = sol.dcon_d[t,i_n,i_m] elif i == 2: sol.c[t,i_n,i_m] = sol.acon_c[t,i_n,i_m] sol.d[t,i_n,i_m] = sol.acon_d[t,i_n,i_m] elif i == 3: sol.c[t,i_n,i_m] = sol.con_c[t,i_n,i_m] sol.d[t,i_n,i_m] = sol.con_d[t,i_n,i_m] # c. derivatives # i. m vm = utility.marg_func(sol.c[t],par) sol.inv_vm[t,:,:] = 1.0/vm # ii. n a = par.grid_m_nd - sol.c[t] - sol.d[t] b = par.grid_n_nd + sol.d[t] + pens.func(sol.d[t],par) wb_now = np.zeros(a.shape) linear_interp.interp_2d_vec(par.grid_b_pd,par.grid_a_pd,wb,b.ravel(),a.ravel(),wb_now.ravel()) vn = wb_now sol.inv_vn[t,:,:] = 1.0/vn
def compute_q(sol, par, t, t_plus): """ compute the post-decision function q """ # unpack (helps numba optimize) q = sol.q # loop over delta for idelta in prange(par.Ndelta): # clean-up q[t, idelta, :] = 0 # temp m_plus = np.empty(par.Na) c_plus = np.empty(par.Na) # loop over shock 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] weight = psi_w * xi_w # ii. next-period extra income component delta_plus = par.grid_delta[idelta] / (psi * par.G) if t == 0: delta_plus *= par.zeta # ii. next-period cash-on-hand for ia in range(par.Na): m_plus[ia] = par.R * par.grid_a[ia] / (psi * par.G) + xi # iii. next-period consumption if par.Ndelta > 1: prep = linear_interp.interp_2d_prep(par.grid_delta, delta_plus, par.Na) else: prep = linear_interp.interp_1d_prep(par.Na) if par.Ndelta > 1: linear_interp.interp_2d_only_last_vec_mon( prep, par.grid_delta, par.grid_m, sol.c[t_plus], delta_plus, m_plus, c_plus) else: linear_interp.interp_1d_vec_mon(prep, par.grid_m, sol.c[t_plus, idelta], m_plus, c_plus) # iv. accumulate all for ia in range(par.Na): q[t, idelta, ia] += weight * par.R * par.beta * utility.marg_func( par.G * psi * c_plus[ia], par)
def shocks_GH(t, inc_no_shock, inc, w, c, m, v, par, d_plus): """ compute v_plus_raw and avg_marg_u_plus using GaussHermite integration if necessary """ # a. initialize c_plus_interp = np.zeros((4, inc_no_shock.size)) v_plus_interp = np.zeros((4, inc_no_shock.size)) v_plus_raw = np.zeros(inc_no_shock.size) avg_marg_u_plus = np.zeros(inc_no_shock.size) prep = linear_interp.interp_1d_prep( len(inc_no_shock )) # save the position of numbers to speed up interpolation # b. loop over GH-nodes for i in range(len(w)): m_plus = inc_no_shock + inc[i] # 1. interpolate for d in d_plus: linear_interp.interp_1d_vec_mon(prep, m[:], c[d, :], m_plus, c_plus_interp[d, :]) linear_interp.interp_1d_vec_mon_rep(prep, m[:], v[d, :], m_plus, v_plus_interp[d, :]) # 2. logsum and v_plus_raw if len(d_plus) == 1: # no taste shocks v_plus_raw += w[i] * v_plus_interp[d_plus[0], :] avg_marg_u_plus += w[i] * utility.marg_func( c_plus_interp[d_plus[0], :], par) elif len(d_plus) == 2: # taste shocks logsum, prob = funs.logsum2(v_plus_interp[d_plus, :], par) v_plus_raw += w[i] * logsum[0, :] marg_u_plus = prob[d_plus[0], :] * utility.marg_func( c_plus_interp[d_plus[0], :], par) + (1 - prob[d_plus[0], :]) * utility.marg_func( c_plus_interp[d_plus[1], :], par) avg_marg_u_plus += w[i] * marg_u_plus elif len(d_plus) == 4: # both are working logsum, prob = funs.logsum4(v_plus_interp[d_plus, :], par) v_plus_raw += w[i] * logsum[0, :] marg_u_plus = ( prob[0, :] * utility.marg_func(c_plus_interp[0, :], par) + prob[1, :] * utility.marg_func(c_plus_interp[1, :], par) + prob[2, :] * utility.marg_func(c_plus_interp[2, :], par) + prob[3, :] * utility.marg_func(c_plus_interp[3, :], par)) avg_marg_u_plus += w[i] * marg_u_plus # c. return results return v_plus_raw, avg_marg_u_plus
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 euler_error(t, ma, st, ra, euler, sol, par, sim, idx, ds): """ compute euler errors for single model""" if idx.size > 0: # unpack sol_m = sol.m[:] sol_c = sol.c[:, ma, st] sol_v = sol.v[:, ma, st] c = sim.c[:, t] m = sim.m[:, t] a = sim.a[:, t] tol = par.tol pi_plus = transitions.survival_lookup_single(t + 1, ma, st, par) # 1. indices idx_in = idx[(tol < c[idx]) & (c[idx] < m[idx] - tol)] # inner solution # 2. prep if ds == 1: D = np.array([0, 1]) elif ds == 0: D = np.array([0]) idx_unsort = np.argsort(np.argsort(a[idx_in])) # indices to unsort a a_sort = np.sort( a[idx_in] ) # sort a since post_decision.compute assumes a is monotone # 3. lhs and rhs avg_marg_u_plus = post_decision.compute(t, ma, st, ra, D, sol_c, sol_m, sol_v, a_sort, par)[1] lhs = utility.marg_func(c[idx_in], par) rhs = par.beta * ( par.R * pi_plus * np.take(avg_marg_u_plus[ds], idx_unsort) + (1 - pi_plus) * par.gamma) fill_arr(euler[:, t], idx_in, lhs - rhs)
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 compute_wq(t, sol, par, compute_w=False, compute_q=False): """ compute the post-decision functions w and/or q """ # this is a variant of Algorithm 5 in Druedahl (2019): A Guide to Solve Non-Convex Consumption-Saving Problems # unpack (helps numba optimize) w = sol.w q = sol.q # loop over outermost post-decision state for ip in prange(par.Np): # in parallel # a. permanent income p = par.grid_p[ip] # b. allocate containers and initialize at zero m_plus = np.empty(par.Na) if compute_w: w[ip, :] = 0 v_plus = np.empty(par.Na) if compute_q: q[ip, :] = 0 c_plus = np.empty(par.Na) # c. loop over shocks and then end-of-period assets 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 income p_plus = p * psi y_plus = p_plus * xi # iii. prepare interpolation in p direction prep = linear_interp.interp_2d_prep(par.grid_p, p_plus, par.Na) # iv. weight weight = psi_w * xi_w # v. next-period cash-on-hand and interpolate for ia in range(par.Na): m_plus[ia] = par.R * par.grid_a[ia] + y_plus # v_plus if compute_w: linear_interp.interp_2d_only_last_vec_mon( prep, par.grid_p, par.grid_m, sol.v[t + 1], p_plus, m_plus, v_plus) # c_plus if compute_q: linear_interp.interp_2d_only_last_vec_mon( prep, par.grid_p, par.grid_m, sol.c[t + 1], p_plus, m_plus, c_plus) # vi. accumulate all if compute_w: for ia in range(par.Na): w[ip, ia] += weight * par.beta * v_plus[ia] if compute_q: for ia in range(par.Na): q[ip, ia] += weight * par.R * par.beta * utility.marg_func( c_plus[ia], par)
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 euler_errors(sim, sol, par): # unpack euler_error = sim.euler_error euler_error_c = sim.euler_error_c for i in prange(par.simN): discrete_plus = np.zeros(1) d_plus = np.zeros(1) d1_plus = np.zeros(1) d2_plus = np.zeros(1) c_plus = np.zeros(1) a_plus = np.zeros(1) for t in range(par.T - 1): constrained = sim.a[t, i] < par.euler_cutoff if constrained: euler_error[t, i] = np.nan euler_error_c[t, i] = np.nan continue else: RHS = 0.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(sim.p[t, i], psi, par) if par.do_2d: n1_plus = trans.n1_plus_func(sim.d1[t, i], par) n2_plus = trans.n2_plus_func(sim.d2[t, i], par) else: n_plus = trans.n_plus_func(sim.d[t, i], par) m_plus = trans.m_plus_func(sim.a[t, i], p_plus, xi, par) # iii. weight weight = psi_w * xi_w # iv. next-period choices if par.do_2d: optimal_choice_2d(t + 1, p_plus, n1_plus, n2_plus, m_plus, discrete_plus, d1_plus, d2_plus, c_plus, a_plus, sol, par) else: optimal_choice(t + 1, p_plus, n_plus, m_plus, discrete_plus, d_plus, c_plus, a_plus, sol, par) # v. next-period marginal utility if par.do_2d: RHS += weight * par.beta * par.R * utility.marg_func_2d( c_plus[0], d1_plus[0], d2_plus[0], par) else: RHS += weight * par.beta * par.R * utility.marg_func( c_plus[0], d_plus[0], par) if par.do_2d: euler_error[t, i] = sim.c[t, i] - utility.inv_marg_func_2d( RHS, sim.d1[t, i], sim.d2[t, i], par) else: euler_error[t, i] = sim.c[t, i] - utility.inv_marg_func( RHS, sim.d[t, i], par) euler_error_c[t, i] = sim.c[t, i]