diff --git a/Documentation/CHANGELOG.md b/Documentation/CHANGELOG.md index 85c087a03..634ef4eef 100644 --- a/Documentation/CHANGELOG.md +++ b/Documentation/CHANGELOG.md @@ -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 diff --git a/HARK/model.py b/HARK/model.py index 7f92bb2e6..d20e3341a 100644 --- a/HARK/model.py +++ b/HARK/model.py @@ -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 diff --git a/HARK/models/consumer.yaml b/HARK/models/consumer.yaml new file mode 100644 index 000000000..a7a315e45 --- /dev/null +++ b/HARK/models/consumer.yaml @@ -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 diff --git a/HARK/parser.py b/HARK/parser.py index 61fcba539..3f21ff7f9 100644 --- a/HARK/parser.py +++ b/HARK/parser.py @@ -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: @@ -17,6 +28,14 @@ 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. @@ -24,3 +43,25 @@ def math_text_to_lambda(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 diff --git a/HARK/tests/test_parser.py b/HARK/tests/test_parser.py new file mode 100644 index 000000000..0a49e4171 --- /dev/null +++ b/HARK/tests/test_parser.py @@ -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}}) diff --git a/requirements/base.txt b/requirements/base.txt index 05ac8eb76..7bcfe9f62 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -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