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

897 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-08 05:31 +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 construct_markov_lognormal_income_process_unemployment, 

17 get_PermShkDstn_from_IncShkDstn, 

18 get_TranShkDstn_from_IncShkDstn, 

19 combine_ind_and_agg_income_shocks, 

20 combine_markov_ind_and_agg_income_shocks, 

21 get_PermShkDstn_from_IncShkDstn_markov, 

22 get_TranShkDstn_from_IncShkDstn_markov, 

23) 

24from HARK.ConsumptionSaving.ConsIndShockModel import ( 

25 ConsumerSolution, 

26 IndShockConsumerType, 

27 make_lognormal_kNrm_init_dstn, 

28 make_lognormal_pLvl_init_dstn, 

29) 

30from HARK.distributions import ( 

31 MarkovProcess, 

32 MeanOneLogNormal, 

33 Uniform, 

34 calc_expectation, 

35 combine_indep_dstns, 

36) 

37from HARK.interpolation import ( 

38 BilinearInterp, 

39 ConstantFunction, 

40 IdentityFunction, 

41 LinearInterp, 

42 LinearInterpOnInterp1D, 

43 LowerEnvelope2D, 

44 MargValueFuncCRRA, 

45 UpperEnvelope, 

46 VariableLowerBoundFunc2D, 

47) 

48from HARK.metric import MetricObject 

49from HARK.rewards import ( 

50 CRRAutility, 

51 CRRAutility_inv, 

52 CRRAutility_invP, 

53 CRRAutilityP, 

54 CRRAutilityP_inv, 

55 CRRAutilityPP, 

56) 

57from HARK.utilities import make_assets_grid, get_it_from, NullFunc 

58 

59__all__ = [ 

60 "AggShockConsumerType", 

61 "AggShockMarkovConsumerType", 

62 "CobbDouglasEconomy", 

63 "SmallOpenEconomy", 

64 "CobbDouglasMarkovEconomy", 

65 "SmallOpenMarkovEconomy", 

66 "AggregateSavingRule", 

67 "AggShocksDynamicRule", 

68 "init_agg_shocks", 

69 "init_agg_mrkv_shocks", 

70 "init_cobb_douglas", 

71 "init_mrkv_cobb_douglas", 

72] 

73 

74utility = CRRAutility 

75utilityP = CRRAutilityP 

76utilityPP = CRRAutilityPP 

77utilityP_inv = CRRAutilityP_inv 

78utility_invP = CRRAutility_invP 

79utility_inv = CRRAutility_inv 

80 

81 

82def make_aggshock_solution_terminal(CRRA): 

83 """ 

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

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

86 

87 Parameters 

88 ---------- 

89 CRRA : float 

90 Coefficient of relative risk aversion. 

91 

92 Returns 

93 ------- 

94 solution_terminal : ConsumerSolution 

95 Solution to the terminal period problem. 

96 """ 

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

98 vPfunc_terminal = MargValueFuncCRRA(cFunc_terminal, CRRA) 

99 mNrmMin_terminal = ConstantFunction(0) 

100 solution_terminal = ConsumerSolution( 

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

102 ) 

103 return solution_terminal 

104 

105 

106def make_aggmrkv_solution_terminal(CRRA, MrkvArray): 

107 """ 

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

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

110 value function. 

111 

112 Parameters 

113 ---------- 

114 CRRA : float 

115 Coefficient of relative risk aversion. 

116 MrkvArray : np.array 

117 Transition probability array. 

118 

119 Returns 

120 ------- 

121 solution_terminal : ConsumerSolution 

122 Solution to the terminal period problem. 

123 """ 

124 solution_terminal = make_aggshock_solution_terminal(CRRA) 

125 

126 # Make replicated terminal period solution 

127 StateCount = MrkvArray.shape[0] 

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

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

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

131 

132 return solution_terminal 

133 

134 

135def make_exponential_MgridBase(MaggCount, MaggPerturb, MaggExpFac): 

136 """ 

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

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

139 

140 Parameters 

141 ---------- 

142 MaggCount : int 

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

144 MaggPerturb : float 

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

146 1+perturb and 1-perturb. 

147 MaggExpFac : float 

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

149 

150 Returns 

151 ------- 

152 MgridBase : np.array 

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

154 """ 

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

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

157 fac = np.exp(MaggExpFac) 

158 for n in range(N - 1): 

159 new_hi = gridpoints[-1] * fac 

160 new_lo = gridpoints[0] / fac 

161 gridpoints.append(new_hi) 

162 gridpoints.insert(0, new_lo) 

163 MgridBase = np.array(gridpoints) 

164 return MgridBase 

165 

166 

167def make_Mgrid(MgridBase, MSS): 

168 """ 

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

170 """ 

171 return MSS * MgridBase 

172 

173 

174############################################################################### 

175 

176 

177def solveConsAggShock( 

178 solution_next, 

179 IncShkDstn, 

180 LivPrb, 

181 DiscFac, 

182 CRRA, 

183 PermGroFac, 

184 PermGroFacAgg, 

185 aXtraGrid, 

186 BoroCnstArt, 

187 Mgrid, 

188 AFunc, 

189 Rfunc, 

190 wFunc, 

191 DeprRte, 

192): 

193 """ 

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

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

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

197 version uses calc_expectation to reduce code clutter. 

198 

199 Parameters 

200 ---------- 

201 solution_next : ConsumerSolution 

202 The solution to the succeeding one period problem. 

203 IncShkDstn : distribution.Distribution 

204 A discrete 

205 approximation to the income process between the period being solved 

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

207 idiosyncratic permanent shocks, idiosyncratic transitory 

208 shocks, aggregate permanent shocks, aggregate transitory shocks. 

209 LivPrb : float 

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

211 the succeeding period. 

212 DiscFac : float 

213 Intertemporal discount factor for future utility. 

214 CRRA : float 

215 Coefficient of relative risk aversion. 

216 PermGroFac : float 

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

218 PermGroFacAgg : float 

219 Expected aggregate productivity growth factor. 

220 aXtraGrid : np.array 

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

222 absolute minimum acceptable level. 

223 BoroCnstArt : float 

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

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

226 Mgrid : np.array 

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

228 AFunc : function 

229 Aggregate savings as a function of aggregate market resources. 

230 Rfunc : function 

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

232 wFunc : function 

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

234 DeprRte : float 

235 Capital Depreciation Rate 

236 

237 Returns 

238 ------- 

239 solution_now : ConsumerSolution 

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

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

242 tions) and marginal value function vPfunc. 

243 """ 

244 # Unpack the income shocks and get grid sizes 

245 PermShkValsNext = IncShkDstn.atoms[0] 

246 TranShkValsNext = IncShkDstn.atoms[1] 

247 PermShkAggValsNext = IncShkDstn.atoms[2] 

248 TranShkAggValsNext = IncShkDstn.atoms[3] 

249 aCount = aXtraGrid.size 

250 Mcount = Mgrid.size 

251 

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

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

254 def calcAggObjects(M, Psi, Theta): 

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

256 # Next period's aggregate capital/labor ratio 

257 kNext = A / (PermGroFacAgg * Psi) 

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

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

