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

925 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-25 05:22 +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.Calibration.Income.IncomeProcesses import ( 

15 construct_lognormal_income_process_unemployment, 

16 get_PermShkDstn_from_IncShkDstn, 

17 get_TranShkDstn_from_IncShkDstn, 

18) 

19from HARK.ConsumptionSaving.ConsIndShockModel import ( 

20 ConsumerSolution, 

21 IndShockConsumerType, 

22 make_lognormal_kNrm_init_dstn, 

23 make_lognormal_pLvl_init_dstn, 

24) 

25from HARK.distributions import ( 

26 MarkovProcess, 

27 MeanOneLogNormal, 

28 Uniform, 

29 calc_expectation, 

30 combine_indep_dstns, 

31) 

32from HARK.interpolation import ( 

33 BilinearInterp, 

34 ConstantFunction, 

35 IdentityFunction, 

36 LinearInterp, 

37 LinearInterpOnInterp1D, 

38 LowerEnvelope2D, 

39 MargValueFuncCRRA, 

40 UpperEnvelope, 

41 VariableLowerBoundFunc2D, 

42) 

43from HARK.metric import MetricObject 

44from HARK.rewards import ( 

45 CRRAutility, 

46 CRRAutility_inv, 

47 CRRAutility_invP, 

48 CRRAutilityP, 

49 CRRAutilityP_inv, 

50 CRRAutilityPP, 

51) 

52from HARK.utilities import make_assets_grid, get_it_from, NullFunc 

53 

54__all__ = [ 

55 "AggShockConsumerType", 

56 "AggShockMarkovConsumerType", 

57 "CobbDouglasEconomy", 

58 "SmallOpenEconomy", 

59 "CobbDouglasMarkovEconomy", 

60 "SmallOpenMarkovEconomy", 

61 "AggregateSavingRule", 

62 "AggShocksDynamicRule", 

63 "init_agg_shocks", 

64 "init_agg_mrkv_shocks", 

65 "init_cobb_douglas", 

66 "init_mrkv_cobb_douglas", 

67] 

68 

69utility = CRRAutility 

70utilityP = CRRAutilityP 

71utilityPP = CRRAutilityPP 

72utilityP_inv = CRRAutilityP_inv 

73utility_invP = CRRAutility_invP 

74utility_inv = CRRAutility_inv 

75 

76 

77def make_aggshock_solution_terminal(CRRA): 

78 """ 

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

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

81 

82 Parameters 

83 ---------- 

84 CRRA : float 

85 Coefficient of relative risk aversion. 

86 

87 Returns 

88 ------- 

89 solution_terminal : ConsumerSolution 

90 Solution to the terminal period problem. 

91 """ 

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

93 vPfunc_terminal = MargValueFuncCRRA(cFunc_terminal, CRRA) 

94 mNrmMin_terminal = ConstantFunction(0) 

95 solution_terminal = ConsumerSolution( 

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

97 ) 

98 return solution_terminal 

99 

100 

101def make_aggmrkv_solution_terminal(CRRA, MrkvArray): 

102 """ 

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

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

105 value function. 

106 

107 Parameters 

108 ---------- 

109 CRRA : float 

110 Coefficient of relative risk aversion. 

111 MrkvArray : np.array 

112 Transition probability array. 

113 

114 Returns 

115 ------- 

116 solution_terminal : ConsumerSolution 

117 Solution to the terminal period problem. 

118 """ 

119 solution_terminal = make_aggshock_solution_terminal(CRRA) 

120 

121 # Make replicated terminal period solution 

122 StateCount = MrkvArray.shape[0] 

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

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

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

126 

127 return solution_terminal 

128 

129 

130def make_exponential_MgridBase(MaggCount, MaggPerturb, MaggExpFac): 

131 """ 

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

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

134 

135 Parameters 

136 ---------- 

137 MaggCount : int 

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

139 MaggPerturb : float 

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

141 1+perturb and 1-perturb. 

142 MaggExpFac : float 

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

144 

145 Returns 

146 ------- 

147 MgridBase : np.array 

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

149 """ 

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

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

152 fac = np.exp(MaggExpFac) 

153 for n in range(N - 1): 

154 new_hi = gridpoints[-1] * fac 

155 new_lo = gridpoints[0] / fac 

156 gridpoints.append(new_hi) 

157 gridpoints.insert(0, new_lo) 

158 MgridBase = np.array(gridpoints) 

159 return MgridBase 

160 

161 

162############################################################################### 

163 

164 

165def solveConsAggShock( 

166 solution_next, 

167 IncShkDstn, 

168 LivPrb, 

169 DiscFac, 

170 CRRA, 

171 PermGroFac, 

172 PermGroFacAgg, 

173 aXtraGrid, 

174 BoroCnstArt, 

175 Mgrid, 

176 AFunc, 

177 Rfunc, 

178 wFunc, 

179 DeprRte, 

180): 

181 """ 

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

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

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

185 version uses calc_expectation to reduce code clutter. 

186 

187 Parameters 

188 ---------- 

189 solution_next : ConsumerSolution 

190 The solution to the succeeding one period problem. 

191 IncShkDstn : distribution.Distribution 

192 A discrete 

193 approximation to the income process between the period being solved 

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

195 idiosyncratic permanent shocks, idiosyncratic transitory 

196 shocks, aggregate permanent shocks, aggregate transitory shocks. 

197 LivPrb : float 

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

199 the succeeding period. 

200 DiscFac : float 

201 Intertemporal discount factor for future utility. 

202 CRRA : float 

203 Coefficient of relative risk aversion. 

204 PermGroFac : float 

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

206 PermGroFacAgg : float 

207 Expected aggregate productivity growth factor. 

208 aXtraGrid : np.array 

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

210 absolute minimum acceptable level. 

211 BoroCnstArt : float 

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

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

214 Mgrid : np.array 

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

216 AFunc : function 

217 Aggregate savings as a function of aggregate market resources. 

218 Rfunc : function 

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

220 wFunc : function 

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

222 DeprRte : float 

223 Capital Depreciation Rate 

224 

225 Returns 

226 ------- 

227 solution_now : ConsumerSolution 

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

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

230 tions) and marginal value function vPfunc. 

231 """ 

232 # Unpack the income shocks and get grid sizes 

233 PermShkValsNext = IncShkDstn.atoms[0] 

234 TranShkValsNext = IncShkDstn.atoms[1] 

235 PermShkAggValsNext = IncShkDstn.atoms[2] 

236 TranShkAggValsNext = IncShkDstn.atoms[3] 

237 aCount = aXtraGrid.size 

238 Mcount = Mgrid.size 

239 

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

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

242 def calcAggObjects(M, Psi, Theta): 

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

244 # Next period's aggregate capital/labor ratio 

245 kNext = A / (PermGroFacAgg * Psi) 

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

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

248 wEff = ( 

249 wFunc(kNextEff) * Theta 

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

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

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

253 return Mnext, Reff, wEff 

254 

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

256 def vPnextFunc(S, a, M): 

257 psi = S[0] 

258 theta = S[1] 

259 Psi = S[2] 

260 Theta = S[3] 

261 

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

263 PermShkTotal = ( 

264 PermGroFac * PermGroFacAgg * psi * Psi 

265 ) # Total / combined permanent shock 

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

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

268 return vPnext 

269 

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

271 # Natural borrowing constraint at each M_t 

272 BoroCnstNat_vec = np.zeros(Mcount) 

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

274 for j in range(Mcount): 

275 Mnext, Reff, wEff = calcAggObjects( 

276 Mgrid[j], PermShkAggValsNext, TranShkAggValsNext 

277 ) 

278 aNrmMin_cand = ( 

279 PermGroFac * PermGroFacAgg * PermShkValsNext * PermShkAggValsNext / Reff 

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

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

282 aNrmNow[:, j] = aNrmMin + aXtraGrid 

283 BoroCnstNat_vec[j] = aNrmMin 

284 

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

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

287 EndOfPrdvP = ( 

288 DiscFac * LivPrb * calc_expectation(IncShkDstn, vPnextFunc, *(aNrmNow, MaggNow)) 

289 ) 

290 

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

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

293 mNrmNow = aNrmNow + cNrmNow 

294 

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

296 cFuncBaseByM_list = [] 

297 for j in range(Mcount): 

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

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

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

301 

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

303 BoroCnstNat = LinearInterp( 

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

305 ) 

306 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

307 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

308 

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

310 cFuncCnst = BilinearInterp( 

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

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

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

314 ) 

315 cFuncNow = LowerEnvelope2D(cFuncUnc, cFuncCnst) 

316 

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

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

319 

320 # Construct the marginal value function using the envelope condition 

321 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

322 

323 # Pack up and return the solution 

324 solution_now = ConsumerSolution( 

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

326 ) 

327 return solution_now 

328 

329 

330############################################################################### 

331 

332 

333def solve_ConsAggMarkov( 

334 solution_next, 

335 IncShkDstn, 

336 LivPrb, 

337 DiscFac, 

338 CRRA, 

339 MrkvArray, 

340 PermGroFac, 

341 PermGroFacAgg, 

342 aXtraGrid, 

343 BoroCnstArt, 

344 Mgrid, 

345 AFunc, 

346 Rfunc, 

347 wFunc, 

348): 

349 """ 

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

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

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

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

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

355 

356 Parameters 

357 ---------- 

358 solution_next : ConsumerSolution 

359 The solution to the succeeding one period problem. 

360 IncShkDstn : [distribution.Distribution] 

361 A list of 

362 discrete approximations to the income process between the period being 

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

364 idisyncratic permanent shocks, idiosyncratic transitory 

365 shocks, aggregate permanent shocks, aggregate transitory shocks. 

366 LivPrb : float 

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

368 the succeeding period. 

369 DiscFac : float 

370 Intertemporal discount factor for future utility. 

371 CRRA : float 

372 Coefficient of relative risk aversion. 

373 MrkvArray : np.array 

374 Markov transition matrix between discrete macroeconomic states. 

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

376 on being in state i this period. 

377 PermGroFac : float 

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

379 for the *individual*'s productivity. 

380 PermGroFacAgg : [float] 

381 Expected aggregate productivity growth in each Markov macro state. 

382 aXtraGrid : np.array 

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

384 absolute minimum acceptable level. 

385 BoroCnstArt : float 

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

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

388 Mgrid : np.array 

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

390 AFunc : [function] 

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

392 Markov macro state. 

393 Rfunc : function 

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

395 wFunc : function 

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

397 DeprRte : float 

398 Capital Depreciation Rate 

399 

400 Returns 

401 ------- 

402 solution_now : ConsumerSolution 

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

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

405 tions) and marginal value function vPfunc. 

406 """ 

407 # Get sizes of grids 

408 aCount = aXtraGrid.size 

409 Mcount = Mgrid.size 

410 StateCount = MrkvArray.shape[0] 

411 

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

413 # Construct EndOfPrdvP_cond functions for each state. 

414 EndOfPrdvPfunc_cond = [] 

415 BoroCnstNat_cond = [] 

416 for j in range(StateCount): 

417 # Unpack next period's solution 

418 vPfuncNext = solution_next.vPfunc[j] 

419 mNrmMinNext = solution_next.mNrmMin[j] 

420 

421 # Unpack the income shocks 

422 ShkPrbsNext = IncShkDstn[j].pmv 

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

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

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

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

427 ShkCount = ShkPrbsNext.size 

428 aXtra_tiled = np.tile( 

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

430 ) 

431 

432 # Make tiled versions of the income shocks 

433 # Dimension order: Mnow, aNow, Shk 

434 ShkPrbsNext_tiled = np.tile( 

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

436 ) 

437 PermShkValsNext_tiled = np.tile( 

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

439 ) 

440 TranShkValsNext_tiled = np.tile( 

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

442 ) 

443 PermShkAggValsNext_tiled = np.tile( 

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

445 ) 

446 TranShkAggValsNext_tiled = np.tile( 

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

448 ) 

449 

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

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

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

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

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

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

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

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

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

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

460 # conditional marginal value functions are constructed is not relevant 

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

462 AaggGrid = AFunc[j](Mgrid) 

463 AaggNow_tiled = np.tile( 

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

465 ) 

466 

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

468 kNext_array = AaggNow_tiled / ( 

469 PermGroFacAgg[j] * PermShkAggValsNext_tiled 

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

471 kNextEff_array = ( 

472 kNext_array / TranShkAggValsNext_tiled 

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

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

475 Reff_array = ( 

476 R_array / LivPrb 

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

478 wEff_array = ( 

479 wFunc(kNextEff_array) * TranShkAggValsNext_tiled 

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

481 PermShkTotal_array = ( 

482 PermGroFac 

483 * PermGroFacAgg[j] 

484 * PermShkValsNext_tiled 

485 * PermShkAggValsNext_tiled 

486 ) # total / combined permanent shock 

487 Mnext_array = ( 

488 kNext_array * R_array + wEff_array 

489 ) # next period's aggregate market resources 

490 

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

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

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

494 aNrmMin_candidates = ( 

495 PermGroFac 

496 * PermGroFacAgg[j] 

497 * PermShkValsNext_tiled[:, 0, :] 

498 * PermShkAggValsNext_tiled[:, 0, :] 

499 / Reff_array[:, 0, :] 

500 * ( 

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

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

503 ) 

504 ) 

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

506 BoroCnstNat_vec = aNrmMin_vec 

507 aNrmMin_tiled = np.tile( 

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

509 ) 

510 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

511 

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

513 mNrmNext_array = ( 

514 Reff_array * aNrmNow_tiled / PermShkTotal_array 

515 + TranShkValsNext_tiled * wEff_array 

516 ) 

517 

518 # Find marginal value next period at every income shock 

519 # realization and every aggregate market resource gridpoint 

520 vPnext_array = ( 

521 Reff_array 

522 * PermShkTotal_array ** (-CRRA) 

523 * vPfuncNext(mNrmNext_array, Mnext_array) 

524 ) 

525 

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

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

528 

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

530 BoroCnstNat = LinearInterp( 

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

532 ) 

533 EndOfPrdvPnvrs = np.concatenate( 

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

535 ) 

536 EndOfPrdvPnvrsFunc_base = BilinearInterp( 

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

538 ) 

539 EndOfPrdvPnvrsFunc = VariableLowerBoundFunc2D( 

540 EndOfPrdvPnvrsFunc_base, BoroCnstNat 

541 ) 

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

543 BoroCnstNat_cond.append(BoroCnstNat) 

544 

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

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

547 cFuncCnst = BilinearInterp( 

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

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

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

551 ) 

552 

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

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

555 # and marginal value function for each state. 

556 cFuncNow = [] 

557 vPfuncNow = [] 

558 mNrmMinNow = [] 

559 for i in range(StateCount): 

560 # Find natural borrowing constraint for this state by Aagg 

561 AaggNow = AFunc[i](Mgrid) 

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

563 for j in range(StateCount): 

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

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

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

567 BoroCnstNat_vec = aNrmMin_vec 

568 

569 # Make tiled grids of aNrm and Aagg 

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

571 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

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

573 

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

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

576 for j in range(StateCount): 

577 if MrkvArray[i, j] > 0.0: 

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

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

580 

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

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

583 mNrmNow = aNrmNow_tiled + cNrmNow 

584 

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

586 cFuncBaseByM_list = [] 

587 for n in range(Mcount): 

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

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

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

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

592 

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

594 BoroCnstNat = LinearInterp( 

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

596 ) 

597 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

598 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

599 

600 # Combine the constrained consumption function with unconstrained component 

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

602 

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

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

605 

606 # Construct the marginal value function using the envelope condition 

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

608 

609 # Pack up and return the solution 

610 solution_now = ConsumerSolution( 

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

612 ) 

613 return solution_now 

614 

615 

616############################################################################### 

617 

618 

619def solve_KrusellSmith( 

620 solution_next, 

621 DiscFac, 

622 CRRA, 

623 aGrid, 

624 Mgrid, 

625 mNextArray, 

626 MnextArray, 

627 ProbArray, 

628 RnextArray, 

629): 

630 """ 

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

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

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

634 default constructor functions for details. 

635 

636 Parameters 

637 ---------- 

638 solution_next : ConsumerSolution 

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

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

641 DiscFac : float 

642 Intertemporal discount factor. 

643 CRRA : float 

644 Coefficient of relative risk aversion. 

645 aGrid : np.array 

646 Array of end-of-period asset values. 

647 Mgrid : np.array 

648 A grid of aggregate market resources in the economy. 

649 mNextArray : np.array 

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

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

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

653 MnextArray : np.array 

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

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

656 shock that might attain. Corresponds to mNextArray. 

657 ProbArray : np.array 

658 Tiled array of transition probabilities among discrete states. Every 

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

660 RnextArray : np.array 

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

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

663 

664 Returns 

665 ------- 

666 solution_now : ConsumerSolution 

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

668 """ 

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

670 vPnext = np.zeros_like(mNextArray) 

671 for j in range(4): 

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

673 mNextArray[:, :, :, j], MnextArray[:, :, :, j] 

674 ) 

675 

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

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

678 

679 # Invert the first order condition to find optimal consumption 

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

681 

682 # Find the endogenous gridpoints 

683 aCount = aGrid.size 

684 Mcount = Mgrid.size 

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

686 mNow = aNow + cNow 

687 

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

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

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

691 

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

693 cFunc_by_state = [] 

694 vPfunc_by_state = [] 

695 for j in range(4): 

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

697 cFunc_j = LinearInterpOnInterp1D(cFunc_by_M, Mgrid) 

698 vPfunc_j = MargValueFuncCRRA(cFunc_j, CRRA) 

699 cFunc_by_state.append(cFunc_j) 

700 vPfunc_by_state.append(vPfunc_j) 

701 

702 # Package and return the solution 

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

704 return solution_now 

705 

706 

707############################################################################### 

708 

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

710aggshock_constructor_dict = { 

711 "IncShkDstn": construct_lognormal_income_process_unemployment, 

712 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

713 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

714 "aXtraGrid": make_assets_grid, 

715 "MgridBase": make_exponential_MgridBase, 

716 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

717 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

718 "solution_terminal": make_aggshock_solution_terminal, 

719} 

720 

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

722default_kNrmInitDstn_params = { 

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

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

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

726} 

727 

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

729default_pLvlInitDstn_params = { 

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

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

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

733} 

734 

735 

736# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

737default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

747} 

748 

749# Default parameters to make aXtraGrid using make_assets_grid 

750default_aXtraGrid_params = { 

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

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

753 "aXtraNestFac": 3, # Exponential nesting factor for aXtraGrid 

754 "aXtraCount": 24, # Number of points in the grid of "assets above minimum" 

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

756} 

757 

758# Default parameters to make MgridBase using make_exponential_MgridBase 

759default_MgridBase_params = { 

760 "MaggCount": 17, 

761 "MaggPerturb": 0.01, 

762 "MaggExpFac": 0.15, 

763} 

764 

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

766init_agg_shocks = { 

767 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

770 "constructors": aggshock_constructor_dict, # See dictionary above 

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

772 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

774 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

777 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

778 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

784 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

788} 

789init_agg_shocks.update(default_kNrmInitDstn_params) 

790init_agg_shocks.update(default_pLvlInitDstn_params) 

791init_agg_shocks.update(default_IncShkDstn_params) 

792init_agg_shocks.update(default_aXtraGrid_params) 

793init_agg_shocks.update(default_MgridBase_params) 

794 

795 

796class AggShockConsumerType(IndShockConsumerType): 

797 """ 

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

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

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

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

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

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

804 decision about how much to consume. 

805 """ 

806 

807 default_ = { 

808 "params": init_agg_shocks, 

809 "solver": solveConsAggShock, 

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

811 } 

812 time_inv_ = IndShockConsumerType.time_inv_.copy() 

813 try: 

814 time_inv_.remove("vFuncBool") 

815 time_inv_.remove("CubicBool") 

816 except: # pragma: nocover 

817 pass 

818 

819 def reset(self): 

820 """ 

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

822 

823 Parameters 

824 ---------- 

825 None 

826 

827 Returns 

828 ------- 

829 None 

830 """ 

831 self.initialize_sim() 

832 self.state_now["aLvlNow"] = self.kInit * np.ones( 

833 self.AgentCount 

834 ) # Start simulation near SS 

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

836 

837 def pre_solve(self): 

838 self.construct("solution_terminal") 

839 

840 def get_economy_data(self, economy): 

841 """ 

842 Imports economy-determined objects into self from a Market. 

843 Instances of AggShockConsumerType "live" in some macroeconomy that has 

844 attributes relevant to their microeconomic model, like the relationship 

845 between the capital-to-labor ratio and the interest and wage rates; this 

846 method imports those attributes from an "economy" object and makes them 

847 attributes of the ConsumerType. 

848 

849 Parameters 

850 ---------- 

851 economy : Market 

852 The "macroeconomy" in which this instance "lives". Might be of the 

853 subclass CobbDouglasEconomy, which has methods to generate the 

854 relevant attributes. 

855 

856 Returns 

857 ------- 

858 None 

859 """ 

860 self.T_sim = ( 

861 economy.act_T 

862 ) # Need to be able to track as many periods as economy runs 

863 self.kInit = economy.kSS # Initialize simulation assets to steady state 

864 self.aNrmInitMean = np.log( 

865 0.00000001 

866 ) # Initialize newborn assets to nearly zero 

867 self.Mgrid = ( 

868 economy.MSS * self.MgridBase 

869 ) # Aggregate market resources grid adjusted around SS capital ratio 

870 self.AFunc = economy.AFunc # Next period's aggregate savings function 

871 self.Rfunc = economy.Rfunc # Interest factor as function of capital ratio 

872 self.wFunc = economy.wFunc # Wage rate as function of capital ratio 

873 self.DeprRte = economy.DeprRte # Rate of capital depreciation 

874 self.PermGroFacAgg = ( 

875 economy.PermGroFacAgg 

876 ) # Aggregate permanent productivity growth 

877 self.add_AggShkDstn( 

878 economy.AggShkDstn 

879 ) # Combine idiosyncratic and aggregate shocks into one dstn 

880 self.add_to_time_inv( 

881 "Mgrid", "AFunc", "Rfunc", "wFunc", "DeprRte", "PermGroFacAgg" 

882 ) 

883 

884 def add_AggShkDstn(self, AggShkDstn): 

885 """ 

886 Updates attribute IncShkDstn by combining idiosyncratic shocks with aggregate shocks. 

887 

888 Parameters 

889 ---------- 

890 AggShkDstn : [np.array] 

891 Aggregate productivity shock distribution. First element is proba- 

892 bilities, second element is agg permanent shocks, third element is 

893 agg transitory shocks. 

894 

895 Returns 

896 ------- 

897 None 

898 """ 

899 if len(self.IncShkDstn[0].atoms) > 2: 

900 self.IncShkDstn = self.IncShkDstnWithoutAggShocks 

901 else: 

902 self.IncShkDstnWithoutAggShocks = self.IncShkDstn 

903 self.IncShkDstn = [ 

904 combine_indep_dstns( 

905 self.IncShkDstn[t], AggShkDstn, seed=self.RNG.integers(0, 2**31 - 1) 

906 ) 

907 for t in range(self.T_cycle) 

908 ] 

909 

910 def sim_birth(self, which_agents): 

911 """ 

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

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

914 

915 Parameters 

916 ---------- 

917 which_agents : np.array(Bool) 

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

919 

920 Returns 

921 ------- 

922 None 

923 """ 

924 IndShockConsumerType.sim_birth(self, which_agents) 

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

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

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

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

929 ) 

930 else: 

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

932 

933 def sim_death(self): 

934 """ 

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

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

937 

938 Parameters 

939 ---------- 

940 None 

941 

942 Returns 

943 ------- 

944 who_dies : np.array(bool) 

945 Boolean array of size AgentCount indicating which agents die. 

946 """ 

947 # Just select a random set of agents to die 

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

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

950 base_bool[0:how_many_die] = True 

951 who_dies = self.RNG.permutation(base_bool) 

952 if self.T_age is not None: 

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

954 

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

956 who_lives = np.logical_not(who_dies) 

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

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

959 Ractuarial = 1.0 + wealth_dead / wealth_living 

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

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

962 ) 

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

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

965 ) 

966 return who_dies 

967 

968 def get_Rport(self): 

969 """ 

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

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

972 

973 Parameters 

974 ---------- 

975 None 

976 

977 Returns 

978 ------- 

979 RfreeNow : np.array 

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

981 """ 

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

983 return RfreeNow 

984 

985 def get_shocks(self): 

986 """ 

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

988 and idiosyncratic shocks of each type. 

989 

990 Parameters 

991 ---------- 

992 None 

993 

994 Returns 

995 ------- 

996 None 

997 """ 

998 IndShockConsumerType.get_shocks(self) # Update idiosyncratic shocks 

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

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

1001 

1002 def get_controls(self): 

1003 """ 

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

1005 

1006 Parameters 

1007 ---------- 

1008 None 

1009 

1010 Returns 

1011 ------- 

1012 None 

1013 """ 

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

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

1016 MaggNow = self.get_MaggNow() 

1017 for t in range(self.T_cycle): 

1018 these = t == self.t_cycle 

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

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

1021 ) 

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

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

1024 ) # Marginal propensity to consume 

1025 

1026 self.controls["cNrm"] = cNrmNow 

1027 self.MPCnow = MPCnow 

1028 

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

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

1031 

1032 def market_action(self): 

1033 """ 

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

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

1036 

1037 Parameters 

1038 ---------- 

1039 None 

1040 

1041 Returns 

1042 ------- 

1043 None 

1044 """ 

1045 self.simulate(1) 

1046 

1047 def calc_bounding_values(self): # pragma: nocover 

1048 """ 

1049 NOT YET IMPLEMENTED FOR THIS CLASS 

1050 """ 

1051 raise NotImplementedError() 

1052 

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

1054 """ 

1055 NOT YET IMPLEMENTED FOR THIS CLASS 

1056 """ 

1057 raise NotImplementedError() 

1058 

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

1060 raise NotImplementedError() 

1061 

1062 def calc_limiting_values(self): # pragma: nocover 

1063 raise NotImplementedError() 

1064 

1065 

1066############################################################################### 

1067 

1068 

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

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

1071 

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

1073init_agg_mrkv_shocks = init_agg_shocks.copy() 

1074init_agg_mrkv_shocks["MrkvArray"] = MrkvArray 

1075aggmrkv_constructor_dict = aggshock_constructor_dict.copy() 

1076aggmrkv_constructor_dict["solution_terminal"] = make_aggmrkv_solution_terminal 

1077init_agg_mrkv_shocks["constructors"] = aggmrkv_constructor_dict 

1078 

1079 

1080class AggShockMarkovConsumerType(AggShockConsumerType): 

1081 """ 

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

1083 experience both aggregate and idiosyncratic shocks to productivity (both 

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

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

1086 """ 

1087 

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

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

1090 default_ = { 

1091 "params": init_agg_mrkv_shocks, 

1092 "solver": solve_ConsAggMarkov, 

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

1094 } 

1095 

1096 def add_AggShkDstn(self, AggShkDstn): 

1097 """ 

1098 Variation on AggShockConsumerType.add_AggShkDstn that handles the Markov 

1099 state. AggShkDstn is a list of aggregate productivity shock distributions 

1100 for each Markov state. 

1101 """ 

1102 if len(self.IncShkDstn[0][0].atoms) > 2: 

1103 self.IncShkDstn = self.IncShkDstnWithoutAggShocks 

1104 else: 

1105 self.IncShkDstnWithoutAggShocks = self.IncShkDstn 

1106 

1107 IncShkDstnOut = [] 

1108 N = self.MrkvArray.shape[0] 

1109 for t in range(self.T_cycle): 

1110 IncShkDstnOut.append( 

1111 [ 

1112 combine_indep_dstns( 

1113 self.IncShkDstn[t][n], 

1114 AggShkDstn[n], 

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

1116 ) 

1117 for n in range(N) 

1118 ] 

1119 ) 

1120 self.IncShkDstn = IncShkDstnOut 

1121 

1122 def initialize_sim(self): 

1123 self.shocks["Mrkv"] = 0 

1124 AggShockConsumerType.initialize_sim(self) 

1125 

1126 def get_shocks(self): 

1127 """ 

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

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

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

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

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

1133 

1134 Parameters 

1135 ---------- 

1136 None 

1137 

1138 Returns 

1139 ------- 

1140 None 

1141 """ 

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

1143 TranShkNow = np.zeros(self.AgentCount) 

1144 newborn = self.t_age == 0 

1145 for t in range(self.T_cycle): 

