Simulation#

Functions to support Monte Carlo simulation of models.

HARK.simulation.monte_carlo.draw_shocks(shocks: Mapping[str, Distribution], conditions: Sequence[int])#

Draw from each shock distribution values, subject to given conditions.

Parameters:
  • shocks (Mapping[str, Distribution]) – A dictionary-like mapping from shock names to distributions from which to draw.

  • conditions (Sequence[int]) – An array of conditions, one for each agent. Typically these will be agent ages.

Returns:

draws – A mapping from shock names to drawn shock values.

Return type:

Mapping[str, Sequence]

HARK.simulation.monte_carlo.calibration_by_age(ages, calibration)#

Returns calibration for this model, but with vectorized values which map age-varying values to agent ages.

Parameters:
  • ages (np.array) – An array of agent ages.

  • calibration (dict) – A calibration dictionary

Returns:

aged_calibration – A dictionary of parameter values. If a parameter is age-varying, the value is a vector corresponding to the values for each input age.

Return type:

dict

class HARK.simulation.monte_carlo.Simulator#

Bases: object

Base class for Monte Carlo simulators.

Subclasses must set the following instance attributes before calling any of the methods defined here:

self.vars – list of variable names tracked in the simulation self.T_sim – int, number of periods to simulate self.agent_count – int, number of agents self.seed – int, random seed self.history – dict, populated by clear_history / simulate self.vars_now – dict, current-period variable values per agent self.newborn_init_history – dict, initial values for newborn agents self.t_sim – int, current simulation time step

Subclasses must also implement sim_one_period.

reset_rng()#

Reset the random number generator for this type.

clear_history()#

Clears the histories.

simulate(sim_periods=None)#

Simulate for a given number of periods, defaulting to self.T_sim.

Records histories of attributes named in self.vars in self.history[var_name].

Parameters:

sim_periods (int, optional) – Number of periods to simulate. If None, simulate for self.T_sim periods.

Returns:

history – The history tracked during the simulation.

Return type:

dict

class HARK.simulation.monte_carlo.AgentTypeMonteCarloSimulator(calibration, block: DBlock, dr, initial, seed=0, agent_count=1, T_sim=10)#

Bases: Simulator

A Monte Carlo simulation engine based on the HARK.core.AgentType framework.

Unlike HARK.core.AgentType, this class does not do any model solving, and depends on dynamic equations, shocks, and decision rules passed into it.

The purpose of this class is to provide a way to simulate models without relying on inheritance from the AgentType class.

This simulator makes assumptions about population birth and mortality which are not generic. All agents are replaced with newborns when they expire.

Parameters:
  • calibration (Mapping[str, Any])

  • block (DBlock) – Has shocks, dynamics, and rewards

  • dr (Mapping[str, Callable])

  • initial (dict)

  • seed (int) – A seed for this instance’s random number generator.

Attributes:
  • agent_count (int) – The number of agents of this type to use in simulation.

  • T_sim (int) – The number of periods to simulate.

state_vars = []#
initialize_sim()#

Prepares for a new simulation. Resets the internal random number generator, makes initial states for all agents (using sim_birth), clears histories of tracked variables.

sim_one_period()#

Simulates one period for this type. Calls the methods get_mortality(), get_shocks() or read_shocks, get_states(), get_controls(), and get_poststates(). These should be defined for AgentType subclasses, except get_mortality (define its components sim_death and sim_birth instead) and read_shocks.

make_shock_history()#

Makes a pre-specified history of shocks for the simulation. Shock variables should be named in self.shock, a mapping from shock names to distributions. This method runs a subset of the standard simulation loop by simulating only mortality and shocks; each variable named in shocks is stored in a T_sim x agent_count array in history dictionary self.history[X]. Automatically sets self.read_shocks to True so that these pre-specified shocks are used for all subsequent calls to simulate().

Returns:

shock_history – The subset of simulation history that are the shocks for each agent and time.

Return type:

dict

get_mortality()#

Simulates mortality or agent turnover. Agents die when their states live is less than or equal to zero.

sim_birth(which_agents)#

Makes new agents for the simulation. Takes a boolean array as an input, indicating which agent indices are to be “born”. Does nothing by default, must be overwritten by a subclass.

Parameters:

which_agents (np.array(Bool)) – Boolean array of size self.agent_count indicating which agents should be “born”.

Return type:

None

class HARK.simulation.monte_carlo.MonteCarloSimulator(calibration, block: DBlock, dr, initial, seed=0, agent_count=1, T_sim=10)#

Bases: Simulator

A Monte Carlo simulation engine based.

Unlike the AgentTypeMonteCarloSimulator HARK.core.AgentType, this class does make any assumptions about aging or mortality. It operates only on model information passed in as blocks.

It also does not have read_shocks functionality; it is a strict subset of the AgentTypeMonteCarloSimulator functionality.

Parameters:
  • calibration (Mapping[str, Any])

  • block (DBlock) – Has shocks, dynamics, and rewards

  • dr (Mapping[str, Callable])

  • initial (dict)

  • seed (int) – A seed for this instance’s random number generator.

Attributes:
  • agent_count (int) – The number of agents of this type to use in simulation.

  • T_sim (int) – The number of periods to simulate.

state_vars = []#
initialize_sim()#

Prepares for a new simulation. Resets the internal random number generator, makes initial states for all agents (using sim_birth), clears histories of tracked variables.

sim_one_period()#

Simulates one period for this type. Calls the methods get_mortality(), get_shocks() or read_shocks, get_states(), get_controls(), and get_poststates(). These should be defined for AgentType subclasses, except get_mortality (define its components sim_death and sim_birth instead) and read_shocks.

sim_birth(which_agents)#

Makes new agents for the simulation. Takes a boolean array as an input, indicating which agent indices are to be “born”. Does nothing by default, must be overwritten by a subclass.

Parameters:

which_agents (np.array(Bool)) – Boolean array of size self.agent_count indicating which agents should be “born”.

Return type:

None

Mixins for simulation variance reduction via moment normalization.

These mixins adjust cross-sectional distributions during simulation so that empirical moments match their analytical values, eliminating sampling noise in aggregates without requiring special population sizes.

Both mixins are strictly opt-in: composing them into a class and leaving their switches at the defaults (normalize_shocks=False, normalize_pLvl=False) changes nothing about a simulation.

Usage:

from HARK.ConsumptionSaving.ConsIndShockModel import IndShockConsumerType
from HARK.simulation.normalization import (
    PermanentIncomeNormalizationMixin,
    ShockNormalizationMixin,
)

class NormalizedIndShock(
    ShockNormalizationMixin,
    PermanentIncomeNormalizationMixin,
    IndShockConsumerType,
):
    pass

agent = NormalizedIndShock(normalize_pLvl=True, normalize_shocks=True, ...)

Both mixins hook into HARK’s own extension points: get_shocks (via super()) and AgentType.post_state_hook, the no-op invoked by sim_one_period between get_states() and get_controls(). Neither overrides sim_one_period itself, so a model with its own sim_one_period (e.g. ConsRiskyContribConsumerType, whose staged structure forces one) is not shadowed. Such a model may not call post_state_hook at all, and initialize_sim warns when that is the case rather than letting normalization silently never run.

Growth is inside the permanent shock#

HARK folds the expected growth factor into the permanent shock array: PermShkNow = psi * PermGroFac, with psi the mean-one innovation (see IndShockConsumerType.get_shocks, and ConsMarkovModel per discrete state). The cross-sectional mean of shocks["PermShk"] is therefore PermGroFac, not 1.0, and ShockNormalizationMixin targets that. Rescaling PermShk to 1.0 would delete permanent income growth rather than sampling noise, because transition() applies the array directly as pLvl = pLvlPrev * PermShk. Transitory shocks genuinely are mean-one, so 1.0 remains the target there.

Age awareness#

Targets are computed period by period from the income process actually used in each simulated period, so life-cycle calibrations with age-varying PermGroFac and PermShkDstn (HARK ships several) get the right profile rather than period 0’s parameters extrapolated. The indexing mirrors get_shocks: an agent at t_cycle == j draws from index (j - 1) % T_cycle, except in its birth period, which is special-cased to index 0.