260 wEff = ( 

261 wFunc(kNextEff) * Theta 

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

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

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

265 return Mnext, Reff, wEff 

266 

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

268 def vPnextFunc(S, a, M): 

269 psi = S[0] 

270 theta = S[1] 

271 Psi = S[2] 

272 Theta = S[3] 

273 

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

275 PermShkTotal = ( 

276 PermGroFac * PermGroFacAgg * psi * Psi 

277 ) # Total / combined permanent shock 

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

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

280 return vPnext 

281 

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

283 # Natural borrowing constraint at each M_t 

284 BoroCnstNat_vec = np.zeros(Mcount) 

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

286 for j in range(Mcount): 

287 Mnext, Reff, wEff = calcAggObjects( 

288 Mgrid[j], PermShkAggValsNext, TranShkAggValsNext 

289 ) 

290 aNrmMin_cand = ( 

291 PermGroFac * PermGroFacAgg * PermShkValsNext * PermShkAggValsNext / Reff 

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

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

294 aNrmNow[:, j] = aNrmMin + aXtraGrid 

295 BoroCnstNat_vec[j] = aNrmMin 

296 

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

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

299 EndOfPrdvP = ( 

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

301 ) 

302 

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

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

305 mNrmNow = aNrmNow + cNrmNow 

306 

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

308 cFuncBaseByM_list = [] 

309 for j in range(Mcount): 

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

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

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

313 

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

315 BoroCnstNat = LinearInterp( 

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

317 ) 

318 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

319 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

320 

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

322 cFuncCnst = BilinearInterp( 

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

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

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

326 ) 

327 cFuncNow = LowerEnvelope2D(cFuncUnc, cFuncCnst) 

328 

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

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

331 

332 # Construct the marginal value function using the envelope condition 

333 vPfuncNow = MargValueFuncCRRA(cFuncNow, CRRA) 

334 

335 # Pack up and return the solution 

336 solution_now = ConsumerSolution( 

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

338 ) 

339 return solution_now 

340 

341 

342############################################################################### 

343 

344 

345def solve_ConsAggMarkov( 

346 solution_next, 

347 IncShkDstn, 

348 LivPrb, 

349 DiscFac, 

350 CRRA, 

351 MrkvArray, 

352 PermGroFac, 

353 PermGroFacAgg, 

354 aXtraGrid, 

355 BoroCnstArt, 

356 Mgrid, 

357 AFunc, 

358 Rfunc, 

359 wFunc, 

360): 

361 """ 

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

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

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

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

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

367 

368 Parameters 

369 ---------- 

370 solution_next : ConsumerSolution 

371 The solution to the succeeding one period problem. 

372 IncShkDstn : [distribution.Distribution] 

373 A list of 

374 discrete approximations to the income process between the period being 

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

376 idisyncratic permanent shocks, idiosyncratic transitory 

377 shocks, aggregate permanent shocks, aggregate transitory shocks. 

378 LivPrb : float 

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

380 the succeeding period. 

381 DiscFac : float 

382 Intertemporal discount factor for future utility. 

383 CRRA : float 

384 Coefficient of relative risk aversion. 

385 MrkvArray : np.array 

386 Markov transition matrix between discrete macroeconomic states. 

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

388 on being in state i this period. 

389 PermGroFac : float 

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

391 for the *individual*'s productivity. 

392 PermGroFacAgg : [float] 

393 Expected aggregate productivity growth in each Markov macro state. 

394 aXtraGrid : np.array 

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

396 absolute minimum acceptable level. 

397 BoroCnstArt : float 

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

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

400 Mgrid : np.array 

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

402 AFunc : [function] 

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

404 Markov macro state. 

405 Rfunc : function 

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

407 wFunc : function 

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

409 DeprRte : float 

410 Capital Depreciation Rate 

411 

412 Returns 

413 ------- 

414 solution_now : ConsumerSolution 

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

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

417 tions) and marginal value function vPfunc. 

418 """ 

419 # Get sizes of grids 

420 aCount = aXtraGrid.size 

421 Mcount = Mgrid.size 

422 StateCount = MrkvArray.shape[0] 

423 

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

425 # Construct EndOfPrdvP_cond functions for each state. 

426 EndOfPrdvPfunc_cond = [] 

427 BoroCnstNat_cond = [] 

428 for j in range(StateCount): 

429 # Unpack next period's solution 

430 vPfuncNext = solution_next.vPfunc[j] 

431 mNrmMinNext = solution_next.mNrmMin[j] 

432 

433 # Unpack the income shocks 

434 ShkPrbsNext = IncShkDstn[j].pmv 

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

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

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

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

439 ShkCount = ShkPrbsNext.size 

440 aXtra_tiled = np.tile( 

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

442 ) 

443 

444 # Make tiled versions of the income shocks 

445 # Dimension order: Mnow, aNow, Shk 

446 ShkPrbsNext_tiled = np.tile( 

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

448 ) 

449 PermShkValsNext_tiled = np.tile( 

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

451 ) 

452 TranShkValsNext_tiled = np.tile( 

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

454 ) 

455 PermShkAggValsNext_tiled = np.tile( 

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

457 ) 

458 TranShkAggValsNext_tiled = np.tile( 

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

460 ) 

461 

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

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

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

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

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

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

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

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

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

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

472 # conditional marginal value functions are constructed is not relevant 

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

474 AaggGrid = AFunc[j](Mgrid) 

475 AaggNow_tiled = np.tile( 

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

477 ) 

478 

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

480 kNext_array = AaggNow_tiled / ( 

481 PermGroFacAgg[j] * PermShkAggValsNext_tiled 

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

483 kNextEff_array = ( 

484 kNext_array / TranShkAggValsNext_tiled 

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

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

487 Reff_array = ( 

488 R_array / LivPrb 

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

490 wEff_array = ( 

491 wFunc(kNextEff_array) * TranShkAggValsNext_tiled 

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

493 PermShkTotal_array = ( 

494 PermGroFac 

495 * PermGroFacAgg[j] 

496 * PermShkValsNext_tiled 

497 * PermShkAggValsNext_tiled 

498 ) # total / combined permanent shock 

499 Mnext_array = ( 

500 kNext_array * R_array + wEff_array 

501 ) # next period's aggregate market resources 

502 

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

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

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

506 aNrmMin_candidates = ( 

507 PermGroFac 

508 * PermGroFacAgg[j] 

509 * PermShkValsNext_tiled[:, 0, :] 

510 * PermShkAggValsNext_tiled[:, 0, :] 

511 / Reff_array[:, 0, :] 

512 * ( 

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

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

515 ) 

516 ) 

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

518 BoroCnstNat_vec = aNrmMin_vec 

519 aNrmMin_tiled = np.tile( 

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

521 ) 

522 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

523 

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

525 mNrmNext_array = ( 

526 Reff_array * aNrmNow_tiled / PermShkTotal_array 

527 + TranShkValsNext_tiled * wEff_array 

528 ) 

529 

530 # Find marginal value next period at every income shock 

531 # realization and every aggregate market resource gridpoint 

532 vPnext_array = ( 

533 Reff_array 

534 * PermShkTotal_array ** (-CRRA) 

535 * vPfuncNext(mNrmNext_array, Mnext_array) 

536 ) 

537 

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

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

540 

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

542 BoroCnstNat = LinearInterp( 

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

544 ) 

545 EndOfPrdvPnvrs = np.concatenate( 

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

547 ) 

548 EndOfPrdvPnvrsFunc_base = BilinearInterp( 

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

550 ) 

551 EndOfPrdvPnvrsFunc = VariableLowerBoundFunc2D( 

552 EndOfPrdvPnvrsFunc_base, BoroCnstNat 

553 ) 

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

555 BoroCnstNat_cond.append(BoroCnstNat) 

556 

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

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

559 cFuncCnst = BilinearInterp( 

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

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

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

563 ) 

564 

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

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

567 # and marginal value function for each state. 

568 cFuncNow = [] 

569 vPfuncNow = [] 

570 mNrmMinNow = [] 

571 for i in range(StateCount): 

572 # Find natural borrowing constraint for this state by Aagg 

573 AaggNow = AFunc[i](Mgrid) 

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

575 for j in range(StateCount): 

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

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

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

579 BoroCnstNat_vec = aNrmMin_vec 

580 

581 # Make tiled grids of aNrm and Aagg 

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

583 aNrmNow_tiled = aNrmMin_tiled + aXtra_tiled 

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

585 

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

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

588 for j in range(StateCount): 

589 if MrkvArray[i, j] > 0.0: 

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

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

592 

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

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

595 mNrmNow = aNrmNow_tiled + cNrmNow 

596 

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

598 cFuncBaseByM_list = [] 

599 for n in range(Mcount): 

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

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

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

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

604 

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

606 BoroCnstNat = LinearInterp( 

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

608 ) 

609 cFuncBase = LinearInterpOnInterp1D(cFuncBaseByM_list, Mgrid) 

610 cFuncUnc = VariableLowerBoundFunc2D(cFuncBase, BoroCnstNat) 

611 

612 # Combine the constrained consumption function with unconstrained component 

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

614 

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

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

617 

618 # Construct the marginal value function using the envelope condition 

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

620 

621 # Pack up and return the solution 

622 solution_now = ConsumerSolution( 

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

624 ) 

625 return solution_now 

626 

627 

628############################################################################### 

629 

630 

631def solve_KrusellSmith( 

632 solution_next, 

633 DiscFac, 

634 CRRA, 

635 aGrid, 

636 Mgrid, 

637 mNextArray, 

638 MnextArray, 

639 ProbArray, 

640 RnextArray, 

641): 

642 """ 

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

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

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

646 default constructor functions for details. 

647 

648 Parameters 

649 ---------- 

650 solution_next : ConsumerSolution 

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

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

653 DiscFac : float 

654 Intertemporal discount factor. 

655 CRRA : float 

656 Coefficient of relative risk aversion. 

657 aGrid : np.array 

658 Array of end-of-period asset values. 

659 Mgrid : np.array 

660 A grid of aggregate market resources in the economy. 

661 mNextArray : np.array 

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

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

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

665 MnextArray : np.array 

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

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

668 shock that might attain. Corresponds to mNextArray. 

669 ProbArray : np.array 

670 Tiled array of transition probabilities among discrete states. Every 

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

672 RnextArray : np.array 

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

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

675 

676 Returns 

677 ------- 

678 solution_now : ConsumerSolution 

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

680 """ 

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

682 vPnext = np.zeros_like(mNextArray) 

683 for j in range(4): 

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

685 mNextArray[:, :, :, j], MnextArray[:, :, :, j] 

686 ) 

687 

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

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

690 

691 # Invert the first order condition to find optimal consumption 

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

693 

694 # Find the endogenous gridpoints 

695 aCount = aGrid.size 

696 Mcount = Mgrid.size 

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

698 mNow = aNow + cNow 

699 

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

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

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

703 

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

705 cFunc_by_state = [] 

706 vPfunc_by_state = [] 

707 for j in range(4): 

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

709 cFunc_j = LinearInterpOnInterp1D(cFunc_by_M, Mgrid) 

710 vPfunc_j = MargValueFuncCRRA(cFunc_j, CRRA) 

711 cFunc_by_state.append(cFunc_j) 

712 vPfunc_by_state.append(vPfunc_j) 

713 

714 # Package and return the solution 

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

716 return solution_now 

717 

718 

719############################################################################### 

720 

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

722aggshock_constructor_dict = { 

723 "IncShkDstnInd": construct_lognormal_income_process_unemployment, 

724 "IncShkDstn": combine_ind_and_agg_income_shocks, 

725 "PermShkDstn": get_PermShkDstn_from_IncShkDstn, 

726 "TranShkDstn": get_TranShkDstn_from_IncShkDstn, 

727 "aXtraGrid": make_assets_grid, 

728 "MgridBase": make_exponential_MgridBase, 

729 "Mgrid": make_Mgrid, 

730 "kNrmInitDstn": make_lognormal_kNrm_init_dstn, 

731 "pLvlInitDstn": make_lognormal_pLvl_init_dstn, 

732 "solution_terminal": make_aggshock_solution_terminal, 

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

734} 

735 

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

737default_kNrmInitDstn_params = { 

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

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

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

741} 

742 

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

744default_pLvlInitDstn_params = { 

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

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

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

748} 

749 

750 

751# Default parameters to make IncShkDstn using construct_lognormal_income_process_unemployment 

752default_IncShkDstn_params = { 

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

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

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

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

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

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

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

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

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

762} 

763 

764# Default parameters to make aXtraGrid using make_assets_grid 

765default_aXtraGrid_params = { 

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

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

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

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

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

771} 

772 

773# Default parameters to make MgridBase using make_exponential_MgridBase 

774default_MgridBase_params = { 

775 "MaggCount": 17, 

776 "MaggPerturb": 0.01, 

777 "MaggExpFac": 0.15, 

778} 

779 

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

781init_agg_shocks = { 

782 # BASIC HARK PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

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

785 "constructors": aggshock_constructor_dict, # See dictionary above 

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

787 # PRIMITIVE RAW PARAMETERS REQUIRED TO SOLVE THE MODEL 

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

789 "DiscFac": 0.96, # Intertemporal discount factor 

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

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

792 "BoroCnstArt": 0.0, # Artificial borrowing constraint 

793 # PARAMETERS REQUIRED TO SIMULATE THE MODEL 

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

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

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

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

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

799 # ADDITIONAL OPTIONAL PARAMETERS 

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

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

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

803} 

804init_agg_shocks.update(default_kNrmInitDstn_params) 

805init_agg_shocks.update(default_pLvlInitDstn_params) 

806init_agg_shocks.update(default_IncShkDstn_params) 

807init_agg_shocks.update(default_aXtraGrid_params) 

808init_agg_shocks.update(default_MgridBase_params) 

809 

810 

811class AggShockConsumerType(IndShockConsumerType): 

812 """ 

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

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

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

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

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

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

819 decision about how much to consume. 

820 

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

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

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

824 instance, probably of the subclass CobbDouglasEconomy or SmallOpenEconomy. 

825 

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

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

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

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

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

831 """ 

832 

833 default_ = { 

834 "params": init_agg_shocks, 

835 "solver": solveConsAggShock, 

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

837 } 

838 time_inv_ = IndShockConsumerType.time_inv_.copy() 

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

840 try: 

841 time_inv_.remove("vFuncBool") 

842 time_inv_.remove("CubicBool") 

843 except: # pragma: nocover 

844 pass 

845 market_vars = [ 

846 "act_T", 

847 "kSS", 

848 "MSS", 

849 "AFunc", 

850 "Rfunc", 

851 "wFunc", 

852 "DeprRte", 

853 "PermGroFacAgg", 

854 "AggShkDstn", 

855 ] 

856 

857 def __init__(self, **kwds): 

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

859 

860 def reset(self): 

861 """ 

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

863 

864 Parameters 

865 ---------- 

866 None 

867 

868 Returns 

869 ------- 

870 None 

871 """ 

872 # Start simulation near SS 

873 self.initialize_sim() 

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

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

876 

877 def pre_solve(self): 

878 self.construct("solution_terminal") 

879 

880 def sim_birth(self, which_agents): 

881 """ 

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

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

884 

885 Parameters 

886 ---------- 

887 which_agents : np.array(Bool) 

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

889 

890 Returns 

891 ------- 

892 None 

893 """ 

894 IndShockConsumerType.sim_birth(self, which_agents) 

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

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

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

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

899 ) 

900 else: 

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

902 

903 def sim_death(self): 

904 """ 

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

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

907 

908 Parameters 

909 ---------- 

910 None 

911 

912 Returns 

913 ------- 

914 who_dies : np.array(bool) 

915 Boolean array of size AgentCount indicating which agents die. 

916 """ 

917 # Just select a random set of agents to die 

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

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

920 base_bool[0:how_many_die] = True 

921 who_dies = self.RNG.permutation(base_bool) 

922 if self.T_age is not None: 

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

924 

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

926 who_lives = np.logical_not(who_dies) 

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

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

929 Ractuarial = 1.0 + wealth_dead / wealth_living 

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

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

932 ) 

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

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

935 ) 

936 return who_dies 

937 

938 def get_Rport(self): 

939 """ 

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

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

942 

943 Parameters 

944 ---------- 

945 None 

946 

947 Returns 

948 ------- 

949 RfreeNow : np.array 

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

951 """ 

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

953 return RfreeNow 

954 

955 def get_shocks(self): 

956 """ 

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

958 and idiosyncratic shocks of each type. 

959 

960 Parameters 

961 ---------- 

962 None 

963 

964 Returns 

965 ------- 

966 None 

967 """ 

968 IndShockConsumerType.get_shocks(self) # Update idiosyncratic shocks 

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

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

971 

972 def get_controls(self): 

973 """ 

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

975 

976 Parameters 

977 ---------- 

978 None 

979 

980 Returns 

981 ------- 

982 None 

983 """ 

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

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

986 MaggNow = self.get_MaggNow() 

987 for t in range(self.T_cycle): 

988 these = t == self.t_cycle 

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

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

991 ) 

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

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

994 ) # Marginal propensity to consume 

