Coverage for HARK / ConsumptionSaving / ConsAggShockModel.py: 99%

897 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 06:00 +0000

1""" 

2Consumption-saving models with aggregate productivity shocks as well as idiosyn- 

3cratic income shocks. Currently only contains one microeconomic model with a 

4basic solver. Also includes a subclass of Market called CobbDouglas economy, 

5used for solving "macroeconomic" models with aggregate shocks. 

6""" 

7 

8from copy import deepcopy 

9 

10import numpy as np 

11import scipy.stats as stats 

12 

13from HARK import AgentType, Market 

14from HARK.ConsumptionSaving.ConsAggIndMarkovModel import ( 

15 AggIndMarkovConsumerType, 

16) 

17from HARK.Calibration.Income.IncomeProcesses import ( 

18 construct_lognormal_income_process_unemployment, 

19 construct_markov_lognormal_income_process_unemployment, 

20 get_PermShkDstn_from_IncShkDstn, 

21 get_TranShkDstn_from_IncShkDstn, 

22 combine_ind_and_agg_income_shocks, 

23 combine_markov_ind_and_agg_income_shocks, 

24 get_PermShkDstn_from_IncShkDstn_markov, 

25 get_TranShkDstn_from_IncShkDstn_markov, 

26) 

27from HARK.ConsumptionSaving.ConsIndShockModel import ( 

28 ConsumerSolution, 

29 IndShockConsumerType, 

30 make_lognormal_kNrm_init_dstn, 

31 make_lognormal_pLvl_init_dstn, 

32) 

33from HARK.distributions import ( 

34 MarkovProcess, 

35 MeanOneLogNormal, 

36 Uniform, 

37 expected, 

38 combine_indep_dstns, 

39) 

40from HARK.interpolation import ( 

41 BilinearInterp, 

42 ConstantFunction, 

43 IdentityFunction, 

44 LinearInterp, 

45 LinearInterpOnInterp1D, 

46 LowerEnvelope2D, 

47 MargValueFuncCRRA, 

48 UpperEnvelope, 

49 VariableLowerBoundFunc2D, 

50) 

51from HARK.metric import MetricObject 

52from HARK.rewards import ( 

53 CRRAutility, 

54 CRRAutility_inv, 

55 CRRAutility_invP, 

56 CRRAutilityP, 

57 CRRAutilityP_inv, 

58 CRRAutilityPP, 

59) 

60from HARK.utilities import make_assets_grid, get_it_from, NullFunc 

61 

62__all__ = [ 

63 "AggShockConsumerType", 

64 "AggShockMarkovConsumerType", 

65 "CobbDouglasEconomy", 

66 "SmallOpenEconomy", 

67 "CobbDouglasMarkovEconomy", 

68 "SmallOpenMarkovEconomy", 

69 "AggregateSavingRule", 

70 "AggShocksDynamicRule", 

71 "init_agg_shocks", 

72 "init_agg_mrkv_shocks", 

73 "init_cobb_douglas", 

74 "init_mrkv_cobb_douglas", 

75] 

76 

77utility = CRRAutility 

78utilityP = CRRAutilityP 

79utilityPP = CRRAutilityPP 

80utilityP_inv = CRRAutilityP_inv 

81utility_invP = CRRAutility_invP 

82utility_inv = CRRAutility_inv 

83 

84 

85def make_aggshock_solution_terminal(CRRA): 

86 """ 

87 Creates the terminal period solution for an aggregate shock consumer. 

88 Only fills in the consumption function and marginal value function. 

89 

90 Parameters 

91 ---------- 

92 CRRA : float 

93 Coefficient of relative risk aversion. 

94 

95 Returns 

96 ------- 

97 solution_terminal : ConsumerSolution 

98 Solution to the terminal period problem. 

99 """ 

100 cFunc_terminal = IdentityFunction(i_dim=0, n_dims=2) 

101 vPfunc_terminal = MargValueFuncCRRA(cFunc_terminal, CRRA) 

102 mNrmMin_terminal = ConstantFunction(0) 

103 solution_terminal = ConsumerSolution( 

104 cFunc=cFunc_terminal, vPfunc=vPfunc_terminal, mNrmMin=mNrmMin_terminal 

105 ) 

106 return solution_terminal 

107 

108 

109def make_aggmrkv_solution_terminal(CRRA, MrkvArray): 

110 """ 

111 Creates the terminal period solution for an aggregate shock consumer with 

112 discrete Markov state. Only fills in the consumption function and marginal 

113 value function. 

114 

115 Parameters 

116 ---------- 

117 CRRA : float 

118 Coefficient of relative risk aversion. 

119 MrkvArray : np.array 

120 Transition probability array. 

121 

122 Returns 

123 ------- 

124 solution_terminal : ConsumerSolution 

125 Solution to the terminal period problem. 

126 """ 

127 solution_terminal = make_aggshock_solution_terminal(CRRA) 

128 

129 # Make replicated terminal period solution 

130 StateCount = MrkvArray.shape[0] 

131 solution_terminal.cFunc = StateCount * [solution_terminal.cFunc] 

132 solution_terminal.vPfunc = StateCount * [solution_terminal.vPfunc] 

133 solution_terminal.mNrmMin = StateCount * [solution_terminal.mNrmMin] 

134 

135 return solution_terminal 

136 

137 

138def make_exponential_MgridBase(MaggCount, MaggPerturb, MaggExpFac): 

139 """ 

140 Constructor function for MgridBase, the grid of aggregate market resources 

141 relative to the steady state. This grid is always centered around 1.0. 

142 

143 Parameters 

144 ---------- 

145 MaggCount : int 

146 Number of gridpoints for aggregate market resources. Should be odd. 

147 MaggPerturb : float 

148 Small perturbation around the steady state; the grid will always include 

149 1+perturb and 1-perturb. 

150 MaggExpFac : float 

151 Log growth factor for gridpoints beyond the two adjacent to the steady state. 

152 

153 Returns 

154 ------- 

155 MgridBase : np.array 

156 Grid of aggregate market resources relative to the steady state. 

157 """ 

158 N = int((MaggCount - 1) / 2) 

159 gridpoints = [1.0 - MaggPerturb, 1.0, 1.0 + MaggPerturb] 

160 fac = np.exp(MaggExpFac) 

161 for n in range(N - 1): 

162 new_hi = gridpoints[-1] * fac 

163 new_lo = gridpoints[0] / fac 

164 gridpoints.append(new_hi) 

165 gridpoints.insert(0, new_lo) 

166 MgridBase = np.array(gridpoints) 

167 return MgridBase 

168 

169 

170def make_Mgrid(MgridBase, MSS): 

171 """ 

172 Make the grid of aggregate market resources as the steady state level times the base grid. 

173 """ 

174 return MSS * MgridBase 

175 

176 

177############################################################################### 

178 

179 

180def solveConsAggShock( 

181 solution_next, 

182 IncShkDstn, 

183 LivPrb, 

184 DiscFac, 

185 CRRA, 

186 PermGroFac, 

187 PermGroFacAgg, 

188 aXtraGrid, 

189 BoroCnstArt, 

190 Mgrid, 

191 AFunc, 

192 Rfunc, 

193 wFunc, 

194): 

195 """ 

196 Solve one period of a consumption-saving problem with idiosyncratic and 

197 aggregate shocks (transitory and permanent). This is a basic solver that 

198 can't handle cubic splines, nor can it calculate a value function. This 

199 version uses calc_expectation to reduce code clutter. 

200 

201 Parameters 

202 ---------- 

203 solution_next : ConsumerSolution 

204 The solution to the succeeding one period problem. 

205 IncShkDstn : distribution.Distribution 

206 A discrete 

207 approximation to the income process between the period being solved 

208 and the one immediately following (in solution_next). Order: 

209 idiosyncratic permanent shocks, idiosyncratic transitory 

210 shocks, aggregate permanent shocks, aggregate transitory shocks. 

211 LivPrb : float 

212 Survival probability; likelihood of being alive at the beginning of 

213 the succeeding period. 

214 DiscFac : float 

215 Intertemporal discount factor for future utility. 

216 CRRA : float 

217 Coefficient of relative risk aversion. 

218 PermGroFac : float 

219 Expected permanent income growth factor at the end of this period. 

220 PermGroFacAgg : float 

221 Expected aggregate productivity growth factor. 

222 aXtraGrid : np.array 

223 Array of "extra" end-of-period asset values-- assets above the 

224 absolute minimum acceptable level. 

225 BoroCnstArt : float 

226 Artificial borrowing constraint; minimum allowable end-of-period asset-to- 

227 permanent-income ratio. Unlike other models, this *can't* be None. 

228 Mgrid : np.array 

229 A grid of aggregate market resourses to permanent income in the economy. 

230 AFunc : function 

231 Aggregate savings as a function of aggregate market resources. 

232 Rfunc : function 

233 The net interest factor on assets as a function of capital ratio k. 

234 wFunc : function 

235 The wage rate for labor as a function of capital-to-labor ratio k. 

236 

237 Returns 

238 ------- 

239 solution_now : ConsumerSolution 

240 The solution to the single period consumption-saving problem. Includes 

241 a consumption function cFunc (linear interpolation over linear interpola- 

242 tions) and marginal value function vPfunc. 

243 """ 

244 # Unpack the income shocks and get grid sizes 

245 PermShkValsNext = IncShkDstn.atoms[0] 

246 TranShkValsNext = IncShkDstn.atoms[1] 

247 PermShkAggValsNext = IncShkDstn.atoms[2] 

248 TranShkAggValsNext = IncShkDstn.atoms[3] 

249 aCount = aXtraGrid.size 

250 Mcount = Mgrid.size 

251 

252 # Define a function that calculates M_{t+1} from M_t and the aggregate shocks; 

253 # the function also returns the wage rate and effective interest factor 

254 def calcAggObjects(M, Psi, Theta): 

255 A = AFunc(M) # End-of-period aggregate assets (normalized) 

256 # Next period's aggregate capital/labor ratio 

257 kNext = A / (PermGroFacAgg * Psi) 

258 kNextEff = kNext / Theta # Same thing, but account for *transitory* shock 

259 R = Rfunc(kNextEff) # Interest factor on aggregate assets 

260 wEff = ( 

261 wFunc(kNextEff) * Theta 

262 ) # Effective wage rate (accounts for labor supply) 

263 Reff = R / LivPrb # Account for redistribution of decedents' wealth 

264 Mnext = kNext * R + wEff # Next period's aggregate market resources 

265 return Mnext, Reff, wEff 

266 

267 # Define a function that evaluates R*v'(m_{t+1},M_{t+1}) from a_t, M_t, and the income shocks 

268 def vPnextFunc(S, a, M): 

269 psi = S[0] 

270 theta = S[1] 

271 Psi = S[2] 

272 Theta = S[3] 

273 

274 Mnext, Reff, wEff = calcAggObjects(M, Psi, Theta) 

275 PermShkTotal = ( 

276 PermGroFac * PermGroFacAgg * psi * Psi 

277 ) # Total / combined permanent shock 

278 mNext = Reff * a / PermShkTotal + theta * wEff # Idiosyncratic market resources 

279 vPnext = Reff * PermShkTotal ** (-CRRA) * solution_next.vPfunc(mNext, Mnext) 

280 return vPnext 

281 

282 # Make an array of a_t values at which to calculate end-of-period marginal value of assets 

283 # Natural borrowing constraint at each M_t 

284 BoroCnstNat_vec = np.zeros(Mcount) 

285 aNrmNow = np.zeros((aCount, Mcount)) 

286 for j in range(Mcount): 

287 Mnext, Reff, wEff = calcAggObjects( 

288 Mgrid[j], PermShkAggValsNext, TranShkAggValsNext 

289 ) 

290 aNrmMin_cand = ( 

291 PermGroFac * PermGroFacAgg * PermShkValsNext * PermShkAggValsNext / Reff 

292 ) * (solution_next.mNrmMin(Mnext) - wEff * TranShkValsNext) 

293 aNrmMin = np.max(aNrmMin_cand) # Lowest valid a_t value for this M_t 

294 aNrmNow[:, j] = aNrmMin + aXtraGrid 

295 BoroCnstNat_vec[j] = aNrmMin 

296 

297 # Compute end-of-period marginal value of assets 

298 MaggNow = np.tile(np.reshape(Mgrid, (1, Mcount)), (aCount, 1)) # Tiled Mgrid 

299 EndOfPrdvP = DiscFac * LivPrb * expected(vPnextFunc, IncShkDstn, (aNrmNow, MaggNow)) 

300 

301 # Calculate optimal consumption from each asset gridpoint and endogenous m_t gridpoint 

302 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA) 

303 mNrmNow = aNrmNow + cNrmNow 

304 

305 # Loop through the values in Mgrid and make a linear consumption function for each 

306 cFuncBaseByM_list = [] 

307 for j in range(Mcount): 

308 c_temp = np.insert(cNrmNow[:, j], 0, 0.0) # Add point at bottom 

309 m_temp = np.insert(mNrmNow[:, j] - BoroCnstNat_vec[j], 0, 0.0) 

310 cFuncBaseByM_list.append(LinearInterp(m_temp, c_temp)) 

311 

312 # Construct the overall unconstrained consumption function by combining the M-specific functions 

313 BoroCnstNat = LinearInterp( 

314 np.insert(Mgrid, 0, 0.0), np.insert(BoroCnstNat_vec, 0, 0.0) 

315 ) 

316 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

317 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

318 

319 # Make the constrained consumption function and combine it with the unconstrained component 

320 cFuncCnst = BilinearInterp( 

321 np.array([[0.0, 0.0], [1.0, 1.0]]), 

322 np.array([BoroCnstArt, BoroCnstArt + 1.0]), 

323 np.array([0.0, 1.0]), 

324 ) 

325 cFuncNow = LowerEnvelope2D(cFuncUnc, cFuncCnst) 

326 

327 # Make the minimum m function as the greater of the natural and artificial constraints 

328 mNrmMinNow = UpperEnvelope(BoroCnstNat, ConstantFunction(BoroCnstArt)) 

329 

330 # Construct the marginal value function using the envelope condition 

331 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

332 

333 # Pack up and return the solution 

334 solution_now = ConsumerSolution( 

335 cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=mNrmMinNow 

336 ) 

337 return solution_now 

338 

339 

340############################################################################### 

341 

342 

343def solve_ConsAggMarkov( 

344 solution_next, 

345 IncShkDstn, 

346 LivPrb, 

347 DiscFac, 

348 CRRA, 

349 MrkvArray, 

350 PermGroFac, 

351 PermGroFacAgg, 

352 aXtraGrid, 

353 BoroCnstArt, 

354 Mgrid, 

355 AFunc, 

356 Rfunc, 

357 wFunc, 

358): 

359 """ 

360 Solve one period of a consumption-saving problem with idiosyncratic and 

361 aggregate shocks (transitory and permanent). Moreover, the macroeconomic 

362 state follows a Markov process that determines the income distribution and 

363 aggregate permanent growth factor. This is a basic solver that can't handle 

364 cubic splines, nor can it calculate a value function. 

365 

366 Parameters 

367 ---------- 

368 solution_next : ConsumerSolution 

369 The solution to the succeeding one period problem. 

370 IncShkDstn : [distribution.Distribution] 

371 A list of 

372 discrete approximations to the income process between the period being 

373 solved and the one immediately following (in solution_next). Order: 

374 idisyncratic permanent shocks, idiosyncratic transitory 

375 shocks, aggregate permanent shocks, aggregate transitory shocks. 

376 LivPrb : float 

377 Survival probability; likelihood of being alive at the beginning of 

378 the succeeding period. 

379 DiscFac : float 

380 Intertemporal discount factor for future utility. 

381 CRRA : float 

382 Coefficient of relative risk aversion. 

383 MrkvArray : np.array 

384 Markov transition matrix between discrete macroeconomic states. 

385 MrkvArray[i,j] is probability of being in state j next period conditional 

386 on being in state i this period. 

387 PermGroFac : float 

388 Expected permanent income growth factor at the end of this period, 

389 for the *individual*'s productivity. 

390 PermGroFacAgg : [float] 

391 Expected aggregate productivity growth in each Markov macro state. 

392 aXtraGrid : np.array 

393 Array of "extra" end-of-period asset values-- assets above the 

394 absolute minimum acceptable level. 

395 BoroCnstArt : float 

396 Artificial borrowing constraint; minimum allowable end-of-period asset-to- 

397 permanent-income ratio. Unlike other models, this *can't* be None. 

398 Mgrid : np.array 

399 A grid of aggregate market resourses to permanent income in the economy. 

400 AFunc : [function] 

401 Aggregate savings as a function of aggregate market resources, for each 

402 Markov macro state. 

403 Rfunc : function 

404 The net interest factor on assets as a function of capital ratio k. 

405 wFunc : function 

406 The wage rate for labor as a function of capital-to-labor ratio k. 

407 

408 Returns 

409 ------- 

410 solution_now : ConsumerSolution 

411 The solution to the single period consumption-saving problem. Includes 

412 a consumption function cFunc (linear interpolation over linear interpola- 

413 tions) and marginal value function vPfunc. 

414 """ 

415 # Get sizes of grids 

416 aCount = aXtraGrid.size 

417 Mcount = Mgrid.size 

418 StateCount = MrkvArray.shape[0] 

419 

420 # Loop through next period's states, assuming we reach each one at a time. 

421 # Construct EndOfPrdvP_cond functions for each state. 

422 EndOfPrdvPfunc_cond = [] 

423 BoroCnstNat_cond = [] 

424 for j in range(StateCount): 

425 # Unpack next period's solution 

426 vPfuncNext = solution_next.vPfunc[j] 

427 mNrmMinNext = solution_next.mNrmMin[j] 

428 

429 # Unpack the income shocks 

430 ShkPrbsNext = IncShkDstn[j].pmv 

431 PermShkValsNext = IncShkDstn[j].atoms[0] 

432 TranShkValsNext = IncShkDstn[j].atoms[1] 

433 PermShkAggValsNext = IncShkDstn[j].atoms[2] 

434 TranShkAggValsNext = IncShkDstn[j].atoms[3] 

435 ShkCount = ShkPrbsNext.size 

436 aXtra_tiled = np.tile( 

437 np.reshape(aXtraGrid, (1, aCount, 1)), (Mcount, 1, ShkCount) 

438 ) 

439 

440 # Make tiled versions of the income shocks 

441 # Dimension order: Mnow, aNow, Shk 

442 ShkPrbsNext_tiled = np.tile( 

443 np.reshape(ShkPrbsNext, (1, 1, ShkCount)), (Mcount, aCount, 1) 

444 ) 

445 PermShkValsNext_tiled = np.tile( 

446 np.reshape(PermShkValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1) 

447 ) 

448 TranShkValsNext_tiled = np.tile( 

449 np.reshape(TranShkValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1) 

450 ) 

451 PermShkAggValsNext_tiled = np.tile( 

452 np.reshape(PermShkAggValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1) 

453 ) 

454 TranShkAggValsNext_tiled = np.tile( 

455 np.reshape(TranShkAggValsNext, (1, 1, ShkCount)), (Mcount, aCount, 1) 

456 ) 

457 

458 # Make a tiled grid of end-of-period aggregate assets. These lines use 

459 # next prd state j's aggregate saving rule to get a relevant set of Aagg, 

460 # which will be used to make an interpolated EndOfPrdvP_cond function. 

461 # After constructing these functions, we will use the aggregate saving 

462 # rule for *current* state i to get values of Aagg at which to evaluate 

463 # these conditional marginal value functions. In the strange, maybe even 

464 # impossible case where the aggregate saving rules differ wildly across 

465 # macro states *and* there is "anti-persistence", so that the macro state 

466 # is very likely to change each period, then this procedure will lead to 

467 # an inaccurate solution because the grid of Aagg values on which the 

468 # conditional marginal value functions are constructed is not relevant 

469 # to the values at which it will actually be evaluated. 

470 AaggGrid = AFunc[j](Mgrid) 

471 AaggNow_tiled = np.tile( 

472 np.reshape(AaggGrid, (Mcount, 1, 1)), (1, aCount, ShkCount) 

473 ) 

474 

475 # Calculate returns to capital and labor in the next period 

476 kNext_array = AaggNow_tiled / ( 

477 PermGroFacAgg[j] * PermShkAggValsNext_tiled 

478 ) # Next period's aggregate capital to labor ratio 

479 kNextEff_array = ( 

480 kNext_array / TranShkAggValsNext_tiled 

481 ) # Same thing, but account for *transitory* shock 

482 R_array = Rfunc(kNextEff_array) # Interest factor on aggregate assets 

483 Reff_array = ( 

484 R_array / LivPrb 

485 ) # Effective interest factor on individual assets *for survivors* 

486 wEff_array = ( 

487 wFunc(kNextEff_array) * TranShkAggValsNext_tiled 

488 ) # Effective wage rate (accounts for labor supply) 

489 PermShkTotal_array = ( 

490 PermGroFac 

491 * PermGroFacAgg[j] 

492 * PermShkValsNext_tiled 

493 * PermShkAggValsNext_tiled 

494 ) # total / combined permanent shock 

495 Mnext_array = ( 

496 kNext_array * R_array + wEff_array 

497 ) # next period's aggregate market resources 

498 

499 # Find the natural borrowing constraint for each value of M in the Mgrid. 

500 # There is likely a faster way to do this, but someone needs to do the math: 

501 # is aNrmMin determined by getting the worst shock of all four types? 

502 aNrmMin_candidates = ( 

503 PermGroFac 

504 * PermGroFacAgg[j] 

505 * PermShkValsNext_tiled[:, 0, :] 

506 * PermShkAggValsNext_tiled[:, 0, :] 

507 / Reff_array[:, 0, :] 

508 * ( 

509 mNrmMinNext(Mnext_array[:, 0, :]) 

510 - wEff_array[:, 0, :] * TranShkValsNext_tiled[:, 0, :] 

511 ) 

512 ) 

513 aNrmMin_vec = np.max(aNrmMin_candidates, axis=1) 

514 BoroCnstNat_vec = aNrmMin_vec 

515 aNrmMin_tiled = np.tile( 

516 np.reshape(aNrmMin_vec, (Mcount, 1, 1)), (1, aCount, ShkCount) 

517 ) 

518 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

519 

520 # Calculate market resources next period (and a constant array of capital-to-labor ratio) 

521 mNrmNext_array = ( 

522 Reff_array * aNrmNow_tiled / PermShkTotal_array 

523 + TranShkValsNext_tiled * wEff_array 

524 ) 

525 

526 # Find marginal value next period at every income shock 

527 # realization and every aggregate market resource gridpoint 

528 vPnext_array = ( 

529 Reff_array 

530 * PermShkTotal_array ** (-CRRA) 

531 * vPfuncNext(mNrmNext_array, Mnext_array) 

532 ) 

533 

534 # Calculate expectated marginal value at the end of the period at every asset gridpoint 

535 EndOfPrdvP = DiscFac * LivPrb * np.sum(vPnext_array * ShkPrbsNext_tiled, axis=2) 

536 

537 # Make the conditional end-of-period marginal value function 

538 BoroCnstNat = LinearInterp( 

539 np.insert(AaggGrid, 0, 0.0), np.insert(BoroCnstNat_vec, 0, 0.0) 

540 ) 

541 EndOfPrdvPnvrs = np.concatenate( 

542 (np.zeros((Mcount, 1)), EndOfPrdvP ** (-1.0 / CRRA)), axis=1 

543 ) 

544 EndOfPrdvPnvrsFunc_base = BilinearInterp( 

545 np.transpose(EndOfPrdvPnvrs), np.insert(aXtraGrid, 0, 0.0), AaggGrid 

546 ) 

547 EndOfPrdvPnvrsFunc = VariableLowerBoundFunc2D( 

548 EndOfPrdvPnvrsFunc_base, BoroCnstNat 

549 ) 

550 EndOfPrdvPfunc_cond.append(MargValueFuncCRRA(EndOfPrdvPnvrsFunc, CRRA)) 

551 BoroCnstNat_cond.append(BoroCnstNat) 

552 

553 # Prepare some objects that are the same across all current states 

554 aXtra_tiled = np.tile(np.reshape(aXtraGrid, (1, aCount)), (Mcount, 1)) 

555 cFuncCnst = BilinearInterp( 

556 np.array([[0.0, 0.0], [1.0, 1.0]]), 

557 np.array([BoroCnstArt, BoroCnstArt + 1.0]), 

558 np.array([0.0, 1.0]), 

559 ) 

560 

561 # Now loop through *this* period's discrete states, calculating end-of-period 

562 # marginal value (weighting across state transitions), then construct consumption 

563 # and marginal value function for each state. 

564 cFuncNow = [] 

565 vPfuncNow = [] 

566 mNrmMinNow = [] 

567 for i in range(StateCount): 

568 # Find natural borrowing constraint for this state by Aagg 

569 AaggNow = AFunc[i](Mgrid) 

570 aNrmMin_candidates = np.zeros((StateCount, Mcount)) + np.nan 

571 for j in range(StateCount): 

572 if MrkvArray[i, j] > 0.0: # Irrelevant if transition is impossible 

573 aNrmMin_candidates[j, :] = BoroCnstNat_cond[j](AaggNow) 

574 aNrmMin_vec = np.nanmax(aNrmMin_candidates, axis=0) 

575 BoroCnstNat_vec = aNrmMin_vec 

576 

577 # Make tiled grids of aNrm and Aagg 

578 aNrmMin_tiled = np.tile(np.reshape(aNrmMin_vec, (Mcount, 1)), (1, aCount)) 

579 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

580 AaggNow_tiled = np.tile(np.reshape(AaggNow, (Mcount, 1)), (1, aCount)) 

581 

582 # Loop through feasible transitions and calculate end-of-period marginal value 

583 EndOfPrdvP = np.zeros((Mcount, aCount)) 

584 for j in range(StateCount): 

585 if MrkvArray[i, j] > 0.0: 

586 temp = EndOfPrdvPfunc_cond[j](aNrmNow_tiled, AaggNow_tiled) 

587 EndOfPrdvP += MrkvArray[i, j] * temp 

588 

589 # Calculate consumption and the endogenous mNrm gridpoints for this state 

590 cNrmNow = EndOfPrdvP ** (-1.0 / CRRA) 

591 mNrmNow = aNrmNow_tiled + cNrmNow 

592 

593 # Loop through the values in Mgrid and make a piecewise linear consumption function for each 

594 cFuncBaseByM_list = [] 

595 for n in range(Mcount): 

596 c_temp = np.insert(cNrmNow[n, :], 0, 0.0) # Add point at bottom 

597 m_temp = np.insert(mNrmNow[n, :] - BoroCnstNat_vec[n], 0, 0.0) 

598 cFuncBaseByM_list.append(LinearInterp(m_temp, c_temp)) 

599 # Add the M-specific consumption function to the list 

600 

601 # Construct the unconstrained consumption function by combining the M-specific functions 

602 BoroCnstNat = LinearInterp( 

603 np.insert(Mgrid, 0, 0.0), np.insert(BoroCnstNat_vec, 0, 0.0) 

604 ) 

605 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

606 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

607 

608 # Combine the constrained consumption function with unconstrained component 

609 cFuncNow.append(LowerEnvelope2D(cFuncUnc, cFuncCnst)) 

610 

611 # Make the minimum m function as the greater of the natural and artificial constraints 

612 mNrmMinNow.append(UpperEnvelope(BoroCnstNat, ConstantFunction(BoroCnstArt))) 

613 

614 # Construct the marginal value function using the envelope condition 

615 vPfuncNow.append(MargValueFuncCRRA(cFuncNow[-1], CRRA)) 

616 

617 # Pack up and return the solution 

618 solution_now = ConsumerSolution( 

619 cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=mNrmMinNow 

620 ) 

621 return solution_now 

622 

623 

624############################################################################### 

625 

626 

627def solve_KrusellSmith( 

628 solution_next, 

629 DiscFac, 

630 CRRA, 

631 aGrid, 

632 Mgrid, 

633 mNextArray, 

634 MnextArray, 

635 ProbArray, 

636 RnextArray, 

637): 

638 """ 

639 Solve the one period problem of an agent in Krusell & Smith's canonical 1998 model. 

640 Because this model is so specialized and only intended to be used with a very narrow 

641 case, many arrays can be precomputed, making the code here very short. See the 

642 default constructor functions for details. 

643 

644 Parameters 

645 ---------- 

646 solution_next : ConsumerSolution 

647 Representation of the solution to next period's problem, including the 

648 discrete-state-conditional consumption function and marginal value function. 

649 DiscFac : float 

650 Intertemporal discount factor. 

651 CRRA : float 

652 Coefficient of relative risk aversion. 

653 aGrid : np.array 

654 Array of end-of-period asset values. 

655 Mgrid : np.array 

656 A grid of aggregate market resources in the economy. 

657 mNextArray : np.array 

658 Precomputed array of next period's market resources attained from every 

659 end-of-period state in the exogenous grid crossed with every shock that 

660 might attain. Has shape [aCount, Mcount, 4, 4] ~ [a, M, s, s']. 

661 MnextArray : np.array 

662 Precomputed array of next period's aggregate market resources attained 

663 from every end-of-period state in the exogenous grid crossed with every 

664 shock that might attain. Corresponds to mNextArray. 

665 ProbArray : np.array 

666 Tiled array of transition probabilities among discrete states. Every 

667 slice [i,j,:,:] is identical and translated from MrkvIndArray. 

668 RnextArray : np.array 

669 Tiled array of net interest factors next period, attained from every 

670 end-of-period state crossed with every shock that might attain. 

671 

672 Returns 

673 ------- 

674 solution_now : ConsumerSolution 

675 Representation of this period's solution to the Krusell-Smith model. 

676 """ 

677 # Loop over next period's state realizations, computing marginal value of market resources 

678 vPnext = np.zeros_like(mNextArray) 

679 for j in range(4): 

680 vPnext[:, :, :, j] = solution_next.vPfunc[j]( 

681 mNextArray[:, :, :, j], MnextArray[:, :, :, j] 

682 ) 

683 

684 # Compute end-of-period marginal value of assets 

685 EndOfPrdvP = DiscFac * np.sum(RnextArray * vPnext * ProbArray, axis=3) 

686 

687 # Invert the first order condition to find optimal consumption 

688 cNow = EndOfPrdvP ** (-1.0 / CRRA) 

689 

690 # Find the endogenous gridpoints 

691 aCount = aGrid.size 

692 Mcount = Mgrid.size 

693 aNow = np.tile(np.reshape(aGrid, [aCount, 1, 1]), [1, Mcount, 4]) 

694 mNow = aNow + cNow 

695 

696 # Insert zeros at the bottom of both cNow and mNow arrays (consume nothing) 

697 cNow = np.concatenate([np.zeros([1, Mcount, 4]), cNow], axis=0) 

698 mNow = np.concatenate([np.zeros([1, Mcount, 4]), mNow], axis=0) 

699 

700 # Construct the consumption and marginal value function for each discrete state 

701 cFunc_by_state = [] 

702 vPfunc_by_state = [] 

703 for j in range(4): 

704 cFunc_by_M = [LinearInterp(mNow[:, k, j], cNow[:, k, j]) for k in range(Mcount)] 

705 cFunc_j = LinearInterpOnInterp1D(cFunc_by_M, Mgrid) 

706 vPfunc_j = MargValueFuncCRRA(cFunc_j, CRRA) 

707 cFunc_by_state.append(cFunc_j) 

708 vPfunc_by_state.append(vPfunc_j) 

709 

710 # Package and return the solution 

711 solution_now = ConsumerSolution(cFunc=cFunc_by_state, vPfunc=vPfunc_by_state) 

712 return solution_now 

713 

714 

715############################################################################### 

716 

717# Make a dictionary of constructors for the aggregate income shocks model 

718aggshock_constructor_dict = { 

719 "IncShkDstnInd": construct_lognormal_income_process_unemployment, 

720 "IncShkDstn": combine_ind_and_agg_income_shocks, 

721 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

722 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

723 "aXtraGrid": make_assets_grid, 

724 "MgridBase": make_exponential_MgridBase, 

725 "Mgrid": make_Mgrid, 

726 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

727 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

728 "solution_terminal": make_aggshock_solution_terminal, 

729 "T_sim": get_it_from("act_T"), 

730} 

731 

732# Make a dictionary with parameters for the default constructor for kNrmInitDstn 

733default_kNrmInitDstn_params = { 

734 "kLogInitMean": 0.0, # Mean of log initial capital 

735 "kLogInitStd": 0.0, # Stdev of log initial capital 

736 "kNrmInitCount": 15, # Number of points in initial capital discretization 

737} 

738 

739# Make a dictionary with parameters for the default constructor for pLvlInitDstn 

740default_pLvlInitDstn_params = { 

741 "pLogInitMean": 0.0, # Mean of log permanent income 

742 "pLogInitStd": 0.0, # Stdev of log permanent income 

743 "pLvlInitCount": 15, # Number of points in initial capital discretization 

744} 

745 

746 

747# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

748default_IncShkDstn_params = { 

749 "PermShkStd": [0.1], # Standard deviation of log permanent income shocks 

750 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks 

751 "TranShkStd": [0.1], # Standard deviation of log transitory income shocks 

752 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks 

753 "UnempPrb": 0.05, # Probability of unemployment while working 

754 "IncUnemp": 0.3, # Unemployment benefits replacement rate while working 

755 "T_retire": 0, # Period of retirement (0 --> no retirement) 

756 "UnempPrbRet": 0.005, # Probability of "unemployment" while retired 

757 "IncUnempRet": 0.0, # "Unemployment" benefits when retired 

758} 