1146 these = t == self.t_cycle 

1147 N = np.sum(these) 

1148 if N > 0: 

1149 IncShkDstnNow = self.IncShkDstn[t - 1][ 

1150 self.shocks["Mrkv"] 

1151 ] # set current income distribution 

1152 # and permanent growth factor 

1153 PermGroFacNow = self.PermGroFac[t - 1] 

1154 

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

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

1157 # Permanent "shock" includes expected growth 

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

1159 TranShkNow[these] = ShockDraws[1] 

1160 

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

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

1163 N = np.sum(newborn) 

1164 if N > 0: 

1165 these = newborn 

1166 IncShkDstnNow = self.IncShkDstn[0][ 

1167 self.shocks["Mrkv"] 

1168 ] # set current income distribution 

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

1170 

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

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

1173 

1174 # Permanent "shock" includes expected growth 

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

1176 TranShkNow[these] = ShockDraws[1] 

1177 

1178 # Store the shocks in self 

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

1180 self.EmpNow[TranShkNow == self.IncUnemp] = False 

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

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

1183 

1184 def get_controls(self): 

1185 """ 

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

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

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

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

1190 

1191 Parameters 

1192 ---------- 

1193 None 

1194 

1195 Returns 

1196 ------- 

1197 None 

1198 """ 

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

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

1201 MaggNow = self.get_MaggNow() 

1202 MrkvNow = self.getMrkvNow() 

1203 

1204 StateCount = self.MrkvArray.shape[0] 

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

1206 for i in range(StateCount): 

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

1208 

1209 for t in range(self.T_cycle): 

1210 these = t == self.t_cycle 

1211 for i in range(StateCount): 

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

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

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

1215 ) 

1216 # Marginal propensity to consume 

1217 MPCnow[those] = ( 

1218 self.solution[t] 

1219 .cFunc[i] 

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

1221 ) 

1222 self.controls["cNrm"] = cNrmNow 

1223 self.MPCnow = MPCnow 

1224 return None 

1225 

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

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

1228 

1229 

1230############################################################################### 

1231 

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

1233 

1234 

1235def make_solution_terminal_KS(CRRA): 

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

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

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

1239 return solution_terminal 

1240 

1241 

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

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

1244 

1245 

1246def make_KS_transition_arrays( 

1247 aGrid, Mgrid, AFunc, LbrInd, UrateB, UrateG, ProdB, ProdG, MrkvIndArray 

1248): 

1249 """ 

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

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

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

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

1254 

1255 Parameters 

1256 ---------- 

1257 aGrid : np.array 

1258 Grid of end-of-period individual assets. 

1259 MGrid : np.array 

1260 Grid of aggregate market resources. 

1261 AFunc : function 

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

1263 LbrInd : float 

1264 Individual labor supply measure. 

1265 UrateB : float 

1266 Unemployment rate in the "bad" aggregate state. 

1267 UrateG : float 

1268 Unemployment rate in the "good" aggregate state. 

1269 ProdB : float 

1270 TFP in the "bad" aggregate state. 

1271 ProdG : float 

1272 TFP in the "good" aggregate state. 

1273 MrkvIndArray : np.array 

1274 Markov transition probabilities from the perspective of the individual. 

1275 

1276 Returns 

1277 ------- 

1278 ProbArray : np.array 

1279 Array of discrete future outcome probabilities. 

1280 mNextArray : np.array 

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

1282 MnextArray : np.array 

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

1284 RnextArray : np.array 

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

1286 """ 

1287 # Get array sizes 

1288 aCount = aGrid.size 

1289 Mcount = Mgrid.size 

1290 

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

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

1293 

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

1295 AnowB = AFunc[0](Mgrid) 

1296 AnowG = AFunc[1](Mgrid) 

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

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

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

1300 

1301 # Make arrays of aggregate labor and TFP next period 

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

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

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

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

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

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

1308 

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

1310 KtoLnext = Knext / Lnext 

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

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

1313 

1314 # Calculate aggregate market resources next period 

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

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

1317 

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

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

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

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

1322 

1323 # Make an array of idiosyncratic labor supply next period 

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

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

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

1327 

1328 # Calculate idiosyncratic market resources next period 

1329 mNext = Rnext_tiled * aNow_tiled + Wnext_tiled * lNext_tiled 

1330 

1331 # Make a tiled array of transition probabilities 

1332 Probs_tiled = np.tile( 

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

1334 ) 

1335 

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

1337 transition_arrays = { 

1338 "ProbArray": Probs_tiled, 

1339 "mNextArray": mNext, 

1340 "MnextArray": Mnext_tiled, 

1341 "RnextArray": Rnext_tiled, 

1342 } 

1343 return transition_arrays 

1344 

1345 

1346############################################################################### 

1347 

1348# Make a dictionary for Krusell-Smith agents 

1349KS_constructor_dict = { 

1350 "solution_terminal": make_solution_terminal_KS, 

1351 "aGrid": make_assets_grid_KS, 

1352 "transition_arrays": make_KS_transition_arrays, 

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

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

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

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

1357 "MgridBase": make_exponential_MgridBase, 

1358} 

1359 

1360init_KS_agents = { 

1361 "T_cycle": 1, 

1362 "pseudo_terminal": False, 

1363 "constructors": KS_constructor_dict, 

1364 "DiscFac": 0.99, 

1365 "CRRA": 1.0, 

1366 "LbrInd": 1.0, 

1367 "aMin": 0.001, 

1368 "aMax": 50.0, 

1369 "aCount": 32, 

1370 "aNestFac": 2, 

1371 "MaggCount": 25, 

1372 "MaggPerturb": 0.01, 

1373 "MaggExpFac": 0.12, 

1374 "MgridBase": np.array([0.99, 1.0, 1.01]), ## dummy, this will be overwritten 

1375 "AgentCount": 5000, 

1376} 

1377 

1378 

1379class KrusellSmithType(AgentType): 

1380 """ 

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

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

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

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

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

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

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

1388 

1389 To make this class work properly, instantiate both this class and an instance 

1390 of KrusellSmithEconomy, then use this class' get_economy_data method with the 

1391 economy object. 

1392 """ 

1393 

1394 time_inv_ = [ 

1395 "DiscFac", 

1396 "CRRA", 

1397 "aGrid", 

1398 "ProbArray", 

1399 "mNextArray", 

1400 "MnextArray", 

1401 "RnextArray", 

1402 ] 

1403 time_vary_ = [] 

1404 shock_vars_ = ["Mrkv"] 

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

1406 default_ = { 

1407 "params": init_KS_agents, 

1408 "solver": solve_KrusellSmith, 

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

1410 } 

1411 

1412 def __init__(self, **kwds): 

1413 temp = kwds.copy() 

1414 temp["construct"] = False 

1415 AgentType.__init__(self, **temp) 

1416 self.construct("MgridBase") 

1417 

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

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

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

1421 # To make it work properly, instantiate both this class and an instance 

1422 # of KrusellSmithEconomy, then use this class' get_economy_data method. 

1423 # Exception: MgridBase must exist 

1424 

1425 def pre_solve(self): 

1426 self.construct("solution_terminal") 

1427 

1428 def get_economy_data(self, Economy): 

1429 """ 

1430 Imports economy-determined objects into self from a Market. 

1431 

1432 Parameters 

1433 ---------- 

1434 Economy : KrusellSmithEconomy 

1435 The "macroeconomy" in which this instance "lives". 

1436 

1437 Returns 

1438 ------- 

1439 None 

1440 """ 

1441 self.T_sim = ( 

1442 Economy.act_T 

1443 ) # Need to be able to track as many periods as economy runs 

1444 self.kInit = Economy.KSS # Initialize simulation assets to steady state 

1445 self.MrkvInit = Economy.sow_init[ 

1446 "Mrkv" 

1447 ] # Starting Markov state for the macroeconomy 

1448 self.Mgrid = ( 

1449 Economy.MSS * self.MgridBase 

1450 ) # Aggregate market resources grid adjusted around SS capital ratio 

1451 self.AFunc = Economy.AFunc # Next period's aggregate savings function 

1452 self.DeprRte = Economy.DeprRte # Rate of capital depreciation 

1453 self.CapShare = Economy.CapShare # Capital's share of production 

1454 # Idiosyncratic labor supply (when employed) 

1455 self.LbrInd = Economy.LbrInd 

1456 self.UrateB = Economy.UrateB # Unemployment rate in bad state 

1457 self.UrateG = Economy.UrateG # Unemployment rate in good state 

1458 self.ProdB = Economy.ProdB # Total factor productivity in bad state 

1459 self.ProdG = Economy.ProdG # Total factor productivity in good state 

1460 self.MrkvIndArray = ( 

1461 Economy.MrkvIndArray 

1462 ) # Transition probabilities among discrete states 

1463 self.MrkvAggArray = ( 

1464 Economy.MrkvArray 

1465 ) # Transition probabilities among aggregate discrete states 

1466 self.add_to_time_inv( 

1467 "Mgrid", 

1468 "AFunc", 

1469 "DeprRte", 

1470 "CapShare", 

1471 "UrateB", 

1472 "LbrInd", 

1473 "UrateG", 

1474 "ProdB", 

1475 "ProdG", 

1476 "MrkvIndArray", 

1477 "MrkvAggArray", 

1478 ) 

1479 

1480 def make_emp_idx_arrays(self): 

1481 """ 

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

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

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

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

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

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

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

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

1490 maintain exact unemployment rates in each period. 

1491 """ 

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

1493 B_unemp_N = int(np.round(self.UrateB * self.AgentCount)) 

1494 B_emp_N = self.AgentCount - B_unemp_N 