995 

996 self.controls["cNrm"] = cNrmNow 

997 self.MPCnow = MPCnow 

998 

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

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

1001 

1002 def market_action(self): 

1003 """ 

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

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

1006 

1007 Parameters 

1008 ---------- 

1009 None 

1010 

1011 Returns 

1012 ------- 

1013 None 

1014 """ 

1015 self.simulate(1) 

1016 

1017 def calc_bounding_values(self): # pragma: nocover 

1018 """ 

1019 NOT YET IMPLEMENTED FOR THIS CLASS 

1020 """ 

1021 raise NotImplementedError() 

1022 

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

1024 """ 

1025 NOT YET IMPLEMENTED FOR THIS CLASS 

1026 """ 

1027 raise NotImplementedError() 

1028 

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

1030 raise NotImplementedError() 

1031 

1032 def calc_limiting_values(self): # pragma: nocover 

1033 raise NotImplementedError() 

1034 

1035 

1036############################################################################### 

1037 

1038 

1039default_IncShkDstnInd_aggmrkv_params = { 

1040 "PermShkStd": np.array( 

1041 [[0.1, 0.1]] 

1042 ), # Standard deviation of log permanent income shocks 

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

1044 "TranShkStd": np.array( 

1045 [[0.1, 0.1]] 

1046 ), # Standard deviation of log transitory income shocks 

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

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

1049 "IncUnemp": np.array( 

1050 [0.3, 0.3] 

1051 ), # Unemployment benefits replacement rate while working 

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

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

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

1055} 

1056 

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

1058init_agg_mrkv_shocks = init_agg_shocks.copy() 

1059init_agg_mrkv_shocks.update(default_IncShkDstnInd_aggmrkv_params) 

1060aggmrkv_constructor_dict = aggshock_constructor_dict.copy() 

1061aggmrkv_constructor_dict["solution_terminal"] = make_aggmrkv_solution_terminal 

1062aggmrkv_constructor_dict["IncShkDstnInd"] = ( 

1063 construct_markov_lognormal_income_process_unemployment 

1064) 

1065aggmrkv_constructor_dict["IncShkDstn"] = combine_markov_ind_and_agg_income_shocks 

1066aggmrkv_constructor_dict["PermShkDstn"] = get_PermShkDstn_from_IncShkDstn_markov 

1067aggmrkv_constructor_dict["TranShkDstn"] = get_TranShkDstn_from_IncShkDstn_markov 

1068init_agg_mrkv_shocks["constructors"] = aggmrkv_constructor_dict 

1069 

1070 

1071class AggShockMarkovConsumerType(AggShockConsumerType): 

1072 """ 

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

1074 experience both aggregate and idiosyncratic shocks to productivity (both 

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

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

1077 

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

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

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

1081 instance, probably of the subclass CobbDouglasMarkovEconomy or SmallOpenMarkovEconomy. 

1082 

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

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

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

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

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

1088 """ 

1089 

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

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

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

1093 default_ = { 

1094 "params": init_agg_mrkv_shocks, 

1095 "solver": solve_ConsAggMarkov, 

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

1097 } 

1098 

1099 def initialize_sim(self): 

1100 self.shocks["Mrkv"] = 0 

1101 AggShockConsumerType.initialize_sim(self) 

1102 

1103 def get_shocks(self): 

1104 """ 

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

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

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

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

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

1110 

1111 Parameters 

1112 ---------- 

1113 None 

1114 

1115 Returns 

1116 ------- 

1117 None 

1118 """ 

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

1120 TranShkNow = np.zeros(self.AgentCount) 

1121 newborn = self.t_age == 0 

1122 Mrkv = self.shocks["Mrkv"] 

1123 for t in range(self.T_cycle): 

1124 these = t == self.t_cycle 

1125 if np.any(these): 

1126 N = np.sum(these) 

1127 # set current income distribution and permanent growth factor 

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

1129 PermGroFacNow = self.PermGroFac[t - 1] 

1130 

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

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

1133 # Permanent "shock" includes expected growth 

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

1135 TranShkNow[these] = ShockDraws[1] 

1136 

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

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

1139 N = np.sum(newborn) 

1140 if N > 0: 

1141 these = newborn 

1142 IncShkDstnNow = self.IncShkDstn[0][ 

1143 self.shocks["Mrkv"] 

1144 ] # set current income distribution 

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

1146 

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

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

1149 

1150 # Permanent "shock" includes expected growth 

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

1152 TranShkNow[these] = ShockDraws[1] 

1153 