759 

760# Default parameters to make aXtraGrid using make_assets_grid 

761default_aXtraGrid_params = { 

762 "aXtraMin": 0.001, # Minimum end-of-period "assets above minimum" value 

763 "aXtraMax": 20, # Maximum end-of-period "assets above minimum" value 

764 "aXtraNestFac": 2, # Exponential nesting factor for aXtraGrid 

765 "aXtraCount": 36, # Number of points in the grid of "assets above minimum" 

766 "aXtraExtra": None, # Additional other values to add in grid (optional) 

767} 

768 

769# Default parameters to make MgridBase using make_exponential_MgridBase 

770default_MgridBase_params = { 

771 "MaggCount": 17, 

772 "MaggPerturb": 0.01, 

773 "MaggExpFac": 0.15, 

774} 

775 

776# Make a dictionary to specify an aggregate income shocks consumer type 

777init_agg_shocks = { 

778 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

779 "cycles": 1, # Finite, non-cyclic model 

780 "T_cycle": 1, # Number of periods in the cycle for this agent type 

781 "constructors": aggshock_constructor_dict, # See dictionary above 

782 "pseudo_terminal": False, # Terminal period is real 

783 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

784 "CRRA": 2.0, # Coefficient of relative risk aversion 

785 "DiscFac": 0.96, # Intertemporal discount factor 

786 "LivPrb": [0.98], # Survival probability after each period 

787 "PermGroFac": [1.00], # Permanent income growth factor 

788 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

789 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

790 "AgentCount": 10000, # Number of agents of this type 

791 "T_age": None, # Age after which simulated agents are automatically killed 

792 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor 

793 # (The portion of PermGroFac attributable to aggregate productivity growth) 

794 "NewbornTransShk": False, # Whether Newborns have transitory shock 

795 # ADDITIONAL OPTIONAL PARAMETERS 

796 "PerfMITShk": False, # Do Perfect Foresight MIT Shock 

797 # (Forces Newborns to follow solution path of the agent they replaced if True) 

798 "neutral_measure": False, # Whether to use permanent income neutral measure (see Harmenberg 2021) 

799} 

800init_agg_shocks.update(default_kNrmInitDstn_params) 

801init_agg_shocks.update(default_pLvlInitDstn_params) 

802init_agg_shocks.update(default_IncShkDstn_params) 

803init_agg_shocks.update(default_aXtraGrid_params) 

804init_agg_shocks.update(default_MgridBase_params) 

805 

806 

807class AggShockConsumerType(IndShockConsumerType): 

808 """ 

809 A class to represent consumers who face idiosyncratic (transitory and per- 

810 manent) shocks to their income and live in an economy that has aggregate 

811 (transitory and permanent) shocks to labor productivity. As the capital- 

812 to-labor ratio varies in the economy, so does the wage rate and interest 

813 rate. "Aggregate shock consumers" have beliefs about how the capital ratio 

814 evolves over time and take aggregate shocks into account when making their 

815 decision about how much to consume. 

816 

817 NB: Unlike most AgentType subclasses, AggShockConsumerType does not automatically 

818 call its construct method as part of instantiation. In most cases, an instance of 

819 this class cannot be meaningfully solved without being associated with a Market 

820 instance, probably of the subclass CobbDouglasEconomy or SmallOpenEconomy. 

821 

822 To be able to fully build and solve an instance of this class, assign it as an 

823 instance of the agents attribute of an appropriate Market, then run that Market's 

824 give_agent_params method. This will distribute market-level parameters and 

825 instruct the agents to run their construct method. You should then be able to 

826 run the solve method on the Market or its agents. 

827 """ 

828 

829 default_ = { 

830 "params": init_agg_shocks, 

831 "solver": solveConsAggShock, 

832 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"], 

833 } 

834 time_inv_ = IndShockConsumerType.time_inv_.copy() 

835 time_inv_ += ["Mgrid", "AFunc", "Rfunc", "wFunc", "PermGroFacAgg"] 

836 try: 

837 time_inv_.remove("vFuncBool") 

838 time_inv_.remove("CubicBool") 

839 except: # pragma: nocover 

840 pass 

841 market_vars = [ 

842 "act_T", 

843 "kSS", 

844 "MSS", 

845 "AFunc", 

846 "Rfunc", 

847 "wFunc", 

848 "PermGroFacAgg", 

849 "AggShkDstn", 

850 ] 

851 

852 def __init__(self, **kwds): 

853 AgentType.__init__(self, construct=False, **kwds) 

854 

855 def reset(self): 

856 """ 

857 Initialize this type for a new simulated history of K/L ratio. 

858 

859 Parameters 

860 ---------- 

861 None 

862 

863 Returns 

864 ------- 

865 None 

866 """ 

867 # Start simulation near SS 

868 self.initialize_sim() 

869 self.state_now["aLvlNow"] = self.kSS * np.ones(self.AgentCount) 

870 self.state_now["aNrm"] = self.state_now["aLvlNow"] / self.state_now["pLvl"] 

871 

872 def pre_solve(self): 

873 self.construct("solution_terminal") 

874 

875 def sim_birth(self, which_agents): 

876 """ 

877 Makes new consumers for the given indices. Initialized variables include 

878 aNrm and pLvl, as well as time variables t_age and t_cycle. 

879 

880 Parameters 

881 ---------- 

882 which_agents : np.array(Bool) 

883 Boolean array of size self.AgentCount indicating which agents should be "born". 

884 

885 Returns 

886 ------- 

887 None 

888 """ 

889 IndShockConsumerType.sim_birth(self, which_agents) 

890 if "aLvl" in self.state_now and self.state_now["aLvl"] is not None: 

891 self.state_now["aLvl"][which_agents] = ( 

892 self.state_now["aNrm"][which_agents] 

893 * self.state_now["pLvl"][which_agents] 

894 ) 

895 else: 

896 self.state_now["aLvl"] = self.state_now["aNrm"] * self.state_now["pLvl"] 

897 

898 def sim_death(self): 

899 """ 

900 Randomly determine which consumers die, and distribute their wealth among the survivors. 

901 This method only works if there is only one period in the cycle. 

902 

903 Parameters 

904 ---------- 

905 None 

906 

907 Returns 

908 ------- 

909 who_dies : np.array(bool) 

910 Boolean array of size AgentCount indicating which agents die. 

911 """ 

912 # Just select a random set of agents to die 

913 how_many_die = int(round(self.AgentCount * (1.0 - self.LivPrb[0]))) 

914 base_bool = np.zeros(self.AgentCount, dtype=bool) 

915 base_bool[0:how_many_die] = True 

916 who_dies = self.RNG.permutation(base_bool) 

917 if self.T_age is not None: 

918 who_dies[self.t_age >= self.T_age] = True 

919 

920 # Divide up the wealth of those who die, giving it to those who survive 

921 who_lives = np.logical_not(who_dies) 

922 wealth_living = np.sum(self.state_now["aLvl"][who_lives]) 

923 wealth_dead = np.sum(self.state_now["aLvl"][who_dies]) 

924 Ractuarial = 1.0 + wealth_dead / wealth_living 

925 self.state_now["aNrm"][who_lives] = ( 

926 self.state_now["aNrm"][who_lives] * Ractuarial 

927 ) 

928 self.state_now["aLvl"][who_lives] = ( 

929 self.state_now["aLvl"][who_lives] * Ractuarial 

930 ) 

931 return who_dies 

932 

933 def get_Rport(self): 

934 """ 

935 Returns an array of size self.AgentCount with self.RfreeNow in every entry. 

936 This is the risk-free portfolio return in this model. 

937 

938 Parameters 

939 ---------- 

940 None 

941 

942 Returns 

943 ------- 

944 RfreeNow : np.array 

945 Array of size self.AgentCount with risk free interest rate for each agent. 

946 """ 

947 RfreeNow = self.RfreeNow * np.ones(self.AgentCount) 

948 return RfreeNow 

949 

950 def get_shocks(self): 

951 """ 

952 Finds the effective permanent and transitory shocks this period by combining the aggregate 

953 and idiosyncratic shocks of each type. 

954 

955 Parameters 

956 ---------- 

957 None 

958 

959 Returns 

960 ------- 

961 None 

962 """ 

963 IndShockConsumerType.get_shocks(self) # Update idiosyncratic shocks 

964 self.shocks["TranShk"] *= self.TranShkAggNow * self.wRteNow 

965 self.shocks["PermShk"] *= self.PermShkAggNow 

966 

967 def get_controls(self): 

968 """ 

969 Calculates consumption for each consumer of this type using the consumption functions. 

970 

971 Parameters 

972 ---------- 

973 None 

974 

975 Returns 

976 ------- 

977 None 

978 """ 

979 cNrmNow = np.zeros(self.AgentCount) + np.nan 

980 MPCnow = np.zeros(self.AgentCount) + np.nan 

981 MaggNow = self.get_MaggNow() 

982 for t in range(self.T_cycle): 

983 these = t == self.t_cycle 

984 cNrmNow[these] = self.solution[t].cFunc( 

985 self.state_now["mNrm"][these], MaggNow[these] 

986 ) 

987 MPCnow[these] = self.solution[t].cFunc.derivativeX( 

988 self.state_now["mNrm"][these], MaggNow[these] 

989 ) # Marginal propensity to consume 

990 

991 self.controls["cNrm"] = cNrmNow 

992 self.MPCnow = MPCnow 

993 

994 def get_MaggNow(self): # This function exists to be overwritten in StickyE model 

995 return self.MaggNow * np.ones(self.AgentCount) 

996 

997 def market_action(self): 

998 """ 

999 In the aggregate shocks model, the "market action" is to simulate one 

1000 period of receiving income and choosing how much to consume. 

1001 

1002 Parameters 

1003 ---------- 

1004 None 

1005 

1006 Returns 

1007 ------- 

1008 None 

1009 """ 

1010 self.simulate(1) 

1011 

1012 def calc_bounding_values(self): # pragma: nocover 

1013 """ 

1014 NOT YET IMPLEMENTED FOR THIS CLASS 

1015 """ 

1016 raise NotImplementedError() 

1017 

1018 def make_euler_error_func(self, mMax=100, approx_inc_dstn=True): # pragma: nocover 

1019 """ 

1020 NOT YET IMPLEMENTED FOR THIS CLASS 

1021 """ 

1022 raise NotImplementedError() 

1023 

1024 def check_conditions(self, verbose=None): # pragma: nocover 

1025 raise NotImplementedError() 

1026 

1027 def calc_limiting_values(self): # pragma: nocover 

1028 raise NotImplementedError() 

1029 

1030 

1031############################################################################### 

1032 

1033 

1034default_IncShkDstnInd_aggmrkv_params = { 

1035 "PermShkStd": np.array( 

1036 [[0.1, 0.1]] 

1037 ), # Standard deviation of log permanent income shocks 

1038 "PermShkCount": 7, # Number of points in discrete approximation to permanent income shocks 

1039 "TranShkStd": np.array( 

1040 [[0.1, 0.1]] 

1041 ), # Standard deviation of log transitory income shocks 

1042 "TranShkCount": 7, # Number of points in discrete approximation to transitory income shocks 

1043 "UnempPrb": np.array([0.05, 0.05]), # Probability of unemployment while working 

1044 "IncUnemp": np.array( 

1045 [0.3, 0.3] 

1046 ), # Unemployment benefits replacement rate while working 

1047 "T_retire": 0, # Period of retirement (0 --> no retirement) 

1048 "UnempPrbRet": None, # Probability of "unemployment" while retired 

1049 "IncUnempRet": None, # "Unemployment" benefits when retired 

1050} 

1051 

1052# Make a dictionary to specify a Markov aggregate shocks consumer 

1053init_agg_mrkv_shocks = init_agg_shocks.copy() 

1054init_agg_mrkv_shocks.update(default_IncShkDstnInd_aggmrkv_params) 

1055aggmrkv_constructor_dict = aggshock_constructor_dict.copy() 

1056aggmrkv_constructor_dict["solution_terminal"] = make_aggmrkv_solution_terminal 

1057aggmrkv_constructor_dict["IncShkDstnInd"] = ( 

1058 construct_markov_lognormal_income_process_unemployment 

1059) 

1060aggmrkv_constructor_dict["IncShkDstn"] = combine_markov_ind_and_agg_income_shocks 

1061aggmrkv_constructor_dict["PermShkDstn"] = get_PermShkDstn_from_IncShkDstn_markov 

1062aggmrkv_constructor_dict["TranShkDstn"] = get_TranShkDstn_from_IncShkDstn_markov 

1063init_agg_mrkv_shocks["constructors"] = aggmrkv_constructor_dict 

1064 

1065 

1066class AggShockMarkovConsumerType(AggShockConsumerType): 

1067 """ 

1068 A class for representing ex ante heterogeneous "types" of consumers who 

1069 experience both aggregate and idiosyncratic shocks to productivity (both 

1070 permanent and transitory), who lives in an environment where the macroeconomic 

1071 state is subject to Markov-style discrete state evolution. 

1072 

1073 NB: Unlike most AgentType subclasses, AggMarkovShockConsumerType does not automatically 

1074 call its construct method as part of instantiation. In most cases, an instance of 

1075 this class cannot be meaningfully solved without being associated with a Market 

1076 instance, probably of the subclass CobbDouglasMarkovEconomy or SmallOpenMarkovEconomy. 

1077 

1078 To be able to fully build and solve an instance of this class, assign it as an 

1079 instance of the agents attribute of an appropriate Market, then run that Market's 

1080 give_agent_params method. This will distribute market-level parameters and 

1081 instruct the agents to run their construct method. You should then be able to 

1082 run the solve method on the Market or its agents. 

1083 """ 

1084 

1085 time_inv_ = AggShockConsumerType.time_inv_ + ["MrkvArray"] 

1086 shock_vars_ = AggShockConsumerType.shock_vars_ + ["Mrkv"] 

1087 market_vars = AggShockConsumerType.market_vars + ["MrkvArray"] 

1088 default_ = { 

1089 "params": init_agg_mrkv_shocks, 

1090 "solver": solve_ConsAggMarkov, 

1091 "track_vars": ["aNrm", "cNrm", "mNrm", "pLvl"], 

1092 } 

1093 

1094 def initialize_sim(self): 

1095 self.shocks["Mrkv"] = 0 

1096 AggShockConsumerType.initialize_sim(self) 

1097 

1098 def get_shocks(self): 

1099 """ 

1100 Gets permanent and transitory income shocks for this period. Samples from IncShkDstn for 

1101 each period in the cycle. This is a copy-paste from IndShockConsumerType, with the 

1102 addition of the Markov macroeconomic state. Unfortunately, the get_shocks method for 

1103 MarkovConsumerType cannot be used, as that method assumes that MrkvNow is a vector 

1104 with a value for each agent, not just a single int. 

1105 

1106 Parameters 

1107 ---------- 

1108 None 

1109 

1110 Returns 

1111 ------- 

1112 None 

1113 """ 

1114 PermShkNow = np.zeros(self.AgentCount) # Initialize shock arrays 

1115 TranShkNow = np.zeros(self.AgentCount) 

1116 newborn = self.t_age == 0 

1117 Mrkv = self.shocks["Mrkv"] 

1118 for t in range(self.T_cycle): 

1119 these = t == self.t_cycle 

1120 if np.any(these): 

1121 N = np.sum(these) 

1122 # set current income distribution and permanent growth factor 

1123 IncShkDstnNow = self.IncShkDstn[t - 1][Mrkv] 

1124 PermGroFacNow = self.PermGroFac[t - 1] 

1125 

1126 # Get random draws of income shocks from the discrete distribution 

1127 ShockDraws = IncShkDstnNow.draw(N, shuffle=True) 

1128 # Permanent "shock" includes expected growth 

1129 PermShkNow[these] = ShockDraws[0] * PermGroFacNow 

1130 TranShkNow[these] = ShockDraws[1] 

1131 

1132 # That procedure used the *last* period in the sequence for newborns, but that's not right 

1133 # Redraw shocks for newborns, using the *first* period in the sequence. Approximation. 

1134 N = np.sum(newborn) 

1135 if N > 0: 

1136 these = newborn 

1137 IncShkDstnNow = self.IncShkDstn[0][ 

1138 self.shocks["Mrkv"] 

1139 ] # set current income distribution 

1140 PermGroFacNow = self.PermGroFac[0] # and permanent growth factor 

1141 

1142 # Get random draws of income shocks from the discrete distribution 

1143 ShockDraws = IncShkDstnNow.draw(N, shuffle=True) 

1144 

1145 # Permanent "shock" includes expected growth 

1146 PermShkNow[these] = ShockDraws[0] * PermGroFacNow 

1147 TranShkNow[these] = ShockDraws[1] 

1148 

1149 # Store the shocks in self 

1150 self.EmpNow = np.ones(self.AgentCount, dtype=bool) 

