Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions HARK/Calibration/Income/IncomeProcesses.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
DiscreteDistributionLabeled,
IndexDistribution,
MeanOneLogNormal,
TimeVaryingDiscreteDistribution,
Lognormal,
Uniform,
)
Expand Down Expand Up @@ -813,14 +812,14 @@ def get_PermShkDstn_from_IncShkDstn(IncShkDstn, RNG):
PermShkDstn = [
this.make_univariate(0, seed=RNG.integers(0, 2**31 - 1)) for this in IncShkDstn
]
return TimeVaryingDiscreteDistribution(PermShkDstn, seed=RNG.integers(0, 2**31 - 1))
return IndexDistribution(distributions=PermShkDstn, seed=RNG.integers(0, 2**31 - 1))


def get_TranShkDstn_from_IncShkDstn(IncShkDstn, RNG):
TranShkDstn = [
this.make_univariate(1, seed=RNG.integers(0, 2**31 - 1)) for this in IncShkDstn
]
return TimeVaryingDiscreteDistribution(TranShkDstn, seed=RNG.integers(0, 2**31 - 1))
return IndexDistribution(distributions=TranShkDstn, seed=RNG.integers(0, 2**31 - 1))


def get_PermShkDstn_from_IncShkDstn_markov(IncShkDstn, RNG):
Expand Down
3 changes: 1 addition & 2 deletions HARK/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from HARK.distributions import (
Distribution,
IndexDistribution,
TimeVaryingDiscreteDistribution,
combine_indep_dstns,
)
from HARK.parallel import multi_thread_commands, multi_thread_commands_fake
Expand Down Expand Up @@ -1051,7 +1050,7 @@ def check_elements_of_time_vary_are_lists(self):
continue
if not isinstance(
getattr(self, param),
(TimeVaryingDiscreteDistribution, IndexDistribution),
(IndexDistribution,),
):
assert type(getattr(self, param)) == list, (
param
Expand Down
2 changes: 0 additions & 2 deletions HARK/distributions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"DiscreteDistributionLabeled",
"Distribution",
"IndexDistribution",
"TimeVaryingDiscreteDistribution",
"Lognormal",
"MeanOneLogNormal",
"Normal",
Expand All @@ -29,7 +28,6 @@
Distribution,
IndexDistribution,
MarkovProcess,
TimeVaryingDiscreteDistribution,
)
from HARK.distributions.continuous import (
Lognormal,
Expand Down
129 changes: 49 additions & 80 deletions HARK/distributions/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,8 @@ class IndexDistribution(Distribution):
class (such as Bernoulli, LogNormal, etc.) with information
about the conditions on the parameters of the distribution.

For example, an IndexDistribution can be defined as
a Bernoulli distribution whose parameter p is a function of
a different input parameter.
It can also wrap a list of pre-discretized distributions (previously
provided by TimeVaryingDiscreteDistribution) and provide the same API.

Parameters
----------
Expand All @@ -235,14 +234,19 @@ class (such as Bernoulli, LogNormal, etc.) with information
Keys should match the arguments to the engine class
constructor.

distributions: [DiscreteDistribution]
Optional. A list of discrete distributions to wrap directly.

seed : int
Seed for random number generator.
"""

conditional = None
engine = None

def __init__(self, engine, conditional, RNG=None, seed=0):
def __init__(
self, engine=None, conditional=None, distributions=None, RNG=None, seed=0
):
if RNG is None:
# Set up the RNG
super().__init__(seed)
Expand All @@ -255,11 +259,24 @@ def __init__(self, engine, conditional, RNG=None, seed=0):
# and create a new one.
self.seed = seed

self.conditional = conditional
# Mode 1: wrapping a list of discrete distributions
if distributions is not None:
self.distributions = distributions
self.engine = None
self.conditional = None
self.dstns = []
return

# Mode 2: engine + conditional parameters (original IndexDistribution)
self.conditional = conditional if conditional is not None else {}
self.engine = engine

self.dstns = []

# If no engine/conditional were provided, remain empty (should not happen in normal use)
if self.engine is None and not self.conditional:
return
Copy link
Preview

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

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

This comment suggests a condition that 'should not happen in normal use', but the code allows it. Consider either removing this case handling or throwing an exception if this truly represents an invalid state.

Suggested change
return
# If no engine/conditional were provided, this is an invalid state.
if self.engine is None and not self.conditional:
raise ValueError("MarkovProcess: No engine or conditional parameters provided; this should not happen in normal use.")

Copilot uses AI. Check for mistakes.


# Test one item to determine case handling
item0 = list(self.conditional.values())[0]

Expand All @@ -273,7 +290,7 @@ def __init__(self, engine, conditional, RNG=None, seed=0):

elif type(item0) is float:
self.dstns = [
self.engine(seed=self._rng.integers(0, 2**31 - 1), **conditional)
self.engine(seed=self._rng.integers(0, 2**31 - 1), **self.conditional)
]

else:
Expand All @@ -284,6 +301,9 @@ def __init__(self, engine, conditional, RNG=None, seed=0):
)

def __getitem__(self, y):
# Prefer discrete list mode if present
if hasattr(self, "distributions") and self.distributions:
return self.distributions[y]
return self.dstns[y]

def discretize(self, N, **kwds):
Expand All @@ -302,16 +322,16 @@ def discretize(self, N, **kwds):

Returns:
------------
dists : [DiscreteDistribution]
A list of DiscreteDistributions that are the
approximation of engine distribution under each condition.

TODO: It would be better if there were a conditional discrete
distribution representation. But that integrates with the
solution code. This implementation will return the list of
distributions representations expected by the solution code.
dists : [DiscreteDistribution] or IndexDistribution
If parameterization is constant, returns a single DiscreteDistribution.
If parameterization varies with index, returns an IndexDistribution in
discrete-list mode, wrapping the corresponding discrete distributions.
"""

# If already in discrete list mode, return self (already discretized)
if hasattr(self, "distributions") and self.distributions:
return self

# test one item to determine case handling
item0 = list(self.conditional.values())[0]

Expand All @@ -320,8 +340,12 @@ def discretize(self, N, **kwds):
return self.dstns[0].discretize(N, **kwds)

if type(item0) is list:
return TimeVaryingDiscreteDistribution(
[self[i].discretize(N, **kwds) for i, _ in enumerate(item0)]
# Return an IndexDistribution wrapping a list of discrete distributions
return IndexDistribution(
distributions=[
self[i].discretize(N, **kwds) for i, _ in enumerate(item0)
],
seed=self.seed,
)

def draw(self, condition):
Expand All @@ -345,6 +369,15 @@ def draw(self, condition):
# are of the same type.
# this matches the HARK 'time-varying' model architecture.

# If wrapping discrete distributions, draw from those
if hasattr(self, "distributions") and self.distributions:
draws = np.zeros(condition.size)
for c in np.unique(condition):
these = c == condition
N = np.sum(these)
draws[these] = self.distributions[c].draw(N)
return draws

# test one item to determine case handling
item0 = list(self.conditional.values())[0]

Expand All @@ -367,70 +400,6 @@ def draw(self, condition):
these = c == condition
N = np.sum(these)

cond = {key: val[c] for (key, val) in self.conditional.items()}
draws[these] = self[c].draw(N)

Copy link
Preview

Copilot AI Aug 9, 2025

Choose a reason for hiding this comment

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

The line cond = {key: val[c] for (key, val) in self.conditional.items()} was removed but self[c].draw(N) still depends on the conditional parameters being properly set for index c. This could cause incorrect behavior when drawing from indexed distributions.

Copilot uses AI. Check for mistakes.

return draws


class TimeVaryingDiscreteDistribution(Distribution):
"""
This class provides a way to define a discrete distribution that
is conditional on an index.

Wraps a list of discrete distributions.

Parameters
----------

distributions : [DiscreteDistribution]
A list of discrete distributions

seed : int
Seed for random number generator.
"""

distributions = []

def __init__(self, distributions, seed=0):
# Set up the RNG
super().__init__(seed)

self.distributions = distributions

def __getitem__(self, y):
return self.distributions[y]

def draw(self, condition):
"""
Generate arrays of draws.
The input is an array containing the conditions.
The output is an array of the same length (axis 1 dimension)
as the conditions containing random draws of the conditional
distribution.

Parameters
----------
condition : np.array
The input conditions to the distribution.

Returns:
------------
draws : np.array
"""
# for now, assume that all the conditionals
# are of the same type.
# this matches the HARK 'time-varying' model architecture.

# conditions are indices into list
# somewhat convoluted sampling strategy retained
# for test backwards compatibility
draws = np.zeros(condition.size)

for c in np.unique(condition):
these = c == condition
N = np.sum(these)

draws[these] = self.distributions[c].draw(N)

return draws
12 changes: 8 additions & 4 deletions HARK/distributions/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import numpy as np
from scipy import stats

from HARK.distributions.base import TimeVaryingDiscreteDistribution
from HARK.distributions.base import IndexDistribution
from HARK.distributions.discrete import (
DiscreteDistribution,
DiscreteDistributionLabeled,
Expand Down Expand Up @@ -265,10 +265,14 @@ def add_discrete_outcome_constant_mean(distribution, x, p, sort=False):
Probability associated with each point in array of discrete
points for discrete probability mass function.
"""
if type(distribution) == TimeVaryingDiscreteDistribution:
if (
isinstance(distribution, IndexDistribution)
and hasattr(distribution, "distributions")
and distribution.distributions
):
# apply recursively on all the internal distributions
return TimeVaryingDiscreteDistribution(
[
return IndexDistribution(
distributions=[
add_discrete_outcome_constant_mean(d, x, p)
for d in distribution.distributions
],
Expand Down
6 changes: 1 addition & 5 deletions HARK/simulation/monte_carlo.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from HARK.distributions import (
Distribution,
IndexDistribution,
TimeVaryingDiscreteDistribution,
)
from HARK.model import Aggregate
from HARK.model import DBlock
Expand Down Expand Up @@ -47,10 +46,7 @@ def draw_shocks(shocks: Mapping[str, Distribution], conditions: Sequence[int]):
draws[shock_var] = np.ones(len(conditions)) * shock
elif isinstance(shock, Aggregate):
draws[shock_var] = shock.dist.draw(1)[0]
elif isinstance(shock, IndexDistribution) or isinstance(
shock, TimeVaryingDiscreteDistribution
):
## TODO his type test is awkward. They should share a superclass.
elif isinstance(shock, IndexDistribution):
draws[shock_var] = shock.draw(conditions)
else:
draws[shock_var] = shock.draw(len(conditions))
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ To install for development see the [Quickstart Guide](https://docs.econ-ark.org/
For more information on contributing to HARK please see [the contributing guide](https://docs.econ-ark.org/docs/guides/contributing.html).
This is the guide that collaborators follow in maintaining the Econ-ARK project.

## Migration notes

- Distributions: `TimeVaryingDiscreteDistribution` has been consolidated into `IndexDistribution`.
- If you previously constructed a time-indexed list of discrete distributions via `TimeVaryingDiscreteDistribution([...])`, use `IndexDistribution(distributions=[...])` instead.
- Existing `IndexDistribution(engine=..., conditional=...)` usage is unchanged.

## Disclaimer

This is a beta version of HARK. The code has not been extensively tested as it should be. We hope it is useful, but there are absolutely no guarantees (expressed or implied) that it works or will do what you want. Use at your own risk. And please, let us know if you find bugs by posting an issue to [the GitHub page](https://github.com/econ-ark/HARK)!
Expand Down
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ See documentation notebooks in /examples/SequenceSpaceJacobians/ . The capabilit
- Documentation files have been moved from /Documentation/ to /docs/ [#1579](https://github.com/econ-ark/HARK/pull/1579)
- All tests have been consolidated into a single directory, rather than being scattered about. [#1578](https://github.com/econ-ark/HARK/pull/1578)
- Add a special README so that the robots know we're on their side when the singularity arrives. [#1577](https://github.com/econ-ark/HARK/pull/1577)
- Consolidates `TimeVaryingDiscreteDistribution` into `IndexDistribution`. For time-varying discrete behavior, use `IndexDistribution(distributions=[...])`. [#1592](https://github.com/econ-ark/HARK/pull/1592)


### 0.16.0
Expand Down
Binary file added test.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test.pdf
Binary file not shown.
Binary file added test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading