def cupyfy(self): # this sends copies of important objects to cupy # only things that are going to be reused are worth storing if not cupy: self.cupy = None return stuff = dict() stuff['theta_orig_on_fine'] = cp.array(self.theta_orig_on_fine,dtype=self.dtype) stuff['thetagrid'] = cp.array(self.thetagrid,dtype=self.dtype) stuff['thetagrid_fine'] = cp.array(self.thetagrid_fine,dtype=self.dtype) stuff['v_thetagrid_fine'] = VecOnGrid(stuff['thetagrid'],stuff['thetagrid_fine'],force_cupy=True) stuff['agrid_c'] = cp.array(self.agrid_c,dtype=self.dtype) stuff['agrid_s'] = cp.array(self.agrid_s,dtype=self.dtype) stuff['sgrid_c'] = cp.array(self.sgrid_c,dtype=self.dtype) stuff['sgrid_s'] = cp.array(self.sgrid_s,dtype=self.dtype) stuff['vsgrid_c'] = VecOnGrid(self.agrid_c,self.sgrid_c,force_cupy=True) stuff['vsgrid_s'] = VecOnGrid(self.agrid_s,self.sgrid_s,force_cupy=True) stuff['mgrid_c'] = cp.array(self.mgrid_c,dtype=self.dtype) stuff['mgrid_s'] = cp.array(self.mgrid_s,dtype=self.dtype) uu = self.u_precomputed upc = {key : {e: cp.array(uu[key][e]) for e in uu[key]} for key in uu} stuff['u_precomputed'] = upc self.cupy = namedtuple('cupy',stuff.keys())(**stuff)
def get_asset_grids(self): self.agrids = dict() g = self.agrids factor_c = 2.0 pwr = 0.0 pivot = 0.1 g['na_s'] = 40 g['na_c'] = 50 g['amin'] = 0.0 g['amax_s'] = 40.0 g['amax_c'] = factor_c * g['amax_s'] def flex_space(amin, amax, na): if pwr == 0.0: return -pivot + np.exp( np.linspace(np.log(amin + pivot), np.log(amax + pivot), na)) else: return -pivot + (np.linspace( (amin + pivot)**pwr, (amax + pivot)**pwr, na))**(1 / pwr) g['agrid_s'] = flex_space(g['amin'], g['amax_s'], g['na_s']) g['agrid_c'] = flex_space(g['amin'], g['amax_c'], g['na_c']) n_between = 7 da_min = 0.01 da_max = 0.25 g['sgrid_s'] = build_s_grid(g['agrid_s'], n_between, da_min, da_max) g['sgrid_c'] = build_s_grid(g['agrid_c'], n_between, da_min, da_max) g['v_sgrid_s'] = VecOnGrid(g['agrid_s'], g['sgrid_s']) g['v_sgrid_c'] = VecOnGrid(g['agrid_c'], g['sgrid_c'])
def v_div_byshare(setup, dc, t, sc, share_fem, share_mal, Vmale, Vfemale, izf, izm, shrs=None, cost_fem=0.0, cost_mal=0.0): # this produces value of divorce for gridpoints given possibly different # shares of how assets are divided. # Returns Vf_divorce, Vm_divorce -- values of singles in case of divorce # matched to the gridpionts for couples # optional cost_fem and cost_mal are monetary costs of divorce if shrs is None: shrs = shrs_def Vm_divorce_M, Vf_divorce_M = v_div_allsplits(setup, dc, t, sc, Vmale, Vfemale, izm, izf, shrs=shrs, cost_fem=cost_fem, cost_mal=cost_mal) # share of assets that goes to the female # this has many repetative values but it turns out it does not matter much fem_gets = VecOnGrid(np.array(shrs), share_fem) mal_gets = VecOnGrid(np.array(shrs), share_mal) i_fem = fem_gets.i wn_fem = fem_gets.wnext i_mal = mal_gets.i wn_mal = mal_gets.wnext inds_exo = np.arange(setup.pars['nexo_t'][t + 1]) if len(Vfemale[:, 0]) > 1: Vf_divorce = (1-wn_fem[None,:])*Vf_divorce_M[:,inds_exo,i_fem] + \ wn_fem[None,:]*Vf_divorce_M[:,inds_exo,i_fem+1] Vm_divorce = (1-wn_mal[None,:])*Vm_divorce_M[:,inds_exo,i_mal] + \ wn_mal[None,:]*Vm_divorce_M[:,inds_exo,i_mal+1] else: Vf_divorce = Vf_divorce_M[0, :, 0] Vm_divorce = Vm_divorce_M[0, :, 0] return Vf_divorce, Vm_divorce
def v_div_vartheta(setup,dc,t,sc,Vmale,Vfemale,izf,izm, cost_fem=0.0,cost_mal=0.0, fun=lambda x : (x,1-x) ): # this produces value of divorce for gridpoints given possibly different # shares of how assets are divided. # Returns Vf_divorce, Vm_divorce -- values of singles in case of divorce # matched to the gridpionts for couples # optional cost_fem and cost_mal are monetary costs of divorce shrs = setup.thetagrid # these are interpolation points Vm_divorce_M, Vf_divorce_M = v_div_allsplits(setup,dc,t,sc, Vmale,Vfemale,izm,izf, shrs=shrs,cost_fem=cost_fem,cost_mal=cost_mal) # share of assets that goes to the female # this has many repetative values but it turns out it does not matter much share_fem, share_mal = fun(setup.thetagrid) fem_gets = VecOnGrid(np.array(shrs),share_fem) mal_gets = VecOnGrid(np.array(shrs),share_mal) i_fem = fem_gets.i wn_fem = fem_gets.wnext wt_fem = setup.dtype(1) - wn_fem i_mal = mal_gets.i wn_mal = mal_gets.wnext wt_mal = setup.dtype(1) - wn_mal Vf_divorce = wt_fem[None,None,:]*Vf_divorce_M[:,:,i_fem] + \ wn_fem[None,None,:]*Vf_divorce_M[:,:,i_fem+1] Vm_divorce = wt_mal[None,None,:]*Vm_divorce_M[:,:,i_mal] + \ wn_mal[None,None,:]*Vm_divorce_M[:,:,i_mal+1] assert Vf_divorce.dtype == setup.dtype return Vf_divorce, Vm_divorce
def v_div_allsplits(setup, dc, t, sc, Vmale, Vfemale, izm, izf, shrs=None, cost_fem=0.0, cost_mal=0.0): if shrs is None: shrs = shrs_def # grid on possible assets divisions if len(Vmale[:, 0]) < 2: shrs = [0.5] shp = (sc.size, izm.size, len(shrs)) Vm_divorce_M = np.zeros(shp) Vf_divorce_M = np.zeros(shp) # find utilities of divorce for different divisions of assets if len(Vmale[:, 0]) > 2: for i, shr in enumerate(shrs): sv_m = VecOnGrid(setup.agrid_s, shr * sc - cost_mal) sv_f = VecOnGrid(setup.agrid_s, shr * sc - cost_fem) Vm_divorce_M[..., i] = sv_m.apply( Vmale, axis=0, take=(1, izm), reshape_i=True) - dc.u_lost_m Vf_divorce_M[..., i] = sv_f.apply( Vfemale, axis=0, take=(1, izf), reshape_i=True) - dc.u_lost_f else: Vm_divorce_M[..., 0] = Vmale[:, izm] - dc.u_lost_m Vf_divorce_M[..., 0] = Vfemale[:, izf] - dc.u_lost_f return Vm_divorce_M, Vf_divorce_M
def v_div_allsplits(setup, dc, t, sc, Vmale, Vfemale, izm, izf, shrs=None, cost_fem=0.0, cost_mal=0.0): if shrs is None: shrs = shrs_def # grid on possible assets divisions shp = (sc.size, izm.size, len(shrs)) Vm_divorce_M = np.zeros(shp) Vf_divorce_M = np.zeros(shp) # !!! potential problem is that when cupy is forced grids get converted many times # find utilities of divorce for different divisions of assets for i, shr in enumerate(shrs): sv_m = VecOnGrid(setup.agrid_s, shr * sc - cost_mal, force_cupy=gpu) sv_f = VecOnGrid(setup.agrid_s, shr * sc - cost_fem, force_cupy=gpu) Vm_divorce_M[..., i] = sv_m.apply( Vmale, axis=0, take=(1, izm), reshape_i=True) - dc.u_lost_m Vf_divorce_M[..., i] = sv_f.apply( Vfemale, axis=0, take=(1, izf), reshape_i=True) - dc.u_lost_f return Vm_divorce_M, Vf_divorce_M
def get_theta_grid(self): self.theta_grid = dict() tg = self.theta_grid ntheta = 11 ntheta_fine = 121 # preliminary theta_min = 0.01 theta_max = 0.99 tg['theta_grid_coarse'] = np.linspace(theta_min, theta_max, ntheta) tg['ntheta_coarse'] = ntheta tfine = np.unique( np.concatenate( (np.linspace(theta_min, theta_max, ntheta_fine), tg['theta_grid_coarse']))) tg['theta_gird_fine'] = tfine tg['ntheta_fine'] = tfine.size tg['v_theta'] = VecOnGrid(tg['theta_grid_coarse'], tg['theta_gird_fine'])
def v_mar_igrid(setup, t, V, icouple, ind_or_inds, *, female, giveabirth, uloss_fem, uloss_mal, uloss_fem_single=0.0, uloss_mal_single=0.0, unplanned_pregnancy=False, interpolate=True, return_all=False): # this returns value functions for couple that entered the last period with # (s,Z,theta) from the grid and is allowed to renegotiate them or breakup if giveabirth: coup = 'Couple and child' else: coup = 'Couple, no children' p_access, abortion_costs = setup.pars['p_abortion_access'], setup.pars[ 'abortion_costs'] if unplanned_pregnancy: assert giveabirth Vfem = (1-p_access)*V['Female and child']['V'] + \ p_access*np.maximum(V['Female and child']['V'],V['Female, single']['V']-abortion_costs) else: Vfem = V['Female, single']['V'] # import objects agrid_c = setup.agrid_c agrid_s = setup.agrid_s gamma = setup.pars['m_bargaining_weight'] VMval_single, VFval_single = V['Male, single'][ 'V'] - uloss_mal_single, Vfem - uloss_fem_single VMval_postren, VFval_postren = V[coup]['VM'] - uloss_mal, V[coup][ 'VF'] - uloss_fem # unify dimensions # substantial part ind, izf, izm, ipsi = setup.all_indices(t, ind_or_inds) #sc = sf+sm # savings of couple s_partner = agrid_c[icouple] - agrid_s # we assume all points on grid # this implicitly trims negative or too large values s_partner_v = VecOnGrid(agrid_s, s_partner, trim=True) # this applies them if female: Vfs = VFval_single[:, izf] Vms = s_partner_v.apply(VMval_single, axis=0, take=(1, izm)) else: Vms = VMval_single[:, izm] Vfs = s_partner_v.apply(VFval_single, axis=0, take=(1, izf)) do_abortion = ((V['Female, single']['V'][:, izf] - abortion_costs) >= V['Female and child']['V'][:, izf]) expnd = lambda x: setup.v_thetagrid_fine.apply(x, axis=2) Vmm, Vfm = (expnd(x[:, ind, :]) for x in (VMval_postren, VFval_postren)) ins = [Vfm, Vmm, Vfs, Vms, gamma] ins = [setup.dtype(x, copy=False) for x in ins] # optional type conversion vfout, vmout, nbsout, agree, ithetaout = mar_mat(*ins) if not return_all: return { 'Values': (vfout, vmout), 'NBS': nbsout, 'theta': ithetaout, 'Decision': agree, 'Abortion': do_abortion } else: return { 'Values': (vfout, vmout), 'NBS': nbsout, 'theta': ithetaout, 'Decision': agree, 'Abortion': do_abortion, 'ins': ins }
def __init__(self,nogrid=False,divorce_costs='Default',separation_costs='Default',**kwargs): p = dict() period_year=1#this can be 1,2,3 or 6 transform=2#this tells how many periods to pull together for duration moments T = int(62/period_year) Tret = int(47/period_year) # first period when the agent is retired Tbef=int(2/period_year) Tren = int(47/period_year)#int(42/period_year) # period starting which people do not renegotiate/divroce Tmeet = int(47/period_year)#int(42/period_year) # period starting which you do not meet anyone p['py']=period_year p['ty']=transform p['T'] = T p['Tret'] = Tret p['Tren'] = Tren p['Tbef'] = Tbef p['sig_zf_0'] = 0.5449176#0.4096**(0.5) p['sig_zf'] = .0272437**(0.5)#0.0399528**(0.5) p['n_zf_t'] = [5]*Tret + [1]*(T-Tret) p['sig_zm_0'] = 0.54896510#.405769**(0.5) p['sig_zm'] = .025014**(0.5)#0.0417483**(0.5) p['n_zm_t'] = [5]*Tret + [1]*(T-Tret) p['sigma_psi_mult'] = 0.28 p['sigma_psi'] = 0.11 p['n_psi_t'] = [11]*T p['R_t'] = [1.02**period_year]*T p['beta_t'] = [0.98**period_year]*T p['A'] = 1.0 # consumption in couple: c = (1/A)*[c_f^(1+rho) + c_m^(1+rho)]^(1/(1+rho)) p['crra_power'] = 1.5 p['couple_rts'] = 0.0 p['sig_partner_a'] = 0.1#0.5 p['sig_partner_z'] = 1.2#1.0#0.4 #This is crazy powerful for the diff in diff estimate p['sig_partner_mult'] = 1.0 p['dump_factor_z'] = 0.85#0.82 p['mean_partner_z_female'] = 0.02#+0.03 p['mean_partner_z_male'] = -0.02#-0.03 p['mean_partner_a_female'] = 0.0#0.1 p['mean_partner_a_male'] = 0.0#-0.1 p['m_bargaining_weight'] = 0.5 p['pmeet'] = 0.5 p['z_drift'] = -0.09#-0.1 p['wage_gap'] = 0.6 p['wret'] = 0.6#0.5 p['uls'] = 0.2 p['pls'] = 1.0 p['u_shift_mar'] = 0.0 p['u_shift_coh'] = -0.00 #Wages over time p['f_wage_trend'] = [0.0*(t>=Tret)+(t<Tret)*(-.74491918 +.04258303*t -.0016542*t**2+.00001775*t**3) for t in range(T)] p['f_wage_trend_single'] = [0.0*(t>=Tret)+(t<Tret)*(-.6805060 +.04629912*t -.00160467*t**2+.00001626*t**3) for t in range(T)] p['m_wage_trend'] = [0.0*(t>=Tret)+(t<Tret)*(-0.5620125 +0.06767721*t -0.00192571*t**2+ 0.00001573*t**3) for t in range(T)] p['m_wage_trend_single'] = [0.0*(t>=Tret)+(t<Tret)*(-.5960803 +.05829568*t -.00169143*t**2+ .00001446*t**3) for t in range(T)] # p['f_wage_trend'] = [(-0.5620125 +0.06767721*t -0.00192571*t**2+ 0.00001573*t**3) for t in range(T)] # p['f_wage_trend_single'] = [(-.5960803 +.05829568*t -.00169143*t**2+ .00001446*t**3) for t in range(T)] # p['m_wage_trend'] = [(-0.5620125 +0.06767721*t -0.00192571*t**2+ 0.00001573*t**3) for t in range(T)] # p['m_wage_trend_single'] = [(-.5960803 +.05829568*t -.00169143*t**2+ .00001446*t**3) for t in range(T)] p['util_lam'] = 0.19#0.4 p['util_alp'] = 0.5 p['util_xi'] = 1.07 p['util_kap'] = (1-0.21)/(0.21) p['rprice_durables'] = 1.0# for key, value in kwargs.items(): assert (key in p), 'wrong name?' p[key] = value #Adjust kappa and alpha to make sense of relative prices p['util_alp_m']=p['util_alp']*(1.0/(p['rprice_durables'])**(1.0-p['util_xi'])) p['util_kap_m']=p['util_kap']*p['rprice_durables']**p['util_lam'] # no replacements after this pint p['sigma_psi_init'] = p['sigma_psi_mult']*p['sigma_psi'] #Get the probability of meeting, adjusting for year-period p_meet=p['pmeet'] for j in range(period_year-1): p_meet=p_meet+(1-p_meet)*p['pmeet'] # timing here p['pmeet_t'] = [p_meet]*Tmeet + [0.0]*(T-Tmeet) p['can divorce'] = [True]*Tren + [False]*(T-Tren) self.pars = p self.dtype = np.float64 # type for all floats # relevant for integration self.state_names = ['Female, single','Male, single','Couple, M', 'Couple, C'] # female labor supply self.ls_levels = np.array([0.0,0.8],dtype=self.dtype) self.mlevel=0.8 #self.ls_utilities = np.array([p['uls'],0.0],dtype=self.dtype) self.ls_pdown = np.array([p['pls'],0.0],dtype=self.dtype) self.nls = len(self.ls_levels) #Cost of Divorce if divorce_costs == 'Default': # by default the costs are set in the bottom self.div_costs = DivorceCosts(eq_split=1.0,assets_kept=1.0) else: if isinstance(divorce_costs,dict): # you can feed in arguments to DivorceCosts self.div_costs = DivorceCosts(**divorce_costs) else: # or just the output of DivorceCosts assert isinstance(divorce_costs,DivorceCosts) self.div_costs = divorce_costs #Cost of Separation if separation_costs == 'Default': # by default the costs are set in the bottom self.sep_costs = DivorceCosts(eq_split=0.0,assets_kept=1.0) else: if isinstance(separation_costs,dict): # you can feed in arguments to DivorceCosts self.sep_costs = DivorceCosts(**separation_costs) else: # or just the output of DivorceCosts assert isinstance(separation_costs,DivorceCosts) self.sep_costs = separation_costs # exogrid should be deprecated if not nogrid: exogrid = dict() # let's approximate three Markov chains # this sets up exogenous grid # FIXME: this uses number of points from 0th entry. # in principle we can generalize this p['n_zf_t'] = [5]*Tret + [5]*(T-Tret) p['n_zm_t'] = [5]*Tret + [5]*(T-Tret) exogrid['zf_t'], exogrid['zf_t_mat'] = rouw_nonst(p['T'],p['sig_zf']*period_year**0.5,p['sig_zf_0'],p['n_zf_t'][0]) exogrid['zm_t'], exogrid['zm_t_mat'] = rouw_nonst(p['T'],p['sig_zm']*period_year**0.5,p['sig_zm_0'],p['n_zm_t'][0]) ################################ #First mimic US pension system ############################### #function to compute pension def pens(value): #Get median income before retirement using men model income in Tret-1 #+ ratio of men and female labor income for the rest yret=(1.73377+(.8427056/1.224638)*1.73377* 0.3246206)/(1+0.3246206) thresh1=0.38*yret thresh2=1.59*yret inc1=np.minimum(value,thresh1) inc2=np.maximum(np.minimum(value-inc1,thresh2-inc1),0) inc3=np.maximum(value-thresh2,0) return inc1*0.9+inc2*0.32+inc3*0.15 for t in range(Tret,T): exogrid['zf_t'][t] = np.array([np.log(p['wret'])]) exogrid['zm_t'][t] = np.array([np.log(p['wret'])]) exogrid['zf_t_mat'][t] = np.atleast_2d(1.0) exogrid['zm_t_mat'][t] = np.atleast_2d(1.0) # fix transition from non-retired to retired exogrid['zf_t_mat'][Tret-1] = np.ones((p['n_zf_t'][Tret-1],1)) exogrid['zm_t_mat'][Tret-1] = np.ones((p['n_zm_t'][Tret-1],1)) #Tax system as in Wu and Kruger for t in range(0,Tret): exogrid['zf_t'][t] = exogrid['zf_t'][t]#*(1-0.1327)+np.log(1-0.1575) exogrid['zm_t'][t] = exogrid['zm_t'][t]#*(1-0.1327)+np.log(1-0.1575) #Comment out the following if you dont want retirment based on income for t in range(Tret,T): #exogrid['zf_t'][t] = np.log(p['wret']*np.exp(p['f_wage_trend'][Tret-1]+exogrid['zf_t'][Tret-1]))#np.array([np.log(p['wret'])]) #exogrid['zm_t'][t] = np.log(p['wret']*np.exp(p['m_wage_trend'][Tret-1]+exogrid['zm_t'][Tret-1])) exogrid['zf_t'][t] = np.log(pens(np.exp(p['f_wage_trend'][Tret-1]+exogrid['zf_t'][Tret-1])))#np.array([np.log(p['wret'])]) exogrid['zm_t'][t] = np.log(pens(np.exp(p['m_wage_trend'][Tret-1]+exogrid['zm_t'][Tret-1]))) exogrid['zf_t_mat'][t] = np.diag(np.ones(len(exogrid['zf_t'][t])))#p.atleast_2d(1.0) exogrid['zm_t_mat'][t] = np.diag(np.ones(len(exogrid['zm_t'][t]))) # fix transition from non-retired to retired exogrid['zf_t_mat'][Tret-1] = np.diag(np.ones(len(exogrid['zf_t'][Tret-1]))) exogrid['zm_t_mat'][Tret-1] = np.diag(np.ones(len(exogrid['zm_t'][Tret-1]))) exogrid['psi_t'], exogrid['psi_t_mat'] = tauchen_nonst(p['T'],p['sigma_psi']*period_year**0.5,p['sigma_psi_init'],p['n_psi_t'][0]) exogrid['psi_t_mat'][Tret-1] = np.diag(np.ones(len(exogrid['psi_t_mat'][Tret-1]))) exogrid['psi_t_mat'][Tret] = np.diag(np.ones(len(exogrid['psi_t_mat'][Tret-1]))) exogrid['psi_t_mat'][Tret+1] = np.diag(np.ones(len(exogrid['psi_t_mat'][Tret-1]))) #Here I impose no change in psi from retirement till the end of time # for t in range(Tret,T-1): # exogrid['psi_t'][t] = exogrid['psi_t'][Tret-1]#np.array([np.log(p['wret'])]) # exogrid['psi_t_mat'][t] = np.diag(np.ones(len(exogrid['psi_t'][t])))#p.atleast_2d(1.0) zfzm, zfzmmat = combine_matrices_two_lists(exogrid['zf_t'], exogrid['zm_t'], exogrid['zf_t_mat'], exogrid['zm_t_mat']) all_t, all_t_mat = combine_matrices_two_lists(zfzm,exogrid['psi_t'],zfzmmat,exogrid['psi_t_mat']) all_t_mat_sparse_T = [sparse.csc_matrix(D.T) if D is not None else None for D in all_t_mat] #Create a new bad version of transition matrix p(zf_t) zf_bad = [tauchen_drift(exogrid['zf_t'][t], exogrid['zf_t'][t+1], 1.0, p['sig_zf'], p['z_drift']) for t in range(self.pars['Tret']-1) ] #Account for retirement here zf_bad = zf_bad+[exogrid['zf_t_mat'][t] for t in range(self.pars['Tret']-1,self.pars['T']-1)]+ [None] zf_t_mat_down = zf_bad zfzm, zfzmmat = combine_matrices_two_lists(exogrid['zf_t'], exogrid['zm_t'], zf_t_mat_down, exogrid['zm_t_mat']) all_t_down, all_t_mat_down = combine_matrices_two_lists(zfzm,exogrid['psi_t'],zfzmmat,exogrid['psi_t_mat']) all_t_mat_down_sparse_T = [sparse.csc_matrix(D.T) if D is not None else None for D in all_t_mat_down] all_t_mat_by_l = [ [(1-p)*m + p*md if m is not None else None for m , md in zip(all_t_mat,all_t_mat_down)] for p in self.ls_pdown ] all_t_mat_by_l_spt = [ [(1-p)*m + p*md if m is not None else None for m, md in zip(all_t_mat_sparse_T,all_t_mat_down_sparse_T)] for p in self.ls_pdown ] exogrid['all_t_mat_by_l'] = all_t_mat_by_l exogrid['all_t_mat_by_l_spt'] = all_t_mat_by_l_spt exogrid['all_t'] = all_t Exogrid_nt = namedtuple('Exogrid_nt',exogrid.keys()) self.exogrid = Exogrid_nt(**exogrid) self.pars['nexo_t'] = [v.shape[0] for v in all_t] #assert False #Grid Couple self.na = 40#40 self.amin = 0 self.amax = 80 self.amax1 = 180 self.agrid_c = np.linspace(self.amin,self.amax,self.na,dtype=self.dtype) tune=2.5 self.agrid_c = np.geomspace(self.amin+tune,self.amax+tune,num=self.na)-tune self.agrid_c[-1]=self.amax1 self.agrid_c[-2]=120 # this builds finer grid for potential savings s_between = 7 # default numer of points between poitns on agrid s_da_min = 0.1 # minimal step (does not create more points) s_da_max = 1.0 # maximal step (creates more if not enough) self.sgrid_c = build_s_grid(self.agrid_c,s_between,s_da_min,s_da_max) self.vsgrid_c = VecOnGrid(self.agrid_c,self.sgrid_c) #Grid Single scale = 1.1 self.amin_s = 0 self.amax_s = self.amax/scale self.agrid_s = np.linspace(self.amin_s,self.amax_s,self.na,dtype=self.dtype) #self.agrid_s[self.na-1]=18#180 tune_s=2.5 self.agrid_s = np.geomspace(self.amin_s+tune_s,self.amax_s+tune_s,num=self.na)-tune_s self.agrid_s[-1]=self.amax1/scale self.agrid_c[-2]=120/scale self.sgrid_s = build_s_grid(self.agrid_s,s_between,s_da_min,s_da_max) self.vsgrid_s = VecOnGrid(self.agrid_s,self.sgrid_s) # grid for theta self.ntheta = 11 self.thetamin = 0.02 self.thetamax = 0.98 self.thetagrid = np.linspace(self.thetamin,self.thetamax,self.ntheta,dtype=self.dtype) # construct finer grid for bargaining ntheta_fine = 5*self.ntheta # actual number may be a bit bigger self.thetagrid_fine = np.unique(np.concatenate( (self.thetagrid,np.linspace(self.thetamin,self.thetamax,ntheta_fine,dtype=self.dtype)) )) self.ntheta_fine = self.thetagrid_fine.size i_orig = list() for theta in self.thetagrid: assert theta in self.thetagrid_fine i_orig.append(np.where(self.thetagrid_fine==theta)[0]) assert len(i_orig) == self.thetagrid.size # allows to recover original gird points on the fine grid self.theta_orig_on_fine = np.array(i_orig).flatten() self.v_thetagrid_fine = VecOnGrid(self.thetagrid,self.thetagrid_fine) # precomputed object for interpolation #Get indexes from fine back to coarse thetagrid cg=VecOnGrid(self.thetagrid,self.thetagrid_fine) index_t=cg.i index_t1=index_t+1 wherep=(cg.wthis<0.5) self.igridcoarse=index_t self.igridcoarse[wherep]=index_t1[wherep] self.exo_grids = {'Female, single':exogrid['zf_t'], 'Male, single':exogrid['zm_t'], 'Couple, M':exogrid['all_t'], 'Couple, C':exogrid['all_t']} self.exo_mats = {'Female, single':exogrid['zf_t_mat'], 'Male, single':exogrid['zm_t_mat'], 'Couple, M':exogrid['all_t_mat_by_l'], 'Couple, C':exogrid['all_t_mat_by_l']} # sparse version? self.utility_shifters = {'Female, single':0.0, 'Male, single':0.0, 'Couple, M':p['u_shift_mar'], 'Couple, C':p['u_shift_coh']} # this pre-computes transition matrices for meeting a partner zf_t_partmat = [self.mar_mats_iexo(t,female=True) if t < p['T'] - 1 else None for t in range(p['T'])] zm_t_partmat = [self.mar_mats_iexo(t,female=False) if t < p['T'] - 1 else None for t in range(p['T'])] self.part_mats = {'Female, single':zf_t_partmat, 'Male, single': zm_t_partmat, 'Couple, M': None, 'Couple, C': None} # last is added for consistency self.mar_mats_assets() self.mar_mats_combine() # building m grid ezfmin = min([np.min(self.ls_levels[-1]*np.exp(g+t)) for g,t in zip(exogrid['zf_t'],p['f_wage_trend'])]) ezmmin = min([np.min(self.mlevel*np.exp(g+t)) for g,t in zip(exogrid['zm_t'],p['m_wage_trend'])]) ezfmax = max([np.max(self.ls_levels[-1]*np.exp(g+t)) for g,t in zip(exogrid['zf_t'],p['f_wage_trend'])]) ezmmax = max([np.max(self.mlevel*np.exp(g+t)) for g,t in zip(exogrid['zm_t'],p['m_wage_trend'])]) self.money_min = 0.95*min(ezmmin,ezfmin) # cause FLS can be up to 0 #self.mgrid = ezmmin + self.sgrid_c # this can be changed later mmin = self.money_min mmax = ezfmax + ezmmax + np.max(self.pars['R_t'])*self.amax1 mint = (ezfmax + ezmmax) # poin where more dense grid begins ndense = 600 nm = 1500 gsparse = np.linspace(mint,mmax,nm-ndense) gdense = np.linspace(mmin,mint,ndense+1) # +1 as there is a common pt self.mgrid = np.zeros(nm,dtype=self.dtype) self.mgrid[ndense:] = gsparse self.mgrid[:(ndense+1)] = gdense assert np.all(np.diff(self.mgrid)>0) self.u_precompute()
def v_mar(setup, V, t, iassets_couple, iexo_couple, *, match_type, female, return_insides=False, nogpu_here=nogpu): # this builds matrix for all matches specified by grid positions # iassets_couple (na X nmatches) and iexo_couple (nmatches) iexo, izf, izm, ipsi = [co(x) for x in setup.all_indices(t, iexo_couple)] vals = pick_values(setup, V, match_type=match_type) V_fem, V_mal = vals['V_fem'], vals['V_mal'] agrid_c = setup.agrid_c if nogpu else setup.cupy.agrid_c agrid_s = setup.agrid_s if nogpu else setup.cupy.agrid_s # obtain partner's value assets_partner = np.clip(agrid_c[iassets_couple] - agrid_s[:, None], 0.0, agrid_s.max()) # this can be fastened by going over repeated values of ipsi v_assets_partner = VecOnGrid(agrid_s, assets_partner) i, wnext, wthis = v_assets_partner.i, v_assets_partner.wnext, v_assets_partner.wthis if type(V_fem) is tuple: # in this case assert match_type == 'Unplanned pregnancy' V_f_no, V_m_no, abortion_decision = get_upp_disagreement_values( setup, t, V_fem, V_mal, izf, izm, female, v_assets_partner) else: if female: V_f_no = V_fem[:, izf] V_m_no = V_mal[i, izm[None, :]] * wthis + V_mal[i + 1, izm[ None, :]] * wnext else: V_f_no = V_fem[i, izf[None, :]] * wthis + V_fem[i + 1, izf[ None, :]] * wnext V_m_no = V_mal[:, izm] V_fem_mar, V_mal_mar = vals['V_fem_mar'], vals['V_mal_mar'] ia = iassets_couple V_f_yes = V_fem_mar[ia, iexo[None, :], :] V_m_yes = V_mal_mar[ia, iexo[None, :], :] assert V_f_yes.shape[:-1] == V_f_no.shape assert V_m_yes.shape[:-1] == V_m_no.shape # fill abortion decisions if match_type == 'Unplanned pregnancy' and female: do_abortion = abortion_decision #vals['i_abortion'][:,izf] else: do_abortion = np.zeros(V_f_no.shape, dtype=np.bool_) if nogpu: it, wnt = setup.v_thetagrid_fine.i, setup.v_thetagrid_fine.wnext else: it, wnt = setup.cupy.v_thetagrid_fine.i, setup.cupy.v_thetagrid_fine.wnext if nogpu: v_f, v_m, agree, nbs, itheta = get_marriage_values( V_f_yes, V_m_yes, V_f_no, V_m_no, it, wnt) else: v_f, v_m, agree, nbs, itheta = v_mar_gpu(V_f_yes, V_m_yes, V_f_no, V_m_no, it, wnt) out = { 'V_fem': v_f, 'V_mal': v_m, 'Agree': agree, 'NBS': nbs, 'itheta': itheta, 'Abortion': do_abortion } if return_insides: out.update({ 'V_f_yes': V_f_yes, 'V_m_yes': V_m_yes, 'V_f_no': V_f_no, 'V_m_no': V_m_no }) return out
def v_div_byshare(model, dc, t, sc, share_fem, share_mal, Vmale, Vfemale, izf, izm, shrs=None, cost_fem=0.0, cost_mal=0.0): # this produces value of divorce for gridpoints given possibly different # shares of how assets are divided. # Returns Vf_divorce, Vm_divorce -- values of singles in case of divorce # matched to the gridpionts for couples setup = model.setup # optional cost_fem and cost_mal are monetary costs of divorce if shrs is None: shrs = shrs_def # there is simpler protocol if shares are fixed def ptp(x): # not implemented in cupy somehow return x.max() - x.min() simple = False if (ptp(share_fem) < 1e-4) and (ptp(share_mal) < 1e-4): simple = True shr_m = share_mal[0] agrid_s = setup.agrid_s if not gpu else setup.cupy.agrid_s sv_m = VecOnGrid(agrid_s, shr_m * sc - cost_mal, force_cupy=gpu) shr_f = share_fem[0] if np.abs(shr_f - shr_m) < 1e-4: sv_f = sv_m else: sv_f = VecOnGrid(agrid_s, shr_f * sc - cost_fem, force_cupy=gpu) def apply(v, sv, iexo): i = sv.i[:, None] ip = i + 1 wt = sv.wthis[:, None] wn = sv.wnext[:, None] ie = iexo[None, :] return v[i, ie] * wt + v[ip, ie] * wn Vf_divorce = apply(Vfemale, sv_f, izf) - dc.u_lost_f Vm_divorce = apply(Vmale, sv_m, izm) - dc.u_lost_m return Vf_divorce, Vm_divorce Vm_divorce_M, Vf_divorce_M = v_div_allsplits(setup, dc, t, sc, Vmale, Vfemale, izm, izf, shrs=shrs, cost_fem=cost_fem, cost_mal=cost_mal) # share of assets that goes to the female # this has many repetative values but it turns out it does not matter much fem_gets = VecOnGrid(np.array(shrs), share_fem, force_cupy=gpu) mal_gets = VecOnGrid(np.array(shrs), share_mal, force_cupy=gpu) i_fem = fem_gets.i wn_fem = fem_gets.wnext i_mal = mal_gets.i wn_mal = mal_gets.wnext inds_exo = np.arange(setup.pars['nexo_t'][t + 1]) Vf_divorce = (1-wn_fem[None,:])*Vf_divorce_M[:,inds_exo,i_fem] + \ wn_fem[None,:]*Vf_divorce_M[:,inds_exo,i_fem+1] Vm_divorce = (1-wn_mal[None,:])*Vm_divorce_M[:,inds_exo,i_mal] + \ wn_mal[None,:]*Vm_divorce_M[:,inds_exo,i_mal+1] return Vf_divorce, Vm_divorce
def statenext(self, t): for ipol in range(self.npol): for ist, sname in enumerate(self.state_names): is_state_any_pol = (self.state[:, t] == ist) is_pol = (self.policy_ind[:, t] == ipol) is_state = (is_state_any_pol) & (is_pol) if not np.any(is_state): continue if self.verbose: print('At t = {} count of {} is {}'.format( t + 1, sname, np.sum(is_state))) ind = np.where(is_state)[0] nind = ind.size if sname == "Female, single" or sname == "Male, single" or sname == "Female and child": # TODO: this is temporary version, it computes partners for # everyone and after that imposes meet / no meet, this should # not happen. # meet a partner ss = self.single_state ss_sm = 'Female and child' if self.female else self.single_state pcoef = self.Mlist[ipol].setup.pars['pmeet_multiplier_fem'] pmeet = pcoef * self.Mlist[ipol].setup.pars['pmeet_t'][ t] # TODO: check timing p_abortion_access = self.Mlist[ipol].setup.pars[ 'p_abortion_access'] # divide by 2 subgroups matches = self.Mlist[ipol].decisions[t][sname] ia = self.iassets[ ind, t + 1] # note that timing is slightly inconsistent # we use iexo from t and savings from t+1 # TODO: fix the seed iznow = self.iexo[ind, t] if not 'Not pregnant' in matches or (pmeet < 1e-5): assert pmeet < 1e-5 # propagate yaftmar # !!! this needs to be verified # iexo is set within iexonext izf = self.iexo[ind, t + 1] self.yaftmar[ind,t+1] = \ (self.yaftmar[ind,t] + 1)*(self.yaftmar[ind,t]>=0)+(-1)*(self.yaftmar[ind,t]<0) if sname != "Female and child": self.ils_i[ind, t + 1] = self.ils_def self.state[ind, t + 1] = self.state_codes[ss] else: fls_policy = self.Mlist[ipol].V[ t + 1]['Female and child']['fls'] self.state[ ind, t + 1] = self.state_codes['Female and child'] self.ils_i[ind, t + 1] = fls_policy[self.iassets[ind, t + 1], izf] continue pmat_np = matches['Not pregnant']['p_mat_extended'][ iznow, :] # assuming it is identical !!! pmat_cum_np = pmat_np.cumsum(axis=1) pmat_cum_p = pmat_cum_np assert np.all(pmat_cum_np < 1 + 1e-5) assert np.all(pmat_cum_np[:, -1] > 1 - 1e-5) assert np.all(pmat_cum_p < 1 + 1e-5) assert np.all(pmat_cum_p[:, -1] > 1 - 1e-5) assert np.all(pmat_cum_p >= 0.0) # there is a rare numerical issue when adding lots of floats # gives imprecise result > 1 pmat_cum_np[:, -1] = 1 + 1e-5 pmat_cum_p[:, -1] = 1 + 1e-5 v = self._shocks_single_iexo[ ind, t] #np.random.random_sample(ind.size) # draw uniform dist i_pmat_np = (v[:, None] > pmat_cum_np).sum( axis=1) # index of the position in pmat i_pmat_p = (v[:, None] > pmat_cum_p).sum( axis=1) # index of the position in pmat # these things are the same for pregnant and not pregnant if self.female: fert = self.setup.pars['is fertile'][t] else: fert = self.setup.pars['is fertile'][ t - 2] if t >= 2 else False if sname == 'Female, single': p_preg = fert * self.setup.upp_precomputed_fem[t][ self.iexo[ind, t]] elif sname == 'Male, single': p_preg = fert * self.setup.upp_precomputed_mal[t][ self.iexo[ind, t]] elif sname == 'Female and child': p_preg = np.ones_like(ind, dtype=np.float64) else: assert False # these are individual-specific pregnancy probabilities # for those who are not fertile this is forced to be zero # potential assets position of couple vpreg = self._shocks_single_preg[ind, t] i_preg = (vpreg < p_preg) #i_pmat = i_pmat_p*(i_preg) + i_pmat_np*(~i_preg) ic_out = matches['Not pregnant']['corresponding_iexo'][i_pmat_np]*(~i_preg) \ + matches['Pregnant']['corresponding_iexo'][i_pmat_p]*(i_preg) imatch = matches['Not pregnant']['corresponding_imatch'][i_pmat_np]*(~i_preg) \ + matches['Pregnant']['corresponding_imatch'][i_pmat_p]*(i_preg) ia_out = matches['Not pregnant']['ia_c_table'][ia,i_pmat_np]*(~i_preg) \ + matches['Pregnant']['ia_c_table'][ia,i_pmat_p]*(i_preg) it_out = matches['Not pregnant']['itheta'][ia,i_pmat_np]*(~i_preg) + \ + matches['Pregnant']['itheta'][ia,i_pmat_p]*(i_preg) iall, izf, izm, ipsi = self.Mlist[ipol].setup.all_indices( t, ic_out) # compute for everyone vmeet = self._shocks_single_meet[ind, t] i_nomeet = np.array(vmeet > pmeet) v_abortion = self._shocks_outsm[ind, t] i_abortion_allowed = np.array( v_abortion < p_abortion_access) i_pot_agree = matches['Not pregnant']['Decision'][ia,i_pmat_np]*(~i_preg) \ + matches['Pregnant']['Decision'][ia,i_pmat_p]*(i_preg) i_m_preferred = matches['Not pregnant']['Child immediately'][ia,i_pmat_np]*(~i_preg) \ + matches['Pregnant']['Child immediately'][ia,i_pmat_p]*(i_preg) i_abortion_preferred = matches['Not pregnant']['Abortion'][ia,i_pmat_np]*(~i_preg) \ + matches['Pregnant']['Abortion'][ia,i_pmat_p]*(i_preg) i_disagree = (~i_pot_agree) i_disagree_or_nomeet = (i_disagree) | (i_nomeet) i_disagree_and_meet = (i_disagree) & ~(i_nomeet) i_abortion = (i_abortion_allowed) & ( i_abortion_preferred) & (i_disagree_and_meet) & ( i_preg) & (sname == 'Female, single') i_kept = (i_abortion_allowed) & (~i_abortion_preferred) & ( i_disagree_and_meet) & (i_preg) & (sname == 'Female, single') i_no_access = ~(i_abortion_allowed) & ( i_disagree_and_meet) & (i_preg) & (sname == 'Female, single') self.disagreed[ind, t] = i_disagree_and_meet self.met_a_partner[ind, t] = ~i_nomeet self.unplanned_preg[ind, t] = (i_preg) & ( ~i_nomeet) & ~(sname == 'Female and child') self.aborted[ind, t] = i_abortion n_abortions = i_abortion.sum() n_kept = i_kept.sum() #if n_abortions>0: print('{} abortions done at t = {} for {}'.format(n_abortions,t,sname)) #if n_kept>0: print('{} abortions refused at t = {} for {}'.format(n_kept,t,sname)) if not sname == 'Female and child': become_sm = (i_disagree_and_meet) & ( i_preg) & self.female & ~(i_abortion) become_single = (i_disagree_or_nomeet) & ~(become_sm) else: become_sm = (i_disagree_or_nomeet) & (i_preg) assert np.all(i_preg) become_single = np.zeros_like(become_sm, dtype=np.bool) assert np.all(i_disagree_or_nomeet == ((become_sm) | (become_single))) i_agree = ~i_disagree_or_nomeet i_agree_mar = (i_agree) & (i_m_preferred) i_agree_coh = (i_agree) & (~i_m_preferred) self.agreed[ind, t] = (i_agree_mar) | (i_agree_coh) self.planned_preg[ind, t] = (i_agree_mar) & ~(i_preg) self.new_child[ind, t + 1] = (i_kept | i_no_access) | ( i_agree_mar & ~(sname == 'Female and child')) assert np.all(~i_nomeet[i_agree]) i_firstmar = (self.nmar[ind, t] == 0) nmar, ncoh, ndis, nnom = np.sum(i_agree_mar), np.sum( i_agree_coh), np.sum(i_disagree_and_meet), np.sum( i_nomeet) ntot = sum((nmar, ncoh, ndis, nnom)) if self.verbose: print( 'for sname = {}: {} mar, {} coh, {} disagreed, {} did not meet ({} total)' .format(sname, nmar, ncoh, ndis, nnom, ntot)) if np.any(i_agree_mar): self.itheta[ind[i_agree_mar], t + 1] = it_out[i_agree_mar] self.iexo[ind[i_agree_mar], t + 1] = iall[i_agree_mar] self.state[ind[i_agree_mar], t + 1] = self.state_codes['Couple and child'] self.iassets[ind[i_agree_mar], t + 1] = ia_out[i_agree_mar] self.agreed_k[ind[i_agree_mar], t] = True self.agreed_unplanned[ind[i_agree_mar], t] = i_preg[i_agree_mar] * ( sname != 'Female and child') self.k_m[ind[i_agree_mar], (t + 1):] = True self.k_m_true[ind[i_agree_mar], (t + 1):] = i_preg[i_agree_mar][:, None] fls_policy = self.Mlist[ipol].V[ t + 1]['Couple and child']['fls'] def thti(*agrs): return np.round( self.tht_interpolate(*agrs)).astype(np.int8) self.ils_i[ind[i_agree_mar],t+1] = \ thti(fls_policy,(self.iassets[ind[i_agree_mar],t+1],self.iexo[ind[i_agree_mar],t+1]),self.itheta[ind[i_agree_mar],t+1]) self.yaftmar[ind[i_agree_mar], t + 1] = 0 self.nmar[ind[i_agree_mar], t + 1:] += 1 if np.any(i_agree_coh): assert not sname == 'Female and child' self.itheta[ind[i_agree_coh], t + 1] = it_out[i_agree_coh] self.iexo[ind[i_agree_coh], t + 1] = iall[i_agree_coh] self.state[ind[i_agree_coh], t + 1] = self.state_codes['Couple, no children'] self.iassets[ind[i_agree_coh], t + 1] = ia_out[i_agree_coh] # FLS decision #tg = self.Mlist[ipol].setup.v_thetagrid_fine #fls_policy = self.V[t+1]['Couple, no children']['fls'] def thti(*agrs): return np.round( self.tht_interpolate(*agrs)).astype(np.int8) fls_policy = self.Mlist[ipol].V[ t + 1]['Couple, no children']['fls'] self.ils_i[ind[i_agree_coh],t+1] = \ thti(fls_policy,(self.iassets[ind[i_agree_coh],t+1],self.iexo[ind[i_agree_coh],t+1]),self.itheta[ind[i_agree_coh],t+1]) self.yaftmar[ind[i_agree_coh], t + 1] = 0 self.nmar[ind[i_agree_coh], t + 1:] += 1 if np.any(i_disagree_or_nomeet): # do not touch assets fls_policy = self.Mlist[ipol].V[ t + 1]['Female and child']['fls'] if sname == 'Female and child': assert not np.any(become_single) if sname == 'Male, single': assert not np.any(become_sm) iz = izf if self.female else izm self.iexo[ind[i_disagree_or_nomeet], t + 1] = iz[i_disagree_or_nomeet] #self.state[ind[i_disagree_or_nomeet],t+1] = self.state_codes['Female, single'] self.state[ind[become_single], t + 1] = self.state_codes[ss] self.state[ind[become_sm], t + 1] = self.state_codes['Female and child'] self.ils_i[ind[become_single], t + 1] = self.ils_def self.ils_i[ind[become_sm], t + 1] = fls_policy[self.iassets[ind[become_sm], t + 1], izf[become_sm]] self.yaftmar[ind[i_disagree_or_nomeet],t+1] = \ (self.yaftmar[ind[i_disagree_or_nomeet],t] + 1)*\ (self.yaftmar[ind[i_disagree_or_nomeet],t]>=0)+\ (-1)*(self.yaftmar[ind[i_disagree_or_nomeet],t]<0) elif sname == "Couple and child" or sname == "Couple, no children": ss = 'Female, single' if self.female else 'Male, single' decision = self.Mlist[ipol].decisions[t][sname] haschild = (sname == "Couple and child") # by default keep the same theta and weights self.itheta[ind, t + 1] = self.itheta[ind, t] nt = self.Mlist[ipol].setup.ntheta_fine # initiate renegotiation isc = self.iassets[ind, t + 1] iall, izf, izm, ipsi = self.Mlist[ipol].setup.all_indices( t + 1, self.iexo[ind, t + 1]) if sname == "Couple and child": transitions = self.setup.child_support_transitions[t + 1] izf_div_0 = transitions['i_this_fem'][izf, izm] izf_div_1 = izf_div_0 + 1 wzf_div_0 = transitions['w_this_fem'][izf, izm] pick_zf_0 = (self._shocks_child_support_fem[ind, t] < wzf_div_0) izf_div = izf_div_0 * (pick_zf_0) + izf_div_1 * ( ~pick_zf_0) izm_div_0 = transitions['i_this_mal'][izm] izm_div_1 = izm_div_0 + 1 wzm_div_0 = transitions['w_this_mal'][izm] pick_zm_0 = (self._shocks_child_support_mal[ind, t] < wzm_div_0) izm_div = izm_div_0 * (pick_zm_0) + izm_div_1 * ( ~pick_zm_0) if self.setup.pars['child_support_share'] < 1e-5: assert np.all(izf_div == izf) assert np.all(izm_div == izm) elif sname == "Couple, no children": izf_div = izf izm_div = izm else: assert False itht = self.itheta[ind, t + 1] agrid = self.Mlist[ipol].setup.agrid_c agrid_s = self.Mlist[ipol].setup.agrid_s sc = agrid[isc] # needed only for dividing asssets thts_all = decision['thetas'] thts_orig_all = np.broadcast_to( np.arange(nt)[None, None, :], thts_all.shape) thts = thts_all[isc, iall, itht] thts_orig = thts_orig_all[isc, iall, itht] dec = decision['Decision'] i_stay = dec[isc, iall] if dec.ndim == 2 else dec[isc, iall, itht] i_div = ~i_stay i_ren = (i_stay) & (thts_orig != thts) i_renf = (i_stay) & (thts_orig > thts) i_renm = (i_stay) & (thts_orig < thts) i_sq = (i_stay) & (thts_orig == thts) #if self.verbose: print('{} divorce, {} ren-f, {} ren-m, {} sq'.format(np.sum(i_div),np.sum(i_renf),np.sum(i_renm),np.sum(i_sq)) ) self.renegotiated[ind, t] = i_ren zf_grid = self.setup.exo_grids['Female, single'][t] zm_grid = self.setup.exo_grids['Male, single'][t] assert np.all(self.yaftmar[ind, t] >= 0) self.yaftmar[ind, t + 1] = self.yaftmar[ ind, t] + 1 # extra year past marriage if np.any(i_div): income_fem = np.exp(zf_grid[izf[i_div]]) income_mal = np.exp(zm_grid[izm[i_div]]) income_share_fem = income_fem / (income_fem + income_mal) # !!! costs = self.Mlist[ ipol].setup.divorce_costs_k if sname == 'Couple and child' else self.Mlist[ ipol].setup.divorce_costs_nk share_f, share_m = costs.shares_if_split( income_share_fem) sf = share_f * sc[i_div] sm = share_m * sc[i_div] self.just_divorced[ind, t] = i_div if haschild: self.just_divorced_k[ind, t] = i_div else: self.just_divorced_nk[ind, t] = i_div shks = self._shocks_couple_a[ind[i_div], t] # FIXME: it should be agrid_s here if self.female: self.iassets[ind[i_div], t + 1] = VecOnGrid( agrid_s, sf).roll(shocks=shks) else: self.iassets[ind[i_div], t + 1] = VecOnGrid( agrid, sm).roll(shocks=shks) self.itheta[ind[i_div], t + 1] = -1 iz = izf_div if self.female else izm_div self.iexo[ind[i_div], t + 1] = iz[i_div] fls_policy = self.Mlist[ipol].V[ t + 1]['Female and child']['fls'] if haschild and self.female: self.state[ ind[i_div], t + 1] = self.state_codes['Female and child'] self.ils_i[ind[i_div], t + 1] = fls_policy[self.iassets[ind[i_div], t + 1], izf[i_div]] else: self.state[ind[i_div], t + 1] = self.state_codes[ss] self.ils_i[ind[i_div], t + 1] = self.ils_def if np.any(i_ren): self.itheta[ind[i_ren], t + 1] = thts[i_ren] #tg = self.setup.v_thetagrid_fine #Distinguish between marriage and cohabitation if sname == "Couple and child": self.state[ind[i_ren], t + 1] = self.state_codes[sname] ipick = (self.iassets[ind[i_ren], t + 1], self.iexo[ind[i_ren], t + 1], self.itheta[ind[i_ren], t + 1]) def thti(*agrs): return np.round(self.tht_interpolate( *agrs)).astype(np.int8) self.ils_i[ind[i_ren], t + 1] = thti( self.Mlist[ipol].V[t + 1][sname]['fls'], ipick[:-1], ipick[-1]) else: i_birth = (decision['Give a birth'][isc, iall, thts] > self._shocks_planned_preg[ind, t]) i_birth1 = i_birth[i_ren] self.planned_preg[ind[i_ren], t] = i_birth1 self.m_k[ind[i_ren][i_birth1], (t + 1):] = True self.new_child[ind[i_ren], t + 1] = i_birth1 ipick = (self.iassets[ind[i_ren], t + 1], self.iexo[ind[i_ren], t + 1], self.itheta[ind[i_ren], t + 1]) def thti(*agrs): return np.round(self.tht_interpolate( *agrs)).astype(np.int8) ils_if_k = thti( self.Mlist[ipol].V[t + 1]["Couple and child"] ['fls'], ipick[:-1], ipick[-1]) ils_if_nk = thti( self.Mlist[ipol].V[ t + 1]["Couple, no children"]['fls'], ipick[:-1], ipick[-1]) self.ils_i[ind[i_ren], t + 1] = i_birth1 * ils_if_k + ( 1 - i_birth1) * ils_if_nk self.state[ ind[i_ren], t + 1] = i_birth1 * self.state_codes[ "Couple and child"] + ( 1 - i_birth1 ) * self.state_codes["Couple, no children"] if np.any(i_sq): self.state[ind[i_sq], t + 1] = self.state_codes[sname] # do not touch theta as already updated def thti(*agrs): return np.round( self.tht_interpolate(*agrs)).astype(np.int8) if sname == "Couple and child": self.state[ind[i_sq], t + 1] = self.state_codes[sname] ipick = (self.iassets[ind[i_sq], t + 1], self.iexo[ind[i_sq], t + 1], self.itheta[ind[i_sq], t + 1]) self.ils_i[ind[i_sq], t + 1] = thti( self.Mlist[ipol].V[t + 1][sname]['fls'], ipick[:-1], ipick[-1]) else: i_birth = (decision['Give a birth'][isc, iall, thts] > self._shocks_planned_preg[ind, t]) i_birth1 = i_birth[i_sq] self.m_k[ind[i_sq][i_birth1], (t + 1):] = True self.planned_preg[ind[i_sq], t] = i_birth1 self.state[ ind[i_sq], t + 1] = i_birth1 * self.state_codes[ "Couple and child"] + ( 1 - i_birth1 ) * self.state_codes["Couple, no children"] self.new_child[ind[i_sq], t + 1] = i_birth1 ipick = (self.iassets[ind[i_sq], t + 1], self.iexo[ind[i_sq], t + 1], self.itheta[ind[i_sq], t + 1]) ils_if_k = thti( self.Mlist[ipol].V[t + 1]["Couple and child"] ['fls'], ipick[:-1], ipick[-1]) ils_if_nk = thti( self.Mlist[ipol].V[ t + 1]["Couple, no children"]['fls'], ipick[:-1], ipick[-1]) self.ils_i[ind[i_sq], t + 1] = i_birth1 * ils_if_k + ( 1 - i_birth1) * ils_if_nk self.state[ ind[i_sq], t + 1] = i_birth1 * self.state_codes[ "Couple and child"] + ( 1 - i_birth1 ) * self.state_codes["Couple, no children"] else: raise Exception('unsupported state?') assert not np.any(np.isnan(self.state[:, t + 1]))
def v_mar_igrid(setup, t, V, icouple, ind_or_inds, *, female, marriage, interpolate=True, return_all=False): # this returns value functions for couple that entered the last period with # (s,Z,theta) from the grid and is allowed to renegotiate them or breakup # if return_all==False returns Vout_f, Vout_m, that are value functions # of male and female from entering this union # if return_all==True returns (Vout_f, Vout_m, ismar, thetaout, technical) # where ismar is marriage decision, thetaout is resulting theta and # tuple technical contains less usable stuff (check v_newmar_core for it) # # combine = True creates matrix (n_s-by-n_inds) # combine = False assumed that n_s is the same shape as n_inds and creates # a flat array. if marriage: coup = 'Couple, M' else: coup = 'Couple, C' dtype = setup.dtype # import objects agrid_c = setup.agrid_c agrid_s = setup.agrid_s gamma = setup.pars['m_bargaining_weight'] VMval_single, VFval_single = V['Male, single']['V'], V['Female, single'][ 'V'] VMval_postren, VFval_postren = V[coup]['VM'][icouple, ...], V[coup]['VF'][icouple, ...] # substantial part ind, izf, izm, ipsi = setup.all_indices(t, ind_or_inds) # using trim = True implicitly trims things on top # so if sf is 0.75*amax and sm is 0.75*amax then sc is 1*amax and not 1.5 #sc = sf+sm # savings of couple s_partner = agrid_c[icouple] - agrid_s # we assume all points on grid # this implicitly trims negative or too large values s_partner_v = VecOnGrid(agrid_s, s_partner, trim=True) if len(agrid_s) > 1 else s_partner # this applies them if female: Vfs = VFval_single[:, izf] Vms = s_partner_v.apply(VMval_single, axis=0, take=( 1, izm)) if len(agrid_s) > 1 else VMval_single[:, izm] else: Vms = VMval_single[:, izm] Vfs = s_partner_v.apply(VFval_single, axis=0, take=( 1, izf)) if len(agrid_s) > 1 else VFval_single[:, izf] expnd = lambda x: setup.v_thetagrid_fine.apply(x, axis=2) Vmm, Vfm = (expnd(x[:, ind, :]) for x in (VMval_postren, VFval_postren)) assert Vmm.dtype == dtype ins = [Vfm, Vmm, Vfs, Vms, np.array(gamma)] ins = [x.astype(dtype, copy=False) for x in ins] # optional type conversion vfout, vmout, nbsout, agree, ithetaout = mar_mat(*ins) return { 'Values': (vfout, vmout), 'NBS': nbsout, 'theta': ithetaout, 'Decision': agree }
def mar_graphs(mdl,t=1): setup = mdl.setup V = mdl.V from marriage import v_mar_igrid from gridvec import VecOnGrid agrid_c = mdl.setup.agrid_c agrid_s = mdl.setup.agrid_s icouple = VecOnGrid(agrid_c,2*agrid_s).i npsi = setup.pars['n_psi_t'][t] psig = setup.exogrid.psi_t[t] nzf = setup.pars['n_zf_t'][t] zfg = setup.exogrid.zf_t[t] zmg = setup.exogrid.zm_t[t] izm = 2 wm = setup.pars['m_wage_trend'][t] + zmg[izm] wzf = np.exp( setup.pars['f_wage_trend'][t] + zfg) / np.exp(wm) ''' print(psig) izf = 3 izm = 2 inds_ = (izf*np.ones(npsi,dtype=np.int16),izm*np.ones(npsi,dtype=np.int16),np.arange(npsi,dtype=np.int16)) inds = mdl.setup.all_indices(t,inds_)[0] ''' inds = mdl.setup.all_indices(t)[0] iac = np.searchsorted(agrid_c,2.0*wm) ias = np.searchsorted(agrid_s,1.0*wm) results = list() for upp in [False,True]: uloss = 0.0 #setup.pars['disutil_shotgun'] if upp else 0.0 res = v_mar_igrid(setup,t,V[t],icouple,inds,female=True,giveabirth=upp, unplanned_pregnancy=upp, uloss_fem=0.0,uloss_mal=0.0, uloss_fem_single=uloss,uloss_mal_single=uloss, return_all=True) res_r = mdl.x_reshape(res['theta'][...,None],t).squeeze(axis=-1) tht_r = setup.thetagrid_fine[res_r] #tht_v[res['theta'] < 0] = None tht_all = ma.masked_where(res_r<0,tht_r) tht_pick = tht_all[iac,:,izm,:] print(tht_pick.shape) fig, ax = plt.subplots() cs = ax.contourf(zfg,psig,tht_pick.T,cmap='Blues',vmin=0.0,vmax=0.8) cb = fig.colorbar(cs) cb.set_label(r'Resulting female bargaining power ($\theta$)') plt.xlabel('Female productivity') plt.ylabel(r'Love shock at match ($\psi$)',labelpad=-3.0) plt.title('Bargaining: {}'.format('Unplanned Pregnancy (No Stigma)' if upp else 'Regular Match')) results.append(res) #ax.grid(True) ax.set_xticks(zfg) ax.set_yticks(psig)#np.arange(-4,5)) plt.savefig(('barg_upp.pdf' if upp else 'barg_reg_match.pdf')) result_noupp, result_upp = results izf = 3 izm = 3 ipsi = 9 fig, axs = plt.subplots(1,2) fig2, axs2 = plt.subplots() for n, upp, res in zip([0,1],[False,True],[result_noupp,result_upp]): Vfm,Vmm,Vfs,Vms,gamma = res['ins'] Vfm0, Vmm0 = [mdl.x_reshape(x,t) for x in [Vfm,Vmm]] Vfs0,Vms0 = [mdl.x_reshape(x[...,None],t) for x in [Vfs,Vms]] if not upp: norm_f = Vfs0[iac,izf,izm,ipsi] norm_m = Vms0[iac,izf,izm,ipsi] vfm_tht = Vfm0[iac,izf,izm,ipsi,:] - norm_f vmm_tht = Vmm0[iac,izf,izm,ipsi,:] - norm_m vfs_tht = Vfs0[iac,izf,izm,ipsi]*np.ones(vfm_tht.size) - norm_f vms_tht = Vms0[iac,izf,izm,ipsi]*np.ones(vmm_tht.size) - norm_m tht_fine = setup.thetagrid_fine l = 'unplanned pregnancy' if upp else 'regular match' c = 'o' if upp else 'x' axs[0].plot(tht_fine,vfm_tht,'{}-k'.format(c),label='Agree, {}'.format(l),markevery=25) axs[0].plot(tht_fine,vfs_tht,'{}--k'.format(c),label='Disagree, {}'.format(l),markevery=25) axs[1].plot(tht_fine,vmm_tht,'{}-k'.format(c),label='Agree, {}'.format(l),markevery=25) axs[1].plot(tht_fine,vms_tht,'{}--k'.format(c),label='Disagree, {}'.format(l),markevery=25) axs[1].set_title('Male') axs[0].set_title('Female') axs2.plot(tht_fine,vfm_tht-vfs_tht,'{}-k'.format(c),label='F, {}'.format(l),markevery=25) axs2.plot(tht_fine,vmm_tht-vms_tht,'{}--k'.format(c),label='M, {}'.format(l),markevery=25) if not upp: axs2.plot(tht_fine,np.zeros_like(tht_fine),':k'.format(c),markevery=25) axs[0].set_ylabel(r'Value functions (normalized)') [ax.set_xlabel(r'Bargaining power ($\theta$)') for ax in axs] axs[1].legend(bbox_to_anchor=(-1.4, -0.175),loc='upper left',ncol=2) fig.suptitle('Change in values b/c of unplanned pregnancy',fontsize=16) fig.subplots_adjust(bottom=+0.25) fig.savefig('change_upp.pdf',bbox='tight',pad_inches=0.5) fig2.suptitle('Surplus over disagreement',fontsize=16) axs2.legend(ncol=1) axs2.set_xlim(0.0,1.0) #axs[0].set_ylim(-50,10) #axs[1].set_ylim(-50,10)
def couples(): ss = self.single_state decision = self.Mlist[ipol].decisions[t][sname] # by default keep the same theta and weights self.itheta[ind, t + 1] = self.itheta[ind, t] nt = self.Mlist[ipol].setup.ntheta_fine # initiate renegotiation isc = self.iassets[ind, t + 1] iall, izf, izm, ipsi = self.Mlist[ipol].setup.all_indices( t + 1, self.iexo[ind, t + 1]) iz = izf if self.female else izm itht = self.itheta[ind, t + 1] agrid = self.Mlist[ipol].setup.agrid_c agrids = self.Mlist[ipol].setup.agrid_s sc = agrid[isc] # needed only for dividing asssets thts_all = decision['thetas'] thts_orig_all = np.broadcast_to( np.arange(nt)[None, None, :], thts_all.shape) thts = thts_all[isc, iall, itht] thts_orig = thts_orig_all[ isc, iall, itht] #this line below takes 43% of the time in coupls dec = decision['Decision'] #this guy below account for 24% of the time in couple i_stay = dec[isc, iall] if dec.ndim == 2 else dec[ isc, iall, itht] #i_stay = dec[isc,iall,itht] bil_bribing = ('Bribing' in decision) i_div = ~i_stay #ifem=decision['Divorce'][0][isc,iall][...,None]<self.Mlist[ipol].V[t]['Couple, M']['VF'][isc,iall,:] #imal=decision['Divorce'][1][isc,iall][...,None]<self.Mlist[ipol].V[t]['Couple, M']['VM'][isc,iall,:] #both=~np.max((ifem) & (imal),axis=1) i_ren = (i_stay) & (thts_orig != thts) i_renf = (i_stay) & (thts_orig > thts) i_renm = (i_stay) & (thts_orig < thts) i_sq = (i_stay) & (thts_orig == thts) if self.verbose: print('{} divorce, {} ren-f, {} ren-m, {} sq'.format( np.sum(i_div), np.sum(i_renf), np.sum(i_renm), np.sum(i_sq))) zf_grid = self.setup.exo_grids['Female, single'][t] zm_grid = self.setup.exo_grids['Male, single'][t] if np.any(i_div): income_fem = np.exp(zf_grid[izf[i_div]] + self.setup.pars['f_wage_trend'][t]) income_mal = np.exp(zm_grid[izm[i_div]] + self.setup.pars['m_wage_trend'][t]) income_share_fem = (income_fem) / (income_fem + income_mal) # this part determines assets costs = self.Mlist[ ipol].setup.div_costs if sname == 'Couple, M' else self.Mlist[ ipol].setup.sep_costs share_f, share_m = costs.shares_if_split( income_share_fem) #Uncomment bnelowe if ren_theta share_f = costs.shares_if_split_theta( self.setup, self.setup.thetagrid[ self.setup.v_thetagrid_fine.i[itht] + 1])[i_div] share_m = 1 - share_f #sf = share_f[i_div]*sc[i_div] #assert np.all(share_f[i_div]>=0) and np.all(share_f[i_div]<=1) #sm = share_m[i_div]*sc[i_div] sf = share_f * sc[i_div] assert np.all(share_f >= 0) and np.all(share_f <= 1) sm = share_m * sc[i_div] s = sf if self.female else sm shks = 1 - self.shocks_div_a[ind[i_div], t] # if bribing happens we overwrite this self.iassets[ind[i_div], t + 1] = VecOnGrid( self.Mlist[ipol].setup.agrid_s, s).roll(shocks=shks) if bil_bribing: iassets = decision['Bribing'][ 1] if self.female else decision['Bribing'][2] do_bribing = decision['Bribing'][0] iassets_ifdiv = iassets[ isc[i_div], iall[i_div], itht[i_div]] # assets resulted from bribing do_b = do_bribing[ isc[i_div], iall[i_div], itht[i_div]] # True / False if bribing happens assert np.all(iassets_ifdiv[do_b] >= 0) if np.any(do_b): #n_b = np.sum(do_b) #n_tot = np.sum(i_div) #share_b = int(100*n_b/n_tot) #print('bribing happens in {} cases, that is {}% of all divorces'.format(n_b,share_b)) self.iassets[ind[i_div][do_b], t + 1] = iassets_ifdiv[do_b] #print(np.mean(agrid[isc[i_div][do_b]]/(agrids[decision['Bribing'][1][isc[i_div][do_b],iall[i_div][do_b],itht[i_div][do_b]]]+ # agrids[decision['Bribing'][2][isc[i_div][do_b],iall[i_div][do_b],itht[i_div][do_b]]]))) #aaa=self.Mlist[ipol].setup.agrid_c[self.iassets[ind[i_div][do_b],t+1]]/(self.Mlist[ipol].setup.agrid_c[self.iassetss[ind[i_div][do_b],t+1]]) #aaa1=(self.Mlist[ipol].setup.agrid_c[self.iassetss[ind[i_div][do_b],t+1]]>0) #if sname == "Couple, M":print(np.mean(aaa[aaa1])) self.itheta[ind[i_div], t + 1] = -1 self.iexo[ind[i_div], t + 1] = iz[i_div] self.state[ind[i_div], t + 1] = self.state_codes[ss] if sname == "Couple, M": self.divorces[ind[i_div], t + 1] = True #FLS self.ils_i[ind[i_div], t + 1] = self.ils_def if np.any(i_ren): self.itheta[ind[i_ren], t + 1] = thts[i_ren] #tg = self.setup.v_thetagrid_fine #Distinguish between marriage and cohabitation if sname == "Couple, M": self.state[ind[i_ren], t + 1] = self.state_codes[sname] ipick = (self.iassets[ind[i_ren], t + 1], self.iexo[ind[i_ren], t + 1], self.itheta[ind[i_ren], t + 1]) self.ils_i[ind[i_ren], t + 1] = self.Mlist[ipol].decisions[ t + 1][sname]['fls'][ipick] else: i_coh = decision[ 'Cohabitation preferred to Marriage'][isc, iall, thts] i_coh1 = i_coh[i_ren] ipick = (self.iassets[ind[i_ren], t + 1], self.iexo[ind[i_ren], t + 1], self.itheta[ind[i_ren], t + 1]) ils_if_mar = self.Mlist[ipol].decisions[ t + 1]["Couple, M"]['fls'][ipick] ils_if_coh = self.Mlist[ipol].decisions[ t + 1]["Couple, C"]['fls'][ipick] self.ils_i[ind[i_ren], t + 1] = i_coh1 * ils_if_coh + ( 1 - i_coh1) * ils_if_mar self.state[ ind[i_ren], t + 1] = i_coh1 * self.state_codes["Couple, C"] + ( 1 - i_coh1) * self.state_codes["Couple, M"] if np.any(i_sq): self.state[ind[i_sq], t + 1] = self.state_codes[sname] # do not touch theta as already updated #Distinguish between marriage and cohabitation if sname == "Couple, M": self.state[ind[i_sq], t + 1] = self.state_codes[sname] ipick = (self.iassets[ind[i_sq], t + 1], self.iexo[ind[i_sq], t + 1], self.itheta[ind[i_sq], t + 1]) self.ils_i[ind[i_sq], t + 1] = self.Mlist[ipol].decisions[ t + 1][sname]['fls'][ipick] else: i_coh = decision[ 'Cohabitation preferred to Marriage'][isc, iall, thts] i_coh1 = i_coh[i_sq] self.state[ ind[i_sq], t + 1] = i_coh1 * self.state_codes["Couple, C"] + ( 1 - i_coh1) * self.state_codes["Couple, M"] ipick = (self.iassets[ind[i_sq], t + 1], self.iexo[ind[i_sq], t + 1], self.itheta[ind[i_sq], t + 1]) ils_if_mar = self.Mlist[ipol].decisions[ t + 1]["Couple, M"]['fls'][ipick] ils_if_coh = self.Mlist[ipol].decisions[ t + 1]["Couple, C"]['fls'][ipick] self.ils_i[ind[i_sq], t + 1] = i_coh1 * ils_if_coh + ( 1 - i_coh1) * ils_if_mar self.state[ ind[i_sq], t + 1] = i_coh1 * self.state_codes["Couple, C"] + ( 1 - i_coh1) * self.state_codes["Couple, M"]
def anext(self, t): # finds savings (potenitally off-grid) for ipol in range(self.npol): for ist, sname in enumerate(self.state_codes): is_state_any_pol = (self.state[:, t] == ist) is_pol = (self.policy_ind[:, min(t + 1, self.T - 1)] == ipol) is_state = (is_state_any_pol) & (is_pol) use_theta = self.has_theta[ist] nst = np.sum(is_state) if nst == 0: continue ind = np.where(is_state)[0] pol = self.Mlist[ipol].decisions[t][sname] if not use_theta: #Dictionaries below account for 90% of the time in this function anext = pol['s'][self.iassets[ind, t], self.iexo[ind, t]] if t + 1 < self.T: self.iassets[ind, t + 1] = VecOnGrid( self.setup.agrid_s, anext).roll(shocks=self.shocks_single_a[ind, t]) self.iassetss[ind, t + 1] = self.iassets[ind, t + 1].copy() self.s[ind, t] = anext if self.draw: self.c[ind, t] = pol['c'][self.iassets[ind, t], self.iexo[ind, t]] if self.draw: self.x[ind, t] = pol['x'][self.iassets[ind, t], self.iexo[ind, t]] else: # interpolate in both assets and theta # function apply_2dim is experimental but I checked it at this setup # apply for couples anext = pol['s'][self.iassets[ind, t], self.iexo[ind, t], self.itheta[ind, t]] self.s[ind, t] = anext if self.draw: self.x[ind, t] = pol['x'][self.iassets[ind, t], self.iexo[ind, t], self.itheta[ind, t]] if self.draw: self.c[ind, t] = pol['c'][self.iassets[ind, t], self.iexo[ind, t], self.itheta[ind, t]] if t + 1 < self.T: self.iassets[ind, t + 1] = VecOnGrid( self.setup.agrid_c, anext).roll(shocks=self.shocks_couple_a[ind, t]) self.iassetss[ind, t + 1] = self.iassets[ind, t + 1].copy() assert np.all(anext >= 0)
def __init__(self, Mlist, age_uni, female=False, pswitchlist=None, N=15000, T=None, verbose=True, nosim=False, draw=False): np.random.seed(8) # take the stuff from the model and arguments # note that this does not induce any copying just creates links if type(Mlist) is not list: Mlist = [Mlist] #Unilateral Divorce self.Mlist = Mlist self.Vlist = [M.V for M in Mlist] self.declist = [M.decisions for M in Mlist] self.npol = len(Mlist) self.transition = len(self.Mlist) > 1 if T is None: T = self.Mlist[0].setup.pars['T'] self.setup = self.Mlist[0].setup self.state_names = self.setup.state_names self.N = N self.T = T self.verbose = verbose self.timer = self.Mlist[0].time self.draw = draw self.female = female self.single_state = 'Female, single' if female else 'Male, single' #Divorces self.divorces = np.zeros((N, T), bool) # all the randomness is here shokko = np.random.random_sample((9, N, T)) self.shocks_single_iexo2 = shokko[ 8, :, :] # np.random.random_sample((N,T)) self.shocks_single_iexo = shokko[ 0, :, :] # np.random.random_sample((N,T)) self.shocks_single_meet = shokko[ 1, :, :] # np.random.random_sample((N,T)) self.shocks_couple_iexo = shokko[ 2, :, :] # np.random.random_sample((N,T)) self.shocks_single_a = shokko[ 3, :, :] # np.random.random_sample((N,T)) self.shocks_couple_a = shokko[ 4, :, :] # np.random.random_sample((N,T)) self.shocks_div_a = shokko[5, :, :] #np.random.random_sample((N,T)) z_t = self.setup.exogrid.zf_t if female else self.setup.exogrid.zm_t sig = self.setup.pars['sig_zf_0'] if female else self.setup.pars[ 'sig_zm_0'] z_prob = int_prob(z_t[0], sig=sig) shocks_init = shokko[6, :, 0] #np.random.random_sample((N,)) i_z = np.sum((shocks_init[:, None] > np.cumsum(z_prob)[None, :]), axis=1) iexoinit = i_z # initial state self.shocks_transition = shokko[ 7, :, :] #np.random.random_sample((N,T)) # no randomnes past this line please # initialize assets self.iassets = np.zeros((N, T), np.int16) self.iassetss = np.zeros((N, T), np.int16) self.tempo = VecOnGrid(self.setup.agrid_s, self.iassets[:, 0]) # initialize FLS #self.ils=np.ones((N,T),np.float64) self.ils_i = np.ones((N, T), np.int8) * (len(self.setup.ls_levels) - 1) self.ils_i[:, -1] = 5 # initialize theta self.itheta = -np.ones((N, T), np.int16) # initialize iexo self.iexo = np.zeros((N, T), np.int16) self.iexos = np.zeros((N, T), np.int16) # TODO: look if we can/need fix the shocks here... self.iexo[:, 0] = iexoinit self.iexos[:, 0] = iexoinit # NB: the last column of these things will not be filled # c refers to consumption expenditures (real consumption of couples # may be higher b/c of returns to scale) self.c = np.zeros((N, T), np.float32) self.x = np.zeros((N, T), np.float32) self.s = np.zeros((N, T), np.float32) self.state_codes = dict() self.has_theta = list() for i, name in enumerate(self.setup.state_names): self.state_codes[name] = i self.has_theta.append((name == 'Couple, C' or name == 'Couple, M')) # initialize state self.state = np.zeros((N, T), dtype=np.int8) self.state[:, 0] = self.state_codes[ self.single_state] # everyone starts as female self.timer('Simulations, creation', verbose=self.verbose) self.ils_def = self.setup.nls - 1 #Create a file with the age of the change foreach person self.policy_ind = np.zeros((N, T), dtype=np.int8) if pswitchlist == None: pswitchlist = [np.eye(self.npol)] * T # this simulates "exogenous" transitions of polciy functions # policy_ind stands for index of the policies to apply, they are # from 0 to (self.npol-1) zeros = np.zeros((N, ), dtype=np.int8) mat_init = pswitchlist[0] self.policy_ind[:, 0] = mc_simulate( zeros, mat_init, shocks=self.shocks_transition[:, 0]) # everyone starts with 0 if self.npol > 1: for t in range(T - 1): mat = pswitchlist[t + 1] self.policy_ind[:, t + 1] = mc_simulate( self.policy_ind[:, t], mat, shocks=self.shocks_transition[:, t + 1]) else: self.policy_ind[:] = 0 if not nosim: self.simulate()
def v_mar(setup, V, t, iassets_couple, iexo_couple, *, match_type, female): # this builds matrix for all matches specified by grid positions # iassets_couple (na X nmatches) and iexo_couple (nmatches) iexo, izf, izm, ipsi = setup.all_indices(t, iexo_couple) vals = pick_values(setup, V, match_type=match_type) V_fem, V_mal = vals['V_fem'], vals['V_mal'] # obtain partner's value assets_partner = np.clip( setup.agrid_c[iassets_couple] - setup.agrid_s[:, None], 0.0, setup.agrid_s.max()) # this can be fastened by going over repeated values of ipsi v_assets_partner = VecOnGrid(setup.agrid_s, assets_partner) i, wnext, wthis = v_assets_partner.i, v_assets_partner.wnext, v_assets_partner.wthis if female: V_f_no = V_fem[:, izf] V_m_no = V_mal[i, izm[None, :]] * wthis + V_mal[i + 1, izm[None, :]] * wnext else: V_f_no = V_fem[i, izf[None, :]] * wthis + V_fem[i + 1, izf[None, :]] * wnext V_m_no = V_mal[:, izm] V_fem_mar, V_mal_mar = vals['V_fem_mar'], vals['V_mal_mar'] ia = iassets_couple V_f_yes = V_fem_mar[ia, iexo[None, :], :] V_m_yes = V_mal_mar[ia, iexo[None, :], :] assert V_f_yes.shape[:-1] == V_f_no.shape assert V_m_yes.shape[:-1] == V_m_no.shape # fill abortion decisions if match_type == 'Unplanned pregnancy' and female: do_abortion = vals['i_abortion'][:, izf] else: do_abortion = np.zeros(V_f_no.shape, dtype=np.bool_) it, wnt = setup.v_thetagrid_fine.i, setup.v_thetagrid_fine.wnext from marriage_gpu import v_mar_gpu if not ugpu: v_f, v_m, agree, nbs, itheta = get_marriage_values( V_f_yes, V_m_yes, V_f_no, V_m_no, it, wnt) else: v_f, v_m, agree, nbs, itheta = v_mar_gpu(V_f_yes, V_m_yes, V_f_no, V_m_no, it, wnt) return { 'V_fem': v_f, 'V_mal': v_m, 'Agree': agree, 'NBS': nbs, 'itheta': itheta, 'Abortion': do_abortion }
def __init__(self, nogrid=False, divorce_costs_k='Default', divorce_costs_nk='Default', **kwargs): p = dict() T = 55 Tret = 45 # first period when the agent is retired Tfert = 18 # first peroid when infertile Tdiv = 44 # first period when cannot divorce / renegotiate Tmeet = 25 # first period when stop meeting partners Tinc = 25 # first period where stop tracking income process and assume it to be fixed p['T'] = T p['Tret'] = Tret p['Tfert'] = Tfert p['Tsim'] = T p['n_zf_t'] = [7] * Tret + [1] * (T - Tret) p['n_zm_t'] = [5] * Tret + [1] * (T - Tret) p['sigma_psi_mult'] = 0.28 p['sigma_psi'] = 0.11 p['R_t'] = [1.025] * T p['n_psi'] = 15 p['beta_t'] = [0.98] * T p['A'] = 1.0 # consumption in couple: c = (1/A)*[c_f^(1+rho) + c_m^(1+rho)]^(1/(1+rho)) p['crra_power'] = 1.5 p['couple_rts'] = 0.23 p['sig_partner_a'] = 0.1 p['mu_partner_a_female'] = 0.00 p['mu_partner_a_male'] = -0.00 p['dump_factor_z'] = 0.75 p['sig_partner_z'] = 0.25 p['mu_partner_z_male'] = -0.02 p['mu_partner_z_female'] = 0.02 p['m_bargaining_weight'] = 0.5 p['pmeet_21'] = 0.1 p['pmeet_28'] = 0.2 p['pmeet_35'] = 0.1 p['m_zf'] = 0.9 p['m_zf0'] = 1.0 p['z_drift'] = -0.09 p['no kids at meeting'] = True p['high education'] = True # what trend to pick p['any kids'] = True p['wret'] = 0.8 p['uls'] = 0.2 p['pls'] = 1.0 p['income_sd_mult'] = 1.0 p['pay_gap'] = True p['preg_mult'] = 1.0 p['u_shift_mar'] = 0.0 p['u_shift_coh'] = 0.0 p['sm_shift'] = 0.0 p['disutil_marry_sm_fem_coef'] = 0.0 p['disutil_marry_sm_mal_coef'] = 10.0 p['disutil_shotgun_coef'] = 2.0 p['pmeet_multiplier_fem'] = 1.0 p['p_to_meet_sm_if_mal'] = 0.1 p['taste_shock_mult'] = 1.0 p['p_abortion_access'] = 0.5 p['abortion_costs_mult'] = 10.0 p['u_lost_divorce_mult'] = 0.0 p['child_a_cost'] = 0.0 p['child_support_share'] = 0.0 p['util_lam'] = 0.7 p['util_alp'] = 0.5 p['util_xi'] = 1.5 p['util_kap'] = 0.5 p['util_qbar'] = 0.0 p['util_out_lf'] = 0.0 p['preg_21'] = 0.01 p['preg_28'] = 0.5 p['preg_35'] = 0.3 for key, value in kwargs.items(): assert (key in p), 'wrong name?' p[key] = value if p['high education']: p['sig_zm'] = p['income_sd_mult'] * 0.16138593 p['sig_zm_0'] = p['income_sd_mult'] * 0.41966813 # FIXME: I need guidance how to pin these down p['sig_zf'] = p['income_sd_mult'] * p['m_zf'] * 0.19571624 p['sig_zf_0'] = p['income_sd_mult'] * p['m_zf0'] * 0.43351219 else: p['sig_zm'] = p['income_sd_mult'] * 0.2033373 p['sig_zm_0'] = p['income_sd_mult'] * 0.40317171 p['sig_zf'] = p['income_sd_mult'] * p['m_zf'] * 0.14586778 p['sig_zf_0'] = p['income_sd_mult'] * p['m_zf0'] * 0.62761052 # college if p['high education']: m_trend_data = [ 0.0, 0.11316599, .2496034, .31260625, .37472204, .4268548, .48067884, .52687573, .57293878, .60941412, .65015743, .6685226, .72482815, .74446455, .76712521, .78038137, .79952806, .80092523, .81972567, .82913486, .83849471, .84308452, .84646086, .85437072, .85499576 ] f_trend_data = [ 0.0, 0.06715984, .21149606, .32283002, .46885336, .52712037, .58302632, .63348555, .68024646, .71450132, .74246337, .77044807, .79946406, .80640353, .83799304, .85356081, .86832235, .87407447, .87820755, .86840901, .87630054, .8765972, .87894493, .87800553, .87932908 ] nm = len(m_trend_data) - 1 nf = len(f_trend_data) - 1 t0 = 4 gap = 3.0340077 - 2.8180354 # male - female c_female = -f_trend_data[t0] c_male = gap - m_trend_data[t0] p['m_wage_trend'] = np.array( [c_male + m_trend_data[min(t, nm)] for t in range(T)]) p['f_wage_trend'] = np.array( [c_female + f_trend_data[min(t, nf)] for t in range(T)]) else: # no college p['m_wage_trend'] = np.array([ -0.2424105 + 0.037659 * (min(t + 2, 30) - 5) - 0.0015337 * ((min(t + 2, 30) - 5)**2) + 0.000026 * ((min(t + 2, 30) - 5)**3) for t in range(T) ]) p['f_wage_trend'] = np.array([ -0.3668214 + 0.0264887 * (min(t, 30) - 5) - 0.0012464 * ((min(t, 30) - 5)**2) + 0.0000251 * ((min(t, 30) - 5)**3) for t in range(T) ]) if not p['pay_gap']: p['sig_zf'], p['sig_zf_0'] = p['sig_zm'], p['sig_zm_0'] p['f_wage_trend'] = p['m_wage_trend'] # derivative parameters p['sigma_psi_init'] = p['sigma_psi_mult'] * p['sigma_psi'] p['disutil_marry_sm_mal'] = p['disutil_marry_sm_mal_coef'] * p[ 'u_shift_mar'] p['disutil_marry_sm_fem'] = p['disutil_marry_sm_fem_coef'] * p[ 'u_shift_mar'] p['disutil_shotgun'] = p['disutil_shotgun_coef'] * p['sigma_psi_init'] p['abortion_costs'] = p['abortion_costs_mult'] * p['u_shift_mar'] p['u_lost_divorce'] = p['u_lost_divorce_mult'] * p['sigma_psi_init'] p['preg_az'] = 0.00 p['preg_azt'] = 0.00 #Get the probability of meeting, adjusting for year-period p['taste_shock'] = 0.0 * p['taste_shock_mult'] * 0.0 #p['sigma_psi'] p['is fertile'] = [p['any kids']] * Tfert + [False] * (T - Tfert) p['can divorce'] = [True] * Tdiv + [False] * (T - Tdiv) #p['poutsm_t'] = [p['poutsm']]*T p['pmeet_0'], p['pmeet_t'], p['pmeet_t2'] = prob_polyfit( (p['pmeet_21'], 0), (p['pmeet_28'], 7), (p['pmeet_35'], 14), max_power=2) p['preg_a0'], p['preg_at'], p['preg_at2'] = prob_polyfit( (p['preg_21'], 0), (p['preg_28'], 7), (p['preg_35'], 14), max_power=2) p['pmeet_t'] = [ np.clip(p['pmeet_0'] + t * p['pmeet_t'] + (t**2) * p['pmeet_t2'], 0.0, 1.0) for t in range(Tmeet) ] + [0.0] * (T - Tmeet) p['n_psi_t'] = [p['n_psi']] * T self.pars = p self.dtype = np.float64 # type for all floats # relevant for integration self.state_names = [ 'Female, single', 'Male, single', 'Female and child', 'Couple, no children', 'Couple and child' ] # female labor supply lmin = 0.2 lmax = 1.0 nl = 2 ls = np.array([0.2, 1.0]) #np.linspace(lmin,lmax,nl,dtype=self.dtype) ps = np.array([p['pls'], 0.0]) ls_ushift = np.array([p['util_out_lf'], 0.0]) self.ls_levels = dict() self.ls_levels['Couple, no children'] = np.array([1.0], dtype=self.dtype) self.ls_levels['Female, single'] = np.array([1.0], dtype=self.dtype) self.ls_levels['Male, single'] = np.array([1.0], dtype=self.dtype) self.ls_levels['Couple and child'] = ls self.ls_levels['Female and child'] = ls self.ls_ushift = dict() self.ls_ushift['Couple, no children'] = np.array([0.0], dtype=self.dtype) self.ls_ushift['Female, single'] = np.array([0.0], dtype=self.dtype) self.ls_ushift['Male, single'] = np.array([0.0], dtype=self.dtype) self.ls_ushift['Couple and child'] = ls_ushift self.ls_ushift['Female and child'] = ls_ushift #self.ls_utilities = np.array([p['uls'],0.0],dtype=self.dtype) self.ls_pdown = dict() self.ls_pdown['Couple, no children'] = np.array([0.0], dtype=self.dtype) self.ls_pdown['Female, single'] = np.array([0.0], dtype=self.dtype) self.ls_pdown['Male, single'] = np.array([0.0], dtype=self.dtype) self.ls_pdown['Female and child'] = ps self.ls_pdown['Couple and child'] = ps self.nls = dict() self.nls['Couple and child'] = len(self.ls_levels['Couple and child']) self.nls['Couple, no children'] = len( self.ls_levels['Couple, no children']) self.nls['Female and child'] = len(self.ls_levels['Female and child']) self.nls['Female, single'] = len(self.ls_levels['Female, single']) self.nls['Male, single'] = len(self.ls_levels['Male, single']) #Cost of Divorce if divorce_costs_k == 'Default': # by default the costs are set in the bottom self.divorce_costs_k = DivorceCosts( u_lost_m=self.pars['u_lost_divorce'], u_lost_f=self.pars['u_lost_divorce']) else: if isinstance(divorce_costs_k, dict): # you can feed in arguments to DivorceCosts self.divorce_costs_k = DivorceCosts(**divorce_costs_k) else: # or just the output of DivorceCosts assert isinstance(divorce_costs_k, DivorceCosts) self.divorce_costs_k = divorce_costs_k #Cost of Separation if divorce_costs_nk == 'Default': # by default the costs are set in the bottom self.divorce_costs_nk = DivorceCosts( u_lost_m=self.pars['u_lost_divorce'], u_lost_f=self.pars['u_lost_divorce']) else: if isinstance(divorce_costs_nk, dict): # you can feed in arguments to DivorceCosts self.divorce_costs_nk = DivorceCosts(**divorce_costs_nk) else: # or just the output of DivorceCosts assert isinstance(divorce_costs_nk, DivorceCosts) self.divorce_costs_nk = divorce_costs_nk # exogrid should be deprecated if not nogrid: exogrid = dict() # let's approximate three Markov chains # this sets up exogenous grid # FIXME: this uses number of points from 0th entry. # in principle we can generalize this exogrid['zf_t'], exogrid['zf_t_mat'] = rouw_nonst( p['T'], p['sig_zf'], p['sig_zf_0'], p['n_zf_t'][0]) exogrid['zm_t'], exogrid['zm_t_mat'] = rouw_nonst( p['T'], p['sig_zm'], p['sig_zm_0'], p['n_zm_t'][0]) for t in range(Tinc, Tret): for key in ['zf_t', 'zf_t_mat', 'zm_t', 'zm_t_mat']: exogrid[key][t] = exogrid[key][Tinc] for t in range(Tret, T): exogrid['zf_t'][t] = np.array([np.log(p['wret'])]) exogrid['zm_t'][t] = np.array([np.log(p['wret'])]) exogrid['zf_t_mat'][t] = np.atleast_2d(1.0) exogrid['zm_t_mat'][t] = np.atleast_2d(1.0) # fix transition from non-retired to retired exogrid['zf_t_mat'][Tret - 1] = np.ones((p['n_zf_t'][Tret - 1], 1)) exogrid['zm_t_mat'][Tret - 1] = np.ones((p['n_zm_t'][Tret - 1], 1)) exogrid['psi_t'], exogrid['psi_t_mat'] = rouw_nonst( p['T'], p['sigma_psi'], p['sigma_psi_init'], p['n_psi_t'][0]) zfzm, zfzmmat = combine_matrices_two_lists(exogrid['zf_t'], exogrid['zm_t'], exogrid['zf_t_mat'], exogrid['zm_t_mat']) all_t, all_t_mat = combine_matrices_two_lists( zfzm, exogrid['psi_t'], zfzmmat, exogrid['psi_t_mat']) all_t_mat_sparse_T = [ sparse.csc_matrix(D.T) if D is not None else None for D in all_t_mat ] #Create a new bad version of transition matrix p(zf_t) zf_bad = [ tauchen_drift(exogrid['zf_t'][t], exogrid['zf_t'][t + 1], 1.0, p['sig_zf'], p['z_drift']) for t in range(self.pars['T'] - 1) ] + [None] #zf_bad = [cut_matrix(exogrid['zf_t_mat'][t]) if t < Tret -1 # else (exogrid['zf_t_mat'][t] if t < T - 1 else None) # for t in range(self.pars['T'])] zf_t_mat_down = zf_bad zfzm, zfzmmat = combine_matrices_two_lists(exogrid['zf_t'], exogrid['zm_t'], zf_t_mat_down, exogrid['zm_t_mat']) all_t_down, all_t_mat_down = combine_matrices_two_lists( zfzm, exogrid['psi_t'], zfzmmat, exogrid['psi_t_mat']) all_t_mat_down_sparse_T = [ sparse.csc_matrix(D.T) if D is not None else None for D in all_t_mat_down ] all_t_mat_by_l_nk = [[ (1 - p) * m + p * md if m is not None else None for m, md in zip(all_t_mat, all_t_mat_down) ] for p in self.ls_pdown['Couple, no children']] all_t_mat_by_l_spt_nk = [[ (1 - p) * m + p * md if m is not None else None for m, md in zip(all_t_mat_sparse_T, all_t_mat_down_sparse_T) ] for p in self.ls_pdown['Couple, no children']] all_t_mat_by_l_k = [[ (1 - p) * m + p * md if m is not None else None for m, md in zip(all_t_mat, all_t_mat_down) ] for p in self.ls_pdown['Couple and child']] all_t_mat_by_l_spt_k = [[ (1 - p) * m + p * md if m is not None else None for m, md in zip(all_t_mat_sparse_T, all_t_mat_down_sparse_T) ] for p in self.ls_pdown['Couple and child']] zf_t_mat_by_l_sk = [[ (1 - p) * m + p * md if md is not None else None for m, md in zip(exogrid['zf_t_mat'], zf_bad) ] for p in self.ls_pdown['Female and child']] exogrid['all_t_mat_by_l_nk'] = all_t_mat_by_l_nk exogrid['all_t_mat_by_l_spt_nk'] = all_t_mat_by_l_spt_nk exogrid['all_t_mat_by_l_k'] = all_t_mat_by_l_k exogrid['all_t_mat_by_l_spt_k'] = all_t_mat_by_l_spt_k exogrid['zf_t_mat_by_l_sk'] = zf_t_mat_by_l_sk exogrid['all_t'] = all_t Exogrid_nt = namedtuple('Exogrid_nt', exogrid.keys()) self.exogrid = Exogrid_nt(**exogrid) self.pars['nexo_t'] = [v.shape[0] for v in all_t] self.compute_child_support_transitions( child_support_share=p['child_support_share']) #assert False #Grid Couple self.na = 40 self.amin = 0 self.amax = 100 self.agrid_c = np.linspace(self.amin**0.5, self.amax**0.5, self.na, dtype=self.dtype)**2 #tune=1.5 #self.agrid_c = np.geomspace(self.amin+tune,self.amax+tune,num=self.na)-tune # this builds finer grid for potential savings s_between = 7 # default numer of points between poitns on agrid s_da_min = 0.01 # minimal step (does not create more points) s_da_max = 0.1 # maximal step (creates more if not enough) self.sgrid_c = build_s_grid(self.agrid_c, s_between, s_da_min, s_da_max) self.vsgrid_c = VecOnGrid(self.agrid_c, self.sgrid_c) #Grid Single self.amin_s = 0 self.amax_s = self.amax / 2.0 self.agrid_s = self.agrid_c / 2.0 #tune_s=1.5 #self.agrid_s = np.geomspace(self.amin_s+tune_s,self.amax_s+tune_s,num=self.na)-tune_s self.sgrid_s = build_s_grid(self.agrid_s, s_between, s_da_min, s_da_max) self.vsgrid_s = VecOnGrid(self.agrid_s, self.sgrid_s) # grid for theta self.ntheta = 11 self.thetamin = 0.01 self.thetamax = 0.99 self.thetagrid = np.linspace(self.thetamin, self.thetamax, self.ntheta, dtype=self.dtype) self.child_a_cost_single = np.minimum(self.agrid_s, self.pars['child_a_cost']) self.child_a_cost_couple = np.minimum(self.agrid_c, self.pars['child_a_cost']) self.vagrid_child_single = VecOnGrid( self.agrid_s, self.agrid_s - self.child_a_cost_single) self.vagrid_child_couple = VecOnGrid( self.agrid_c, self.agrid_c - self.child_a_cost_couple) # construct finer grid for bargaining ntheta_fine = 10 * self.ntheta # actual number may be a bit bigger self.thetagrid_fine = np.unique( np.concatenate((self.thetagrid, np.linspace(self.thetamin, self.thetamax, ntheta_fine, dtype=self.dtype)))) self.ntheta_fine = self.thetagrid_fine.size i_orig = list() for theta in self.thetagrid: assert theta in self.thetagrid_fine i_orig.append(np.where(self.thetagrid_fine == theta)[0]) assert len(i_orig) == self.thetagrid.size # allows to recover original gird points on the fine grid self.theta_orig_on_fine = np.array(i_orig).flatten() self.v_thetagrid_fine = VecOnGrid(self.thetagrid, self.thetagrid_fine) # precomputed object for interpolation self.exo_grids = { 'Female, single': exogrid['zf_t'], 'Male, single': exogrid['zm_t'], 'Female and child': exogrid['zf_t'], 'Couple and child': exogrid['all_t'], 'Couple, no children': exogrid['all_t'] } self.exo_mats = { 'Female, single': exogrid['zf_t_mat'], 'Male, single': exogrid['zm_t_mat'], 'Female and child': exogrid['zf_t_mat_by_l_sk'], 'Couple and child': exogrid['all_t_mat_by_l_k'], 'Couple, no children': exogrid['all_t_mat_by_l_nk'] } # sparse version? self.utility_shifters = { 'Female, single': 0.0, 'Male, single': 0.0, 'Female and child': p['u_shift_mar'] + p['sm_shift'], 'Couple and child': p['u_shift_mar'], 'Couple, no children': p['u_shift_coh'] } # this pre-computes transition matrices for meeting a partner try: self.partners_distribution_fem = filer('az_dist_fem.pkl', 0, 0, repeat=False) self.partners_distribution_mal = filer('az_dist_mal.pkl', 0, 0, repeat=False) except: print('recreating estimates...') est_fem = get_estimates( fname='income_assets_distribution_male.csv', age_start=23, age_stop=42, zlist=self.exogrid.zm_t[2:]) filer('az_dist_fem.pkl', est_fem, True, repeat=False) self.partners_distribution_fem = est_fem est_mal = get_estimates( fname='income_assets_distribution_female.csv', age_start=21, age_stop=40, zlist=self.exogrid.zf_t[0:]) filer('az_dist_mal.pkl', est_mal, True, repeat=False) self.partners_distribution_mal = est_mal self.build_matches() # building m grid ezfmin = min([ np.min(np.exp(g + t)) for g, t in zip(exogrid['zf_t'], p['f_wage_trend']) ]) ezmmin = min([ np.min(np.exp(g + t)) for g, t in zip(exogrid['zm_t'], p['m_wage_trend']) ]) ezfmax = max([ np.max(np.exp(g + t)) for g, t in zip(exogrid['zf_t'], p['f_wage_trend']) ]) ezmmax = max([ np.max(np.exp(g + t)) for g, t in zip(exogrid['zm_t'], p['m_wage_trend']) ]) self.money_min = 0.95 * min(self.ls_levels['Female and child']) * min( ezmmin, ezfmin) # cause FLS can be up to 0 mmin = self.money_min mmax = ezfmax + ezmmax + np.max(self.pars['R_t']) * self.amax mint = (ezfmax + ezmmax) # poin where more dense grid begins ndense = 600 nm = 1500 gsparse = np.linspace(mint, mmax, nm - ndense) gdense = np.linspace(mmin, mint, ndense + 1) # +1 as there is a common pt self.mgrid = np.zeros(nm, dtype=self.dtype) self.mgrid[ndense:] = gsparse self.mgrid[:(ndense + 1)] = gdense self.mgrid_c = self.mgrid self.mgrid_s = self.mgrid assert np.all(np.diff(self.mgrid) > 0) self.u_precompute() self.unplanned_pregnancy_probability() self.compute_taxes()
def __init__(self,nogrid=False,divorce_costs_k='Default',divorce_costs_nk='Default',**kwargs): p = dict() #age_begin = 21 #age_data = 23 # age at which the data start being available #age_death = 76 #age_retire = 65 #age_fertile = 41 T = 55 Tret = 45 # first period when the agent is retired Tfert = 20 # first peroid when infertile Tdiv = 44 # first period when cannot divorce / renegotiate Tmeet = 43 # first period when stop meeting partners Tinc = 25 # first period where stop tracking income process and assume it to be fixed p['T'] = T p['Tret'] = Tret p['Tfert'] = Tfert p['Tsim'] = T p['Tmeet'] = Tmeet p['n_zf_t'] = [7]*Tret + [1]*(T-Tret) p['n_zm_t'] = [5]*Tret + [1]*(T-Tret) p['sigma_psi_init'] = 0.28 p['mu_psi_init'] = 0.0 p['sigma_psi'] = 0.11 p['R_t'] = [1/0.96]*T p['n_psi'] = 17 p['beta_t'] = [0.96]*T p['A'] = 1.0 # consumption in couple: c = (1/A)*[c_f^(1+rho) + c_m^(1+rho)]^(1/(1+rho)) p['crra_power'] = 1.5 p['couple_rts'] = 0.23 p['sig_partner_a'] = 0.1 p['mu_partner_a_female'] = 0.00 p['mu_partner_a_male'] = -0.00 p['dump_factor_z'] = 0.75 p['sig_partner_z'] = 0.25 p['mu_partner_z_male'] = -0.02 p['mu_partner_z_female'] = 0.02 p['m_bargaining_weight'] = 0.5 p['pmeet_21'] = 0.1 p['pmeet_30'] = 0.2 p['pmeet_40'] = 0.1 p['pmeet_pre25'] = None p['ppreg_pre25'] = None p['pmeet_exo'] = None p['ppreg_exo'] = None p['m_zf'] = 1.0 p['m_zf0'] = 1.0 p['no kids at meeting'] = True p['high education'] = True # what trend to pick p['any kids'] = True p['z_drift'] = -0.09 if p['high education'] else -0.06 p['wret'] = 0.8 p['uls'] = 0.2 p['pls'] = 1.0 p['income_sd_mult'] = 1.0 p['pay_gap'] = True p['preg_mult'] = 1.0 p['u_shift_mar'] = 0.0 p['u_shift_coh'] = 0.0 p['sm_shift'] = 0.0 p['disutil_marry_sm_fem'] = 0.0 p['disutil_marry_sm_mal'] = 10.0 p['disutil_shotgun'] = 2.0 p['pmeet_multiplier_fem'] = 1.0 p['p_to_meet_sm_if_mal'] = 0.1 p['taste_shock_mult'] = 1.0 p['p_abortion_access'] = 0.5 p['abortion_costs'] = 10.0 p['u_lost_divorce'] = 0.0 p['child_a_cost'] = 0.0 p['child_support_share'] = 0.2 p['child_support_awarded_nm'] = 0.284 p['child_support_awarded_div'] = 0.461 p['util_lam'] = 0.7 p['util_alp'] = 0.5 p['util_xi'] = 1.5 p['util_kap'] = 0.5 p['util_qbar'] = 0.0 p['util_out_lf'] = 0.0 p['ppreg_sim_mult'] = 1.0 p['tax_childless_couples'] = True p['tax_couples_woth_children'] = True p['tax_single_mothers'] = True p['preg_21'] = 0.01 p['preg_28'] = 0.5 p['preg_35'] = 0.3 for key, value in kwargs.items(): assert (key in p), 'wrong name?' p[key] = value if p['high education']: p['sig_zm'] = p['income_sd_mult']*0.16138593 p['sig_zm_0'] = p['income_sd_mult']*0.41966813 p['sig_zf'] = p['income_sd_mult']*p['m_zf']*0.19571624 p['sig_zf_0'] = p['income_sd_mult']*p['m_zf0']*0.43351219 else: p['sig_zm'] = p['income_sd_mult']*0.17195085 p['sig_zm_0'] = p['income_sd_mult']*0.2268650 p['sig_zf'] = p['income_sd_mult']*p['m_zf']*0.1762148 p['sig_zf_0'] = p['income_sd_mult']*p['m_zf0']*0.1762148 p['sm_init'] = (0.02 if p['high education'] else 0.25) if p['any kids'] else 0.0 # initial share of single moms # college if p['high education']: m_trend_data = [3.3899509,3.5031169,3.6395543,3.7025571,3.7646729,3.8168057,3.8706297,3.9168266,3.9628897,3.999365,4.0401083,4.0584735,4.114779,4.1344154,4.1570761,4.1703323,4.189479,4.1908761,4.2096766,4.2190858,4.2284456,4.2330354,4.2364118,4.2443216,4.2449467] f_trend_data = [3.0796507,3.1468105,3.2911468,3.4024807,3.5485041,3.6067711,3.662677,3.7131363,3.7598972,3.794152,3.8221141,3.8500988,3.8791148,3.8860543,3.9176438,3.9332115,3.9479731,3.9537252,3.9578583,3.9480597,3.9559513,3.9562479,3.9585957,3.9576563,3.9589798] nm = len(m_trend_data)-1 nf = len(f_trend_data)-1 p['m_wage_trend'] = np.array( [m_trend_data[min(t,nm)] for t in range(T)] ) p['f_wage_trend'] = np.array( [f_trend_data[min(t,nf)] for t in range(T)] ) else: # no college m_trend_data = [3.091856,3.130104,3.2259613,3.2581962,3.2772099,3.2999002,3.3206571,3.3314928,3.3573047,3.3663062,3.3801406,3.3909449,3.4160915,3.4358479,3.4488938,3.4534575,3.4654005,3.4655065,3.4815268,3.4859583,3.4967845,3.4972438,3.5118738,3.525121,3.5271331] f_trend_data = [2.9056071,2.9427025,2.9773922,2.999882,3.0932755,3.1129375,3.1199322,3.1352323,3.1498192,3.1606338,3.168912,3.1722982,3.1775691,3.1831384,3.2040837,3.1997439,3.2122542,3.2085543,3.2209876,3.2232882,3.2273824,3.2336534,3.233879,3.244275,3.2527455] nm = len(m_trend_data)-1 nf = len(f_trend_data)-1 p['m_wage_trend'] = np.array( [m_trend_data[min(t,nm)] for t in range(T)] ) p['f_wage_trend'] = np.array( [f_trend_data[min(t,nf)] for t in range(T)] ) if not p['pay_gap']: p['sig_zf'], p['sig_zf_0'] = p['sig_zm'], p['sig_zm_0'] p['f_wage_trend'] = p['m_wage_trend'] p['preg_az'] = 0.00 p['preg_azt'] = 0.00 #Get the probability of meeting, adjusting for year-period p['taste_shock'] = 0.0 #*p['taste_shock_mult']*0.0#p['sigma_psi'] p['is fertile'] = [p['any kids']]*Tfert + [False]*(T-Tfert) p['can divorce'] = [True]*Tdiv + [False]*(T-Tdiv) #p['poutsm_t'] = [p['poutsm']]*T p['pmeet_0'], p['pmeet_t'], p['pmeet_t2'] = prob_polyfit( (p['pmeet_21'],0),(p['pmeet_30'],9),(p['pmeet_40'],19), max_power=2) p['preg_a0'], p['preg_at'], p['preg_at2'] = prob_polyfit( (p['preg_21'],0),(p['preg_28'],7),(p['preg_35'],14), max_power=2) if p['pmeet_exo'] is None: p['pmeet_t'] = [np.clip(p['pmeet_0'] + t*p['pmeet_t'] + (t**2)*p['pmeet_t2'],0.0,1.0) for t in range(20)] + \ [p['pmeet_40']]*(Tmeet - 20) + [0.0]*(T-Tmeet) p['pmeet_t'] = np.array(p['pmeet_t']) if p['pmeet_pre25'] is not None: p['pmeet_t'][:4] = p['pmeet_pre25'] else: p['pmeet_t'] = [p['pmeet_exo'][min(t,p['pmeet_exo'].size-1)] for t in range(Tmeet)] + [0.0]*(T-Tmeet) p['pmeet_t'] = np.array(p['pmeet_t']) p['n_psi_t'] = [p['n_psi']]*T p['psi_clip'] = 8.5*p['sigma_psi_init'] p['fert_prob_t'] = [0.86*(t<=3) + 0.78*(t>3 and t<=8) + 0.63*(t>=9 and t<=13) + 0.52*(t>=14) for t in range(T)] #p['fert_prob_t'] = [1.0]*T self.pars = p self.dtype = np.float64 # type for all floats # relevant for integration self.state_names = ['Female, single','Male, single','Female and child','Couple, no children','Couple and child'] # female labor supply lmin = 0.2 lmax = 1.0 nl = 2 ls = np.array([0.2,1.0]) #np.linspace(lmin,lmax,nl,dtype=self.dtype) ps = np.array([p['pls'],0.0]) ls_ushift = np.array([p['util_out_lf'],0.0]) self.ls_levels = dict() self.ls_levels['Couple, no children'] = np.array([1.0],dtype=self.dtype) self.ls_levels['Female, single'] = np.array([1.0],dtype=self.dtype) self.ls_levels['Male, single'] = np.array([1.0],dtype=self.dtype) self.ls_levels['Couple and child'] = ls self.ls_levels['Female and child'] = ls self.ls_ushift = dict() self.ls_ushift['Couple, no children'] = np.array([0.0],dtype=self.dtype) self.ls_ushift['Female, single'] = np.array([0.0],dtype=self.dtype) self.ls_ushift['Male, single'] = np.array([0.0],dtype=self.dtype) self.ls_ushift['Couple and child'] = ls_ushift self.ls_ushift['Female and child'] = ls_ushift #self.ls_utilities = np.array([p['uls'],0.0],dtype=self.dtype) self.ls_pdown = dict() self.ls_pdown['Couple, no children'] = np.array([0.0],dtype=self.dtype) self.ls_pdown['Female, single'] = np.array([0.0],dtype=self.dtype) self.ls_pdown['Male, single'] = np.array([0.0],dtype=self.dtype) self.ls_pdown['Female and child'] = ps self.ls_pdown['Couple and child'] = ps self.nls = dict() self.nls['Couple and child'] = len(self.ls_levels['Couple and child']) self.nls['Couple, no children'] = len(self.ls_levels['Couple, no children']) self.nls['Female and child'] = len(self.ls_levels['Female and child']) self.nls['Female, single'] = len(self.ls_levels['Female, single']) self.nls['Male, single'] = len(self.ls_levels['Male, single']) #Cost of Divorce if divorce_costs_k == 'Default': # by default the costs are set in the bottom self.divorce_costs_k = DivorceCosts(u_lost_m=self.pars['u_lost_divorce'], u_lost_f=self.pars['u_lost_divorce']) else: if isinstance(divorce_costs_k,dict): # you can feed in arguments to DivorceCosts self.divorce_costs_k = DivorceCosts(**divorce_costs_k) else: # or just the output of DivorceCosts assert isinstance(divorce_costs_k,DivorceCosts) self.divorce_costs_k = divorce_costs_k #Cost of Separation if divorce_costs_nk == 'Default': # by default the costs are set in the bottom self.divorce_costs_nk = DivorceCosts(u_lost_m=self.pars['u_lost_divorce'], u_lost_f=self.pars['u_lost_divorce']) else: if isinstance(divorce_costs_nk,dict): # you can feed in arguments to DivorceCosts self.divorce_costs_nk = DivorceCosts(**divorce_costs_nk) else: # or just the output of DivorceCosts assert isinstance(divorce_costs_nk,DivorceCosts) self.divorce_costs_nk = divorce_costs_nk # exogrid should be deprecated if not nogrid: exogrid = dict() # let's approximate three Markov chains # this sets up exogenous grid # FIXME: this uses number of points from 0th entry. # in principle we can generalize this exogrid['zf_t'], exogrid['zf_t_mat'] = rouw_nonst(p['T'],p['sig_zf'],p['sig_zf_0'],p['n_zf_t'][0]) exogrid['zm_t'], exogrid['zm_t_mat'] = rouw_nonst(p['T'],p['sig_zm'],p['sig_zm_0'],p['n_zm_t'][0]) for t in range(Tinc,Tret): for key in ['zf_t','zf_t_mat','zm_t','zm_t_mat']: exogrid[key][t] = exogrid[key][Tinc] for t in range(Tret,T): exogrid['zf_t'][t] = np.array([np.log(p['wret'])]) exogrid['zm_t'][t] = np.array([np.log(p['wret'])]) exogrid['zf_t_mat'][t] = np.atleast_2d(1.0) exogrid['zm_t_mat'][t] = np.atleast_2d(1.0) # fix transition from non-retired to retired exogrid['zf_t_mat'][Tret-1] = np.ones((p['n_zf_t'][Tret-1],1)) exogrid['zm_t_mat'][Tret-1] = np.ones((p['n_zm_t'][Tret-1],1)) exogrid['psi_t'], exogrid['psi_t_mat'] = rouw_nonst(p['T'],p['sigma_psi'],p['sigma_psi_init'],p['n_psi_t'][0]) #exogrid['psi_t'], exogrid['psi_t_mat'] = tauchen_nonst(p['T'],p['sigma_psi'],p['sigma_psi_init'],p['n_psi_t'][0],nsd=2.5,fix_0=False) #assert False zfzm, zfzmmat = combine_matrices_two_lists(exogrid['zf_t'], exogrid['zm_t'], exogrid['zf_t_mat'], exogrid['zm_t_mat']) all_t, all_t_mat = combine_matrices_two_lists(zfzm,exogrid['psi_t'],zfzmmat,exogrid['psi_t_mat']) all_t_mat_sparse_T = [sparse.csc_matrix(D.T) if D is not None else None for D in all_t_mat] #Create a new bad version of transition matrix p(zf_t) zf_bad = [tauchen_drift(exogrid['zf_t'][t], exogrid['zf_t'][t+1], 1.0, p['sig_zf'], p['z_drift']) for t in range(self.pars['T']-1) ] + [None] #zf_bad = [cut_matrix(exogrid['zf_t_mat'][t]) if t < Tret -1 # else (exogrid['zf_t_mat'][t] if t < T - 1 else None) # for t in range(self.pars['T'])] zf_t_mat_down = zf_bad zfzm, zfzmmat = combine_matrices_two_lists(exogrid['zf_t'], exogrid['zm_t'], zf_t_mat_down, exogrid['zm_t_mat']) all_t_down, all_t_mat_down = combine_matrices_two_lists(zfzm,exogrid['psi_t'],zfzmmat,exogrid['psi_t_mat']) all_t_mat_down_sparse_T = [sparse.csc_matrix(D.T) if D is not None else None for D in all_t_mat_down] all_t_mat_by_l_nk = [ [(1-p)*m + p*md if m is not None else None for m , md in zip(all_t_mat,all_t_mat_down)] for p in self.ls_pdown['Couple, no children'] ] all_t_mat_by_l_spt_nk = [ [(1-p)*m + p*md if m is not None else None for m, md in zip(all_t_mat_sparse_T,all_t_mat_down_sparse_T)] for p in self.ls_pdown['Couple, no children'] ] all_t_mat_by_l_k = [ [(1-p)*m + p*md if m is not None else None for m , md in zip(all_t_mat,all_t_mat_down)] for p in self.ls_pdown['Couple and child'] ] all_t_mat_by_l_spt_k = [ [(1-p)*m + p*md if m is not None else None for m, md in zip(all_t_mat_sparse_T,all_t_mat_down_sparse_T)] for p in self.ls_pdown['Couple and child'] ] zf_t_mat_by_l_sk = [ [(1-p)*m + p*md if md is not None else None for m , md in zip(exogrid['zf_t_mat'],zf_bad)] for p in self.ls_pdown['Female and child'] ] exogrid['all_t_mat_by_l_nk'] = all_t_mat_by_l_nk exogrid['all_t_mat_by_l_spt_nk'] = all_t_mat_by_l_spt_nk exogrid['all_t_mat_by_l_k'] = all_t_mat_by_l_k exogrid['all_t_mat_by_l_spt_k'] = all_t_mat_by_l_spt_k exogrid['zf_t_mat_by_l_sk'] = zf_t_mat_by_l_sk exogrid['all_t'] = all_t Exogrid_nt = namedtuple('Exogrid_nt',exogrid.keys()) self.exogrid = Exogrid_nt(**exogrid) self.pars['nexo_t'] = [v.shape[0] for v in all_t] self.compute_child_support_transitions(child_support_share=p['child_support_share']) #assert False #Grid Couple self.na = 40 self.amin = 0 self.amax = 1000.0 self.agrid_c = np.linspace(self.amin**0.5,self.amax**0.5,self.na,dtype=self.dtype)**2 #tune=1.5 #self.agrid_c = np.geomspace(self.amin+tune,self.amax+tune,num=self.na)-tune # this builds finer grid for potential savings s_between = 7 # default numer of points between poitns on agrid s_da_min = 0.2 # minimal step (does not create more points) s_da_max = 10.0 # maximal step (creates more if not enough) self.sgrid_c = build_s_grid(self.agrid_c,s_between,s_da_min,s_da_max) self.vsgrid_c = VecOnGrid(self.agrid_c,self.sgrid_c) #Grid Single self.amin_s = 0 self.amax_s = self.amax/2.0 self.agrid_s = self.agrid_c/2.0 #tune_s=1.5 #self.agrid_s = np.geomspace(self.amin_s+tune_s,self.amax_s+tune_s,num=self.na)-tune_s self.sgrid_s = build_s_grid(self.agrid_s,s_between,s_da_min,s_da_max) self.vsgrid_s = VecOnGrid(self.agrid_s,self.sgrid_s) # grid for theta self.ntheta = 11 self.thetamin = 0.05 self.thetamax = 0.95 self.thetagrid = np.linspace(self.thetamin,self.thetamax,self.ntheta,dtype=self.dtype) self.child_a_cost_single = np.minimum(self.agrid_s,self.pars['child_a_cost']) self.child_a_cost_couple = np.minimum(self.agrid_c,self.pars['child_a_cost']) assert self.pars['child_a_cost']<1e-3, 'not implemented' #self.vagrid_child_single = VecOnGrid(self.agrid_s, self.agrid_s - self.child_a_cost_single) #self.vagrid_child_couple = VecOnGrid(self.agrid_c, self.agrid_c - self.child_a_cost_couple) # construct finer grid for bargaining ntheta_fine = 10*self.ntheta # actual number may be a bit bigger self.thetagrid_fine = np.unique(np.concatenate( (self.thetagrid,np.linspace(self.thetamin,self.thetamax,ntheta_fine,dtype=self.dtype)) )) self.ntheta_fine = self.thetagrid_fine.size i_orig = list() for theta in self.thetagrid: assert theta in self.thetagrid_fine i_orig.append(np.where(self.thetagrid_fine==theta)[0]) assert len(i_orig) == self.thetagrid.size # allows to recover original gird points on the fine grid self.theta_orig_on_fine = np.array(i_orig).flatten() self.v_thetagrid_fine = VecOnGrid(self.thetagrid,self.thetagrid_fine) # precomputed object for interpolation self.exo_grids = {'Female, single':exogrid['zf_t'], 'Male, single':exogrid['zm_t'], 'Female and child':exogrid['zf_t'], 'Couple and child':exogrid['all_t'], 'Couple, no children':exogrid['all_t']} self.exo_mats = {'Female, single':exogrid['zf_t_mat'], 'Male, single':exogrid['zm_t_mat'], 'Female and child':exogrid['zf_t_mat_by_l_sk'], 'Couple and child':exogrid['all_t_mat_by_l_k'], 'Couple, no children':exogrid['all_t_mat_by_l_nk']} # sparse version? self.utility_shifters = {'Female, single':0.0, 'Male, single':0.0, 'Female and child':p['u_shift_mar'] + p['sm_shift'], 'Couple and child':p['u_shift_mar'], 'Couple, no children':p['u_shift_coh']} # this pre-computes transition matrices for meeting a partner name_fem_pkl = 'az_dist_fem.pkl' if p['high education'] else 'az_dist_fem_noc.pkl' name_mal_pkl = 'az_dist_mal.pkl' if p['high education'] else 'az_dist_mal_noc.pkl' name_fem_csv = 'income_assets_distribution_male_col.csv' if p['high education'] else 'income_assets_distribution_male_hs.csv' name_mal_csv = 'income_assets_distribution_female_col.csv' if p['high education'] else 'income_assets_distribution_female_hs.csv' # this is not an error, things are switched try: self.partners_distribution_fem = filer(name_fem_pkl,0,0,repeat=False) self.partners_distribution_mal = filer(name_mal_pkl,0,0,repeat=False) except: print('recreating estimates...') est_fem = get_estimates(fname=name_fem_csv, age_start=23,age_stop=42, zlist=self.exogrid.zm_t[2:], female=False,college=p['high education']) filer(name_fem_pkl,est_fem,True,repeat=False) self.partners_distribution_fem = est_fem est_mal = get_estimates(fname=name_mal_csv, age_start=21,age_stop=40, zlist=self.exogrid.zf_t[0:], female=True,college=p['high education']) filer(name_mal_pkl,est_mal,True,repeat=False) self.partners_distribution_mal = est_mal self.build_matches() # building m grid ezfmin = min([np.min(np.exp(g+t)) for g,t in zip(exogrid['zf_t'],p['f_wage_trend'])]) ezmmin = min([np.min(np.exp(g+t)) for g,t in zip(exogrid['zm_t'],p['m_wage_trend'])]) ezfmax = max([np.max(np.exp(g+t)) for g,t in zip(exogrid['zf_t'],p['f_wage_trend'])]) ezmmax = max([np.max(np.exp(g+t)) for g,t in zip(exogrid['zm_t'],p['m_wage_trend'])]) self.money_min = 0.95*min(self.ls_levels['Female and child'])*min(ezmmin,ezfmin) # cause FLS can be up to 0 mmin = self.money_min mmax = ezfmax + ezmmax + np.max(self.pars['R_t'])*self.amax mint = (ezfmax + ezmmax) # poin where more dense grid begins ndense = 1900 nm = 3500 gsparse = np.linspace(mint,mmax,nm-ndense) gdense = np.linspace(mmin,mint,ndense+1) # +1 as there is a common pt self.mgrid = np.zeros(nm,dtype=self.dtype) self.mgrid[ndense:] = gsparse self.mgrid[:(ndense+1)] = gdense self.mgrid_c = self.mgrid self.mgrid_s = self.mgrid assert np.all(np.diff(self.mgrid)>0) self.u_precompute() self.unplanned_pregnancy_probability() self.compute_taxes() self.cupyfy()
def anext(self, t): # finds savings (potenitally off-grid) for ipol in range(self.npol): for ist, sname in enumerate(self.state_codes): is_state_any_pol = (self.state[:, t] == ist) is_pol = (self.policy_ind[:, t] == ipol) is_state = (is_state_any_pol) & (is_pol) use_theta = self.has_theta[ist] nst = np.sum(is_state) if nst == 0: continue ind = np.where(is_state)[0] #pol = self.Mlist[ipol].decisions[t][sname] vpol = self.Mlist[ipol].V[t][sname] if not use_theta: # apply for singles anext = vpol['s'][self.iassets[ind, t], self.iexo[ind, t]] self.iassets[ind, t + 1] = VecOnGrid( self.setup.agrid_s, anext).roll(shocks=self._shocks_single_a[ind, t]) self.s[ind, t] = anext self.c[ind, t] = vpol['c'][self.iassets[ind, t], self.iexo[ind, t]] self.x[ind, t] = vpol['x'][self.iassets[ind, t], self.iexo[ind, t]] if np.any(self.iassets[ind, t + 1] == self.setup.na - 1): self.ub_hit_single = True else: # interpolate in both assets and theta # function apply_2dim is experimental but I checked it at this setup # apply for couples def tint(x): return self.tht_interpolate( x, (self.iassets[ind, t], self.iexo[ind, t]), self.itheta[ind, t]) anext = tint(vpol['s']) self.s[ind, t] = anext self.x[ind, t] = tint(vpol['x']) self.c[ind, t] = tint(vpol['c']) self.iassets[ind, t + 1] = VecOnGrid( self.setup.agrid_c, anext).roll(shocks=self._shocks_couple_a[ind, t]) if np.any(self.iassets[ind, t + 1] == self.setup.na - 1): self.couple = True assert np.all(anext >= 0)