Skip to content

Commit

Permalink
Merge pull request #1465 from sbenthall/i1373
Browse files Browse the repository at this point in the history
YAML configuration for consumer portfolio problem, with parser and working tests.
  • Loading branch information
mnwhite authored Jul 31, 2024
2 parents 79f0b16 + feacec7 commit 35c5361
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 0 deletions.
1 change: 1 addition & 0 deletions Documentation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Release Date: TBD
- 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)
- Allows construction of shocks with arguments based on mathematical expressions [#1464](https://github.com/econ-ark/HARK/pull/1464)
- YAML configuration file for the normalized consumption and portolio choice [#1465](https://github.com/econ-ark/HARK/pull/1465)

#### Minor Changes

Expand Down
4 changes: 4 additions & 0 deletions HARK/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ def __post_init__(self):
if isinstance(self.dynamics[v], str):
self.dynamics[v] = math_text_to_lambda(self.dynamics[v])

for r in self.reward:
if isinstance(self.reward[r], str):
self.reward[r] = math_text_to_lambda(self.reward[r])

def get_shocks(self):
return self.shocks

Expand Down
43 changes: 43 additions & 0 deletions HARK/models/consumer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#
# A YAML configuration file for the consumption portfolio problem blocks.
#

calibration:
DiscFac: 0.96
CRRA: 2.0
R: 1.03 # can be overriden by portfolio dynamics
Rfree: 1.03
EqP: 0.02
LivPrb: 0.98
PermGroFac: 1.01
BoroCnstArt: None
TranShkStd: 0.1
RiskyStd: 0.1

blocks:
- &cons_normalized
name: consumption normalized
shocks:
live: !Bernoulli
p: LivPrb
theta: !MeanOneLogNormal
sigma: TranShkStd
dynamics:
b: k * R / PermGroFac
m: b + theta
c: !Control
info: m
a: m - c

reward:
u: c ** (1 - CRRA) / (1 - CRRA)
- &portfolio_choice
name: portfolio choice
shocks:
risky_return: !Lognormal
mean: Rfree + EqP
std: RiskyStd
dynamics:
stigma: !Control
info: a
R: Rfree + (risky_return - Rfree) * stigma
41 changes: 41 additions & 0 deletions HARK/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
from HARK.distribution import Bernoulli, Lognormal, MeanOneLogNormal
from sympy.utilities.lambdify import lambdify
from sympy.parsing.sympy_parser import parse_expr
import yaml


class ControlToken:
"""
Represents a parsed Control variable.
"""

def __init__(self, args):
pass


class Expression:
Expand All @@ -17,10 +28,40 @@ def func(self):
return lambdify(list(self.expr.free_symbols), self.expr, "numpy")


def tuple_constructor_from_class(cls):
def constructor(loader, node):
value = loader.construct_mapping(node)
return (cls, value)

return constructor


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


def harklang_loader():
"""
A PyYAML loader that supports tags for HARKLang,
such as random variables and model tags.
"""
loader = yaml.SafeLoader
yaml.SafeLoader.add_constructor(
"!Bernoulli", tuple_constructor_from_class(Bernoulli)
)
yaml.SafeLoader.add_constructor(
"!MeanOneLogNormal", tuple_constructor_from_class(MeanOneLogNormal)
)
yaml.SafeLoader.add_constructor(
"!Lognormal", tuple_constructor_from_class(Lognormal)
)
yaml.SafeLoader.add_constructor(
"!Control", tuple_constructor_from_class(ControlToken)
)

return loader
33 changes: 33 additions & 0 deletions HARK/tests/test_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os
import unittest

import HARK.model as model
import HARK.parser as parser
import yaml


class test_consumption_parsing(unittest.TestCase):
def setUp(self):
this_file_path = os.path.dirname(__file__)
consumer_yaml_path = os.path.join(this_file_path, "../models/consumer.yaml")

self.consumer_yaml_file = open(consumer_yaml_path, "r")

def test_parse(self):
self.consumer_yaml_file

config = yaml.load(self.consumer_yaml_file, Loader=parser.harklang_loader())

self.assertEqual(config["calibration"]["DiscFac"], 0.96)
self.assertEqual(config["blocks"][0]["name"], "consumption normalized")

## construct and test the consumption block
cons_norm_block = model.DBlock(**config["blocks"][0])
cons_norm_block.construct_shocks(config["calibration"])
cons_norm_block.discretize({"theta": {"N": 5}})
self.assertEqual(cons_norm_block.calc_reward({"c": 1, "CRRA": 2})["u"], -1.0)

## construct and test the portfolio block
portfolio_block = model.DBlock(**config["blocks"][1])
portfolio_block.construct_shocks(config["calibration"])
portfolio_block.discretize({"risky_return": {"N": 5}})
1 change: 1 addition & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ networkx>=3
numba<0.60.0
numpy>=1.23
pandas>=1.5
pyyaml>=6.0
quantecon
scipy>=1.10
seaborn>=0.12
Expand Down

0 comments on commit 35c5361

Please sign in to comment.