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

932 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-02 05:14 +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.ConsumptionSaving.ConsMarkovModel import MarkovConsumerType 

26from HARK.distributions import ( 

27 MarkovProcess, 

28 MeanOneLogNormal, 

29 Uniform, 

30 calc_expectation, 

31 combine_indep_dstns, 

32) 

33from HARK.interpolation import ( 

34 BilinearInterp, 

35 ConstantFunction, 

36 IdentityFunction, 

37 LinearInterp, 

38 LinearInterpOnInterp1D, 

39 LowerEnvelope2D, 

40 MargValueFuncCRRA, 

41 UpperEnvelope, 

42 VariableLowerBoundFunc2D, 

43) 

44from HARK.metric import MetricObject 

45from HARK.rewards import ( 

46 CRRAutility, 

47 CRRAutility_inv, 

48 CRRAutility_invP, 

49 CRRAutilityP, 

50 CRRAutilityP_inv, 

51 CRRAutilityPP, 

52) 

53from HARK.utilities import make_assets_grid, get_it_from 

54 

55__all__ = [ 

56 "AggShockConsumerType", 

57 "AggShockMarkovConsumerType", 

58 "CobbDouglasEconomy", 

59 "SmallOpenEconomy", 

60 "CobbDouglasMarkovEconomy", 

61 "SmallOpenMarkovEconomy", 

62 "AggregateSavingRule", 

63 "AggShocksDynamicRule", 

64 "init_agg_shocks", 

65 "init_agg_mrkv_shocks", 

66 "init_cobb_douglas", 

67 "init_mrkv_cobb_douglas", 

68] 

69 

70utility = CRRAutility 

71utilityP = CRRAutilityP 

72utilityPP = CRRAutilityPP 

73utilityP_inv = CRRAutilityP_inv 

74utility_invP = CRRAutility_invP 

75utility_inv = CRRAutility_inv 

76 

77 

78def make_aggshock_solution_terminal(CRRA): 

79 """ 

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

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

82 

83 Parameters 

84 ---------- 

85 CRRA : float 

86 Coefficient of relative risk aversion. 

87 

88 Returns 

89 ------- 

90 solution_terminal : ConsumerSolution 

91 Solution to the terminal period problem. 

92 """ 

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

94 vPfunc_terminal = MargValueFuncCRRA(cFunc_terminal, CRRA) 

95 mNrmMin_terminal = ConstantFunction(0) 

96 solution_terminal = ConsumerSolution( 

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

98 ) 

99 return solution_terminal 

100 

101 

102def make_aggmrkv_solution_terminal(CRRA, MrkvArray): 

103 """ 

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

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

106 value function. 

107 

108 Parameters 

109 ---------- 

110 CRRA : float 

111 Coefficient of relative risk aversion. 

112 MrkvArray : np.array 

113 Transition probability array. 

114 

115 Returns 

116 ------- 

117 solution_terminal : ConsumerSolution 

118 Solution to the terminal period problem. 

119 """ 

120 solution_terminal = make_aggshock_solution_terminal(CRRA) 

121 

122 # Make replicated terminal period solution 

123 StateCount = MrkvArray.shape[0] 

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

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

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

127 

128 return solution_terminal 

129 

130 

131def make_exponential_MgridBase(MaggCount, MaggPerturb, MaggExpFac): 

132 """ 

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

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

135 

136 Parameters 

137 ---------- 

138 MaggCount : int 

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

140 MaggPerturb : float 

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

142 1+perturb and 1-perturb. 

143 MaggExpFac : float 

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

145 

146 Returns 

147 ------- 

148 MgridBase : np.array 

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

150 """ 

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

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

153 fac = np.exp(MaggExpFac) 

154 for n in range(N - 1): 

155 new_hi = gridpoints[-1] * fac 

156 new_lo = gridpoints[0] / fac 

157 gridpoints.append(new_hi) 

158 gridpoints.insert(0, new_lo) 

159 MgridBase = np.array(gridpoints) 

160 return MgridBase 

161 

162 

163############################################################################### 

164 

165 

166def solveConsAggShock( 

167 solution_next, 

168 IncShkDstn, 

169 LivPrb, 

170 DiscFac, 

171 CRRA, 

172 PermGroFac, 

173 PermGroFacAgg, 

174 aXtraGrid, 

175 BoroCnstArt, 

176 Mgrid, 

177 AFunc, 

178 Rfunc, 

179 wFunc, 

180 DeprFac, 

181): 

182 """ 

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

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

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

186 version uses calc_expectation to reduce code clutter. 

187 

188 Parameters 

189 ---------- 

190 solution_next : ConsumerSolution 

191 The solution to the succeeding one period problem. 

192 IncShkDstn : distribution.Distribution 

193 A discrete 

194 approximation to the income process between the period being solved 

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

196 idiosyncratic permanent shocks, idiosyncratic transitory 

197 shocks, aggregate permanent shocks, aggregate transitory shocks. 

198 LivPrb : float 

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

200 the succeeding period. 

201 DiscFac : float 

202 Intertemporal discount factor for future utility. 

203 CRRA : float 

204 Coefficient of relative risk aversion. 

205 PermGroFac : float 

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

207 PermGroFacAgg : float 

208 Expected aggregate productivity growth factor. 

209 aXtraGrid : np.array 

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

211 absolute minimum acceptable level. 

212 BoroCnstArt : float 

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

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

215 Mgrid : np.array 

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

217 AFunc : function 

218 Aggregate savings as a function of aggregate market resources. 

219 Rfunc : function 

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

221 wFunc : function 

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

223 DeprFac : float 

224 Capital Depreciation Rate 

225 

226 Returns 

227 ------- 

228 solution_now : ConsumerSolution 

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

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

231 tions) and marginal value function vPfunc. 

232 """ 

233 # Unpack the income shocks and get grid sizes 

234 PermShkValsNext = IncShkDstn.atoms[0] 

235 TranShkValsNext = IncShkDstn.atoms[1] 

236 PermShkAggValsNext = IncShkDstn.atoms[2] 

237 TranShkAggValsNext = IncShkDstn.atoms[3] 

238 aCount = aXtraGrid.size 

239 Mcount = Mgrid.size 

240 

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

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

243 def calcAggObjects(M, Psi, Theta): 

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

245 # Next period's aggregate capital/labor ratio 

246 kNext = A / (PermGroFacAgg * Psi) 

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

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

249 wEff = ( 

250 wFunc(kNextEff) * Theta 

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

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

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

254 return Mnext, Reff, wEff 

255 

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

257 def vPnextFunc(S, a, M): 

258 psi = S[0] 

259 theta = S[1] 

260 Psi = S[2] 

261 Theta = S[3] 

262 

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

264 PermShkTotal = ( 

265 PermGroFac * PermGroFacAgg * psi * Psi 

266 ) # Total / combined permanent shock 

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

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

269 return vPnext 

270 

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

272 # Natural borrowing constraint at each M_t 

273 BoroCnstNat_vec = np.zeros(Mcount) 

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

275 for j in range(Mcount): 

276 Mnext, Reff, wEff = calcAggObjects( 

277 Mgrid[j], PermShkAggValsNext, TranShkAggValsNext 

278 ) 

279 aNrmMin_cand = ( 

280 PermGroFac * PermGroFacAgg * PermShkValsNext * PermShkAggValsNext / Reff 

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

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

283 aNrmNow[:, j] = aNrmMin + aXtraGrid 

284 BoroCnstNat_vec[j] = aNrmMin 

285 

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

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

288 EndOfPrdvP = ( 

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

290 ) 

291 

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

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

294 mNrmNow = aNrmNow + cNrmNow 

295 

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

297 cFuncBaseByM_list = [] 

298 for j in range(Mcount): 

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

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

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

302 

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

304 BoroCnstNat = LinearInterp( 

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

306 ) 

307 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

308 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

309 

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

311 cFuncCnst = BilinearInterp( 

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

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

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

315 ) 

316 cFuncNow = LowerEnvelope2D(cFuncUnc, cFuncCnst) 

317 

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

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

320 

321 # Construct the marginal value function using the envelope condition 

322 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

323 

324 # Pack up and return the solution 

325 solution_now = ConsumerSolution( 

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

327 ) 

328 return solution_now 

329 

330 

331############################################################################### 

332 

333 

334def solve_ConsAggMarkov( 

335 solution_next, 

336 IncShkDstn, 

337 LivPrb, 

338 DiscFac, 

339 CRRA, 

340 MrkvArray, 

341 PermGroFac, 

342 PermGroFacAgg, 

343 aXtraGrid, 

344 BoroCnstArt, 

345 Mgrid, 

346 AFunc, 

347 Rfunc, 

348 wFunc, 

349): 

350 """ 

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

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

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

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

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

356 

357 Parameters 

358 ---------- 

359 solution_next : ConsumerSolution 

360 The solution to the succeeding one period problem. 

361 IncShkDstn : [distribution.Distribution] 

362 A list of 

363 discrete approximations to the income process between the period being 

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

365 idisyncratic permanent shocks, idiosyncratic transitory 

366 shocks, aggregate permanent shocks, aggregate transitory shocks. 

367 LivPrb : float 

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

369 the succeeding period. 

370 DiscFac : float 

371 Intertemporal discount factor for future utility. 

372 CRRA : float 

373 Coefficient of relative risk aversion. 

374 MrkvArray : np.array 

375 Markov transition matrix between discrete macroeconomic states. 

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

377 on being in state i this period. 

378 PermGroFac : float 

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

380 for the *individual*'s productivity. 

381 PermGroFacAgg : [float] 

382 Expected aggregate productivity growth in each Markov macro state. 

383 aXtraGrid : np.array 

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

385 absolute minimum acceptable level. 

386 BoroCnstArt : float 

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

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

389 Mgrid : np.array 

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

391 AFunc : [function] 

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

393 Markov macro state. 

394 Rfunc : function 

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

396 wFunc : function 

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

398 DeprFac : float 

399 Capital Depreciation Rate 

400 

401 Returns 

402 ------- 

403 solution_now : ConsumerSolution 

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

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

406 tions) and marginal value function vPfunc. 

407 """ 

408 # Get sizes of grids 

409 aCount = aXtraGrid.size 

410 Mcount = Mgrid.size 

411 StateCount = MrkvArray.shape[0] 

412 

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

414 # Construct EndOfPrdvP_cond functions for each state. 

415 EndOfPrdvPfunc_cond = [] 

416 BoroCnstNat_cond = [] 

417 for j in range(StateCount): 

418 # Unpack next period's solution 

419 vPfuncNext = solution_next.vPfunc[j] 

420 mNrmMinNext = solution_next.mNrmMin[j] 

421 

422 # Unpack the income shocks 

423 ShkPrbsNext = IncShkDstn[j].pmv 

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

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

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

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

428 ShkCount = ShkPrbsNext.size 

429 aXtra_tiled = np.tile( 

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

431 ) 

432 

433 # Make tiled versions of the income shocks 

434 # Dimension order: Mnow, aNow, Shk 

435 ShkPrbsNext_tiled = np.tile( 

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

437 ) 

438 PermShkValsNext_tiled = np.tile( 

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

440 ) 

441 TranShkValsNext_tiled = np.tile( 

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

443 ) 

444 PermShkAggValsNext_tiled = np.tile( 

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

446 ) 

447 TranShkAggValsNext_tiled = np.tile( 

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

449 ) 

450 

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

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

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

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

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

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

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

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

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

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

461 # conditional marginal value functions are constructed is not relevant 

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

463 AaggGrid = AFunc[j](Mgrid) 

464 AaggNow_tiled = np.tile( 

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

466 ) 

467 

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

469 kNext_array = AaggNow_tiled / ( 

470 PermGroFacAgg[j] * PermShkAggValsNext_tiled 

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

472 kNextEff_array = ( 

473 kNext_array / TranShkAggValsNext_tiled 

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

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

476 Reff_array = ( 

477 R_array / LivPrb 

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

479 wEff_array = ( 

480 wFunc(kNextEff_array) * TranShkAggValsNext_tiled 

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

482 PermShkTotal_array = ( 

483 PermGroFac 

484 * PermGroFacAgg[j] 

485 * PermShkValsNext_tiled 

486 * PermShkAggValsNext_tiled 

487 ) # total / combined permanent shock 

488 Mnext_array = ( 

489 kNext_array * R_array + wEff_array 

490 ) # next period's aggregate market resources 

491 

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

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

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

495 aNrmMin_candidates = ( 

496 PermGroFac 

497 * PermGroFacAgg[j] 

498 * PermShkValsNext_tiled[:, 0, :] 

499 * PermShkAggValsNext_tiled[:, 0, :] 

500 / Reff_array[:, 0, :] 

501 * ( 

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

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

504 ) 

505 ) 

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

507 BoroCnstNat_vec = aNrmMin_vec 

508 aNrmMin_tiled = np.tile( 

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

510 ) 

511 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

512 

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

514 mNrmNext_array = ( 

515 Reff_array * aNrmNow_tiled / PermShkTotal_array 

516 + TranShkValsNext_tiled * wEff_array 

517 ) 

518 

519 # Find marginal value next period at every income shock 

520 # realization and every aggregate market resource gridpoint 

521 vPnext_array = ( 

522 Reff_array 

523 * PermShkTotal_array ** (-CRRA) 

524 * vPfuncNext(mNrmNext_array, Mnext_array) 

525 ) 

526 

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

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

529 

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

531 BoroCnstNat = LinearInterp( 

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

533 ) 

534 EndOfPrdvPnvrs = np.concatenate( 

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

536 ) 

537 EndOfPrdvPnvrsFunc_base = BilinearInterp( 

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

539 ) 

540 EndOfPrdvPnvrsFunc = VariableLowerBoundFunc2D( 

541 EndOfPrdvPnvrsFunc_base, BoroCnstNat 

542 ) 

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

544 BoroCnstNat_cond.append(BoroCnstNat) 

545 

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

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

548 cFuncCnst = BilinearInterp( 

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

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

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

552 ) 

553 

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

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

556 # and marginal value function for each state. 

557 cFuncNow = [] 

558 vPfuncNow = [] 

559 mNrmMinNow = [] 

560 for i in range(StateCount): 

561 # Find natural borrowing constraint for this state by Aagg 

562 AaggNow = AFunc[i](Mgrid) 

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

564 for j in range(StateCount): 

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

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

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

568 BoroCnstNat_vec = aNrmMin_vec 

569 

570 # Make tiled grids of aNrm and Aagg 

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

572 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

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

574 

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

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

577 for j in range(StateCount): 

578 if MrkvArray[i, j] > 0.0: 

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

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

581 

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

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

584 mNrmNow = aNrmNow_tiled + cNrmNow 

585 

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

587 cFuncBaseByM_list = [] 

588 for n in range(Mcount): 

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

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

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

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

593 

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

595 BoroCnstNat = LinearInterp( 

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

597 ) 

598 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

599 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

600 

601 # Combine the constrained consumption function with unconstrained component 

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

603 

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

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

606 

607 # Construct the marginal value function using the envelope condition 

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

609 

610 # Pack up and return the solution 

611 solution_now = ConsumerSolution( 

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

613 ) 

614 return solution_now 

615 

616 

617############################################################################### 

618 

619 

620def solve_KrusellSmith( 

621 solution_next, 

622 DiscFac, 

623 CRRA, 

624 aGrid, 

625 Mgrid, 

626 mNextArray, 

627 MnextArray, 

628 ProbArray, 

629 RnextArray, 

630): 

631 """ 

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

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

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

635 default constructor functions for details. 

636 

637 Parameters 

638 ---------- 

639 solution_next : ConsumerSolution 

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

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

642 DiscFac : float 

643 Intertemporal discount factor. 

644 CRRA : float 

645 Coefficient of relative risk aversion. 

646 aGrid : np.array 

647 Array of end-of-period asset values. 

648 Mgrid : np.array 

649 A grid of aggregate market resources in the economy. 

650 mNextArray : np.array 

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

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

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

654 MnextArray : np.array 

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

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

657 shock that might attain. Corresponds to mNextArray. 

658 ProbArray : np.array 

659 Tiled array of transition probabilities among discrete states. Every 

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

661 RnextArray : np.array 

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

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

664 

665 Returns 

666 ------- 

667 solution_now : ConsumerSolution 

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

669 """ 

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

671 vPnext = np.zeros_like(mNextArray) 

672 for j in range(4): 

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

674 mNextArray[:, :, :, j], MnextArray[:, :, :, j] 

675 ) 

676 

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

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

679 

680 # Invert the first order condition to find optimal consumption 

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

682 

683 # Find the endogenous gridpoints 

684 aCount = aGrid.size 

685 Mcount = Mgrid.size 

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

687 mNow = aNow + cNow 

688 

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

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

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

692 

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

694 cFunc_by_state = [] 

695 vPfunc_by_state = [] 

696 for j in range(4): 

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

698 cFunc_j = LinearInterpOnInterp1D(cFunc_by_M, Mgrid) 

699 vPfunc_j = MargValueFuncCRRA(cFunc_j, CRRA) 

700 cFunc_by_state.append(cFunc_j) 

701 vPfunc_by_state.append(vPfunc_j) 

702 

703 # Package and return the solution 

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

705 return solution_now 

706 

707 

708############################################################################### 

709 

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

711aggshock_constructor_dict = { 

712 "IncShkDstn": construct_lognormal_income_process_unemployment, 

713 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

714 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

715 "aXtraGrid": make_assets_grid, 

716 "MgridBase": make_exponential_MgridBase, 

717 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

718 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

719 "solution_terminal": make_aggshock_solution_terminal, 

720} 

721 

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

723default_kNrmInitDstn_params = { 

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

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

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

727} 

728 

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

730default_pLvlInitDstn_params = { 

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

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

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

734} 

735 

736 

737# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

738default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

748} 

749 

750# Default parameters to make aXtraGrid using make_assets_grid 

751default_aXtraGrid_params = { 

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

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

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

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

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

757} 

758 

759# Default parameters to make MgridBase using make_exponential_MgridBase 

760default_MgridBase_params = { 

761 "MaggCount": 17, 

762 "MaggPerturb": 0.01, 

763 "MaggExpFac": 0.15, 

764} 

765 

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

767init_agg_shocks = { 

768 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

771 "constructors": aggshock_constructor_dict, # See dictionary above 

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

773 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

775 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

778 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

779 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

785 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

789} 

790init_agg_shocks.update(default_kNrmInitDstn_params) 

791init_agg_shocks.update(default_pLvlInitDstn_params) 

792init_agg_shocks.update(default_IncShkDstn_params) 

793init_agg_shocks.update(default_aXtraGrid_params) 

794init_agg_shocks.update(default_MgridBase_params) 

795 

796 

797class AggShockConsumerType(IndShockConsumerType): 

798 """ 

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

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

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

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

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

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

805 decision about how much to consume. 

806 """ 

807 

808 default_ = {"params": init_agg_shocks, "solver": solveConsAggShock} 

809 time_inv_ = IndShockConsumerType.time_inv_.copy() 

810 try: 

811 time_inv_.remove("vFuncBool") 

812 time_inv_.remove("CubicBool") 

813 except: 

814 pass 

815 

816 def reset(self): 

817 """ 

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

819 

820 Parameters 

821 ---------- 

822 None 

823 

824 Returns 

825 ------- 

826 None 

827 """ 

828 self.initialize_sim() 

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

830 self.AgentCount 

831 ) # Start simulation near SS 

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

833 

834 def pre_solve(self): 

835 self.construct("solution_terminal") 

836 

837 def get_economy_data(self, economy): 

838 """ 

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

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

841 attributes relevant to their microeconomic model, like the relationship 

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

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

844 attributes of the ConsumerType. 

845 

846 Parameters 

847 ---------- 

848 economy : Market 

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

850 subclass CobbDouglasEconomy, which has methods to generate the 

851 relevant attributes. 

852 

853 Returns 

854 ------- 

855 None 

856 """ 

857 self.T_sim = ( 

858 economy.act_T 

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

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

861 self.aNrmInitMean = np.log( 

862 0.00000001 

863 ) # Initialize newborn assets to nearly zero 

864 self.Mgrid = ( 

865 economy.MSS * self.MgridBase 

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

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

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

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

870 self.DeprFac = economy.DeprFac # Rate of capital depreciation 

871 self.PermGroFacAgg = ( 

872 economy.PermGroFacAgg 

873 ) # Aggregate permanent productivity growth 

874 self.add_AggShkDstn( 

875 economy.AggShkDstn 

876 ) # Combine idiosyncratic and aggregate shocks into one dstn 

877 self.add_to_time_inv( 

878 "Mgrid", "AFunc", "Rfunc", "wFunc", "DeprFac", "PermGroFacAgg" 

879 ) 

880 

881 def add_AggShkDstn(self, AggShkDstn): 

882 """ 

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

884 

885 Parameters 

886 ---------- 

887 AggShkDstn : [np.array] 

888 Aggregate productivity shock distribution. First element is proba- 

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

890 agg transitory shocks. 

891 

892 Returns 

893 ------- 

894 None 

895 """ 

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

897 self.IncShkDstn = self.IncShkDstnWithoutAggShocks 

898 else: 

899 self.IncShkDstnWithoutAggShocks = self.IncShkDstn 

900 self.IncShkDstn = [ 

901 combine_indep_dstns(self.IncShkDstn[t], AggShkDstn) 

902 for t in range(self.T_cycle) 

903 ] 

904 

905 def sim_birth(self, which_agents): 

906 """ 

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

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

909 

910 Parameters 

911 ---------- 

912 which_agents : np.array(Bool) 

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

914 

915 Returns 

916 ------- 

917 None 

918 """ 

919 IndShockConsumerType.sim_birth(self, which_agents) 

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

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

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

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

924 ) 

925 else: 

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

927 

928 def sim_death(self): 

929 """ 

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

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

932 

933 Parameters 

934 ---------- 

935 None 

936 

937 Returns 

938 ------- 

939 who_dies : np.array(bool) 

940 Boolean array of size AgentCount indicating which agents die. 

941 """ 

942 # Just select a random set of agents to die 

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

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

945 base_bool[0:how_many_die] = True 

946 who_dies = self.RNG.permutation(base_bool) 

947 if self.T_age is not None: 

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

949 

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

951 who_lives = np.logical_not(who_dies) 

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

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

954 Ractuarial = 1.0 + wealth_dead / wealth_living 

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

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

957 ) 

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

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

960 ) 

961 return who_dies 

962 

963 def get_Rfree(self): 

964 """ 

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

966 

967 Parameters 

968 ---------- 

969 None 

970 

971 Returns 

972 ------- 

973 RfreeNow : np.array 

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

975 """ 

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

977 return RfreeNow 

978 

979 def get_shocks(self): 

980 """ 

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

982 and idiosyncratic shocks of each type. 

983 

984 Parameters 

985 ---------- 

986 None 

987 

988 Returns 

989 ------- 

990 None 

991 """ 

992 IndShockConsumerType.get_shocks(self) # Update idiosyncratic shocks 

993 self.shocks["TranShk"] = ( 

994 self.shocks["TranShk"] * self.TranShkAggNow * self.wRteNow 

995 ) 

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

997 

998 def get_controls(self): 

999 """ 

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

1001 

1002 Parameters 

1003 ---------- 

1004 None 

1005 

1006 Returns 

1007 ------- 

1008 None 

1009 """ 

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

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

1012 MaggNow = self.get_MaggNow() 

1013 for t in range(self.T_cycle): 

1014 these = t == self.t_cycle 

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

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

1017 ) 

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

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

1020 ) # Marginal propensity to consume 

1021 

1022 self.controls["cNrm"] = cNrmNow 

1023 self.MPCnow = MPCnow 

1024 

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

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

1027 

1028 def market_action(self): 

1029 """ 

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

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

1032 

1033 Parameters 

1034 ---------- 

1035 None 

1036 

1037 Returns 

1038 ------- 

1039 None 

1040 """ 

1041 self.simulate(1) 

1042 

1043 def calc_bounding_values(self): 

1044 """ 

1045 Calculate human wealth plus minimum and maximum MPC in an infinite 

1046 horizon model with only one period repeated indefinitely. Store results 

1047 as attributes of self. Human wealth is the present discounted value of 

1048 expected future income after receiving income this period, ignoring mort- 

1049 ality. The maximum MPC is the limit of the MPC as m --> mNrmMin. The 

1050 minimum MPC is the limit of the MPC as m --> infty. 

1051 

1052 NOT YET IMPLEMENTED FOR THIS CLASS 

1053 

1054 Parameters 

1055 ---------- 

1056 None 

1057 

1058 Returns 

1059 ------- 

1060 None 

1061 """ 

1062 raise NotImplementedError() 

1063 

1064 def make_euler_error_func(self, mMax=100, approx_inc_dstn=True): 

1065 """ 

1066 Creates a "normalized Euler error" function for this instance, mapping 

1067 from market resources to "consumption error per dollar of consumption." 

1068 Stores result in attribute eulerErrorFunc as an interpolated function. 

1069 Has option to use approximate income distribution stored in self.IncShkDstn 

1070 or to use a (temporary) very dense approximation. 

1071 

1072 NOT YET IMPLEMENTED FOR THIS CLASS 

1073 

1074 Parameters 

1075 ---------- 

1076 mMax : float 

1077 Maximum normalized market resources for the Euler error function. 

1078 approx_inc_dstn : Boolean 

1079 Indicator for whether to use the approximate discrete income distri- 

1080 bution stored in self.IncShkDstn[0], or to use a very accurate 

1081 discrete approximation instead. When True, uses approximation in 

1082 IncShkDstn; when False, makes and uses a very dense approximation. 

1083 

1084 Returns 

1085 ------- 

1086 None 

1087 

1088 Notes 

1089 ----- 

1090 This method is not used by any other code in the library. Rather, it is here 

1091 for expository and benchmarking purposes. 

1092 

1093 """ 

