Skip to content

Implement timing-corrected model architecture in HARK (ConsumptionSavingX)#1584

Draft
Copilot wants to merge 4 commits intomainfrom
copilot/fix-1565
Draft

Implement timing-corrected model architecture in HARK (ConsumptionSavingX)#1584
Copilot wants to merge 4 commits intomainfrom
copilot/fix-1565

Conversation

Copy link
Contributor

Copilot AI commented Jul 24, 2025

This PR implements a timing-corrected model architecture to address the confusing parameter indexing in HARK's consumption-saving models. The current "solver-first" timing convention causes parameters to be indexed by when the solver needs them rather than the actual period they belong to, leading to error-prone offsets and arbitrary workarounds.

Problem

The original HARK design has several timing-related issues:

  1. Inconsistent parameter indexing across methods:

    # Different methods use different indexing schemes
    get_Rfree(): Rfree_array[self.t_cycle]
    get_shocks(): PermGroFac[self.t_cycle - 1] 
    sim_death(): DiePrb_by_t_cycle[self.t_cycle - 1 if self.cycles == 1 else self.t_cycle]
  2. Newborn parameter hack requiring 60+ lines of workaround code:

    # That procedure used the *last* period in the sequence for newborns, but that's not right
    # Redraw shocks for newborns, using the *first* period in the sequence. Approximation.
    IncShkDstnNow = self.IncShkDstn[0]  # Arbitrary fallback!
  3. Confusing lifecycle parameter creation where parameters don't align with their mathematical meaning.

Solution

This PR creates a parallel ConsumptionSavingX module with timing-corrected implementations:

🔧 Consistent Parameter Indexing

All methods now use uniform indexing logic:

# Consistent across all parameter access methods
t_index = t_cycle - 1 if self.cycles == 1 else t_cycle

🚫 Eliminated Newborn Hack

The arbitrary newborn parameter workaround is completely removed. Newborns now get proper parameters through consistent indexing automatically.

📖 Clear Parameter Semantics

  • Rfree[t] is the interest rate that applies in period t
  • LivPrb[t] is the survival probability for period t
  • PermGroFac[t] is the growth factor applied in period t

Usage

Users can easily switch to timing-corrected models:

# Original
from HARK.ConsumptionSaving.ConsIndShockModel import init_lifecycle

# Timing-corrected  
from HARK.ConsumptionSavingX.ConsIndShockModel import init_lifecycle_X

Key Files

  • HARK/ConsumptionSavingX/ - Complete timing-corrected module
  • HARK/ConsumptionSavingX/README.md - Documentation with before/after examples
  • tests/ConsumptionSavingX/ - Test structure for validation
  • examples/ConsumptionSavingX_timing_demo.py - Interactive demonstration
  • IMPLEMENTATION_SUMMARY.md - Detailed technical overview

Benefits

  • Educational: Eliminates confusing timing offsets that "every single person" found problematic
  • Safe Migration: Parallel implementation allows gradual adoption and validation
  • Foundation for Modularity: Enables cleaner separation between solvers, simulators, and model definitions
  • External Integration: Better compatibility with frameworks like Dolo and Dynare

