Skip to content

Commit

Permalink
Merge branch 'master' into plumbing/more_param_types
Browse files Browse the repository at this point in the history
  • Loading branch information
mnwhite authored Jun 28, 2024
2 parents 2ece3ea + 3faa570 commit f50b08d
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 11 deletions.
10 changes: 6 additions & 4 deletions Documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ For more information on HARK, see [our Github organization](https://github.com/e

## Changes

### 0.15.2 (in development)
### 0.16.0 (in development)

Release Date: TBD

#### Major Changes
### Major Changes

none yet
- Adds a discretize method to DBlocks and RBlocks (#1460)[https://github.com/econ-ark/HARK/pull/1460]
- Allows structural equations in model files to be provided in string form [#1427](https://github.com/econ-ark/HARK/pull/1427)
- Introduces `HARK.parser' module for parsing configuration files into models [#1427](https://github.com/econ-ark/HARK/pull/1427)

#### Minor Changes

Expand Down Expand Up @@ -51,7 +53,7 @@ This release drops support for Python 3.8 and 3.9, consistent with SPEC 0, and a
- Object-oriented solver code has been moved to /HARK/ConsumptionSaving/LegacyOOsolvers.py, for legacy support of downstream projects.
- AgentTypeMonteCarloSimulator now requires model shock, parameter, and dynamics information to be organized into 'blocks'. The DBlock object is introduced. [#1411](https://github.com/econ-ark/HARK/pull/1411)
- RBlock object allows for recursive composition of DBlocks in models, as demonstrated by the AgentTypeMonteCarloSimulator [#1417](https://github.com/econ-ark/HARK/pull/1417/)
- Transtion, reward, state-rulle value function, decision value function, and arrival value function added to DBlock [#1417](https://github.com/econ-ark/HARK/pull/1417/)
- Transtion, reward, state-rule value function, decision value function, and arrival value function added to DBlock [#1417](https://github.com/econ-ark/HARK/pull/1417/)
- All methods that construct inputs for solvers are now functions that are specified in the dictionary attribute `constructors`. [#1410](https://github.com/econ-ark/HARK/pull/1410)
- Such constructed inputs can use alternate parameterizations / formats by changing the `constructor` function and providing its arguments in `parameters`.
- Move `HARK.datasets` to `HARK.Calibration` for better organization of data and calibration tools. [#1430](https://github.com/econ-ark/HARK/pull/1430)
Expand Down
67 changes: 62 additions & 5 deletions HARK/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
Tools for crafting models.
"""

from dataclasses import dataclass, field
from dataclasses import dataclass, field, replace
from copy import copy, deepcopy
from HARK.distribution import (
Distribution,
DiscreteDistributionLabeled,
Expand All @@ -11,6 +12,7 @@
)
from inspect import signature
import numpy as np
from HARK.parser import math_text_to_lambda
from typing import Any, Callable, Mapping, List, Union


Expand Down Expand Up @@ -134,16 +136,29 @@ def simulate_dynamics(
return vals


class Block:
pass


@dataclass
class DBlock:
class DBlock(Block):
"""
Represents a 'block' of model behavior.
It prioritizes a representation of the dynamics of the block.
Control variables are designated by the appropriate dynamic rule.
Parameters
----------
...
shocks: Mapping(str, Distribution)
A mapping from variable names to Distribution objects,
representing exogenous shocks.
dynamics: Mapping(str, str or callable)
A dictionary mapping variable names to mathematical expressions.
These expressions can be simple functions, in which case the
argument names should match the variable inputs.
Or these can be strings, which are parsed into functions.
"""

name: str = ""
Expand All @@ -152,6 +167,31 @@ class DBlock:
dynamics: dict = field(default_factory=dict)
reward: dict = field(default_factory=dict)

def discretize(self, disc_params):
"""
Returns a new DBlock which is a copy of this one, but with shock discretized.
"""

disc_shocks = {}

for shockn in self.shocks:
if shockn in disc_params:
disc_shocks[shockn] = self.shocks[shockn].discretize(
**disc_params[shockn]
)
else:
disc_shocks[shockn] = deepcopy(self.shocks[shockn])

# replace returns a modified copy
new_dblock = replace(self, shocks=disc_shocks)

return new_dblock

def __post_init__(self):
for v in self.dynamics:
if isinstance(self.dynamics[v], str):
self.dynamics[v] = math_text_to_lambda(self.dynamics[v])

def get_shocks(self):
return self.shocks

Expand Down Expand Up @@ -246,7 +286,7 @@ def mod_dvf(shock_value_array):


@dataclass
class RBlock:
class RBlock(Block):
"""
A recursive block.
Expand All @@ -257,7 +297,24 @@ class RBlock:

name: str = ""
description: str = ""
blocks: List[DBlock] = field(default_factory=list)
blocks: List[Block] = field(default_factory=list)

def discretize(self, disc_params):
"""
Recursively discretizes all the blocks.
It replaces any DBlocks with new blocks with discretized shocks.
"""
cbs = copy(self.blocks)

for i, b in list(enumerate(cbs)):
if isinstance(b, DBlock):
nb = b.discretize(disc_params)
cbs[i] = nb
elif isinstance(b, RBlock):
b.discretize(disc_params)

# returns a copy of the RBlock with the blocks replaced
return replace(self, blocks=cbs)

def get_shocks(self):
### TODO: Bug in here is causing AttributeError: 'set' object has no attribute 'draw'
Expand Down
2 changes: 1 addition & 1 deletion HARK/models/consumer.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"b": lambda k, R, PermGroFac: k * R / PermGroFac,
"m": lambda b, theta: b + theta,
"c": Control(["m"]),
"a": lambda m, c: m - c,
"a": "m - c",
},
"reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)},
}
Expand Down
26 changes: 26 additions & 0 deletions HARK/parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from sympy.utilities.lambdify import lambdify
from sympy.parsing.sympy_parser import parse_expr


class Expression:
def __init__(self, text):
self.txt
self.expr = parse_expr(text)
self.npf = self.func()

# first derivatives.
self.grad = {
sym.__str__(): self.expr.diff(sym) for sym in list(self.expr.free_symbols)
}

def func(self):
return lambdify(list(self.expr.free_symbols), self.expr, "numpy")


def math_text_to_lambda(text):
"""
Returns a function represented by the given mathematical text.
"""
expr = parse_expr(text)
func = lambdify(list(expr.free_symbols), expr, "numpy")
return func
19 changes: 18 additions & 1 deletion HARK/tests/test_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest

from HARK.distribution import Bernoulli
from HARK.distribution import Bernoulli, DiscreteDistribution
import HARK.model as model
from HARK.model import Control
import HARK.models.consumer as cons
Expand Down Expand Up @@ -44,6 +44,11 @@ def setUp(self):
def test_init(self):
self.assertEqual(self.test_block_A.name, "test block A")

def test_discretize(self):
dbl = self.cblock.discretize({"theta": {"N": 5}})

self.assertEqual(len(dbl.shocks["theta"].pmv), 5)

def test_transition(self):
post = self.cblock.transition(self.dpre, self.dr)

Expand Down Expand Up @@ -79,6 +84,8 @@ def setUp(self):
self.test_block_C = model.DBlock(**test_block_C_data)
self.test_block_D = model.DBlock(**test_block_D_data)

self.cpp = cons.cons_portfolio_problem

def test_init(self):
r_block_tree = model.RBlock(
blocks=[
Expand All @@ -89,3 +96,13 @@ def test_init(self):

r_block_tree.get_shocks()
self.assertEqual(len(r_block_tree.get_shocks()), 3)

def test_discretize(self):
cppd = self.cpp.discretize({"theta": {"N": 5}, "risky_return": {"N": 6}})

self.assertEqual(len(cppd.get_shocks()["theta"].pmv), 5)
self.assertEqual(len(cppd.get_shocks()["risky_return"].pmv), 6)

self.assertFalse(
isinstance(self.cpp.get_shocks()["theta"], DiscreteDistribution)
)

0 comments on commit f50b08d

Please sign in to comment.