1094 raise NotImplementedError() 

1095 

1096 

1097############################################################################### 

1098 

1099 

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

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

1102 

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

1104init_agg_mrkv_shocks = init_agg_shocks.copy() 

1105init_agg_mrkv_shocks["MrkvArray"] = MrkvArray 

1106aggmrkv_constructor_dict = aggshock_constructor_dict.copy() 

1107aggmrkv_constructor_dict["solution_terminal"] = make_aggmrkv_solution_terminal 

1108init_agg_mrkv_shocks["constructors"] = aggmrkv_constructor_dict 

1109 

1110 

1111class AggShockMarkovConsumerType(AggShockConsumerType): 

1112 """ 

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

1114 experience both aggregate and idiosyncratic shocks to productivity (both 

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

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

1117 """ 

1118 

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

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

1121 default_ = {"params": init_agg_mrkv_shocks, "solver": solve_ConsAggMarkov} 

1122 

1123 def add_AggShkDstn(self, AggShkDstn): 

1124 """ 

1125 Variation on AggShockConsumerType.add_AggShkDstn that handles the Markov 

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

1127 for each Markov state. 

1128 """ 

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

1130 self.IncShkDstn = self.IncShkDstnWithoutAggShocks 

1131 else: 

1132 self.IncShkDstnWithoutAggShocks = self.IncShkDstn 

1133 

1134 IncShkDstnOut = [] 

1135 N = self.MrkvArray.shape[0] 

1136 for t in range(self.T_cycle): 

1137 IncShkDstnOut.append( 

1138 [ 

1139 combine_indep_dstns(self.IncShkDstn[t][n], AggShkDstn[n]) 

1140 for n in range(N) 

1141 ] 

1142 ) 

1143 self.IncShkDstn = IncShkDstnOut 

1144 

1145 def reset_rng(self): 

1146 MarkovConsumerType.reset_rng(self) 

1147 

1148 def initialize_sim(self): 

1149 self.shocks["Mrkv"] = 0 

1150 AggShockConsumerType.initialize_sim(self) 

1151 

1152 def get_shocks(self): 

1153 """ 

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

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

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

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

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

1159 

1160 Parameters 

1161 ---------- 

1162 None 

1163 

1164 Returns 

1165 ------- 

1166 None 

1167 """ 

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

1169 TranShkNow = np.zeros(self.AgentCount) 

1170 newborn = self.t_age == 0 

1171 for t in range(self.T_cycle): 

1172 these = t == self.t_cycle 

1173 N = np.sum(these) 

1174 if N > 0: 

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

1176 self.shocks["Mrkv"] 

1177 ] # set current income distribution 

1178 # and permanent growth factor 

1179 PermGroFacNow = self.PermGroFac[t - 1] 

1180 

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

1182 ShockDraws = IncShkDstnNow.draw(N, exact_match=True) 

1183 # Permanent "shock" includes expected growth 

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

1185 TranShkNow[these] = ShockDraws[1] 

1186 

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

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

1189 N = np.sum(newborn) 

1190 if N > 0: 

1191 these = newborn 

1192 IncShkDstnNow = self.IncShkDstn[0][ 

1193 self.shocks["Mrkv"] 

1194 ] # set current income distribution 

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

1196 

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

1198 ShockDraws = IncShkDstnNow.draw(N, exact_match=True) 

1199 

1200 # Permanent "shock" includes expected growth 

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

1202 TranShkNow[these] = ShockDraws[1] 

1203 

1204 # Store the shocks in self 

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

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

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

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

1209 

1210 def get_controls(self): 

1211 """ 

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

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

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

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

1216 

1217 Parameters 

1218 ---------- 

1219 None 

1220 

1221 Returns 

1222 ------- 

1223 None 

1224 """ 

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

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

1227 MaggNow = self.get_MaggNow() 

1228 MrkvNow = self.getMrkvNow() 

1229 

1230 StateCount = self.MrkvArray.shape[0] 

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

1232 for i in range(StateCount): 

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

1234 

1235 for t in range(self.T_cycle): 

1236 these = t == self.t_cycle 

1237 for i in range(StateCount): 

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

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

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

1241 ) 

1242 # Marginal propensity to consume 

1243 MPCnow[those] = ( 

1244 self.solution[t] 

1245 .cFunc[i] 

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

1247 ) 

1248 self.controls["cNrm"] = cNrmNow 

1249 self.MPCnow = MPCnow 

1250 return None 

1251 

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

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

1254 

1255 

1256############################################################################### 

1257 

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

1259 

1260 

1261def make_solution_terminal_KS(CRRA): 

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

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

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

1265 return solution_terminal 

1266 

1267 

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

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

1270 

1271 

1272def make_KS_transition_arrays( 

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

1274): 

1275 """ 

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

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

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

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

1280 

1281 Parameters 

1282 ---------- 

1283 aGrid : np.array 

1284 Grid of end-of-period individual assets. 

1285 MGrid : np.array 

1286 Grid of aggregate market resources. 

1287 AFunc : function 

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

1289 LbrInd : float 

1290 Individual labor supply measure. 

1291 UrateB : float 

1292 Unemployment rate in the "bad" aggregate state. 

1293 UrateG : float 

1294 Unemployment rate in the "good" aggregate state. 

1295 ProdB : float 

1296 TFP in the "bad" aggregate state. 

1297 ProdG : float 

1298 TFP in the "good" aggregate state. 

1299 MrkvIndArray : np.array 

1300 Markov transition probabilities from the perspective of the individual. 

1301 

1302 Returns 

1303 ------- 

1304 ProbArray : np.array 

1305 Array of discrete future outcome probabilities. 

1306 mNextArray : np.array 

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

1308 MnextArray : np.array 

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

1310 RnextArray : np.array 

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

1312 """ 

1313 # Get array sizes 

1314 aCount = aGrid.size 

1315 Mcount = Mgrid.size 

1316 

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

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

1319 

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

1321 AnowB = AFunc[0](Mgrid) 

1322 AnowG = AFunc[1](Mgrid) 

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

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

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

1326 

1327 # Make arrays of aggregate labor and TFP next period 

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

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

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

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

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

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

1334 

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

1336 KtoLnext = Knext / Lnext 

1337 Rnext = 1.0 + Znext * CapShare * KtoLnext ** (CapShare - 1.0) - DeprFac 

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

1339 

1340 # Calculate aggregate market resources next period 

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

1342 Mnext = (1.0 - DeprFac) * Knext + Ynext 

1343 

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

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

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

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

1348 

1349 # Make an array of idiosyncratic labor supply next period 

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

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

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

1353 

1354 # Calculate idiosyncratic market resources next period 

1355 mNext = Rnext_tiled * aNow_tiled + Wnext_tiled * lNext_tiled 

1356 

1357 # Make a tiled array of transition probabilities 

1358 Probs_tiled = np.tile( 

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

1360 ) 

1361 

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

1363 transition_arrays = { 

1364 "ProbArray": Probs_tiled, 

1365 "mNextArray": mNext, 

1366 "MnextArray": Mnext_tiled, 

1367 "RnextArray": Rnext_tiled, 

1368 } 

1369 return transition_arrays 

1370 

1371 

1372############################################################################### 

1373 

1374# Make a dictionary for Krusell-Smith agents 

1375KS_constructor_dict = { 

1376 "solution_terminal": make_solution_terminal_KS, 

1377 "aGrid": make_assets_grid_KS, 

1378 "transition_arrays": make_KS_transition_arrays, 

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

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

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

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

1383 "MgridBase": make_exponential_MgridBase, 

1384} 