Compatibility

  • Infinite-horizon models: Identical results (timing doesn't matter for cyclical patterns)
  • Finite-horizon models: Very similar results with corrected parameter semantics
  • API: Same interface, just different timing conventions

This timing-corrected architecture provides the logical, error-resistant foundation for HARK's future development while maintaining full backward compatibility.

Fixes #1565.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

Copilot AI and others added 3 commits July 24, 2025 23:06
Co-authored-by: alanlujan91 <5382704+alanlujan91@users.noreply.github.com>
Co-authored-by: alanlujan91 <5382704+alanlujan91@users.noreply.github.com>
…mentation and examples

Co-authored-by: alanlujan91 <5382704+alanlujan91@users.noreply.github.com>
Copilot AI changed the title [WIP] Timing-Corrected Model Architecture in HARK Implement timing-corrected model architecture in HARK (ConsumptionSavingX) Jul 24, 2025
Copilot AI requested a review from alanlujan91 July 24, 2025 23:14
@mnwhite mnwhite moved this to In progress in Issues & PRs Jan 3, 2026
@alanlujan91 alanlujan91 requested a review from Copilot January 28, 2026 19:15
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

This PR adds a parallel ConsumptionSavingX module intended to implement timing-corrected consumption-saving (and related) model variants to eliminate confusing parameter indexing offsets in HARK’s legacy “solver-first” timing convention.

Changes:

  • Introduces multiple timing-corrected model modules (portfolio, Markov, labor, rep-agent, and HANK-related extensions) under HARK/ConsumptionSavingX/.
  • Adds labeled/xarray-based solver infrastructure for select models (EGM-style workflow with labeled shock distributions).
  • Adds a Numba-accelerated “fast” variant of IndShock/PerfForesight logic in ConsumptionSavingX.

Reviewed changes

Copilot reviewed 15 out of 26 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
HARK/ConsumptionSavingX/ConsWealthPortfolioModel.py Adds a wealth-in-utility portfolio model and one-period solver in the timing-corrected module.
HARK/ConsumptionSavingX/ConsSequentialPortfolioModel.py Adds a sequential portfolio consumer type wired to a legacy OO solver.
HARK/ConsumptionSavingX/ConsRepAgentModel.py Adds representative-agent and Markov RA model implementations under ConsumptionSavingX.
HARK/ConsumptionSavingX/ConsNewKeynesianModel.py Adds a HANK-oriented consumer type with grid-based transition simulation and Jacobian routines.
HARK/ConsumptionSavingX/ConsMarkovModel.py Adds a Markov-state consumption-saving model implementation under ConsumptionSavingX.
HARK/ConsumptionSavingX/ConsLaborModel.py Adds an intensive-margin labor supply + consumption-saving model implementation.
HARK/ConsumptionSavingX/ConsLabeledModel.py Adds xarray/labeled distribution based solvers for PF/IndShock/RiskyAsset/Portfolio variants.
HARK/ConsumptionSavingX/ConsIndShockModelFast.py Adds a Numba-accelerated implementation of PF and IndShock solution logic.

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

Comment on lines +366 to +370
end_dvda, end_dvds = DiscFacEff * expected(
calc_end_dvdx,
RiskyDstn,
args=(aNrmNow, ShareNext, Rfree, med_dvdb_func),
)
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

This multiplies DiscFacEff * expected(...) before unpacking. If expected(...) returns a tuple (common when the integrand returns multiple arrays), float * tuple will raise a TypeError. Apply DiscFacEff after unpacking (or multiply each returned component) to keep this robust regardless of expected’s return type.

Suggested change
end_dvda, end_dvds = DiscFacEff * expected(
calc_end_dvdx,
RiskyDstn,
args=(aNrmNow, ShareNext, Rfree, med_dvdb_func),
)
end_dvda, end_dvds = expected(
calc_end_dvdx,
RiskyDstn,
args=(aNrmNow, ShareNext, Rfree, med_dvdb_func),
)
end_dvda *= DiscFacEff
end_dvds *= DiscFacEff

Copilot uses AI. Check for mistakes.
end_v = DiscFacEff * expected(
calc_end_v,
RiskyDstn,
args=(aNrmNow, ShareNext, PermGroFac, CRRA, med_v_func),
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

calc_end_v is defined as calc_end_v(shocks, a_nrm, share, rfree, v_func), but this call passes five args after shocks and also passes PermGroFac/CRRA where rfree/v_func are expected. This will raise at runtime (wrong arity / wrong argument meaning). Update the expected(..., args=...) call to match calc_end_v’s signature (use Rfree and med_v_func, and do not pass PermGroFac/CRRA).

Suggested change
args=(aNrmNow, ShareNext, PermGroFac, CRRA, med_v_func),
args=(aNrmNow, ShareNext, Rfree, med_v_func),

Copilot uses AI. Check for mistakes.

self.BoroCnstNat = (
self.solution_next.attrs["m_nrm_min"] - 1
) / self.params.Rfree
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The perfect-foresight natural borrowing constraint is missing the PermGroFac factor. Given m_{t+1} = a_t * Rfree / PermGroFac + 1, solving for a_t at the minimum implies BoroCnstNat = (m_min_next - 1) * PermGroFac / Rfree. Omitting PermGroFac yields an incorrect borrowing constraint whenever PermGroFac != 1.

Suggested change
) / self.params.Rfree
) * self.params.PermGroFac / self.params.Rfree

