Coverage for HARK / ConsumptionSaving / ConsAggIndMarkovModel.py: 43%

46 statements  

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

1""" 

2General-purpose consumer type with combined aggregate and idiosyncratic 

3discrete Markov states — the "hierarchical Markov" pattern. 

4 

5Both Krusell-Smith (1998) and the HAFiscal aggregate-demand model share 

6a common structure: agents face discrete *macro* (aggregate) states that 

7are common to the whole economy, plus discrete *micro* (idiosyncratic) 

8states whose transition probabilities depend on the current macro state. 

9The full state space is the Cartesian product of the two, encoded as a 

10single integer index: 

11 

12 combined = num_micro_states * macro_state + micro_state 

13 

14This module provides: 

15 

16* ``make_hierarchical_mrkv_array`` — builds the full (M*N) x (M*N) Markov 

17 transition matrix from an M x M aggregate matrix and M conditional N x N 

18 micro matrices. 

19 

20* ``AggIndMarkovConsumerType`` — an ``AgentType`` subclass that implements 

21 two-step Markov draws (macro from economy, micro per-agent) each period. 

22 

23Design note 

24----------- 

25This class was created in response to the prompt: 

26 

27 "Create a comprehensive, self-contained prompt document that will guide 

28 creation of a new general-purpose HARK class (AggIndMarkovConsumerType) 

29 and companion AggIndMarkovEconomy that unify the patterns currently 

30 implemented ad-hoc in HARK's KrusellSmithType and HAFiscal's 

31 AggFiscalType." 

32 

33The prompt was executed by Claude Opus 4.6 (Anthropic, 2025). 

34""" 

35 

36import numpy as np 

37 

38from HARK import AgentType 

39 

40 

41# ============================================================================= 

42# Utility: build the full hierarchical Markov transition matrix 

43# ============================================================================= 

44 

45 

46def make_hierarchical_mrkv_array(MacroMrkvArray, CondMrkvArrays): 

47 """ 

48 Build a full (M*N) x (M*N) Markov transition matrix. 

49 

50 Parameters 

51 ---------- 

52 MacroMrkvArray : np.ndarray, shape (M, M) 

53 Aggregate Markov transition matrix. 

54 CondMrkvArrays : list of np.ndarray, each shape (N, N) 

55 ``CondMrkvArrays[j][mi, mj]`` is ``Pr(micro'=mj | micro=mi, macro'=j)``. 

56 There must be M such matrices (one per destination macro state). 

57 

58 Returns 

59 ------- 

60 np.ndarray, shape (M*N, M*N) 

61 Full transition matrix with combined-state indexing 

62 ``combined = N * macro + micro``. 

63 """ 

64 M = MacroMrkvArray.shape[0] 

65 N = CondMrkvArrays[0].shape[0] 

66 full_size = M * N 

67 MrkvArray = np.zeros((full_size, full_size)) 

68 

69 for i in range(M): 

70 for j in range(M): 

71 p_macro = MacroMrkvArray[i, j] 

72 cond_micro = CondMrkvArrays[j] 

73 MrkvArray[N * i : N * (i + 1), N * j : N * (j + 1)] = p_macro * cond_micro 

74 

75 return MrkvArray 

76 

77 

78# ============================================================================= 

79# AggIndMarkovConsumerType 

80# ============================================================================= 

81 

82 

83class AggIndMarkovConsumerType(AgentType): 

84 """ 

85 A consumer with two-level hierarchical discrete Markov states. 

86 

87 * **M** aggregate (macro) states — common to all agents, received from 

88 an economy each period via the sow variable ``"Mrkv"``. 

89 * **N** idiosyncratic (micro) states — drawn per-agent each period, 

90 conditional on the new macro state. 

91 

92 The combined state index is ``N * macro + micro``. 

93 

94 This class does **not** bundle a solver; subclasses must set one 

95 via ``default_["solver"]``. 

96 

97 Subclass hooks 

98 -------------- 

99 * ``get_micro_markov_states()`` — override for exact-match or other 

100 custom micro-state draws (default: stochastic draw from 

101 ``CondMrkvArrays``). 

102 * ``get_states()``, ``get_controls()``, ``get_poststates()`` — the 

103 usual model-specific economics. 

104 

105 Attributes set each period 

106 -------------------------- 

107 * ``MacroMrkvNow`` (int): current macro state (scalar, from economy). 

108 * ``MicroMrkvNow`` (np.ndarray of int): per-agent micro states. 

109 * ``MrkvCombined`` (np.ndarray of int): per-agent combined-state indices. 

110 """ 

111 

112 shock_vars_ = ["Mrkv"] 

113 

114 def __init__(self, num_macro_states, num_micro_states, **kwds): 

115 self.num_macro_states = num_macro_states 

116 self.num_micro_states = num_micro_states 

117 self.CondMrkvArrays = None # set by economy or subclass 

118 AgentType.__init__(self, **kwds) 

119 

120 # ----- Hierarchical Markov draw machinery -------------------------------- 

121 

122 def get_markov_states(self): 

123 """Two-step draw: macro (from economy), then micro (per-agent).""" 

124 self.get_macro_markov_states() 

125 self.get_micro_markov_states() 

126 N = self.num_micro_states 

127 self.MrkvCombined = N * self.MacroMrkvNow + self.MicroMrkvNow 

128 

129 def get_macro_markov_states(self): 

130 """Read the scalar macro state sowed by the economy as ``"Mrkv"``.""" 

131 self.MacroMrkvNow = int(self.shocks["Mrkv"]) 

132 

133 def get_micro_markov_states(self): 

134 """ 

135 Draw idiosyncratic micro states conditional on the current macro state. 

136 

137 Default implementation: stochastic draw from ``self.CondMrkvArrays``. 

138 Override for exact-match permutation logic (e.g. Krusell-Smith). 

139 """ 

140 N = self.num_micro_states 

141 MacroNow = self.MacroMrkvNow 

142 micro_prev = self.MicroMrkvNow.copy() 

143 

144 new_micro = np.empty_like(micro_prev) 

145 for mi in range(N): 

146 mask = micro_prev == mi 

147 n_agents = mask.sum() 

148 if n_agents == 0: 

149 continue 

150 probs = self.CondMrkvArrays[MacroNow][mi, :] 

151 probs = probs / probs.sum() 

152 draws = self.RNG.choice(N, size=n_agents, p=probs) 

153 new_micro[mask] = draws 

154 self.MicroMrkvNow = new_micro 

155 

156 # ----- Convenience ------------------------------------------------------- 

157 

158 def macro_from_combined(self, mrkv): 

159 """Extract the macro state from a combined-state index.""" 

160 return mrkv // self.num_micro_states 

161 

162 def micro_from_combined(self, mrkv): 

163 """Extract the micro state from a combined-state index.""" 

164 return mrkv % self.num_micro_states