1385 

1386init_KS_agents = { 

1387 "T_cycle": 1, 

1388 "pseudo_terminal": False, 

1389 "constructors": KS_constructor_dict, 

1390 "DiscFac": 0.99, 

1391 "CRRA": 1.0, 

1392 "LbrInd": 1.0, 

1393 "aMin": 0.001, 

1394 "aMax": 50.0, 

1395 "aCount": 32, 

1396 "aNestFac": 2, 

1397 "MaggCount": 25, 

1398 "MaggPerturb": 0.01, 

1399 "MaggExpFac": 0.12, 

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

1401 "AgentCount": 5000, 

1402} 

1403 

1404 

1405class KrusellSmithType(AgentType): 

1406 """ 

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

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

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

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

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

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

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

1414 

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

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

1417 economy object. 

1418 """ 

1419 

1420 time_inv_ = [ 

1421 "DiscFac", 

1422 "CRRA", 

1423 "aGrid", 

1424 "ProbArray", 

1425 "mNextArray", 

1426 "MnextArray", 

1427 "RnextArray", 

1428 ] 

1429 time_vary_ = [] 

1430 shock_vars_ = ["Mrkv"] 

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

1432 default_ = {"params": init_KS_agents, "solver": solve_KrusellSmith} 

1433 

1434 def __init__(self, **kwds): 

1435 temp = kwds.copy() 

1436 temp["construct"] = False 

1437 AgentType.__init__(self, **temp) 

1438 self.construct("MgridBase") 

1439 

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

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

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

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

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

1445 # Exception: MgridBase must exist 

1446 

1447 def pre_solve(self): 

1448 self.construct("solution_terminal") 

1449 

1450 def get_economy_data(self, Economy): 

1451 """ 

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

1453 

1454 Parameters 

1455 ---------- 

1456 Economy : KrusellSmithEconomy 

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

1458 

1459 Returns 

1460 ------- 

1461 None 

1462 """ 

1463 self.T_sim = ( 

1464 Economy.act_T 

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

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

1467 self.MrkvInit = Economy.sow_init[ 

1468 "Mrkv" 

1469 ] # Starting Markov state for the macroeconomy 

1470 self.Mgrid = ( 

1471 Economy.MSS * self.MgridBase 

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

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

1474 self.DeprFac = Economy.DeprFac # Rate of capital depreciation 

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

1476 # Idiosyncratic labor supply (when employed) 

1477 self.LbrInd = Economy.LbrInd 

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

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

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

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

1482 self.MrkvIndArray = ( 

1483 Economy.MrkvIndArray 

1484 ) # Transition probabilities among discrete states 

1485 self.MrkvAggArray = ( 

1486 Economy.MrkvArray 

1487 ) # Transition probabilities among aggregate discrete states 

1488 self.add_to_time_inv( 

1489 "Mgrid", 

1490 "AFunc", 

1491 "DeprFac", 

1492 "CapShare", 

1493 "UrateB", 

1494 "LbrInd", 

1495 "UrateG", 

1496 "ProdB", 

1497 "ProdG", 

1498 "MrkvIndArray", 

1499 "MrkvAggArray", 

1500 ) 

1501 

1502 def make_emp_idx_arrays(self): 

1503 """ 

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

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

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

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

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

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

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

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

1512 maintain exact unemployment rates in each period. 

1513 """ 

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

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

1516 B_emp_N = self.AgentCount - B_unemp_N 

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

1518 G_emp_N = self.AgentCount - G_unemp_N 

1519 

1520 # Bad-bad transition indices 

1521 BB_stay_unemp_N = int( 

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

1523 ) 

1524 BB_become_unemp_N = B_unemp_N - BB_stay_unemp_N 

1525 BB_stay_emp_N = int( 

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

1527 ) 

1528 BB_become_emp_N = B_emp_N - BB_stay_emp_N 

1529 BB_unemp_permute = np.concatenate( 

1530 [ 

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

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

1533 ] 

1534 ) 

1535 BB_emp_permute = np.concatenate( 

1536 [ 

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

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

1539 ] 

1540 ) 

1541 

1542 # Bad-good transition indices 

1543 BG_stay_unemp_N = int( 

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

1545 ) 

1546 BG_become_unemp_N = G_unemp_N - BG_stay_unemp_N 

1547 BG_stay_emp_N = int( 

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

1549 ) 

1550 BG_become_emp_N = G_emp_N - BG_stay_emp_N 

1551 BG_unemp_permute = np.concatenate( 

1552 [ 

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

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

1555 ] 

1556 ) 

1557 BG_emp_permute = np.concatenate( 

1558 [ 

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

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

1561 ] 

1562 ) 

1563 

1564 # Good-bad transition indices 

1565 GB_stay_unemp_N = int( 

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

1567 ) 

1568 GB_become_unemp_N = B_unemp_N - GB_stay_unemp_N 

1569 GB_stay_emp_N = int( 

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

1571 ) 

1572 GB_become_emp_N = B_emp_N - GB_stay_emp_N 

1573 GB_unemp_permute = np.concatenate( 

1574 [ 

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

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

1577 ] 

1578 ) 

1579 GB_emp_permute = np.concatenate( 

1580 [ 

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

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

1583 ] 

1584 ) 

1585 

1586 # Good-good transition indices 

1587 GG_stay_unemp_N = int( 

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

1589 ) 

1590 GG_become_unemp_N = G_unemp_N - GG_stay_unemp_N 

1591 GG_stay_emp_N = int( 

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

1593 ) 

1594 GG_become_emp_N = G_emp_N - GG_stay_emp_N 

1595 GG_unemp_permute = np.concatenate( 

1596 [ 

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

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

1599 ] 

1600 ) 

1601 GG_emp_permute = np.concatenate( 

1602 [ 

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

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

1605 ] 

1606 ) 

1607 

1608 # Store transition matrices as attributes of self 

1609 self.unemp_permute = [ 

1610 [BB_unemp_permute, BG_unemp_permute], 

1611 [GB_unemp_permute, GG_unemp_permute], 

1612 ] 

1613 self.emp_permute = [ 

1614 [BB_emp_permute, BG_emp_permute], 

1615 [GB_emp_permute, GG_emp_permute], 

1616 ] 

1617 

1618 def reset(self): 

1619 self.initialize_sim() 

1620 

1621 def market_action(self): 

1622 self.simulate(1) 

1623 

1624 def initialize_sim(self): 

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

1626 AgentType.initialize_sim(self) 

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

1628 self.make_emp_idx_arrays() 

1629 

1630 def sim_birth(self, which): 

1631 """ 

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

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

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

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

1636 is the correct behavior for the model. 

1637 """ 

1638 N = np.sum(which) 

1639 if N == 0: 

1640 return 

1641 

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

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

1644 emp_N = self.AgentCount - unemp_N 

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

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

1647 emp_N = self.AgentCount - unemp_N 

1648 else: 

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

1650 EmpNew = np.concatenate( 

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

1652 ) 

1653 

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

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

1656 

1657 def get_shocks(self): 

1658 """ 

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

1660 """ 

1661 # Get boolean arrays for current employment states 

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

1663 unemployed = np.logical_not(employed) 

1664 

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

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

1667 

1668 # Transition some agents between unemployment and employment 

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

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

1671 # TODO: replace poststate_vars functionality with shocks here 

1672 EmpNow = self.state_now["EmpNow"] 

1673 

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

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

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

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

1678 

1679 def get_states(self): 

1680 """ 

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

1682 """ 

1683 self.state_now["mNow"] = ( 

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

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

1686 ) 

1687 

1688 def get_controls(self): 

1689 """ 

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

1691 """ 

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

1693 unemployed = np.logical_not(employed) 

1694 

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

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

1697 unemp_idx = 0 

1698 emp_idx = 1 

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

1700 unemp_idx = 2 

1701 emp_idx = 3 

1702 else: 

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

1704 

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

1706 cNow = np.zeros(self.AgentCount) 

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

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

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

1710 ) 

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

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

1713 ) 

1714 self.controls["cNow"] = cNow 

1715 

1716 def get_poststates(self): 

1717 """ 

1718 Gets each agent's retained assets after consumption. 

1719 """ 

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

1721 

1722 

1723CRRA = 2.0 

1724DiscFac = 0.96 

1725 

1726# Parameters for a Cobb-Douglas economy 

1727PermGroFacAgg = 1.00 # Aggregate permanent income growth factor 

1728PermShkAggCount = ( 

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

1730) 