1151 self.EmpNow[TranShkNow == self.IncUnemp[Mrkv]] = False 

1152 self.shocks["TranShk"] = TranShkNow * self.TranShkAggNow * self.wRteNow 

1153 self.shocks["PermShk"] = PermShkNow * self.PermShkAggNow 

1154 

1155 def get_controls(self): 

1156 """ 

1157 Calculates consumption for each consumer of this type using the consumption functions. 

1158 For this AgentType class, MrkvNow is the same for all consumers. However, in an 

1159 extension with "macroeconomic inattention", consumers might misperceive the state 

1160 and thus act as if they are in different states. 

1161 

1162 Parameters 

1163 ---------- 

1164 None 

1165 

1166 Returns 

1167 ------- 

1168 None 

1169 """ 

1170 cNrmNow = np.zeros(self.AgentCount) + np.nan 

1171 MPCnow = np.zeros(self.AgentCount) + np.nan 

1172 MaggNow = self.get_MaggNow() 

1173 MrkvNow = self.getMrkvNow() 

1174 

1175 StateCount = self.MrkvArray.shape[0] 

1176 MrkvBoolArray = np.zeros((StateCount, self.AgentCount), dtype=bool) 

1177 for i in range(StateCount): 

1178 MrkvBoolArray[i, :] = i == MrkvNow 

1179 

1180 for t in range(self.T_cycle): 

1181 these = t == self.t_cycle 

1182 for i in range(StateCount): 

1183 those = np.logical_and(these, MrkvBoolArray[i, :]) 

1184 cNrmNow[those] = self.solution[t].cFunc[i]( 

1185 self.state_now["mNrm"][those], MaggNow[those] 

1186 ) 

1187 # Marginal propensity to consume 

1188 MPCnow[those] = ( 

1189 self.solution[t] 

1190 .cFunc[i] 

1191 .derivativeX(self.state_now["mNrm"][those], MaggNow[those]) 

1192 ) 

1193 self.controls["cNrm"] = cNrmNow 

1194 self.MPCnow = MPCnow 

1195 return None 

1196 

1197 def getMrkvNow(self): # This function exists to be overwritten in StickyE model 

1198 return self.shocks["Mrkv"] * np.ones(self.AgentCount, dtype=int) 

1199 

1200 

1201############################################################################### 

1202 

1203# Define some constructor functions for the basic Krusell-Smith model 

1204 

1205 

1206def make_solution_terminal_KS(CRRA): 

1207 cFunc_terminal = 4 * [IdentityFunction(n_dims=2)] 

1208 vPfunc_terminal = [MargValueFuncCRRA(cFunc_terminal[j], CRRA) for j in range(4)] 

1209 solution_terminal = ConsumerSolution(cFunc=cFunc_terminal, vPfunc=vPfunc_terminal) 

1210 return solution_terminal 

1211 

1212 

1213def make_assets_grid_KS(aMin, aMax, aCount, aNestFac): 

1214 return make_assets_grid(aMin, aMax, aCount, None, aNestFac) 

1215 

1216 

1217def make_KS_transition_arrays( 

1218 aGrid, 

1219 Mgrid, 

1220 AFunc, 

1221 LbrInd, 

1222 UrateB, 

1223 UrateG, 

1224 ProdB, 

1225 ProdG, 

1226 MrkvIndArray, 

1227 CapShare, 

1228 DeprRte, 

1229): 

1230 """ 

1231 Construct the attributes ProbArray, mNextArray, MnextArray, and RnextArray, 

1232 which will be used by the one period solver. The information for this method 

1233 is usually obtained by the get_economy_data method. Output is returned as a 

1234 *list* of four arrays, which are later assigned to their appropriate attributes. 

1235 

1236 Parameters 

1237 ---------- 

1238 aGrid : np.array 

1239 Grid of end-of-period individual assets. 

1240 MGrid : np.array 

1241 Grid of aggregate market resources. 

1242 AFunc : function 

1243 End-of-period aggregate assets as a function of aggregate market resources. 

1244 LbrInd : float 

1245 Individual labor supply measure. 

1246 UrateB : float 

1247 Unemployment rate in the "bad" aggregate state. 

1248 UrateG : float 

1249 Unemployment rate in the "good" aggregate state. 

1250 ProdB : float 

1251 TFP in the "bad" aggregate state. 

1252 ProdG : float 

1253 TFP in the "good" aggregate state. 

1254 MrkvIndArray : np.array 

1255 Markov transition probabilities from the perspective of the individual. 

1256 CapShare : float 

1257 Capital's share of production. 

1258 DeprRte : float 

1259 Capital depreciation rate. 

1260 

1261 Returns 

1262 ------- 

1263 ProbArray : np.array 

1264 Array of discrete future outcome probabilities. 

1265 mNextArray : np.array 

1266 Array of discrete realizations of next-period idiosyncratic market resources. 

1267 MnextArray : np.array 

1268 Array of discrete realizations of next-period aggregate market resources. 

1269 RnextArray : np.array 

1270 Array of discrete realizations of next-period rate of return. 

1271 """ 

1272 # Get array sizes 

1273 aCount = aGrid.size 

1274 Mcount = Mgrid.size 

1275 

1276 # Make tiled array of end-of-period idiosyncratic assets (order: a, M, s, s') 

1277 aNow_tiled = np.tile(np.reshape(aGrid, [aCount, 1, 1, 1]), [1, Mcount, 4, 4]) 

1278 

1279 # Make arrays of end-of-period aggregate assets (capital next period) 

1280 AnowB = AFunc[0](Mgrid) 

1281 AnowG = AFunc[1](Mgrid) 

1282 KnextB = np.tile(np.reshape(AnowB, [1, Mcount, 1, 1]), [1, 1, 1, 4]) 

1283 KnextG = np.tile(np.reshape(AnowG, [1, Mcount, 1, 1]), [1, 1, 1, 4]) 

1284 Knext = np.concatenate((KnextB, KnextB, KnextG, KnextG), axis=2) 

1285 

1286 # Make arrays of aggregate labor and TFP next period 

1287 Lnext = np.zeros((1, Mcount, 4, 4)) # shape (1,Mcount,4,4) 

1288 Lnext[0, :, :, 0:2] = (1.0 - UrateB) * LbrInd 

1289 Lnext[0, :, :, 2:4] = (1.0 - UrateG) * LbrInd 

1290 Znext = np.zeros((1, Mcount, 4, 4)) 

1291 Znext[0, :, :, 0:2] = ProdB 

1292 Znext[0, :, :, 2:4] = ProdG 

1293 

1294 # Calculate (net) interest factor and wage rate next period 

1295 KtoLnext = Knext / Lnext 

1296 Rnext = 1.0 + Znext * CapShare * KtoLnext ** (CapShare - 1.0) - DeprRte 

1297 Wnext = Znext * (1.0 - CapShare) * KtoLnext**CapShare 

1298 

1299 # Calculate aggregate market resources next period 

1300 Ynext = Znext * Knext**CapShare * Lnext ** (1.0 - CapShare) 

1301 Mnext = (1.0 - DeprRte) * Knext + Ynext 

1302 

1303 # Tile the interest, wage, and aggregate market resources arrays 

1304 Rnext_tiled = np.tile(Rnext, [aCount, 1, 1, 1]) 

1305 Wnext_tiled = np.tile(Wnext, [aCount, 1, 1, 1]) 

1306 Mnext_tiled = np.tile(Mnext, [aCount, 1, 1, 1]) 

1307 

1308 # Make an array of idiosyncratic labor supply next period 

1309 lNext_tiled = np.zeros([aCount, Mcount, 4, 4]) 

1310 lNext_tiled[:, :, :, 1] = LbrInd 

1311 lNext_tiled[:, :, :, 3] = LbrInd 

1312 

1313 # Calculate idiosyncratic market resources next period 

1314 mNext = Rnext_tiled * aNow_tiled + Wnext_tiled * lNext_tiled 

1315 

1316 # Make a tiled array of transition probabilities 

1317 Probs_tiled = np.tile( 

1318 np.reshape(MrkvIndArray, [1, 1, 4, 4]), [aCount, Mcount, 1, 1] 

1319 ) 

1320 

1321 # Return the attributes that will be used by the solver 

1322 transition_arrays = { 

1323 "ProbArray": Probs_tiled, 

1324 "mNextArray": mNext, 

1325 "MnextArray": Mnext_tiled, 

1326 "RnextArray": Rnext_tiled, 

1327 } 

1328 return transition_arrays 

1329 

1330 

1331def make_emp_idx_arrays(UrateB, UrateG, MrkvIndArray, MrkvAggArray, AgentCount): 

1332 """ 

1333 Construct the attributes emp_permute and unemp_permute, each of which is 

1334 a 2x2 nested list of boolean arrays. The j,k-th element of emp_permute 

1335 represents the employment states this period for agents who were employed 

1336 last period when the macroeconomy is transitioning from state j to state k. 

1337 Likewise, j,k-th element of unemp_permute represents the employment states 

1338 this period for agents who were unemployed last period when the macro- 

1339 economy is transitioning from state j to state k. These attributes are 

1340 referenced during simulation, when they are randomly permuted in order to 

1341 maintain exact unemployment rates in each period. 

1342 

1343 Parameters 

1344 ---------- 

1345 UrateB : float 

1346 Unemployment rate in bad economic state. 

1347 UrateG : float 

1348 Unemployment rate in good economic state. 

1349 MrkvIndArray : np.array 

1350 4x4 array of transition probabilities among discrete states from the 

1351 perspective of an individual consumer. 

1352 MrkvAggArray : np.array 

1353 2x2 array of transition probabilities among aggregate discrete states. 

1354 AgentCount : int 

1355 Number of agents to simulate. 

1356 

1357 Returns 

1358 ------- 

1359 emp_idx_arrays : dict 

1360 Dictionary with two entries: unemp_permute and emp_permute. 

1361 """ 

1362 # Get counts of employed and unemployed agents in each macroeconomic state 

1363 B_unemp_N = int(np.round(UrateB * AgentCount)) 

1364 B_emp_N = AgentCount - B_unemp_N 

1365 G_unemp_N = int(np.round(UrateG * AgentCount)) 

1366 G_emp_N = AgentCount - G_unemp_N 

1367 

1368 # Bad-bad transition indices 

1369 BB_stay_unemp_N = int(np.round(B_unemp_N * MrkvIndArray[0, 0] / MrkvAggArray[0, 0])) 

1370 BB_become_unemp_N = B_unemp_N - BB_stay_unemp_N 

1371 BB_stay_emp_N = int(np.round(B_emp_N * MrkvIndArray[1, 1] / MrkvAggArray[0, 0])) 

1372 BB_become_emp_N = B_emp_N - BB_stay_emp_N 

1373 BB_unemp_permute = np.concatenate( 

1374 [ 

1375 np.ones(BB_become_emp_N, dtype=bool), 

1376 np.zeros(BB_stay_unemp_N, dtype=bool), 

1377 ] 

1378 ) 

1379 BB_emp_permute = np.concatenate( 

1380 [ 

1381 np.ones(BB_stay_emp_N, dtype=bool), 

1382 np.zeros(BB_become_unemp_N, dtype=bool), 

1383 ] 

1384 ) 

1385 

1386 # Bad-good transition indices 

1387 BG_stay_unemp_N = int(np.round(B_unemp_N * MrkvIndArray[0, 2] / MrkvAggArray[0, 1])) 

1388 BG_become_unemp_N = G_unemp_N - BG_stay_unemp_N 

1389 BG_stay_emp_N = int(np.round(B_emp_N * MrkvIndArray[1, 3] / MrkvAggArray[0, 1])) 

1390 BG_become_emp_N = G_emp_N - BG_stay_emp_N 

1391 BG_unemp_permute = np.concatenate( 

1392 [ 

1393 np.ones(BG_become_emp_N, dtype=bool), 

1394 np.zeros(BG_stay_unemp_N, dtype=bool), 

1395 ] 

1396 ) 

1397 BG_emp_permute = np.concatenate( 

1398 [ 

1399 np.ones(BG_stay_emp_N, dtype=bool), 

1400 np.zeros(BG_become_unemp_N, dtype=bool), 

1401 ] 

1402 ) 

1403 

1404 # Good-bad transition indices 

1405 GB_stay_unemp_N = int(np.round(G_unemp_N * MrkvIndArray[2, 0] / MrkvAggArray[1, 0])) 

1406 GB_become_unemp_N = B_unemp_N - GB_stay_unemp_N 

1407 GB_stay_emp_N = int(np.round(G_emp_N * MrkvIndArray[3, 1] / MrkvAggArray[1, 0])) 

1408 GB_become_emp_N = B_emp_N - GB_stay_emp_N 

1409 GB_unemp_permute = np.concatenate( 

1410 [ 

1411 np.ones(GB_become_emp_N, dtype=bool), 

1412 np.zeros(GB_stay_unemp_N, dtype=bool), 

1413 ] 

1414 ) 

1415 GB_emp_permute = np.concatenate( 

1416 [ 

1417 np.ones(GB_stay_emp_N, dtype=bool), 

1418 np.zeros(GB_become_unemp_N, dtype=bool), 

1419 ] 

1420 ) 

1421 

1422 # Good-good transition indices 

1423 GG_stay_unemp_N = int(np.round(G_unemp_N * MrkvIndArray[2, 2] / MrkvAggArray[1, 1])) 

1424 GG_become_unemp_N = G_unemp_N - GG_stay_unemp_N 

1425 GG_stay_emp_N = int(np.round(G_emp_N * MrkvIndArray[3, 3] / MrkvAggArray[1, 1])) 

1426 GG_become_emp_N = G_emp_N - GG_stay_emp_N 

1427 GG_unemp_permute = np.concatenate( 

1428 [ 

1429 np.ones(GG_become_emp_N, dtype=bool), 

1430 np.zeros(GG_stay_unemp_N, dtype=bool), 

1431 ] 

1432 ) 

1433 GG_emp_permute = np.concatenate( 

1434 [ 

1435 np.ones(GG_stay_emp_N, dtype=bool), 

1436 np.zeros(GG_become_unemp_N, dtype=bool), 

1437 ] 

1438 ) 

1439 

1440 # Package transition indices as a dictionary 

1441 unemp_permute = [ 

1442 [BB_unemp_permute, BG_unemp_permute], 

1443 [GB_unemp_permute, GG_unemp_permute], 

1444 ] 

1445 emp_permute = [ 

1446 [BB_emp_permute, BG_emp_permute], 

1447 [GB_emp_permute, GG_emp_permute], 

1448 ] 

1449 emp_idx_arrays = { 

1450 "unemp_permute": unemp_permute, 

1451 "emp_permute": emp_permute, 

1452 } 

1453 return emp_idx_arrays 

1454 

1455 

1456############################################################################### 

1457 

1458# Make a dictionary for Krusell-Smith agents 

1459KS_constructor_dict = { 

1460 "solution_terminal": make_solution_terminal_KS, 

1461 "aGrid": make_assets_grid_KS, 

1462 "transition_arrays": make_KS_transition_arrays, 

1463 "ProbArray": get_it_from("transition_arrays"), 

1464 "mNextArray": get_it_from("transition_arrays"), 

1465 "MnextArray": get_it_from("transition_arrays"), 

1466 "RnextArray": get_it_from("transition_arrays"), 

1467 "emp_idx_arrays": make_emp_idx_arrays, 

1468 "unemp_permute": get_it_from("emp_idx_arrays"), 

1469 "emp_permute": get_it_from("emp_idx_arrays"), 

1470 "MgridBase": make_exponential_MgridBase, 

1471 "T_sim": get_it_from("act_T"), 

1472 "Mgrid": make_Mgrid, 

1473} 

1474 

1475init_KS_agents = { 

1476 "T_cycle": 1, 

1477 "cycles": 0, 

1478 "pseudo_terminal": False, 

1479 "constructors": KS_constructor_dict, 

1480 "DiscFac": 0.99, 

1481 "CRRA": 1.0, 

1482 "aMin": 0.001, 

1483 "aMax": 50.0, 

1484 "aCount": 32, 

1485 "aNestFac": 2, 

1486 "MaggCount": 25, 

1487 "MaggPerturb": 0.01, 

1488 "MaggExpFac": 0.12, 

1489 "AgentCount": 10000, 

1490} 

1491 

1492 

1493class KrusellSmithType(AggIndMarkovConsumerType): 

1494 """ 

1495 A class for representing agents in the seminal Krusell-Smith (1998) model from 

1496 the paper "Income and Wealth Heterogeneity in the Macroeconomy". All default 

1497 parameters have been set to match those in the paper, but the equilibrium object 

1498 is perceptions of aggregate assets as a function of aggregate market resources 

1499 in each macroeconomic state (bad=0, good=1), rather than aggregate capital as 

1500 a function of previous aggregate capital. This choice was made so that some 

1501 of the code from HARK's other HA-macro models can be used. 

1502 

1503 This class inherits from AggIndMarkovConsumerType, which provides the 

1504 generic two-level hierarchical Markov state machinery: 

1505 - 2 macro states: bad (0), good (1) 

1506 - 2 micro states: unemployed (0), employed (1) 

1507 - Combined index: 0=BU, 1=BE, 2=GU, 3=GE 

1508 

1509 The micro-state transitions use exact-match permutation arrays to maintain 

1510 precise unemployment rates each period (overrides the default stochastic 

1511 draw in the base class). 

1512 

1513 NB: Unlike most AgentType subclasses, KrusellSmithType does not automatically 

1514 call its construct method as part of instantiation. In most cases, an instance of 

1515 this class cannot be meaningfully solved without being associated with a Market 

1516 instance, probably of the subclass KrusellSmithEconomy. 

1517 

1518 To be able to fully build and solve an instance of this class, assign it as an 

1519 instance of the agents attribute of an appropriate Market, then run that Market's 

1520 give_agent_params method. This will distribute market-level parameters and 

1521 instruct the agents to run their construct method. You should then be able to 

1522 run the solve method on the Market or its agents. 

1523 """ 

1524 

1525 time_inv_ = [ 

1526 "DiscFac", 

1527 "CRRA", 

1528 "aGrid", 

1529 "ProbArray", 

1530 "mNextArray", 

1531 "MnextArray", 

1532 "RnextArray", 

1533 "Mgrid", 

1534 ] 

1535 time_vary_ = [] 

1536 shock_vars_ = ["Mrkv"] 

1537 state_vars = ["aNow", "mNow", "EmpNow"] 

1538 market_vars = [ 

1539 "act_T", 

1540 "KSS", 

1541 "MSS", 

1542 "AFunc", 

1543 "CapShare", 

1544 "DeprRte", 

1545 "LbrInd", 

1546 "UrateB", 

1547 "UrateG", 

1548 "ProdB", 

1549 "ProdG", 

1550 "MrkvIndArray", 

1551 "MrkvAggArray", 

1552 "MrkvInit", 

1553 ] 

1554 default_ = { 

1555 "params": init_KS_agents, 

1556 "solver": solve_KrusellSmith, 

1557 "track_vars": ["aNow", "cNow", "mNow", "EmpNow"], 

1558 } 

1559 

1560 def __init__(self, **kwds): 

1561 temp = kwds.copy() 

1562 temp["construct"] = False 

1563 AggIndMarkovConsumerType.__init__( 

1564 self, num_macro_states=2, num_micro_states=2, **temp 

1565 ) 

1566 self.construct("MgridBase") 

1567 

1568 # Special case: this type *must* be initialized with construct=False 

1569 # because the data required to make its solution attributes is obtained 

1570 # from the associated economy, not passed as part of its parameters. 

1571 # To make it work properly, instantiate both this class and assign it 

1572 # as an element of agents to a KrusellSmithEconomy instance, then call 

1573 # that economy's give_agent_params method. 

1574 

1575 def pre_solve(self): 

1576 self.construct("solution_terminal") 

1577 

1578 def reset(self): 

1579 self.initialize_sim() 

1580 

1581 def market_action(self): 

1582 self.simulate(1) 

1583 

1584 def initialize_sim(self): 

1585 self.shocks["Mrkv"] = self.MrkvInit 

1586 self.MacroMrkvNow = self.MrkvInit 

1587 AgentType.initialize_sim(self) 

1588 self.state_now["EmpNow"] = self.state_now["EmpNow"].astype(bool) 

1589 self.MicroMrkvNow = self.state_now["EmpNow"].astype(int) 

1590 

1591 def sim_birth(self, which): 

1592 """ 

1593 Create newborn agents with randomly drawn employment states. This will 

1594 only ever be called by initialize_sim() at the start of a new simulation 

1595 history, as the Krusell-Smith model does not have death and replacement. 

1596 The sim_death() method does not exist, as AgentType's default of "no death" 

1597 is the correct behavior for the model. 

1598 """ 

1599 N = np.sum(which) 

1600 if N == 0: 

1601 return 

1602 

1603 S = self.shocks["Mrkv"] 

1604 Urate = [self.UrateB, self.UrateG] 

1605 unemp_N = int(np.round(Urate[S] * N)) 

1606 emp_N = self.AgentCount - unemp_N 

1607 

1608 EmpNew = np.concatenate( 

1609 [np.zeros(unemp_N, dtype=bool), np.ones(emp_N, dtype=bool)] 

1610 ) 

1611 

1612 self.state_now["EmpNow"][which] = self.RNG.permutation(EmpNew) 

1613 self.state_now["aNow"][which] = self.KSS 

1614 self.MicroMrkvNow = self.state_now["EmpNow"].astype(int) 

1615 

1616 def get_shocks(self): 

1617 """ 

1618 Two-step hierarchical Markov draw, then sync employment states. 

1619 

1620 Uses the AggIndMarkovConsumerType machinery: 

1621 1. Read macro state from economy (via self.shocks["Mrkv"]) 

1622 2. Draw micro states via exact-match permutations 

1623 3. Compute combined state index 

1624 """ 

1625 self.get_markov_states() 

1626 self.state_now["EmpNow"] = self.MicroMrkvNow.astype(bool) 

1627 

1628 def get_micro_markov_states(self): 

1629 """ 

1630 Exact-match permutation logic for idiosyncratic employment transitions. 

1631 

1632 Instead of drawing stochastically from conditional probabilities, this 

1633 method shuffles boolean arrays to maintain the exact unemployment rate 

1634 implied by the macro state transition, matching Krusell & Smith (1998). 

1635 """ 

1636 employed = self.state_prev["EmpNow"].copy().astype(bool) 

1637 unemployed = np.logical_not(employed) 

1638 

1639 mrkv_prev = int((unemployed.sum() / float(self.AgentCount)) != self.UrateB) 

1640 MacroNow = self.MacroMrkvNow 

1641 

1642 emp_permute = self.emp_permute[mrkv_prev][MacroNow] 

1643 unemp_permute = self.unemp_permute[mrkv_prev][MacroNow] 

1644 

1645 EmpNow = self.state_now["EmpNow"].copy() 

1646 EmpNow[employed] = self.RNG.permutation(emp_permute) 

1647 EmpNow[unemployed] = self.RNG.permutation(unemp_permute) 

1648 

1649 self.state_now["EmpNow"] = EmpNow 

1650 self.MicroMrkvNow = EmpNow.astype(int) 

1651 

1652 def get_states(self): 

1653 """ 

1654 Get each agent's idiosyncratic state, their household market resources. 

1655 """ 

1656 self.state_now["mNow"] = ( 

1657 self.Rnow * self.state_prev["aNow"] 

1658 + self.Wnow * self.LbrInd * self.state_now["EmpNow"] 

1659 ) 

1660 

1661 def get_controls(self): 

1662 """ 

1663 Get each agent's consumption using the combined Markov state index 

1664 to look up the appropriate 2D consumption function. 

1665 """ 

1666 employed = self.state_now["EmpNow"].copy().astype(bool) 

1667 unemployed = np.logical_not(employed) 

1668 

1669 N = self.num_micro_states 

1670 unemp_idx = N * self.MacroMrkvNow + 0 

1671 emp_idx = N * self.MacroMrkvNow + 1 

1672 

1673 cNow = np.zeros(self.AgentCount) 

1674 Mnow = self.Mnow * np.ones(self.AgentCount) 

1675 cNow[unemployed] = self.solution[0].cFunc[unemp_idx]( 

1676 self.state_now["mNow"][unemployed], Mnow[unemployed] 

1677 ) 

1678 cNow[employed] = self.solution[0].cFunc[emp_idx]( 

1679 self.state_now["mNow"][employed], Mnow[employed] 

1680 ) 

1681 self.controls["cNow"] = cNow 

1682 

1683 def get_poststates(self): 

1684 """ 

1685 Gets each agent's retained assets after consumption. 

1686 """ 

1687 self.state_now["aNow"] = self.state_now["mNow"] - self.controls["cNow"] 

1688 

1689 

1690############################################################################### 

1691 

1692 

1693# Make a dictionary to specify a Cobb-Douglas economy 

1694init_cobb_douglas = { 

1695 "PermShkAggCount": 3, # Number of points in discrete approximation to aggregate permanent shock dist 

1696 "TranShkAggCount": 3, # Number of points in discrete approximation to aggregate transitory shock dist 

1697 "PermShkAggStd": 0.0063, # Standard deviation of log aggregate permanent shocks 

1698 "TranShkAggStd": 0.0031, # Standard deviation of log aggregate transitory shocks 

1699 "DeprRte": 0.025, # Capital depreciation rate 

1700 "CapShare": 0.36, # Capital's share of income 

1701 "DiscFac": 0.96, # Discount factor of perfect foresight calibration 

1702 "CRRA": 2.0, # Coefficient of relative risk aversion of perfect foresight calibration 

1703 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor 

1704 "intercept_prev": 0.0, # Intercept of aggregate savings function 

1705 "slope_prev": 1.0, # Slope of aggregate savings function, 

1706 "verbose": False, # Whether to print solution progress to screen while solving 

1707 "T_discard": 200, # Number of simulated "burn in" periods to discard when updating AFunc 

1708 "DampingFac": 0.1, # Damping factor when updating AFunc 

1709 "max_loops": 20, # Maximum number of AFunc updating loops to allow 

1710} 

1711 

1712 

1713class CobbDouglasEconomy(Market): 

1714 """ 

1715 A class to represent an economy with a Cobb-Douglas aggregate production 

1716 function over labor and capital, extending HARK.Market. The "aggregate 

1717 market process" for this market combines all individuals' asset holdings 

1718 into aggregate capital, yielding the interest factor on assets and the wage 

1719 rate for the upcoming period. 

1720 

1721 Note: The current implementation assumes a constant labor supply, but 

1722 this will be generalized in the future. 

1723 

1724 Parameters 

1725 ---------- 

1726 agents : [ConsumerType] 

1727 List of types of consumers that live in this economy. 

1728 tolerance: float 

1729 Minimum acceptable distance between "dynamic rules" to consider the 

1730 solution process converged. Distance depends on intercept and slope 

1731 of the log-linear "next capital ratio" function. 

1732 act_T : int 

1733 Number of periods to simulate when making a history of of the market. 

1734 """ 

1735 

1736 def __init__(self, agents=None, tolerance=0.0001, act_T=1200, **kwds): 

1737 agents = agents if agents is not None else list() 

1738 params = init_cobb_douglas.copy() 

1739 params["sow_vars"] = [ 

1740 "MaggNow", 

1741 "AaggNow", 

1742 "RfreeNow", 

1743 "wRteNow", 

1744 "PermShkAggNow", 

1745 "TranShkAggNow", 

1746 "KtoLnow", 

1747 ] 

1748 params["reap_vars"] = ["aLvl", "pLvl"] 

1749 params["track_vars"] = ["MaggNow", "AaggNow"] 

1750 params["dyn_vars"] = ["AFunc"] 

1751 params["distributions"] = ["PermShkAggDstn", "TranShkAggDstn", "AggShkDstn"] 

1752 params.update(kwds) 

1753 

1754 Market.__init__(self, agents=agents, tolerance=tolerance, act_T=act_T, **params) 

1755 self.update() 

1756 

1757 def mill_rule(self, aLvl, pLvl): 

1758 """ 

1759 Function to calculate the capital to labor ratio, interest factor, and 

1760 wage rate based on each agent's current state. Just calls calc_R_and_W(). 

1761 

1762 See documentation for calc_R_and_W for more information. 

1763 """ 

1764 return self.calc_R_and_W(aLvl, pLvl) 

1765 

1766 def calc_dynamics(self, MaggNow, AaggNow): 

1767 """ 

1768 Calculates a new dynamic rule for the economy: end of period savings as 

1769 a function of aggregate market resources. Just calls calc_AFunc(). 

1770 

1771 See documentation for calc_AFunc for more information. 

1772 """ 

1773 return self.calc_AFunc(MaggNow, AaggNow) 

1774 

1775 def update(self): 

1776 """ 

1777 Use primitive parameters (and perfect foresight calibrations) to make 

1778 interest factor and wage rate functions (of capital to labor ratio), 

1779 as well as discrete approximations to the aggregate shock distributions. 

1780 

1781 Parameters 

1782 ---------- 

1783 None 

1784 

1785 Returns 

1786 ------- 

1787 None 

1788 """ 

1789 self.kSS = ( 

1790 ( 

1791 self.get_PermGroFacAggLR() ** (self.CRRA) / self.DiscFac 

1792 - (1.0 - self.DeprRte) 

1793 ) 

1794 / self.CapShare 

1795 ) ** (1.0 / (self.CapShare - 1.0)) 

1796 self.KtoYSS = self.kSS ** (1.0 - self.CapShare) 

1797 self.wRteSS = (1.0 - self.CapShare) * self.kSS ** (self.CapShare) 

1798 self.RfreeSS = ( 

1799 1.0 + self.CapShare * self.kSS ** (self.CapShare - 1.0) - self.DeprRte 

1800 ) 

1801 self.MSS = self.kSS * self.RfreeSS + self.wRteSS 

1802 self.convertKtoY = lambda KtoY: KtoY ** ( 

1803 1.0 / (1.0 - self.CapShare) 

1804 ) # converts K/Y to K/L 

1805 self.Rfunc = lambda k: ( 

1806 1.0 + self.CapShare * k ** (self.CapShare - 1.0) - self.DeprRte 

1807 ) 

1808 self.wFunc = lambda k: ((1.0 - self.CapShare) * k ** (self.CapShare)) 

1809 

1810 self.sow_init["KtoLnow"] = self.kSS 

1811 self.sow_init["MaggNow"] = self.kSS 

1812 self.sow_init["AaggNow"] = self.kSS 

1813 self.sow_init["RfreeNow"] = self.Rfunc(self.kSS) 

1814 self.sow_init["wRteNow"] = self.wFunc(self.kSS) 

1815 self.sow_init["PermShkAggNow"] = 1.0 

1816 self.sow_init["TranShkAggNow"] = 1.0 

1817 self.make_AggShkDstn() 

1818 self.AFunc = AggregateSavingRule(self.intercept_prev, self.slope_prev) 

1819 

1820 def get_PermGroFacAggLR(self): 

1821 """ 

1822 A trivial function that returns self.PermGroFacAgg. Exists to be overwritten 

1823 and extended by ConsAggShockMarkov model. 

1824 

1825 Parameters 

1826 ---------- 

1827 None 

1828 

1829 Returns 

1830 ------- 

1831 PermGroFacAggLR : float 

1832 Long run aggregate permanent income growth, which is the same thing 

1833 as aggregate permanent income growth. 

1834 """ 

1835 return self.PermGroFacAgg 

1836 

1837 def make_AggShkDstn(self): 

1838 """ 

1839 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

1840 Draws on attributes TranShkAggStd, PermShkAddStd, TranShkAggCount, PermShkAggCount. 

1841 

1842 Parameters 

1843 ---------- 

1844 None 

1845 

1846 Returns 

1847 ------- 

1848 None 

1849 """ 

1850 RNG = self.RNG 

1851 TranShkAggDstn = MeanOneLogNormal( 

1852 sigma=self.TranShkAggStd, seed=RNG.integers(0, 2**31 - 1) 

1853 ) 

1854 self.TranShkAggDstn = TranShkAggDstn.discretize(N=self.TranShkAggCount) 

1855 PermShkAggDstn = MeanOneLogNormal( 

1856 sigma=self.PermShkAggStd, seed=RNG.integers(0, 2**31 - 1) 

1857 ) 

1858 self.PermShkAggDstn = PermShkAggDstn.discretize(N=self.PermShkAggCount) 

1859 self.AggShkDstn = combine_indep_dstns( 

1860 self.PermShkAggDstn, self.TranShkAggDstn, seed=RNG.integers(0, 2**31 - 1) 

1861 ) 

1862 

1863 def reset(self): 

1864 """ 

1865 Reset the economy to prepare for a new simulation. Sets the time index 

1866 of aggregate shocks to zero and runs Market.reset(). 

1867 

1868 Parameters 

1869 ---------- 

1870 None 

1871 

1872 Returns 

1873 ------- 

1874 None 

1875 """ 

1876 self.Shk_idx = 0 

1877 Market.reset(self) 

1878 

1879 def make_AggShkHist(self): 

1880 """ 

1881 Make simulated histories of aggregate transitory and permanent shocks. 

1882 Histories are of length self.act_T, for use in the general equilibrium 

1883 simulation. 

1884 

1885 Parameters 

1886 ---------- 

1887 None 

1888 

1889 Returns 

1890 ------- 

1891 None 

1892 """ 

1893 sim_periods = self.act_T 

1894 Events = np.arange(self.AggShkDstn.pmv.size) # just a list of integers 

1895 EventDraws = self.AggShkDstn.draw(N=sim_periods, atoms=Events) 

1896 PermShkAggHist = self.AggShkDstn.atoms[0][EventDraws] 

