Skip to content

[WIP] Habit formation consumption-saving model#1739

Open
mnwhite wants to merge 4 commits intomainfrom
HabitFormation
Open

[WIP] Habit formation consumption-saving model#1739
mnwhite wants to merge 4 commits intomainfrom
HabitFormation

Conversation

@mnwhite
Copy link
Contributor

@mnwhite mnwhite commented Mar 5, 2026

This PR adds a new consumption-saving model that extends IndShockConsumerType to include consumption habit formation. The two new parameters are HabitWgt ($\alpha$), the exponent on the habit stock when used as a divisor in the utility function and HabitRte ($\lambda$), the linear weight on current consumption when updating the habit stock.

Utility:
$u(c,h) = (c/h^\alpha)^{1-\rho} / (1-\rho).$

Dynamics:
$H_t = \lambda c_t + (1-\lambda) h_t,$
$h_{t+1} = H_t / (\psi_{t+1} \Gamma_{t+1}).$

The end-of-period habit stock $H_t$ acts as a second post-state variable. Note that we also apply the permanent income normalization to $h_t$, so when time advances to $t+1$ we divide it by realized permanent income growth. The permanent income scaling exponent in the Bellman equation is also changed by the presence of habits.

The model solves pretty quickly, at least once the numba helper function has compiled.

mnwhite added 2 commits March 5, 2026 09:48
Programmed this on Monday, moving to PR branch now.
Using lowercase and uppercase gamma to mean different things wasn't great.
@mnwhite mnwhite changed the title Habit formation consumption-saving model [WIP] Habit formation consumption-saving model Mar 5, 2026
@mnwhite
Copy link
Contributor Author

mnwhite commented Mar 5, 2026

This currently has no tests and no notebook. I'll get a simple demonstration notebook up and running right now, but it will be heavily revised before merging.

This doesn't have information about parameters or constructors, but it does have a neat demonstration of what habits do.
@alanlujan91 alanlujan91 requested a review from Copilot March 5, 2026 18:00
Fix a few words in notebook and comments.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +484 to +489
default_ = {
"params": HabitConsumerType_defaults,
"solver": solve_one_period_ConsHabit,
"model": "ConsHabit.yaml",
"track_vars": ["aNrm", "cNrm", "mNrm", "hNrm", "pLvl"],
}
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a new public consumer type (HabitConsumerType) but doesn’t add any tests. There are analogous smoke/behavior tests for other consumption-saving models under tests/ConsumptionSaving/; please add at least a basic test that (1) instantiates HabitConsumerType with defaults, (2) calls solve(), and (3) runs initialize_sym() + symulate() for a few periods to ensure the YAML model spec and solver stay in sync.

Copilot uses AI. Check for mistakes.
- LivPrb \\ survival probability at end of period
- HabitRte \\ rate of habit stock updating
functions:
- cFunc* \\ consumption function over market resources
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cFunc* is described as “consumption function over market resources”, but in this model it’s called with two arguments (mNrm, hNrm). Consider updating the description to reflect that the policy depends on both market resources and the normalized habit stock, to avoid confusion when using the simulator’s model introspection.

Suggested change
- cFunc* \\ consumption function over market resources
- cFunc* \\ consumption function of normalized market resources mNrm and habit stock hNrm

Copilot uses AI. Check for mistakes.
Comment on lines +453 to +461
class HabitConsumerType(AgentType):
r"""
A class for representing consumers who form consumption habits. Agents get
flow utility according to a CRRA felicity function that depends on both current
consumption and the habit stock h_t. The habit stock evolves as a weighted
average of current consumption and prior habit stock. Consumers can save in
a single risk-free asset, so this model is an extension of the workhorse
IndShockConsumerType to include a habit stock.

Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says this model “extends IndShockConsumerType”, but HabitConsumerType currently inherits directly from AgentType. If this is intentional, please update the PR description/docstring accordingly; if not, inheriting from IndShockConsumerType would reuse the existing restriction checks, simulation hooks, and established API behavior.

Copilot uses AI. Check for mistakes.
Parameters
----------
hLogInitMean : float
Mean of log habit stockfor newborns.
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in docstring: “stockfor” should be “stock for”.

Suggested change
Mean of log habit stockfor newborns.
Mean of log habit stock for newborns.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +26
- Rfree \\ risk free return factor on assets
- PermGroFac \\ expected permanent income growth factor
- LivPrb \\ survival probability at end of period
- HabitRte \\ rate of habit stock updating
functions:
- cFunc* \\ consumption function over market resources
distributions:
- IncShkDstn \\ joint distribution of permanent and transitory shocks
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In most consumption-saving YAML model specs that are used by initialize_sym/symulate, time-varying objects that should be indexed with a one-period offset are flagged with + (or listed under symbols: offset:), e.g. Rfree+, PermGroFac+, and IncShkDstn+ in ConsPortfolio/ConsLaborIntMarg. ConsHabit.yaml currently declares these without +, which will change how make_simulator_from_agent selects period-specific values for these sequences. Please confirm the intended timing and add + flags (or an explicit symbols: offset: list) if the habit model should match the standard consumption model timing.

Suggested change
- Rfree \\ risk free return factor on assets
- PermGroFac \\ expected permanent income growth factor
- LivPrb \\ survival probability at end of period
- HabitRte \\ rate of habit stock updating
functions:
- cFunc* \\ consumption function over market resources
distributions:
- IncShkDstn \\ joint distribution of permanent and transitory shocks
- Rfree+ \\ risk free return factor on assets
- PermGroFac+ \\ expected permanent income growth factor
- LivPrb+ \\ survival probability at end of period
- HabitRte \\ rate of habit stock updating
functions:
- cFunc* \\ consumption function over market resources
distributions:
- IncShkDstn+ \\ joint distribution of permanent and transitory shocks

Copilot uses AI. Check for mistakes.
Grid of consumption habit stocks on which to solve the problem.
FOCinverter : HabitFormationInverter
Function that inverts the first order conditions to yield optimal consumption
and the decision-time habit stock from which it was chose.
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammar in docstring: “from which it was chose” should be “from which it was chosen”.

Suggested change
and the decision-time habit stock from which it was chose.
and the decision-time habit stock from which it was chosen.

Copilot uses AI. Check for mistakes.
"LivPrb": [0.98], # Survival probability after each period
"PermGroFac": [1.01], # Permanent income growth factor
"BoroCnstArt": 0.0, # Artificial borrowing constraint
"HabitWgt": 0.5, # Weight on consumption habit
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter comment mismatch: HabitWgt is described elsewhere (and in the PR description) as the exponent on habit stock in utility, but the default params comment says “Weight on consumption habit”. Consider updating the inline comment to “Exponent on habit stock in utility” (or similar) for consistency.

Suggested change
"HabitWgt": 0.5, # Weight on consumption habit
"HabitWgt": 0.5, # Exponent on habit stock in utility

Copilot uses AI. Check for mistakes.
Comment on lines +113 to +117
def __call__(self, H, chi):
h = self.hFunc(H, chi)
rate = self.rate
c = (H - (1 - rate) * h) / (rate)
return c, h
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HabitFormationInverter.call/cFunc divides by rate (HabitRte). If HabitRte is 0 (which the docstring describes as a valid edge case), this will raise a ZeroDivisionError and the model can't be solved. Consider validating 0 < HabitRte < 1 at construction time (and update the docstring), or implement an explicit HabitRte==0 branch with the correct limiting FOC logic.

Copilot uses AI. Check for mistakes.
Comment on lines +283 to +286
cNrm = np.concatenate((np.zeros((1, HabitGrid.size)), cNrm), axis=0)
mNrm = np.concatenate((aNrmMin * np.ones((1, HabitGrid.size)), mNrm), axis=0)
hBot = np.reshape(HabitGrid / (1.0 - HabitRte), (1, HabitGrid.size))
hNrm = np.concatenate((hBot, hNrm), axis=0)
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hBot = HabitGrid / (1.0 - HabitRte) will divide by zero when HabitRte==1, and the rest of the solver/inverter also assumes HabitRte is strictly between 0 and 1. Please either (a) enforce 0 < HabitRte < 1 with a clear ValueError early in solve_one_period_ConsHabit/constructor, or (b) add a dedicated HabitRte==1 limiting-case implementation.

Copilot uses AI. Check for mistakes.
Returns
-------
HabitInitDstn : DiscreteDistribution
Discretized distribution of initial capital holdings for newborns.
Copy link

Copilot AI Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Returns section of make_lognormal_habit_init_dstn says it returns “initial capital holdings for newborns”, but this function constructs the initial habit stock distribution. Please update the Returns description to match what’s actually returned.

Suggested change
Discretized distribution of initial capital holdings for newborns.
Discretized distribution of initial (normalized) habit stock for newborns.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants