From c7d043e196eaa31121a7e133347d23761a99c864 Mon Sep 17 00:00:00 2001 From: charwick <1117120+charwick@users.noreply.github.com> Date: Mon, 11 Dec 2023 18:35:58 -0500 Subject: [PATCH] Simplify parameter ID tuples --- helipad/model.py | 10 ++++++++-- helipad/param.py | 7 ++++++- sample-models/Helicopter-OMO.py | 2 +- sample-models/Helicopter.py | 20 ++++++++++---------- sample-notebooks/helicopter.ipynb | 20 ++++++++++---------- 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/helipad/model.py b/helipad/model.py index 2b42580..5ace8fd 100644 --- a/helipad/model.py +++ b/helipad/model.py @@ -109,8 +109,14 @@ def addButton(self, text: str, func, desc=None): self.shocks.add(text, None, func, 'button', True, desc) def param(self, param, val=None): - """Get or set a model parameter, depending on whether there are two or three arguments. https://helipad.dev/functions/model/param/""" - item = param[2] if isinstance(param, tuple) and len(param)>2 else None + """Get or set a model parameter, depending on whether there is one or two arguments. https://helipad.dev/functions/model/param/""" + if isinstance(param, tuple): + #Deprecated in Helipad 1.7, remove in 1.9 + if len(param) > 1 and param[1] in ['breed', 'good']: + warnings.warn(ï('Three-item parameter tuple identifiers have been deprecated. The second parameter can be removed.'), FutureWarning, 2) + item = param[2] + else: item = param[1] if len(param)>1 else None + else: item = None param = self.params[param[0]] if isinstance(param, tuple) else self.params[param] if val is not None: param.set(val, item) diff --git a/helipad/param.py b/helipad/param.py index 29d6e4a..d26421b 100644 --- a/helipad/param.py +++ b/helipad/param.py @@ -534,7 +534,12 @@ def add(self, name: str, param, valFunc, timerFunc, active: bool=True, desc=None """Register a shock to parameter `param`. `valFunc` takes the current value and returns a new value. `timerFunc` is a function that takes the current model time and returns `bool` (or the string `'button'`, in which case the value is shocked when a control panel button is pressed); `valFunc` will execute whenever `timerFunc` returns `True`. `param` can also be set to `None`, in which case `valFunc` receives the model object. https://helipad.dev/functions/shocks/add/""" if param is None: item=None else: - item = param[2] if isinstance(param, tuple) and len(param)>2 else None #The good or breed whose parameter to shock + if isinstance(param, tuple): + #Deprecated in Helipad 1.7, remove in 1.9 + if len(param) > 1 and param[1] in ['breed', 'good']: + warnings.warn(ï('Three-item parameter tuple identifiers have been deprecated. The second parameter can be removed.'), FutureWarning, 2) + item = param[2] + else: item = param[1] if len(param)>1 else None param = self.model.params[param[0]] if isinstance(param, tuple) else self.model.params[param] super().add(name, self.Shock( diff --git a/sample-models/Helicopter-OMO.py b/sample-models/Helicopter-OMO.py index abdbdc5..5ed2462 100644 --- a/sample-models/Helicopter-OMO.py +++ b/sample-models/Helicopter-OMO.py @@ -290,7 +290,7 @@ def baseAgentInit(agent, model): @heli.hook def agentInit(agent, model): if model.param('num_bank') > 0: - agent.liqPref = model.param(('liqPref', 'breed', agent.breed, 'agent')) + agent.liqPref = model.param(('liqPref', agent.breed)) @heli.hook def agentStep(agent, model, stage): diff --git a/sample-models/Helicopter.py b/sample-models/Helicopter.py index cc2157a..86a7d48 100644 --- a/sample-models/Helicopter.py +++ b/sample-models/Helicopter.py @@ -20,10 +20,10 @@ def __init__(self, breed, id, model): super().__init__(breed, id, model) #Start with equilibrium prices. Not strictly necessary, but it eliminates the burn-in period. See eq. A7 - sm=sum(1/sqrt(model.param(('prod','good',g))) for g in model.goods.nonmonetary) * M0/(model.param('num_agent')*(len(model.goods.nonmonetary)+sum(1+model.param(('rbd','breed',b,'agent')) for b in model.agents['agent'].breeds))) - self.price = {g:sm/(sqrt(model.param(('prod','good',g)))) for g in model.goods.nonmonetary} + sm=sum(1/sqrt(model.param(('prod',g))) for g in model.goods.nonmonetary) * M0/(model.param('num_agent')*(len(model.goods.nonmonetary)+sum(1+model.param(('rbd',b)) for b in model.agents['agent'].breeds))) + self.price = {g:sm/(sqrt(model.param(('prod',g)))) for g in model.goods.nonmonetary} - self.invTarget = {g:model.param(('prod','good',g))*model.param('num_agent')*2 for g in model.goods.nonmonetary} + self.invTarget = {g:model.param(('prod',g))*model.param('num_agent')*2 for g in model.goods.nonmonetary} self.portion = {g:1/(len(model.goods.nonmonetary)) for g in model.goods.nonmonetary} #Capital allocation self.wage = 0 self.cashDemand = 0 @@ -53,11 +53,11 @@ def step(self, stage): for i in self.model.goods.nonmonetary: #Just have a fixed inventory target, but update if params do - self.invTarget = {g:self.model.param(('prod','good',g))*self.model.param('num_agent')*2 for g in self.model.goods.nonmonetary} + self.invTarget = {g:self.model.param(('prod',g))*self.model.param('num_agent')*2 for g in self.model.goods.nonmonetary} #Produce stuff self.portion[i] = (self.model.param('kImmob') * self.portion[i] + self.price[i]/tPrice) / (self.model.param('kImmob') + 1) #Calculate capital allocation - self.stocks[i] = self.stocks[i] + self.portion[i] * labor * self.model.param(('prod', 'good', i)) + self.stocks[i] = self.stocks[i] + self.portion[i] * labor * self.model.param(('prod', i)) #Set prices #Change in the direction of hitting the inventory target @@ -133,10 +133,10 @@ def rbalUpdater(model, var, breed, val): #Takes as input the slider value, outputs b_g. See equation (A8) in the paper. def rbaltodemand(breed): def reporter(model): - rbd = model.param(('rbd', 'breed', breed, 'agent')) + rbd = model.param(('rbd', breed)) beta = rbd/(1+rbd) - return (beta/(1-beta)) * len(model.goods) * sqrt(model.param(('prod','good',AgentGoods[breed]))) / sum(1/sqrt(pr) for pr in model.param(('prod','good')).values()) + return (beta/(1-beta)) * len(model.goods) * sqrt(model.param(('prod',AgentGoods[breed]))) / sum(1/sqrt(pr) for pr in model.param('prod').values()) return reporter @@ -246,10 +246,10 @@ def terminate(model, data): def agentInit(agent, model): agent.store = model.agents['store'][0] agent.item = AgentGoods[agent.breed] - rbd = model.param(('rbd', 'breed', agent.breed, 'agent')) + rbd = model.param(('rbd', agent.breed)) beta = rbd/(rbd+1) agent.utility = CES({'good': 1-beta, 'rbal': beta }, agent.model.param('sigma')) - agent.expCons = model.param(('prod', 'good', agent.item)) + agent.expCons = model.param(('prod', agent.item)) #Set cash endowment to equilibrium value based on parameters. Not strictly necessary but avoids the burn-in period. agent.stocks[model.goods.money] = agent.store.price[agent.item] * rbaltodemand(agent.breed)(heli) @@ -294,7 +294,7 @@ def modelPostStep(model): model.cb.step() #Step the central bank last def shock(v): c = random.normal(v, 4) return c if c >= 1 else 1 - heli.shocks.add('Dwarf real balances', ('rbd','breed','dwarf','agent'), shock, heli.shocks.randn(2), active=False) + heli.shocks.add('Dwarf real balances', ('rbd','dwarf'), shock, heli.shocks.randn(2), active=False) #Shock the money supply def mshock(model): diff --git a/sample-notebooks/helicopter.ipynb b/sample-notebooks/helicopter.ipynb index 4cfd1cf..a9b2c9f 100644 --- a/sample-notebooks/helicopter.ipynb +++ b/sample-notebooks/helicopter.ipynb @@ -49,10 +49,10 @@ "\t\tsuper().__init__(breed, id, model)\n", "\n", "\t\t#Start with equilibrium prices. Not strictly necessary, but it eliminates the burn-in period. See eq. A7\n", - "\t\tsm=sum(1/sqrt(model.param(('prod','good',g))) for g in model.goods.nonmonetary) * M0/(model.param('num_agent')*(len(model.goods.nonmonetary)+sum(1+model.param(('rbd','breed',b,'agent')) for b in model.agents['agent'].breeds)))\n", - "\t\tself.price = {g:sm/(sqrt(model.param(('prod','good',g)))) for g in model.goods.nonmonetary}\n", + "\t\tsm=sum(1/sqrt(model.param(('prod',g))) for g in model.goods.nonmonetary) * M0/(model.param('num_agent')*(len(model.goods.nonmonetary)+sum(1+model.param(('rbd',b,'agent')) for b in model.agents['agent'].breeds)))\n", + "\t\tself.price = {g:sm/(sqrt(model.param(('prod',g)))) for g in model.goods.nonmonetary}\n", "\n", - "\t\tself.invTarget = {g:model.param(('prod','good',g))*model.param('num_agent')*2 for g in model.goods.nonmonetary}\n", + "\t\tself.invTarget = {g:model.param(('prod',g))*model.param('num_agent')*2 for g in model.goods.nonmonetary}\n", "\t\tself.portion = {g:1/(len(model.goods.nonmonetary)) for g in model.goods.nonmonetary} #Capital allocation\n", "\t\tself.wage = 0\n", "\t\tself.cashDemand = 0\n", @@ -82,11 +82,11 @@ "\t\tfor i in self.model.goods.nonmonetary:\n", "\n", "\t\t\t#Just have a fixed inventory target, but update if params do\n", - "\t\t\tself.invTarget = {g:self.model.param(('prod','good',g))*self.model.param('num_agent')*2 for g in self.model.goods.nonmonetary}\n", + "\t\t\tself.invTarget = {g:self.model.param(('prod',g))*self.model.param('num_agent')*2 for g in self.model.goods.nonmonetary}\n", "\n", "\t\t\t#Produce stuff\n", "\t\t\tself.portion[i] = (self.model.param('kImmob') * self.portion[i] + self.price[i]/tPrice) / (self.model.param('kImmob') + 1)\t#Calculate capital allocation\n", - "\t\t\tself.stocks[i] = self.stocks[i] + self.portion[i] * labor * self.model.param(('prod', 'good', i))\n", + "\t\t\tself.stocks[i] = self.stocks[i] + self.portion[i] * labor * self.model.param(('prod', i))\n", "\n", "\t\t\t#Set prices\n", "\t\t\t#Change in the direction of hitting the inventory target\n", @@ -170,10 +170,10 @@ "#Takes as input the slider value, outputs b_g. See equation (A8) in the paper.\n", "def rbaltodemand(breed):\n", "\tdef reporter(model):\n", - "\t\trbd = model.param(('rbd', 'breed', breed, 'agent'))\n", + "\t\trbd = model.param(('rbd', breed, 'agent'))\n", "\t\tbeta = rbd/(1+rbd)\n", "\t\t\n", - "\t\treturn (beta/(1-beta)) * len(model.goods) * sqrt(model.param(('prod','good',AgentGoods[breed]))) / sum(1/sqrt(pr) for pr in model.param(('prod','good')).values())\n", + "\t\treturn (beta/(1-beta)) * len(model.goods) * sqrt(model.param(('prod',AgentGoods[breed]))) / sum(1/sqrt(pr) for pr in model.param('prod').values())\n", "\n", "\treturn reporter" ] @@ -281,10 +281,10 @@ "def agentInit(agent, model):\n", "\tagent.store = model.agents['store'][0]\n", "\tagent.item = AgentGoods[agent.breed]\n", - "\trbd = model.param(('rbd', 'breed', agent.breed, 'agent'))\n", + "\trbd = model.param(('rbd', agent.breed, 'agent'))\n", "\tbeta = rbd/(rbd+1)\n", "\tagent.utility = CES({'good': 1-beta, 'rbal': beta }, agent.model.param('sigma'))\n", - "\tagent.expCons = model.param(('prod', 'good', agent.item))\n", + "\tagent.expCons = model.param(('prod', agent.item))\n", "\t\n", "\t#Set cash endowment to equilibrium value based on parameters. Not strictly necessary but avoids the burn-in period.\n", "\tagent.stocks[model.goods.money] = agent.store.price[agent.item] * rbaltodemand(agent.breed)(heli)\n", @@ -388,7 +388,7 @@ "def shock(v):\n", "\tc = random.normal(v, 4)\n", "\treturn c if c >= 1 else 1\n", - "heli.shocks.add('Dwarf real balances', ('rbd','breed','dwarf','agent'), shock, heli.shocks.randn(2))\n", + "heli.shocks.add('Dwarf real balances', ('rbd','dwarf'), shock, heli.shocks.randn(2))\n", "\n", "def mshock(model):\n", "\tpct = random.normal(3, 15) #High mean to counteract the downward bias of (1-%)(1+%)\n",