From 4c58ca7f311f27420265feae518b1192dd844dbb Mon Sep 17 00:00:00 2001 From: sb Date: Sat, 22 Jun 2024 10:17:32 -0400 Subject: [PATCH 1/5] only source inputs to shocks from within calibrations scope --- HARK/models/consumer.py | 17 ++++++++--------- HARK/models/fisher.py | 8 ++------ HARK/models/perfect_foresight.py | 9 ++------- HARK/models/perfect_foresight_normalized.py | 14 ++++---------- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/HARK/models/consumer.py b/HARK/models/consumer.py index bee7b102a..08d4660c6 100644 --- a/HARK/models/consumer.py +++ b/HARK/models/consumer.py @@ -8,9 +8,7 @@ """ # TODO: Include these in calibration, then construct shocks -LivPrb = 0.98 -TranShkStd = 0.1 -RiskyStd = 0.1 + calibration = { "DiscFac": 0.96, @@ -18,17 +16,18 @@ "R": 1.03, # note: this can be overriden by the portfolio dynamics "Rfree": 1.03, "EqP": 0.02, - "LivPrb": LivPrb, + "LivPrb": 0.98, "PermGroFac": 1.01, "BoroCnstArt": None, + "TranShkStd" : 0.1 } consumption_block = DBlock( **{ "name": "consumption", "shocks": { - "live": Bernoulli(p=LivPrb), # Move to tick or mortality block? - "theta": MeanOneLogNormal(sigma=TranShkStd), + "live": Bernoulli(p=calibration["LivPrb"]), # Move to tick or mortality block? + "theta": MeanOneLogNormal(sigma=calibration["TranShkStd"]), }, "dynamics": { "b": lambda k, R: k * R, @@ -46,8 +45,8 @@ **{ "name": "consumption normalized", "shocks": { - "live": Bernoulli(p=LivPrb), # Move to tick or mortality block? - "theta": MeanOneLogNormal(sigma=TranShkStd), + "live": Bernoulli(p=calibration["LivPrb"]), # Move to tick or mortality block? + "theta": MeanOneLogNormal(sigma=calibration["TranShkStd"]), }, "dynamics": { "b": lambda k, R, PermGroFac: k * R / PermGroFac, @@ -64,7 +63,7 @@ "name": "portfolio", "shocks": { "risky_return": Lognormal.from_mean_std( - calibration["Rfree"] + calibration["EqP"], RiskyStd + calibration["Rfree"] + calibration["EqP"], 0.1 # RiskyStd ) }, "dynamics": { diff --git a/HARK/models/fisher.py b/HARK/models/fisher.py index aae27cbf9..fd5d94c4b 100644 --- a/HARK/models/fisher.py +++ b/HARK/models/fisher.py @@ -4,14 +4,10 @@ from HARK.model import Control, DBlock -# This way of distributing parameters across the scope is clunky -# Can be handled better if parsed from a YAML file, probably -# But it would be better to have a more graceful Python version as well. -CRRA = (2.0,) calibration = { "DiscFac": 0.96, - "CRRA": CRRA, + "CRRA": (2.0,), "Rfree": 1.03, "y": [1.0, 1.0], "BoroCnstArt": None, @@ -25,6 +21,6 @@ "c": Control(["m"]), "a": lambda m, c: m - c, }, - "reward": {"u": lambda c: c ** (1 - CRRA) / (1 - CRRA)}, + "reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)}, } ) diff --git a/HARK/models/perfect_foresight.py b/HARK/models/perfect_foresight.py index 22b02ad83..5f2c0de8e 100644 --- a/HARK/models/perfect_foresight.py +++ b/HARK/models/perfect_foresight.py @@ -1,16 +1,11 @@ from HARK.distribution import Bernoulli from HARK.model import Control, DBlock -# This way of distributing parameters across the scope is clunky -# Can be handled better if parsed from a YAML file, probably -# But it would be better to have a more graceful Python version as well. -LivPrb = 0.98 - calibration = { "DiscFac": 0.96, "CRRA": 2.0, "Rfree": 1.03, - "LivPrb": LivPrb, + "LivPrb": 0.98, "PermGroFac": 1.01, "BoroCnstArt": None, } @@ -19,7 +14,7 @@ **{ "name": "consumption", "shocks": { - "live": Bernoulli(p=LivPrb), + "live": Bernoulli(p=calibration["LivPrb"]), }, "dynamics": { "y": lambda p: p, diff --git a/HARK/models/perfect_foresight_normalized.py b/HARK/models/perfect_foresight_normalized.py index 544a14fbc..52745ab26 100644 --- a/HARK/models/perfect_foresight_normalized.py +++ b/HARK/models/perfect_foresight_normalized.py @@ -1,17 +1,11 @@ from HARK.distribution import Bernoulli from HARK.model import Control, DBlock -# This way of distributing parameters across the scope is clunky -# Can be handled better if parsed from a YAML file, probably -# But it would be better to have a more graceful Python version as well. -CRRA = (2.0,) -LivPrb = 0.98 - calibration = { "DiscFac": 0.96, - "CRRA": CRRA, + "CRRA": (2.0,), "Rfree": 1.03, - "LivPrb": LivPrb, + "LivPrb": 0.98, "PermGroFac": 1.01, "BoroCnstArt": None, } @@ -19,7 +13,7 @@ block = DBlock( **{ "shocks": { - "live": Bernoulli(p=LivPrb), + "live": Bernoulli(p=calibration["LivPrb"]), }, "dynamics": { "p": lambda PermGroFac, p: PermGroFac * p, @@ -29,6 +23,6 @@ "c_nrm": Control(["m_nrm"]), "a_nrm": lambda m_nrm, c_nrm: m_nrm - c_nrm, }, - "reward": {"u": lambda c: c ** (1 - CRRA) / (1 - CRRA)}, + "reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)}, } ) From 5f998110990cdd44c36cb576643f22010b53f9f0 Mon Sep 17 00:00:00 2001 From: sb Date: Sat, 22 Jun 2024 13:34:21 -0400 Subject: [PATCH 2/5] ruff fixes --- HARK/models/consumer.py | 13 ++- .../ModelConfiguration/consumer_example.py | 86 +++++++++++++++++++ 2 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 examples/ModelConfiguration/consumer_example.py diff --git a/HARK/models/consumer.py b/HARK/models/consumer.py index c2e36ad30..b1dab14e8 100644 --- a/HARK/models/consumer.py +++ b/HARK/models/consumer.py @@ -19,14 +19,16 @@ "LivPrb": 0.98, "PermGroFac": 1.01, "BoroCnstArt": None, - "TranShkStd" : 0.1 + "TranShkStd": 0.1, } consumption_block = DBlock( **{ "name": "consumption", "shocks": { - "live": Bernoulli(p=calibration["LivPrb"]), # Move to tick or mortality block? + "live": Bernoulli( + p=calibration["LivPrb"] + ), # Move to tick or mortality block? "theta": MeanOneLogNormal(sigma=calibration["TranShkStd"]), }, "dynamics": { @@ -45,7 +47,9 @@ **{ "name": "consumption normalized", "shocks": { - "live": Bernoulli(p=calibration["LivPrb"]), # Move to tick or mortality block? + "live": Bernoulli( + p=calibration["LivPrb"] + ), # Move to tick or mortality block? "theta": MeanOneLogNormal(sigma=calibration["TranShkStd"]), }, "dynamics": { @@ -63,7 +67,8 @@ "name": "portfolio", "shocks": { "risky_return": Lognormal.from_mean_std( - calibration["Rfree"] + calibration["EqP"], 0.1 # RiskyStd + calibration["Rfree"] + calibration["EqP"], + 0.1, # RiskyStd ) }, "dynamics": { diff --git a/examples/ModelConfiguration/consumer_example.py b/examples/ModelConfiguration/consumer_example.py new file mode 100644 index 000000000..dc3343b0f --- /dev/null +++ b/examples/ModelConfiguration/consumer_example.py @@ -0,0 +1,86 @@ +from HARK.distribution import Bernoulli, Lognormal, MeanOneLogNormal +from HARK.model import Control + +""" +This is an example of what a full configuration looks like for: + +(a) a true model + +(b) approximations made to it to enable efficient computation + +(c) additional parameters needed to solve and simulate the model. + +This file shows the model configuraiton in Python. + +Another file will show a model configuration in YAML, which gets parsed into +a data structure much like this one. +""" + +model_config = { + "calibration": { + "DiscFac": 0.96, + "CRRA": 2.0, + "R": 1.03, # note: this can be overriden by the portfolio dynamics + "Rfree": 1.03, + "EqP": 0.02, + "LivPrb": 0.98, + "PermGroFac": 1.01, + "BoroCnstArt": None, + "TranShkStd": 0.1, + }, + "agent": { + "size": 10, # the population size. Is this only for the simulation? + "blocks": [ + { + "name": "consumption normalized", + "shocks": { + "live": [Bernoulli, {"p": "LivPrb"}], + "theta": [MeanOneLogNormal, {"sigma": "TranShkStd"}], + }, + "dynamics": { + "b": lambda k, R, PermGroFac: k * R / PermGroFac, + "m": lambda b, theta: b + theta, + "c": Control(["m"]), + "a": lambda m, c: m - c, + }, + "reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)}, + }, + { + "name": "portfolio", + "shocks": { + "risky_return": [ + Lognormal.from_mean_std, + {"mean": "Rfree + EqP", "std": 0.1}, + ] + }, + "dynamics": { + "stigma": Control(["a"]), + "R": lambda stigma, Rfree, risky_return: Rfree + + (risky_return - Rfree) * stigma, + }, + }, + { + "name": "tick", + "dynamics": { + "k": lambda a: a, + }, + }, + ], + }, + "approximation": { + "theta": {"N": 5}, + "risky_return": {"N": 5}, + }, + "workflows": [ + {"action": "solve", "algorithm": "vbi"}, + { + "action": "simulate", + "initialization": { # initial values # type: ignore + "k": Lognormal(-6, 0), + "R": 1.03, + }, + "population": 10, # ten agents + "T": 20, # total number of simulated periods + }, + ], +} From 73483313017d8fb9f89ca1e7854ae950a52468b4 Mon Sep 17 00:00:00 2001 From: sb Date: Tue, 2 Jul 2024 10:51:27 -0400 Subject: [PATCH 3/5] adding YAML file example --- .../ModelConfiguration/consumer_example.py | 96 +++++++++---------- .../ModelConfiguration/consumer_example.yaml | 90 +++++++++++++++++ 2 files changed, 138 insertions(+), 48 deletions(-) create mode 100644 examples/ModelConfiguration/consumer_example.yaml diff --git a/examples/ModelConfiguration/consumer_example.py b/examples/ModelConfiguration/consumer_example.py index dc3343b0f..7b7d770b2 100644 --- a/examples/ModelConfiguration/consumer_example.py +++ b/examples/ModelConfiguration/consumer_example.py @@ -17,6 +17,9 @@ """ model_config = { + "agents": { + "consumer" : {"count" : 10} + }, "calibration": { "DiscFac": 0.96, "CRRA": 2.0, @@ -28,59 +31,56 @@ "BoroCnstArt": None, "TranShkStd": 0.1, }, - "agent": { - "size": 10, # the population size. Is this only for the simulation? - "blocks": [ - { - "name": "consumption normalized", - "shocks": { - "live": [Bernoulli, {"p": "LivPrb"}], - "theta": [MeanOneLogNormal, {"sigma": "TranShkStd"}], - }, - "dynamics": { - "b": lambda k, R, PermGroFac: k * R / PermGroFac, - "m": lambda b, theta: b + theta, - "c": Control(["m"]), - "a": lambda m, c: m - c, - }, - "reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)}, - }, - { - "name": "portfolio", - "shocks": { - "risky_return": [ - Lognormal.from_mean_std, - {"mean": "Rfree + EqP", "std": 0.1}, - ] - }, - "dynamics": { - "stigma": Control(["a"]), - "R": lambda stigma, Rfree, risky_return: Rfree - + (risky_return - Rfree) * stigma, - }, + "blocks": [ + { + "name": "consumption normalized", + "shocks": { + "live": [Bernoulli, {"p": "LivPrb"}], + "theta": [MeanOneLogNormal, {"sigma": "TranShkStd"}], + }, + "dynamics": { + "b": lambda k, R, PermGroFac: k * R / PermGroFac, + "m": lambda b, theta: b + theta, + "c": Control(["m"]), + "a": lambda m, c: m - c, + }, + "reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)}, + }, + { + "name": "portfolio", + "shocks": { + "risky_return": [ + Lognormal.from_mean_std, + {"mean": "Rfree + EqP", "std": 0.1}, + ] }, - { - "name": "tick", - "dynamics": { - "k": lambda a: a, + "dynamics": { + "stigma": Control(["a"]), + "R": lambda stigma, Rfree, risky_return: Rfree + + (risky_return - Rfree) * stigma, }, + }, + { + "name": "tick", + "dynamics": { + "k": lambda a: a, }, - ], - }, + }, + ], "approximation": { "theta": {"N": 5}, "risky_return": {"N": 5}, }, - "workflows": [ - {"action": "solve", "algorithm": "vbi"}, - { - "action": "simulate", - "initialization": { # initial values # type: ignore - "k": Lognormal(-6, 0), - "R": 1.03, - }, - "population": 10, # ten agents - "T": 20, # total number of simulated periods - }, - ], + #"workflows": [ + # {"action": "solve", "algorithm": "vbi"}, + # { + # "action": "simulate", + # "initialization": { # initial values # type: ignore + # "k": Lognormal(-6, 0), + # "R": 1.03, + # }, + # "population": 10, # ten agents + # "T": 20, # total number of simulated periods + # }, + #], } diff --git a/examples/ModelConfiguration/consumer_example.yaml b/examples/ModelConfiguration/consumer_example.yaml new file mode 100644 index 000000000..c9d1832c6 --- /dev/null +++ b/examples/ModelConfiguration/consumer_example.yaml @@ -0,0 +1,90 @@ +model: &consumer_portfolio + agents: + consumer: + count: 100 # a model with 100 consumers + + calibration: + # - DiscFac: 0.96 put this in the solution section + - CRRA: 2.0 + - R: 1.03 # note: this can be overriden by the portfolio dynamics + - Rfree: 1.03 + - EqP: 0.02 + - PermGroFac: 1.01 + - TranShkStd: 0.1 + + blocks: + - &cons # block definition begins with name of the block. Alias + name: consumption_normalized + agent: consumer + shocks: + theta: !MeanOneLogNormal + sigma: TranShkStd + + dynamics: + b: k * R / PermGroFac + m: b + theta, + c: !Control + inset: m + constraints: + - c < m + a: m - c + reward: + u: c ** (1 - CRRA) / (1 - CRRA)} + + - &port + name: portfolio + agent: consumer + shocks: + risky_return: !Lognormal + mean: Rfree + EqP + std: 0.1 + + dynamics: + stigma: Control(["a"]), + R: Rfree + (risky_return - Rfree) * stigma + + structure: # the sequence of blocks + - *cons + - *port + - twist: # shorthand fo renaming a variable + m: k + - tick # pass discrete time step + # defaults to infinite horizion problem + +approximation: &approx # right syntax? + theta: # to discretize a continuous probability distribution + N : 5 + method: equiprobable + risky_return: + N: 5 + method: gauss-hermite + m: !Linspace # grid over continuous state space + start: 0 + stop: 10 + num: 25 + + +strategy: # these configure constructors/solvers of decision rules + # for model control variables + - &test_policy_profile + model: *consumer_portfolio + method: hard_rule + rules: + - c : m / 2 + - stigma : a / (5 + a) + + - &approx_best_policy_profile + model: *consumer_portfolio + method: policy_iteration + approximation: *approx + discount: 0.98 # A discount factor applied at each tick + +simulation: !MonteCarlo + initialization: + k: !Lognormal + mean: 1 + sigma: 0 + R: 1.03 + approximation: *approx # use the same shock discretization + policy_profile: *approx_best_policy_profile + periods: 100 \ No newline at end of file From 9718464106ed929c81f159336a5fc1e06b9c1ad1 Mon Sep 17 00:00:00 2001 From: sb Date: Thu, 14 Nov 2024 10:55:26 -0500 Subject: [PATCH 4/5] ruff fixes --- .../ModelConfiguration/consumer_example.py | 28 +++++++++---------- .../ModelConfiguration/consumer_example.yaml | 14 +++++----- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/examples/ModelConfiguration/consumer_example.py b/examples/ModelConfiguration/consumer_example.py index 7b7d770b2..8367c3c11 100644 --- a/examples/ModelConfiguration/consumer_example.py +++ b/examples/ModelConfiguration/consumer_example.py @@ -17,9 +17,7 @@ """ model_config = { - "agents": { - "consumer" : {"count" : 10} - }, + "agents": {"consumer": {"count": 10}}, "calibration": { "DiscFac": 0.96, "CRRA": 2.0, @@ -35,15 +33,15 @@ { "name": "consumption normalized", "shocks": { - "live": [Bernoulli, {"p": "LivPrb"}], - "theta": [MeanOneLogNormal, {"sigma": "TranShkStd"}], - }, - "dynamics": { - "b": lambda k, R, PermGroFac: k * R / PermGroFac, - "m": lambda b, theta: b + theta, - "c": Control(["m"]), - "a": lambda m, c: m - c, - }, + "live": [Bernoulli, {"p": "LivPrb"}], + "theta": [MeanOneLogNormal, {"sigma": "TranShkStd"}], + }, + "dynamics": { + "b": lambda k, R, PermGroFac: k * R / PermGroFac, + "m": lambda b, theta: b + theta, + "c": Control(["m"]), + "a": lambda m, c: m - c, + }, "reward": {"u": lambda c, CRRA: c ** (1 - CRRA) / (1 - CRRA)}, }, { @@ -58,7 +56,7 @@ "stigma": Control(["a"]), "R": lambda stigma, Rfree, risky_return: Rfree + (risky_return - Rfree) * stigma, - }, + }, }, { "name": "tick", @@ -71,7 +69,7 @@ "theta": {"N": 5}, "risky_return": {"N": 5}, }, - #"workflows": [ + # "workflows": [ # {"action": "solve", "algorithm": "vbi"}, # { # "action": "simulate", @@ -82,5 +80,5 @@ # "population": 10, # ten agents # "T": 20, # total number of simulated periods # }, - #], + # ], } diff --git a/examples/ModelConfiguration/consumer_example.yaml b/examples/ModelConfiguration/consumer_example.yaml index c9d1832c6..2de7621ad 100644 --- a/examples/ModelConfiguration/consumer_example.yaml +++ b/examples/ModelConfiguration/consumer_example.yaml @@ -13,7 +13,7 @@ model: &consumer_portfolio - TranShkStd: 0.1 blocks: - - &cons # block definition begins with name of the block. Alias + - &cons # block definition begins with name of the block. Alias name: consumption_normalized agent: consumer shocks: @@ -30,7 +30,7 @@ model: &consumer_portfolio a: m - c reward: u: c ** (1 - CRRA) / (1 - CRRA)} - + - &port name: portfolio agent: consumer @@ -42,7 +42,7 @@ model: &consumer_portfolio dynamics: stigma: Control(["a"]), R: Rfree + (risky_return - Rfree) * stigma - + structure: # the sequence of blocks - *cons - *port @@ -55,15 +55,15 @@ approximation: &approx # right syntax? theta: # to discretize a continuous probability distribution N : 5 method: equiprobable - risky_return: + risky_return: N: 5 method: gauss-hermite m: !Linspace # grid over continuous state space start: 0 stop: 10 num: 25 - - + + strategy: # these configure constructors/solvers of decision rules # for model control variables - &test_policy_profile @@ -87,4 +87,4 @@ simulation: !MonteCarlo R: 1.03 approximation: *approx # use the same shock discretization policy_profile: *approx_best_policy_profile - periods: 100 \ No newline at end of file + periods: 100 From 0224a6d090868e8af50dc09727c53b1d3364e62a Mon Sep 17 00:00:00 2001 From: sb Date: Thu, 14 Nov 2024 11:02:06 -0500 Subject: [PATCH 5/5] CHANGELOG for #1463 ModelConfiguration example --- Documentation/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/CHANGELOG.md b/Documentation/CHANGELOG.md index f9d27bb03..55e52ae29 100644 --- a/Documentation/CHANGELOG.md +++ b/Documentation/CHANGELOG.md @@ -28,6 +28,7 @@ Release Date: TBD - Changes the behavior of make_lognormal_RiskyDstn so that the standard deviation represents the standard deviation of log(returns) - Adds detailed parameter and latex documentation to most models. - Add PermGroFac constructor that explicitly combines idiosyncratic and aggregate sources of growth. [1489](https://github.com/econ-ark/HARK/pull/1489) +- Adds an example, ModelConfiguration, which demonstrates how a consumption saving "true" model, approximations, solution paramaters, and simulation parameters can be defined in one YAML file. [1463](https://github.com/econ-ark/HARK/pull/1463) ### 0.15.1