1495 G_unemp_N = int(np.round(self.UrateG * self.AgentCount)) 

1496 G_emp_N = self.AgentCount - G_unemp_N 

1497 

1498 # Bad-bad transition indices 

1499 BB_stay_unemp_N = int( 

1500 np.round(B_unemp_N * self.MrkvIndArray[0, 0] / self.MrkvAggArray[0, 0]) 

1501 ) 

1502 BB_become_unemp_N = B_unemp_N - BB_stay_unemp_N 

1503 BB_stay_emp_N = int( 

1504 np.round(B_emp_N * self.MrkvIndArray[1, 1] / self.MrkvAggArray[0, 0]) 

1505 ) 

1506 BB_become_emp_N = B_emp_N - BB_stay_emp_N 

1507 BB_unemp_permute = np.concatenate( 

1508 [ 

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

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

1511 ] 

1512 ) 

1513 BB_emp_permute = np.concatenate( 

1514 [ 

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

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

1517 ] 

1518 ) 

1519 

1520 # Bad-good transition indices 

1521 BG_stay_unemp_N = int( 

1522 np.round(B_unemp_N * self.MrkvIndArray[0, 2] / self.MrkvAggArray[0, 1]) 

1523 ) 

1524 BG_become_unemp_N = G_unemp_N - BG_stay_unemp_N 

1525 BG_stay_emp_N = int( 

1526 np.round(B_emp_N * self.MrkvIndArray[1, 3] / self.MrkvAggArray[0, 1]) 

1527 ) 

1528 BG_become_emp_N = G_emp_N - BG_stay_emp_N 

1529 BG_unemp_permute = np.concatenate( 

1530 [ 

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

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

1533 ] 

1534 ) 

1535 BG_emp_permute = np.concatenate( 

1536 [ 

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

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

1539 ] 

1540 ) 

1541 

1542 # Good-bad transition indices 

1543 GB_stay_unemp_N = int( 

1544 np.round(G_unemp_N * self.MrkvIndArray[2, 0] / self.MrkvAggArray[1, 0]) 

1545 ) 

1546 GB_become_unemp_N = B_unemp_N - GB_stay_unemp_N 

1547 GB_stay_emp_N = int( 

1548 np.round(G_emp_N * self.MrkvIndArray[3, 1] / self.MrkvAggArray[1, 0]) 

1549 ) 

1550 GB_become_emp_N = B_emp_N - GB_stay_emp_N 

1551 GB_unemp_permute = np.concatenate( 

1552 [ 

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

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

1555 ] 

1556 ) 

1557 GB_emp_permute = np.concatenate( 

1558 [ 

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

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

1561 ] 

1562 ) 

1563 

1564 # Good-good transition indices 

1565 GG_stay_unemp_N = int( 

1566 np.round(G_unemp_N * self.MrkvIndArray[2, 2] / self.MrkvAggArray[1, 1]) 

1567 ) 

1568 GG_become_unemp_N = G_unemp_N - GG_stay_unemp_N 

1569 GG_stay_emp_N = int( 

1570 np.round(G_emp_N * self.MrkvIndArray[3, 3] / self.MrkvAggArray[1, 1]) 

1571 ) 

1572 GG_become_emp_N = G_emp_N - GG_stay_emp_N 

1573 GG_unemp_permute = np.concatenate( 

1574 [ 

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

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

1577 ] 

1578 ) 

1579 GG_emp_permute = np.concatenate( 

1580 [ 

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

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

1583 ] 

1584 ) 

1585 

1586 # Store transition matrices as attributes of self 

1587 self.unemp_permute = [ 

1588 [BB_unemp_permute, BG_unemp_permute], 

1589 [GB_unemp_permute, GG_unemp_permute], 

1590 ] 

1591 self.emp_permute = [ 

1592 [BB_emp_permute, BG_emp_permute], 

1593 [GB_emp_permute, GG_emp_permute], 

1594 ] 

1595 

1596 def reset(self): 

1597 self.initialize_sim() 

1598 

1599 def market_action(self): 

1600 self.simulate(1) 

1601 

1602 def initialize_sim(self): 

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

1604 AgentType.initialize_sim(self) 

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

1606 self.make_emp_idx_arrays() 

1607 

1608 def sim_birth(self, which): 

1609 """ 

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

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

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

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

1614 is the correct behavior for the model. 

1615 """ 

1616 N = np.sum(which) 

1617 if N == 0: 

1618 return 

1619 

1620 if self.shocks["Mrkv"] == 0: 

1621 unemp_N = int(np.round(self.UrateB * N)) 

1622 emp_N = self.AgentCount - unemp_N 

1623 elif self.shocks["Mrkv"] == 1: 

1624 unemp_N = int(np.round(self.UrateG * N)) 

1625 emp_N = self.AgentCount - unemp_N 

1626 else: 

1627 assert False, "Illegal macroeconomic state: MrkvNow must be 0 or 1" 

1628 EmpNew = np.concatenate( 

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

1630 ) 

1631 

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

1633 self.state_now["aNow"][which] = self.kInit 

1634 

1635 def get_shocks(self): 

1636 """ 

1637 Get new idiosyncratic employment states based on the macroeconomic state. 

1638 """ 

1639 # Get boolean arrays for current employment states 

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

1641 unemployed = np.logical_not(employed) 

1642 

1643 # derive from past employment rate rather than store previous value 

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

1645 

1646 # Transition some agents between unemployment and employment 

1647 emp_permute = self.emp_permute[mrkv_prev][self.shocks["Mrkv"]] 

1648 unemp_permute = self.unemp_permute[mrkv_prev][self.shocks["Mrkv"]] 

1649 # TODO: replace poststate_vars functionality with shocks here 

1650 EmpNow = self.state_now["EmpNow"] 

1651 

1652 # It's really this permutation that is the shock... 

1653 # This apparatus is trying to 'exact match' the 'internal' Markov process. 

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

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

1656 

1657 def get_states(self): 

1658 """ 

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

1660 """ 

1661 self.state_now["mNow"] = ( 

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

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

1664 ) 

1665 

1666 def get_controls(self): 

1667 """ 

1668 Get each agent's consumption given their current state.' 

1669 """ 

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

1671 unemployed = np.logical_not(employed) 

1672 

1673 # Get the discrete index for (un)employed agents 

1674 if self.shocks["Mrkv"] == 0: # Bad macroeconomic conditions 

1675 unemp_idx = 0 

1676 emp_idx = 1 

1677 elif self.shocks["Mrkv"] == 1: # Good macroeconomic conditions 

1678 unemp_idx = 2 

1679 emp_idx = 3 

1680 else: 

1681 assert False, "Illegal macroeconomic state: MrkvNow must be 0 or 1" 

1682 

1683 # Get consumption for each agent using the appropriate consumption function 

1684 cNow = np.zeros(self.AgentCount) 

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

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

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

1688 ) 

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

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

1691 ) 

1692 self.controls["cNow"] = cNow 

1693 

1694 def get_poststates(self): 

1695 """ 

1696 Gets each agent's retained assets after consumption. 

1697 """ 

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

1699 

1700 

1701CRRA = 2.0 

1702DiscFac = 0.96 

1703 

1704# Parameters for a Cobb-Douglas economy 

1705PermGroFacAgg = 1.00 # Aggregate permanent income growth factor 

1706PermShkAggCount = ( 

1707 3 # Number of points in discrete approximation to aggregate permanent shock dist 

1708) 

1709TranShkAggCount = ( 

1710 3 # Number of points in discrete approximation to aggregate transitory shock dist 

1711) 

1712PermShkAggStd = 0.0063 # Standard deviation of log aggregate permanent shocks 

1713TranShkAggStd = 0.0031 # Standard deviation of log aggregate transitory shocks 

1714DeprRte = 0.025 # Capital depreciation rate 

1715CapShare = 0.36 # Capital's share of income 

1716DiscFacPF = DiscFac # Discount factor of perfect foresight calibration 

1717CRRAPF = CRRA # Coefficient of relative risk aversion of perfect foresight calibration 

1718intercept_prev = 0.0 # Intercept of aggregate savings function 

1719slope_prev = 1.0 # Slope of aggregate savings function 

1720verbose_cobb_douglas = ( 

1721 True # Whether to print solution progress to screen while solving 

1722) 

1723T_discard = 200 # Number of simulated "burn in" periods to discard when updating AFunc 

1724# Damping factor when updating AFunc; puts DampingFac weight on old params, rest on new 

1725DampingFac = 0.5 

1726max_loops = 20 # Maximum number of AFunc updating loops to allow 

1727 

1728 

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

1730init_cobb_douglas = { 

1731 "PermShkAggCount": PermShkAggCount, 

1732 "TranShkAggCount": TranShkAggCount, 

1733 "PermShkAggStd": PermShkAggStd, 

1734 "TranShkAggStd": TranShkAggStd, 

1735 "DeprRte": DeprRte, 

1736 "CapShare": CapShare, 

1737 "DiscFac": DiscFacPF, 

1738 "CRRA": CRRAPF, 

1739 "PermGroFacAgg": PermGroFacAgg, 

1740 "AggregateL": 1.0, 

1741 "intercept_prev": intercept_prev, 

1742 "slope_prev": slope_prev, 

1743 "verbose": verbose_cobb_douglas, 

1744 "T_discard": T_discard, 

1745 "DampingFac": DampingFac, 

1746 "max_loops": max_loops, 

1747} 

1748 

1749 

1750class CobbDouglasEconomy(Market): 

1751 """ 

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

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

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

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

1756 rate for the upcoming period. 

1757 

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

1759 this will be generalized in the future. 

1760 

1761 Parameters 

1762 ---------- 

1763 agents : [ConsumerType] 

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

1765 tolerance: float 

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

1767 solution process converged. Distance depends on intercept and slope 

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

1769 act_T : int 

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

1771 """ 