1154 # Store the shocks in self 

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

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

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

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

1159 

1160 def get_controls(self): 

1161 """ 

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

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

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

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

1166 

1167 Parameters 

1168 ---------- 

1169 None 

1170 

1171 Returns 

1172 ------- 

1173 None 

1174 """ 

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

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

1177 MaggNow = self.get_MaggNow() 

1178 MrkvNow = self.getMrkvNow() 

1179 

1180 StateCount = self.MrkvArray.shape[0] 

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

1182 for i in range(StateCount): 

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

1184 

1185 for t in range(self.T_cycle): 

1186 these = t == self.t_cycle 

1187 for i in range(StateCount): 

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

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

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

1191 ) 

1192 # Marginal propensity to consume 

1193 MPCnow[those] = ( 

1194 self.solution[t] 

1195 .cFunc[i] 

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

1197 ) 

1198 self.controls["cNrm"] = cNrmNow 

1199 self.MPCnow = MPCnow 

1200 return None 

1201 

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

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

1204 

1205 

1206############################################################################### 

1207 

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

1209 

1210 

1211def make_solution_terminal_KS(CRRA): 

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

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

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

1215 return solution_terminal 

1216 

1217 

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

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

1220 

1221 

1222def make_KS_transition_arrays( 

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

1224): 

1225 """ 

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

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

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

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

1230 

1231 Parameters 

1232 ---------- 

1233 aGrid : np.array 

1234 Grid of end-of-period individual assets. 

1235 MGrid : np.array 

1236 Grid of aggregate market resources. 

1237 AFunc : function 

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

1239 LbrInd : float 

1240 Individual labor supply measure. 

1241 UrateB : float 

1242 Unemployment rate in the "bad" aggregate state. 

1243 UrateG : float 

1244 Unemployment rate in the "good" aggregate state. 

1245 ProdB : float 

1246 TFP in the "bad" aggregate state. 

1247 ProdG : float 

1248 TFP in the "good" aggregate state. 

1249 MrkvIndArray : np.array 

1250 Markov transition probabilities from the perspective of the individual. 

1251 

1252 Returns 

1253 ------- 

1254 ProbArray : np.array 

1255 Array of discrete future outcome probabilities. 

1256 mNextArray : np.array 

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

1258 MnextArray : np.array 

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

1260 RnextArray : np.array 

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

1262 """ 

1263 # Get array sizes 

1264 aCount = aGrid.size 

1265 Mcount = Mgrid.size 

1266 

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

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

1269 

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

1271 AnowB = AFunc[0](Mgrid) 

1272 AnowG = AFunc[1](Mgrid) 

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

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

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

1276 

1277 # Make arrays of aggregate labor and TFP next period 

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

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

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

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

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

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

1284 

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

1286 KtoLnext = Knext / Lnext 

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

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

1289 

1290 # Calculate aggregate market resources next period 

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

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

1293 

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

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

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

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

1298 

1299 # Make an array of idiosyncratic labor supply next period 

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

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

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

1303 

1304 # Calculate idiosyncratic market resources next period 

1305 mNext = Rnext_tiled * aNow_tiled + Wnext_tiled * lNext_tiled 

1306 

1307 # Make a tiled array of transition probabilities 

1308 Probs_tiled = np.tile( 

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

1310 ) 

1311 

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

1313 transition_arrays = { 

1314 "ProbArray": Probs_tiled, 

1315 "mNextArray": mNext, 

1316 "MnextArray": Mnext_tiled, 

1317 "RnextArray": Rnext_tiled, 

1318 } 

1319 return transition_arrays 

1320 

1321 

1322############################################################################### 

1323 

1324# Make a dictionary for Krusell-Smith agents 

1325KS_constructor_dict = { 

1326 "solution_terminal": make_solution_terminal_KS, 

1327 "aGrid": make_assets_grid_KS, 

1328 "transition_arrays": make_KS_transition_arrays, 

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

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

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

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

1333 "MgridBase": make_exponential_MgridBase, 

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

1335 "Mgrid": make_Mgrid, 

1336} 

1337 

1338init_KS_agents = { 

1339 "T_cycle": 1, 

1340 "pseudo_terminal": False, 

1341 "constructors": KS_constructor_dict, 

1342 "DiscFac": 0.99, 

1343 "CRRA": 1.0, 

1344 "LbrInd": 1.0, 

1345 "aMin": 0.001, 

1346 "aMax": 50.0, 

1347 "aCount": 32, 

1348 "aNestFac": 2, 

1349 "MaggCount": 25, 

1350 "MaggPerturb": 0.01, 

1351 "MaggExpFac": 0.12, 

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

1353 "AgentCount": 5000, 

1354} 

1355 

1356 

1357class KrusellSmithType(AgentType): 

1358 """ 

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

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

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

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

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

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

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

1366 

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

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

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

1370 instance, probably of the subclass KrusellSmithEconomy. 

1371 

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

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

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

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

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

1377 """ 

1378 

1379 time_inv_ = [ 

1380 "DiscFac", 

1381 "CRRA", 

1382 "aGrid", 

1383 "ProbArray", 

1384 "mNextArray", 

1385 "MnextArray", 

1386 "RnextArray", 

1387 "Mgrid", 

1388 ] 

1389 time_vary_ = [] 

1390 shock_vars_ = ["Mrkv"] 

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

1392 market_vars = [ 

1393 "act_T", 

1394 "KSS", 

1395 "MSS", 

1396 "AFunc", 

1397 "CapShare", 

1398 "DeprRte", 

1399 "LbrInd", 

1400 "UrateB", 

1401 "UrateG", 

1402 "ProdB", 

1403 "ProdG", 

1404 "MrkvIndArray", 

1405 "MrkvAggArray", 

1406 "MrkvInit", 

1407 ] 

1408 default_ = { 

1409 "params": init_KS_agents, 

1410 "solver": solve_KrusellSmith, 

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

1412 } 

1413 

1414 def __init__(self, **kwds): 

1415 temp = kwds.copy() 

1416 temp["construct"] = False 

1417 AgentType.__init__(self, **temp) 

1418 self.construct("MgridBase") 

1419 

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

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

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

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

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

1425 # that economy's give_agent_params method. 

1426 

1427 def pre_solve(self): 

1428 self.construct("solution_terminal") 

1429 

1430 def make_emp_idx_arrays(self): 

1431 """ 

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

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

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

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

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

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

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

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

1440 maintain exact unemployment rates in each period. 

1441 """ 

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

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

1444 B_emp_N = self.AgentCount - B_unemp_N 

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

1446 G_emp_N = self.AgentCount - G_unemp_N 

1447 

1448 # Bad-bad transition indices 

1449 BB_stay_unemp_N = int( 

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

1451 ) 

1452 BB_become_unemp_N = B_unemp_N - BB_stay_unemp_N 

1453 BB_stay_emp_N = int( 

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

1455 ) 

1456 BB_become_emp_N = B_emp_N - BB_stay_emp_N 

1457 BB_unemp_permute = np.concatenate( 

1458 [ 

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

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

1461 ] 

1462 ) 

1463 BB_emp_permute = np.concatenate( 

1464 [ 

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

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

1467 ] 

1468 ) 

1469 

1470 # Bad-good transition indices 

1471 BG_stay_unemp_N = int( 

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

1473 ) 

1474 BG_become_unemp_N = G_unemp_N - BG_stay_unemp_N 

1475 BG_stay_emp_N = int( 

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

1477 ) 

1478 BG_become_emp_N = G_emp_N - BG_stay_emp_N 

1479 BG_unemp_permute = np.concatenate( 

1480 [ 

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

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

1483 ] 

1484 ) 

1485 BG_emp_permute = np.concatenate( 

1486 [ 

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

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

1489 ] 

1490 ) 

1491 

1492 # Good-bad transition indices 

1493 GB_stay_unemp_N = int( 

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

1495 ) 

1496 GB_become_unemp_N = B_unemp_N - GB_stay_unemp_N 

1497 GB_stay_emp_N = int( 

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

1499 ) 

1500 GB_become_emp_N = B_emp_N - GB_stay_emp_N 

1501 GB_unemp_permute = np.concatenate( 

1502 [ 

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

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

1505 ] 

1506 ) 

1507 GB_emp_permute = np.concatenate( 

1508 [ 

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

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

1511 ] 

1512 ) 

1513 

1514 # Good-good transition indices 

1515 GG_stay_unemp_N = int( 

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

1517 ) 

1518 GG_become_unemp_N = G_unemp_N - GG_stay_unemp_N 

1519 GG_stay_emp_N = int( 

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

1521 ) 

1522 GG_become_emp_N = G_emp_N - GG_stay_emp_N 

1523 GG_unemp_permute = np.concatenate( 

1524 [ 

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

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

1527 ] 

1528 ) 

1529 GG_emp_permute = np.concatenate( 

1530 [ 

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

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

1533 ] 

1534 ) 

1535 

1536 # Store transition matrices as attributes of self 

1537 self.unemp_permute = [ 

1538 [BB_unemp_permute, BG_unemp_permute], 

1539 [GB_unemp_permute, GG_unemp_permute], 

1540 ] 

1541 self.emp_permute = [ 

1542 [BB_emp_permute, BG_emp_permute], 

1543 [GB_emp_permute, GG_emp_permute], 

1544 ] 

1545 

1546 def reset(self): 

1547 self.initialize_sim() 

1548 

1549 def market_action(self): 

1550 self.simulate(1) 

1551 

1552 def initialize_sim(self): 

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

1554 AgentType.initialize_sim(self) 

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

1556 self.make_emp_idx_arrays() 

1557 

1558 def sim_birth(self, which): 

1559 """ 

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

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

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

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

1564 is the correct behavior for the model. 

1565 """ 

1566 N = np.sum(which) 

1567 if N == 0: 

1568 return 

1569 

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

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

1572 emp_N = self.AgentCount - unemp_N 

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

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

1575 emp_N = self.AgentCount - unemp_N 

1576 else: 

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

1578 EmpNew = np.concatenate( 

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

1580 ) 

1581 

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

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

1584 

1585 def get_shocks(self): 

1586 """ 

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

1588 """ 

1589 # Get boolean arrays for current employment states 

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

1591 unemployed = np.logical_not(employed) 

1592 

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

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

1595 

1596 # Transition some agents between unemployment and employment 

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

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

1599 # TODO: replace poststate_vars functionality with shocks here 

1600 EmpNow = self.state_now["EmpNow"] 

1601 

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

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

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

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

1606 

1607 def get_states(self): 

1608 """ 

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

1610 """ 

1611 self.state_now["mNow"] = ( 

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

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

1614 ) 

1615 

1616 def get_controls(self): 

1617 """ 

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

1619 """ 

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

1621 unemployed = np.logical_not(employed) 

1622 

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

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

1625 unemp_idx = 0 

1626 emp_idx = 1 

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

1628 unemp_idx = 2 

1629 emp_idx = 3 

1630 else: 

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

1632 

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

1634 cNow = np.zeros(self.AgentCount) 

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

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

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

1638 ) 

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

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

1641 ) 

1642 self.controls["cNow"] = cNow 

1643 

1644 def get_poststates(self): 

1645 """ 

1646 Gets each agent's retained assets after consumption. 

1647 """ 

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

1649 

1650 

1651############################################################################### 

1652 

1653 

1654CRRA = 2.0 

1655DiscFac = 0.96 

1656 

1657# Parameters for a Cobb-Douglas economy 

1658PermGroFacAgg = 1.00 # Aggregate permanent income growth factor 

1659PermShkAggCount = ( 

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

1661) 

1662TranShkAggCount = ( 

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

1664) 

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

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

1667DeprRte = 0.025 # Capital depreciation rate 

1668CapShare = 0.36 # Capital's share of income 

1669DiscFacPF = DiscFac # Discount factor of perfect foresight calibration 

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

1671intercept_prev = 0.0 # Intercept of aggregate savings function 

1672slope_prev = 1.0 # Slope of aggregate savings function 

1673verbose_cobb_douglas = ( 

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

1675) 

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

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

1678DampingFac = 0.5 

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

1680 

1681 

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

1683init_cobb_douglas = { 

1684 "PermShkAggCount": PermShkAggCount, 

1685 "TranShkAggCount": TranShkAggCount, 

1686 "PermShkAggStd": PermShkAggStd, 

1687 "TranShkAggStd": TranShkAggStd, 

1688 "DeprRte": DeprRte, 

1689 "CapShare": CapShare, 

1690 "DiscFac": DiscFacPF, 

1691 "CRRA": CRRAPF, 

1692 "PermGroFacAgg": PermGroFacAgg, 

1693 "AggregateL": 1.0, 

1694 "intercept_prev": intercept_prev, 

1695 "slope_prev": slope_prev, 

1696 "verbose": verbose_cobb_douglas, 

1697 "T_discard": T_discard, 

1698 "DampingFac": DampingFac, 

1699 "max_loops": max_loops, 

1700} 

1701 

1702 

1703class CobbDouglasEconomy(Market): 

1704 """ 

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

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

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

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

1709 rate for the upcoming period. 

1710 

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

1712 this will be generalized in the future. 

1713 

1714 Parameters 

1715 ---------- 

1716 agents : [ConsumerType] 

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

1718 tolerance: float 

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

1720 solution process converged. Distance depends on intercept and slope 

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

1722 act_T : int 

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

1724 """ 

1725 

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

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

1728 params = init_cobb_douglas.copy() 

1729 params["sow_vars"] = [ 

1730 "MaggNow", 

1731 "AaggNow", 

1732 "RfreeNow", 

1733 "wRteNow", 

1734 "PermShkAggNow", 

1735 "TranShkAggNow", 

1736 "KtoLnow", 

1737 ] 

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

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

1740 params["dyn_vars"] = ["AFunc"] 

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

1742 params.update(kwds) 

1743 

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

1745 self.update() 

1746 

1747 def mill_rule(self, aLvl, pLvl): 

1748 """ 

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

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

1751 

1752 See documentation for calc_R_and_W for more information. 

1753 """ 

1754 return self.calc_R_and_W(aLvl, pLvl) 

1755 

1756 def calc_dynamics(self, MaggNow, AaggNow): 

1757 """ 

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

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

1760 

1761 See documentation for calc_AFunc for more information. 

1762 """ 

1763 return self.calc_AFunc(MaggNow, AaggNow) 

1764 

1765 def update(self): 

1766 """ 

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

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

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

1770 

1771 Parameters 

1772 ---------- 

1773 None 

1774 

1775 Returns 

1776 ------- 

1777 None 

1778 """ 

1779 self.kSS = ( 

1780 ( 

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

1782 - (1.0 - self.DeprRte) 

1783 ) 

1784 / self.CapShare 

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

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

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

1788 self.RfreeSS = ( 

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

1790 ) 

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

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

1793 1.0 / (1.0 - self.CapShare) 

1794 ) # converts K/Y to K/L 

1795 self.Rfunc = lambda k: ( 

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

1797 ) 

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

1799 

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

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

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

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

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

1805 self.sow_init["PermShkAggNow"] = 1.0 

1806 self.sow_init["TranShkAggNow"] = 1.0 

1807 self.make_AggShkDstn() 

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

1809 

1810 def get_PermGroFacAggLR(self): 

1811 """ 

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

1813 and extended by ConsAggShockMarkov model. 

1814 

1815 Parameters 

1816 ---------- 

1817 None 

1818 

1819 Returns 

1820 ------- 

1821 PermGroFacAggLR : float 

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

1823 as aggregate permanent income growth. 

1824 """ 

1825 return self.PermGroFacAgg 

1826 

1827 def make_AggShkDstn(self): 

1828 """ 

1829 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

1831 

1832 Parameters 

1833 ---------- 

1834 None 

1835 

1836 Returns 

1837 ------- 

1838 None 

1839 """ 

1840 RNG = self.RNG 

1841 TranShkAggDstn = MeanOneLogNormal( 

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

1843 ) 

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

1845 PermShkAggDstn = MeanOneLogNormal( 

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

1847 ) 

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

1849 self.AggShkDstn = combine_indep_dstns( 

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

1851 ) 

1852 

1853 def reset(self): 

1854 """ 

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

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

1857 

1858 Parameters 

1859 ---------- 

1860 None 

1861 

1862 Returns 

1863 ------- 

1864 None 

1865 """ 

1866 self.Shk_idx = 0 

1867 Market.reset(self) 

1868 

1869 def make_AggShkHist(self): 

1870 """ 

1871 Make simulated histories of aggregate transitory and permanent shocks. 

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

1873 simulation. 

1874 

1875 Parameters 

1876 ---------- 

1877 None 

1878 

1879 Returns 

1880 ------- 

1881 None 

1882 """ 

1883 sim_periods = self.act_T 

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

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

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

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

1888 

1889 # Store the histories 

1890 self.PermShkAggHist = PermShkAggHist * self.PermGroFacAgg 

1891 self.TranShkAggHist = TranShkAggHist 

1892 

1893 def calc_R_and_W(self, aLvlNow, pLvlNow): 

1894 """ 

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

1896 capital stock to get the aggregate capital ratio. 

1897 

1898 Parameters 

1899 ---------- 

1900 aLvlNow : [np.array] 

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

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

1903 

1904 Returns 

1905 ------- 

1906 MaggNow : float 

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

1908 AaggNow : float 

1909 Aggregate savings for this period normalized by mean permanent income 

1910 RfreeNow : float 

1911 Interest factor on assets in the economy this period. 

1912 wRteNow : float 

1913 Wage rate for labor in the economy this period. 

1914 PermShkAggNow : float 

1915 Permanent shock to aggregate labor productivity this period. 

1916 TranShkAggNow : float 

1917 Transitory shock to aggregate labor productivity this period. 

1918 KtoLnow : float 

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

1920 """ 

1921 # Calculate aggregate savings 

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

1923 pLvlNow 

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

1925 # Calculate aggregate capital this period 

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

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

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

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

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

1931 

1932 # Get this period's aggregate shocks 

1933 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

1934 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

1935 self.Shk_idx += 1 

1936 

1937 AggregateL = np.mean(pLvlNow) * PermShkAggNow 

1938 

1939 # Calculate the interest factor and wage rate this period 

1940 KtoLnow = AggregateK / AggregateL 

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

1942 RfreeNow = self.Rfunc(KtoLnow / TranShkAggNow) 

1943 wRteNow = self.wFunc(KtoLnow / TranShkAggNow) 

1944 MaggNow = KtoLnow * RfreeNow + wRteNow * TranShkAggNow 

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

1946 

1947 # Package the results into an object and return it 

1948 return ( 

1949 MaggNow, 

1950 AaggPrev, 

1951 RfreeNow, 

1952 wRteNow, 

1953 PermShkAggNow, 

1954 TranShkAggNow, 

1955 KtoLnow, 

1956 ) 

1957 

1958 def calc_AFunc(self, MaggNow, AaggNow): 