Copilot uses AI. Check for mistakes.
next_state = {} # pytree
next_state["rDiff"] = params.Rfree - shocks["risky"]
next_state["rPort"] = (
params.Rfree + next_state["rDiff"] * params.RiskyShareFixed
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The portfolio return algebra appears to have the sign flipped. For a fixed risky share s, the standard definition is rPort = Rfree + (risky - Rfree) * s. With rDiff = Rfree - risky, the current code computes rPort = Rfree + (Rfree - risky) * s, which moves returns in the wrong direction as the risky return increases.

Suggested change
params.Rfree + next_state["rDiff"] * params.RiskyShareFixed
params.Rfree - next_state["rDiff"] * params.RiskyShareFixed

Copilot uses AI. Check for mistakes.
Comment on lines +265 to +274
print(
"Error: make sure CRRA coefficient is strictly greater than alpha/(1+alpha)."
)
sys.exit()
if BoroCnstArt is not None:
print("Error: Model cannot handle artificial borrowing constraint yet. ")
sys.exit()
if vFuncBool or CubicBool is True:
print("Error: Model cannot handle cubic interpolation yet.")
sys.exit()
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Library/model code should not call sys.exit() on invalid configuration because it terminates the entire host process (including notebooks and downstream applications). Replace these with appropriate exceptions (e.g., ValueError for invalid parameter restrictions, and NotImplementedError for unsupported features like BoroCnstArt/cubic/value-function options).

Suggested change
print(
"Error: make sure CRRA coefficient is strictly greater than alpha/(1+alpha)."
)
sys.exit()
if BoroCnstArt is not None:
print("Error: Model cannot handle artificial borrowing constraint yet. ")
sys.exit()
if vFuncBool or CubicBool is True:
print("Error: Model cannot handle cubic interpolation yet.")
sys.exit()
raise ValueError(
"CRRA coefficient must be strictly greater than alpha/(1+alpha)."
)
if BoroCnstArt is not None:
raise NotImplementedError(
"Model cannot handle artificial borrowing constraint yet."
)
if vFuncBool or CubicBool is True:
raise NotImplementedError(
"Model cannot handle cubic interpolation or value-function options yet."
)

Copilot uses AI. Check for mistakes.

class WealthPortfolioConsumerType(PortfolioConsumerType):
"""
TODO: This docstring is missing and needs to be written.
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

This introduces a public consumer type, but its docstring is a TODO. Please replace with a real docstring describing the model, key parameters (especially WealthShare/WealthShift and the timing convention), and any important constraints/assumptions (e.g., the enforced BoroCnstArt=0.0).

Suggested change
TODO: This docstring is missing and needs to be written.
Consumer type for a portfolio choice / consumptionsaving model with a
wealth-based state variable.
This type extends :class:`PortfolioConsumerType` by tracking the share of
total market resources held in the risky asset, in addition to the usual
cash-on-hand state. The model follows the standard HARK timing convention:
* At the beginning of each period the agent observes current market
resources and any exogenous state, chooses consumption and a portfolio
allocation between a risk-free and a risky asset.
* Between periods, portfolio returns and income shocks are realized, then
the next period's state is formed.
Key parameters
--------------
WealthShare : array-like or None
A specification of the wealth-share state grid (the share of total
market resources allocated to the risky asset). This governs the
discretization of the wealth share that enters the solution and value
functions.
WealthShift : array-like or None
A shift or transformation applied to the wealth-share state. This is
used to map between the model's internal wealth-share representation
and the raw portfolio share, and can vary by period.
ChiFunc : callable
A function of wealth (and possibly other states) that captures the
utility cost or adjustment friction associated with shifting the
risky share of wealth.
RiskyDstn : distribution-like
Distribution of returns on the risky asset, possibly already combined
with income shocks.
Important assumptions
---------------------
* The model enforces a natural borrowing constraint with
``BoroCnstArt = 0.0``; i.e., the agent cannot choose negative market
resources in the next period via an exogenous borrowing limit.
* Time-invariant attributes extend those of :class:`PortfolioConsumerType`
via ``time_inv_``, adding ``WealthShare``, ``WealthShift``, ``ChiFunc``,
and ``RiskyDstn``.
* The terminal period solution is constructed so that the risky share
decision is degenerate at 1.0 (all wealth effectively in the safe or
terminal asset), implemented via ``ConstantFunction(1.0)`` for the
share function.

Copilot uses AI. Check for mistakes.
Rfree : [float]
Risk free interest factor on end-of-period assets for each Markov
state in the succeeding period.
PermGroGac : [float]
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Correct the typo in the docstring: PermGroGac should be PermGroFac.

Suggested change
PermGroGac : [float]
PermGroFac : [float]

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

Status: In progress

Development

Successfully merging this pull request may close these issues.

Timing-Corrected Model Architecture in HARK

3 participants