1772 

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

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

1775 params = init_cobb_douglas.copy() 

1776 params["sow_vars"] = [ 

1777 "MaggNow", 

1778 "AaggNow", 

1779 "RfreeNow", 

1780 "wRteNow", 

1781 "PermShkAggNow", 

1782 "TranShkAggNow", 

1783 "KtoLnow", 

1784 ] 

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

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

1787 params["dyn_vars"] = ["AFunc"] 

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

1789 params.update(kwds) 

1790 

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

1792 self.update() 

1793 

1794 def mill_rule(self, aLvl, pLvl): 

1795 """ 

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

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

1798 

1799 See documentation for calc_R_and_W for more information. 

1800 """ 

1801 return self.calc_R_and_W(aLvl, pLvl) 

1802 

1803 def calc_dynamics(self, MaggNow, AaggNow): 

1804 """ 

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

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

1807 

1808 See documentation for calc_AFunc for more information. 

1809 """ 

1810 return self.calc_AFunc(MaggNow, AaggNow) 

1811 

1812 def update(self): 

1813 """ 

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

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

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

1817 

1818 Parameters 

1819 ---------- 

1820 None 

1821 

1822 Returns 

1823 ------- 

1824 None 

1825 """ 

1826 self.kSS = ( 

1827 ( 

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

1829 - (1.0 - self.DeprRte) 

1830 ) 

1831 / self.CapShare 

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

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

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

1835 self.RfreeSS = ( 

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

1837 ) 

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

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

1840 1.0 / (1.0 - self.CapShare) 

1841 ) # converts K/Y to K/L 

1842 self.Rfunc = lambda k: ( 

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

1844 ) 

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

1846 

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

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

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

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

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

1852 self.sow_init["PermShkAggNow"] = 1.0 

1853 self.sow_init["TranShkAggNow"] = 1.0 

1854 self.make_AggShkDstn() 

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

1856 

1857 def get_PermGroFacAggLR(self): 

1858 """ 

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

1860 and extended by ConsAggShockMarkov model. 

1861 

1862 Parameters 

1863 ---------- 

1864 None 

1865 

1866 Returns 

1867 ------- 

1868 PermGroFacAggLR : float 

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

1870 as aggregate permanent income growth. 

1871 """ 

1872 return self.PermGroFacAgg 

1873 

1874 def make_AggShkDstn(self): 

1875 """ 

1876 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

1878 

1879 Parameters 

1880 ---------- 

1881 None 

1882 

1883 Returns 

1884 ------- 

1885 None 

1886 """ 

1887 RNG = self.RNG 

1888 TranShkAggDstn = MeanOneLogNormal( 

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

1890 ) 

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

1892 PermShkAggDstn = MeanOneLogNormal( 

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

1894 ) 

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

1896 self.AggShkDstn = combine_indep_dstns( 

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

1898 ) 

1899 

1900 def reset(self): 

1901 """ 

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

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

1904 

1905 Parameters 

1906 ---------- 

1907 None 

1908 

1909 Returns 

1910 ------- 

1911 None 

1912 """ 

1913 self.Shk_idx = 0 

1914 Market.reset(self) 

1915 

1916 def make_AggShkHist(self): 

1917 """ 

1918 Make simulated histories of aggregate transitory and permanent shocks. 

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

1920 simulation. 

1921 

1922 Parameters 

1923 ---------- 

1924 None 

1925 

1926 Returns 

1927 ------- 

1928 None 

1929 """ 

1930 sim_periods = self.act_T 

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

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

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

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

1935 

1936 # Store the histories 

1937 self.PermShkAggHist = PermShkAggHist * self.PermGroFacAgg 

1938 self.TranShkAggHist = TranShkAggHist 

1939 

1940 def calc_R_and_W(self, aLvlNow, pLvlNow): 

1941 """ 

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

1943 capital stock to get the aggregate capital ratio. 

1944 

1945 Parameters 

1946 ---------- 

1947 aLvlNow : [np.array] 

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

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

1950 

1951 Returns 

1952 ------- 

1953 MaggNow : float 

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

1955 AaggNow : float 

1956 Aggregate savings for this period normalized by mean permanent income 

1957 RfreeNow : float 

1958 Interest factor on assets in the economy this period. 

1959 wRteNow : float 

1960 Wage rate for labor in the economy this period. 

1961 PermShkAggNow : float 

1962 Permanent shock to aggregate labor productivity this period. 

1963 TranShkAggNow : float 

1964 Transitory shock to aggregate labor productivity this period. 

1965 KtoLnow : float 

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

1967 """ 

1968 # Calculate aggregate savings 

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

1970 pLvlNow 

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

1972 # Calculate aggregate capital this period 

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

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

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

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

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

1978 

1979 # Get this period's aggregate shocks 

1980 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

1981 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

1982 self.Shk_idx += 1 

1983 

1984 AggregateL = np.mean(pLvlNow) * PermShkAggNow 

1985 

1986 # Calculate the interest factor and wage rate this period 

1987 KtoLnow = AggregateK / AggregateL 

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

1989 RfreeNow = self.Rfunc(KtoLnow / TranShkAggNow) 

1990 wRteNow = self.wFunc(KtoLnow / TranShkAggNow) 

1991 MaggNow = KtoLnow * RfreeNow + wRteNow * TranShkAggNow 

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

1993 

1994 # Package the results into an object and return it 

1995 return ( 

1996 MaggNow, 

1997 AaggPrev, 

1998 RfreeNow, 

1999 wRteNow, 

2000 PermShkAggNow, 

2001 TranShkAggNow, 

2002 KtoLnow, 

2003 ) 

2004 

2005 def calc_AFunc(self, MaggNow, AaggNow): 

2006 """ 

2007 Calculate a new aggregate savings rule based on the history 

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

2009 

2010 Parameters 

2011 ---------- 

2012 MaggNow : [float] 

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

2014 AaggNow : [float] 

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

2016 

2017 Returns 

2018 ------- 

2019 (unnamed) : CapDynamicRule 

2020 Object containing a new savings rule 

2021 """ 

2022 verbose = self.verbose 

2023 discard_periods = ( 

2024 self.T_discard 

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

2026 update_weight = ( 

2027 1.0 - self.DampingFac 

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

2029 total_periods = len(MaggNow) 

2030 

2031 # Regress the log savings against log market resources 

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

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

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

2035 

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

2037 # with the previous guess 

2038 intercept = ( 

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

2040 ) 

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

2042 AFunc = AggregateSavingRule( 

2043 intercept, slope 

2044 ) # Make a new next-period capital function 

2045 

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

2047 self.intercept_prev = intercept 

2048 self.slope_prev = slope 

2049 

2050 # Print the new parameters 

2051 if verbose: 

2052 print( 

2053 "intercept=" 

2054 + str(intercept) 

2055 + ", slope=" 

2056 + str(slope) 

2057 + ", r-sq=" 

2058 + str(r_value**2) 

2059 ) 

2060 

2061 return AggShocksDynamicRule(AFunc) 

2062 

2063 

2064class SmallOpenEconomy(Market): 

2065 """ 

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

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

2068 aggregate productivity shocks. 

2069 

2070 Parameters 

2071 ---------- 

2072 agents : [ConsumerType] 

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

2074 tolerance: float 

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

2076 solution process converged. Distance depends on intercept and slope 

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

2078 act_T : int 

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

2080 """ 

2081 

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

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

2084 Market.__init__( 

2085 self, 

2086 agents=agents, 

2087 sow_vars=[ 

2088 "MaggNow", 

2089 "AaggNow", 

2090 "RfreeNow", 

2091 "wRteNow", 

2092 "PermShkAggNow", 

2093 "TranShkAggNow", 

2094 "KtoLnow", 

2095 ], 

2096 reap_vars=[], 

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

2098 dyn_vars=[], 

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

2100 tolerance=tolerance, 

2101 act_T=act_T, 

2102 ) 

2103 self.assign_parameters(**kwds) 

2104 self.update() 

2105 

2106 def update(self): 

2107 """ 

2108 Use primitive parameters to set basic objects. 

2109 This is an extremely stripped-down version 

2110 of update for CobbDouglasEconomy. 

2111 

2112 Parameters 

2113 ---------- 

2114 none 

2115 

2116 Returns 

2117 ------- 

2118 none 

2119 """ 

2120 self.kSS = 1.0 

2121 self.MSS = 1.0 

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

2123 self.Rfunc = ConstantFunction(self.Rfree) 

2124 self.wFunc = ConstantFunction(self.wRte) 

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

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

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

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

2129 self.sow_init["PermShkAggNow"] = 1.0 

2130 self.sow_init["TranShkAggNow"] = 1.0 

2131 self.make_AggShkDstn() 

2132 self.AFunc = ConstantFunction(1.0) 

2133 

2134 def make_AggShkDstn(self): 

2135 """ 

2136 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

2138 

2139 Parameters 

2140 ---------- 

2141 None 

2142 

2143 Returns 

2144 ------- 

2145 None 

2146 """ 

2147 RNG = self.RNG 

2148 TranShkAggDstn = MeanOneLogNormal( 

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

2150 ) 

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

2152 PermShkAggDstn = MeanOneLogNormal( 

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

2154 ) 

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

2156 self.AggShkDstn = combine_indep_dstns( 

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

2158 ) 

2159 

2160 def mill_rule(self): 

2161 """ 

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

2163 exogenously determined. However, aggregate shocks may occur. 

2164 

2165 See documentation for get_AggShocks() for more information. 

2166 """ 

2167 return self.get_AggShocks() 

2168 

2169 def calc_dynamics(self, KtoLnow): 

2170 """ 

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

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

2173 """ 

2174 return MetricObject() 

2175 

2176 def reset(self): 

2177 """ 

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

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

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

2181 

2182 Parameters 

2183 ---------- 

2184 None 

2185 

2186 Returns 

2187 ------- 

2188 None 

2189 """ 

2190 self.Shk_idx = 0 

2191 Market.reset(self) 

2192 

2193 def make_AggShkHist(self): 

2194 """ 

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

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

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

2198 

2199 Parameters 

2200 ---------- 

2201 None 

2202 

2203 Returns 

2204 ------- 

2205 None 

2206 """ 

2207 sim_periods = self.act_T 

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

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

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

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

2212 

2213 # Store the histories 

2214 self.PermShkAggHist = PermShkAggHist 

2215 self.TranShkAggHist = TranShkAggHist 

2216 

2217 def get_AggShocks(self): 

2218 """ 

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

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

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

2222 

2223 Parameters 

2224 ---------- 

2225 None 

2226 

2227 Returns 

2228 ------- 

2229 MaggNow : float 

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

2231 AaggNow : float 

2232 Aggregate savings for this period normalized by mean permanent income 

2233 RfreeNow : float 

2234 Interest factor on assets in the economy this period. 

2235 wRteNow : float 

2236 Wage rate for labor in the economy this period. 

2237 PermShkAggNow : float 

2238 Permanent shock to aggregate labor productivity this period. 

2239 TranShkAggNow : float 

2240 Transitory shock to aggregate labor productivity this period. 

2241 KtoLnow : float 

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

2243 

2244 """ 

2245 # Get this period's aggregate shocks 

2246 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

2247 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

2248 self.Shk_idx += 1 

2249 

2250 # Factor prices are constant 

2251 RfreeNow = self.Rfunc(1.0 / TranShkAggNow) 

2252 wRteNow = self.wFunc(1.0 / TranShkAggNow) 

2253 

2254 # Aggregates are irrelavent 

2255 AaggNow = 1.0 

2256 MaggNow = 1.0 

2257 KtoLnow = 1.0 / PermShkAggNow 

2258 

2259 return ( 

2260 MaggNow, 

2261 AaggNow, 

2262 RfreeNow, 

2263 wRteNow, 

2264 PermShkAggNow, 

2265 TranShkAggNow, 

2266 KtoLnow, 

2267 ) 

2268 

2269 

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

2271init_mrkv_cobb_douglas = init_cobb_douglas.copy() 

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

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

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

2275init_mrkv_cobb_douglas["MrkvArray"] = MrkvArray 

2276init_mrkv_cobb_douglas["MrkvNow_init"] = 0 

2277init_mrkv_cobb_douglas["slope_prev"] = 2 * [slope_prev] 

2278init_mrkv_cobb_douglas["intercept_prev"] = 2 * [intercept_prev] 

2279 

2280 

2281class CobbDouglasMarkovEconomy(CobbDouglasEconomy): 

2282 """ 

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

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

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

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

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

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

2289 productivity growth factor can vary over time. 

2290 

2291 Parameters 

2292 ---------- 

2293 agents : [ConsumerType] 

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

2295 tolerance: float 

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

2297 solution process converged. Distance depends on intercept and slope 

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

2299 act_T : int 

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

2301 """ 

2302 

2303 def __init__( 

2304 self, 

2305 agents=None, 

2306 tolerance=0.0001, 

2307 act_T=1200, 

2308 **kwds, 

2309 ): 

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

2311 params = init_mrkv_cobb_douglas.copy() 

2312 params.update(kwds) 

2313 

2314 CobbDouglasEconomy.__init__( 

2315 self, 

2316 agents=agents, 

2317 tolerance=tolerance, 

2318 act_T=act_T, 

2319 sow_vars=[ 

2320 "MaggNow", 

2321 "AaggNow", 

2322 "RfreeNow", 

2323 "wRteNow", 

2324 "PermShkAggNow", 

2325 "TranShkAggNow", 

2326 "KtoLnow", 

2327 "Mrkv", # This one is new 

2328 ], 

2329 **params, 

2330 ) 

2331 

2332 self.sow_init["Mrkv"] = params["MrkvNow_init"] 

2333 

2334 def update(self): 

2335 """ 

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

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

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

2339 

2340 Parameters 

2341 ---------- 

2342 None 

2343 

2344 Returns 

2345 ------- 

2346 None 

2347 """ 

2348 CobbDouglasEconomy.update(self) 

2349 StateCount = self.MrkvArray.shape[0] 

2350 AFunc_all = [] 

2351 for i in range(StateCount): 

2352 AFunc_all.append( 

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

2354 ) 

2355 self.AFunc = AFunc_all 

2356 

2357 def get_PermGroFacAggLR(self): 

2358 """ 

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

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

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

2362 

2363 Parameters 

2364 ---------- 

2365 None 

2366 

2367 Returns 

2368 ------- 

2369 PermGroFacAggLR : float 

2370 Long run aggregate permanent income growth factor 

2371 """ 

2372 # Find the long run distribution of Markov states 

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

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

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

2376 LR_dstn = x / np.sum(x) 

2377 

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

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

2380 return PermGroFacAggLR 

2381 

2382 def make_AggShkDstn(self): 

2383 """ 

2384 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

2386 This version accounts for the Markov macroeconomic state. 

2387 

2388 Parameters 

2389 ---------- 

2390 None 

2391 

2392 Returns 

2393 ------- 

2394 None 

2395 """ 

2396 TranShkAggDstn = [] 

2397 PermShkAggDstn = [] 

2398 AggShkDstn = [] 

2399 StateCount = self.MrkvArray.shape[0] 

2400 RNG = self.RNG 

2401 

2402 for i in range(StateCount): 

2403 TranShkAggDstn.append( 

2404 MeanOneLogNormal( 

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

2406 ).discretize( 

2407 N=self.TranShkAggCount, 

2408 ) 

2409 ) 

2410 PermShkAggDstn.append( 

2411 MeanOneLogNormal( 

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

2413 ).discretize( 

2414 N=self.PermShkAggCount, 

2415 ) 

2416 ) 

2417 AggShkDstn.append( 

2418 combine_indep_dstns( 

2419 PermShkAggDstn[-1], 

2420 TranShkAggDstn[-1], 

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

2422 ) 

2423 ) 

2424 

2425 self.TranShkAggDstn = TranShkAggDstn 

2426 self.PermShkAggDstn = PermShkAggDstn 

2427 self.AggShkDstn = AggShkDstn 

2428 

2429 def make_AggShkHist(self): 

2430 """ 

2431 Make simulated histories of aggregate transitory and permanent shocks. 

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

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

2434 internal call to make_Mrkv_history(). 

2435 

2436 Parameters 

2437 ---------- 

2438 None 

2439 

2440 Returns 

2441 ------- 

2442 None 

2443 """ 

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

2445 sim_periods = self.act_T 

2446 

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

2448 # that would occur in that state in that period 

2449 StateCount = self.MrkvArray.shape[0] 

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

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

2452 for i in range(StateCount): 

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

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

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

2456 

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

2458 # of Markov states that the economy experiences 

2459 PermShkAggHist = np.zeros(sim_periods) 

2460 TranShkAggHist = np.zeros(sim_periods) 

2461 for i in range(StateCount): 

2462 these = i == self.MrkvNow_hist 

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

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

2465 

2466 # Store the histories 

2467 self.PermShkAggHist = PermShkAggHist 

2468 self.TranShkAggHist = TranShkAggHist 

2469 

2470 def make_Mrkv_history(self): 

2471 """ 

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

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

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

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

2476 initially specified level. 

2477 

2478 Parameters 

2479 ---------- 

2480 None 

2481 

2482 Returns 

2483 ------- 

2484 None 

2485 """ 

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

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

2488 logit_scale = ( 

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

2490 ) 

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

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

2493 

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

2495 if hasattr(self, "act_T_orig"): 

2496 act_T = self.act_T_orig 

2497 else: # Or store it for the first time 

2498 self.act_T_orig = self.act_T 

2499 act_T = self.act_T 

2500 

2501 # Find the long run distribution of Markov states 

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

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

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

2505 LR_dstn = x / np.sum(x) 

2506 

2507 # Initialize the Markov history and set up transitions 

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

2509 loops = 0 

2510 go = True 

2511 MrkvNow = self.sow_init["Mrkv"] 

2512 t = 0 

2513 StateCount = self.MrkvArray.shape[0] 

2514 

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

2516 while go: 

2517 draws = Uniform(seed=loops).draw(N=self.act_T_orig) 

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

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

2520 MrkvNow_hist[t] = MrkvNow 

2521 MrkvNow = markov_process.draw(MrkvNow) 

2522 t += 1 

2523 

2524 # Calculate the empirical distribution 

2525 state_T = np.zeros(StateCount) 

2526 for i in range(StateCount): 

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

2528 

2529 # Check whether each state has been visited state_T_min times 

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

2531 go = False # If so, terminate the loop 

2532 continue 

2533 

2534 # Choose an underrepresented state to "jump" to 

2535 if np.any( 

2536 state_T == 0 

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

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

2539 MrkvNow = np.random.choice(never_visited) 

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

2541 emp_dstn = state_T / act_T 

2542 ratios = LR_dstn / emp_dstn 

2543 ratios_adj = ratios - np.max(ratios) 

2544 ratios_exp = np.exp(ratios_adj / logit_scale) 

2545 ratios_sum = np.sum(ratios_exp) 

2546 jump_probs = ratios_exp / ratios_sum 

2547 cum_probs = np.cumsum(jump_probs) 

2548 MrkvNow = np.searchsorted(cum_probs, draws[-1]) 

2549 

2550 loops += 1 

2551 # Make the Markov state history longer by act_T_orig periods 

2552 if loops >= loops_max: 

2553 go = False 

2554 print( 

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

2556 ) 

2557 else: 

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

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

2560 act_T += self.act_T_orig 

2561 

2562 # Store the results as attributes of self 

2563 self.MrkvNow_hist = MrkvNow_hist 

2564 self.act_T = act_T 

2565 

2566 def mill_rule(self, aLvl, pLvl): 

2567 """ 

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

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

2570 and adds the Markov state index. 

2571 

2572 See documentation for calc_R_and_W for more information. 

2573 

2574 Params 

2575 ------- 

2576 aLvl : float 

2577 pLvl : float 

2578 

2579 Returns 

2580 ------- 

2581 Mnow : float 

2582 Aggregate market resources for this period. 

2583 Aprev : float 

2584 Aggregate savings for the prior period. 

2585 KtoLnow : float 

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

2587 Rnow : float 

2588 Interest factor on assets in the economy this period. 

2589 Wnow : float 

2590 Wage rate for labor in the economy this period. 

2591 MrkvNow : int 

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

2593 """ 

2594 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2595 temp = self.calc_R_and_W(aLvl, pLvl) 

2596 

2597 return temp + (MrkvNow,) 

2598 

2599 def calc_AFunc(self, MaggNow, AaggNow): 

2600 """ 

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

2602 aggregate savings and aggregate market resources from a simulation. 

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

2604 

2605 Parameters 

2606 ---------- 

2607 MaggNow : [float] 

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

2609 AaggNow : [float] 

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

2611 

2612 Returns 

2613 ------- 

2614 (unnamed) : CapDynamicRule 

2615 Object containing new saving rules for each Markov state. 

2616 """ 

2617 verbose = self.verbose 

2618 discard_periods = ( 

2619 self.T_discard 

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

2621 update_weight = ( 

2622 1.0 - self.DampingFac 

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

2624 total_periods = len(MaggNow) 

2625 

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

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

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

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

2630 

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

2632 AFunc_list = [] 

2633 rSq_list = [] 

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

2635 these = i == MrkvHist 

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

2637 logMagg[these], logAagg[these] 

2638 ) 

2639 

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

2641 # with the previous guess 

2642 intercept = ( 

2643 update_weight * intercept 

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

2645 ) 

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

2647 AFunc_list.append( 

2648 AggregateSavingRule(intercept, slope) 

2649 ) # Make a new next-period capital function 

2650 rSq_list.append(r_value**2) 

2651 

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

2653 self.intercept_prev[i] = intercept 

2654 self.slope_prev[i] = slope 

2655 

2656 # Print the new parameters 

2657 if verbose: # pragma: nocover 

2658 print( 

2659 "intercept=" 

2660 + str(self.intercept_prev) 

2661 + ", slope=" 

2662 + str(self.slope_prev) 

2663 + ", r-sq=" 

2664 + str(rSq_list) 

2665 ) 

2666 

2667 return AggShocksDynamicRule(AFunc_list) 

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_cobb_douglas.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": True, 

2715 "act_T": 11000, 

2716 "T_discard": 1000, 

2717 "DampingFac": 0.5, 

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 "MrkvNow_init": 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 : [ConsumerType] 

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 * 1.0 / 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 * 1.0 / 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.MrkvArray = 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.MrkvNow_init 

2880 

2881 markov_process = MarkovProcess(self.MrkvArray, 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 

2907 return self.calc_R_and_W(aNow, EmpNow) 

2908 

2909 def calc_dynamics(self, Mnow, Aprev): 

2910 """ 

2911 Method to update perceptions of the aggregate saving rule in each 

2912 macroeconomic state; just calls calc_AFunc. 

2913 """ 

2914 return self.calc_AFunc(Mnow, Aprev) 

2915 

2916 def calc_R_and_W(self, aNow, EmpNow): 

2917 """ 

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

2919 capital stock to get the aggregate capital ratio. 

2920 

2921 Parameters 

2922 ---------- 

2923 aNow : [np.array] 

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

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

2926 EmpNow [np.array] 

2927 Agents' binary employment states. Not actually used in computation of 

2928 interest and wage rates, but stored in the history to verify that the 

2929 idiosyncratic unemployment probabilities are behaving as expected. 

2930 

2931 Returns 

2932 ------- 

2933 Mnow : float 

2934 Aggregate market resources for this period. 

2935 Aprev : float 

2936 Aggregate savings for the prior period. 

2937 MrkvNow : int 

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

2939 Rnow : float 

2940 Interest factor on assets in the economy this period. 

2941 Wnow : float 

2942 Wage rate for labor in the economy this period. 

2943 """ 

2944 # Calculate aggregate savings 

2945 # End-of-period savings from last period 

2946 Aprev = np.mean(np.array(aNow)) 

2947 # Calculate aggregate capital this period 

2948 AggK = Aprev # ...becomes capital today 

2949 

2950 # Calculate unemployment rate 

2951 Urate = 1.0 - np.mean(np.array(EmpNow)) 

2952 self.Urate = Urate # This is the unemployment rate for the *prior* period 

2953 

2954 # Get this period's TFP and labor supply 

2955 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2956 if MrkvNow == 0: 

2957 Prod = self.ProdB 

2958 AggL = (1.0 - self.UrateB) * self.LbrInd 

2959 elif MrkvNow == 1: 

2960 Prod = self.ProdG 

2961 AggL = (1.0 - self.UrateG) * self.LbrInd 

2962 self.Shk_idx += 1 

2963 

2964 # Calculate the interest factor and wage rate this period 

2965 KtoLnow = AggK / AggL 

2966 Rnow = 1.0 + Prod * self.rFunc(KtoLnow) - self.DeprRte 

2967 Wnow = Prod * self.Wfunc(KtoLnow) 

2968 Mnow = Rnow * AggK + Wnow * AggL 

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

2970 

2971 # Returns a tuple of these values 

2972 return Mnow, Aprev, MrkvNow, Rnow, Wnow 

2973 

2974 def calc_AFunc(self, Mnow, Aprev): 

2975 """ 

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

2977 aggregate savings and aggregate market resources from a simulation. 

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

2979 

2980 Parameters 

2981 ---------- 

2982 Mnow : [float] 

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

2984 Anow : [float] 

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

2986 

2987 Returns 

2988 ------- 

2989 (unnamed) : CapDynamicRule 

2990 Object containing new saving rules for each Markov state. 

2991 """ 

2992 verbose = self.verbose 

2993 discard_periods = ( 

2994 self.T_discard 

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

2996 update_weight = ( 

2997 1.0 - self.DampingFac 

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

2999 total_periods = len(Mnow) 

3000 

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

3002 logAagg = np.log(Aprev[discard_periods:total_periods]) 

3003 logMagg = np.log(Mnow[discard_periods - 1 : total_periods - 1]) 

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

3005 

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

3007 AFunc_list = [] 

3008 rSq_list = [] 

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

3010 these = i == MrkvHist 

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

3012 logMagg[these], logAagg[these] 

3013 ) 

3014 

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

3016 # with the previous guess 

3017 intercept = ( 

3018 update_weight * intercept 

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

3020 ) 

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

3022 AFunc_list.append( 

3023 AggregateSavingRule(intercept, slope) 

3024 ) # Make a new next-period capital function 

3025 rSq_list.append(r_value**2) 

3026 

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

3028 self.intercept_prev[i] = intercept 

3029 self.slope_prev[i] = slope 

3030 

3031 # Print the new parameters 

3032 if verbose: # pragma: nocover 

3033 print( 

3034 "intercept=" 

3035 + str(self.intercept_prev) 

3036 + ", slope=" 

3037 + str(self.slope_prev) 

3038 + ", r-sq=" 

3039 + str(rSq_list) 

3040 ) 

3041 

3042 return AggShocksDynamicRule(AFunc_list) 

3043 

3044 

3045class AggregateSavingRule(MetricObject): 

3046 """ 

3047 A class to represent agent beliefs about aggregate saving at the end of this period (AaggNow) as 

3048 a function of (normalized) aggregate market resources at the beginning of the period (MaggNow). 

3049 

3050 Parameters 

3051 ---------- 

3052 intercept : float 

3053 Intercept of the log-linear capital evolution rule. 

3054 slope : float 

3055 Slope of the log-linear capital evolution rule. 

3056 """ 

3057 

3058 def __init__(self, intercept, slope): 

3059 self.intercept = intercept 

3060 self.slope = slope 

3061 self.distance_criteria = ["slope", "intercept"] 

3062 

3063 def __call__(self, Mnow): 

3064 """ 

3065 Evaluates aggregate savings as a function of the aggregate market resources this period. 

3066 

3067 Parameters 

3068 ---------- 

3069 Mnow : float 

3070 Aggregate market resources this period. 

3071 

3072 Returns 

3073 ------- 

3074 Aagg : Expected aggregate savings this period. 

3075 """ 

3076 Aagg = np.exp(self.intercept + self.slope * np.log(Mnow)) 

3077 return Aagg 

3078 

3079 

3080class AggShocksDynamicRule(MetricObject): 

3081 """ 

3082 Just a container class for passing the dynamic rule in the aggregate shocks model to agents. 

3083 

3084 Parameters 

3085 ---------- 

3086 AFunc : CapitalEvoRule 

3087 Aggregate savings as a function of aggregate market resources. 

3088 """ 

3089 

3090 def __init__(self, AFunc): 

3091 self.AFunc = AFunc 

3092 self.distance_criteria = ["AFunc"]