1731TranShkAggCount = ( 

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

1733) 

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

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

1736DeprFac = 0.025 # Capital depreciation rate 

1737CapShare = 0.36 # Capital's share of income 

1738DiscFacPF = DiscFac # Discount factor of perfect foresight calibration 

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

1740intercept_prev = 0.0 # Intercept of aggregate savings function 

1741slope_prev = 1.0 # Slope of aggregate savings function 

1742verbose_cobb_douglas = ( 

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

1744) 

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

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

1747DampingFac = 0.5 

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

1749 

1750 

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

1752init_cobb_douglas = { 

1753 "PermShkAggCount": PermShkAggCount, 

1754 "TranShkAggCount": TranShkAggCount, 

1755 "PermShkAggStd": PermShkAggStd, 

1756 "TranShkAggStd": TranShkAggStd, 

1757 "DeprFac": DeprFac, 

1758 "CapShare": CapShare, 

1759 "DiscFac": DiscFacPF, 

1760 "CRRA": CRRAPF, 

1761 "PermGroFacAgg": PermGroFacAgg, 

1762 "AggregateL": 1.0, 

1763 "intercept_prev": intercept_prev, 

1764 "slope_prev": slope_prev, 

1765 "verbose": verbose_cobb_douglas, 

1766 "T_discard": T_discard, 

1767 "DampingFac": DampingFac, 

1768 "max_loops": max_loops, 

1769} 

1770 

1771 

1772class CobbDouglasEconomy(Market): 

1773 """ 

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

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

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

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

1778 rate for the upcoming period. 

1779 

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

1781 this will be generalized in the future. 

1782 

1783 Parameters 

1784 ---------- 

1785 agents : [ConsumerType] 

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

1787 tolerance: float 

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

1789 solution process converged. Distance depends on intercept and slope 

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

1791 act_T : int 

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

1793 """ 

1794 

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

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

1797 params = init_cobb_douglas.copy() 

1798 params["sow_vars"] = [ 

1799 "MaggNow", 

1800 "AaggNow", 

1801 "RfreeNow", 

1802 "wRteNow", 

1803 "PermShkAggNow", 

1804 "TranShkAggNow", 

1805 "KtoLnow", 

1806 ] 

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

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

1809 params["dyn_vars"] = ["AFunc"] 

1810 params.update(kwds) 

1811 

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

1813 self.update() 

1814 

1815 def mill_rule(self, aLvl, pLvl): 

1816 """ 

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

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

1819 

1820 See documentation for calc_R_and_W for more information. 

1821 """ 

1822 return self.calc_R_and_W(aLvl, pLvl) 

1823 

1824 def calc_dynamics(self, MaggNow, AaggNow): 

1825 """ 

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

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

1828 

1829 See documentation for calc_AFunc for more information. 

1830 """ 

1831 return self.calc_AFunc(MaggNow, AaggNow) 

1832 

1833 def update(self): 

1834 """ 

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

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

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

1838 

1839 Parameters 

1840 ---------- 

1841 None 

1842 

1843 Returns 

1844 ------- 

1845 None 

1846 """ 

1847 self.kSS = ( 

1848 ( 

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

1850 - (1.0 - self.DeprFac) 

1851 ) 

1852 / self.CapShare 

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

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

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

1856 self.RfreeSS = ( 

1857 1.0 + self.CapShare * self.kSS ** (self.CapShare - 1.0) - self.DeprFac 

1858 ) 

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

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

1861 1.0 / (1.0 - self.CapShare) 

1862 ) # converts K/Y to K/L 

1863 self.Rfunc = lambda k: ( 

1864 1.0 + self.CapShare * k ** (self.CapShare - 1.0) - self.DeprFac 

1865 ) 

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

1867 

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

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

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

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

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

1873 self.sow_init["PermShkAggNow"] = 1.0 

1874 self.sow_init["TranShkAggNow"] = 1.0 

1875 self.make_AggShkDstn() 

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

1877 

1878 def get_PermGroFacAggLR(self): 

1879 """ 

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

1881 and extended by ConsAggShockMarkov model. 

1882 

1883 Parameters 

1884 ---------- 

1885 None 

1886 

1887 Returns 

1888 ------- 

1889 PermGroFacAggLR : float 

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

1891 as aggregate permanent income growth. 

1892 """ 

1893 return self.PermGroFacAgg 

1894 

1895 def make_AggShkDstn(self): 

1896 """ 

1897 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

1899 

1900 Parameters 

1901 ---------- 

1902 None 

1903 

1904 Returns 

1905 ------- 

1906 None 

1907 """ 

1908 self.TranShkAggDstn = MeanOneLogNormal(sigma=self.TranShkAggStd).discretize( 

1909 N=self.TranShkAggCount, method="equiprobable" 

1910 ) 

1911 self.PermShkAggDstn = MeanOneLogNormal(sigma=self.PermShkAggStd).discretize( 

1912 N=self.PermShkAggCount, method="equiprobable" 

1913 ) 

1914 self.AggShkDstn = combine_indep_dstns(self.PermShkAggDstn, self.TranShkAggDstn) 

1915 

1916 def reset(self): 

1917 """ 

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

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

1920 

1921 Parameters 

1922 ---------- 

1923 None 

1924 

1925 Returns 

1926 ------- 

1927 None 

1928 """ 

1929 self.Shk_idx = 0 

1930 Market.reset(self) 

1931 

1932 def make_AggShkHist(self): 

1933 """ 

1934 Make simulated histories of aggregate transitory and permanent shocks. 

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

1936 simulation. 

1937 

1938 Parameters 

1939 ---------- 

1940 None 

1941 

1942 Returns 

1943 ------- 

1944 None 

1945 """ 

1946 sim_periods = self.act_T 

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

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

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

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

1951 

1952 # Store the histories 

1953 self.PermShkAggHist = PermShkAggHist * self.PermGroFacAgg 

1954 self.TranShkAggHist = TranShkAggHist 

1955 

1956 def calc_R_and_W(self, aLvlNow, pLvlNow): 

1957 """ 

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

1959 capital stock to get the aggregate capital ratio. 

1960 

1961 Parameters 

1962 ---------- 

1963 aLvlNow : [np.array] 

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

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

1966 

1967 Returns 

1968 ------- 

1969 MaggNow : float 

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

1971 AaggNow : float 

1972 Aggregate savings for this period normalized by mean permanent income 

1973 RfreeNow : float 

1974 Interest factor on assets in the economy this period. 

1975 wRteNow : float 

1976 Wage rate for labor in the economy this period. 

1977 PermShkAggNow : float 

1978 Permanent shock to aggregate labor productivity this period. 

1979 TranShkAggNow : float 

1980 Transitory shock to aggregate labor productivity this period. 

1981 KtoLnow : float 

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

1983 """ 

1984 # Calculate aggregate savings 

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

1986 pLvlNow 

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

1988 # Calculate aggregate capital this period 

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

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

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

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

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

1994 

1995 # Get this period's aggregate shocks 

1996 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

1997 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

1998 self.Shk_idx += 1 

1999 

2000 AggregateL = np.mean(pLvlNow) * PermShkAggNow 

2001 

2002 # Calculate the interest factor and wage rate this period 

2003 KtoLnow = AggregateK / AggregateL 

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

2005 RfreeNow = self.Rfunc(KtoLnow / TranShkAggNow) 

2006 wRteNow = self.wFunc(KtoLnow / TranShkAggNow) 

2007 MaggNow = KtoLnow * RfreeNow + wRteNow * TranShkAggNow 

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

2009 

2010 # Package the results into an object and return it 

2011 return ( 

2012 MaggNow, 

2013 AaggPrev, 

2014 RfreeNow, 

2015 wRteNow, 

2016 PermShkAggNow, 

2017 TranShkAggNow, 

2018 KtoLnow, 

2019 ) 

2020 

2021 def calc_AFunc(self, MaggNow, AaggNow): 

2022 """ 

2023 Calculate a new aggregate savings rule based on the history 

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

2025 

2026 Parameters 

2027 ---------- 

2028 MaggNow : [float] 

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

2030 AaggNow : [float] 

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

2032 

2033 Returns 

2034 ------- 

2035 (unnamed) : CapDynamicRule 

2036 Object containing a new savings rule 

2037 """ 

2038 verbose = self.verbose 

