-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathNewGLcode.py
1478 lines (1277 loc) · 56.7 KB
/
NewGLcode.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Classes to solve and simulate consumption-savings model with a discrete, exogenous,
stochastic Markov state. The only solver here extends ConsIndShockModel to
include a Markov state; the interest factor, permanent growth factor, and income
distribution can vary with the discrete state.
"""
from __future__ import division, print_function
from __future__ import absolute_import
from builtins import range
from copy import deepcopy
import numpy as np
from HARK.core import AgentType
from HARK.ConsumptionSaving.ConsIndShockModel import (
ConsIndShockSolver,
ValueFunc,
MargValueFunc,
ConsumerSolution,
IndShockConsumerType,
PerfForesightConsumerType,
)
from HARK.distribution import DiscreteDistribution, Uniform
from HARK.interpolation import CubicInterp, LowerEnvelope, LinearInterp
from HARK.utilities import (
CRRAutility,
CRRAutilityP,
CRRAutilityPP,
CRRAutilityP_inv,
CRRAutility_invP,
CRRAutility_inv,
CRRAutilityP_invP,
)
from scipy.io import loadmat
from HARK.utilities import getArgNames, NullFunc, plotFuncs
__all__ = ["GLSolver", "MarkovConsumerType"]
utility = CRRAutility
utilityP = CRRAutilityP
utilityPP = CRRAutilityPP
utilityP_inv = CRRAutilityP_inv
utility_invP = CRRAutility_invP
utility_inv = CRRAutility_inv
utilityP_invP = CRRAutilityP_invP
class GLSolver(ConsIndShockSolver):
"""
A class to solve a single period consumption-saving problem with risky income
and stochastic transitions between discrete states, in a Markov fashion.
Extends ConsIndShockSolver, with identical inputs but for a discrete
Markov state, whose transition rule is summarized in MrkvArray. Markov
states can differ in their interest factor, permanent growth factor, live probability, and
income distribution, so the inputs Rfree, PermGroFac, IncomeDstn, and LivPrb are
now arrays or lists specifying those values in each (succeeding) Markov state.
"""
def __init__(
self,
solution_next,
IncomeDstn_list,
LivPrb,
DiscFac,
CRRA,
Rfree_list,
PermGroFac_list,
MrkvArray,
BoroCnstArt,
aXtraGrid,
vFuncBool,
CubicBool,
eta,
nu,
pssi,
B,
):
"""
Constructor for a new solver for a one period problem with risky income
and transitions between discrete Markov states. In the descriptions below,
N is the number of discrete states.
Parameters
----------
solution_next : ConsumerSolution
The solution to next period's one period problem.
IncomeDstn_list : [[np.array]]
A length N list of income distributions in each succeeding Markov
state. Each income distribution contains three arrays of floats,
representing a discrete approximation to the income process at the
beginning of the succeeding period. Order: event probabilities,
permanent shocks, transitory shocks.
LivPrb : np.array
Survival probability; likelihood of being alive at the beginning of
the succeeding period for each Markov state.
DiscFac : float
Intertemporal discount factor for future utility.
CRRA : float
Coefficient of relative risk aversion.
Rfree_list : np.array
Risk free interest factor on end-of-period assets for each Markov
state in the succeeding period.
PermGroFac_list : np.array
Expected permanent income growth factor at the end of this period
for each Markov state in the succeeding period.
MrkvArray : np.array
An NxN array representing a Markov transition matrix between discrete
states. The i,j-th element of MrkvArray is the probability of
moving from state i in period t to state j in period t+1.
BoroCnstArt: float or None
Borrowing constraint for the minimum allowable assets to end the
period with. If it is less than the natural borrowing constraint,
then it is irrelevant; BoroCnstArt=None indicates no artificial bor-
rowing constraint.
aXtraGrid: np.array
Array of "extra" end-of-period asset values-- assets above the
absolute minimum acceptable level.
vFuncBool: boolean
An indicator for whether the value function should be computed and
included in the reported solution.
CubicBool: boolean
An indicator for whether the solver should use cubic or linear inter-
polation.
Returns
-------
None
"""
# Set basic attributes of the problem
self.assignParameters(
solution_next=solution_next,
IncomeDstn_list=IncomeDstn_list,
LivPrb=LivPrb,
DiscFac=DiscFac,
CRRA=CRRA,
BoroCnstArt=BoroCnstArt,
aXtraGrid=aXtraGrid,
vFuncBool=vFuncBool,
CubicBool=CubicBool,
Rfree_list=Rfree_list,
PermGroFac_list=PermGroFac_list,
MrkvArray=MrkvArray,
StateCount=MrkvArray.shape[0],
eta=eta,
nu=nu,
pssi=pssi,
B=B,
)
self.defUtilityFuncs()
def solve(self):
"""
Solve the one period problem of the consumption-saving model with a Markov state.
Parameters
----------
none
Returns
-------
solution : ConsumerSolution
The solution to the single period consumption-saving problem. Includes
a consumption function cFunc (using cubic or linear splines), a marg-
inal value function vPfunc, a minimum acceptable level of normalized
market resources mNrmMin, normalized human wealth hNrm, and bounding
MPCs MPCmin and MPCmax. It might also have a value function vFunc
and marginal marginal value function vPPfunc. All of these attributes
are lists or arrays, with elements corresponding to the current
Markov state. E.g. solution.cFunc[0] is the consumption function
when in the i=0 Markov state this period.
"""
# Find the natural borrowing constraint in each current state
self.defBoundary()
# Initialize end-of-period (marginal) value functions
self.EndOfPrdvFunc_list = []
self.EndOfPrdvPfunc_list = []
self.ExIncNextAll = (
np.zeros(self.StateCount) + np.nan
) # expected income conditional on the next state
self.WorstIncPrbAll = (
np.zeros(self.StateCount) + np.nan
) # probability of getting the worst income shock in each next period state
# Loop through each next-period-state and calculate the end-of-period
# (marginal) value function
for j in range(self.StateCount):
# Condition values on next period's state (and record a couple for later use)
self.conditionOnState(j)
self.ExIncNextAll[j] = np.dot(
self.ShkPrbsNext, self.PermShkValsNext * self.TranShkValsNext
)
self.WorstIncPrbAll[j] = self.WorstIncPrb
# Construct the end-of-period marginal value function conditional
# on next period's state and add it to the list of value functions
EndOfPrdvPfunc_cond = self.makeEndOfPrdvPfuncCond()
self.EndOfPrdvPfunc_list.append(EndOfPrdvPfunc_cond)
# Construct the end-of-period value functional conditional on next
# period's state and add it to the list of value functions
if self.vFuncBool:
EndOfPrdvFunc_cond = self.makeEndOfPrdvFuncCond()
self.EndOfPrdvFunc_list.append(EndOfPrdvFunc_cond)
# EndOfPrdvP_cond is EndOfPrdvP conditional on *next* period's state.
# Take expectations to get EndOfPrdvP conditional on *this* period's state.
self.calcEndOfPrdvP()
# Calculate the bounding MPCs and PDV of human wealth for each state
self.calcHumWealthAndBoundingMPCs()
# Find consumption and market resources corresponding to each end-of-period
# assets point for each state (and add an additional point at the lower bound)
aNrm = (
np.asarray(self.aXtraGrid)[np.newaxis, :]
+ np.array(self.BoroCnstNat_list)[:, np.newaxis]
)
self.getPointsForInterpolationGL(self.EndOfPrdvP, aNrm)
cNrm = np.hstack((np.zeros((self.StateCount, 1)), self.cNrmNow))
BNrm = np.hstack(
(np.reshape(self.mNrmMin_list, (self.StateCount, 1)), self.Bnow)
)
#note later we will need interpolate between bonds and labor
# Package and return the solution for this period
self.BoroCnstNat = self.BoroCnstNat_list
solution = self.makeSolutionGL(cNrm, BNrm)
return solution
def getPointsForInterpolationGL(self, EndOfPrdvP, aNrmNow):
"""
Finds interpolation points (c,m) for the consumption function.
Parameters
----------
EndOfPrdvP : np.array
Array of end-of-period marginal values.
aNrmNow : np.array
Array of end-of-period asset values that yield the marginal values
in EndOfPrdvP.
Returns
-------
c_for_interpolation : np.array
Consumption points for interpolation.
m_for_interpolation : np.array
Corresponding market resource points for interpolation.
"""
Matlabdict = loadmat('inc_process.mat')
data = list(Matlabdict.items())
data_array=np.asarray(data)
x=data_array[3,1]
Pr=data_array[4,1]
pr = data_array[5,1]
theta = np.concatenate((np.array([1e-10]).reshape(1,1),np.exp(x).reshape(1,12)),axis=1).reshape(13,)
fin = 0.8820 #job-finding probability
sep = 0.0573 #separation probability
cmin= 1e-6 # lower bound on consumption
#constructing transition Matrix
G=np.array([1-fin]).reshape(1,1)
A = np.concatenate((G, fin*pr), axis=1)
K= sep**np.ones(12).reshape(12,1)
D=np.concatenate((K,np.multiply((1-sep),Pr)),axis=1)
Pr = np.concatenate((A,D))
# find new invariate distribution
pr = np.concatenate([np.array([0]).reshape(1,1), pr],axis=1)
dif = 1
while dif > 1e-5:
pri = pr.dot(Pr)
dif = np.amax(np.absolute(pri-pr))
pr = pri
fac = ((self.pssi / theta)** (1/self.eta)).reshape(13,)
tau = (self.nu*pr[0,0] + (self.Rfree-1)/(self.Rfree)*self.B) / (1 - pr[0,0]) # labor tax
z = np.insert(-tau*np.ones(12),0,self.nu).reshape(13,1)
facMat = np.diag(fac)
thetaMat = np.diag(theta)
Matlabgrid=loadmat('Bgrid')
griddata=list(Matlabgrid.items())
datagrid_array=np.array(griddata)
Bgrid_uc=datagrid_array[3,1]
#self.Bgrid=[]
#for i in range(200):
#if Bgrid_uc[0,i] > -1.600:
# self.Bgrid.append(Bgrid_uc[0,i])
#self.Bgrid = np.array(self.Bgrid).reshape(1,len(self.Bgrid))
self.Bgrid_rep=np.tile(Bgrid_uc,(13,1))
#need to make Bgrid 13 times on itself
cNrmNow = self.uPinv(EndOfPrdvP)
Nnow = np.maximum(1-facMat.dot(cNrmNow**(self.CRRA/self.eta)),0)
Bnow = self.Bgrid_rep/(self.Rfree) + cNrmNow - thetaMat.dot(Nnow) - z
# Limiting consumption is zero as m approaches mNrmMin
c_for_interpolation = np.insert(cNrmNow, 0, 0.0, axis=-1)
m_for_interpolation = np.insert(Bnow, 0, self.BoroCnstNat, axis=-1)
# Store these for calcvFunc
self.cNrmNow = cNrmNow
self.Nnow = Nnow
self.Bnow = Bnow
return c_for_interpolation, m_for_interpolation
def defBoundary(self):
"""
Find the borrowing constraint for each current state and save it as an
attribute of self for use by other methods.
Parameters
----------
none
Returns
-------
none
"""
self.BoroCnstNatAll = np.zeros(self.StateCount) + np.nan
# Find the natural borrowing constraint conditional on next period's state
for j in range(self.StateCount):
PermShkMinNext = np.min(self.IncomeDstn_list[j].X[0])
TranShkMinNext = np.min(self.IncomeDstn_list[j].X[1])
self.BoroCnstNatAll[j] = (
(self.solution_next.mNrmMin[j] - TranShkMinNext)
* (self.PermGroFac_list[j] * PermShkMinNext)
/ self.Rfree_list[j]
)
self.BoroCnstNat_list = np.zeros(self.StateCount) + np.nan
self.mNrmMin_list = np.zeros(self.StateCount) + np.nan
self.BoroCnstDependency = np.zeros((self.StateCount, self.StateCount)) + np.nan
# The natural borrowing constraint in each current state is the *highest*
# among next-state-conditional natural borrowing constraints that could
# occur from this current state.
for i in range(self.StateCount):
possible_next_states = self.MrkvArray[i, :] > 0
self.BoroCnstNat_list[i] = np.max(self.BoroCnstNatAll[possible_next_states])
# Explicitly handle the "None" case:
if self.BoroCnstArt is None:
self.mNrmMin_list[i] = self.BoroCnstNat_list[i]
else:
self.mNrmMin_list[i] = np.max(
[self.BoroCnstNat_list[i], self.BoroCnstArt]
)
self.BoroCnstDependency[i, :] = (
self.BoroCnstNat_list[i] == self.BoroCnstNatAll
)
# Also creates a Boolean array indicating whether the natural borrowing
# constraint *could* be hit when transitioning from i to j.
def conditionOnState(self, state_index):
"""
Temporarily assume that a particular Markov state will occur in the
succeeding period, and condition solver attributes on this assumption.
Allows the solver to construct the future-state-conditional marginal
value function (etc) for that future state.
Parameters
----------
state_index : int
Index of the future Markov state to condition on.
Returns
-------
none
"""
# Set future-state-conditional values as attributes of self
self.IncomeDstn = self.IncomeDstn_list[state_index]
self.Rfree = self.Rfree_list[state_index]
self.PermGroFac = self.PermGroFac_list[state_index]
self.vPfuncNext = self.solution_next.vPfunc[state_index]
self.mNrmMinNow = self.mNrmMin_list[state_index]
self.BoroCnstNat = self.BoroCnstNatAll[state_index]
self.setAndUpdateValues(
self.solution_next, self.IncomeDstn, self.LivPrb, self.DiscFac
)
self.DiscFacEff = (
self.DiscFac
) # survival probability LivPrb represents probability from
# *current* state, so DiscFacEff is just DiscFac for now
# These lines have to come after setAndUpdateValues to override the definitions there
self.vPfuncNext = self.solution_next.vPfunc[state_index]
if self.CubicBool:
self.vPPfuncNext = self.solution_next.vPPfunc[state_index]
if self.vFuncBool:
self.vFuncNext = self.solution_next.vFunc[state_index]
def calcEndOfPrdvPP(self):
"""
Calculates end-of-period marginal marginal value using a pre-defined
array of next period market resources in self.mNrmNext.
Parameters
----------
none
Returns
-------
EndOfPrdvPP : np.array
End-of-period marginal marginal value of assets at each value in
the grid of assets.
"""
EndOfPrdvPP = (
self.DiscFacEff
* self.Rfree
* self.Rfree
* self.PermGroFac ** (-self.CRRA - 1.0)
* np.sum(
self.PermShkVals_temp ** (-self.CRRA - 1.0)
* self.vPPfuncNext(self.mNrmNext)
* self.ShkPrbs_temp,
axis=0,
)
)
return EndOfPrdvPP
def makeEndOfPrdvFuncCond(self):
"""
Construct the end-of-period value function conditional on next period's
state. NOTE: It might be possible to eliminate this method and replace
it with ConsIndShockSolver.makeEndOfPrdvFunc, but the self.X_cond
variables must be renamed.
Parameters
----------
none
Returns
-------
EndofPrdvFunc_cond : ValueFunc
The end-of-period value function conditional on a particular state
occuring in the next period.
"""
VLvlNext = (
self.PermShkVals_temp ** (1.0 - self.CRRA)
* self.PermGroFac ** (1.0 - self.CRRA)
) * self.vFuncNext(self.mNrmNext)
EndOfPrdv_cond = self.DiscFacEff * np.sum(VLvlNext * self.ShkPrbs_temp, axis=0)
EndOfPrdvNvrs_cond = self.uinv(EndOfPrdv_cond)
EndOfPrdvNvrsP_cond = self.EndOfPrdvP_cond * self.uinvP(EndOfPrdv_cond)
EndOfPrdvNvrs_cond = np.insert(EndOfPrdvNvrs_cond, 0, 0.0)
EndOfPrdvNvrsP_cond = np.insert(EndOfPrdvNvrsP_cond, 0, EndOfPrdvNvrsP_cond[0])
aNrm_temp = np.insert(self.aNrm_cond, 0, self.BoroCnstNat)
EndOfPrdvNvrsFunc_cond = CubicInterp(
aNrm_temp, EndOfPrdvNvrs_cond, EndOfPrdvNvrsP_cond
)
EndofPrdvFunc_cond = ValueFunc(EndOfPrdvNvrsFunc_cond, self.CRRA)
return EndofPrdvFunc_cond
def calcEndOfPrdvPcond(self):
"""
Calculate end-of-period marginal value of assets at each point in aNrmNow
conditional on a particular state occuring in the next period.
Parameters
----------
None
Returns
-------
EndOfPrdvP : np.array
A 1D array of end-of-period marginal value of assets.
"""
#EndOfPrdvPcond = ConsIndShockSolver.calcEndOfPrdvP(self)
EndOfPrdvPcond = self.vPfuncNext(self.aNrmNow)
return EndOfPrdvPcond
def makeEndOfPrdvPfuncCond(self):
"""
Construct the end-of-period marginal value function conditional on next
period's state.
Parameters
----------
None
Returns
-------
EndofPrdvPfunc_cond : MargValueFunc
The end-of-period marginal value function conditional on a particular
state occuring in the succeeding period.
"""
# Get data to construct the end-of-period marginal value function (conditional on next state)
self.aNrm_cond = self.prepareToCalcEndOfPrdvP()
self.EndOfPrdvP_cond = self.calcEndOfPrdvPcond()
EndOfPrdvPnvrs_cond = self.uPinv(
self.EndOfPrdvP_cond
) # "decurved" marginal value
if self.CubicBool:
EndOfPrdvPP_cond = self.calcEndOfPrdvPP()
EndOfPrdvPnvrsP_cond = EndOfPrdvPP_cond * self.uPinvP(
self.EndOfPrdvP_cond
) # "decurved" marginal marginal value
# Construct the end-of-period marginal value function conditional on the next state.
if self.CubicBool:
EndOfPrdvPnvrsFunc_cond = CubicInterp(
self.aNrm_cond,
EndOfPrdvPnvrs_cond,
EndOfPrdvPnvrsP_cond,
lower_extrap=True,
)
else:
EndOfPrdvPnvrsFunc_cond = LinearInterp(
self.aNrm_cond, EndOfPrdvPnvrs_cond, lower_extrap=True
)
EndofPrdvPfunc_cond = MargValueFunc(
EndOfPrdvPnvrsFunc_cond, self.CRRA
) # "recurve" the interpolated marginal value function
return EndofPrdvPfunc_cond
def calcEndOfPrdvP(self):
"""
Calculates end of period marginal value (and marginal marginal) value
at each aXtra gridpoint for each current state, unconditional on the
future Markov state (i.e. weighting conditional end-of-period marginal
value by transition probabilities).
Parameters
----------
none
Returns
-------
none
"""
# Find unique values of minimum acceptable end-of-period assets (and the
# current period states for which they apply).
aNrmMin_unique, state_inverse = np.unique(
self.BoroCnstNat_list, return_inverse=True
)
self.possible_transitions = self.MrkvArray > 0
# Calculate end-of-period marginal value (and marg marg value) at each
# asset gridpoint for each current period state
EndOfPrdvP = np.zeros((self.StateCount, self.aXtraGrid.size))
EndOfPrdvPP = np.zeros((self.StateCount, self.aXtraGrid.size))
for k in range(aNrmMin_unique.size):
aNrmMin = aNrmMin_unique[k] # minimum assets for this pass
which_states = (
state_inverse == k
) # the states for which this minimum applies
aGrid = aNrmMin + self.aXtraGrid # assets grid for this pass
EndOfPrdvP_all = np.zeros((self.StateCount, self.aXtraGrid.size))
EndOfPrdvPP_all = np.zeros((self.StateCount, self.aXtraGrid.size))
for j in range(self.StateCount):
if np.any(
np.logical_and(self.possible_transitions[:, j], which_states)
): # only consider a future state if one of the relevant states could transition to it
EndOfPrdvP_all[j, :] = self.EndOfPrdvPfunc_list[j](aGrid)
if (
self.CubicBool
): # Add conditional end-of-period (marginal) marginal value to the arrays
EndOfPrdvPP_all[j, :] = self.EndOfPrdvPfunc_list[j].derivative(
aGrid
)
# Weight conditional marginal (marginal) values by transition probs
# to get unconditional marginal (marginal) value at each gridpoint.
EndOfPrdvP_temp = np.dot(self.MrkvArray, EndOfPrdvP_all)
EndOfPrdvP[which_states, :] = EndOfPrdvP_temp[
which_states, :
] # only take the states for which this asset minimum applies
if self.CubicBool:
EndOfPrdvPP_temp = np.dot(self.MrkvArray, EndOfPrdvPP_all)
EndOfPrdvPP[which_states, :] = EndOfPrdvPP_temp[which_states, :]
# Store the results as attributes of self, scaling end of period marginal value by survival probability from each current state
LivPrb_tiled = np.tile(
np.reshape(self.LivPrb, (self.StateCount, 1)), (1, self.aXtraGrid.size)
)
self.EndOfPrdvP = self.Rfree*self.DiscFac*LivPrb_tiled * EndOfPrdvP #---------------############################################
if self.CubicBool:
self.EndOfPrdvPP = LivPrb_tiled * EndOfPrdvPP
def calcHumWealthAndBoundingMPCs(self):
"""
Calculates human wealth and the maximum and minimum MPC for each current
period state, then stores them as attributes of self for use by other methods.
Parameters
----------
none
Returns
-------
none
"""
# Upper bound on MPC at lower m-bound
WorstIncPrb_array = self.BoroCnstDependency * np.tile(
np.reshape(self.WorstIncPrbAll, (1, self.StateCount)), (self.StateCount, 1)
)
temp_array = self.MrkvArray * WorstIncPrb_array
WorstIncPrbNow = np.sum(
temp_array, axis=1
) # Probability of getting the "worst" income shock and transition from each current state
ExMPCmaxNext = (
np.dot(
temp_array,
self.Rfree_list ** (1.0 - self.CRRA)
* self.solution_next.MPCmax ** (-self.CRRA),
)
/ WorstIncPrbNow
) ** (-1.0 / self.CRRA)
DiscFacEff_temp = self.DiscFac * self.LivPrb
self.MPCmaxNow = 1.0 / (
1.0
+ ((DiscFacEff_temp * WorstIncPrbNow) ** (1.0 / self.CRRA)) / ExMPCmaxNext
)
self.MPCmaxEff = self.MPCmaxNow
self.MPCmaxEff[self.BoroCnstNat_list < self.mNrmMin_list] = 1.0
# State-conditional PDV of human wealth
hNrmPlusIncNext = self.ExIncNextAll + self.solution_next.hNrm
self.hNrmNow = np.dot(
self.MrkvArray, (self.PermGroFac_list / self.Rfree_list) * hNrmPlusIncNext
)
# Lower bound on MPC as m gets arbitrarily large
temp = (
DiscFacEff_temp
* np.dot(
self.MrkvArray,
self.solution_next.MPCmin ** (-self.CRRA)
* self.Rfree_list ** (1.0 - self.CRRA),
)
) ** (1.0 / self.CRRA)
self.MPCminNow = 1.0 / (1.0 + temp)
def makeSolutionGL(self, cNrm, BNrm):
"""
Construct an object representing the solution to this period's problem.
Parameters
----------
cNrm : np.array
Array of normalized consumption values for interpolation. Each row
corresponds to a Markov state for this period.
mNrm : np.array
Array of normalized market resource values for interpolation. Each
row corresponds to a Markov state for this period.
Returns
-------
solution : ConsumerSolution
The solution to the single period consumption-saving problem. Includes
a consumption function cFunc (using cubic or linear splines), a marg-
inal value function vPfunc, a minimum acceptable level of normalized
market resources mNrmMin, normalized human wealth hNrm, and bounding
MPCs MPCmin and MPCmax. It might also have a value function vFunc
and marginal marginal value function vPPfunc. All of these attributes
are lists or arrays, with elements corresponding to the current
Markov state. E.g. solution.cFunc[0] is the consumption function
when in the i=0 Markov state this period.
"""
solution = (
ConsumerSolution()
) # An empty solution to which we'll add state-conditional solutions
# Calculate the MPC at each market resource gridpoint in each state (if desired)
if self.CubicBool:
dcda = self.EndOfPrdvPP / self.uPP(np.array(self.cNrmNow))
MPC = dcda / (dcda + 1.0)
self.MPC_temp = np.hstack(
(np.reshape(self.MPCmaxNow, (self.StateCount, 1)), MPC)
)
interpfunc = self.makeCubiccFunc
else:
interpfunc = self.makeLinearcFunc
# Loop through each current period state and add its solution to the overall solution
for i in range(self.StateCount):
# Set current-period-conditional human wealth and MPC bounds
self.hNrmNow_j = self.hNrmNow[i]
self.MPCminNow_j = self.MPCminNow[i]
if self.CubicBool:
self.MPC_temp_j = self.MPC_temp[i, :]
# Construct the consumption function by combining the constrained and unconstrained portions
self.cFuncNowCnst = LinearInterp(
[self.mNrmMin_list[i], self.mNrmMin_list[i] + 1.0], [0.0, 1.0]
)
cFuncNowUnc = interpfunc(BNrm[i, :], cNrm[i, :])
cFuncNow = LowerEnvelope(cFuncNowUnc, self.cFuncNowCnst)
# Make the marginal value function and pack up the current-state-conditional solution
vPfuncNow = MargValueFunc(cFuncNow, self.CRRA)
solution_cond = ConsumerSolution(
cFunc=cFuncNow, vPfunc=vPfuncNow, mNrmMin=self.mNrmMinNow
)
if (
self.CubicBool
): # Add the state-conditional marginal marginal value function (if desired)
solution_cond = self.addvPPfunc(solution_cond)
# Add the current-state-conditional solution to the overall period solution
solution.appendSolution(solution_cond)
# Add the lower bounds of market resources, MPC limits, human resources,
# and the value functions to the overall solution
solution.mNrmMin = self.mNrmMin_list
solution = self.addMPCandHumanWealth(solution)
if self.vFuncBool:
vFuncNow = self.makevFunc(solution)
solution.vFunc = vFuncNow
# Return the overall solution to this period
return solution
def makeLinearcFunc(self, BNrm, cNrm):
"""
Make a linear interpolation to represent the (unconstrained) consumption
function conditional on the current period state.
Parameters
----------
mNrm : np.array
Array of normalized market resource values for interpolation.
cNrm : np.array
Array of normalized consumption values for interpolation.
Returns
-------
cFuncUnc: an instance of HARK.interpolation.LinearInterp
"""
cFuncUnc = LinearInterp(
BNrm, cNrm, self.MPCminNow_j * self.hNrmNow_j, self.MPCminNow_j
)
return cFuncUnc
def makeCubiccFunc(self, mNrm, cNrm):
"""
Make a cubic interpolation to represent the (unconstrained) consumption
function conditional on the current period state.
Parameters
----------
mNrm : np.array
Array of normalized market resource values for interpolation.
cNrm : np.array
Array of normalized consumption values for interpolation.
Returns
-------
cFuncUnc: an instance of HARK.interpolation.CubicInterp
"""
cFuncUnc = CubicInterp(
mNrm,
cNrm,
self.MPC_temp_j,
self.MPCminNow_j * self.hNrmNow_j,
self.MPCminNow_j,
)
return cFuncUnc
def makevFunc(self, solution):
"""
Construct the value function for each current state.
Parameters
----------
solution : ConsumerSolution
The solution to the single period consumption-saving problem. Must
have a consumption function cFunc (using cubic or linear splines) as
a list with elements corresponding to the current Markov state. E.g.
solution.cFunc[0] is the consumption function when in the i=0 Markov
state this period.
Returns
-------
vFuncNow : [ValueFunc]
A list of value functions (defined over normalized market resources
m) for each current period Markov state.
"""
vFuncNow = [] # Initialize an empty list of value functions
# Loop over each current period state and construct the value function
for i in range(self.StateCount):
# Make state-conditional grids of market resources and consumption
mNrmMin = self.mNrmMin_list[i]
mGrid = mNrmMin + self.aXtraGrid
cGrid = solution.cFunc[i](mGrid)
aGrid = mGrid - cGrid
# Calculate end-of-period value at each gridpoint
EndOfPrdv_all = np.zeros((self.StateCount, self.aXtraGrid.size))
for j in range(self.StateCount):
if self.possible_transitions[i, j]:
EndOfPrdv_all[j, :] = self.EndOfPrdvFunc_list[j](aGrid)
EndOfPrdv = np.dot(self.MrkvArray[i, :], EndOfPrdv_all)
# Calculate (normalized) value and marginal value at each gridpoint
vNrmNow = self.u(cGrid) + EndOfPrdv
vPnow = self.uP(cGrid)
# Make a "decurved" value function with the inverse utility function
vNvrs = self.uinv(vNrmNow) # value transformed through inverse utility
vNvrsP = vPnow * self.uinvP(vNrmNow)
mNrm_temp = np.insert(mGrid, 0, mNrmMin) # add the lower bound
vNvrs = np.insert(vNvrs, 0, 0.0)
vNvrsP = np.insert(
vNvrsP, 0, self.MPCmaxEff[i] ** (-self.CRRA / (1.0 - self.CRRA))
)
MPCminNvrs = self.MPCminNow[i] ** (-self.CRRA / (1.0 - self.CRRA))
vNvrsFunc_i = CubicInterp(
mNrm_temp, vNvrs, vNvrsP, MPCminNvrs * self.hNrmNow[i], MPCminNvrs
)
# "Recurve" the decurved value function and add it to the list
vFunc_i = ValueFunc(vNvrsFunc_i, self.CRRA)
vFuncNow.append(vFunc_i)
return vFuncNow
def _solveGL(
solution_next,
IncomeDstn,
LivPrb,
DiscFac,
CRRA,
Rfree,
PermGroFac,
MrkvArray,
BoroCnstArt,
aXtraGrid,
vFuncBool,
CubicBool,
eta,
nu,
pssi,
B,
):
"""
Solves a single period consumption-saving problem with risky income and
stochastic transitions between discrete states, in a Markov fashion. Has
identical inputs as solveConsIndShock, except for a discrete
Markov transitionrule MrkvArray. Markov states can differ in their interest
factor, permanent growth factor, and income distribution, so the inputs Rfree,
PermGroFac, and IncomeDstn are arrays or lists specifying those values in each
(succeeding) Markov state.
Parameters
----------
solution_next : ConsumerSolution
The solution to next period's one period problem.
IncomeDstn_list : [[np.array]]
A length N list of income distributions in each succeeding Markov
state. Each income distribution contains three arrays of floats,
representing a discrete approximation to the income process at the
beginning of the succeeding period. Order: event probabilities,
permanent shocks, transitory shocks.
LivPrb : float
Survival probability; likelihood of being alive at the beginning of
the succeeding period.
DiscFac : float
Intertemporal discount factor for future utility.
CRRA : float
Coefficient of relative risk aversion.
Rfree_list : np.array
Risk free interest factor on end-of-period assets for each Markov
state in the succeeding period.
PermGroGac_list : float
Expected permanent income growth factor at the end of this period
for each Markov state in the succeeding period.
MrkvArray : numpy.array
An NxN array representing a Markov transition matrix between discrete
states. The i,j-th element of MrkvArray is the probability of
moving from state i in period t to state j in period t+1.
BoroCnstArt: float or None
Borrowing constraint for the minimum allowable assets to end the
period with. If it is less than the natural borrowing constraint,
then it is irrelevant; BoroCnstArt=None indicates no artificial bor-
rowing constraint.
aXtraGrid: np.array
Array of "extra" end-of-period asset values-- assets above the
absolute minimum acceptable level.
vFuncBool: boolean
An indicator for whether the value function should be computed and
included in the reported solution.
CubicBool: boolean
An indicator for whether the solver should use cubic or linear inter-
polation.
Returns
-------
solution : ConsumerSolution
The solution to the single period consumption-saving problem. Includes
a consumption function cFunc (using cubic or linear splines), a marg-
inal value function vPfunc, a minimum acceptable level of normalized
market resources mNrmMin, normalized human wealth hNrm, and bounding
MPCs MPCmin and MPCmax. It might also have a value function vFunc
and marginal marginal value function vPPfunc. All of these attributes
are lists or arrays, with elements corresponding to the current
Markov state. E.g. solution.cFunc[0] is the consumption function
when in the i=0 Markov state this period.
"""
solver = GLSolver(
solution_next,
IncomeDstn,
LivPrb,
DiscFac,
CRRA,
Rfree,
PermGroFac,
MrkvArray,
BoroCnstArt,
aXtraGrid,
vFuncBool,
CubicBool,
eta,
nu,
pssi,
B,
)
solution_now = solver.solve()
return solution_now
####################################################################################################
####################################################################################################
class MarkovConsumerType(IndShockConsumerType):
"""
An agent in the Markov consumption-saving model. His problem is defined by a sequence
of income distributions, survival probabilities, discount factors, and permanent
income growth rates, as well as time invariant values for risk aversion, the
interest rate, the grid of end-of-period assets, and how he is borrowing constrained.
"""
time_vary_ = IndShockConsumerType.time_vary_ + ["MrkvArray"]
time_inv_ = IndShockConsumerType.time_inv_ + ["eta",
"nu",
"pssi",
"B",
]
# Is 'MrkvNow' a shock or a state?
shock_vars_ = IndShockConsumerType.shock_vars_ + ["MrkvNow"]
state_vars = IndShockConsumerType.state_vars + ["MrkvNow"]
def __init__(self, cycles=100, **kwds):
IndShockConsumerType.__init__(self, cycles=1, **kwds)
self.solveOnePeriod = _solveGL
if not hasattr(self, "global_markov"):
self.global_markov = False
def checkMarkovInputs(self):
"""
Many parameters used by MarkovConsumerType are arrays. Make sure those arrays are the
right shape.
Parameters
----------
None
Returns
-------
None
"""
StateCount = self.MrkvArray[0].shape[0]
# Check that arrays are the right shape
if not isinstance(self.Rfree, np.ndarray) or self.Rfree.shape != (StateCount,):
raise ValueError(
"Rfree not the right shape, it should an array of Rfree of all the states."
)
# Check that arrays in lists are the right shape
for MrkvArray_t in self.MrkvArray:
if not isinstance(MrkvArray_t, np.ndarray) or MrkvArray_t.shape != (
StateCount,
StateCount,
):