Design notes#

  • Markov-capable. Models whose per-period growth factor is a vector over discrete (Markov) states are supported: the analytical drift uses the stationary-distribution-weighted mean log growth, and the moment targeting automatically degrades to MEAN-ONLY (see below).

  • Why mean-only under state-dependent growth. With heterogeneous growth histories (e.g. employed vs unemployed spells), the cross- sectional variance of log pLvl at a given age exceeds the pure shock-accumulation term by the variance contributed by heterogeneous Markov paths. The simple analytical variance formula used here counts only the shock term, so rescaling the cross-sectional spread to it would shrink genuine heterogeneity. Shifting the mean is exact and safe; therefore pLvl_norm_moments="auto" applies mean-and-std for scalar-growth models and mean-only for vector-growth models. Models with a correct model-specific variance target can override _analytical_log_pLvl_moments and set pLvl_norm_moments="mean_and_std".

  • Stationarity caveat. The stationary-weighted drift is the exact population drift only once the discrete state has reached its stationary distribution. In a life-cycle Markov model (T_cycle > 1 with vector growth) young cohorts have not, so the mean target is approximate and initialize_sim warns.

class HARK.simulation.normalization.ShockNormalizationMixin#

Bases: _NormalizationIndexMixin

Opt-in normalization of income shocks to pin cross-sectional means.

After get_shocks() draws PermShk and TranShk for the population, rescales each so that the cross-sectional mean equals its theoretical value: PermGroFac for PermShk (HARK folds growth into that array; see the module docstring) and 1.0 for TranShk. This makes the aggregate effect of sampling noise exact in every period, regardless of population size, without touching the deterministic growth trend.

Agents are grouped by the income process they actually drew from – the period index from _income_dstn_index() and, when the model carries one, the discrete (Markov) state – and normalized within group, since only within a group is the target common.

Attributes:

normalize_shocks (bool) – When True, shock normalization is applied. Default False (no behavior change).

normalize_shocks = False#
get_shocks()#

Draw shocks, then normalize cross-sectional means if enabled.

read_shocks_from_history()#

Replay stored shocks, warning that normalization is bypassed.

class HARK.simulation.normalization.PermanentIncomeNormalizationMixin#

Bases: _NormalizationIndexMixin

Opt-in per-cohort pLvl normalization for variance reduction.

Within each age cohort k, adjusts log(pLvl) so that its cross-sectional moments match analytical values. The affine transform in log space preserves the rank ordering of agents’ permanent incomes, so within-cohort wealth-income correlations are unaffected. Each cohort gets a different affine map, so a correlation pooled across ages is not guaranteed to be preserved exactly where age is itself correlated with wealth. Normalized state variables (mNrm, bNrm) are rescaled inversely so that level quantities (mLvl = mNrm * pLvl) are preserved.

Attributes:
  • normalize_pLvl (bool) – When True, normalization runs each simulated period. Default False (no behavior change).

  • pLvl_norm_moments (str) – "auto" (default): mean-and-std for scalar-growth models, mean-only for vector-(Markov-)growth models. "mean_and_std" or "mean" force the respective behavior. See the module docstring for why mean-only is the safe default under state-dependent growth.

  • _pLvl_norm_adjust_vars (tuple of str) – Normalized state variables rescaled by the inverse pLvl change, so that the level quantities they imply are preserved. Default ("mNrm", "bNrm"). Override on a model carrying a different set of normalized states; a normalized state left out of this tuple keeps its old value while its pLvl moves, which silently changes the level it stands for.

  • _pLvl_norm_min_cohort (int) – Smallest cohort that gets normalized. Default 5. Cohorts below it are skipped and reported through the small_cohort warning, whose text points here.

normalize_pLvl = False#
pLvl_norm_moments = 'auto'#
post_sim_normalize_pLvl()#

Normalize pLvl to the analytical per-cohort log-moments.

Applies the resolved moments mode per age cohort, then rescales the variables in _pLvl_norm_adjust_vars by the inverse pLvl ratio so level quantities are preserved. Cohorts smaller than _pLvl_norm_min_cohort, and cohorts whose members disagree about t_cycle (so their income history is not the one _income_dstn_index_history() reconstructs), are left alone with a warning rather than normalized to a target that does not apply to them.

initialize_sim()#

Reset the moment cache and check that the hook will actually fire.

post_state_hook()#

Run the normalization; chain to any base-class hook first.