1959 """ 

1960 Calculate a new aggregate savings rule based on the history 

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

1962 

1963 Parameters 

1964 ---------- 

1965 MaggNow : [float] 

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

1967 AaggNow : [float] 

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

1969 

1970 Returns 

1971 ------- 

1972 (unnamed) : CapDynamicRule 

1973 Object containing a new savings rule 

1974 """ 

1975 verbose = self.verbose 

1976 discard_periods = ( 

1977 self.T_discard 

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

1979 update_weight = ( 

1980 1.0 - self.DampingFac 

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

1982 total_periods = len(MaggNow) 

1983 

1984 # Regress the log savings against log market resources 

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

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

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

1988 

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

1990 # with the previous guess 

1991 intercept = ( 

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

1993 ) 

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

1995 AFunc = AggregateSavingRule( 

1996 intercept, slope 

1997 ) # Make a new next-period capital function 

1998 

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

2000 self.intercept_prev = intercept 

2001 self.slope_prev = slope 

2002 

2003 # Print the new parameters 

2004 if verbose: 

2005 print( 

2006 "intercept=" 

2007 + str(intercept) 

2008 + ", slope=" 

2009 + str(slope) 

2010 + ", r-sq=" 

2011 + str(r_value**2) 

2012 ) 

2013 

2014 return AggShocksDynamicRule(AFunc) 

2015 

2016 

2017class SmallOpenEconomy(Market): 

2018 """ 

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

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

2021 aggregate productivity shocks. 

2022 

2023 Parameters 

2024 ---------- 

2025 agents : [ConsumerType] 

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

2027 tolerance: float 

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

2029 solution process converged. Distance depends on intercept and slope 

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

2031 act_T : int 

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

2033 """ 

2034 

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

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

2037 Market.__init__( 

2038 self, 

2039 agents=agents, 

2040 sow_vars=[ 

2041 "MaggNow", 

2042 "AaggNow", 

2043 "RfreeNow", 

2044 "wRteNow", 

2045 "PermShkAggNow", 

2046 "TranShkAggNow", 

2047 "KtoLnow", 

2048 ], 

2049 reap_vars=[], 

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

2051 dyn_vars=[], 

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

2053 tolerance=tolerance, 

2054 act_T=act_T, 

2055 ) 

2056 self.assign_parameters(**kwds) 

2057 self.update() 

2058 

2059 def update(self): 

2060 """ 

2061 Use primitive parameters to set basic objects. 

2062 This is an extremely stripped-down version 

2063 of update for CobbDouglasEconomy. 

2064 

2065 Parameters 

2066 ---------- 

2067 none 

2068 

2069 Returns 

2070 ------- 

2071 none 

2072 """ 

2073 self.kSS = 1.0 

2074 self.MSS = 1.0 

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

2076 self.Rfunc = ConstantFunction(self.Rfree) 

2077 self.wFunc = ConstantFunction(self.wRte) 

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

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

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

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

2082 self.sow_init["PermShkAggNow"] = 1.0 

2083 self.sow_init["TranShkAggNow"] = 1.0 

2084 self.make_AggShkDstn() 

2085 self.AFunc = ConstantFunction(1.0) 

2086 

2087 def make_AggShkDstn(self): 

2088 """ 

2089 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

2091 

2092 Parameters 

2093 ---------- 

2094 None 

2095 

2096 Returns 

2097 ------- 

2098 None 

2099 """ 

2100 RNG = self.RNG 

2101 TranShkAggDstn = MeanOneLogNormal( 

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

2103 ) 

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

2105 PermShkAggDstn = MeanOneLogNormal( 

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

2107 ) 

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

2109 self.AggShkDstn = combine_indep_dstns( 

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

2111 ) 

2112 

2113 def mill_rule(self): 

2114 """ 

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

2116 exogenously determined. However, aggregate shocks may occur. 

2117 

2118 See documentation for get_AggShocks() for more information. 

2119 """ 

2120 return self.get_AggShocks() 

2121 

2122 def calc_dynamics(self, KtoLnow): 

2123 """ 

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

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

2126 """ 

2127 return MetricObject() 

2128 

2129 def reset(self): 

2130 """ 

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

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

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

2134 

2135 Parameters 

2136 ---------- 

2137 None 

2138 

2139 Returns 

2140 ------- 

2141 None 

2142 """ 

2143 self.Shk_idx = 0 

2144 Market.reset(self) 

2145 

2146 def make_AggShkHist(self): 

2147 """ 

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

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

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

2151 

2152 Parameters 

2153 ---------- 

2154 None 

2155 

2156 Returns 

2157 ------- 

2158 None 

2159 """ 

2160 sim_periods = self.act_T 

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

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

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

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

2165 

2166 # Store the histories 

2167 self.PermShkAggHist = PermShkAggHist 

2168 self.TranShkAggHist = TranShkAggHist 

2169 

2170 def get_AggShocks(self): 

2171 """ 

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

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

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

2175 

2176 Parameters 

2177 ---------- 

2178 None 

2179 

2180 Returns 

2181 ------- 

2182 MaggNow : float 

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

2184 AaggNow : float 

2185 Aggregate savings for this period normalized by mean permanent income 

2186 RfreeNow : float 

2187 Interest factor on assets in the economy this period. 

2188 wRteNow : float 

2189 Wage rate for labor in the economy this period. 

2190 PermShkAggNow : float 

2191 Permanent shock to aggregate labor productivity this period. 

2192 TranShkAggNow : float 

2193 Transitory shock to aggregate labor productivity this period. 

2194 KtoLnow : float 

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

2196 

2197 """ 

2198 # Get this period's aggregate shocks 

2199 PermShkAggNow = self.PermShkAggHist[self.Shk_idx] 

2200 TranShkAggNow = self.TranShkAggHist[self.Shk_idx] 

2201 self.Shk_idx += 1 

2202 

2203 # Factor prices are constant 

2204 RfreeNow = self.Rfunc(1.0 / TranShkAggNow) 

2205 wRteNow = self.wFunc(1.0 / TranShkAggNow) 

2206 

2207 # Aggregates are irrelavent 

2208 AaggNow = 1.0 

2209 MaggNow = 1.0 

2210 KtoLnow = 1.0 / PermShkAggNow 

2211 

2212 return ( 

2213 MaggNow, 

2214 AaggNow, 

2215 RfreeNow, 

2216 wRteNow, 

2217 PermShkAggNow, 

2218 TranShkAggNow, 

2219 KtoLnow, 

2220 ) 

2221 

2222 

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

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

2225 

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

2227init_mrkv_cobb_douglas = init_cobb_douglas.copy() 

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

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

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

2231init_mrkv_cobb_douglas["MrkvArray"] = MrkvArray 

2232init_mrkv_cobb_douglas["MrkvInit"] = 0 

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

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

2235 

2236 

2237class CobbDouglasMarkovEconomy(CobbDouglasEconomy): 

2238 """ 

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

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

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

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

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

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

2245 productivity growth factor can vary over time. 

2246 

2247 Parameters 

2248 ---------- 

2249 agents : [ConsumerType] 

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

2251 tolerance: float 

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

2253 solution process converged. Distance depends on intercept and slope 

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

2255 act_T : int 

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

2257 """ 

2258 

2259 def __init__( 

2260 self, 

2261 agents=None, 

2262 tolerance=0.0001, 

2263 act_T=1200, 

2264 **kwds, 

2265 ): 

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

2267 params = init_mrkv_cobb_douglas.copy() 

2268 params.update(kwds) 

2269 

2270 CobbDouglasEconomy.__init__( 

2271 self, 

2272 agents=agents, 

2273 tolerance=tolerance, 

2274 act_T=act_T, 

2275 sow_vars=[ 

2276 "MaggNow", 

2277 "AaggNow", 

2278 "RfreeNow", 

2279 "wRteNow", 

2280 "PermShkAggNow", 

2281 "TranShkAggNow", 

2282 "KtoLnow", 

2283 "Mrkv", # This one is new 

2284 ], 

2285 **params, 

2286 ) 

2287 

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

2289 

2290 def update(self): 

2291 """ 

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

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

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

2295 

2296 Parameters 

2297 ---------- 

2298 None 

2299 

2300 Returns 

2301 ------- 

2302 None 

2303 """ 

2304 CobbDouglasEconomy.update(self) 

2305 StateCount = self.MrkvArray.shape[0] 

2306 AFunc_all = [] 

2307 for i in range(StateCount): 

2308 AFunc_all.append( 

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

2310 ) 

2311 self.AFunc = AFunc_all 

2312 

2313 def get_PermGroFacAggLR(self): 

2314 """ 

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

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

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

2318 

2319 Parameters 

2320 ---------- 

2321 None 

2322 

2323 Returns 

2324 ------- 

2325 PermGroFacAggLR : float 

2326 Long run aggregate permanent income growth factor 

2327 """ 

2328 # Find the long run distribution of Markov states 

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

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

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

2332 LR_dstn = x / np.sum(x) 

2333 

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

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

2336 return PermGroFacAggLR 

2337 

2338 def make_AggShkDstn(self): 

2339 """ 

2340 Creates the attributes TranShkAggDstn, PermShkAggDstn, and AggShkDstn. 

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

2342 This version accounts for the Markov macroeconomic state. 

2343 

2344 Parameters 

2345 ---------- 

2346 None 

2347 

2348 Returns 

2349 ------- 

2350 None 

2351 """ 

2352 TranShkAggDstn = [] 

2353 PermShkAggDstn = [] 

2354 AggShkDstn = [] 

2355 StateCount = self.MrkvArray.shape[0] 

2356 RNG = self.RNG 

2357 

2358 for i in range(StateCount): 

2359 TranShkAggDstn.append( 

2360 MeanOneLogNormal( 

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

2362 ).discretize( 

2363 N=self.TranShkAggCount, 

2364 ) 

2365 ) 

2366 PermShkAggDstn.append( 

2367 MeanOneLogNormal( 

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

2369 ).discretize( 

2370 N=self.PermShkAggCount, 

2371 ) 

2372 ) 

2373 AggShkDstn.append( 

2374 combine_indep_dstns( 

2375 PermShkAggDstn[-1], 

2376 TranShkAggDstn[-1], 

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

2378 ) 

2379 ) 

2380 

2381 self.TranShkAggDstn = TranShkAggDstn 

2382 self.PermShkAggDstn = PermShkAggDstn 

2383 self.AggShkDstn = AggShkDstn 

2384 

2385 def make_AggShkHist(self): 

2386 """ 

2387 Make simulated histories of aggregate transitory and permanent shocks. 

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

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

2390 internal call to make_Mrkv_history(). 

2391 

2392 Parameters 

2393 ---------- 

2394 None 

2395 

2396 Returns 

2397 ------- 

2398 None 

2399 """ 

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

2401 sim_periods = self.act_T 

2402 

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

2404 # that would occur in that state in that period 

2405 StateCount = self.MrkvArray.shape[0] 

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

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

2408 for i in range(StateCount): 

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

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

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

2412 

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

2414 # of Markov states that the economy experiences 

2415 PermShkAggHist = np.zeros(sim_periods) 

2416 TranShkAggHist = np.zeros(sim_periods) 

2417 for i in range(StateCount): 

2418 these = i == self.MrkvNow_hist 

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

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

2421 

2422 # Store the histories 

2423 self.PermShkAggHist = PermShkAggHist 

2424 self.TranShkAggHist = TranShkAggHist 

2425 

2426 def make_Mrkv_history(self): 

2427 """ 

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

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

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

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

2432 initially specified level. 

2433 

2434 Parameters 

2435 ---------- 

2436 None 

2437 

2438 Returns 

2439 ------- 

2440 None 

2441 """ 

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

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

2444 logit_scale = ( 

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

2446 ) 

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

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

2449 

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

2451 if hasattr(self, "act_T_orig"): 

2452 act_T = self.act_T_orig 

2453 else: # Or store it for the first time 

2454 self.act_T_orig = self.act_T 

2455 act_T = self.act_T 

2456 

2457 # Find the long run distribution of Markov states 

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

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

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

2461 LR_dstn = x / np.sum(x) 

2462 

2463 # Initialize the Markov history and set up transitions 

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

2465 loops = 0 

2466 go = True 

2467 MrkvNow = self.sow_init["Mrkv"] 

2468 t = 0 

2469 StateCount = self.MrkvArray.shape[0] 

2470 

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

2472 while go: 

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

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

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

2476 MrkvNow_hist[t] = MrkvNow 

2477 MrkvNow = markov_process.draw(MrkvNow) 

2478 t += 1 

2479 

2480 # Calculate the empirical distribution 

2481 state_T = np.zeros(StateCount) 

2482 for i in range(StateCount): 

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

2484 

2485 # Check whether each state has been visited state_T_min times 

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

2487 go = False # If so, terminate the loop 

2488 continue 

2489 

2490 # Choose an underrepresented state to "jump" to 

2491 if np.any( 

2492 state_T == 0 

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

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

2495 MrkvNow = np.random.choice(never_visited) 

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

2497 emp_dstn = state_T / act_T 

2498 ratios = LR_dstn / emp_dstn 

2499 ratios_adj = ratios - np.max(ratios) 

2500 ratios_exp = np.exp(ratios_adj / logit_scale) 

2501 ratios_sum = np.sum(ratios_exp) 

2502 jump_probs = ratios_exp / ratios_sum 

2503 cum_probs = np.cumsum(jump_probs) 

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

2505 

2506 loops += 1 

2507 # Make the Markov state history longer by act_T_orig periods 

2508 if loops >= loops_max: 

2509 go = False 

2510 print( 

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

2512 ) 

2513 else: 

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

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

2516 act_T += self.act_T_orig 

2517 

2518 # Store the results as attributes of self 

2519 self.MrkvNow_hist = MrkvNow_hist 

2520 self.act_T = act_T 

2521 

2522 def mill_rule(self, aLvl, pLvl): 

2523 """ 

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

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

2526 and adds the Markov state index. 

2527 

2528 See documentation for calc_R_and_W for more information. 

2529 

2530 Params 

2531 ------- 

2532 aLvl : float 

2533 pLvl : float 

2534 

2535 Returns 

2536 ------- 

2537 Mnow : float 

2538 Aggregate market resources for this period. 

2539 Aprev : float 

2540 Aggregate savings for the prior period. 

2541 KtoLnow : float 

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

2543 Rnow : float 

2544 Interest factor on assets in the economy this period. 

2545 Wnow : float 

2546 Wage rate for labor in the economy this period. 

2547 MrkvNow : int 

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

2549 """ 

2550 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2551 temp = self.calc_R_and_W(aLvl, pLvl) 

2552 

2553 return temp + (MrkvNow,) 

2554 

2555 def calc_AFunc(self, MaggNow, AaggNow): 

2556 """ 

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

2558 aggregate savings and aggregate market resources from a simulation. 

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

2560 

2561 Parameters 

2562 ---------- 

2563 MaggNow : [float] 

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

2565 AaggNow : [float] 

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

2567 

2568 Returns 

2569 ------- 

2570 (unnamed) : CapDynamicRule 

2571 Object containing new saving rules for each Markov state. 

2572 """ 

2573 verbose = self.verbose 

2574 discard_periods = ( 

2575 self.T_discard 

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

2577 update_weight = ( 

2578 1.0 - self.DampingFac 

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

2580 total_periods = len(MaggNow) 

2581 

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

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

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

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

2586 

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

2588 AFunc_list = [] 

2589 rSq_list = [] 

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

2591 these = i == MrkvHist 

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

2593 logMagg[these], logAagg[these] 

2594 ) 

2595 

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

2597 # with the previous guess 

2598 intercept = ( 

2599 update_weight * intercept 

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

2601 ) 

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

2603 AFunc_list.append( 

2604 AggregateSavingRule(intercept, slope) 

2605 ) # Make a new next-period capital function 

2606 rSq_list.append(r_value**2) 

2607 

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

2609 self.intercept_prev[i] = intercept 

2610 self.slope_prev[i] = slope 

2611 

2612 # Print the new parameters 

2613 if verbose: # pragma: nocover 

2614 print( 

2615 "intercept=" 

2616 + str(self.intercept_prev) 

2617 + ", slope=" 

2618 + str(self.slope_prev) 

2619 + ", r-sq=" 

2620 + str(rSq_list) 

2621 ) 

2622 

2623 return AggShocksDynamicRule(AFunc_list) 

2624 

2625 

2626class SmallOpenMarkovEconomy(CobbDouglasMarkovEconomy, SmallOpenEconomy): 

2627 """ 

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

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

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

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

2632 """ 

2633 

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

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

2636 temp_dict = init_mrkv_cobb_douglas.copy() 

2637 temp_dict.update(kwds) 

2638 CobbDouglasMarkovEconomy.__init__( 

2639 self, 

2640 agents=agents, 

2641 tolerance=tolerance, 

2642 act_T=act_T, 

2643 reap_vars=[], 

2644 dyn_vars=[], 

2645 **temp_dict, 

2646 ) 

2647 

2648 def update(self): 

2649 SmallOpenEconomy.update(self) 

2650 StateCount = self.MrkvArray.shape[0] 

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

2652 

2653 def make_AggShkDstn(self): 

2654 CobbDouglasMarkovEconomy.make_AggShkDstn(self) 

2655 

2656 def mill_rule(self): 

2657 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2658 temp = SmallOpenEconomy.get_AggShocks(self) 

2659 temp += (MrkvNow,) 

2660 return temp 

2661 

2662 def calc_dynamics(self): 

2663 return NullFunc() 

2664 

2665 def make_AggShkHist(self): 

2666 CobbDouglasMarkovEconomy.make_AggShkHist(self) 

2667 

2668 

2669init_KS_economy = { 

2670 "verbose": True, 

2671 "act_T": 11000, 

2672 "T_discard": 1000, 

2673 "DampingFac": 0.5, 

2674 "intercept_prev": [0.0, 0.0], 

2675 "slope_prev": [1.0, 1.0], 

2676 "DiscFac": 0.99, 

2677 "CRRA": 1.0, 

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

2679 "LbrInd": 0.3271, 

2680 "ProdB": 0.99, 

2681 "ProdG": 1.01, 

2682 "CapShare": 0.36, 

2683 "DeprRte": 0.025, 

2684 "DurMeanB": 8.0, 

2685 "DurMeanG": 8.0, 

2686 "SpellMeanB": 2.5, 

2687 "SpellMeanG": 1.5, 

2688 "UrateB": 0.10, 

2689 "UrateG": 0.04, 

2690 "RelProbBG": 0.75, 

2691 "RelProbGB": 1.25, 

2692 "MrkvInit": 0, 

2693} 

2694 

2695 

2696class KrusellSmithEconomy(Market): 

2697 """ 

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

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

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

2701 those in the paper. 

2702 

2703 Parameters 

2704 ---------- 

2705 agents : [ConsumerType] 

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

2707 tolerance: float 

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

2709 solution process converged. Distance depends on intercept and slope 

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

2711 act_T : int 

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

2713 """ 

2714 

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

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

2717 params = deepcopy(init_KS_economy) 

2718 params.update(kwds) 

2719 

2720 Market.__init__( 

2721 self, 

2722 agents=agents, 

2723 tolerance=tolerance, 

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

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

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

2727 dyn_vars=["AFunc"], 

2728 **params, 

2729 ) 

2730 self.update() 

2731 

2732 def update(self): 

2733 """ 

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

2735 as the perfect foresight steady state and associated objects. 

2736 """ 

2737 StateCount = 2 

2738 AFunc_all = [ 

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

2740 for j in range(StateCount) 

2741 ] 

2742 self.AFunc = AFunc_all 

2743 self.KtoLSS = ( 

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

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

2746 self.KSS = self.KtoLSS * self.LbrInd 

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

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

2749 self.RSS = ( 

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

2751 ) 

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

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

2754 1.0 / (1.0 - self.CapShare) 

2755 ) # converts K/Y to K/L 

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

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

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

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

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

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

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

2763 self.PermShkAggNow_init = 1.0 

2764 self.TranShkAggNow_init = 1.0 

2765 self.sow_init["Mrkv"] = 0 

2766 self.make_MrkvArray() 

2767 

2768 def reset(self): 

2769 """ 

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

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

2772 """ 

2773 self.Shk_idx = 0 

2774 Market.reset(self) 

2775 

2776 def make_MrkvArray(self): 

2777 """ 

2778 Construct the attributes MrkvAggArray and MrkvIndArray from the primitive 

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

2780 RelProbGB, and RelProbBG. 

2781 """ 

2782 # Construct aggregate Markov transition probabilities 

2783 ProbBG = 1.0 / self.DurMeanB 

2784 ProbGB = 1.0 / self.DurMeanG 

2785 ProbBB = 1.0 - ProbBG 

2786 ProbGG = 1.0 - ProbGB 

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

2788 

2789 # Construct idiosyncratic Markov transition probabilities 

2790 # ORDER: BU, BE, GU, GE 

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

2792 

2793 # BAD-BAD QUADRANT 

2794 MrkvIndArray[0, 1] = ProbBB * 1.0 / self.SpellMeanB 

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

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

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

2798 

2799 # GOOD-GOOD QUADRANT 

2800 MrkvIndArray[2, 3] = ProbGG * 1.0 / self.SpellMeanG 

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

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

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

2804 

2805 # BAD-GOOD QUADRANT 

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

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

2808 MrkvIndArray[1, 2] = ( 

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

2810 ) / (1.0 - self.UrateB) 

2811 MrkvIndArray[1, 3] = ProbBG - MrkvIndArray[1, 2] 

2812 

2813 # GOOD-BAD QUADRANT 

2814 MrkvIndArray[2, 0] = self.RelProbGB * MrkvIndArray[0, 0] / ProbBB * ProbGB 

2815 MrkvIndArray[2, 1] = ProbGB - MrkvIndArray[2, 0] 

2816 MrkvIndArray[3, 0] = ( 

2817 ProbGB * self.UrateB - self.UrateG * MrkvIndArray[2, 0] 

2818 ) / (1.0 - self.UrateG) 

2819 MrkvIndArray[3, 1] = ProbGB - MrkvIndArray[3, 0] 

2820 

2821 # Test for valid idiosyncratic transition probabilities 

2822 assert np.all(MrkvIndArray >= 0.0), ( 

2823 "Invalid idiosyncratic transition probabilities!" 

2824 ) 

2825 self.MrkvAggArray = MrkvAggArray 

2826 self.MrkvIndArray = MrkvIndArray 

2827 

2828 def make_Mrkv_history(self): 

2829 """ 

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

2831 MrkvNow_hist. This variable is binary (0 bad, 1 good) in the KS model. 

2832 """ 

2833 # Initialize the Markov history and set up transitions 

2834 self.MrkvNow_hist = np.zeros(self.act_T, dtype=int) 

2835 MrkvNow = self.MrkvInit 

2836 

2837 markov_process = MarkovProcess(self.MrkvAggArray, seed=0) 

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

2839 self.MrkvNow_hist[s] = MrkvNow 

2840 MrkvNow = markov_process.draw(MrkvNow) 

2841 

2842 def mill_rule(self, aNow, EmpNow): 

2843 """ 

2844 Method to calculate the capital to labor ratio, interest factor, and 

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

2846 

2847 See documentation for calc_R_and_W for more information. 

2848 

2849 Returns 

2850 ------- 

2851 Mnow : float 

2852 Aggregate market resources for this period. 

2853 Aprev : float 

2854 Aggregate savings for the prior period. 

2855 MrkvNow : int 

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

2857 Rnow : float 

2858 Interest factor on assets in the economy this period. 

2859 Wnow : float 

2860 Wage rate for labor in the economy this period. 

2861 """ 

2862 return self.calc_R_and_W(aNow, EmpNow) 

2863 

2864 def calc_dynamics(self, Mnow, Aprev): 

2865 """ 

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

2867 macroeconomic state; just calls calc_AFunc. 

2868 """ 

2869 return self.calc_AFunc(Mnow, Aprev) 

2870 

2871 def calc_R_and_W(self, aNow, EmpNow): 

2872 """ 

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

2874 capital stock to get the aggregate capital ratio. 

2875 

2876 Parameters 

2877 ---------- 

2878 aNow : [np.array] 

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

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

2881 EmpNow [np.array] 

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

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

2884 idiosyncratic unemployment probabilities are behaving as expected. 

2885 

2886 Returns 

2887 ------- 

2888 Mnow : float 

2889 Aggregate market resources for this period. 

2890 Aprev : float 

2891 Aggregate savings for the prior period. 

2892 MrkvNow : int 

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

2894 Rnow : float 

2895 Interest factor on assets in the economy this period. 

2896 Wnow : float 

2897 Wage rate for labor in the economy this period. 

2898 """ 

2899 # Calculate aggregate savings 

2900 # End-of-period savings from last period 

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

2902 # Calculate aggregate capital this period 

2903 AggK = Aprev # ...becomes capital today 

2904 

2905 # Calculate unemployment rate 

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

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

2908 

2909 # Get this period's TFP and labor supply 

2910 MrkvNow = self.MrkvNow_hist[self.Shk_idx] 

2911 if MrkvNow == 0: 

2912 Prod = self.ProdB 

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

2914 elif MrkvNow == 1: 

2915 Prod = self.ProdG 

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

2917 self.Shk_idx += 1 

2918 

2919 # Calculate the interest factor and wage rate this period 

2920 KtoLnow = AggK / AggL 

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

2922 Wnow = Prod * self.Wfunc(KtoLnow) 

2923 Mnow = Rnow * AggK + Wnow * AggL 

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

2925 

2926 # Returns a tuple of these values 

2927 return Mnow, Aprev, MrkvNow, Rnow, Wnow 

2928 

2929 def calc_AFunc(self, Mnow, Aprev): 

2930 """ 

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

2932 aggregate savings and aggregate market resources from a simulation. 

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

2934 

2935 Parameters 

2936 ---------- 

2937 Mnow : [float] 

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

2939 Anow : [float] 

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

2941 

2942 Returns 

2943 ------- 

2944 (unnamed) : CapDynamicRule 

2945 Object containing new saving rules for each Markov state. 

2946 """ 

2947 verbose = self.verbose 

2948 discard_periods = ( 

2949 self.T_discard 

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

2951 update_weight = ( 

2952 1.0 - self.DampingFac 

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

2954 total_periods = len(Mnow) 

2955 

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

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

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

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

2960 

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

2962 AFunc_list = [] 

2963 rSq_list = [] 

2964 for i in range(self.MrkvAggArray.shape[0]): 

2965 these = i == MrkvHist 

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

2967 logMagg[these], logAagg[these] 

2968 ) 

2969 

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

2971 # with the previous guess 

2972 intercept = ( 

2973 update_weight * intercept 

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

2975 ) 

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

2977 AFunc_list.append( 

2978 AggregateSavingRule(intercept, slope) 

2979 ) # Make a new next-period capital function 

2980 rSq_list.append(r_value**2) 

2981 

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

2983 self.intercept_prev[i] = intercept 

2984 self.slope_prev[i] = slope 

2985 

2986 # Print the new parameters 

2987 if verbose: # pragma: nocover 

2988 print( 

2989 "intercept=" 

2990 + str(self.intercept_prev) 

2991 + ", slope=" 

2992 + str(self.slope_prev) 

2993 + ", r-sq=" 

2994 + str(rSq_list) 

2995 ) 

2996 

2997 return AggShocksDynamicRule(AFunc_list) 

2998 

2999 

3000class AggregateSavingRule(MetricObject): 

3001 """ 

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

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

3004 

3005 Parameters 

3006 ---------- 

3007 intercept : float 

3008 Intercept of the log-linear capital evolution rule. 

3009 slope : float 

3010 Slope of the log-linear capital evolution rule. 

3011 """ 

3012 

3013 def __init__(self, intercept, slope): 

3014 self.intercept = intercept 

3015 self.slope = slope 

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

3017 

3018 def __call__(self, Mnow): 

3019 """ 

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

3021 

3022 Parameters 

3023 ---------- 

3024 Mnow : float 

3025 Aggregate market resources this period. 

3026 

3027 Returns 

3028 ------- 

3029 Aagg : Expected aggregate savings this period. 

3030 """ 

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

3032 return Aagg 

3033 

3034 

3035class AggShocksDynamicRule(MetricObject): 

3036 """ 

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

3038 

3039 Parameters 

3040 ---------- 

3041 AFunc : CapitalEvoRule 

3042 Aggregate savings as a function of aggregate market resources. 

3043 """ 

3044 

3045 def __init__(self, AFunc): 

3046 self.AFunc = AFunc 

3047 self.distance_criteria = ["AFunc"]