1897 TranShkAggHist = self.AggShkDstn.atoms[1][EventDraws] 

1898 

1899 # Store the histories 

1900 self.PermShkAggHist = PermShkAggHist * self.PermGroFacAgg 

1901 self.TranShkAggHist = TranShkAggHist 

1902 

1903 def calc_R_and_W(self, aLvlNow, pLvlNow): 

1904 """ 

1905 Calculates the interest factor and wage rate this period using each agent's 

1906 capital stock to get the aggregate capital ratio. 

1907 

1908 Parameters 

1909 ---------- 

1910 aLvlNow : [np.array] 

1911 Agents' current end-of-period assets. Elements of the list correspond 

1912 to types in the economy, entries within arrays to agents of that type. 

1913 

1914 Returns 

1915 ------- 

1916 MaggNow : float 

1917 Aggregate market resources for this period normalized by mean permanent income 

1918 AaggNow : float 

1919 Aggregate savings for this period normalized by mean permanent income 

1920 RfreeNow : float 

1921 Interest factor on assets in the economy this period. 

1922 wRteNow : float 

1923 Wage rate for labor in the economy this period. 

1924 PermShkAggNow : float 

1925 Permanent shock to aggregate labor productivity this period. 

1926 TranShkAggNow : float 

1927 Transitory shock to aggregate labor productivity this period. 

1928 KtoLnow : float 

1929 Capital-to-labor ratio in the economy this period. 

1930 """ 

1931 # Calculate aggregate savings 

1932 AaggPrev = np.mean(np.array(aLvlNow)) / np.mean( 

1933 pLvlNow 

1934 ) # End-of-period savings from last period 

1935 # Calculate aggregate capital this period 

1936 AggregateK = np.mean(np.array(aLvlNow)) # ...becomes capital today 

1937 # This version uses end-of-period assets and 

1938 # permanent income to calculate aggregate capital, unlike the Mathematica 

1939 # version, which first applies the idiosyncratic permanent income shocks 

1940 # and then aggregates. Obviously this is mathematically equivalent. 

1941 

1942 # Get this period's aggregate shocks 

1943 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

1944 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

1945 self.Shk_idx += 1 

1946 

1947 AggregateL = np.mean(pLvlNow) * PermShkAggNow 

1948 

1949 # Calculate the interest factor and wage rate this period 

1950 KtoLnow = AggregateK / AggregateL 

1951 self.KtoYnow = KtoLnow ** (1.0 - self.CapShare) 

1952 RfreeNow = self.Rfunc(KtoLnow / TranShkAggNow) 

1953 wRteNow = self.wFunc(KtoLnow / TranShkAggNow) 

1954 MaggNow = KtoLnow * RfreeNow + wRteNow * TranShkAggNow 

1955 self.KtoLnow = KtoLnow # Need to store this as it is a sow variable 

1956 

1957 # Package the results into an object and return it 

1958 return ( 

1959 MaggNow, 

1960 AaggPrev, 

1961 RfreeNow, 

1962 wRteNow, 

1963 PermShkAggNow, 

1964 TranShkAggNow, 

1965 KtoLnow, 

1966 ) 

1967 

1968 def calc_AFunc(self, MaggNow, AaggNow): 

1969 """ 

1970 Calculate a new aggregate savings rule based on the history 

1971 of the aggregate savings and aggregate market resources from a simulation. 

1972 

1973 Parameters 

1974 ---------- 

1975 MaggNow : [float] 

1976 List of the history of the simulated aggregate market resources for an economy. 

1977 AaggNow : [float] 

1978 List of the history of the simulated aggregate savings for an economy. 

1979 

1980 Returns 

1981 ------- 

1982 (unnamed) : CapDynamicRule 

1983 Object containing a new savings rule 

1984 """ 

1985 verbose = self.verbose 

1986 discard_periods = ( 

1987 self.T_discard 

1988 ) # Throw out the first T periods to allow the simulation to approach the SS 

1989 update_weight = ( 

1990 1.0 - self.DampingFac 

1991 ) # Proportional weight to put on new function vs old function parameters 

1992 total_periods = len(MaggNow) 

1993 

1994 # Regress the log savings against log market resources 

1995 logAagg = np.log(AaggNow[discard_periods:total_periods]) 

1996 logMagg = np.log(MaggNow[discard_periods - 1 : total_periods - 1]) 

1997 slope, intercept, r_value, p_value, std_err = stats.linregress(logMagg, logAagg) 

1998 

1999 # Make a new aggregate savings rule by combining the new regression parameters 

2000 # with the previous guess 

2001 intercept = ( 

2002 update_weight * intercept + (1.0 - update_weight) * self.intercept_prev 

2003 ) 

2004 slope = update_weight * slope + (1.0 - update_weight) * self.slope_prev 

2005 AFunc = AggregateSavingRule( 

2006 intercept, slope 

2007 ) # Make a new next-period capital function 

2008 

2009 # Save the new values as "previous" values for the next iteration 

2010 self.intercept_prev = intercept 

2011 self.slope_prev = slope 

2012 

2013 # Print the new parameters 

2014 if verbose: 

2015 print( 

2016 "intercept=" 

2017 + str(intercept) 

2018 + ", slope=" 

2019 + str(slope) 

2020 + ", r-sq=" 

2021 + str(r_value**2) 

2022 ) 

2023 

2024 return AggShocksDynamicRule(AFunc) 

2025 

2026 

2027# Make a dictionary to specify a small open economy 

2028init_small_open_economy = { 

2029 "PermShkAggCount": 3, # Number of points in discrete approximation to aggregate permanent shock dist 

2030 "TranShkAggCount": 3, # Number of points in discrete approximation to aggregate transitory shock dist 

2031 "PermShkAggStd": 0.0063, # Standard deviation of log aggregate permanent shocks 

2032 "TranShkAggStd": 0.0031, # Standard deviation of log aggregate transitory shocks 

2033 "Rfree": 1.02, # exogenous and fixed return factor on assets 

2034 "wRte": 1.0, # exogenous and fixed wage rate 

2035 "PermGroFacAgg": 1.0, # Aggregate permanent income growth factor 

2036 "intercept_prev": 0.0, # Intercept of aggregate savings function 

2037 "slope_prev": 1.0, # Slope of aggregate savings function, 

2038 "verbose": False, # Whether to print solution progress to screen while solving 

2039 "max_loops": 1, # Maximum number of AFunc updating loops to allow, should always be 1 

2040} 

2041 

2042 

2043class SmallOpenEconomy(Market): 

2044 """ 

2045 A class for representing a small open economy, where the wage rate and interest rate are 

2046 exogenously determined by some "global" rate. However, the economy is still subject to 

2047 aggregate productivity shocks. 

2048 

2049 Parameters 

2050 ---------- 

2051 agents : [ConsumerType] 

2052 List of types of consumers that live in this economy. 

2053 tolerance: float 

2054 Minimum acceptable distance between "dynamic rules" to consider the 

2055 solution process converged. Distance depends on intercept and slope 

2056 of the log-linear "next capital ratio" function. 

2057 act_T : int 

2058 Number of periods to simulate when making a history of of the market. 

2059 """ 

2060 

2061 def __init__(self, agents=None, tolerance=0.0001, act_T=1000, **kwds): 

2062 agents = agents if agents is not None else list() 

2063 params = init_small_open_economy.copy() 

2064 params.update(**kwds) 

2065 Market.__init__( 

2066 self, 

2067 agents=agents, 

2068 sow_vars=[ 

2069 "MaggNow", 

2070 "AaggNow", 

2071 "RfreeNow", 

2072 "wRteNow", 

2073 "PermShkAggNow", 

2074 "TranShkAggNow", 

2075 "KtoLnow", 

2076 ], 

2077 reap_vars=[], 

2078 track_vars=["MaggNow", "AaggNow", "KtoLnow"], 

2079 dyn_vars=[], 

2080 distributions=["PermShkAggDstn", "TranShkAggDstn", "AggShkDstn"], 

2081 tolerance=tolerance, 

2082 act_T=act_T, 

2083 **params, 

2084 ) 

2085 self.assign_parameters(**kwds) 

2086 self.update() 

2087 

2088 def update(self): 

2089 """ 

2090 Use primitive parameters to set basic objects. 

2091 This is an extremely stripped-down version 

2092 of update for CobbDouglasEconomy. 

2093 

2094 Parameters 

2095 ---------- 

2096 none 

2097 

2098 Returns 

2099 ------- 

2100 none 

2101 """ 

2102 self.kSS = 1.0 

2103 self.MSS = 1.0 

2104 self.sow_init["KtoLnow_init"] = self.kSS 

2105 self.Rfunc = ConstantFunction(self.Rfree) 

2106 self.wFunc = ConstantFunction(self.wRte) 

2107 self.sow_init["RfreeNow"] = self.Rfunc(self.kSS) 

2108 self.sow_init["wRteNow"] = self.wFunc(self.kSS) 

2109 self.sow_init["MaggNow"] = self.kSS 

2110 self.sow_init["AaggNow"] = self.kSS 

2111 self.sow_init["PermShkAggNow"] = 1.0 

2112 self.sow_init["TranShkAggNow"] = 1.0 

2113 self.make_AggShkDstn() 

2114 self.AFunc = ConstantFunction(1.0) 

2115 

2116 def make_AggShkDstn(self): 

2117 """ 

2118 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

2119 Draws on attributes TranShkAggStd, PermShkAddStd, TranShkAggCount, PermShkAggCount. 

2120 

2121 Parameters 

2122 ---------- 

2123 None 

2124 

2125 Returns 

2126 ------- 

2127 None 

2128 """ 

2129 RNG = self.RNG 

2130 TranShkAggDstn = MeanOneLogNormal( 

2131 sigma=self.TranShkAggStd, seed=RNG.integers(0, 2**31 - 1) 

2132 ) 

2133 self.TranShkAggDstn = TranShkAggDstn.discretize(N=self.TranShkAggCount) 

2134 PermShkAggDstn = MeanOneLogNormal( 

2135 sigma=self.PermShkAggStd, seed=RNG.integers(0, 2**31 - 1) 

2136 ) 

2137 self.PermShkAggDstn = PermShkAggDstn.discretize(N=self.PermShkAggCount) 

2138 self.AggShkDstn = combine_indep_dstns( 

2139 self.PermShkAggDstn, self.TranShkAggDstn, seed=RNG.integers(0, 2**31 - 1) 

2140 ) 

2141 

2142 def mill_rule(self): 

2143 """ 

2144 No aggregation occurs for a small open economy, because the wage and interest rates are 

2145 exogenously determined. However, aggregate shocks may occur. 

2146 

2147 See documentation for get_AggShocks() for more information. 

2148 """ 

2149 return self.get_AggShocks() 

2150 

2151 def calc_dynamics(self, KtoLnow): 

2152 """ 

2153 Calculates a new dynamic rule for the economy, which is just an empty object. 

2154 There is no "dynamic rule" for a small open economy, because K/L does not generate w and R. 

2155 """ 

2156 return MetricObject() 

2157 

2158 def reset(self): 

2159 """ 

2160 Reset the economy to prepare for a new simulation. Sets the time index of aggregate shocks 

2161 to zero and runs Market.reset(). This replicates the reset method for CobbDouglasEconomy; 

2162 future version should create parent class of that class and this one. 

2163 

2164 Parameters 

2165 ---------- 

2166 None 

2167 

2168 Returns 

2169 ------- 

2170 None 

2171 """ 

2172 self.Shk_idx = 0 

2173 Market.reset(self) 

2174 

2175 def make_AggShkHist(self): 

2176 """ 

2177 Make simulated histories of aggregate transitory and permanent shocks. Histories are of 

2178 length self.act_T, for use in the general equilibrium simulation. This replicates the same 

2179 method for CobbDouglasEconomy; future version should create parent class. 

2180 

2181 Parameters 

2182 ---------- 

2183 None 

2184 

2185 Returns 

2186 ------- 

2187 None 

2188 """ 

2189 sim_periods = self.act_T 

2190 Events = np.arange(self.AggShkDstn.pmv.size) # just a list of integers 

2191 EventDraws = self.AggShkDstn.draw(N=sim_periods, atoms=Events) 

2192 PermShkAggHist = self.AggShkDstn.atoms[0][EventDraws] 

2193 TranShkAggHist = self.AggShkDstn.atoms[1][EventDraws] 

2194 

2195 # Store the histories 

2196 self.PermShkAggHist = PermShkAggHist 

2197 self.TranShkAggHist = TranShkAggHist 

2198 

2199 def get_AggShocks(self): 

2200 """ 

2201 Returns aggregate state variables and shocks for this period. The capital-to-labor ratio 

2202 is irrelevant and thus treated as constant, and the wage and interest rates are also 

2203 constant. However, aggregate shocks are assigned from a prespecified history. 

2204 

2205 Parameters 

2206 ---------- 

2207 None 

2208 

2209 Returns 

2210 ------- 

2211 MaggNow : float 

2212 Aggregate market resources for this period normalized by mean permanent income 

2213 AaggNow : float 

2214 Aggregate savings for this period normalized by mean permanent income 

2215 RfreeNow : float 

2216 Interest factor on assets in the economy this period. 

2217 wRteNow : float 

2218 Wage rate for labor in the economy this period. 

2219 PermShkAggNow : float 

2220 Permanent shock to aggregate labor productivity this period. 

2221 TranShkAggNow : float 

2222 Transitory shock to aggregate labor productivity this period. 

2223 KtoLnow : float 

2224 Capital-to-labor ratio in the economy this period. 

2225 

2226 """ 

2227 # Get this period's aggregate shocks 

2228 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

2229 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

2230 self.Shk_idx += 1 

2231 

2232 # Factor prices are constant 

2233 RfreeNow = self.Rfunc(1.0 / TranShkAggNow) 

2234 wRteNow = self.wFunc(1.0 / TranShkAggNow) 

2235 

2236 # Aggregates are irrelavent 

2237 AaggNow = 1.0 

2238 MaggNow = 1.0 

2239 KtoLnow = 1.0 / PermShkAggNow 

2240 

2241 return ( 

2242 MaggNow, 

2243 AaggNow, 

2244 RfreeNow, 

2245 wRteNow, 

2246 PermShkAggNow, 

2247 TranShkAggNow, 

2248 KtoLnow, 

2249 ) 

2250 

2251 

2252# This example makes a high risk, low growth state and a low risk, high growth state 

2253MrkvArray = np.array([[0.90, 0.10], [0.04, 0.96]]) 

2254 

2255# Make a dictionary to specify a Markov Cobb-Douglas economy 

2256init_mrkv_cobb_douglas = init_cobb_douglas.copy() 

2257init_mrkv_cobb_douglas["PermShkAggStd"] = [0.012, 0.006] 

2258init_mrkv_cobb_douglas["TranShkAggStd"] = [0.006, 0.003] 

2259init_mrkv_cobb_douglas["PermGroFacAgg"] = [0.98, 1.02] 

2260init_mrkv_cobb_douglas["MrkvArray"] = MrkvArray 

2261init_mrkv_cobb_douglas["MrkvInit"] = 0 

2262init_mrkv_cobb_douglas["slope_prev"] = 2 * [1.0] 

2263init_mrkv_cobb_douglas["intercept_prev"] = 2 * [0.0] 

2264 

2265 

2266class CobbDouglasMarkovEconomy(CobbDouglasEconomy): 

2267 """ 

2268 A class to represent an economy with a Cobb-Douglas aggregate production 

2269 function over labor and capital, extending HARK.Market. The "aggregate 

2270 market process" for this market combines all individuals' asset holdings 

2271 into aggregate capital, yielding the interest factor on assets and the wage 

2272 rate for the upcoming period. This small extension incorporates a Markov 

2273 state for the "macroeconomy", so that the shock distribution and aggregate 

2274 productivity growth factor can vary over time. 

2275 

2276 Parameters 

2277 ---------- 

2278 agents : [ConsumerType] 

2279 List of types of consumers that live in this economy. 

2280 tolerance: float 

2281 Minimum acceptable distance between "dynamic rules" to consider the 

2282 solution process converged. Distance depends on intercept and slope 

2283 of the log-linear "next capital ratio" function. 

2284 act_T : int 

2285 Number of periods to simulate when making a history of of the market. 

2286 """ 

2287 

2288 def __init__( 

2289 self, 

2290 agents=None, 

2291 tolerance=0.0001, 

2292 act_T=1200, 

2293 **kwds, 

2294 ): 

2295 agents = agents if agents is not None else list() 

2296 params = init_mrkv_cobb_douglas.copy() 

2297 params.update(kwds) 

2298 

2299 CobbDouglasEconomy.__init__( 

2300 self, 

2301 agents=agents, 

2302 tolerance=tolerance, 

2303 act_T=act_T, 

2304 sow_vars=[ 

2305 "MaggNow", 

2306 "AaggNow", 

2307 "RfreeNow", 

2308 "wRteNow", 

2309 "PermShkAggNow", 

2310 "TranShkAggNow", 

2311 "KtoLnow", 

2312 "Mrkv", # This one is new 

2313 ], 

2314 **params, 

2315 ) 

2316 

2317 self.sow_init["Mrkv"] = params["MrkvInit"] 

2318 

2319 def update(self): 

2320 """ 

2321 Use primitive parameters (and perfect foresight calibrations) to make 

2322 interest factor and wage rate functions (of capital to labor ratio), 

2323 as well as discrete approximations to the aggregate shock distributions. 

2324 

2325 Parameters 

2326 ---------- 

2327 None 

2328 

2329 Returns 

2330 ------- 

2331 None 

2332 """ 

2333 CobbDouglasEconomy.update(self) 

2334 StateCount = self.MrkvArray.shape[0] 

2335 AFunc_all = [] 

2336 for i in range(StateCount): 

2337 AFunc_all.append( 

2338 AggregateSavingRule(self.intercept_prev[i], self.slope_prev[i]) 

2339 ) 

2340 self.AFunc = AFunc_all 

2341 

2342 def get_PermGroFacAggLR(self): 

2343 """ 

2344 Calculates and returns the long run permanent income growth factor. This 

2345 is the average growth factor in self.PermGroFacAgg, weighted by the long 

2346 run distribution of Markov states (as determined by self.MrkvArray). 

2347 

2348 Parameters 

2349 ---------- 

2350 None 

2351 

2352 Returns 

2353 ------- 

2354 PermGroFacAggLR : float 

2355 Long run aggregate permanent income growth factor 

2356 """ 

2357 # Find the long run distribution of Markov states 

2358 w, v = np.linalg.eig(np.transpose(self.MrkvArray)) 

2359 idx = (np.abs(w - 1.0)).argmin() 

2360 x = v[:, idx].astype(float) 

2361 LR_dstn = x / np.sum(x) 

2362 

2363 # Return the weighted average of aggregate permanent income growth factors 

2364 PermGroFacAggLR = np.dot(LR_dstn, np.array(self.PermGroFacAgg)) 

2365 return PermGroFacAggLR 

2366 

2367 def make_AggShkDstn(self): 

2368 """ 

2369 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

2370 Draws on attributes TranShkAggStd, PermShkAddStd, TranShkAggCount, PermShkAggCount. 

2371 This version accounts for the Markov macroeconomic state. 

2372 

2373 Parameters 

2374 ---------- 

2375 None 

2376 

2377 Returns 

2378 ------- 

2379 None 

2380 """ 

2381 TranShkAggDstn = [] 

2382 PermShkAggDstn = [] 

2383 AggShkDstn = [] 

2384 StateCount = self.MrkvArray.shape[0] 

2385 RNG = self.RNG 

2386 

2387 for i in range(StateCount): 

2388 TranShkAggDstn.append( 

2389 MeanOneLogNormal( 

2390 sigma=self.TranShkAggStd[i], seed=RNG.integers(0, 2**31 - 1) 

2391 ).discretize( 

2392 N=self.TranShkAggCount, 

2393 ) 

2394 ) 

2395 PermShkAggDstn.append( 

2396 MeanOneLogNormal( 

2397 sigma=self.PermShkAggStd[i], seed=RNG.integers(0, 2**31 - 1) 

2398 ).discretize( 

2399 N=self.PermShkAggCount, 

2400 ) 

2401 ) 

2402 AggShkDstn.append( 

2403 combine_indep_dstns( 

2404 PermShkAggDstn[-1], 

2405 TranShkAggDstn[-1], 

2406 seed=RNG.integers(0, 2**31 - 1), 

2407 ) 

2408 ) 

2409 

2410 self.TranShkAggDstn = TranShkAggDstn 

2411 self.PermShkAggDstn = PermShkAggDstn 

2412 self.AggShkDstn = AggShkDstn 

2413 

2414 def make_AggShkHist(self): 

2415 """ 

2416 Make simulated histories of aggregate transitory and permanent shocks. 

2417 Histories are of length self.act_T, for use in the general equilibrium 

2418 simulation. Draws on history of aggregate Markov states generated by 

2419 internal call to make_Mrkv_history(). 

2420 

2421 Parameters 

2422 ---------- 

2423 None 

2424 

2425 Returns 

2426 ------- 

2427 None 

2428 """ 

2429 self.make_Mrkv_history() # Make a (pseudo)random sequence of Markov states 

2430 sim_periods = self.act_T 

2431 

2432 # For each Markov state in each simulated period, draw the aggregate shocks 

2433 # that would occur in that state in that period 

2434 StateCount = self.MrkvArray.shape[0] 

2435 PermShkAggHistAll = np.zeros((StateCount, sim_periods)) 

2436 TranShkAggHistAll = np.zeros((StateCount, sim_periods)) 

2437 for i in range(StateCount): 

2438 AggShockDraws = self.AggShkDstn[i].draw(N=sim_periods) 

2439 PermShkAggHistAll[i, :] = AggShockDraws[0, :] 

2440 TranShkAggHistAll[i, :] = AggShockDraws[1, :] 

2441 

2442 # Select the actual history of aggregate shocks based on the sequence 

2443 # of Markov states that the economy experiences 

2444 PermShkAggHist = np.zeros(sim_periods) 

2445 TranShkAggHist = np.zeros(sim_periods) 

2446 for i in range(StateCount): 

2447 these = i == self.MrkvNow_hist 

2448 PermShkAggHist[these] = PermShkAggHistAll[i, these] * self.PermGroFacAgg[i] 

2449 TranShkAggHist[these] = TranShkAggHistAll[i, these] 

2450 

2451 # Store the histories 

2452 self.PermShkAggHist = PermShkAggHist 

2453 self.TranShkAggHist = TranShkAggHist 

2454 

2455 def make_Mrkv_history(self): 

2456 """ 

2457 Makes a history of macroeconomic Markov states, stored in the attribute 

2458 MrkvNow_hist. This version ensures that each state is reached a sufficient 

2459 number of times to have a valid sample for calc_dynamics to produce a good 

2460 dynamic rule. It will sometimes cause act_T to be increased beyond its 

2461 initially specified level. 

2462 

2463 Parameters 

2464 ---------- 

2465 None 

2466 

2467 Returns 

2468 ------- 

2469 None 

2470 """ 

2471 loops_max = getattr(self, "loops_max", 10) 

2472 state_T_min = 50 # Choose minimum number of periods in each state for a valid Markov sequence 

2473 logit_scale = ( 

2474 0.2 # Scaling factor on logit choice shocks when jumping to a new state 

2475 ) 

2476 # Values close to zero make the most underrepresented states very likely to visit, while 

2477 # large values of logit_scale make any state very likely to be jumped to. 

2478 

2479 # Reset act_T to the level actually specified by the user 

2480 if hasattr(self, "act_T_orig"): 

2481 act_T = self.act_T_orig 

2482 else: # Or store it for the first time 

2483 self.act_T_orig = self.act_T 

2484 act_T = self.act_T 

2485 

2486 # Find the long run distribution of Markov states 

2487 w, v = np.linalg.eig(np.transpose(self.MrkvArray)) 

2488 idx = (np.abs(w - 1.0)).argmin() 

2489 x = v[:, idx].astype(float) 

2490 LR_dstn = x / np.sum(x) 

2491 

2492 # Initialize the Markov history and set up transitions 

2493 MrkvNow_hist = np.zeros(self.act_T_orig, dtype=int) 

2494 loops = 0 

2495 go = True 

2496 MrkvNow = self.sow_init["Mrkv"] 

2497 t = 0 

2498 StateCount = self.MrkvArray.shape[0] 

2499 

2500 # Add histories until each state has been visited at least state_T_min times 

2501 rare_dstn = Uniform(seed=0) 

2502 while go: 

2503 markov_process = MarkovProcess(self.MrkvArray, seed=loops) 

2504 for s in range(self.act_T_orig): # Add act_T_orig more periods 

2505 MrkvNow_hist[t] = MrkvNow 

2506 MrkvNow = markov_process.draw(MrkvNow) 

2507 t += 1 

2508 

2509 # Calculate the empirical distribution 

2510 state_T = np.zeros(StateCount) 

2511 for i in range(StateCount): 

2512 state_T[i] = np.sum(MrkvNow_hist == i) 

2513 

2514 # Check whether each state has been visited state_T_min times 

2515 if np.all(state_T >= state_T_min): 

2516 go = False # If so, terminate the loop 

2517 continue 

2518 

2519 # Choose an underrepresented state to "jump" to 

2520 if np.any( 

2521 state_T == 0 

2522 ): # If any states have *never* been visited, randomly choose one of those 

2523 never_visited = np.where(np.array(state_T == 0))[0] 

2524 MrkvNow = np.random.choice(never_visited) 

2525 else: # Otherwise, use logit choice probabilities to visit an underrepresented state 

2526 emp_dstn = state_T / act_T 

2527 ratios = LR_dstn / emp_dstn 

2528 ratios_adj = ratios - np.max(ratios) 

2529 ratios_exp = np.exp(ratios_adj / logit_scale) 

2530 ratios_sum = np.sum(ratios_exp) 

2531 jump_probs = ratios_exp / ratios_sum 

2532 cum_probs = np.cumsum(jump_probs) 

2533 MrkvNow = np.searchsorted(cum_probs, rare_dstn.draw(1)[0]) 

2534 

2535 loops += 1 

2536 # Make the Markov state history longer by act_T_orig periods 

2537 if loops >= loops_max: 

2538 go = False 

2539 raise ( 

2540 ValueError( 

2541 "make_Mrkv_history reached maximum number of loops without generating a valid sequence!" 

2542 ) 

2543 ) 

2544 else: 

2545 MrkvNow_new = np.zeros(self.act_T_orig, dtype=int) 

2546 MrkvNow_hist = np.concatenate((MrkvNow_hist, MrkvNow_new)) 

2547 act_T += self.act_T_orig 

2548 

2549 # Store the results as attributes of self 

2550 self.MrkvNow_hist = MrkvNow_hist 

2551 self.act_T = act_T 

2552 

2553 def mill_rule(self, aLvl, pLvl): 

2554 """ 

2555 Function to calculate the capital to labor ratio, interest factor, and 

2556 wage rate based on each agent's current state. Just calls calc_R_and_W() 

2557 and adds the Markov state index. 

2558 

2559 See documentation for calc_R_and_W for more information. 

2560 

2561 Params 

2562 ------- 

2563 aLvl : float 

2564 pLvl : float 

2565 

2566 Returns 

2567 ------- 

2568 Mnow : float 

2569 Aggregate market resources for this period. 

2570 Aprev : float 

2571 Aggregate savings for the prior period. 

2572 KtoLnow : float 

2573 Capital-to-labor ratio in the economy this period. 

2574 Rnow : float 

2575 Interest factor on assets in the economy this period. 

2576 Wnow : float 

2577 Wage rate for labor in the economy this period. 

2578 MrkvNow : int 

2579 Binary indicator for bad (0) or good (1) macroeconomic state. 

2580 """ 

2581 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2582 temp = self.calc_R_and_W(aLvl, pLvl) 

2583 

2584 return temp + (MrkvNow,) 

2585 

2586 def calc_AFunc(self, MaggNow, AaggNow): 

2587 """ 

2588 Calculate a new aggregate savings rule based on the history of the 

2589 aggregate savings and aggregate market resources from a simulation. 

2590 Calculates an aggregate saving rule for each macroeconomic Markov state. 

2591 

2592 Parameters 

2593 ---------- 

2594 MaggNow : [float] 

2595 List of the history of the simulated aggregate market resources for an economy. 

2596 AaggNow : [float] 

2597 List of the history of the simulated aggregate savings for an economy. 

2598 

2599 Returns 

2600 ------- 

2601 (unnamed) : CapDynamicRule 

2602 Object containing new saving rules for each Markov state. 

2603 """ 

2604 verbose = self.verbose 

2605 discard_periods = ( 

2606 self.T_discard 

2607 ) # Throw out the first T periods to allow the simulation to approach the SS 

2608 update_weight = ( 

2609 1.0 - self.DampingFac 

2610 ) # Proportional weight to put on new function vs old function parameters 

2611 total_periods = len(MaggNow) 

2612 

2613 # Trim the histories of M_t and A_t and convert them to logs 

2614 logAagg = np.log(AaggNow[discard_periods:total_periods]) 

2615 logMagg = np.log(MaggNow[discard_periods - 1 : total_periods - 1]) 

2616 MrkvHist = self.MrkvNow_hist[discard_periods - 1 : total_periods - 1] 

2617 

2618 # For each Markov state, regress A_t on M_t and update the saving rule 

2619 AFunc_list = [] 

2620 rSq_list = [] 

2621 for i in range(self.MrkvArray.shape[0]): 

2622 these = i == MrkvHist 

2623 slope, intercept, r_value, p_value, std_err = stats.linregress( 

2624 logMagg[these], logAagg[these] 

2625 ) 

2626 

2627 # Make a new aggregate savings rule by combining the new regression parameters 

2628 # with the previous guess 

2629 intercept = ( 

2630 update_weight * intercept 

2631 + (1.0 - update_weight) * self.intercept_prev[i] 

2632 ) 

2633 slope = update_weight * slope + (1.0 - update_weight) * self.slope_prev[i] 

2634 AFunc_list.append( 

2635 AggregateSavingRule(intercept, slope) 

2636 ) # Make a new next-period capital function 

2637 rSq_list.append(r_value**2) 

2638 

2639 # Save the new values as "previous" values for the next iteration 

2640 self.intercept_prev[i] = intercept 

2641 self.slope_prev[i] = slope 

2642 

2643 # Print the new parameters 

2644 if verbose: # pragma: nocover 

2645 print( 

2646 "intercept=" 

2647 + str(self.intercept_prev) 

2648 + ", slope=" 

2649 + str(self.slope_prev) 

2650 + ", r-sq=" 

2651 + str(rSq_list) 

2652 ) 

2653 

2654 return AggShocksDynamicRule(AFunc_list) 

2655 

2656 

2657# Make a dictionary to specify a small open Markov economy 

2658init_mrkv_small_open_economy = init_small_open_economy.copy() 

2659init_mrkv_small_open_economy["PermShkAggStd"] = [0.012, 0.006] 

2660init_mrkv_small_open_economy["TranShkAggStd"] = [0.006, 0.003] 

2661init_mrkv_small_open_economy["PermGroFacAgg"] = [0.98, 1.02] 

2662init_mrkv_small_open_economy["MrkvArray"] = MrkvArray 

2663init_mrkv_small_open_economy["MrkvInit"] = 0 

2664init_mrkv_small_open_economy["slope_prev"] = 2 * [1.0] 

2665init_mrkv_small_open_economy["intercept_prev"] = 2 * [0.0] 

2666init_mrkv_small_open_economy["Rfree"] = 1.02 

2667init_mrkv_small_open_economy["wRte"] = 1.0 

2668 

2669 

2670class SmallOpenMarkovEconomy(CobbDouglasMarkovEconomy, SmallOpenEconomy): 

2671 """ 

2672 A class for representing a small open economy, where the wage rate and interest rate are 

2673 exogenously determined by some "global" rate. However, the economy is still subject to 

2674 aggregate productivity shocks. This version supports a discrete Markov state. All 

2675 methods in this class inherit from the two parent classes. 

2676 """ 

2677 

2678 def __init__(self, agents=None, tolerance=0.0001, act_T=1000, **kwds): 

2679 agents = agents if agents is not None else list() 

2680 temp_dict = init_mrkv_small_open_economy.copy() 

2681 temp_dict.update(kwds) 

2682 CobbDouglasMarkovEconomy.__init__( 

2683 self, 

2684 agents=agents, 

2685 tolerance=tolerance, 

2686 act_T=act_T, 

2687 reap_vars=[], 

2688 dyn_vars=[], 

2689 **temp_dict, 

2690 ) 

2691 

2692 def update(self): 

2693 SmallOpenEconomy.update(self) 

2694 StateCount = self.MrkvArray.shape[0] 

2695 self.AFunc = StateCount * [IdentityFunction()] 

2696 

2697 def make_AggShkDstn(self): 

2698 CobbDouglasMarkovEconomy.make_AggShkDstn(self) 

2699 

2700 def mill_rule(self): 

2701 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2702 temp = SmallOpenEconomy.get_AggShocks(self) 

2703 temp += (MrkvNow,) 

2704 return temp 

2705 

2706 def calc_dynamics(self): 

2707 return NullFunc() 

2708 

2709 def make_AggShkHist(self): 

2710 CobbDouglasMarkovEconomy.make_AggShkHist(self) 

2711 

2712 

2713init_KS_economy = { 

2714 "verbose": False, 

2715 "act_T": 11000, 

2716 "T_discard": 1000, 

2717 "DampingFac": 0.1, 

2718 "intercept_prev": [0.0, 0.0], 

2719 "slope_prev": [1.0, 1.0], 

2720 "DiscFac": 0.99, 

2721 "CRRA": 1.0, 

2722 # Not listed in KS (1998), but Alan Lujan got this number indirectly from KS 

2723 "LbrInd": 0.3271, 

2724 "ProdB": 0.99, 

2725 "ProdG": 1.01, 

2726 "CapShare": 0.36, 

2727 "DeprRte": 0.025, 

2728 "DurMeanB": 8.0, 

2729 "DurMeanG": 8.0, 

2730 "SpellMeanB": 2.5, 

2731 "SpellMeanG": 1.5, 

2732 "UrateB": 0.10, 

2733 "UrateG": 0.04, 

2734 "RelProbBG": 0.75, 

2735 "RelProbGB": 1.25, 

2736 "MrkvInit": 0, 

2737} 

2738 

2739 

2740class KrusellSmithEconomy(Market): 

2741 """ 

2742 A class to represent an economy in the special Krusell-Smith (1998) model. 

2743 This model replicates the one presented in the JPE article "Income and Wealth 

2744 Heterogeneity in the Macroeconomy", with its default parameters set to match 

2745 those in the paper. 

2746 

2747 Parameters 

2748 ---------- 

2749 agents : [KrusellSmithType] 

2750 List of types of consumers that live in this economy. 

2751 tolerance: float 

2752 Minimum acceptable distance between "dynamic rules" to consider the 

2753 solution process converged. Distance depends on intercept and slope 

2754 of the log-linear "next capital ratio" function. 

2755 act_T : int 

2756 Number of periods to simulate when making a history of of the market. 

2757 """ 

2758 

2759 def __init__(self, agents=None, tolerance=0.0001, **kwds): 

2760 agents = agents if agents is not None else list() 

2761 params = deepcopy(init_KS_economy) 

2762 params.update(kwds) 

2763 

2764 Market.__init__( 

2765 self, 

2766 agents=agents, 

2767 tolerance=tolerance, 

2768 sow_vars=["Mnow", "Aprev", "Mrkv", "Rnow", "Wnow"], 

2769 reap_vars=["aNow", "EmpNow"], 

2770 track_vars=["Mrkv", "Aprev", "Mnow", "Urate"], 

2771 dyn_vars=["AFunc"], 

2772 **params, 

2773 ) 

2774 self.update() 

2775 

2776 def update(self): 

2777 """ 

2778 Construct trivial initial guesses of the aggregate saving rules, as well 

2779 as the perfect foresight steady state and associated objects. 

2780 """ 

2781 StateCount = 2 

2782 AFunc_all = [ 

2783 AggregateSavingRule(self.intercept_prev[j], self.slope_prev[j]) 

2784 for j in range(StateCount) 

2785 ] 

2786 self.AFunc = AFunc_all 

2787 self.KtoLSS = ( 

2788 (1.0**self.CRRA / self.DiscFac - (1.0 - self.DeprRte)) / self.CapShare 

2789 ) ** (1.0 / (self.CapShare - 1.0)) 

2790 self.KSS = self.KtoLSS * self.LbrInd 

2791 self.KtoYSS = self.KtoLSS ** (1.0 - self.CapShare) 

2792 self.WSS = (1.0 - self.CapShare) * self.KtoLSS ** (self.CapShare) 

2793 self.RSS = ( 

2794 1.0 + self.CapShare * self.KtoLSS ** (self.CapShare - 1.0) - self.DeprRte 

2795 ) 

2796 self.MSS = self.KSS * self.RSS + self.WSS * self.LbrInd 

2797 self.convertKtoY = lambda KtoY: KtoY ** ( 

2798 1.0 / (1.0 - self.CapShare) 

2799 ) # converts K/Y to K/L 

2800 self.rFunc = lambda k: self.CapShare * k ** (self.CapShare - 1.0) 

2801 self.Wfunc = lambda k: ((1.0 - self.CapShare) * k ** (self.CapShare)) 

2802 self.sow_init["KtoLnow"] = self.KtoLSS 

2803 self.sow_init["Mnow"] = self.MSS 

2804 self.sow_init["Aprev"] = self.KSS 

2805 self.sow_init["Rnow"] = self.RSS 

2806 self.sow_init["Wnow"] = self.WSS 

2807 self.PermShkAggNow_init = 1.0 

2808 self.TranShkAggNow_init = 1.0 

2809 self.sow_init["Mrkv"] = 0 

2810 self.make_MrkvArray() 

2811 

2812 def reset(self): 

2813 """ 

2814 Reset the economy to prepare for a new simulation. Sets the time index 

2815 of aggregate shocks to zero and runs Market.reset(). 

2816 """ 

2817 self.Shk_idx = 0 

2818 Market.reset(self) 

2819 

2820 def make_MrkvArray(self): 

2821 """ 

2822 Construct the attributes MrkvAggArray and MrkvIndArray from the primitive 

2823 attributes DurMeanB, DurMeanG, SpellMeanB, SpellMeanG, UrateB, UrateG, 

2824 RelProbGB, and RelProbBG. 

2825 """ 

2826 # Construct aggregate Markov transition probabilities 

2827 ProbBG = 1.0 / self.DurMeanB 

2828 ProbGB = 1.0 / self.DurMeanG 

2829 ProbBB = 1.0 - ProbBG 

2830 ProbGG = 1.0 - ProbGB 

2831 MrkvAggArray = np.array([[ProbBB, ProbBG], [ProbGB, ProbGG]]) 

2832 

2833 # Construct idiosyncratic Markov transition probabilities 

2834 # ORDER: BU, BE, GU, GE 

2835 MrkvIndArray = np.zeros((4, 4)) 

2836 

2837 # BAD-BAD QUADRANT 

2838 MrkvIndArray[0, 1] = ProbBB / self.SpellMeanB 

2839 MrkvIndArray[0, 0] = ProbBB * (1 - 1.0 / self.SpellMeanB) 

2840 MrkvIndArray[1, 0] = self.UrateB / (1.0 - self.UrateB) * MrkvIndArray[0, 1] 

2841 MrkvIndArray[1, 1] = ProbBB - MrkvIndArray[1, 0] 

2842 

2843 # GOOD-GOOD QUADRANT 

2844 MrkvIndArray[2, 3] = ProbGG / self.SpellMeanG 

2845 MrkvIndArray[2, 2] = ProbGG * (1 - 1.0 / self.SpellMeanG) 

2846 MrkvIndArray[3, 2] = self.UrateG / (1.0 - self.UrateG) * MrkvIndArray[2, 3] 

2847 MrkvIndArray[3, 3] = ProbGG - MrkvIndArray[3, 2] 

2848 

2849 # BAD-GOOD QUADRANT 

2850 MrkvIndArray[0, 2] = self.RelProbBG * MrkvIndArray[2, 2] / ProbGG * ProbBG 

2851 MrkvIndArray[0, 3] = ProbBG - MrkvIndArray[0, 2] 

2852 MrkvIndArray[1, 2] = ( 

2853 ProbBG * self.UrateG - self.UrateB * MrkvIndArray[0, 2] 

2854 ) / (1.0 - self.UrateB) 

2855 MrkvIndArray[1, 3] = ProbBG - MrkvIndArray[1, 2] 

2856 

2857 # GOOD-BAD QUADRANT 

2858 MrkvIndArray[2, 0] = self.RelProbGB * MrkvIndArray[0, 0] / ProbBB * ProbGB 

2859 MrkvIndArray[2, 1] = ProbGB - MrkvIndArray[2, 0] 

2860 MrkvIndArray[3, 0] = ( 

2861 ProbGB * self.UrateB - self.UrateG * MrkvIndArray[2, 0] 

2862 ) / (1.0 - self.UrateG) 

2863 MrkvIndArray[3, 1] = ProbGB - MrkvIndArray[3, 0] 

2864 

2865 # Test for valid idiosyncratic transition probabilities 

2866 assert np.all(MrkvIndArray >= 0.0), ( 

2867 "Invalid idiosyncratic transition probabilities!" 

2868 ) 

2869 self.MrkvAggArray = MrkvAggArray 

2870 self.MrkvIndArray = MrkvIndArray 

2871 

2872 def make_Mrkv_history(self): 

2873 """ 

2874 Makes a history of macroeconomic Markov states, stored in the attribute 

2875 MrkvNow_hist. This variable is binary (0 bad, 1 good) in the KS model. 

2876 """ 

2877 # Initialize the Markov history and set up transitions 

2878 self.MrkvNow_hist = np.zeros(self.act_T, dtype=int) 

2879 MrkvNow = self.MrkvInit 

2880 

2881 markov_process = MarkovProcess(self.MrkvAggArray, seed=0) 

2882 for s in range(self.act_T): # Add act_T_orig more periods 

2883 self.MrkvNow_hist[s] = MrkvNow 

2884 MrkvNow = markov_process.draw(MrkvNow) 

2885 

2886 def mill_rule(self, aNow, EmpNow): 

2887 """ 

2888 Method to calculate the capital to labor ratio, interest factor, and 

2889 wage rate based on each agent's current state. Just calls calc_R_and_W(). 

2890 

2891 See documentation for calc_R_and_W for more information. 

2892 

2893 Returns 

2894 ------- 

2895 Mnow : float 

2896 Aggregate market resources for this period. 

2897 Aprev : float 

2898 Aggregate savings for the prior period. 

2899 MrkvNow : int 

2900 Binary indicator for bad (0) or good (1) macroeconomic state. 

2901 Rnow : float 

2902 Interest factor on assets in the economy this period. 

2903 Wnow : float 

2904 Wage rate for labor in the economy this period. 

2905 """ 

2906 return self.calc_R_and_W(aNow, EmpNow) 

2907 

2908 def calc_dynamics(self, Mnow, Aprev): 

2909 """ 

2910 Method to update perceptions of the aggregate saving rule in each 

2911 macroeconomic state; just calls calc_AFunc. 

2912 """ 

2913 return self.calc_AFunc(Mnow, Aprev) 

2914 

2915 def calc_R_and_W(self, aNow, EmpNow): 

2916 """ 

2917 Calculates the interest factor and wage rate this period using each agent's 

2918 capital stock to get the aggregate capital ratio. 

2919 

2920 Parameters 

2921 ---------- 

2922 aNow : [np.array] 

2923 Agents' current end-of-period assets. Elements of the list correspond 

2924 to types in the economy, entries within arrays to agents of that type. 

2925 EmpNow [np.array] 

2926 Agents' binary employment states. Not actually used in computation of 

2927 interest and wage rates, but stored in the history to verify that the 

2928 idiosyncratic unemployment probabilities are behaving as expected. 

2929 

2930 Returns 

2931 ------- 

2932 Mnow : float 

2933 Aggregate market resources for this period. 

2934 Aprev : float 

2935 Aggregate savings for the prior period. 

2936 MrkvNow : int 

2937 Binary indicator for bad (0) or good (1) macroeconomic state. 

2938 Rnow : float 

2939 Interest factor on assets in the economy this period. 

2940 Wnow : float 

2941 Wage rate for labor in the economy this period. 

2942 """ 

2943 # Calculate aggregate savings 

2944 # End-of-period savings from last period 

2945 Aprev = np.mean(np.array(aNow)) 

2946 # Calculate aggregate capital this period 

2947 AggK = Aprev # ...becomes capital today 

2948 

2949 # Calculate unemployment rate 

2950 Urate = 1.0 - np.mean(np.array(EmpNow)) 

2951 self.Urate = Urate # This is the unemployment rate for the *prior* period 

2952 

2953 # Get this period's TFP and labor supply 

2954 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2955 if MrkvNow == 0: 

2956 Prod = self.ProdB 

2957 AggL = (1.0 - self.UrateB) * self.LbrInd 

2958 elif MrkvNow == 1: 

2959 Prod = self.ProdG 

2960 AggL = (1.0 - self.UrateG) * self.LbrInd 

2961 self.Shk_idx += 1 

2962 

2963 # Calculate the interest factor and wage rate this period 

2964 KtoLnow = AggK / AggL 

2965 Rnow = 1.0 + Prod * self.rFunc(KtoLnow) - self.DeprRte 

2966 Wnow = Prod * self.Wfunc(KtoLnow) 

2967 Mnow = Rnow * AggK + Wnow * AggL 

2968 self.KtoLnow = KtoLnow # Need to store this as it is a sow variable 

2969 

2970 # Returns a tuple of these values 

2971 return Mnow, Aprev, MrkvNow, Rnow, Wnow 

2972 

2973 def calc_AFunc(self, Mnow, Aprev): 

2974 """ 

2975 Calculate a new aggregate savings rule based on the history of the 

2976 aggregate savings and aggregate market resources from a simulation. 

2977 Calculates an aggregate saving rule for each macroeconomic Markov state. 

2978 

2979 Parameters 

2980 ---------- 

2981 Mnow : [float] 

2982 List of the history of the simulated aggregate market resources for an economy. 

2983 Anow : [float] 

2984 List of the history of the simulated aggregate savings for an economy. 

2985 

2986 Returns 

2987 ------- 

2988 (unnamed) : AggShocksDynamicRule 

2989 Object containing new saving rules for each Markov state. 

2990 """ 

2991 verbose = self.verbose 

2992 # Throw out the first T periods to allow the simulation to approach the SS 

2993 discard_periods = self.T_discard 

2994 # Proportional weight to put on new function vs old function parameters 

2995 update_weight = 1.0 - self.DampingFac 

2996 total_periods = len(Mnow) 

2997 

2998 # Trim the histories of M_t and A_t and convert them to logs 

2999 logAagg = np.log(Aprev[discard_periods:total_periods]) 

3000 logMagg = np.log(Mnow[discard_periods - 1 : total_periods - 1]) 

3001 MrkvHist = self.MrkvNow_hist[discard_periods - 1 : total_periods - 1] 

3002 

3003 # For each Markov state, regress A_t on M_t and update the saving rule 

3004 AFunc_list = [] 

3005 rSq_list = [] 

3006 for i in range(self.MrkvAggArray.shape[0]): 

3007 these = i == MrkvHist 

3008 slope, intercept, r_value, p_value, std_err = stats.linregress( 

3009 logMagg[these], logAagg[these] 

3010 ) 

3011 

3012 # Make a new aggregate savings rule by combining the new regression parameters 

3013 # with the previous guess 

3014 intercept = ( 

3015 update_weight * intercept 

3016 + (1.0 - update_weight) * self.intercept_prev[i] 

3017 ) 

3018 slope = update_weight * slope + (1.0 - update_weight) * self.slope_prev[i] 

3019 AFunc_list.append( 

3020 AggregateSavingRule(intercept, slope) 

3021 ) # Make a new next-period capital function 

3022 rSq_list.append(r_value**2) 

3023 

3024 # Save the new values as "previous" values for the next iteration 

3025 self.intercept_prev[i] = intercept 

3026 self.slope_prev[i] = slope 

3027 

3028 # Print the new parameters 

3029 if verbose: # pragma: nocover 

3030 print( 

3031 "intercept=" 

3032 + str(self.intercept_prev) 

3033 + ", slope=" 

3034 + str(self.slope_prev) 

3035 + ", r-sq=" 

3036 + str(rSq_list) 

3037 ) 

3038 

3039 return AggShocksDynamicRule(AFunc_list) 

3040 

3041 

3042class AggregateSavingRule(MetricObject): 

3043 """ 

3044 A class to represent agent beliefs about aggregate saving at the end of this period (AaggNow) as 

3045 a function of (normalized) aggregate market resources at the beginning of the period (MaggNow). 

3046 

3047 Parameters 

3048 ---------- 

3049 intercept : float 

3050 Intercept of the log-linear capital evolution rule. 

3051 slope : float 

3052 Slope of the log-linear capital evolution rule. 

3053 """ 

3054 

3055 def __init__(self, intercept, slope): 

3056 self.intercept = intercept 

3057 self.slope = slope 

3058 self.distance_criteria = ["slope", "intercept"] 

3059 

3060 def __call__(self, Mnow): 

3061 """ 

3062 Evaluates aggregate savings as a function of the aggregate market resources this period. 

3063 

3064 Parameters 

3065 ---------- 

3066 Mnow : float 

3067 Aggregate market resources this period. 

3068 

3069 Returns 

3070 ------- 

3071 Aagg : Expected aggregate savings this period. 

3072 """ 

3073 Aagg = np.exp(self.intercept + self.slope * np.log(Mnow)) 

3074 return Aagg 

3075 

3076 

3077class AggShocksDynamicRule(MetricObject): 

3078 """ 

3079 Just a container class for passing the dynamic rule in the aggregate shocks model to agents. 

3080 

3081 Parameters 

3082 ---------- 

3083 AFunc : CapitalEvoRule 

3084 Aggregate savings as a function of aggregate market resources. 

3085 """ 

3086 

3087 def __init__(self, AFunc): 

3088 self.AFunc = AFunc 

3089 self.distance_criteria = ["AFunc"]