2039 discard_periods = ( 

2040 self.T_discard 

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

2042 update_weight = ( 

2043 1.0 - self.DampingFac 

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

2045 total_periods = len(MaggNow) 

2046 

2047 # Regress the log savings against log market resources 

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

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

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

2051 

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

2053 # with the previous guess 

2054 intercept = ( 

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

2056 ) 

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

2058 AFunc = AggregateSavingRule( 

2059 intercept, slope 

2060 ) # Make a new next-period capital function 

2061 

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

2063 self.intercept_prev = intercept 

2064 self.slope_prev = slope 

2065 

2066 # Print the new parameters 

2067 if verbose: 

2068 print( 

2069 "intercept=" 

2070 + str(intercept) 

2071 + ", slope=" 

2072 + str(slope) 

2073 + ", r-sq=" 

2074 + str(r_value**2) 

2075 ) 

2076 

2077 return AggShocksDynamicRule(AFunc) 

2078 

2079 

2080class SmallOpenEconomy(Market): 

2081 """ 

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

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

2084 aggregate productivity shocks. 

2085 

2086 Parameters 

2087 ---------- 

2088 agents : [ConsumerType] 

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

2090 tolerance: float 

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

2092 solution process converged. Distance depends on intercept and slope 

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

2094 act_T : int 

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

2096 """ 

2097 

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

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

2100 Market.__init__( 

2101 self, 

2102 agents=agents, 

2103 sow_vars=[ 

2104 "MaggNow", 

2105 "AaggNow", 

2106 "RfreeNow", 

2107 "wRteNow", 

2108 "PermShkAggNow", 

2109 "TranShkAggNow", 

2110 "KtoLnow", 

2111 ], 

2112 reap_vars=[], 

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

2114 dyn_vars=[], 

2115 tolerance=tolerance, 

2116 act_T=act_T, 

2117 ) 

2118 self.assign_parameters(**kwds) 

2119 self.update() 

2120 

2121 def update(self): 

2122 """ 

2123 Use primitive parameters to set basic objects. 

2124 This is an extremely stripped-down version 

2125 of update for CobbDouglasEconomy. 

2126 

2127 Parameters 

2128 ---------- 

2129 none 

2130 

2131 Returns 

2132 ------- 

2133 none 

2134 """ 

2135 self.kSS = 1.0 

2136 self.MSS = 1.0 

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

2138 self.Rfunc = ConstantFunction(self.Rfree) 

2139 self.wFunc = ConstantFunction(self.wRte) 

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

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

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

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

2144 self.sow_init["PermShkAggNow"] = 1.0 

2145 self.sow_init["TranShkAggNow"] = 1.0 

2146 self.make_AggShkDstn() 

2147 self.AFunc = ConstantFunction(1.0) 

2148 

2149 def make_AggShkDstn(self): 

2150 """ 

2151 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

2153 

2154 Parameters 

2155 ---------- 

2156 None 

2157 

2158 Returns 

2159 ------- 

2160 None 

2161 """ 

2162 self.TranShkAggDstn = MeanOneLogNormal(sigma=self.TranShkAggStd).discretize( 

2163 N=self.TranShkAggCount, method="equiprobable" 

2164 ) 

2165 self.PermShkAggDstn = MeanOneLogNormal(sigma=self.PermShkAggStd).discretize( 

2166 N=self.PermShkAggCount, method="equiprobable" 

2167 ) 

2168 self.AggShkDstn = combine_indep_dstns(self.PermShkAggDstn, self.TranShkAggDstn) 

2169 

2170 def mill_rule(self): 

2171 """ 

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

2173 exogenously determined. However, aggregate shocks may occur. 

2174 

2175 See documentation for get_AggShocks() for more information. 

2176 """ 

2177 return self.get_AggShocks() 

2178 

2179 def calc_dynamics(self, KtoLnow): 

2180 """ 

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

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

2183 """ 

2184 return MetricObject() 

2185 

2186 def reset(self): 

2187 """ 

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

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

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

2191 

2192 Parameters 

2193 ---------- 

2194 None 

2195 

2196 Returns 

2197 ------- 

2198 None 

2199 """ 

2200 self.Shk_idx = 0 

2201 Market.reset(self) 

2202 

2203 def make_AggShkHist(self): 

2204 """ 

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

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

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

2208 

2209 Parameters 

2210 ---------- 

2211 None 

2212 

2213 Returns 

2214 ------- 

2215 None 

2216 """ 

2217 sim_periods = self.act_T 

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

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

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

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

2222 

2223 # Store the histories 

2224 self.PermShkAggHist = PermShkAggHist 

2225 self.TranShkAggHist = TranShkAggHist 

2226 

2227 def get_AggShocks(self): 

2228 """ 

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

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

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

2232 

2233 Parameters 

2234 ---------- 

2235 None 

2236 

2237 Returns 

2238 ------- 

2239 MaggNow : float 

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

2241 AaggNow : float 

2242 Aggregate savings for this period normalized by mean permanent income 

2243 RfreeNow : float 

2244 Interest factor on assets in the economy this period. 

2245 wRteNow : float 

2246 Wage rate for labor in the economy this period. 

2247 PermShkAggNow : float 

2248 Permanent shock to aggregate labor productivity this period. 

2249 TranShkAggNow : float 

2250 Transitory shock to aggregate labor productivity this period. 

2251 KtoLnow : float 

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

2253 

2254 """ 

2255 # Get this period's aggregate shocks 

2256 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

2257 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

2258 self.Shk_idx += 1 

2259 

2260 # Factor prices are constant 

2261 RfreeNow = self.Rfunc(1.0 / PermShkAggNow) 

2262 wRteNow = self.wFunc(1.0 / PermShkAggNow) 

2263 

2264 # Aggregates are irrelavent 

2265 AaggNow = 1.0 

2266 MaggNow = 1.0 

2267 KtoLnow = 1.0 / PermShkAggNow 

2268 

2269 return ( 

2270 MaggNow, 

2271 AaggNow, 

2272 RfreeNow, 

2273 wRteNow, 

2274 PermShkAggNow, 

2275 TranShkAggNow, 

2276 KtoLnow, 

2277 ) 

2278 

2279 

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

2281init_mrkv_cobb_douglas = init_cobb_douglas.copy() 

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

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

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

2285init_mrkv_cobb_douglas["MrkvArray"] = MrkvArray 

2286init_mrkv_cobb_douglas["MrkvNow_init"] = 0 

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

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

2289 

2290 

2291class CobbDouglasMarkovEconomy(CobbDouglasEconomy): 

2292 """ 

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

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

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

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

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

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

2299 productivity growth factor can vary over time. 

2300 

2301 Parameters 

2302 ---------- 

2303 agents : [ConsumerType] 

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

2305 tolerance: float 

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

2307 solution process converged. Distance depends on intercept and slope 

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

2309 act_T : int 

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

2311 """ 

2312 

2313 def __init__( 

2314 self, 

2315 agents=None, 

2316 tolerance=0.0001, 

2317 act_T=1200, 

2318 sow_vars=[ 

2319 "MaggNow", 

2320 "AaggNow", 

2321 "RfreeNow", 

2322 "wRteNow", 

2323 "PermShkAggNow", 

2324 "TranShkAggNow", 

2325 "KtoLnow", 

2326 "Mrkv", # This one is new 

2327 ], 

2328 **kwds, 

2329 ): 

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

2331 params = init_mrkv_cobb_douglas.copy() 

2332 params.update(kwds) 

2333 

2334 CobbDouglasEconomy.__init__( 

2335 self, 

2336 agents=agents, 

2337 tolerance=tolerance, 

2338 act_T=act_T, 

2339 sow_vars=sow_vars, 

2340 **params, 

2341 ) 

2342 

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

2344 

2345 def update(self): 

2346 """ 

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

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

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

2350 

2351 Parameters 

2352 ---------- 

2353 None 

2354 

2355 Returns 

2356 ------- 

2357 None 

2358 """ 

2359 CobbDouglasEconomy.update(self) 

2360 StateCount = self.MrkvArray.shape[0] 

2361 AFunc_all = [] 

2362 for i in range(StateCount): 

2363 AFunc_all.append( 

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

2365 ) 

2366 self.AFunc = AFunc_all 

2367 

2368 def get_PermGroFacAggLR(self): 

2369 """ 

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

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

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

2373 

2374 Parameters 

2375 ---------- 

2376 None 

2377 

2378 Returns 

2379 ------- 

2380 PermGroFacAggLR : float 

2381 Long run aggregate permanent income growth factor 

2382 """ 

2383 # Find the long run distribution of Markov states 

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

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

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

2387 LR_dstn = x / np.sum(x) 

2388 

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

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

2391 return PermGroFacAggLR 

2392 

2393 def make_AggShkDstn(self): 

2394 """ 

2395 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

2397 This version accounts for the Markov macroeconomic state. 

2398 

2399 Parameters 

2400 ---------- 

2401 None 

2402 

2403 Returns 

2404 ------- 

2405 None 

2406 """ 

2407 TranShkAggDstn = [] 

2408 PermShkAggDstn = [] 

2409 AggShkDstn = [] 

2410 StateCount = self.MrkvArray.shape[0] 

2411 

2412 for i in range(StateCount): 

2413 TranShkAggDstn.append( 

2414 MeanOneLogNormal(sigma=self.TranShkAggStd[i]).discretize( 

2415 N=self.TranShkAggCount, method="equiprobable" 

2416 ) 

2417 ) 

2418 PermShkAggDstn.append( 

2419 MeanOneLogNormal(sigma=self.PermShkAggStd[i]).discretize( 

2420 N=self.PermShkAggCount, method="equiprobable" 

2421 ) 

2422 ) 

2423 AggShkDstn.append( 

2424 combine_indep_dstns(PermShkAggDstn[-1], TranShkAggDstn[-1]) 

2425 ) 

2426 

2427 self.TranShkAggDstn = TranShkAggDstn 

2428 self.PermShkAggDstn = PermShkAggDstn 

2429 self.AggShkDstn = AggShkDstn 

2430 

2431 def make_AggShkHist(self): 

2432 """ 

2433 Make simulated histories of aggregate transitory and permanent shocks. 

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

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

2436 internal call to make_Mrkv_history(). 

2437 

2438 Parameters 

2439 ---------- 

2440 None 

2441 

2442 Returns 

2443 ------- 

2444 None 

2445 """ 

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

2447 sim_periods = self.act_T 

2448 

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

2450 # that would occur in that state in that period 

2451 StateCount = self.MrkvArray.shape[0] 

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

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

2454 for i in range(StateCount): 

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

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

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

2458 

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

2460 # of Markov states that the economy experiences 

2461 PermShkAggHist = np.zeros(sim_periods) 

2462 TranShkAggHist = np.zeros(sim_periods) 

2463 for i in range(StateCount): 

2464 these = i == self.MrkvNow_hist 

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

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

2467 

2468 # Store the histories 

2469 self.PermShkAggHist = PermShkAggHist 

2470 self.TranShkAggHist = TranShkAggHist 

2471 

2472 def make_Mrkv_history(self): 

2473 """ 

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

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

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

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

2478 initially specified level. 

2479 

2480 Parameters 

2481 ---------- 

2482 None 

2483 

2484 Returns 

2485 ------- 

2486 None 

2487 """ 

2488 if hasattr(self, "loops_max"): 

2489 loops_max = self.loops_max 

2490 else: # Maximum number of loops; final act_T never exceeds act_T*loops_max 

2491 loops_max = 10 

2492 

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

2494 logit_scale = ( 

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

2496 ) 

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

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

2499 

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

2501 if hasattr(self, "act_T_orig"): 

2502 act_T = self.act_T_orig 

2503 else: # Or store it for the first time 

2504 self.act_T_orig = self.act_T 

2505 act_T = self.act_T 

2506 

2507 # Find the long run distribution of Markov states 

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

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

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

2511 LR_dstn = x / np.sum(x) 

2512 

2513 # Initialize the Markov history and set up transitions 

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

2515 loops = 0 

2516 go = True 

2517 MrkvNow = self.sow_init["Mrkv"] 

2518 t = 0 

2519 StateCount = self.MrkvArray.shape[0] 

2520 

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

2522 while go: 

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

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

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

2526 MrkvNow_hist[t] = MrkvNow 

2527 MrkvNow = markov_process.draw(MrkvNow) 

2528 t += 1 

2529 

2530 # Calculate the empirical distribution 

2531 state_T = np.zeros(StateCount) 

2532 for i in range(StateCount): 

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

2534 

2535 # Check whether each state has been visited state_T_min times 

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

2537 go = False # If so, terminate the loop 

2538 continue 

2539 

2540 # Choose an underrepresented state to "jump" to 

2541 if np.any( 

2542 state_T == 0 

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

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

2545 MrkvNow = np.random.choice(never_visited) 

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

2547 emp_dstn = state_T / act_T 

2548 ratios = LR_dstn / emp_dstn 

2549 ratios_adj = ratios - np.max(ratios) 

2550 ratios_exp = np.exp(ratios_adj / logit_scale) 

2551 ratios_sum = np.sum(ratios_exp) 

2552 jump_probs = ratios_exp / ratios_sum 

2553 cum_probs = np.cumsum(jump_probs) 

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

2555 

2556 loops += 1 

2557 # Make the Markov state history longer by act_T_orig periods 

2558 if loops >= loops_max: 

2559 go = False 

2560 print( 

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

2562 ) 

2563 else: 

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

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

2566 act_T += self.act_T_orig 

2567 

2568 # Store the results as attributes of self 

2569 self.MrkvNow_hist = MrkvNow_hist 

2570 self.act_T = act_T 

2571 

2572 def mill_rule(self, aLvl, pLvl): 

2573 """ 

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

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

2576 and adds the Markov state index. 

2577 

2578 See documentation for calc_R_and_W for more information. 

2579 

2580 Params 

2581 ------- 

2582 aLvl : float 

2583 pLvl : float 

2584 

2585 Returns 

2586 ------- 

2587 Mnow : float 

2588 Aggregate market resources for this period. 

2589 Aprev : float 

2590 Aggregate savings for the prior period. 

2591 KtoLnow : float 

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

2593 Rnow : float 

2594 Interest factor on assets in the economy this period. 

2595 Wnow : float 

2596 Wage rate for labor in the economy this period. 

2597 MrkvNow : int 

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

2599 """ 

2600 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2601 temp = self.calc_R_and_W(aLvl, pLvl) 

2602 

2603 return temp + (MrkvNow,) 

2604 

2605 def calc_AFunc(self, MaggNow, AaggNow): 

2606 """ 

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

2608 aggregate savings and aggregate market resources from a simulation. 

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

2610 

2611 Parameters 

2612 ---------- 

2613 MaggNow : [float] 

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

2615 AaggNow : [float] 

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

2617 

2618 Returns 

2619 ------- 

2620 (unnamed) : CapDynamicRule 

2621 Object containing new saving rules for each Markov state. 

2622 """ 

2623 verbose = self.verbose 

2624 discard_periods = ( 

2625 self.T_discard 

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

2627 update_weight = ( 

2628 1.0 - self.DampingFac 

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

2630 total_periods = len(MaggNow) 

2631 

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

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

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

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

2636 

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

2638 AFunc_list = [] 

2639 rSq_list = [] 

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

2641 these = i == MrkvHist 

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

2643 logMagg[these], logAagg[these] 

2644 ) 

2645 

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

2647 # with the previous guess 

2648 intercept = ( 

2649 update_weight * intercept 

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

2651 ) 

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

2653 AFunc_list.append( 

2654 AggregateSavingRule(intercept, slope) 

2655 ) # Make a new next-period capital function 

2656 rSq_list.append(r_value**2) 

2657 

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

2659 self.intercept_prev[i] = intercept 

2660 self.slope_prev[i] = slope 

2661 

2662 # Print the new parameters 

2663 if verbose: 

2664 print( 

2665 "intercept=" 

2666 + str(self.intercept_prev) 

2667 + ", slope=" 

2668 + str(self.slope_prev) 

2669 + ", r-sq=" 

2670 + str(rSq_list) 

2671 ) 

2672 

2673 return AggShocksDynamicRule(AFunc_list) 

2674 

2675 

2676class SmallOpenMarkovEconomy(CobbDouglasMarkovEconomy, SmallOpenEconomy): 

2677 """ 

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

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

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

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

2682 """ 

2683 

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

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

2686 CobbDouglasMarkovEconomy.__init__( 

2687 self, agents=agents, tolerance=tolerance, act_T=act_T, **kwds 

2688 ) 

2689 self.reap_vars = [] 

2690 self.dyn_vars = [] 

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=MrkvNow) 

2704 return temp 

2705 

2706 def calc_dynamics(self, KtoLnow): 

2707 return MetricObject() 

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 "DeprFac": 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.DeprFac)) / 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.DeprFac 

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.DeprFac 

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: 

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"]